1 /*******************************************************************************
2 Copyright (c) 2011-2014 Dmitry Matveev <me@dmitrymatveev.co.uk>
3 Copyright (c) 2014-2018 Vladimir Kondratyev <vladimir@kondratyev.su>
4 SPDX-License-Identifier: MIT
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 THE SOFTWARE.
23 *******************************************************************************/
24
25 #include "compat.h"
26
27 #include <errno.h> /* errno */
28 #include <fcntl.h> /* open */
29 #include <unistd.h> /* close */
30 #include <string.h> /* strdup */
31 #include <stdlib.h> /* free */
32 #include <assert.h>
33
34 #include <sys/types.h>
35 #include <sys/event.h> /* kevent */
36 #include <sys/stat.h> /* stat */
37 #include <stdio.h> /* snprintf */
38
39 #include "utils.h"
40 #include "watch.h"
41 #include "sys/inotify.h"
42
43 /**
44 * Convert the inotify watch mask to the kqueue event filter flags.
45 *
46 * @param[in] flags An inotify watch mask.
47 * @param[in] wf A kqueue watch internal flags.
48 * @return Converted kqueue event filter flags.
49 **/
50 uint32_t
inotify_to_kqueue(uint32_t flags,watch_flags_t wf)51 inotify_to_kqueue (uint32_t flags, watch_flags_t wf)
52 {
53 uint32_t result = 0;
54
55 if (!(S_ISREG (wf) || S_ISDIR (wf) || S_ISLNK (wf))) {
56 return result;
57 }
58
59 #ifdef NOTE_OPEN
60 if (flags & IN_OPEN)
61 result |= NOTE_OPEN;
62 #endif
63 #ifdef NOTE_CLOSE
64 if (flags & IN_CLOSE_NOWRITE)
65 result |= NOTE_CLOSE;
66 #endif
67 #ifdef NOTE_CLOSE_WRITE
68 if (flags & IN_CLOSE_WRITE && S_ISREG (wf))
69 result |= NOTE_CLOSE_WRITE;
70 #endif
71 #ifdef NOTE_READ
72 if (flags & IN_ACCESS && (S_ISREG (wf) || S_ISDIR (wf)))
73 result |= NOTE_READ;
74 #endif
75 if (flags & IN_ATTRIB)
76 result |= NOTE_ATTRIB;
77 if (flags & IN_MODIFY && S_ISREG (wf))
78 result |= NOTE_WRITE;
79 if (!(wf & WF_ISSUBWATCH)) {
80 if (S_ISDIR (wf)) {
81 result |= NOTE_WRITE;
82 #if defined(HAVE_NOTE_EXTEND_ON_MOVE_TO) || \
83 defined(HAVE_NOTE_EXTEND_ON_MOVE_FROM)
84 result |= NOTE_EXTEND;
85 #endif
86 }
87 if (flags & IN_ATTRIB && S_ISREG (wf))
88 result |= NOTE_LINK;
89 if (flags & IN_MOVE_SELF)
90 result |= NOTE_RENAME;
91 result |= NOTE_DELETE | NOTE_REVOKE;
92 }
93 return result;
94 }
95
96 /**
97 * Convert the kqueue event filter flags to the inotify watch mask.
98 *
99 * @param[in] flags A kqueue filter flags.
100 * @param[in] wf A kqueue watch internal flags.
101 * @return Converted inotify watch mask.
102 **/
103 uint32_t
kqueue_to_inotify(uint32_t flags,watch_flags_t wf)104 kqueue_to_inotify (uint32_t flags, watch_flags_t wf)
105 {
106 uint32_t result = 0;
107
108 #ifdef NOTE_OPEN
109 if (flags & NOTE_OPEN)
110 result |= IN_OPEN;
111 #endif
112 #ifdef NOTE_CLOSE
113 if (flags & NOTE_CLOSE)
114 result |= IN_CLOSE_NOWRITE;
115 #endif
116 #ifdef NOTE_CLOSE_WRITE
117 if (flags & NOTE_CLOSE_WRITE)
118 result |= IN_CLOSE_WRITE;
119 #endif
120 #ifdef NOTE_READ
121 if (flags & NOTE_READ && (S_ISREG (wf) || S_ISDIR (wf)))
122 result |= IN_ACCESS;
123 #endif
124
125 if (flags & NOTE_ATTRIB || /* attribute changes */
126 (flags & (NOTE_LINK | NOTE_DELETE) && /* link number changes */
127 S_ISREG (wf) && !(wf & WF_ISSUBWATCH)))
128 result |= IN_ATTRIB;
129
130 if (flags & NOTE_WRITE && S_ISREG (wf))
131 result |= IN_MODIFY;
132
133 /* Do not issue IN_DELETE_SELF if links still exist */
134 if (flags & NOTE_DELETE && !(wf & WF_ISSUBWATCH) &&
135 (wf & WF_DELETED || !S_ISREG (wf)))
136 result |= IN_DELETE_SELF;
137
138 if (flags & NOTE_RENAME && !(wf & WF_ISSUBWATCH))
139 result |= IN_MOVE_SELF;
140
141 if (flags & NOTE_REVOKE && !(wf & WF_ISSUBWATCH))
142 result |= IN_UNMOUNT;
143
144 /* IN_ISDIR flag for subwatches is set in the enqueue_event routine */
145 if ((result & (IN_ATTRIB | IN_OPEN | IN_ACCESS | IN_CLOSE))
146 && S_ISDIR (wf) && !(wf & WF_ISSUBWATCH)) {
147 result |= IN_ISDIR;
148 }
149
150 return result;
151 }
152
153 /* struct kevent is declared slightly differently on the different BSDs.
154 * This macros will help to avoid cast warnings on the supported platforms. */
155 #if defined (__NetBSD__)
156 #define PTR_TO_UDATA(X) ((intptr_t)X)
157 #else
158 #define PTR_TO_UDATA(X) (X)
159 #endif
160
161 /**
162 * Register vnode kqueue watch in kernel kqueue(2) subsystem
163 *
164 * @param[in] w A pointer to a watch
165 * @param[in] fflags A filter flags in kqueue format
166 * @return 1 on success, -1 on error and 0 if no events have been registered
167 **/
168 int
watch_register_event(watch * w,uint32_t fflags)169 watch_register_event (watch *w, uint32_t fflags)
170 {
171 assert (w != NULL);
172 int kq = w->iw->wrk->kq;
173 assert (kq != -1);
174
175 struct kevent ev;
176
177 EV_SET (&ev,
178 w->fd,
179 EVFILT_VNODE,
180 EV_ADD | EV_ENABLE | EV_CLEAR,
181 fflags,
182 0,
183 PTR_TO_UDATA (w));
184
185 return kevent (kq, &ev, 1, NULL, 0, NULL);
186 }
187
188 /**
189 * Opens a file descriptor of kqueue watch
190 *
191 * @param[in] dirfd A filedes of parent directory or AT_FDCWD.
192 * @param[in] path A pointer to filename
193 * @param[in] flags A watch flags in inotify format
194 * @return A file descriptor of opened kqueue watch
195 **/
196 int
watch_open(int dirfd,const char * path,uint32_t flags)197 watch_open (int dirfd, const char *path, uint32_t flags)
198 {
199 assert (path != NULL);
200
201 int openflags = O_NONBLOCK;
202 #ifdef O_EVTONLY
203 openflags |= O_EVTONLY;
204 #else
205 openflags |= O_RDONLY;
206 #endif
207 #ifdef O_CLOEXEC
208 openflags |= O_CLOEXEC;
209 #endif
210 if (flags & IN_DONT_FOLLOW) {
211 #ifdef O_SYMLINK
212 openflags |= O_SYMLINK;
213 #else
214 openflags |= O_NOFOLLOW;
215 #endif
216 }
217 #ifdef O_DIRECTORY
218 if (flags & IN_ONLYDIR) {
219 openflags |= O_DIRECTORY;
220 }
221 #endif
222
223 int fd = openat (dirfd, path, openflags);
224 if (fd == -1) {
225 return -1;
226 }
227
228 #ifndef O_DIRECTORY
229 if (flags & IN_ONLYDIR) {
230 struct stat st;
231 if (fstat (fd, &st) == -1) {
232 perror_msg ("Failed to fstat on watch open %s", path);
233 close (fd);
234 return -1;
235 }
236
237 if (!S_ISDIR (st.st_mode)) {
238 errno = ENOTDIR;
239 close (fd);
240 return -1;
241 }
242 }
243 #endif
244
245 #ifndef O_CLOEXEC
246 if (set_cloexec_flag (fd, 1) == -1) {
247 close (fd);
248 return -1;
249 }
250 #endif
251
252 return fd;
253 }
254
255 /**
256 * Initialize a watch.
257 *
258 * @param[in] iw; A backreference to parent #i_watch.
259 * @param[in] watch_type The type of the watch.
260 * @param[in] fd A file descriptor of a watched entry.
261 * @param[in] st A stat structure of watch.
262 * @return A pointer to a watch on success, NULL on failure.
263 **/
264 watch *
watch_init(i_watch * iw,watch_type_t watch_type,int fd,struct stat * st)265 watch_init (i_watch *iw, watch_type_t watch_type, int fd, struct stat *st)
266 {
267 assert (iw != NULL);
268 assert (fd != -1);
269
270 watch_flags_t wf = watch_type != WATCH_USER ? WF_ISSUBWATCH : 0;
271 wf |= st->st_mode & S_IFMT;
272
273 uint32_t fflags = inotify_to_kqueue (iw->flags, wf);
274 /* Skip watches with empty kqueue filter flags */
275 if (fflags == 0) {
276 return NULL;
277 }
278
279 watch *w = calloc (1, sizeof (struct watch));
280 if (w == NULL) {
281 perror_msg ("Failed to allocate watch");
282 return NULL;
283 }
284
285 w->iw = iw;
286 w->fd = fd;
287 w->flags = wf;
288 w->refcount = 0;
289 /* Inode number obtained via fstat call cannot be used here as it
290 * differs from readdir`s one at mount points. */
291 w->inode = st->st_ino;
292
293 if (watch_register_event (w, fflags) == -1) {
294 free (w);
295 return NULL;
296 }
297
298 return w;
299 }
300
301 /**
302 * Free a watch and all the associated memory.
303 *
304 * @param[in] w A pointer to a watch.
305 **/
306 void
watch_free(watch * w)307 watch_free (watch *w)
308 {
309 assert (w != NULL);
310 if (w->fd != -1) {
311 close (w->fd);
312 }
313 free (w);
314 }
315