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