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