...

Source file src/k8s.io/utils/inotify/inotify_linux.go

Documentation: k8s.io/utils/inotify

     1  //go:build linux
     2  // +build linux
     3  
     4  // Copyright 2010 The Go Authors. All rights reserved.
     5  // Use of this source code is governed by a BSD-style
     6  // license that can be found in the LICENSE file.
     7  
     8  /*
     9  Package inotify implements a wrapper for the Linux inotify system.
    10  
    11  Example:
    12  
    13  	watcher, err := inotify.NewWatcher()
    14  	if err != nil {
    15  	    log.Fatal(err)
    16  	}
    17  	err = watcher.Watch("/tmp")
    18  	if err != nil {
    19  	    log.Fatal(err)
    20  	}
    21  	for {
    22  	    select {
    23  	    case ev := <-watcher.Event:
    24  	        log.Println("event:", ev)
    25  	    case err := <-watcher.Error:
    26  	        log.Println("error:", err)
    27  	    }
    28  	}
    29  */
    30  package inotify // import "k8s.io/utils/inotify"
    31  
    32  import (
    33  	"errors"
    34  	"fmt"
    35  	"os"
    36  	"strings"
    37  	"syscall"
    38  	"unsafe"
    39  )
    40  
    41  // NewWatcher creates and returns a new inotify instance using inotify_init(2)
    42  func NewWatcher() (*Watcher, error) {
    43  	fd, errno := syscall.InotifyInit1(syscall.IN_CLOEXEC)
    44  	if fd == -1 {
    45  		return nil, os.NewSyscallError("inotify_init", errno)
    46  	}
    47  	w := &Watcher{
    48  		fd:      fd,
    49  		watches: make(map[string]*watch),
    50  		paths:   make(map[int]string),
    51  		Event:   make(chan *Event),
    52  		Error:   make(chan error),
    53  		done:    make(chan bool, 1),
    54  	}
    55  
    56  	go w.readEvents()
    57  	return w, nil
    58  }
    59  
    60  // Close closes an inotify watcher instance
    61  // It sends a message to the reader goroutine to quit and removes all watches
    62  // associated with the inotify instance
    63  func (w *Watcher) Close() error {
    64  	if w.isClosed {
    65  		return nil
    66  	}
    67  	w.isClosed = true
    68  
    69  	// Send "quit" message to the reader goroutine
    70  	w.done <- true
    71  	for path := range w.watches {
    72  		w.RemoveWatch(path)
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  // AddWatch adds path to the watched file set.
    79  // The flags are interpreted as described in inotify_add_watch(2).
    80  func (w *Watcher) AddWatch(path string, flags uint32) error {
    81  	if w.isClosed {
    82  		return errors.New("inotify instance already closed")
    83  	}
    84  
    85  	watchEntry, found := w.watches[path]
    86  	if found {
    87  		watchEntry.flags |= flags
    88  		flags |= syscall.IN_MASK_ADD
    89  	}
    90  
    91  	w.mu.Lock() // synchronize with readEvents goroutine
    92  
    93  	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
    94  	if err != nil {
    95  		w.mu.Unlock()
    96  		return &os.PathError{
    97  			Op:   "inotify_add_watch",
    98  			Path: path,
    99  			Err:  err,
   100  		}
   101  	}
   102  
   103  	if !found {
   104  		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
   105  		w.paths[wd] = path
   106  	}
   107  	w.mu.Unlock()
   108  	return nil
   109  }
   110  
   111  // Watch adds path to the watched file set, watching all events.
   112  func (w *Watcher) Watch(path string) error {
   113  	return w.AddWatch(path, InAllEvents)
   114  }
   115  
   116  // RemoveWatch removes path from the watched file set.
   117  func (w *Watcher) RemoveWatch(path string) error {
   118  	watch, ok := w.watches[path]
   119  	if !ok {
   120  		return fmt.Errorf("can't remove non-existent inotify watch for: %s", path)
   121  	}
   122  	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
   123  	if success == -1 {
   124  		// when file descriptor or watch descriptor not found, InotifyRmWatch syscall return EINVAL error
   125  		// if return error, it may lead this path remain in watches and paths map, and no other event can trigger remove action.
   126  		if errno != syscall.EINVAL {
   127  			return os.NewSyscallError("inotify_rm_watch", errno)
   128  		}
   129  	}
   130  	delete(w.watches, path)
   131  	// Locking here to protect the read from paths in readEvents.
   132  	w.mu.Lock()
   133  	delete(w.paths, int(watch.wd))
   134  	w.mu.Unlock()
   135  	return nil
   136  }
   137  
   138  // readEvents reads from the inotify file descriptor, converts the
   139  // received events into Event objects and sends them via the Event channel
   140  func (w *Watcher) readEvents() {
   141  	var buf [syscall.SizeofInotifyEvent * 4096]byte
   142  
   143  	for {
   144  		n, err := syscall.Read(w.fd, buf[:])
   145  		// See if there is a message on the "done" channel
   146  		var done bool
   147  		select {
   148  		case done = <-w.done:
   149  		default:
   150  		}
   151  
   152  		// If EOF or a "done" message is received
   153  		if n == 0 || done {
   154  			// The syscall.Close can be slow.  Close
   155  			// w.Event first.
   156  			close(w.Event)
   157  			err := syscall.Close(w.fd)
   158  			if err != nil {
   159  				w.Error <- os.NewSyscallError("close", err)
   160  			}
   161  			close(w.Error)
   162  			return
   163  		}
   164  		if n < 0 {
   165  			w.Error <- os.NewSyscallError("read", err)
   166  			continue
   167  		}
   168  		if n < syscall.SizeofInotifyEvent {
   169  			w.Error <- errors.New("inotify: short read in readEvents()")
   170  			continue
   171  		}
   172  
   173  		var offset uint32
   174  		// We don't know how many events we just read into the buffer
   175  		// While the offset points to at least one whole event...
   176  		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
   177  			// Point "raw" to the event in the buffer
   178  			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
   179  			event := new(Event)
   180  			event.Mask = uint32(raw.Mask)
   181  			event.Cookie = uint32(raw.Cookie)
   182  			nameLen := uint32(raw.Len)
   183  			// If the event happened to the watched directory or the watched file, the kernel
   184  			// doesn't append the filename to the event, but we would like to always fill the
   185  			// the "Name" field with a valid filename. We retrieve the path of the watch from
   186  			// the "paths" map.
   187  			w.mu.Lock()
   188  			name, ok := w.paths[int(raw.Wd)]
   189  			w.mu.Unlock()
   190  			if ok {
   191  				event.Name = name
   192  				if nameLen > 0 {
   193  					// Point "bytes" at the first byte of the filename
   194  					bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
   195  					// The filename is padded with NUL bytes. TrimRight() gets rid of those.
   196  					event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
   197  				}
   198  				// Send the event on the events channel
   199  				w.Event <- event
   200  			}
   201  			// Move to the next event in the buffer
   202  			offset += syscall.SizeofInotifyEvent + nameLen
   203  		}
   204  	}
   205  }
   206  
   207  // String formats the event e in the form
   208  // "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
   209  func (e *Event) String() string {
   210  	var events string
   211  
   212  	m := e.Mask
   213  	for _, b := range eventBits {
   214  		if m&b.Value == b.Value {
   215  			m &^= b.Value
   216  			events += "|" + b.Name
   217  		}
   218  	}
   219  
   220  	if m != 0 {
   221  		events += fmt.Sprintf("|%#x", m)
   222  	}
   223  	if len(events) > 0 {
   224  		events = " == " + events[1:]
   225  	}
   226  
   227  	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
   228  }
   229  
   230  const (
   231  	// Options for inotify_init() are not exported
   232  	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
   233  	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK
   234  
   235  	// Options for AddWatch
   236  
   237  	// InDontFollow : Don't dereference pathname if it is a symbolic link
   238  	InDontFollow uint32 = syscall.IN_DONT_FOLLOW
   239  	// InOneshot : Monitor the filesystem object corresponding to pathname for one event, then remove from watch list
   240  	InOneshot uint32 = syscall.IN_ONESHOT
   241  	// InOnlydir : Watch pathname only if it is a directory
   242  	InOnlydir uint32 = syscall.IN_ONLYDIR
   243  
   244  	// The "IN_MASK_ADD" option is not exported, as AddWatch
   245  	// adds it automatically, if there is already a watch for the given path
   246  	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD
   247  
   248  	// Events
   249  
   250  	// InAccess : File was accessed
   251  	InAccess uint32 = syscall.IN_ACCESS
   252  	// InAllEvents : Bit mask for all notify events
   253  	InAllEvents uint32 = syscall.IN_ALL_EVENTS
   254  	// InAttrib : Metadata changed
   255  	InAttrib uint32 = syscall.IN_ATTRIB
   256  	// InClose : Equates to IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
   257  	InClose uint32 = syscall.IN_CLOSE
   258  	// InCloseNowrite : File or directory not opened for writing was closed
   259  	InCloseNowrite uint32 = syscall.IN_CLOSE_NOWRITE
   260  	// InCloseWrite : File opened for writing was closed
   261  	InCloseWrite uint32 = syscall.IN_CLOSE_WRITE
   262  	// InCreate : File/directory created in watched directory
   263  	InCreate uint32 = syscall.IN_CREATE
   264  	// InDelete : File/directory deleted from watched directory
   265  	InDelete uint32 = syscall.IN_DELETE
   266  	// InDeleteSelf : Watched file/directory was itself deleted
   267  	InDeleteSelf uint32 = syscall.IN_DELETE_SELF
   268  	// InModify : File was modified
   269  	InModify uint32 = syscall.IN_MODIFY
   270  	// InMove : Equates to IN_MOVED_FROM | IN_MOVED_TO
   271  	InMove uint32 = syscall.IN_MOVE
   272  	// InMovedFrom : Generated for the directory containing the old filename when a file is renamed
   273  	InMovedFrom uint32 = syscall.IN_MOVED_FROM
   274  	// InMovedTo : Generated for the directory containing the new filename when a file is renamed
   275  	InMovedTo uint32 = syscall.IN_MOVED_TO
   276  	// InMoveSelf : Watched file/directory was itself moved
   277  	InMoveSelf uint32 = syscall.IN_MOVE_SELF
   278  	// InOpen : File or directory was opened
   279  	InOpen uint32 = syscall.IN_OPEN
   280  
   281  	// Special events
   282  
   283  	// InIsdir : Subject of this event is a directory
   284  	InIsdir uint32 = syscall.IN_ISDIR
   285  	// InIgnored : Watch was removed explicitly or automatically
   286  	InIgnored uint32 = syscall.IN_IGNORED
   287  	// InQOverflow : Event queue overflowed
   288  	InQOverflow uint32 = syscall.IN_Q_OVERFLOW
   289  	// InUnmount : Filesystem containing watched object was unmounted
   290  	InUnmount uint32 = syscall.IN_UNMOUNT
   291  )
   292  
   293  var eventBits = []struct {
   294  	Value uint32
   295  	Name  string
   296  }{
   297  	{InAccess, "IN_ACCESS"},
   298  	{InAttrib, "IN_ATTRIB"},
   299  	{InClose, "IN_CLOSE"},
   300  	{InCloseNowrite, "IN_CLOSE_NOWRITE"},
   301  	{InCloseWrite, "IN_CLOSE_WRITE"},
   302  	{InCreate, "IN_CREATE"},
   303  	{InDelete, "IN_DELETE"},
   304  	{InDeleteSelf, "IN_DELETE_SELF"},
   305  	{InModify, "IN_MODIFY"},
   306  	{InMove, "IN_MOVE"},
   307  	{InMovedFrom, "IN_MOVED_FROM"},
   308  	{InMovedTo, "IN_MOVED_TO"},
   309  	{InMoveSelf, "IN_MOVE_SELF"},
   310  	{InOpen, "IN_OPEN"},
   311  	{InIsdir, "IN_ISDIR"},
   312  	{InIgnored, "IN_IGNORED"},
   313  	{InQOverflow, "IN_Q_OVERFLOW"},
   314  	{InUnmount, "IN_UNMOUNT"},
   315  }
   316  

View as plain text