/* * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "webdavd.h" #include #include #include #include #include #include #include #include #include "webdav_cache.h" #include "OpaqueIDs.h" /*****************************************************************************/ static int open_cache_files = 0; pthread_mutex_t g_node_cache_lock; /*****************************************************************************/ /* * The root node_entry. * The rest of the node_entry entries will be in the tree below the root node_entry. */ struct node_entry *g_root_node; /* * The deleted root node_entry. * When an open file is deleted (including when something is renamed over an * existing node), it will be removed from the main tree and inserted as a child * of this "fake" node_entry. URLs cannot be created for children of this node * (but they should never need to talk to the server anyway -- they are deleted) * Nodes are freed (using nodecache_free_nodes()) only when they are children of this node. */ struct node_entry *g_deleted_root_node; /* * The next fileid number to be assigned. */ u_int32_t g_next_fileid; /* * The file_cache_head list. * Entries are stored in the order inserted into the list. */ struct node_head g_file_list; /* static prototypes */ static int internal_add_attributes( struct node_entry *node, uid_t uid, struct stat *statp, char *appledoubleheader); static int internal_remove_attributes( struct node_entry *node, int remove_appledoubleheader); static int internal_node_appledoubleheader_valid( struct node_entry *node, uid_t uid); static void invalidate_level( struct node_entry *dir_node); static void internal_remove_file_cache( struct node_entry *node); static int internal_add_file_cache( struct node_entry *node, int fd); static int internal_get_node( struct node_entry *parent, size_t name_length, const char *name, int make_entry, int client_created, webdav_filetype_t node_type, struct node_entry **node); static void internal_free_nodes(void); static int internal_move_node( struct node_entry *node, struct node_entry *new_parent, size_t new_name_length, char *new_name); static int delete_node_tree( struct node_entry *node, int recursive); static int internal_invalidate_directory_node_time( struct node_entry *dir_node); static int internal_delete_invalid_directory_nodes( struct node_entry *dir_node); static int internal_get_path_from_node( struct node_entry *target_node, char **path); static int init_node_cache_lock(void); static void lock_node_cache(void); static void unlock_node_cache(void); /*****************************************************************************/ /* this adds or replaces attribute information to node */ static int internal_add_attributes( struct node_entry *node, /* the node_entry to update or add attributes_entry to */ uid_t uid, /* the uid these attributes are valid for */ struct stat *statp, /* the stat buffer */ char *appledoubleheader) /* pointer appledoubleheader or NULL */ { int error; time_t current_time; error = 0; /* first, clear out any old attributes */ (void)internal_remove_attributes(node, (appledoubleheader != NULL)); current_time = time(NULL); require_action(current_time != -1, time, error = errno); /* add appledoubleheader first since it could fail */ if ( appledoubleheader != NULL ) { node->attr_appledoubleheader = malloc(APPLEDOUBLEHEADER_LENGTH); require_action(node->attr_appledoubleheader != NULL, malloc_attr_appledoubleheader, error = ENOMEM); memcpy(node->attr_appledoubleheader, appledoubleheader, APPLEDOUBLEHEADER_LENGTH); node->attr_appledoubleheader_time = current_time; } /* fill in the rest of the fields */ node->attr_uid = uid; node->attr_time = current_time; memcpy(&(node->attr_stat), statp, sizeof(struct stat)); malloc_attr_appledoubleheader: time: return ( error ); } /*****************************************************************************/ int nodecache_add_attributes( struct node_entry *node, /* the node_entry to update or add attributes_entry to */ uid_t uid, /* the uid these attributes are valid for */ struct stat *statp, /* the stat buffer */ char *appledoubleheader) /* pointer appledoubleheader or NULL */ { int error; lock_node_cache(); error = internal_add_attributes(node, uid, statp, appledoubleheader); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* clear out the attribute fields and release any memory */ static int internal_remove_attributes( struct node_entry *node, int remove_appledoubleheader) { node->attr_uid = 0; node->attr_time = 0; memset(&node->attr_stat, 0, sizeof(struct stat)); if ( remove_appledoubleheader && (node->attr_appledoubleheader != NULL) ) { free(node->attr_appledoubleheader); node->attr_appledoubleheader = NULL; node->attr_appledoubleheader_time = 0; } return ( 0 ); } /*****************************************************************************/ int nodecache_remove_attributes( struct node_entry *node) /* the node to remove attributes from */ { int error; lock_node_cache(); error = internal_remove_attributes(node, TRUE); unlock_node_cache(); return ( error ); } /*****************************************************************************/ static int internal_node_appledoubleheader_valid( struct node_entry *node, uid_t uid) { int result; result = ( (node->attr_appledoubleheader != NULL) && /* is there attr_appledoubleheader? */ (node->attr_appledoubleheader_time != 0) && /* 0 attr_appledoubleheader_time is invalid */ ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes? */ (time(NULL) < (node->attr_appledoubleheader_time + FILE_VALIDATION_TIMEOUT)) ); /* don't cache them too long */ return ( result ); } /*****************************************************************************/ int node_appledoubleheader_valid( struct node_entry *node, uid_t uid) { int result; lock_node_cache(); result = internal_node_appledoubleheader_valid(node, uid); unlock_node_cache(); return ( result ); } /*****************************************************************************/ int node_attributes_valid( struct node_entry *node, uid_t uid) { int result; lock_node_cache(); result = ( (node->attr_time != 0) && /* 0 attr_time is invalid */ ((uid == node->attr_uid) || (0 == node->attr_uid)) && /* does this user or root have access to the cached attributes */ (time(NULL) < (node->attr_time + ATTRIBUTES_TIMEOUT_MAX)) ); /* don't cache them too long */ unlock_node_cache(); return ( result ); #if 0 /* FUTURE */ int result; /* are the cached attributes possibly valid and does this user or root have access to them? */ if ( (node->attr_time != 0) && ((uid == node->attr_uid) || (0 == node->attr_uid)) ) { /* * Determine attribute_time_out. It will be something between * ATTRIBUTES_TIMEOUT_MIN and ATTRIBUTES_TIMEOUT_MAX where recently * modified files have a short timeout and files that haven't been * modified in a long time have a long timeout. This is the same * algorithm used by NFS (with different min and max values). */ time_t current_time; time_t attribute_time_out; current_time = time(NULL); attribute_time_out = (current_time - node->attr_stat.st_mtimespec.tv_sec) / 10; if (attribute_time_out < ATTRIBUTES_TIMEOUT_MIN) { attribute_time_out = ATTRIBUTES_TIMEOUT_MIN; } else if (attribute_time_out > ATTRIBUTES_TIMEOUT_MAX) { attribute_time_out = ATTRIBUTES_TIMEOUT_MAX; } /* has too much time passed? */ result = ((current_time - node->attr_time) <= attribute_time_out); } else { result = FALSE; } return ( result ); #endif /* FUTURE */ } /*****************************************************************************/ /* invalidate the node attribute and file cache caches for dir_node's children */ static void invalidate_level(struct node_entry *dir_node) { struct node_entry *node; /* invalidate each child node */ LIST_FOREACH(node, &(dir_node->children), entries) { node->attr_time = 0; node->file_validated_time = 0; /* invalidate this node's children (if any) */ invalidate_level(node); } } /* filesystem_invalidate_caches will call this to do all the work */ void nodecache_invalidate_caches(void) { struct node_entry *node; lock_node_cache(); node = g_root_node; node->attr_time = 0; node->file_validated_time = 0; /* invalidate this node's children (if any) */ invalidate_level(node); unlock_node_cache(); } /*****************************************************************************/ #if 0 static int gtabs; static void display_node_tree_level(struct node_entry *dir_node) { struct node_entry *node; LIST_FOREACH(node, &(dir_node->children), entries) { ++gtabs; 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); display_node_tree_level(node); --gtabs; } } void nodecache_display_node_tree(void) { struct node_entry *node; lock_node_cache(); node = g_root_node; gtabs = 0; 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); display_node_tree_level(node); syslog(LOG_ERR, "-----"); unlock_node_cache(); } void nodecache_display_file_cache(void) { struct node_entry *node; unsigned long count; count = 0; lock_node_cache(); LIST_FOREACH(node, &g_file_list, file_list) { ++count; if ( !NODE_IS_DELETED(node) ) { syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s", node->file_fd, node->fileid, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", NODE_FILE_IS_OPEN(node) ? "+ " : "- ", node->name); } else { syslog(LOG_ERR, "fd: %d, ino %ld %s%s %s", node->file_fd, node->fileid, node->node_type == WEBDAV_DIR_TYPE ? "d" : "f", NODE_FILE_IS_OPEN(node) ? "+X" : "-X", node->name); } } syslog(LOG_ERR, "----- %ld -----", count); unlock_node_cache(); } #endif /*****************************************************************************/ static struct node_entry *g_next_file_cache_node = NULL; struct node_entry *nodecache_get_next_file_cache_node( int get_first) /* if true, return first file cache node; otherwise, the next one */ { struct node_entry *node; lock_node_cache(); if ( get_first ) { node = g_file_list.lh_first; } else { node = g_next_file_cache_node; } if ( node != NULL ) { g_next_file_cache_node = node->file_list.le_next; } else { g_next_file_cache_node = NULL; } unlock_node_cache(); return ( node ); } /*****************************************************************************/ static int internal_add_file_cache( struct node_entry *node, /* the node_entry to add a file_cache_entry to */ int fd) /* the file descriptor of the cache file */ { int error; error = 0; /* only add it if it's not already cached */ require_quiet(!NODE_FILE_IS_CACHED(node), already_cached); while ( open_cache_files >= WEBDAV_MAX_OPEN_FILES ) { struct node_entry *file_node; struct node_entry *victim_node; /* find oldest victim node */ victim_node = NULL; LIST_FOREACH(file_node, &g_file_list, file_list) { if ( !NODE_FILE_IS_OPEN(file_node) ) { victim_node = file_node; } } require_action(victim_node != NULL, too_many_files_open, error = ENFILE); internal_remove_file_cache(victim_node); } ++open_cache_files; node->flags |= nodeInFileListMask; node->file_fd = fd; node->file_status = WEBDAV_DOWNLOAD_NEVER; node->file_validated_time = 0; node->file_inactive_time = 0; node->file_last_modified = -1; if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); node->file_entity_tag = NULL; } node->file_locktoken_uid = 0; if ( node->file_locktoken != NULL ) { free(node->file_locktoken); node->file_locktoken = NULL; } /* add it to the head of the g_file_active_list */ LIST_INSERT_HEAD(&g_file_list, node, file_list); too_many_files_open: already_cached: return ( error ); } /*****************************************************************************/ int nodecache_add_file_cache( struct node_entry *node, /* the node_entry to add a file_cache_entry to */ int fd) /* the file descriptor of the cache file */ { int error; lock_node_cache(); error = internal_add_file_cache(node, fd); unlock_node_cache(); return ( error ); } /*****************************************************************************/ static void internal_remove_file_cache(struct node_entry *node) { if ( NODE_FILE_IS_CACHED(node) ) { if (open_cache_files > 0) { --open_cache_files; } else { debug_string("internal_remove_file_cache: open_cache_files was zero"); } node->flags &= ~nodeInFileListMask; close(node->file_fd); node->file_fd = -1; if ( node == g_next_file_cache_node ) { g_next_file_cache_node = node->file_list.le_next; } /* remove from g_file_list */ LIST_REMOVE(node, file_list); node->file_list.le_next = NULL; node->file_list.le_prev = NULL; node->file_status = WEBDAV_DOWNLOAD_NEVER; node->file_validated_time = 0; node->file_inactive_time = 0; node->file_last_modified = -1; if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); node->file_entity_tag = NULL; } node->file_locktoken_uid = 0; if ( node->file_locktoken != NULL ) { free(node->file_locktoken); node->file_locktoken = NULL; } } } /*****************************************************************************/ void nodecache_remove_file_cache(struct node_entry *node) { lock_node_cache(); internal_remove_file_cache(node); unlock_node_cache(); } /*****************************************************************************/ /* called at mount time to initialize */ int nodecache_init( size_t name_length, /* length of root node name */ char *name, /* the utf8 name of root node */ struct node_entry **root_node) /* the root node */ { int error; error = 0; g_next_fileid = WEBDAV_ROOTFILEID; /* allocate space for g_root_node and its name */ g_root_node = calloc(1, sizeof(struct node_entry)); require_action(g_root_node != NULL, calloc_g_root_node, error = ENOMEM); *root_node = g_root_node; g_root_node->name = malloc(name_length + 1); require_action(g_root_node->name != NULL, malloc_name, error = ENOMEM); /* initialize the node_entry */ g_root_node->parent = NULL; LIST_INIT(&g_root_node->children); g_root_node->name_length = name_length; memcpy(g_root_node->name, name, name_length); g_root_node->name[name_length] = '\0'; g_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull); g_root_node->fileid = g_next_fileid++; g_root_node->node_type = WEBDAV_DIR_TYPE; g_root_node->node_time = time(NULL); error = AssignOpaqueID(g_root_node, &g_root_node->nodeid); require_noerr(error, AssignOpaqueID); /* attribute fields are already zeroed */ /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ g_root_node->file_fd = -1; /* allocate space for g_deleted_root_node and its empty name */ g_deleted_root_node = calloc(1, sizeof(struct node_entry)); require_action(g_root_node != NULL, calloc_g_deleted_root_node, error = ENOMEM); g_deleted_root_node->name = malloc(1); require_action(g_root_node->name != NULL, malloc_g_deleted_root_node_name, error = ENOMEM); /* initialize the node_entry */ g_deleted_root_node->parent = NULL; LIST_INIT(&g_deleted_root_node->children); g_deleted_root_node->name_length = 0; g_deleted_root_node->name[0] = '\0'; g_deleted_root_node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, g_deleted_root_node->name, kCFStringEncodingUTF8, kCFAllocatorNull); /* attribute fields are already zeroed */ /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ g_deleted_root_node->file_fd = -1; /* initialize g_file_list header */ LIST_INIT(&g_file_list); error = init_node_cache_lock(); malloc_g_deleted_root_node_name: calloc_g_deleted_root_node: malloc_name: AssignOpaqueID: calloc_g_root_node: return ( error ); } /*****************************************************************************/ /* * Finds a node by name in the parent node's children. If the node is * not found and make_entry is TRUE, then internal_get_node creates a new node. */ static int internal_get_node( struct node_entry *parent, /* the parent node_entry */ size_t name_length, /* length of name */ const char *name, /* the utf8 name of the node */ int make_entry, /* TRUE if a new node_entry should be created if needed */ int client_created, /* TRUE if this client created the node (used in conjunction with make_entry */ webdav_filetype_t node_type, /* if make_entry is TRUE, the type of node to create */ struct node_entry **node) /* the found (or new) node_entry */ { struct node_entry *node_ptr; int error; node_ptr = NULL; error = 0; if ( name_length != 0 && name != NULL ) { CFStringRef name_string; name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)name, name_length, kCFStringEncodingUTF8, false); require_action(name_string != NULL, out, error = EINVAL); /* search for an existing node_entry */ LIST_FOREACH(node_ptr, &(parent->children), entries) { if ( CFStringCompare(name_string, node_ptr->name_ref, kCFCompareNonliteral) == kCFCompareEqualTo ) { break; } } CFRelease(name_string); } else { node_ptr = parent; } /* if we didn't find it and we're supposed to create it... */ if ( node_ptr == NULL ) { if ( make_entry ) { /* allocate new node_entry */ node_ptr = calloc(1, sizeof(struct node_entry)); require_action(node_ptr != NULL, calloc_node_ptr, error = ENOMEM; webdav_kill(-1)); /* allocate space for its name */ node_ptr->name = malloc(name_length + 1); require_action(node_ptr->name != NULL, malloc_name, node_ptr = NULL; error = ENOMEM; webdav_kill(-1)); /* initialize the node_entry */ node_ptr->parent = parent; LIST_INIT(&node_ptr->children); node_ptr->name_length = name_length; memcpy(node_ptr->name, name, name_length); node_ptr->name[name_length] = '\0'; node_ptr->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node_ptr->name, kCFStringEncodingUTF8, kCFAllocatorNull); node_ptr->fileid = g_next_fileid++; node_ptr->node_type = node_type; node_ptr->node_time = time(NULL); if ( client_created ) { node_ptr->flags |= nodeRecentMask; } error = AssignOpaqueID(node_ptr, &node_ptr->nodeid); require_noerr_action(error, AssignOpaqueID, node_ptr = NULL; webdav_kill(-1)); /* attribute fields are already zeroed */ /* file cache fields are already zeroed - set file_fd to indicate there is no cache file */ node_ptr->file_fd = -1; /* insert the node_entry into the parent's children list */ LIST_INSERT_HEAD(&parent->children, node_ptr, entries); } else { error = ENOENT; } } else { /* update node_time of existing node */ if ( make_entry ) { node_ptr->node_time = time(NULL); } /* set or clear the recent flag */ if ( client_created ) { node_ptr->flags |= nodeRecentMask; } else { node_ptr->flags &= ~nodeRecentMask; } } AssignOpaqueID: malloc_name: calloc_node_ptr: out: /* return node_entry */ if ( error == 0 ) { *node = node_ptr; } else { *node = NULL; } return ( error ); } /*****************************************************************************/ int nodecache_get_node( struct node_entry *parent, /* the parent node_entry */ size_t name_length, /* length of name */ const char *name, /* the utf8 name of the node */ int make_entry, /* TRUE if a new node_entry should be created if needed*/ int client_created, /* TRUE if this client created the node (used in conjunction with make_entry */ webdav_filetype_t node_type, /* if make_entry is TRUE, the type of node to create */ struct node_entry **node) /* the found (or new) node */ { int error; lock_node_cache(); error = internal_get_node(parent, name_length, name, make_entry, client_created, node_type, node); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* * internal_free_nodes * This function frees uncached nodes on the deleted list. */ static void internal_free_nodes(void) { struct node_entry *node; node = g_deleted_root_node->children.lh_first; while ( node != NULL ) { struct node_entry *next_node; next_node = node->entries.le_next; /* free this node ONLY if it's NOT on the file list */ if ( !NODE_FILE_IS_CACHED(node) ) { /* remove the node_entry from the list it is in */ LIST_REMOVE(node, entries); /* invalidate the nodeid */ (void) DeleteOpaqueID(node->nodeid); node->nodeid = kInvalidOpaqueID; if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); node->file_entity_tag = NULL; } node->file_locktoken_uid = 0; if ( node->file_locktoken != NULL ) { free(node->file_locktoken); node->file_locktoken = NULL; } /* free memory used by the node */ free(node->name); CFRelease(node->name_ref); (void) internal_remove_attributes(node, TRUE); free(node); } node = next_node; } } /*****************************************************************************/ void nodecache_free_nodes(void) { lock_node_cache(); internal_free_nodes(); unlock_node_cache(); } /*****************************************************************************/ static int internal_move_node( struct node_entry *node, /* the node_entry to move */ struct node_entry *new_parent, /* the new parent node_entry */ size_t new_name_length, /* length of new_name or 0 if not renaming */ char *new_name) /* the utf8 new name of the node or NULL */ { int error; error = 0; /* new name? */ if ( (new_name_length != 0) && (new_name != NULL) ) { CFStringRef name_string; CFComparisonResult compare_result; name_string = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8 *)new_name, new_name_length, kCFStringEncodingUTF8, false); compare_result = CFStringCompare(name_string, node->name_ref, kCFCompareNonliteral); CFRelease(name_string); /* did the name change? */ if ( compare_result != kCFCompareEqualTo ) { char *name; /* allocate space for new name */ name = malloc(new_name_length + 1); require_action(name != NULL, malloc_name, error = errno; webdav_kill(-1)); free(node->name); CFRelease(node->name_ref); node->name = name; memcpy(node->name, new_name, new_name_length); node->name[new_name_length] = '\0'; node->name_ref = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, node->name, kCFStringEncodingUTF8, kCFAllocatorNull); node->name_length = new_name_length; } } /* change parents? */ if ( node->parent != new_parent ) { /* move the node_entry to the new parent */ LIST_REMOVE(node, entries); LIST_INSERT_HEAD(&new_parent->children, node, entries); node->parent = new_parent; } malloc_name: return ( error ); } /*****************************************************************************/ int nodecache_move_node( struct node_entry *node, /* the node_entry to move */ struct node_entry *new_parent, /* the new parent node_entry */ size_t new_name_length, /* length of new_name or 0 if not renaming */ char *new_name) /* the utf8 new name of the node or NULL */ { int error; lock_node_cache(); error = internal_move_node(node, new_parent, new_name_length, new_name); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* delete dir_node's descendents and then delete dir_node */ static int delete_node_tree( struct node_entry *node, /* the node_entry to delete */ int recursive) /* delete recursively if node is a directory */ { int error; error = 0; if ( recursive ) { struct node_entry *child_node; child_node = (node->children).lh_first; while ( child_node != NULL ) { struct node_entry *next_node; next_node = child_node->entries.le_next; error = delete_node_tree(child_node, TRUE); require_noerr(error, delete_child_node); child_node = next_node; } } else { require_action((node->children).lh_first == NULL, has_children, error = EBUSY); } /* move node over to the deleted list and mark it as deleted */ error = internal_move_node(node, g_deleted_root_node, 0, NULL); require_noerr(error, internal_move_node); node->flags |= nodeDeletedMask; node->attr_time = 0; internal_move_node: has_children: delete_child_node: return ( error ); } /*****************************************************************************/ int nodecache_delete_node( struct node_entry *node, /* the node_entry to delete */ int recursive) /* delete recursively if node is a directory */ { int error; lock_node_cache(); error = delete_node_tree(node, recursive); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* invalidate dir_node's children's node_times */ static int internal_invalidate_directory_node_time(struct node_entry *dir_node) { int error; struct node_entry *node; error = 0; require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR); LIST_FOREACH(node, &(dir_node->children), entries) { node->node_time = 0; } not_directory: return ( error ); } /*****************************************************************************/ int nodecache_invalidate_directory_node_time( struct node_entry *dir_node) /* parent directory node */ { int error; lock_node_cache(); error = internal_invalidate_directory_node_time(dir_node); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* invalidate dir_node's children's node_times */ static int internal_delete_invalid_directory_nodes(struct node_entry *dir_node) { int error; struct node_entry *node; error = 0; require_action(dir_node->node_type == WEBDAV_DIR_TYPE, not_directory, error = ENOTDIR); node = (&(dir_node->children))->lh_first; while ( node != NULL ) { struct node_entry *next_node; next_node = node->entries.le_next; if ( node->node_time == 0 ) { error = delete_node_tree(node, TRUE); require_noerr(error, delete_node_tree); } node = next_node; } delete_node_tree: not_directory: return ( error ); } /*****************************************************************************/ int nodecache_delete_invalid_directory_nodes( struct node_entry *dir_node) /* parent directory node */ { int error; lock_node_cache(); error = internal_delete_invalid_directory_nodes(dir_node); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /* * nodecache_get_path_from_node * * Builds a UTF-8 slash '/' delimited NULL terminated path from * the root node (but not including the root node) to target_node. * If the node is directory, the path MUST end with a slash. * The path will MUST NOT start with a slash since this is always a relative path. * If result is 0 (no error), then a newly allocated buffer containing the * path is returned and the caller is responsible for freeing it. */ static int internal_get_path_from_node( struct node_entry *target_node, char **path) { int error; struct node_entry *cur_node; char *cur_ptr; char *pathbuf; size_t path_len; error = 0; pathbuf = NULL; path_len = 0; require_action(!NODE_IS_DELETED(target_node), node_deleted, error = ENOENT); pathbuf = malloc(PATH_MAX); require_action(pathbuf != NULL, malloc_pathbuf, error = errno); if ( target_node == g_root_node ) { /* nothing to do */ *pathbuf = '\0'; } else { /* point at last character and put in the string terminator */ cur_ptr = pathbuf + PATH_MAX - 1; *cur_ptr = '\0'; cur_node = target_node; while ( cur_node != g_root_node ) { require_action(cur_node != NULL, name_too_long, error = ENAMETOOLONG); /* make sure there's enough room for the next node name and a slash */ require_action((cur_ptr - cur_node->name_length + 1) >= pathbuf, name_too_long, error = ENAMETOOLONG); /* add a slash */ --cur_ptr; *cur_ptr = '/'; /* copy in the name */ cur_ptr -= cur_node->name_length; memcpy(cur_ptr, cur_node->name, cur_node->name_length); cur_node = cur_node->parent; } /* move the path to the front of the buffer */ path_len = strlen(cur_ptr); memmove(pathbuf, cur_ptr, path_len + 1); /* remove trailing slash if not directory */ if ( target_node->node_type != WEBDAV_DIR_TYPE ) { --(path_len); pathbuf[path_len] = '\0'; } } name_too_long: malloc_pathbuf: node_deleted: if ( error ) { if ( pathbuf != NULL ) { free(pathbuf); pathbuf = NULL; } } *path = pathbuf; return ( error ); } /*****************************************************************************/ int nodecache_get_path_from_node( struct node_entry *node, /* -> node */ char **path) /* <- relative path to root node */ { int error; lock_node_cache(); error = internal_get_path_from_node(node, path); unlock_node_cache(); return ( error ); } /*****************************************************************************/ /*****************************************************************************/ static int init_node_cache_lock(void) { int error; pthread_mutexattr_t mutexattr; error = pthread_mutexattr_init(&mutexattr); require_noerr(error, pthread_mutexattr_init); error = pthread_mutex_init(&g_node_cache_lock, &mutexattr); require_noerr(error, pthread_mutex_init); pthread_mutex_init: pthread_mutexattr_init: return ( error ); } /*****************************************************************************/ static void lock_node_cache(void) { int error; error = pthread_mutex_lock(&g_node_cache_lock); require_noerr_action(error, pthread_mutex_lock, webdav_kill(-1)); pthread_mutex_lock: return; } /*****************************************************************************/ static void unlock_node_cache(void) { int error; error = pthread_mutex_unlock(&g_node_cache_lock); require_noerr_action(error, pthread_mutex_unlock, webdav_kill(-1)); pthread_mutex_unlock: return; } /*****************************************************************************/