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