summaryrefslogtreecommitdiff
path: root/inotify/inotify.go
blob: 1c0c3c6273c23ecec4acbff63b6df51002a3cc85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
//
// This is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this manual; if not, see
// <http://www.gnu.org/licenses/>.

// Package inotify provides an interface to the Linux inotify system.
// The inotify system is a mechanism for monitoring filesystem events.
package inotify

import (
	"sync"
	"syscall"
	"unsafe"
	"os"
)

// Most of the complexity here is that we syncronize around access to
// the file descriptor.  Why?  Because we're worried that it could get
// closed and re-used between the time the fd is read from the os.File
// and the time the system call is actually dispatched.
//
//              A                        B
//         in.method()       in.Close(); syscall.Open()
//     ---------------------------------------------------
//     fd = in.fd
//                                syscall.Close()
//                                syscall.Open()
//     syscall(fd)
//
// And you're wondering "should we really protoect against that; after
// all: share by communicating, don't communicate by sharing" and "why
// do we have to deal with this condition if os.File doesn't?"
// Because an inotify instance is more like a net.Listener.  If you
// notice, net.netFD has do deal with a very similar situtation.  At
// some level, you may simply observe that with inotify it makes sense
// to have one goroutine add/remove watches, and have another read
// them; where this kind of communication doesn't make sense with
// ordinary files.
//
// So, how does net.netFD deal with it?  Well... it is tightly coupled
// with some private routines in runtime/sema.go.  That is, we can't
// look at it for too much guidance.

type Inotify struct {
	fd      inFd
	fdLock  sync.RWMutex
	fdBlock bool

	buffFull [4096]byte // 4KiB is a good size, but the bare
			    // minimum is `sizeof(struct
			    // inotify_event) + NAME_MAX + 1`
	buff     []byte
	buffLock sync.Mutex
	buffErr  error

	ch chan chev
}

// Event is a file system event from inotify.
//
// An Event is invalid if Wd is < 0.
type Event struct {
	Wd     Wd      // Watch descriptor
	Mask   Mask    // Mask describing event
	Cookie uint32  // Unique cookie associating related events (for rename(2))
	Name   *string // Optional name
}

type chev struct {
	Ev  Event
	Err error
}

func newInotify(fd inFd, blocking bool) *Inotify {
	if fd < 0 {
		return nil
	}
	in := &Inotify{
		fd: fd,
		fdBlock: blocking,
		ch: make(chan chev),
	}
	in.buff = in.buffFull[:0]
	go in.worker()
	return in
}

// NewInotify creates an Inotify object that wraps an inotify instance
// on an already open file descriptor, much like net.FileListener.
//
// Closing file does not affect the Inotify, and closing Inotify does
// not affect file.
func NewInotify(file *os.File) (*Inotify, error) {
	dup, err := sys_dupfd_cloexec(inFd(file.Fd()))
	if err != nil {
		return nil, err
	}

	nonblock, err := sys_getnonblock(dup)
	if err != nil {
		sys_close(dup)
		return nil, err
	}

	err = sys_setnonblock(dup, false)
	if err != nil {
		sys_close(dup)
		return nil, err
	}

	return newInotify(dup, !nonblock), nil
}

// InotifyInit creates an inotify instance.  The variant
// InotifyInit1() allows flags to access extra functionality.
func InotifyInit() (*Inotify, error) {
	fd, err := sys_inotify_init()
	return newInotify(fd, true), err
}

// InotifyInit1 create an inotify instance, with flags specifying
// extra functionality.
func InotifyInit1(flags int) (*Inotify, error) {
	fd, err := sys_inotify_init1(flags &^ IN_NONBLOCK)
	return newInotify(fd, flags & IN_NONBLOCK == 0), err
}

// AddWatch adds a watch to the inotify instance, or modifies an
// existing watch item.
func (in *Inotify) AddWatch(path string, mask Mask) (Wd, error) {
	in.fdLock.RLock()
	defer in.fdLock.RUnlock()
	return sys_inotify_add_watch(in.fd, path, mask)
}

// RmWatch removes a watch from the inotify instance.
func (in *Inotify) RmWatch(wd Wd) error {
	in.fdLock.RLock()
	defer in.fdLock.RUnlock()
	return sys_inotify_rm_watch(in.fd, wd)
}

