1/* 2 * Copyright (c) 2004 Apple Computer, 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#include "webdavd.h" 25 26#include <sys/syslog.h> 27#include <sys/errno.h> 28#include <stdlib.h> 29#include <strings.h> 30#include <stdio.h> 31#include <time.h> 32#include <unistd.h> 33#include <pthread.h> 34 35#include "webdav_cache.h" 36#include "webdav_parse.h" 37#include "OpaqueIDs.h" 38#include "LogMessage.h" 39 40/*****************************************************************************/ 41 42static int open_cache_files = 0; 43 44pthread_mutex_t g_node_cache_lock; 45 46/*****************************************************************************/ 47 48/* 49 * The root node_entry. 50 * The rest of the node_entry entries will be in the tree below the root node_entry. 51 */ 52struct node_entry *g_root_node; 53 54/* 55 * The deleted root node_entry. 56 * When an open file is deleted (including when something is renamed over an 57 * existing node), it will be removed from the main tree and inserted as a child 58 * of this "fake" node_entry. URLs cannot be created for children of this node 59 * (but they should never need to talk to the server anyway -- they are deleted) 60 * Nodes are freed (using nodecache_free_nodes()) only when they are children of this node. 61 */ 62struct node_entry *g_deleted_root_node; 63 64/* 65 * The next fileid number to be assigned. 66*/ 67u_int32_t g_next_fileid; 68 69/* 70 * The file_cache_head list. 71 * Entries are stored in the order inserted into the list. 72 */ 73struct node_head g_file_list; 74 75/* static prototypes */ 76 77static int internal_add_attributes( 78 struct node_entry *node, 79 uid_t uid, 80 struct webdav_stat_attr *statp, 81 char *appledoubleheader); 82static int internal_remove_attributes( 83 struct node_entry *node, 84 int remove_appledoubleheader); 85static int internal_node_appledoubleheader_valid( 86 struct node_entry *node, 87 uid_t uid); 88static void invalidate_level( 89 struct node_entry *dir_node); 90static void internal_remove_file_cache( 91 struct node_entry *node); 92static int internal_add_file_cache( 93 struct node_entry *node, 94 int fd); 95static int internal_get_node( 96 struct node_entry *parent, 97 size_t name_length, 98 const char *name, 99 int make_entry, 100 int client_created, 101 webdav_filetype_t node_type, 102 struct node_entry **node); 103static void internal_free_nodes(void); 104static int internal_move_node( 105 struct node_entry *node, 106 struct node_entry *new_parent, 107 size_t new_name_length, 108 char *new_name); 109static int delete_node_tree( 110 struct node_entry *node, 111 int recursive); 112static int internal_invalidate_directory_node_time( 113 struct node_entry *dir_node); 114static int internal_delete_invalid_directory_nodes( 115 struct node_entry *dir_node); 116static int internal_get_path_from_node( 117 struct node_entry *target_node, 118 bool *pathHasRedirection, 119 char **path); 120static CFArrayRef internal_get_locktokens( 121 struct node_entry *a_node); 122 123static int init_node_cache_lock(void); 124 125/*****************************************************************************/ 126 127/* this adds or replaces attribute information to node */ 128static int internal_add_attributes( 129 struct node_entry *node, /* the node_entry to update or add attributes_entry to */ 130 uid_t uid, /* the uid these attributes are valid for */ 131 struct webdav_stat_attr *statp, /* the stat buffer */ 132 char *appledoubleheader) /* pointer appledoubleheader or NULL */ 133{ 134 int error; 135 time_t current_time; 136 137 error = 0; 138 139 /* first, clear out any old attributes */ 140 (void)internal_remove_attributes(node, (appledoubleheader != NULL)); 141 142 current_time = time(NULL); 143 require_action(current_time != -1, time, error = errno); 144 /* add appledoubleheader first since it could fail */ 145 if ( appledoubleheader != NULL ) 146 { 147 node->attr_appledoubleheader = malloc(APPLEDOUBLEHEADER_LENGTH); 148 require_action(node->attr_appledoubleheader != NULL, malloc_attr_appledoubleheader, error = ENOMEM); 149 150 memcpy(node->attr_appledoubleheader, appledoubleheader, APPLEDOUBLEHEADER_LENGTH); 151 node->attr_appledoubleheader_time = current_time; 152 } 153 /* fill in the rest of the fields */ 154 node->attr_uid = uid; 155 node->attr_time = current_time; 156 memcpy(&(node->attr_stat_info), statp, sizeof(struct webdav_stat_attr)); 157 158malloc_attr_appledoubleheader: 159time: 160 161 return ( error ); 162} 163 164/*****************************************************************************/ 165 166int nodecache_add_attributes( 167 struct node_entry *node, /* the node_entry to update or add attributes_entry to */ 168 uid_t uid, /* the uid these attributes are valid for */ 169 struct webdav_stat_attr *statp, /* the stat buffer */ 170 char *appledoubleheader) /* pointer appledoubleheader or NULL */ 171{ 172 int error; 173 174 lock_node_cache(); 175 176 error = internal_add_attributes(node, uid, statp, appledoubleheader); 177 178 unlock_node_cache(); 179 180 return ( error ); 181} 182 183/*****************************************************************************/ 184 185/* clear out the attribute fields and release any memory */ 186static int internal_remove_attributes( 187 struct node_entry *node, 188 int remove_appledoubleheader) 189{ 190 node->attr_uid = 0; 191 node->attr_time = 0; 192 node->attr_stat_info.attr_create_time.tv_sec = 0; 193 memset(&node->attr_stat_info, 0, sizeof(struct webdav_stat_attr)); 194 if ( remove_appledoubleheader && (node->attr_appledoubleheader != NULL) ) 195 { 196 free(node->attr_appledoubleheader); 197 node->attr_appledoubleheader = NULL; 198 node->attr_appledoubleheader_time = 0; 199 } 200 return ( 0 ); 201} 202 203/*****************************************************************************/ 204 205int nodecache_remove_attributes( 206 struct node_entry *node) /* the node to remove attributes from */ 207{ 208 int error; 209 210 lock_node_cache(); 211 212 error = internal_remove_attributes(node, TRUE); 213 214 unlock_node_cache(); 215 216 return ( error ); 217} 218 219/*****************************************************************************/ 220 221static int internal_node_appledoubleheader_valid( 222 struct node_entry *node, 223 uid_t uid) 224{ 225 int result; 226 227 result = ( (node->attr_appledoubleheader != NULL) && /* is there attr_appledoubleheader? */ 228 (node->attr_appledoubleheader_time != 0) && /* 0 attr_appledoubleheader_time is invalid */ 229 ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes? */ 230 (time(NULL) < (node->attr_appledoubleheader_time + FILE_VALIDATION_TIMEOUT)) ); /* don't cache them too long */ 231 232 return ( result ); 233} 234 235/*****************************************************************************/ 236 237int node_appledoubleheader_valid( 238 struct node_entry *node, 239 uid_t uid) 240{ 241 int result; 242 243 lock_node_cache(); 244 245 result = internal_node_appledoubleheader_valid(node, uid); 246 247 unlock_node_cache(); 248 249 return ( result ); 250} 251 252/*****************************************************************************/ 253 254int node_attributes_valid( 255 struct node_entry *node, 256 uid_t uid) 257{ 258 int result; 259 260 lock_node_cache(); 261 262 result = ( (node->attr_time != 0) && /* 0 attr_time is invalid */ 263 ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes */ 264 (time(NULL) < (node->attr_time + ATTRIBUTES_TIMEOUT_MAX)) ); /* don't cache them too long */ 265 266 unlock_node_cache(); 267 268 return ( result ); 269 270#if 0 /* FUTURE */ 271 int result; 272 273 /* are the cached attributes possibly valid and does this user or root have access to them? */ 274 if ( (node->attr_time != 0) && ((uid == node->attr_uid) || (0 == node->attr_uid)) ) 275 { 276 /* 277 * Determine attribute_time_out. It will be something between 278 * ATTRIBUTES_TIMEOUT_MIN and ATTRIBUTES_TIMEOUT_MAX where recently 279 * modified files have a short timeout and files that haven't been 280 * modified in a long time have a long timeout. This is the same 281 * algorithm used by NFS (with different min and max values). 282 */ 283 time_t current_time; 284 time_t attribute_time_out; 285 286 current_time = time(NULL); 287 attribute_time_out = (current_time - node->attr_stat.st_mtimespec.tv_sec) / 10; 288 if (attribute_time_out < ATTRIBUTES_TIMEOUT_MIN) 289 { 290 attribute_time_out = ATTRIBUTES_TIMEOUT_MIN; 291 } 292 else if (attribute_time_out > ATTRIBUTES_TIMEOUT_MAX) 293 { 294 attribute_time_out = ATTRIBUTES_TIMEOUT_MAX; 295 } 296 /* has too much time passed? */ 297 result = ((current_time - node->attr_time) <= attribute_time_out); 298 } 299 else 300 { 301 result = FALSE; 302 } 303 return ( result ); 304#endif /* FUTURE */ 305} 306 307/*****************************************************************************/ 308 309/* invalidate the node attribute and file cache caches for dir_node's children */ 310static void invalidate_level(struct node_entry *dir_node) 311{ 312 struct node_entry *node; 313 314 /* invalidate each child node */ 315 LIST_FOREACH(node, &(dir_node->children), entries) 316 { 317 node->attr_time = 0; 318 node->attr_stat_info.attr_create_time.tv_sec = -1; 319 node->file_validated_time = 0; 320 /* invalidate this node's children (if any) */ 321 invalidate_level(node); 322 } 323} 324 325/* filesystem_invalidate_caches will call this to do all the work */ 326void nodecache_invalidate_caches(void) 327{ 328 struct node_entry *node; 329 330 lock_node_cache(); 331 332 node = g_root_node; 333 node->attr_time = 0; 334 node->attr_stat_info.attr_create_time.tv_sec = -1; 335 node->file_validated_time = 0; 336 /* invalidate this node's children (if any) */ 337 invalidate_level(node); 338 339 unlock_node_cache(); 340} 341 342/*****************************************************************************/ 343 344#if 0 345 346static int gtabs; 347 348static void display_node_tree_level(struct node_entry *dir_node) 349{ 350 struct node_entry *node; 351 352 LIST_FOREACH(node, &(dir_node->children), entries) 353 { 354 ++gtabs; 355 syslog(LOG_ERR, "%*s%s: %ld %s, node ino %ld, attr ino %u", gtabs*3, "", node->name, (unsigned long)node, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", node->fileid, node->attr_stat.st_ino); 356 display_node_tree_level(node); 357 --gtabs; 358 } 359} 360 361void nodecache_display_node_tree(void) 362{ 363 struct node_entry *node; 364 365 lock_node_cache(); 366 367 node = g_root_node; 368 gtabs = 0; 369 syslog(LOG_ERR, "%*s%s: %ld %s, node ino %ld, attr ino %u", gtabs*3, "", node->name, (unsigned long)node, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", node->fileid, node->attr_stat.st_ino); 370 display_node_tree_level(node); 371 syslog(LOG_ERR, "-----"); 372 373 unlock_node_cache(); 374} 375 376void nodecache_display_file_cache(void) 377{ 378 struct node_entry *node; 379 unsigned long count; 380 381 count = 0; 382 383 lock_node_cache(); 384 385 LIST_FOREACH(node, &g_file_list, file_list) 386 { 387 ++count; 388 389 if ( !NODE_IS_DELETED(node) ) 390 { 391 syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s", 392 node->file_fd, 393 node->fileid, 394 node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", 395 NODE_FILE_IS_OPEN(node) ? "+ " : "- ", 396 node->name); 397 } 398 else 399 { 400 syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s", 401 node->file_fd, 402 node->fileid, 403 node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", 404 NODE_FILE_IS_OPEN(node) ? "+X" : "-X", 405 node->name); 406 } 407 } 408 syslog(LOG_ERR, "----- %ld -----", count); 409 410 unlock_node_cache(); 411} 412 413#endif 414 415/*****************************************************************************/ 416 417static struct node_entry *g_next_file_cache_node = NULL; 418 419struct node_entry *nodecache_get_next_file_cache_node( 420 int get_first) /* if true, return first file cache node; otherwise, the next one */ 421{ 422 struct node_entry *node; 423 424 lock_node_cache(); 425 426 if ( get_first ) 427 { 428 node = g_file_list.lh_first; 429 } 430 else 431 { 432 node = g_next_file_cache_node; 433 } 434 435 if ( node != NULL ) 436 { 437 g_next_file_cache_node = node->file_list.le_next; 438 } 439 else 440 { 441 g_next_file_cache_node = NULL; 442 } 443 444 unlock_node_cache(); 445 446 return ( node ); 447} 448 449/*****************************************************************************/ 450 451static int internal_add_file_cache( 452 struct node_entry *node, /* the node_entry to add a file_cache_entry to */ 453 int fd) /* the file descriptor of the cache file */ 454{ 455 int error; 456 457 error = 0; 458 459 /* only add it if it's not already cached */ 460 require_quiet(!NODE_FILE_IS_CACHED(node), already_cached); 461 462 while ( open_cache_files >= WEBDAV_MAX_OPEN_FILES ) 463 { 464 struct node_entry *file_node; 465 struct node_entry *victim_node; 466 467 /* find oldest victim node */ 468 victim_node = NULL; 469 LIST_FOREACH(file_node, &g_file_list, file_list) 470 { 471 if ( !NODE_FILE_IS_OPEN(file_node) ) 472 { 473 victim_node = file_node; 474 } 475 } 476 477 require_action(victim_node != NULL, too_many_files_open, error = ENFILE); 478 479 internal_remove_file_cache(victim_node); 480 } 481 482 ++open_cache_files; 483 node->flags |= nodeInFileListMask; 484 node->file_fd = fd; 485 node->file_status = WEBDAV_DOWNLOAD_NEVER; 486 node->file_validated_time = 0; 487 node->file_inactive_time = 0; 488 node->file_last_modified = -1; 489 if ( node->file_entity_tag != NULL ) 490 { 491 free(node->file_entity_tag); 492 node->file_entity_tag = NULL; 493 } 494 node->file_locktoken_uid = 0; 495 if ( node->file_locktoken != NULL ) 496 { 497 free(node->file_locktoken); 498 node->file_locktoken = NULL; 499 } 500 501 /* add it to the head of the g_file_active_list */ 502 LIST_INSERT_HEAD(&g_file_list, node, file_list); 503 504too_many_files_open: 505already_cached: 506 507 return ( error ); 508} 509 510/*****************************************************************************/ 511 512int nodecache_add_file_cache( 513 struct node_entry *node, /* the node_entry to add a file_cache_entry to */ 514 int fd) /* the file descriptor of the cache file */ 515{ 516 int error; 517 518 lock_node_cache(); 519 520 error = internal_add_file_cache(node, fd); 521 522 unlock_node_cache(); 523 524 return ( error ); 525} 526 527/*****************************************************************************/ 528 529static void internal_remove_file_cache(struct node_entry *node) 530{ 531 if ( NODE_FILE_IS_CACHED(node) ) 532 { 533 if (open_cache_files > 0) 534 { 535 --open_cache_files; 536 } 537 else 538 { 539 debug_string("internal_remove_file_cache: open_cache_files was zero"); 540 } 541 node->flags &= ~nodeInFileListMask; 542 close(node->file_fd); 543 node->file_fd = -1; 544 if ( node == g_next_file_cache_node ) 545 { 546 g_next_file_cache_node = node->file_list.le_next; 547 } 548 /* remove from g_file_list */ 549 LIST_REMOVE(node, file_list); 550 node->file_list.le_next = NULL; 551 node->file_list.le_prev = NULL; 552 node->file_status = WEBDAV_DOWNLOAD_NEVER; 553 node->file_validated_time = 0; 554 node->file_inactive_time = 0; 555 node->file_last_modified = -1; 556 if ( node->file_entity_tag != NULL ) 557 { 558 free(node->file_entity_tag); 559 node->file_entity_tag = NULL; 560 } 561 node->file_locktoken_uid = 0; 562 if ( node->file_locktoken != NULL ) 563 { 564 free(node->file_locktoken); 565 node->file_locktoken = NULL; 566 } 567 } 568} 569 570/*****************************************************************************/ 571 572void nodecache_remove_file_cache(struct node_entry *node) 573{ 574 lock_node_cache(); 575 576 internal_remove_file_cache(node); 577 578 unlock_node_cache(); 579} 580 581/*****************************************************************************/ 582 583/* called at mount time to initialize */ 584int nodecache_init( 585 size_t name_length, /* length of root node name */ 586 char *name, /* the utf8 name of root node */ 587 struct node_entry **root_node) /* the root node */ 588{ 589 int error; 590 591 error = 0; 592 593 g_next_fileid = WEBDAV_ROOTFILEID; 594 595 /* allocate space for g_root_node and its name */ 596 g_root_node = calloc(1, sizeof(struct node_entry)); 597 require_action(g_root_node != NULL, calloc_g_root_node, error = ENOMEM); 598 599 *root_node = g_root_node; 600 g_root_node->name = malloc(name_length + 1); 601 require_action(g_root_node->name != NULL, malloc_name, error = ENOMEM); 602 603 /* initialize the node_entry */ 604 g_root_node->parent = NULL; 605 LIST_INIT(&g_root_node->children); 606 g_root_node->name_length = name_length; 607 memcpy(g_root_node->name, name, name_length); 608 g_root_node->name[name_length] = '\0'; 609 g_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull); 610 g_root_node->fileid = g_next_fileid++; 611 g_root_node->node_type = WEBDAV_DIR_TYPE; 612 g_root_node->node_time = time(NULL); 613 error = AssignOpaqueID(g_root_node, &g_root_node->nodeid); 614 require_noerr(error, AssignOpaqueID); 615 616 /* attribute fields are already zeroed */ 617 /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ 618 g_root_node->file_fd = -1; 619 620 /* allocate space for g_deleted_root_node and its empty name */ 621 g_deleted_root_node = calloc(1, sizeof(struct node_entry)); 622 require_action(g_root_node != NULL, calloc_g_deleted_root_node, error = ENOMEM); 623 624 g_deleted_root_node->name = malloc(1); 625 require_action(g_root_node->name != NULL, malloc_g_deleted_root_node_name, error = ENOMEM); 626 627 /* initialize the node_entry */ 628 g_deleted_root_node->parent = NULL; 629 LIST_INIT(&g_deleted_root_node->children); 630 g_deleted_root_node->name_length = 0; 631 g_deleted_root_node->name[0] = '\0'; 632 g_deleted_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_deleted_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull); 633 /* attribute fields are already zeroed */ 634 /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ 635 g_deleted_root_node->file_fd = -1; 636 637 /* initialize g_file_list header */ 638 LIST_INIT(&g_file_list); 639 640 error = init_node_cache_lock(); 641 642malloc_g_deleted_root_node_name: 643calloc_g_deleted_root_node: 644malloc_name: 645AssignOpaqueID: 646calloc_g_root_node: 647 648 return ( error ); 649} 650 651/*****************************************************************************/ 652 653/* 654 * Finds a node by name in the parent node's children. If the node is 655 * not found and make_entry is TRUE, then internal_get_node creates a new node. 656 */ 657static int internal_get_node( 658 struct node_entry *parent, /* the parent node_entry */ 659 size_t name_length, /* length of name */ 660 const char *name, /* the utf8 name of the node */ 661 int make_entry, /* TRUE if a new node_entry should be created if needed */ 662 int client_created, /* TRUE if this client created the node (used in conjunction with make_entry */ 663 webdav_filetype_t node_type, /* if make_entry is TRUE, the type of node to create */ 664 struct node_entry **node) /* the found (or new) node_entry */ 665{ 666 struct node_entry *node_ptr; 667 int error; 668 669 node_ptr = NULL; 670 error = 0; 671 672 if ( name_length != 0 && name != NULL ) 673 { 674 CFStringRef name_string; 675 676 name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)name, name_length, kCFStringEncodingUTF8, false); 677 require_action(name_string != NULL, out, error = EINVAL); 678 679 /* search for an existing node_entry */ 680 LIST_FOREACH(node_ptr, &(parent->children), entries) 681 { 682 if ( CFStringCompare(name_string, node_ptr->name_ref, kCFCompareNonliteral) == kCFCompareEqualTo ) 683 { 684 break; 685 } 686 } 687 688 CFRelease(name_string); 689 } 690 else 691 { 692 node_ptr = parent; 693 } 694 695 /* if we didn't find it and we're supposed to create it... */ 696 if ( node_ptr == NULL ) 697 { 698 if ( make_entry ) 699 { 700 /* allocate new node_entry */ 701 node_ptr = calloc(1, sizeof(struct node_entry)); 702 require_action(node_ptr != NULL, calloc_node_ptr, error = ENOMEM; webdav_kill(-1)); 703 704 /* allocate space for its name */ 705 node_ptr->name = malloc(name_length + 1); 706 require_action(node_ptr->name != NULL, malloc_name, node_ptr = NULL; error = ENOMEM; webdav_kill(-1)); 707 708 /* initialize the node_entry */ 709 node_ptr->parent = parent; 710 LIST_INIT(&node_ptr->children); 711 node_ptr->name_length = name_length; 712 memcpy(node_ptr->name, name, name_length); 713 node_ptr->name[name_length] = '\0'; 714 node_ptr->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node_ptr->name, kCFStringEncodingUTF8, kCFAllocatorNull); 715 node_ptr->fileid = g_next_fileid++; 716 node_ptr->node_type = node_type; 717 node_ptr->node_time = time(NULL); 718 node_ptr->attr_stat_info.attr_create_time.tv_sec = 0; 719 node_ptr->isRedirected = false; 720 if ( client_created ) 721 { 722 node_ptr->flags |= nodeRecentMask; 723 } 724 error = AssignOpaqueID(node_ptr, &node_ptr->nodeid); 725 require_noerr_action(error, AssignOpaqueID, node_ptr = NULL; webdav_kill(-1)); 726 727 /* attribute fields are already zeroed */ 728 /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ 729 node_ptr->file_fd = -1; 730 731 /* insert the node_entry into the parent's children list */ 732 LIST_INSERT_HEAD(&parent->children, node_ptr, entries); 733 } 734 else 735 { 736 error = ENOENT; 737 } 738 } 739 else 740 { 741 /* update node_time of existing node */ 742 if ( make_entry ) 743 { 744 node_ptr->node_time = time(NULL); 745 } 746 /* set or clear the recent flag */ 747 if ( client_created ) 748 { 749 node_ptr->flags |= nodeRecentMask; 750 } 751 else 752 { 753 node_ptr->flags &= ~nodeRecentMask; 754 } 755 } 756 757AssignOpaqueID: 758malloc_name: 759calloc_node_ptr: 760out: 761 762 /* return node_entry */ 763 if ( error == 0 ) 764 { 765 *node = node_ptr; 766 } 767 else 768 { 769 *node = NULL; 770 } 771 772 return ( error ); 773} 774 775/*****************************************************************************/ 776 777int nodecache_get_node( 778 struct node_entry *parent, /* the parent node_entry */ 779 size_t name_length, /* length of name */ 780 const char *name, /* the utf8 name of the node */ 781 int make_entry, /* TRUE if a new node_entry should be created if needed*/ 782 int client_created, /* TRUE if this client created the node (used in conjunction with make_entry */ 783 webdav_filetype_t node_type, /* if make_entry is TRUE, the type of node to create */ 784 struct node_entry **node) /* the found (or new) node */ 785{ 786 int error; 787 788 lock_node_cache(); 789 790 error = internal_get_node(parent, name_length, name, make_entry, client_created, node_type, node); 791 792 unlock_node_cache(); 793 794 return ( error ); 795} 796 797/*****************************************************************************/ 798 799/* 800 * internal_free_nodes 801 * This function frees uncached nodes on the deleted list. 802 */ 803static void internal_free_nodes(void) 804{ 805 struct node_entry *node; 806 807 node = g_deleted_root_node->children.lh_first; 808 while ( node != NULL ) 809 { 810 struct node_entry *next_node; 811 812 next_node = node->entries.le_next; 813 /* free this node ONLY if it's NOT on the file list */ 814 if ( !NODE_FILE_IS_CACHED(node) ) 815 { 816 /* remove the node_entry from the list it is in */ 817 LIST_REMOVE(node, entries); 818 819 /* invalidate the nodeid */ 820 (void) DeleteOpaqueID(node->nodeid); 821 node->nodeid = kInvalidOpaqueID; 822 823 if ( node->file_entity_tag != NULL ) 824 { 825 free(node->file_entity_tag); 826 node->file_entity_tag = NULL; 827 } 828 node->file_locktoken_uid = 0; 829 if ( node->file_locktoken != NULL ) 830 { 831 free(node->file_locktoken); 832 node->file_locktoken = NULL; 833 } 834 835 /* free memory used by the node */ 836 free(node->name); 837 CFRelease(node->name_ref); 838 if (node->redir_name != NULL) 839 free (node->redir_name); 840 841 (void) internal_remove_attributes(node, TRUE); 842 843 free(node); 844 } 845 node = next_node; 846 } 847} 848 849/*****************************************************************************/ 850 851void nodecache_free_nodes(void) 852{ 853 lock_node_cache(); 854 855 internal_free_nodes(); 856 857 unlock_node_cache(); 858} 859 860/*****************************************************************************/ 861 862static int internal_move_node( 863 struct node_entry *node, /* the node_entry to move */ 864 struct node_entry *new_parent, /* the new parent node_entry */ 865 size_t new_name_length, /* length of new_name or 0 if not renaming */ 866 char *new_name) /* the utf8 new name of the node or NULL */ 867{ 868 int error; 869 870 error = 0; 871 872 /* new name? */ 873 if ( (new_name_length != 0) && (new_name != NULL) ) 874 { 875 CFStringRef name_string; 876 CFComparisonResult compare_result; 877 878 name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)new_name, new_name_length, kCFStringEncodingUTF8, false); 879 compare_result = CFStringCompare(name_string, node->name_ref, kCFCompareNonliteral); 880 CFRelease(name_string); 881 882 /* did the name change? */ 883 if ( compare_result != kCFCompareEqualTo ) 884 { 885 char *name; 886 887 /* allocate space for new name */ 888 name = malloc(new_name_length + 1); 889 require_action(name != NULL, malloc_name, error = errno; webdav_kill(-1)); 890 891 free(node->name); 892 CFRelease(node->name_ref); 893 node->name = name; 894 memcpy(node->name, new_name, new_name_length); 895 node->name[new_name_length] = '\0'; 896 node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node->name, kCFStringEncodingUTF8, kCFAllocatorNull); 897 node->name_length = new_name_length; 898 } 899 } 900 901 /* change parents? */ 902 if ( node->parent != new_parent ) 903 { 904 /* move the node_entry to the new parent */ 905 LIST_REMOVE(node, entries); 906 LIST_INSERT_HEAD(&new_parent->children, node, entries); 907 node->parent = new_parent; 908 } 909 910malloc_name: 911 912 return ( error ); 913} 914 915/*****************************************************************************/ 916 917int nodecache_move_node( 918 struct node_entry *node, /* the node_entry to move */ 919 struct node_entry *new_parent, /* the new parent node_entry */ 920 size_t new_name_length, /* length of new_name or 0 if not renaming */ 921 char *new_name) /* the utf8 new name of the node or NULL */ 922{ 923 int error; 924 925 lock_node_cache(); 926 927 error = internal_move_node(node, new_parent, new_name_length, new_name); 928 929 unlock_node_cache(); 930 931 return ( error ); 932} 933 934/*****************************************************************************/ 935 936/* delete dir_node's descendents and then delete dir_node */ 937static int delete_node_tree( 938 struct node_entry *node, /* the node_entry to delete */ 939 int recursive) /* delete recursively if node is a directory */ 940{ 941 int error; 942 943 error = 0; 944 945 if ( recursive ) 946 { 947 struct node_entry *child_node; 948 949 child_node = (node->children).lh_first; 950 while ( child_node != NULL ) 951 { 952 struct node_entry *next_node; 953 954 next_node = child_node->entries.le_next; 955 error = delete_node_tree(child_node, TRUE); 956 require_noerr(error, delete_child_node); 957 958 child_node = next_node; 959 } 960 } 961 else 962 { 963 require_action((node->children).lh_first == NULL, has_children, error = EBUSY); 964 } 965 966 /* move node over to the deleted list and mark it as deleted */ 967 error = internal_move_node(node, g_deleted_root_node, 0, NULL); 968 require_noerr(error, internal_move_node); 969 970 node->flags |= nodeDeletedMask; 971 node->attr_time = 0; 972 node->attr_stat_info.attr_create_time.tv_sec = 0; 973 974internal_move_node: 975has_children: 976delete_child_node: 977 978 return ( error ); 979} 980 981/*****************************************************************************/ 982 983int nodecache_delete_node( 984 struct node_entry *node, /* the node_entry to delete */ 985 int recursive) /* delete recursively if node is a directory */ 986{ 987 int error; 988 989 lock_node_cache(); 990 991 error = delete_node_tree(node, recursive); 992 993 unlock_node_cache(); 994 995 return ( error ); 996} 997 998/*****************************************************************************/ 999 1000/* invalidate dir_node's children's node_times */ 1001static int internal_invalidate_directory_node_time(struct node_entry *dir_node) 1002{ 1003 int error; 1004 struct node_entry *node; 1005 1006 error = 0; 1007 1008 require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR); 1009 1010 LIST_FOREACH(node, &(dir_node->children), entries) 1011 { 1012 node->node_time = 0; 1013 } 1014 1015not_directory: 1016 1017 return ( error ); 1018} 1019 1020/*****************************************************************************/ 1021 1022int nodecache_invalidate_directory_node_time( 1023 struct node_entry *dir_node) /* parent directory node */ 1024{ 1025 int error; 1026 1027 lock_node_cache(); 1028 1029 error = internal_invalidate_directory_node_time(dir_node); 1030 1031 unlock_node_cache(); 1032 1033 return ( error ); 1034} 1035 1036/*****************************************************************************/ 1037 1038/* invalidate dir_node's children's node_times */ 1039static int internal_delete_invalid_directory_nodes(struct node_entry *dir_node) 1040{ 1041 int error; 1042 struct node_entry *node; 1043 1044 error = 0; 1045 1046 require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR); 1047 1048 node = (&(dir_node->children))->lh_first; 1049 while ( node != NULL ) 1050 { 1051 struct node_entry *next_node; 1052 1053 next_node = node->entries.le_next; 1054 if ( node->node_time == 0 ) 1055 { 1056 //syslog(LOG_ERR,"NODE NAME IS %s",node->name); 1057 error = delete_node_tree(node, TRUE); 1058 require_noerr(error, delete_node_tree); 1059 } 1060 node = next_node; 1061 } 1062 1063delete_node_tree: 1064not_directory: 1065 1066 return ( error ); 1067} 1068 1069/*****************************************************************************/ 1070 1071int nodecache_delete_invalid_directory_nodes( 1072 struct node_entry *dir_node) /* parent directory node */ 1073{ 1074 int error; 1075 1076 lock_node_cache(); 1077 1078 error = internal_delete_invalid_directory_nodes(dir_node); 1079 1080 unlock_node_cache(); 1081 1082 return ( error ); 1083} 1084 1085/*****************************************************************************/ 1086 1087/* 1088 * nodecache_get_path_from_node 1089 * 1090 * Builds a UTF-8 slash '/' delimited NULL terminated path from 1091 * the root node (but not including the root node) to the target_node. 1092 * 1093 * Note: If a node in the path has been redirected by an HTTP 3xx redirect, then 1094 * the returned path will be from the redirected node 1095 * to the target_node, and the 'pathHasRedirection' output arg is set to true. 1096 * 1097 * If the node is directory, the path MUST end with a slash. 1098 * The path MUST NOT start with a slash since this is always a relative path. 1099 * If result is 0 (no error), then a newly allocated buffer containing the 1100 * path is returned and the caller is responsible for freeing it. 1101 */ 1102static int internal_get_path_from_node( 1103 struct node_entry *target_node, 1104 bool *pathHasRedirection, /* true if path contains a URL from a redirected node (http 3xx redirect) */ 1105 char **path) 1106{ 1107 int error; 1108 struct node_entry *cur_node; 1109 char *cur_ptr; 1110 char *pathbuf; 1111 size_t path_len; 1112 1113 error = 0; 1114 pathbuf = NULL; 1115 path_len = 0; 1116 *pathHasRedirection = false; 1117 1118 require_action(!NODE_IS_DELETED(target_node), node_deleted, error = ENOENT); 1119 1120 pathbuf = malloc(PATH_MAX); 1121 require_action(pathbuf != NULL, malloc_pathbuf, error = errno); 1122 1123 if ( target_node == g_root_node ) 1124 { 1125 /* nothing to do */ 1126 *pathbuf = '\0'; 1127 } 1128 else 1129 { 1130 /* point at last character and put in the string terminator */ 1131 cur_ptr = pathbuf + PATH_MAX - 1; 1132 *cur_ptr = '\0'; 1133 1134 cur_node = target_node; 1135 while ( cur_node != g_root_node ) 1136 { 1137 require_action(cur_node != NULL, name_too_long, error = ENAMETOOLONG); 1138 1139 /* was this node redirected? */ 1140 if (cur_node->isRedirected == TRUE) { 1141 /* make sure there's enough room for the node redir_name and a slash */ 1142 require_action((cur_ptr - cur_node->redir_name_length + 1) >= pathbuf, 1143 name_too_long, error = ENAMETOOLONG); 1144 1145 /* add a slash */ 1146 --cur_ptr; 1147 *cur_ptr = '/'; 1148 1149 /* copy in the redir_name */ 1150 cur_ptr -= cur_node->redir_name_length; 1151 memcpy(cur_ptr, cur_node->redir_name, cur_node->redir_name_length); 1152 1153 *pathHasRedirection = true; 1154 break; 1155 } 1156 1157 /* make sure there's enough room for the next node name and a slash */ 1158 require_action((cur_ptr - cur_node->name_length + 1) >= pathbuf, 1159 name_too_long, error = ENAMETOOLONG); 1160 1161 /* add a slash */ 1162 --cur_ptr; 1163 *cur_ptr = '/'; 1164 1165 /* copy in the name */ 1166 cur_ptr -= cur_node->name_length; 1167 memcpy(cur_ptr, cur_node->name, cur_node->name_length); 1168 cur_node = cur_node->parent; 1169 } 1170 1171 /* move the path to the front of the buffer */ 1172 path_len = strlen(cur_ptr); 1173 memmove(pathbuf, cur_ptr, path_len + 1); 1174 1175 /* remove trailing slash if not directory */ 1176 if ( target_node->node_type != WEBDAV_DIR_TYPE ) 1177 { 1178 --(path_len); 1179 pathbuf[path_len] = '\0'; 1180 } 1181 } 1182 1183name_too_long: 1184malloc_pathbuf: 1185node_deleted: 1186 1187 if ( error ) 1188 { 1189 if ( pathbuf != NULL ) 1190 { 1191 free(pathbuf); 1192 pathbuf = NULL; 1193 } 1194 } 1195 1196 *path = pathbuf; 1197 1198 return ( error ); 1199} 1200 1201/*****************************************************************************/ 1202 1203int nodecache_get_path_from_node( 1204 struct node_entry *node, /* -> node */ 1205 bool *pathHasRedirection, /* true if path contains a URL from a redirected node (http 3xx redirect) */ 1206 char **path) /* <- relative path to root node */ 1207{ 1208 int error; 1209 1210 lock_node_cache(); 1211 1212 error = internal_get_path_from_node(node, pathHasRedirection, path); 1213 1214 unlock_node_cache(); 1215 1216 return ( error ); 1217} 1218 1219/*****************************************************************************/ 1220 1221int nodecache_redirect_node( 1222 CFURLRef url, /* original url that caused the redirect */ 1223 struct node_entry *redirected_node, /* the node_entry that was redirected */ 1224 CFHTTPMessageRef responseRef, /* the 3xx redirect response message */ 1225 CFIndex statusCode) /* 3xx status code from response message */ 1226{ 1227 CFMutableStringRef newLocationRef; 1228 CFStringRef locationRef, mmeHostRef, tmpStringRef; 1229 CFURLRef newBaseURL; 1230 boolean_t result, isRootNode = false; 1231 size_t name_ptr_len; 1232 char *name_ptr; 1233 int error = 0; 1234 1235 newLocationRef = NULL; 1236 locationRef = NULL; 1237 mmeHostRef = NULL; 1238 1239 if (responseRef == NULL) { 1240 syslog(LOG_ERR, "%s: NULL responseRef\n", __FUNCTION__); 1241 error = EIO; 1242 goto out; 1243 } 1244 1245 newLocationRef = CFStringCreateMutable(kCFAllocatorDefault,0); 1246 if (newLocationRef == NULL){ 1247 syslog(LOG_ERR, "%s: no mem for newLocationRef\n", __FUNCTION__); 1248 error = ENOMEM; 1249 goto out; 1250 } 1251 1252 // First need to build a Location string 1253 if (statusCode == 330) { 1254 // This is a MobileMe Realm type of redirection. 1255 // Need to pull out X-Apple-MMe-Host 1256 mmeHostRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("X-Apple-MMe-Host")); 1257 1258 if (mmeHostRef == NULL) { 1259 syslog(LOG_ERR, "%s: 330 Redirection missing MMe-Host header\n", __FUNCTION__); 1260 error = EIO; 1261 goto out; 1262 } 1263 1264 if (gSecureConnection == TRUE) 1265 CFStringAppend(newLocationRef, CFSTR("https://")); 1266 else 1267 CFStringAppend(newLocationRef, CFSTR("http://")); 1268 1269 // add host from X-Apple-MMe-Host header 1270 CFStringAppend(newLocationRef, mmeHostRef); 1271 1272 // fetch the path from the original URL 1273 tmpStringRef = CFURLCopyPath(url); 1274 1275 if (tmpStringRef != NULL) { 1276 CFStringAppend(newLocationRef, tmpStringRef); 1277 CFRelease(tmpStringRef); 1278 } 1279 } 1280 else { 1281 // Not an MMe 330, just grab Location field 1282 locationRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("Location")); 1283 1284 if (locationRef == NULL) { 1285 syslog(LOG_ERR, "%s: 3xx response missing Location field\n", __FUNCTION__); 1286 error = EIO; 1287 return (error); 1288 } 1289 1290 CFStringAppend(newLocationRef, locationRef); 1291 } 1292 1293 if (redirected_node == NULL || redirected_node == g_root_node) 1294 isRootNode = true; 1295 else 1296 isRootNode = false; 1297 1298 // Add or remove trailing slash as needed 1299 if (isRootNode == true) { 1300 // we want a trailing slash 1301 if (CFStringHasSuffix(newLocationRef, CFSTR("/")) == false) 1302 CFStringAppend(newLocationRef, CFSTR("/")); 1303 } 1304 else { 1305 // Not the root node, don't want a trailing slash 1306 if (CFStringHasSuffix(newLocationRef, CFSTR("/")) == true) 1307 CFStringDelete(newLocationRef, CFRangeMake(CFStringGetLength(newLocationRef) - 1, 1)); 1308 } 1309 1310 // Setup C string for new redir_name 1311 name_ptr = malloc(WEBDAV_MAX_URI_LEN + 1); 1312 if (name_ptr == NULL) { 1313 syslog(LOG_ERR, "%s: no mem for name_ptr\n", __FUNCTION__); 1314 error = ENOMEM; 1315 goto out; 1316 } 1317 1318 result = CFStringGetCString(newLocationRef, name_ptr, WEBDAV_MAX_URI_LEN, kCFStringEncodingUTF8); 1319 if (result == false) { 1320 syslog(LOG_ERR, "%s: CFStringGetCString returned false for newLocationRef\n", __FUNCTION__); 1321 error = EIO; 1322 free(name_ptr); 1323 goto out; 1324 } 1325 1326 name_ptr_len = strlen(name_ptr); 1327 if (name_ptr_len <= 0) { 1328 error = EIO; 1329 free(name_ptr); 1330 goto out; 1331 } 1332 1333 // Block any attempt to redirect from a secure host to a non-secure host 1334 if (gSecureConnection == TRUE) { 1335 if (CFStringHasPrefix(newLocationRef, CFSTR("https://")) == false) { 1336 syslog(LOG_ERR, "%s: redirect to non-secure host denied: %s\n", __FUNCTION__, name_ptr); 1337 error = EIO; 1338 free(name_ptr); 1339 goto out; 1340 } 1341 } 1342 1343 lock_node_cache(); 1344 1345 if (isRootNode == false) { 1346 if (redirected_node->isRedirected == true) { 1347 // cleanup old redirection 1348 if (redirected_node->redir_name != NULL) 1349 free(redirected_node->redir_name); 1350 } 1351 1352 // now change node state 1353 redirected_node->isRedirected = true; 1354 redirected_node->redir_name = name_ptr; 1355 redirected_node->redir_name_length = name_ptr_len; 1356 1357 syslog(LOG_DEBUG, "Node %s redirected to: %s", redirected_node->name, name_ptr); 1358 } 1359 else { 1360 // The root node, g_root_node is being redirected 1361 // Create the new gBaseURL 1362 newBaseURL = CFURLCreateAbsoluteURLWithBytes(kCFAllocatorDefault, (UInt8 *)name_ptr, name_ptr_len, kCFStringEncodingUTF8, NULL, FALSE); 1363 if (newBaseURL == NULL) { 1364 syslog(LOG_ERR, "%s: Location field was not legal UTF8: %s\n", __FUNCTION__, name_ptr); 1365 free(name_ptr); 1366 error = ENOMEM; 1367 goto create_baseurl; 1368 } 1369 1370 if (g_root_node->isRedirected == true) { 1371 // cleanup old redirection 1372 if (g_root_node->redir_name != NULL) 1373 free(g_root_node->redir_name); 1374 } 1375 1376 // Now change node state 1377 CFRelease(gBaseURL); 1378 gBaseURL = newBaseURL; 1379 1380 if (gBasePath != NULL) { 1381 CFRelease(gBasePath); 1382 gBasePath = NULL; 1383 } 1384 1385 gBasePath = CFURLCopyPath(gBaseURL); 1386 if (gBasePath != NULL) { 1387 CFStringGetCString(gBasePath, gBasePathStr, MAXPATHLEN, kCFStringEncodingUTF8); 1388 } 1389 1390 g_root_node->isRedirected = true; 1391 g_root_node->redir_name = name_ptr; 1392 g_root_node->redir_name_length = name_ptr_len; 1393 1394 syslog(LOG_DEBUG, "Base node redirected to: %s", name_ptr); 1395 } 1396 1397create_baseurl: 1398 unlock_node_cache(); 1399out: 1400 if (newLocationRef != NULL) 1401 CFRelease(newLocationRef); 1402 if (locationRef != NULL) 1403 CFRelease(locationRef); 1404 if (mmeHostRef != NULL) 1405 CFRelease(mmeHostRef); 1406 1407 return (error); 1408} 1409 1410 1411/* Returns a CFArray containing (1) the a_node's file_locktoken (if exists) */ 1412/* AND (2) all the a_node's children's file_locktokens (if any exists) */ 1413static CFArrayRef internal_get_locktokens(struct node_entry *a_node) 1414{ 1415 int error; 1416 CFMutableArrayRef arr; 1417 CFStringRef lockTokenStr; 1418 struct node_entry *node; 1419 1420 arr = NULL; 1421 error = 0; 1422 arr = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 1423 require(arr != NULL, create_array); 1424 1425 // First do parent 1426 if (a_node->file_locktoken != NULL) { 1427 lockTokenStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), a_node->file_locktoken); 1428 if (lockTokenStr != NULL) { 1429 CFArrayAppendValue(arr, lockTokenStr); 1430 CFRelease(lockTokenStr); // we don't need to hold a reference 1431 } 1432 else { 1433 // apparently no mem to do our job, so punt. 1434 goto done; 1435 } 1436 } 1437 1438 // We're done if no child nodes 1439 if (a_node->node_type != WEBDAV_DIR_TYPE) 1440 goto done; 1441 1442 // Now get child locktokens 1443 LIST_FOREACH(node, &(a_node->children), entries) 1444 { 1445 if (node->file_locktoken != NULL) { 1446 lockTokenStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), node->file_locktoken); 1447 if (lockTokenStr != NULL) { 1448 CFArrayAppendValue(arr, lockTokenStr); 1449 CFRelease(lockTokenStr); // we don't need to hold a reference 1450 } 1451 } 1452 } 1453 1454done: 1455 if (!CFArrayGetCount(arr)) { 1456 // no file_locktokens found, return NULL 1457 CFRelease(arr); 1458 arr = NULL; 1459 } 1460 1461create_array: 1462 1463 return ( arr ); 1464} 1465 1466/*****************************************************************************/ 1467 1468CFArrayRef nodecache_get_locktokens(struct node_entry *a_node) 1469{ 1470 CFArrayRef arr; 1471 1472 lock_node_cache(); 1473 1474 arr = internal_get_locktokens(a_node); 1475 1476 unlock_node_cache(); 1477 1478 return ( arr ); 1479} 1480 1481/*****************************************************************************/ 1482static int init_node_cache_lock(void) 1483{ 1484 int error; 1485 pthread_mutexattr_t mutexattr; 1486 1487 error = pthread_mutexattr_init(&mutexattr); 1488 require_noerr(error, pthread_mutexattr_init); 1489 1490 error = pthread_mutex_init(&g_node_cache_lock, &mutexattr); 1491 require_noerr(error, pthread_mutex_init); 1492 1493pthread_mutex_init: 1494pthread_mutexattr_init: 1495 1496 return ( error ); 1497} 1498 1499/*****************************************************************************/ 1500 1501/* 1502 * CFURLRef nodecache_get_baseURL(void) 1503 * 1504 * Returns a retained reference to the current gBaseURL. Caller must CFRelease 1505 * the returned reference when done with it. 1506 * 1507 */ 1508CFURLRef nodecache_get_baseURL(void) 1509{ 1510 CFURLRef baseURL; 1511 1512 lock_node_cache(); 1513 CFRetain(gBaseURL); 1514 baseURL = gBaseURL; 1515 unlock_node_cache(); 1516 return baseURL; 1517} 1518 1519/*****************************************************************************/ 1520 void lock_node_cache(void) 1521{ 1522 int error; 1523 1524 error = pthread_mutex_lock(&g_node_cache_lock); 1525 require_noerr_action(error, pthread_mutex_lock, webdav_kill(-1)); 1526 1527pthread_mutex_lock: 1528 1529 return; 1530} 1531 1532/*****************************************************************************/ 1533 1534void unlock_node_cache(void) 1535{ 1536 int error; 1537 1538 error = pthread_mutex_unlock(&g_node_cache_lock); 1539 require_noerr_action(error, pthread_mutex_unlock, webdav_kill(-1)); 1540 1541pthread_mutex_unlock: 1542 1543 return; 1544} 1545 1546/*****************************************************************************/ 1547