github.com/DiversionCompany/notify@v0.9.9/watcher_inotify.go (about)

     1  // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
     2  // Use of this source code is governed by the MIT license that can be
     3  // found in the LICENSE file.
     4  
     5  //go:build linux
     6  // +build linux
     7  
     8  package notify
     9  
    10  import (
    11  	"bytes"
    12  	"errors"
    13  	"path/filepath"
    14  	"runtime"
    15  	"sync"
    16  	"sync/atomic"
    17  	"unsafe"
    18  
    19  	"golang.org/x/sys/unix"
    20  )
    21  
    22  // eventBufferSize defines the size of the buffer given to read(2) function. One
    23  // should not depend on this value, since it was arbitrary chosen and may be
    24  // changed in the future.
    25  const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1)
    26  
    27  // consumersCount defines the number of consumers in producer-consumer based
    28  // implementation. Each consumer is run in a separate goroutine and has read
    29  // access to watched files map.
    30  const consumersCount = 2
    31  
    32  const invalidDescriptor = -1
    33  
    34  // watched is a pair of file path and inotify mask used as a value in
    35  // watched files map.
    36  type watched struct {
    37  	path string
    38  	mask uint32
    39  }
    40  
    41  // inotify implements Watcher interface.
    42  type inotify struct {
    43  	sync.RWMutex                       // protects inotify.m map
    44  	m            map[int32]*watched    // watch descriptor to watched object
    45  	fd           int32                 // inotify file descriptor
    46  	pipefd       []int                 // pipe's read and write descriptors
    47  	epfd         int                   // epoll descriptor
    48  	epes         []unix.EpollEvent     // epoll events
    49  	buffer       [eventBufferSize]byte // inotify event buffer
    50  	wg           sync.WaitGroup        // wait group used to close main loop
    51  	c            chan<- EventInfo      // event dispatcher channel
    52  }
    53  
    54  // NewWatcher creates new non-recursive inotify backed by inotify.
    55  func newWatcher(c chan<- EventInfo) watcher {
    56  	i := &inotify{
    57  		m:      make(map[int32]*watched),
    58  		fd:     invalidDescriptor,
    59  		pipefd: []int{invalidDescriptor, invalidDescriptor},
    60  		epfd:   invalidDescriptor,
    61  		epes:   make([]unix.EpollEvent, 0),
    62  		c:      c,
    63  	}
    64  	runtime.SetFinalizer(i, func(i *inotify) {
    65  		i.epollclose()
    66  		if i.fd != invalidDescriptor {
    67  			unix.Close(int(i.fd))
    68  		}
    69  	})
    70  	return i
    71  }
    72  
    73  // Watch implements notify.watcher interface.
    74  func (i *inotify) Watch(path string, e Event) error {
    75  	return i.watch(path, e)
    76  }
    77  
    78  // Rewatch implements notify.watcher interface.
    79  func (i *inotify) Rewatch(path string, _, newevent Event) error {
    80  	return i.watch(path, newevent)
    81  }
    82  
    83  // watch adds a new watcher to the set of watched objects or modifies the existing
    84  // one. If called for the first time, this function initializes inotify filesystem
    85  // monitor and starts producer-consumers goroutines.
    86  func (i *inotify) watch(path string, e Event) (err error) {
    87  	if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 {
    88  		return errors.New("notify: unknown event")
    89  	}
    90  	if err = i.lazyinit(); err != nil {
    91  		dbgprintf("failed to lazyinit: %v\n", err)
    92  		return
    93  	}
    94  	iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e))
    95  	if err != nil {
    96  		dbgprintf("failed to add watch on %s: %v\n", path, err)
    97  		return
    98  	}
    99  	dbgprintf("added watch on wd=%d ('%s')\n", iwd, path)
   100  	i.Lock()
   101  	if wd, ok := i.m[int32(iwd)]; !ok {
   102  		i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
   103  	} else {
   104  		wd.path = path
   105  		wd.mask = uint32(e)
   106  	}
   107  	i.Unlock()
   108  	return nil
   109  }
   110  
   111  // lazyinit sets up all required file descriptors and starts 1+consumersCount
   112  // goroutines. The producer goroutine blocks until file-system notifications
   113  // occur. Then, all events are read from system buffer and sent to consumer
   114  // goroutines which construct valid notify events. This method uses
   115  // Double-Checked Locking optimization.
   116  func (i *inotify) lazyinit() error {
   117  	if atomic.LoadInt32(&i.fd) == invalidDescriptor {
   118  		i.Lock()
   119  		defer i.Unlock()
   120  		if atomic.LoadInt32(&i.fd) == invalidDescriptor {
   121  			fd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
   122  			if err != nil {
   123  				return err
   124  			}
   125  			i.fd = int32(fd)
   126  			if err = i.epollinit(); err != nil {
   127  				_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
   128  				i.fd = invalidDescriptor
   129  				return err
   130  			}
   131  			esch := make(chan []*event)
   132  			go i.loop(esch)
   133  			i.wg.Add(consumersCount)
   134  			for n := 0; n < consumersCount; n++ {
   135  				go i.send(esch)
   136  			}
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  // epollinit opens an epoll file descriptor and creates a pipe which will be
   143  // used to wake up the epoll_wait(2) function. Then, file descriptor associated
   144  // with inotify event queue and the read end of the pipe are added to epoll set.
   145  // Note that `fd` member must be set before this function is called.
   146  func (i *inotify) epollinit() (err error) {
   147  	if i.epfd, err = unix.EpollCreate1(0); err != nil {
   148  		return
   149  	}
   150  	if err = unix.Pipe(i.pipefd); err != nil {
   151  		return
   152  	}
   153  	i.epes = []unix.EpollEvent{
   154  		{Events: unix.EPOLLIN, Fd: i.fd},
   155  		{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])},
   156  	}
   157  	if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
   158  		return
   159  	}
   160  	return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
   161  }
   162  
   163  // epollclose closes the file descriptor created by the call to epoll_create(2)
   164  // and two file descriptors opened by pipe(2) function.
   165  func (i *inotify) epollclose() (err error) {
   166  	if i.epfd != invalidDescriptor {
   167  		if err = unix.Close(i.epfd); err == nil {
   168  			i.epfd = invalidDescriptor
   169  		}
   170  	}
   171  	for n, fd := range i.pipefd {
   172  		if fd != invalidDescriptor {
   173  			switch e := unix.Close(fd); {
   174  			case e != nil && err == nil:
   175  				err = e
   176  			case e == nil:
   177  				i.pipefd[n] = invalidDescriptor
   178  			}
   179  		}
   180  	}
   181  	return
   182  }
   183  
   184  // loop blocks until either inotify or pipe file descriptor is ready for I/O.
   185  // All read operations triggered by filesystem notifications are forwarded to
   186  // one of the event's consumers. If pipe fd became ready, loop function closes
   187  // all file descriptors opened by lazyinit method and returns afterwards.
   188  func (i *inotify) loop(esch chan<- []*event) {
   189  	epes := make([]unix.EpollEvent, 1)
   190  	fd := atomic.LoadInt32(&i.fd)
   191  	for {
   192  		switch _, err := unix.EpollWait(i.epfd, epes, -1); err {
   193  		case nil:
   194  			switch epes[0].Fd {
   195  			case fd:
   196  				esch <- i.read()
   197  				epes[0].Fd = 0
   198  			case int32(i.pipefd[0]):
   199  				i.Lock()
   200  				defer i.Unlock()
   201  				if err = unix.Close(int(fd)); err != nil && err != unix.EINTR {
   202  					panic("notify: close(2) error " + err.Error())
   203  				}
   204  				atomic.StoreInt32(&i.fd, invalidDescriptor)
   205  				if err = i.epollclose(); err != nil && err != unix.EINTR {
   206  					panic("notify: epollclose error " + err.Error())
   207  				}
   208  				close(esch)
   209  				return
   210  			}
   211  		case unix.EINTR:
   212  			continue
   213  		default: // We should never reach this line.
   214  			panic("notify: epoll_wait(2) error " + err.Error())
   215  		}
   216  	}
   217  }
   218  
   219  // read reads events from an inotify file descriptor. It does not handle errors
   220  // returned from read(2) function since they are not critical to watcher logic.
   221  func (i *inotify) read() (es []*event) {
   222  	n, err := unix.Read(int(i.fd), i.buffer[:])
   223  	if err != nil || n < unix.SizeofInotifyEvent {
   224  		return
   225  	}
   226  	var sys *unix.InotifyEvent
   227  	nmin := n - unix.SizeofInotifyEvent
   228  	for pos, path := 0, ""; pos <= nmin; {
   229  		sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
   230  		pos += unix.SizeofInotifyEvent
   231  		if path = ""; sys.Len > 0 {
   232  			endpos := pos + int(sys.Len)
   233  			path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
   234  			pos = endpos
   235  		}
   236  		es = append(es, &event{
   237  			sys: unix.InotifyEvent{
   238  				Wd:     sys.Wd,
   239  				Mask:   sys.Mask,
   240  				Cookie: sys.Cookie,
   241  			},
   242  			path: path,
   243  		})
   244  	}
   245  	return
   246  }
   247  
   248  // send is a consumer function which sends events to event dispatcher channel.
   249  // It is run in a separate goroutine in order to not block loop method when
   250  // possibly expensive write operations are performed on inotify map.
   251  func (i *inotify) send(esch <-chan []*event) {
   252  	for es := range esch {
   253  		for _, e := range i.transform(es) {
   254  			if e != nil {
   255  				i.c <- e
   256  			}
   257  		}
   258  	}
   259  	i.wg.Done()
   260  }
   261  
   262  func logEvent(prefix string, e *event, idx int, len int) {
   263  	dbgprintf("%s %v (0x%x) ('%s', i=%d, wd=%d, cookie=%d, len=%d)\n",
   264  		prefix,
   265  		Event(e.sys.Mask),
   266  		e.sys.Mask, e.path, idx, e.sys.Wd, e.sys.Cookie, len)
   267  }
   268  
   269  // transform prepares events read from inotify file descriptor for sending to
   270  // user. It removes invalid events and these which are no longer present in
   271  // inotify map. This method may also split one raw event into two different ones
   272  // when system-dependent result is required.
   273  func (i *inotify) transform(es []*event) []*event {
   274  	var multi []*event
   275  	i.RLock()
   276  	for idx, e := range es {
   277  		if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 {
   278  			es[idx] = nil
   279  			logEvent("ignored_event", e, idx, len(es))
   280  			continue
   281  		}
   282  		wd, ok := i.m[e.sys.Wd]
   283  		if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
   284  			es[idx] = nil
   285  			logEvent("undecoded_event", e, idx, len(es))
   286  			continue
   287  		}
   288  		if e.path == "" {
   289  			e.path = wd.path
   290  		} else {
   291  			e.path = filepath.Join(wd.path, e.path)
   292  		}
   293  		logEvent("received_event", e, idx, len(es))
   294  		multi = append(multi, decode(Event(wd.mask), e))
   295  		if e.event == 0 {
   296  			es[idx] = nil
   297  		}
   298  	}
   299  	i.RUnlock()
   300  	es = append(es, multi...)
   301  	return es
   302  }
   303  
   304  // encode converts notify system-independent events to valid inotify mask
   305  // which can be passed to inotify_add_watch(2) function.
   306  func encode(e Event) uint32 {
   307  	if e&Create != 0 {
   308  		e = (e ^ Create) | InCreate | InMovedTo
   309  	}
   310  	if e&Remove != 0 {
   311  		e = (e ^ Remove) | InDelete | InDeleteSelf
   312  	}
   313  	if e&Write != 0 {
   314  		e = (e ^ Write) | InModify
   315  	}
   316  	if e&Rename != 0 {
   317  		e = (e ^ Rename) | InMovedFrom | InMoveSelf
   318  	}
   319  	return uint32(e)
   320  }
   321  
   322  // decode uses internally stored mask to distinguish whether system-independent
   323  // or system-dependent event is requested. The first one is created by modifying
   324  // `e` argument. decode method sets e.event value to 0 when an event should be
   325  // skipped. System-dependent event is set as the function's return value which
   326  // can be nil when the event should not be passed on.
   327  func decode(mask Event, e *event) (syse *event) {
   328  	if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
   329  		syse = &event{sys: unix.InotifyEvent{
   330  			Wd:     e.sys.Wd,
   331  			Mask:   e.sys.Mask,
   332  			Cookie: e.sys.Cookie,
   333  		}, event: Event(sysmask), path: e.path}
   334  	}
   335  	imask := encode(mask)
   336  	switch {
   337  	case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
   338  		e.event = Create
   339  	case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
   340  		e.event = Remove
   341  	case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
   342  		e.event = Write
   343  	case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
   344  		e.event = Rename
   345  	default:
   346  		e.event = 0
   347  	}
   348  	return
   349  }
   350  
   351  // Unwatch implements notify.watcher interface. It looks for watch descriptor
   352  // related to registered path and if found, calls inotify_rm_watch(2) function.
   353  // This method is allowed to return EINVAL error when concurrently requested to
   354  // delete identical path.
   355  func (i *inotify) Unwatch(path string) (err error) {
   356  	iwd := int32(invalidDescriptor)
   357  	i.RLock()
   358  	for iwdkey, wd := range i.m {
   359  		if wd.path == path {
   360  			iwd = iwdkey
   361  			break
   362  		}
   363  	}
   364  	i.RUnlock()
   365  	if iwd == invalidDescriptor {
   366  		return errors.New("notify: path " + path + " is already watched")
   367  	}
   368  	fd := atomic.LoadInt32(&i.fd)
   369  	if err = removeInotifyWatch(fd, iwd); err != nil {
   370  		return
   371  	}
   372  	i.Lock()
   373  	delete(i.m, iwd)
   374  	i.Unlock()
   375  	return nil
   376  }
   377  
   378  // Close implements notify.watcher interface. It removes all existing watch
   379  // descriptors and wakes up producer goroutine by sending data to the write end
   380  // of the pipe. The function waits for a signal from producer which means that
   381  // all operations on current monitoring instance are done.
   382  func (i *inotify) Close() (err error) {
   383  	i.Lock()
   384  	if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
   385  		i.Unlock()
   386  		return nil
   387  	}
   388  	for iwd := range i.m {
   389  		if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil {
   390  			err = e
   391  		}
   392  		delete(i.m, iwd)
   393  	}
   394  	switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); {
   395  	case errwrite != nil && err == nil:
   396  		err = errwrite
   397  		fallthrough
   398  	case errwrite != nil:
   399  		i.Unlock()
   400  	default:
   401  		i.Unlock()
   402  		i.wg.Wait()
   403  	}
   404  	return
   405  }
   406  
   407  // if path was removed, notify already removed the watch and returns EINVAL error
   408  func removeInotifyWatch(fd int32, iwd int32) (err error) {
   409  	if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL {
   410  		return
   411  	}
   412  	return nil
   413  }