// Close closes the inotify instance; further calls to this object
// will error.
func (in *Inotify) Close() (err error) {
	defer func() {
		if r := recover(); r != nil {
			// This is a double-close condition.
			//
			// Choices for the error:
			// - Linux: EBADF
			// - os.File: syscall.EINVAL
			// - net.netFD: net.errClosing = errors.New(...)
			err = os.NewSyscallError("close", syscall.EBADF)
		}
	}()
	close(in.ch) // will panic if already closed; hence above

	// I would love to be able to return the error from sys_close;
	// but it might block forever.
	go func() {
		in.fdLock.Lock()
		in.fdLock.Unlock()
		sys_close(in.fd)
	}()
	return
}

// read is a low-level read an event from the inotify instance.
//
// It's low-level/private because it can deadlock between it and the
// `go` block in Close.  Instead, a worker calls this in a loop,
// writes the result to a channel, and a public reader can safely get
// it from the channel.  No deadlock.
func (in *Inotify) read() (Event, error) {
	in.buffLock.Lock()
	defer in.buffLock.Unlock()

	if len(in.buff) == 0 {
		if in.buffErr != nil {
			return Event{Wd: -1}, in.buffErr
		}
		var n int
		in.fdLock.RLock()
		n, in.buffErr = sys_read(in.fd, in.buffFull[:])
		in.fdLock.RUnlock()
		in.buff = in.buffFull[0:n]
	}

	if len(in.buff) < syscall.SizeofInotifyEvent {
		// Either Linux screwed up (and we have no chance of
		// handling that sanely), or this Inotify came from an
		// existing FD that wasn't really an inotify instance.
		in.buffErr = syscall.EBADF
		return Event{Wd: -1}, in.buffErr
	}
	raw := (*syscall.InotifyEvent)(unsafe.Pointer(&in.buff[0]))
	ret := Event{
		Wd:     Wd(raw.Wd),
		Mask:   Mask(raw.Mask),
		Cookie: raw.Cookie,
		Name:   nil,
	}
	if int64(len(in.buff)) < syscall.SizeofInotifyEvent+int64(raw.Len) {
		// Same as above.
		in.buffErr = syscall.EBADF
		return Event{Wd: -1}, in.buffErr
	}
	if raw.Len > 0 {
		bytes := (*[syscall.NAME_MAX]byte)(unsafe.Pointer(&in.buff[syscall.SizeofInotifyEvent]))
		name := string(bytes[:raw.Len-1])
		ret.Name = &name
	}
	in.buff = in.buff[0 : syscall.SizeofInotifyEvent+raw.Len]
	return ret, nil
}

func (in *Inotify) worker() {
	defer recover()
	for {
		ev, err := in.read()
		in.ch <- chev{ev, err} // will panic on .Close()
	}
}

// Read calls either ReadBlock or ReadNonblock, depending on whether
// the Inotify instance has the IN_NONBLOCK flag set.
func (in *Inotify) Read() (Event, error) {
	if in.fdBlock {
		return in.ReadBlock()
	} else {
		return in.ReadNonblock()
	}
}

// ReadBlock reads exactly one Event or an error from the inotify
// instance.  If an Event or error is not available for reading, it
// blocks until one is.
//
// If err is non-nil, then the Event is invalid (ev.Wd < 0); and if
// err is nil, then then the Event is valid.
func (in *Inotify) ReadBlock() (Event, error) {
	e, ok := <-in.ch
	if !ok {
		return Event{Wd: -1}, os.NewSyscallError("read", syscall.EBADF)
	}
	return e.Ev, e.Err
}

// ReadNonblock reads either an Event or an error from the inotify
// instance, but doesn't block if an event isn't ready.
//
// Unlike Read, it may be the case that both the Event is invalid and
// the error is nil.  This indicates that the read simply returned
// instead of blocking.
//
//     ev, err := in.ReadNonblock()
//     haveRead = ev.Wd >= 0 || err != nil
func (in *Inotify) ReadNonblock() (Event, error) {
	select {
	case e, ok := <-in.ch:
		if !ok {
			return Event{Wd: -1}, os.NewSyscallError("read", syscall.EBADF)
		}
		return e.Ev, e.Err
	default:
		return Event{Wd: -1}, nil
	}
}