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 */
89extern 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 */
98typedef 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
110static 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 */
121static 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 */
126static int
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 */
208static int
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 */
281static void
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 */
317static void
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 */
339static void
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 */
385static vnode_t *
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
464static vnode_t *
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 */
480static void
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 */
575static void
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 */
601static void
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 */
612static void
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 */
653static void
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 */
677void
678path_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
686static void
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 */
720static path_node_t *
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 */
799static void
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 */
930path_node_t *
931path_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