diff --git a/tail.go b/tail.go index f3c6934..cedbb23 100644 --- a/tail.go +++ b/tail.go @@ -104,7 +104,7 @@ func TailFile(filename string, config Config) (*Tail, error) { if t.MustExist { var err error - t.file, err = os.Open(t.Filename) + t.file, err = OpenFile(t.Filename) if err != nil { return nil, err } @@ -149,7 +149,7 @@ func (tail *Tail) reopen() error { } for { var err error - tail.file, err = os.Open(tail.Filename) + tail.file, err = OpenFile(tail.Filename) if err != nil { if os.IsNotExist(err) { tail.Logger.Printf("Waiting for %s to appear...", tail.Filename) diff --git a/tail_linux.go b/tail_linux.go new file mode 100644 index 0000000..60160fd --- /dev/null +++ b/tail_linux.go @@ -0,0 +1,11 @@ +// +build linux + +package tail + +import ( + "os" +) + +func OpenFile(name string) (file *os.File, err error) { + return os.Open(name) +} diff --git a/tail_windows.go b/tail_windows.go new file mode 100644 index 0000000..5a26372 --- /dev/null +++ b/tail_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package tail + +import ( + "github.com/ActiveState/tail/winfile" + "os" +) + +func OpenFile(name string) (file *os.File, err error) { + return winfile.OpenFile(name, os.O_RDONLY, 0) +} diff --git a/watch/polling.go b/watch/polling.go index 3fe50af..10a17eb 100644 --- a/watch/polling.go +++ b/watch/polling.go @@ -51,6 +51,8 @@ func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, origFi os.FileInfo) *Fi go func() { defer changes.Close() + var retry int = 0 + prevSize := fw.Size for { select { @@ -67,6 +69,11 @@ func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, origFi os.FileInfo) *Fi changes.NotifyDeleted() return } + + if permissionErrorRetry(err, &retry) { + continue + } + // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } diff --git a/watch/polling_linux.go b/watch/polling_linux.go new file mode 100644 index 0000000..dd0df1c --- /dev/null +++ b/watch/polling_linux.go @@ -0,0 +1,9 @@ +// Copyright (c) 2013 ActiveState Software Inc. All rights reserved. +// +build linux + +package watch + +func permissionErrorRetry(err error, retry *int) bool { + // No need for this on linux, don't retry + return false +} diff --git a/watch/polling_windows.go b/watch/polling_windows.go new file mode 100644 index 0000000..9ffd23d --- /dev/null +++ b/watch/polling_windows.go @@ -0,0 +1,18 @@ +// +build windows + +package watch + +import ( + "os" +) + +const permissionDeniedRetryCount int = 5 + +func permissionErrorRetry(err error, retry *int) bool { + if os.IsPermission(err) && *retry < permissionDeniedRetryCount { + // While pooling a file that does not exist yet, but will be created by another process we can get Permission Denied + (*retry)++ + return true + } + return false +} diff --git a/winfile/winfile.go b/winfile/winfile.go new file mode 100644 index 0000000..aa7e7bc --- /dev/null +++ b/winfile/winfile.go @@ -0,0 +1,92 @@ +// +build windows + +package winfile + +import ( + "os" + "syscall" + "unsafe" +) + +// issue also described here +//https://codereview.appspot.com/8203043/ + +// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L218 +func Open(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 +} + +// https://github.com/jnwhiteh/golang/blob/master/src/pkg/syscall/syscall_windows.go#L211 +func makeInheritSa() *syscall.SecurityAttributes { + var sa syscall.SecurityAttributes + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.InheritHandle = 1 + return &sa +} + +// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_windows.go#L133 +func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) { + r, e := Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm)) + if e != nil { + return nil, e + } + return os.NewFile(uintptr(r), name), nil +} + +// https://github.com/jnwhiteh/golang/blob/master/src/pkg/os/file_posix.go#L61 +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 +}