xref: /NextBSD/usr.sbin/notifyd/pathwatch.c (revision 33da5adc555b3bc29986eeadca03829e4ad06b1e)
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