|
|
@ -13,14 +13,15 @@ import ( |
|
|
|
"runtime" |
|
|
|
"runtime" |
|
|
|
"sync" |
|
|
|
"sync" |
|
|
|
"sync/atomic" |
|
|
|
"sync/atomic" |
|
|
|
"syscall" |
|
|
|
|
|
|
|
"unsafe" |
|
|
|
"unsafe" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"golang.org/x/sys/unix" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// eventBufferSize defines the size of the buffer given to read(2) function. One
|
|
|
|
// eventBufferSize defines the size of the buffer given to read(2) function. One
|
|
|
|
// should not depend on this value, since it was arbitrary chosen and may be
|
|
|
|
// should not depend on this value, since it was arbitrary chosen and may be
|
|
|
|
// changed in the future.
|
|
|
|
// changed in the future.
|
|
|
|
const eventBufferSize = 64 * (syscall.SizeofInotifyEvent + syscall.PathMax + 1) |
|
|
|
const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1) |
|
|
|
|
|
|
|
|
|
|
|
// consumersCount defines the number of consumers in producer-consumer based
|
|
|
|
// consumersCount defines the number of consumers in producer-consumer based
|
|
|
|
// implementation. Each consumer is run in a separate goroutine and has read
|
|
|
|
// implementation. Each consumer is run in a separate goroutine and has read
|
|
|
@ -43,7 +44,7 @@ type inotify struct { |
|
|
|
fd int32 // inotify file descriptor
|
|
|
|
fd int32 // inotify file descriptor
|
|
|
|
pipefd []int // pipe's read and write descriptors
|
|
|
|
pipefd []int // pipe's read and write descriptors
|
|
|
|
epfd int // epoll descriptor
|
|
|
|
epfd int // epoll descriptor
|
|
|
|
epes []syscall.EpollEvent // epoll events
|
|
|
|
epes []unix.EpollEvent // epoll events
|
|
|
|
buffer [eventBufferSize]byte // inotify event buffer
|
|
|
|
buffer [eventBufferSize]byte // inotify event buffer
|
|
|
|
wg sync.WaitGroup // wait group used to close main loop
|
|
|
|
wg sync.WaitGroup // wait group used to close main loop
|
|
|
|
c chan<- EventInfo // event dispatcher channel
|
|
|
|
c chan<- EventInfo // event dispatcher channel
|
|
|
@ -56,13 +57,13 @@ func newWatcher(c chan<- EventInfo) watcher { |
|
|
|
fd: invalidDescriptor, |
|
|
|
fd: invalidDescriptor, |
|
|
|
pipefd: []int{invalidDescriptor, invalidDescriptor}, |
|
|
|
pipefd: []int{invalidDescriptor, invalidDescriptor}, |
|
|
|
epfd: invalidDescriptor, |
|
|
|
epfd: invalidDescriptor, |
|
|
|
epes: make([]syscall.EpollEvent, 0), |
|
|
|
epes: make([]unix.EpollEvent, 0), |
|
|
|
c: c, |
|
|
|
c: c, |
|
|
|
} |
|
|
|
} |
|
|
|
runtime.SetFinalizer(i, func(i *inotify) { |
|
|
|
runtime.SetFinalizer(i, func(i *inotify) { |
|
|
|
i.epollclose() |
|
|
|
i.epollclose() |
|
|
|
if i.fd != invalidDescriptor { |
|
|
|
if i.fd != invalidDescriptor { |
|
|
|
syscall.Close(int(i.fd)) |
|
|
|
unix.Close(int(i.fd)) |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
return i |
|
|
|
return i |
|
|
@ -82,13 +83,13 @@ func (i *inotify) Rewatch(path string, _, newevent Event) error { |
|
|
|
// one. If called for the first time, this function initializes inotify filesystem
|
|
|
|
// one. If called for the first time, this function initializes inotify filesystem
|
|
|
|
// monitor and starts producer-consumers goroutines.
|
|
|
|
// monitor and starts producer-consumers goroutines.
|
|
|
|
func (i *inotify) watch(path string, e Event) (err error) { |
|
|
|
func (i *inotify) watch(path string, e Event) (err error) { |
|
|
|
if e&^(All|Event(syscall.IN_ALL_EVENTS)) != 0 { |
|
|
|
if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 { |
|
|
|
return errors.New("notify: unknown event") |
|
|
|
return errors.New("notify: unknown event") |
|
|
|
} |
|
|
|
} |
|
|
|
if err = i.lazyinit(); err != nil { |
|
|
|
if err = i.lazyinit(); err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
iwd, err := syscall.InotifyAddWatch(int(i.fd), path, encode(e)) |
|
|
|
iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e)) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
@ -119,13 +120,13 @@ func (i *inotify) lazyinit() error { |
|
|
|
i.Lock() |
|
|
|
i.Lock() |
|
|
|
defer i.Unlock() |
|
|
|
defer i.Unlock() |
|
|
|
if atomic.LoadInt32(&i.fd) == invalidDescriptor { |
|
|
|
if atomic.LoadInt32(&i.fd) == invalidDescriptor { |
|
|
|
fd, err := syscall.InotifyInit() |
|
|
|
fd, err := unix.InotifyInit1(unix.IN_CLOEXEC) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
i.fd = int32(fd) |
|
|
|
i.fd = int32(fd) |
|
|
|
if err = i.epollinit(); err != nil { |
|
|
|
if err = i.epollinit(); err != nil { |
|
|
|
_, _ = i.epollclose(), syscall.Close(int(fd)) // Ignore errors.
|
|
|
|
_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
|
|
|
|
i.fd = invalidDescriptor |
|
|
|
i.fd = invalidDescriptor |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
@ -145,33 +146,33 @@ func (i *inotify) lazyinit() error { |
|
|
|
// with inotify event queue and the read end of the pipe are added to epoll set.
|
|
|
|
// with inotify event queue and the read end of the pipe are added to epoll set.
|
|
|
|
// Note that `fd` member must be set before this function is called.
|
|
|
|
// Note that `fd` member must be set before this function is called.
|
|
|
|
func (i *inotify) epollinit() (err error) { |
|
|
|
func (i *inotify) epollinit() (err error) { |
|
|
|
if i.epfd, err = syscall.EpollCreate1(0); err != nil { |
|
|
|
if i.epfd, err = unix.EpollCreate1(0); err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
if err = syscall.Pipe(i.pipefd); err != nil { |
|
|
|
if err = unix.Pipe(i.pipefd); err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
i.epes = []syscall.EpollEvent{ |
|
|
|
i.epes = []unix.EpollEvent{ |
|
|
|
{Events: syscall.EPOLLIN, Fd: i.fd}, |
|
|
|
{Events: unix.EPOLLIN, Fd: i.fd}, |
|
|
|
{Events: syscall.EPOLLIN, Fd: int32(i.pipefd[0])}, |
|
|
|
{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])}, |
|
|
|
} |
|
|
|
} |
|
|
|
if err = syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil { |
|
|
|
if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
return syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1]) |
|
|
|
return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// epollclose closes the file descriptor created by the call to epoll_create(2)
|
|
|
|
// epollclose closes the file descriptor created by the call to epoll_create(2)
|
|
|
|
// and two file descriptors opened by pipe(2) function.
|
|
|
|
// and two file descriptors opened by pipe(2) function.
|
|
|
|
func (i *inotify) epollclose() (err error) { |
|
|
|
func (i *inotify) epollclose() (err error) { |
|
|
|
if i.epfd != invalidDescriptor { |
|
|
|
if i.epfd != invalidDescriptor { |
|
|
|
if err = syscall.Close(i.epfd); err == nil { |
|
|
|
if err = unix.Close(i.epfd); err == nil { |
|
|
|
i.epfd = invalidDescriptor |
|
|
|
i.epfd = invalidDescriptor |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
for n, fd := range i.pipefd { |
|
|
|
for n, fd := range i.pipefd { |
|
|
|
if fd != invalidDescriptor { |
|
|
|
if fd != invalidDescriptor { |
|
|
|
switch e := syscall.Close(fd); { |
|
|
|
switch e := unix.Close(fd); { |
|
|
|
case e != nil && err == nil: |
|
|
|
case e != nil && err == nil: |
|
|
|
err = e |
|
|
|
err = e |
|
|
|
case e == nil: |
|
|
|
case e == nil: |
|
|
@ -187,10 +188,10 @@ func (i *inotify) epollclose() (err error) { |
|
|
|
// one of the event's consumers. If pipe fd became ready, loop function closes
|
|
|
|
// one of the event's consumers. If pipe fd became ready, loop function closes
|
|
|
|
// all file descriptors opened by lazyinit method and returns afterwards.
|
|
|
|
// all file descriptors opened by lazyinit method and returns afterwards.
|
|
|
|
func (i *inotify) loop(esch chan<- []*event) { |
|
|
|
func (i *inotify) loop(esch chan<- []*event) { |
|
|
|
epes := make([]syscall.EpollEvent, 1) |
|
|
|
epes := make([]unix.EpollEvent, 1) |
|
|
|
fd := atomic.LoadInt32(&i.fd) |
|
|
|
fd := atomic.LoadInt32(&i.fd) |
|
|
|
for { |
|
|
|
for { |
|
|
|
switch _, err := syscall.EpollWait(i.epfd, epes, -1); err { |
|
|
|
switch _, err := unix.EpollWait(i.epfd, epes, -1); err { |
|
|
|
case nil: |
|
|
|
case nil: |
|
|
|
switch epes[0].Fd { |
|
|
|
switch epes[0].Fd { |
|
|
|
case fd: |
|
|
|
case fd: |
|
|
@ -199,17 +200,17 @@ func (i *inotify) loop(esch chan<- []*event) { |
|
|
|
case int32(i.pipefd[0]): |
|
|
|
case int32(i.pipefd[0]): |
|
|
|
i.Lock() |
|
|
|
i.Lock() |
|
|
|
defer i.Unlock() |
|
|
|
defer i.Unlock() |
|
|
|
if err = syscall.Close(int(fd)); err != nil && err != syscall.EINTR { |
|
|
|
if err = unix.Close(int(fd)); err != nil && err != unix.EINTR { |
|
|
|
panic("notify: close(2) error " + err.Error()) |
|
|
|
panic("notify: close(2) error " + err.Error()) |
|
|
|
} |
|
|
|
} |
|
|
|
atomic.StoreInt32(&i.fd, invalidDescriptor) |
|
|
|
atomic.StoreInt32(&i.fd, invalidDescriptor) |
|
|
|
if err = i.epollclose(); err != nil && err != syscall.EINTR { |
|
|
|
if err = i.epollclose(); err != nil && err != unix.EINTR { |
|
|
|
panic("notify: epollclose error " + err.Error()) |
|
|
|
panic("notify: epollclose error " + err.Error()) |
|
|
|
} |
|
|
|
} |
|
|
|
close(esch) |
|
|
|
close(esch) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
case syscall.EINTR: |
|
|
|
case unix.EINTR: |
|
|
|
continue |
|
|
|
continue |
|
|
|
default: // We should never reach this line.
|
|
|
|
default: // We should never reach this line.
|
|
|
|
panic("notify: epoll_wait(2) error " + err.Error()) |
|
|
|
panic("notify: epoll_wait(2) error " + err.Error()) |
|
|
@ -220,22 +221,22 @@ func (i *inotify) loop(esch chan<- []*event) { |
|
|
|
// read reads events from an inotify file descriptor. It does not handle errors
|
|
|
|
// read reads events from an inotify file descriptor. It does not handle errors
|
|
|
|
// returned from read(2) function since they are not critical to watcher logic.
|
|
|
|
// returned from read(2) function since they are not critical to watcher logic.
|
|
|
|
func (i *inotify) read() (es []*event) { |
|
|
|
func (i *inotify) read() (es []*event) { |
|
|
|
n, err := syscall.Read(int(i.fd), i.buffer[:]) |
|
|
|
n, err := unix.Read(int(i.fd), i.buffer[:]) |
|
|
|
if err != nil || n < syscall.SizeofInotifyEvent { |
|
|
|
if err != nil || n < unix.SizeofInotifyEvent { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
var sys *syscall.InotifyEvent |
|
|
|
var sys *unix.InotifyEvent |
|
|
|
nmin := n - syscall.SizeofInotifyEvent |
|
|
|
nmin := n - unix.SizeofInotifyEvent |
|
|
|
for pos, path := 0, ""; pos <= nmin; { |
|
|
|
for pos, path := 0, ""; pos <= nmin; { |
|
|
|
sys = (*syscall.InotifyEvent)(unsafe.Pointer(&i.buffer[pos])) |
|
|
|
sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos])) |
|
|
|
pos += syscall.SizeofInotifyEvent |
|
|
|
pos += unix.SizeofInotifyEvent |
|
|
|
if path = ""; sys.Len > 0 { |
|
|
|
if path = ""; sys.Len > 0 { |
|
|
|
endpos := pos + int(sys.Len) |
|
|
|
endpos := pos + int(sys.Len) |
|
|
|
path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00")) |
|
|
|
path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00")) |
|
|
|
pos = endpos |
|
|
|
pos = endpos |
|
|
|
} |
|
|
|
} |
|
|
|
es = append(es, &event{ |
|
|
|
es = append(es, &event{ |
|
|
|
sys: syscall.InotifyEvent{ |
|
|
|
sys: unix.InotifyEvent{ |
|
|
|
Wd: sys.Wd, |
|
|
|
Wd: sys.Wd, |
|
|
|
Mask: sys.Mask, |
|
|
|
Mask: sys.Mask, |
|
|
|
Cookie: sys.Cookie, |
|
|
|
Cookie: sys.Cookie, |
|
|
@ -268,7 +269,7 @@ func (i *inotify) transform(es []*event) []*event { |
|
|
|
var multi []*event |
|
|
|
var multi []*event |
|
|
|
i.RLock() |
|
|
|
i.RLock() |
|
|
|
for idx, e := range es { |
|
|
|
for idx, e := range es { |
|
|
|
if e.sys.Mask&(syscall.IN_IGNORED|syscall.IN_Q_OVERFLOW) != 0 { |
|
|
|
if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 { |
|
|
|
es[idx] = nil |
|
|
|
es[idx] = nil |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
@ -317,7 +318,7 @@ func encode(e Event) uint32 { |
|
|
|
// can be nil when the event should not be passed on.
|
|
|
|
// can be nil when the event should not be passed on.
|
|
|
|
func decode(mask Event, e *event) (syse *event) { |
|
|
|
func decode(mask Event, e *event) (syse *event) { |
|
|
|
if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 { |
|
|
|
if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 { |
|
|
|
syse = &event{sys: syscall.InotifyEvent{ |
|
|
|
syse = &event{sys: unix.InotifyEvent{ |
|
|
|
Wd: e.sys.Wd, |
|
|
|
Wd: e.sys.Wd, |
|
|
|
Mask: e.sys.Mask, |
|
|
|
Mask: e.sys.Mask, |
|
|
|
Cookie: e.sys.Cookie, |
|
|
|
Cookie: e.sys.Cookie, |
|
|
@ -357,7 +358,7 @@ func (i *inotify) Unwatch(path string) (err error) { |
|
|
|
return errors.New("notify: path " + path + " is already watched") |
|
|
|
return errors.New("notify: path " + path + " is already watched") |
|
|
|
} |
|
|
|
} |
|
|
|
fd := atomic.LoadInt32(&i.fd) |
|
|
|
fd := atomic.LoadInt32(&i.fd) |
|
|
|
if _, err = syscall.InotifyRmWatch(int(fd), uint32(iwd)); err != nil { |
|
|
|
if err = removeInotifyWatch(fd, iwd); err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
i.Lock() |
|
|
|
i.Lock() |
|
|
@ -377,12 +378,12 @@ func (i *inotify) Close() (err error) { |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
for iwd := range i.m { |
|
|
|
for iwd := range i.m { |
|
|
|
if _, e := syscall.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil { |
|
|
|
if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil { |
|
|
|
err = e |
|
|
|
err = e |
|
|
|
} |
|
|
|
} |
|
|
|
delete(i.m, iwd) |
|
|
|
delete(i.m, iwd) |
|
|
|
} |
|
|
|
} |
|
|
|
switch _, errwrite := syscall.Write(i.pipefd[1], []byte{0x00}); { |
|
|
|
switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); { |
|
|
|
case errwrite != nil && err == nil: |
|
|
|
case errwrite != nil && err == nil: |
|
|
|
err = errwrite |
|
|
|
err = errwrite |
|
|
|
fallthrough |
|
|
|
fallthrough |
|
|
@ -394,3 +395,11 @@ func (i *inotify) Close() (err error) { |
|
|
|
} |
|
|
|
} |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if path was removed, notify already removed the watch and returns EINVAL error
|
|
|
|
|
|
|
|
func removeInotifyWatch(fd int32, iwd int32) (err error) { |
|
|
|
|
|
|
|
if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL { |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|