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