mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
49aa66b597
Truncating the current log file while a reader is still reading through it results in log lines getting missed. In contrast, rotating the file allows readers who have the file open can continue to read from it undisturbed. Rotating frees up the file name for the logger to create a new file in its place. This remains true even when max-file=1; the current log file is "rotated" from its name without giving it a new one. On POSIXy filesystem APIs, rotating the last file is straightforward: unlink()ing a file name immediately deletes the name from the filesystem and makes it available for reuse, even if processes have the file open at the time. Windows on the other hand only makes the name available for reuse once the file itself is deleted, which only happens when no processes have it open. To reuse the file name while the file is still in use, the file needs to be renamed. So that's what we have to do: rotate the file to a temporary name before marking it for deletion. Signed-off-by: Cory Snider <csnider@mirantis.com>
252 lines
7 KiB
Go
252 lines
7 KiB
Go
package loggerutils
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
func open(name string) (*os.File, error) {
|
|
return openFile(name, os.O_RDONLY, 0)
|
|
}
|
|
|
|
func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|
if name == "" {
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
|
}
|
|
h, err := syscallOpen(fixLongPath(name), flag|syscall.O_CLOEXEC, syscallMode(perm))
|
|
if err != nil {
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
return os.NewFile(uintptr(h), name), nil
|
|
}
|
|
|
|
// syscallOpen is copied from syscall.Open but is modified to
|
|
// always open a file with FILE_SHARE_DELETE
|
|
func syscallOpen(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
|
|
if len(path) == 0 {
|
|
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
|
|
}
|
|
|
|
pathp, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return syscall.InvalidHandle, err
|
|
}
|
|
var access uint32
|
|
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
|
case syscall.O_RDONLY:
|
|
access = syscall.GENERIC_READ
|
|
case syscall.O_WRONLY:
|
|
access = syscall.GENERIC_WRITE
|
|
case syscall.O_RDWR:
|
|
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
|
|
}
|
|
if mode&syscall.O_CREAT != 0 {
|
|
access |= syscall.GENERIC_WRITE
|
|
}
|
|
if mode&syscall.O_APPEND != 0 {
|
|
access &^= syscall.GENERIC_WRITE
|
|
access |= syscall.FILE_APPEND_DATA
|
|
}
|
|
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
|
|
var sa *syscall.SecurityAttributes
|
|
if mode&syscall.O_CLOEXEC == 0 {
|
|
sa = makeInheritSa()
|
|
}
|
|
var createmode uint32
|
|
switch {
|
|
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
|
|
createmode = syscall.CREATE_NEW
|
|
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
|
|
createmode = syscall.CREATE_ALWAYS
|
|
case mode&syscall.O_CREAT == syscall.O_CREAT:
|
|
createmode = syscall.OPEN_ALWAYS
|
|
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
|
|
createmode = syscall.TRUNCATE_EXISTING
|
|
default:
|
|
createmode = syscall.OPEN_EXISTING
|
|
}
|
|
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL, 0)
|
|
return h, e
|
|
}
|
|
|
|
func makeInheritSa() *syscall.SecurityAttributes {
|
|
var sa syscall.SecurityAttributes
|
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
|
sa.InheritHandle = 1
|
|
return &sa
|
|
}
|
|
|
|
// fixLongPath returns the extended-length (\\?\-prefixed) form of
|
|
// path when needed, in order to avoid the default 260 character file
|
|
// path limit imposed by Windows. If path is not easily converted to
|
|
// the extended-length form (for example, if path is a relative path
|
|
// or contains .. elements), or is short enough, fixLongPath returns
|
|
// path unmodified.
|
|
//
|
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
|
|
//
|
|
// Copied from os.OpenFile
|
|
func fixLongPath(path string) string {
|
|
// Do nothing (and don't allocate) if the path is "short".
|
|
// Empirically (at least on the Windows Server 2013 builder),
|
|
// the kernel is arbitrarily okay with < 248 bytes. That
|
|
// matches what the docs above say:
|
|
// "When using an API to create a directory, the specified
|
|
// path cannot be so long that you cannot append an 8.3 file
|
|
// name (that is, the directory name cannot exceed MAX_PATH
|
|
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
|
|
//
|
|
// The MSDN docs appear to say that a normal path that is 248 bytes long
|
|
// will work; empirically the path must be less then 248 bytes long.
|
|
if len(path) < 248 {
|
|
// Don't fix. (This is how Go 1.7 and earlier worked,
|
|
// not automatically generating the \\?\ form)
|
|
return path
|
|
}
|
|
|
|
// The extended form begins with \\?\, as in
|
|
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
|
|
// The extended form disables evaluation of . and .. path
|
|
// elements and disables the interpretation of / as equivalent
|
|
// to \. The conversion here rewrites / to \ and elides
|
|
// . elements as well as trailing or duplicate separators. For
|
|
// simplicity it avoids the conversion entirely for relative
|
|
// paths or paths containing .. elements. For now,
|
|
// \\server\share paths are not converted to
|
|
// \\?\UNC\server\share paths because the rules for doing so
|
|
// are less well-specified.
|
|
if len(path) >= 2 && path[:2] == `\\` {
|
|
// Don't canonicalize UNC paths.
|
|
return path
|
|
}
|
|
if !isAbs(path) {
|
|
// Relative path
|
|
return path
|
|
}
|
|
|
|
const prefix = `\\?`
|
|
|
|
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
|
|
copy(pathbuf, prefix)
|
|
n := len(path)
|
|
r, w := 0, len(prefix)
|
|
for r < n {
|
|
switch {
|
|
case os.IsPathSeparator(path[r]):
|
|
// empty block
|
|
r++
|
|
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
|
|
// /./
|
|
r++
|
|
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
|
|
// /../ is currently unhandled
|
|
return path
|
|
default:
|
|
pathbuf[w] = '\\'
|
|
w++
|
|
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
|
|
pathbuf[w] = path[r]
|
|
w++
|
|
}
|
|
}
|
|
}
|
|
// A drive's root directory needs a trailing \
|
|
if w == len(`\\?\c:`) {
|
|
pathbuf[w] = '\\'
|
|
w++
|
|
}
|
|
return string(pathbuf[:w])
|
|
}
|
|
|
|
// copied from os package for os.OpenFile
|
|
func syscallMode(i os.FileMode) (o uint32) {
|
|
o |= uint32(i.Perm())
|
|
if i&os.ModeSetuid != 0 {
|
|
o |= syscall.S_ISUID
|
|
}
|
|
if i&os.ModeSetgid != 0 {
|
|
o |= syscall.S_ISGID
|
|
}
|
|
if i&os.ModeSticky != 0 {
|
|
o |= syscall.S_ISVTX
|
|
}
|
|
// No mapping for Go's ModeTemporary (plan9 only).
|
|
return
|
|
}
|
|
|
|
func isAbs(path string) (b bool) {
|
|
v := volumeName(path)
|
|
if v == "" {
|
|
return false
|
|
}
|
|
path = path[len(v):]
|
|
if path == "" {
|
|
return false
|
|
}
|
|
return os.IsPathSeparator(path[0])
|
|
}
|
|
|
|
func volumeName(path string) (v string) {
|
|
if len(path) < 2 {
|
|
return ""
|
|
}
|
|
// with drive letter
|
|
c := path[0]
|
|
if path[1] == ':' &&
|
|
('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
|
|
'A' <= c && c <= 'Z') {
|
|
return path[:2]
|
|
}
|
|
// is it UNC
|
|
if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) &&
|
|
!os.IsPathSeparator(path[2]) && path[2] != '.' {
|
|
// first, leading `\\` and next shouldn't be `\`. its server name.
|
|
for n := 3; n < l-1; n++ {
|
|
// second, next '\' shouldn't be repeated.
|
|
if os.IsPathSeparator(path[n]) {
|
|
n++
|
|
// third, following something characters. its share name.
|
|
if !os.IsPathSeparator(path[n]) {
|
|
if path[n] == '.' {
|
|
break
|
|
}
|
|
for ; n < l; n++ {
|
|
if os.IsPathSeparator(path[n]) {
|
|
break
|
|
}
|
|
}
|
|
return path[:n]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func unlink(name string) error {
|
|
// Rename the file before deleting it so that the original name is freed
|
|
// up to be reused, even while there are still open FILE_SHARE_DELETE
|
|
// file handles. Emulate POSIX unlink() semantics, essentially.
|
|
name, err := filepath.Abs(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dir, fname := filepath.Split(name)
|
|
f, err := os.CreateTemp(dir, fname+".*.deleted")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmpname := f.Name()
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
err = os.Rename(name, tmpname)
|
|
rmErr := os.Remove(tmpname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return rmErr
|
|
}
|