1 /*
2 * Copyright (c) 2009-2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * These routines, accessed through path_node_create() and path_node_release(),
26 * provide an API for monitoring a path in the filesystem. The path may contain
27 * directories and symbolic links. The path may not even exist! If the path
28 * does exist, this code will respond to path deletions, component renaming, and
29 * access control changes that either delete the path or make it inaccessible to a
30 * target user/group. A notification will be provided if the path comes back into
31 * existance or again becomes accessible.
32 *
33 * path_node_create() returns a path_node_t object, which contains a dispatch_source_t.
34 * This source behaves very much like a DISPATCH_SOURCE_TYPE_VNODE, except that it also
35 * triggers on the creation of a path.
36 *
37 * Internally, the work of monitoring a path is done by a set of helper vnode_t
38 * objects. A vnode_t contains a dispatch_source_t (of type DISPATCH_SOURCE_TYPE_VNODE)
39 * for a particular vnode. When a path_node_t is created, it creates (or shares)
40 * vnode_t objects for each component of the desired path. For example, a path_node_t
41 * for "/a/b/c" will create (or share, if some other path_node_t has already created) a
42 * dispatch_source_t for "/", "/a", "/a/b", and "/a/b/c". If any of these sources is
43 * notified of a change, the vnode_t will trigger an update for all path_node_t
44 * objects that contain that path component.
45 *
46 * When a path_node_t update is triggered by a vnode_t component, the node re-evaluates
47 * the target path that it is charged with monitoring. If the path exists and the end-point
48 * vnode changed, then the update operation will trigger its dispatch_source_t to notify the
49 * end-user of the change. If an intermediate path component is removed, renamed, or becomes
50 * blocked by an access-control change, then the end-point dispatch_source_t is triggered to
51 * indicate that the path has been deleted. However, the path_node_t remains active and
52 * monitors the path components that still exist. Eventually, if the path is recreated or
53 * if access controls change so that the path becomes visible to the target user, then the
54 * end-point dispatch_source_t is triggered with a PATH_NODE_CREATE bit set in its data flags.
55 *
56 * path_node_releases() releases a path_node_t object and all of the vnode_t objects
57 * that were monitoring components of its target path.
58 */
59
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <errno.h>
64 #include <sys/stat.h>
65 #include <sys/param.h>
66 #include <sys/syscall.h>
67 //#include <sys/kauth.h>
68 #include <pwd.h>
69 #include <fcntl.h>
70 #include <assert.h>
71 //#include <tzfile.h>
72 #include "pathwatch.h"
73
74 #define forever for(;;)
75 #define streq(A,B) (strcmp(A,B)==0)
76 #define DISPATCH_VNODE_ALL 0x7f
77
78 #define PATH_STAT_OK 0
79 #define PATH_STAT_FAILED 1
80 #define PATH_STAT_ACCESS 2
81
82 #define VPATH_NODE_TYPE_REG 0
83 #define VPATH_NODE_TYPE_LINK 1
84 #define VPATH_NODE_TYPE_DELETED 2
85
86 #define DISPATCH_VNODE_UNAVAIL (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE)
87
88 /* Libinfo global */
89 extern uint32_t gL1CacheEnabled;
90
91 /*
92 * vnode_t represents a vnode.
93 *
94 * The dispatch source is of type DISPATCH_SOURCE_TYPE_VNODE for file descriptor fd.
95 * The handler for the source triggers an update routine for all the path_node_t
96 * objects in the path_node list.
97 */
98 typedef struct
99 {
100 char *path;
101 uint32_t type;
102 int fd;
103 struct timespec mtime;
104 struct timespec ctime;
105 dispatch_source_t src;
106 uint32_t path_node_count;
107 path_node_t **path_node;
108 } vnode_t;
109
110 static struct
111 {
112 dispatch_once_t pathwatch_init;
113 dispatch_queue_t pathwatch_queue;
114 uint32_t vnode_count;
115 vnode_t **vnode;
116 char *tzdir;
117 size_t tzdir_len;
118 } _global = {0};
119
120 /* forward */
121 static void _path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode);
122
123 /*
124 * stat() or lstat() a path as a particular user/group.
125 */
126 static int
_path_stat(const char * path,int link,uid_t uid,gid_t gid)127 _path_stat(const char *path, int link, uid_t uid, gid_t gid)
128 {
129 struct stat sb;
130 gid_t orig_gidset[NGROUPS_MAX];
131 int ngroups, status, stat_status;
132 struct passwd *p;
133 uint32_t orig_cache_enabled;
134
135 /* disable L1 cache to avoid notification deadlock */
136 orig_cache_enabled = gL1CacheEnabled;
137 gL1CacheEnabled = 0;
138
139 /* get my group list */
140 memset(orig_gidset, 0, sizeof(orig_gidset));
141 ngroups = getgroups(NGROUPS_MAX, orig_gidset);
142 if (ngroups < 0)
143 {
144 return PATH_STAT_FAILED;
145 }
146
147 /* look up user name */
148 p = getpwuid(uid);
149 if (p == NULL)
150 {
151 gL1CacheEnabled = orig_cache_enabled;
152 return PATH_STAT_FAILED;
153 }
154
155 /* switch to user's grouplist */
156 status = initgroups(p->pw_name, gid);
157 if (status < 0)
158 {
159 gL1CacheEnabled = orig_cache_enabled;
160 return PATH_STAT_FAILED;
161 }
162
163 /* reset gL1CacheEnabled */
164 gL1CacheEnabled = orig_cache_enabled;
165
166 /* set thread credentials */
167 pthread_setugid_np(uid, gid);
168
169 /* stat the file */
170 stat_status = -1;
171 if (link != 0)
172 {
173 stat_status = lstat(path, &sb);
174 }
175 else
176 {
177 stat_status = stat(path, &sb);
178 }
179
180 /* unset thread credentials */
181 pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE);
182
183 /* restore original grouplist for UID 0 */
184 status = syscall(SYS_initgroups, ngroups, orig_gidset, 0);
185 if (status < 0)
186 {
187 return PATH_STAT_FAILED;
188 }
189
190 /* return status */
191 if (stat_status == 0)
192 {
193 return PATH_STAT_OK;
194 }
195
196 if (errno == EACCES)
197 {
198 return PATH_STAT_ACCESS;
199 }
200
201 return PATH_STAT_FAILED;
202 }
203
204 /*
205 * Check access to a path by a particular user/group.
206 * Sets ftype output parameter if it is non-NULL.
207 */
208 static int
_path_stat_check_access(const char * path,uid_t uid,gid_t gid,uint32_t * ftype)209 _path_stat_check_access(const char *path, uid_t uid, gid_t gid, uint32_t *ftype)
210 {
211 struct stat sb;
212 char buf[MAXPATHLEN + 1];
213 int status, t;
214
215 if (path == NULL) return PATH_STAT_FAILED;
216
217 if (ftype != NULL) *ftype = PATH_NODE_TYPE_GHOST;
218
219 /* Paths must be absolute */
220 if (path[0] != '/') return PATH_STAT_FAILED;
221
222 /* Root dir is readable */
223 if (path[1] == '\0')
224 {
225 if (ftype != NULL) *ftype = PATH_NODE_TYPE_DIR;
226 return PATH_STAT_OK;
227 }
228
229 memset(&sb, 0, sizeof(struct stat));
230 status = lstat(path, &sb);
231
232 if (status != 0) return PATH_STAT_FAILED;
233 else if ((sb.st_mode & S_IFMT) == S_IFDIR) t = PATH_NODE_TYPE_DIR;
234 else if ((sb.st_mode & S_IFMT) == S_IFREG) t = PATH_NODE_TYPE_FILE;
235 else if ((sb.st_mode & S_IFMT) == S_IFLNK) t = PATH_NODE_TYPE_LINK;
236 else t = PATH_NODE_TYPE_OTHER;
237
238 if (ftype != NULL) *ftype = t;
239
240 if (t == PATH_NODE_TYPE_OTHER) return PATH_STAT_FAILED;
241
242 /* skip access control check if uid is zero */
243 if (uid == 0) return 0;
244
245 /* special case: anything in the timezone directory is OK */
246 memset(buf, 0, sizeof(buf));
247 if (realpath(path, buf) == NULL) return PATH_STAT_FAILED;
248 if ((_global.tzdir != NULL) && (!strncasecmp(buf, _global.tzdir, _global.tzdir_len)))
249 {
250 return PATH_STAT_OK;
251 }
252
253 /* call _path_stat to check access as the user/group provided */
254 if (t == PATH_NODE_TYPE_FILE)
255 {
256 status = _path_stat(path, 0, uid, gid);
257 if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
258 return status;
259 }
260 else if (t == PATH_NODE_TYPE_LINK)
261 {
262 status = _path_stat(path, 1, uid, gid);
263 if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
264 return status;
265 }
266 else if (t == PATH_NODE_TYPE_DIR)
267 {
268 snprintf(buf, MAXPATHLEN, "%s/.", path);
269 status = _path_stat(buf, 0, uid, gid);
270 if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
271 return status;
272 }
273
274 /* we don't ever get here, but... */
275 return PATH_STAT_FAILED;
276 }
277
278 /*
279 * Uniquely add a pnode to a vnode's list of path nodes.
280 */
281 static void
_vnode_add_pnode(vnode_t * vnode,path_node_t * pnode)282 _vnode_add_pnode(vnode_t *vnode, path_node_t *pnode)
283 {
284 uint32_t i;
285
286 for (i = 0; i < vnode->path_node_count; i++)
287 {
288 if (vnode->path_node[i] == pnode) return;
289 }
290
291 for (i = 0; i < vnode->path_node_count; i++)
292 {
293 if (vnode->path_node[i] == NULL)
294 {
295 vnode->path_node[i] = pnode;
296 return;
297 }
298 }
299
300 if (vnode->path_node_count == 0)
301 {
302 vnode->path_node = (path_node_t **)calloc(1, sizeof(path_node_t *));
303 }
304 else
305 {
306 vnode->path_node = (path_node_t **)reallocf(vnode->path_node, (vnode->path_node_count + 1) * sizeof(path_node_t *));
307 }
308
309 assert(vnode->path_node != NULL);
310
311 vnode->path_node[vnode->path_node_count++] = pnode;
312 }
313
314 /*
315 * Free a vnode_t and cancel/release its dispatch source.
316 */
317 static void
_vnode_free(vnode_t * vnode)318 _vnode_free(vnode_t *vnode)
319 {
320 dispatch_source_cancel(vnode->src);
321
322 /*
323 * Actually free the vnode on the pathwatch queue. This allows any
324 * enqueued _vnode_event operations to complete before the vnode disappears.
325 * _vnode_event() quietly returns if the source has been cancelled.
326 */
327 dispatch_async(_global.pathwatch_queue, ^{
328 dispatch_release(vnode->src);
329 free(vnode->path);
330 free(vnode->path_node);
331 free(vnode);
332 });
333 }
334
335 /*
336 * Handler routine for vnode_t objects.
337 * Invokes the _path_node_update routine for all of the vnode's pnodes.
338 */
339 static void
_vnode_event(vnode_t * vnode)340 _vnode_event(vnode_t *vnode)
341 {
342 uint32_t i, flags;
343 unsigned long ulf;
344 struct stat sb;
345
346 if (vnode == NULL) return;
347 if ((vnode->src != NULL) && (dispatch_source_testcancel(vnode->src))) return;
348
349 ulf = dispatch_source_get_data(vnode->src);
350 flags = ulf;
351
352 memset(&sb, 0, sizeof(struct stat));
353 if (fstat(vnode->fd, &sb) == 0)
354 {
355 if ((vnode->mtime.tv_sec != sb.st_mtimespec.tv_sec) || (vnode->mtime.tv_nsec != sb.st_mtimespec.tv_nsec))
356 {
357 flags |= PATH_NODE_MTIME;
358 vnode->mtime = sb.st_mtimespec;
359 }
360
361 if ((vnode->ctime.tv_sec != sb.st_ctimespec.tv_sec) || (vnode->ctime.tv_nsec != sb.st_ctimespec.tv_nsec))
362 {
363 flags |= PATH_NODE_CTIME;
364 vnode->ctime = sb.st_ctimespec;
365 }
366 }
367
368 /*
369 * Flag deleted sources.
370 * We can't delete them here, since _path_node_update may need them.
371 * However, _path_node_update will release them and they will get cleaned
372 * up in a _vnode_sweep later on.
373 */
374 if (flags & DISPATCH_VNODE_DELETE) vnode->type = VPATH_NODE_TYPE_DELETED;
375
376 for (i = 0; i < vnode->path_node_count; i++)
377 {
378 _path_node_update(vnode->path_node[i], flags, vnode);
379 }
380 }
381
382 /*
383 * Creates a vnode_t object.
384 */
385 static vnode_t *
_vnode_create(const char * path,uint32_t type,path_node_t * pnode)386 _vnode_create(const char *path, uint32_t type, path_node_t *pnode)
387 {
388 int fd, flags;
389 uint32_t i;
390 vnode_t *vnode;
391 dispatch_source_t src;
392 struct stat sb;
393
394 if (path == NULL) path = "/";
395 if (path[0] == '\0') path = "/";
396
397 for (i = 0; i < _global.vnode_count; i++)
398 {
399 vnode = _global.vnode[i];
400 if (vnode == NULL) continue;
401
402 if ((vnode->type == type) && (streq(path, vnode->path)))
403 {
404 _vnode_add_pnode(vnode, pnode);
405 return vnode;
406 }
407 }
408
409 vnode = NULL;
410
411 flags = O_EVTONLY;
412 if (type == VPATH_NODE_TYPE_LINK) flags |= O_SYMLINK;
413
414 fd = open(path, flags, 0);
415 if (fd < 0) return NULL;
416
417 src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, (uintptr_t)fd, DISPATCH_VNODE_ALL, _global.pathwatch_queue);
418 if (src == NULL)
419 {
420 close(fd);
421 return NULL;
422 }
423
424 vnode = (vnode_t *)calloc(1, sizeof(vnode_t));
425 assert(vnode != NULL);
426
427 vnode->type = type;
428 vnode->path = strdup(path);
429 assert(vnode->path != NULL);
430
431 vnode->fd = fd;
432 vnode->src = src;
433
434 memset(&sb, 0, sizeof(struct stat));
435 if (fstat(fd, &sb) == 0)
436 {
437 vnode->mtime = sb.st_mtimespec;
438 vnode->ctime = sb.st_ctimespec;
439 }
440
441 _vnode_add_pnode(vnode, pnode);
442
443 dispatch_source_set_event_handler(src, ^{ _vnode_event(vnode); });
444 dispatch_source_set_cancel_handler(src, ^{ close(fd); });
445
446 if (_global.vnode_count == 0)
447 {
448 _global.vnode = (vnode_t **)calloc(1, sizeof(vnode_t *));
449 }
450 else
451 {
452 _global.vnode = (vnode_t **)reallocf(_global.vnode, (_global.vnode_count + 1) * sizeof(vnode_t *));
453 }
454
455 assert(_global.vnode != NULL);
456
457 _global.vnode[_global.vnode_count++] = vnode;
458
459 dispatch_resume(src);
460
461 return vnode;
462 }
463
464 static vnode_t *
_vnode_create_real_path(const char * path,uint32_t type,path_node_t * pnode)465 _vnode_create_real_path(const char *path, uint32_t type, path_node_t *pnode)
466 {
467 char real[MAXPATHLEN + 1];
468
469 if (path == NULL) return _vnode_create(path, type, pnode);
470
471 if (NULL != realpath(path, real)) return _vnode_create(real, type, pnode);
472
473 return NULL;
474 }
475
476 /*
477 * Examines all the vnode_t objects (held in the _global data),
478 * frees any that have no path nodes.
479 */
480 static void
_vnode_sweep()481 _vnode_sweep()
482 {
483 uint32_t i, j, new_vnode_count, new_path_node_count;
484 vnode_t **new_source, *vnode;
485 path_node_t **new_path_node;
486
487 new_source = NULL;
488
489 for (i = 0; i < _global.vnode_count; i++)
490 {
491 vnode = _global.vnode[i];
492 if (vnode == NULL) continue;
493
494 new_path_node_count = 0;
495 new_path_node = NULL;
496
497 for (j = 0; j < vnode->path_node_count; j++)
498 {
499 if (vnode->path_node[j] != NULL) new_path_node_count++;
500 }
501
502 if (new_path_node_count == vnode->path_node_count)
503 {
504 /* no change */
505 continue;
506 }
507 else if (new_path_node_count > 0)
508 {
509 new_path_node = (path_node_t **)calloc(new_path_node_count, sizeof(path_node_t *));
510 assert(new_path_node != NULL);
511
512 new_path_node_count = 0;
513 for (j = 0; j < vnode->path_node_count; j++)
514 {
515 if (vnode->path_node[j] != NULL)
516 {
517 new_path_node[new_path_node_count++] = vnode->path_node[j];
518 }
519 }
520 }
521
522 free(vnode->path_node);
523 vnode->path_node = new_path_node;
524 vnode->path_node_count = new_path_node_count;
525 }
526
527 new_vnode_count = 0;
528 for (i = 0; i < _global.vnode_count; i++)
529 {
530 vnode = _global.vnode[i];
531 if (vnode == NULL) continue;
532 if (vnode->path_node_count > 0) new_vnode_count++;
533 }
534
535 if (new_vnode_count == _global.vnode_count)
536 {
537 /* no change */
538 return;
539 }
540 else if (new_vnode_count > 0)
541 {
542 new_source = (vnode_t **)calloc(new_vnode_count, sizeof(vnode_t *));
543 assert(new_source != NULL);
544
545 new_vnode_count = 0;
546 for (i = 0; i < _global.vnode_count; i++)
547 {
548 vnode = _global.vnode[i];
549 if (vnode == NULL) continue;
550
551 if (vnode->path_node_count > 0)
552 {
553 new_source[new_vnode_count++] = vnode;
554 }
555 else
556 {
557 _vnode_free(vnode);
558 }
559 }
560 }
561
562 free(_global.vnode);
563 _global.vnode = new_source;
564 _global.vnode_count = new_vnode_count;
565 }
566
567 /*
568 * Releases sources that have a particular node on their list.
569 * This is a deferred release mechanism for vnode_t objects.
570 * The calling routine must call _vnode_sweep subsequent to
571 * calling this routine.
572 * _vnode_sweep will actually free any vnode_t objects
573 * that have a no path nodes.
574 */
575 static void
_vnode_release_for_node(path_node_t * pnode)576 _vnode_release_for_node(path_node_t *pnode)
577 {
578 uint32_t i, j;
579 vnode_t *vnode;
580
581 for (i = 0; i < _global.vnode_count; i++)
582 {
583 vnode = _global.vnode[i];
584 if (vnode == NULL) continue;
585
586 for (j = 0; j < vnode->path_node_count; j++)
587 {
588 if (vnode->path_node[j] == pnode)
589 {
590 vnode->path_node[j] = NULL;
591 break;
592 }
593 }
594 }
595 }
596
597 /*
598 * Retain a path_node_t object.
599 * Dispatched on _global.pathwatch_queue.
600 */
601 static void
_path_node_retain(path_node_t * pnode)602 _path_node_retain(path_node_t *pnode)
603 {
604 if (pnode == NULL) return;
605 pnode->refcount++;
606 }
607
608 /*
609 * Free a path_node_t object.
610 * Dispatched on _global.pathwatch_queue.
611 */
612 static void
_path_node_free(path_node_t * pnode)613 _path_node_free(path_node_t *pnode)
614 {
615 uint32_t i, n;
616
617 if (pnode == NULL) return;
618
619 /*
620 * Remove this path node from all vnodes.
621 */
622 _vnode_release_for_node(pnode);
623 _vnode_sweep();
624
625 free(pnode->path);
626
627 if (pnode->pname != NULL)
628 {
629 n = pnode->pname_count;
630 pnode->pname_count = 0;
631
632 for (i = 0; i < n; i++)
633 {
634 free(pnode->pname[i]);
635 pnode->pname[i] = NULL;
636 }
637
638 free(pnode->pname);
639 }
640
641 free(pnode->contextp);
642
643 dispatch_release(pnode->src);
644 dispatch_release(pnode->src_queue);
645
646 memset(pnode, 0, sizeof(path_node_t));
647 free(pnode);
648 }
649
650 /*
651 * Release a path_node_t object.
652 */
653 static void
_path_node_release(path_node_t * pnode)654 _path_node_release(path_node_t *pnode)
655 {
656 if (pnode == NULL) return;
657
658 /*
659 * We need to make sure that the node's event handler isn't currently
660 * executing before freeing the node. We dispatch on the src_queue, so
661 * that when the block executes there will be no more events in the queue.
662 * From there, we dispatch async back to the pathwatch_queue to do the
663 * data structure cleanup.
664 */
665 dispatch_async(pnode->src_queue, ^{
666 dispatch_async(_global.pathwatch_queue, ^{
667 if (pnode->refcount > 0) pnode->refcount--;
668 if (pnode->refcount == 0) _path_node_free(pnode);
669 });
670 });
671 }
672
673 /*
674 * Frees a path_node_t object.
675 * The work is actually done on the global pathwatch_queue to make this safe.
676 */
677 void
path_node_close(path_node_t * pnode)678 path_node_close(path_node_t *pnode)
679 {
680 if (pnode == NULL) return;
681
682 if (pnode->src != NULL) dispatch_source_cancel(pnode->src);
683 _path_node_release(pnode);
684 }
685
686 static void
_pathwatch_init()687 _pathwatch_init()
688 {
689 char buf[MAXPATHLEN];
690
691 /* Create serial queue for node creation / deletion operations */
692 _global.pathwatch_queue = dispatch_queue_create("pathwatch", NULL);
693
694 _global.tzdir = NULL;
695 _global.tzdir_len = 0;
696
697 /* Get the real path to TZDIR */
698 if (realpath(TZDIR, buf) != NULL)
699 {
700 _global.tzdir_len = strlen(buf);
701 _global.tzdir = strdup(buf);
702 if (_global.tzdir == NULL) _global.tzdir_len = 0;
703 }
704 }
705
706 /*
707 * _path_node_init is responsible for allocating a path_node_t structure,
708 * and for creating the pname array and setting the path component.
709 * The path is a sanatized version of the caller's path with redundant "/"
710 * characters stripped out. The pname array contains each "/" separated
711 * component of the path.
712 *
713 * For example, _path_node_init("///foo////bar//baz/") creates:
714 * pnode->path = "/foo/bar/baz"
715 * pnode->pname_count = 3
716 * pnode->pname[0] = "foo"
717 * pnode->pname[1] = "bar"
718 * pnode->pname[2] = "baz"
719 */
720 static path_node_t *
_path_node_init(const char * path)721 _path_node_init(const char *path)
722 {
723 size_t len;
724 uint32_t i;
725 path_node_t *pnode;
726 const char *start, *end;
727 char *name;
728
729 if (path == NULL) path = "/";
730 if (path[0] != '/') return NULL;
731
732 pnode = (path_node_t *)calloc(1, sizeof(path_node_t));
733 assert(pnode != NULL);
734
735 pnode->plen = 1;
736 start = path;
737 while (*start == '/') start++;
738
739 forever
740 {
741 end = strchr(start, '/');
742 if (end == NULL) end = strchr(start, '\0');
743
744 len = end - start;
745 if (len == 0) break;
746
747 pnode->plen += (len + 1);
748
749 name = NULL;
750 if (end == NULL)
751 {
752 name = strdup(start);
753 }
754 else
755 {
756 name = malloc(len + 1);
757 assert(name != NULL);
758 strncpy(name, start, len);
759 name[len] = '\0';
760 }
761
762 if (pnode->pname_count == 0)
763 {
764 pnode->pname = (char **)calloc(1, sizeof(char *));
765 }
766 else
767 {
768 pnode->pname = (char **)reallocf(pnode->pname, (pnode->pname_count + 1) * sizeof(char *));
769 }
770
771 assert(pnode->pname != NULL);
772 pnode->pname[pnode->pname_count] = name;
773 pnode->pname_count++;
774
775 start = end;
776 if (start != NULL)
777 {
778 /* skip '/' chars */
779 while (*start == '/') start++;
780 }
781 }
782
783 pnode->path = calloc(1, pnode->plen);
784 assert(pnode->path != NULL);
785 /*
786 * Reconstruct the path here to strip out excess "/" chars.
787 * This ensures that path comparisons in _path_node_update are correct.
788 */
789 for (i = 0; i < pnode->pname_count; i++)
790 {
791 strlcat(pnode->path, "/", pnode->plen);
792 strlcat(pnode->path, pnode->pname[i], pnode->plen);
793 }
794
795 return pnode;
796 }
797
798 /* dispatched on _global.pathwatch_queue */
799 static void
_path_node_update(path_node_t * pnode,uint32_t flags,vnode_t * vnode)800 _path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode)
801 {
802 char *buf, fixed[MAXPATHLEN + 1];
803 uint32_t i, old_type;
804 int status;
805 unsigned long data;
806 struct stat sb;
807
808 if (pnode == NULL) return;
809 if ((pnode->src != NULL) && (dispatch_source_testcancel(pnode->src))) return;
810
811 old_type = pnode->type;
812
813 status = _path_stat_check_access(pnode->path, pnode->uid, pnode->gid, &(pnode->type));
814 if (status == PATH_STAT_ACCESS) flags |= DISPATCH_VNODE_REVOKE;
815
816 data = 0;
817
818 if (vnode != NULL)
819 {
820 /* update status */
821
822 if (flags & DISPATCH_VNODE_UNAVAIL)
823 {
824 pnode->type = PATH_NODE_TYPE_GHOST;
825 data |= (flags & DISPATCH_VNODE_UNAVAIL);
826 data |= DISPATCH_VNODE_DELETE;
827 }
828
829 if ((vnode->path != NULL) && (pnode->path != NULL) && streq(vnode->path, pnode->path))
830 {
831 /* this is the target VNODE - transfer flags to src data */
832 data |= flags;
833 }
834
835 if (old_type == PATH_NODE_TYPE_GHOST)
836 {
837 /* transition from ghost to non-ghost */
838 if (pnode->type != PATH_NODE_TYPE_GHOST)
839 {
840 data |= PATH_NODE_CREATE;
841 }
842 else
843 {
844 data = 0;
845 }
846 }
847 else if (pnode->type == PATH_NODE_TYPE_GHOST)
848 {
849 /* transition from non-ghost to ghost */
850 data |= PATH_NODE_DELETE;
851 }
852
853 data &= (pnode->flags & PATH_NODE_ALL);
854 if (data != 0)
855 {
856 if ((pnode->flags & PATH_SRC_SUSPENDED) == 0)
857 {
858 /* suspend pnode->src, and fire it after PNODE_COALESCE_TIME */
859 pnode->flags |= PATH_SRC_SUSPENDED;
860 dispatch_suspend(pnode->src);
861
862 dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, PNODE_COALESCE_TIME);
863 _path_node_retain(pnode);
864
865 dispatch_after(delay, _global.pathwatch_queue, ^{
866 pnode->flags &= ~PATH_SRC_SUSPENDED;
867 dispatch_resume(pnode->src);
868 _path_node_release(pnode);
869 });
870 }
871
872 dispatch_source_merge_data(pnode->src, data);
873 }
874 }
875
876 buf = NULL;
877 if (pnode->plen < MAXPATHLEN) buf = fixed;
878 else buf = malloc(pnode->plen);
879 assert(buf != NULL);
880
881 /* "autorelease" current sources (_vnode_sweep() will delete those with zero refcount) */
882 _vnode_release_for_node(pnode);
883
884 /* create new sources (may re-use existing sources) */
885 _vnode_create(NULL, 0, pnode);
886
887 memset(buf, 0, pnode->plen);
888 for (i = 0; i < pnode->pname_count; i++)
889 {
890 assert((strlen(buf) + 1) <= pnode->plen);
891 strlcat(buf, "/", pnode->plen);
892
893 assert(pnode->pname[i] != NULL);
894 assert((strlen(buf) + strlen(pnode->pname[i])) <= pnode->plen);
895 strlcat(buf, pnode->pname[i], pnode->plen);
896
897 memset(&sb, 0, sizeof(struct stat));
898 if (lstat(buf, &sb) < 0)
899 {
900 /* the path stops existing here */
901 break;
902 }
903
904 if ((sb.st_mode & S_IFMT) == S_IFLNK)
905 {
906 /* open the symlink itself */
907 _vnode_create(buf, VPATH_NODE_TYPE_LINK, pnode);
908
909 /* open the symlink target */
910 _vnode_create_real_path(buf, 0, pnode);
911 }
912 else
913 {
914 _vnode_create(buf, 0, pnode);
915 }
916 }
917
918 /* sweep source list (deletes those with zero refcount) */
919 _vnode_sweep();
920
921 if (buf != fixed) free(buf);
922 }
923
924 /*
925 * Creates a dispatch source that activates when a path changes.
926 * Internally, creates a data structure (path_node_t) that represents the entire path.
927 * Also creates dispatch sources (vnode_t) for each path component. These vnodes may
928 * be shared with other path_node_t structures.
929 */
930 path_node_t *
path_node_create(const char * path,uid_t uid,gid_t gid,uint32_t mask,dispatch_queue_t queue)931 path_node_create(const char *path, uid_t uid, gid_t gid, uint32_t mask, dispatch_queue_t queue)
932 {
933 path_node_t *pnode;
934
935 dispatch_once(&(_global.pathwatch_init), ^{ _pathwatch_init(); });
936
937 pnode = _path_node_init(path);
938 if (pnode == NULL) return NULL;
939
940 pnode->refcount = 1;
941 pnode->uid = uid;
942 pnode->gid = gid;
943
944 dispatch_sync(_global.pathwatch_queue, ^{ _path_node_update(pnode, 0, NULL); });
945
946 dispatch_retain(queue);
947
948 pnode->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, queue);
949 pnode->src_queue = queue;
950 pnode->flags = mask & PATH_NODE_ALL;
951
952 return pnode;
953 }
954