/* * Copyright (c) 2000-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 #include #include #include #include "webdav_cache.h" #include "webdav_network.h" /*****************************************************************************/ #define WEBDAV_STATFS_TIMEOUT 60 /* Number of seconds statfs_cache_buffer is valid */ static time_t statfs_cache_time; static struct statfs statfs_cache_buffer; static int vfc_typenum; static pthread_mutex_t webdav_cachefile_lock; /* this mutex protects webdav_cachefile */ static int webdav_cachefile; /* file descriptor for an empty, unlinked cache file or -1 */ /*****************************************************************************/ static int get_cachefile(int *fd); static void save_cachefile(int fd); static int associate_cachefile(int ref, int fd); /*****************************************************************************/ #define TMP_CACHE_DIR _PATH_TMP ".webdavcache" /* Directory for local file cache */ #define CACHEFILE_TEMPLATE "webdav.XXXXXX" /* template for cache files */ /* get_cachefile returns the fd for a cache file. If webdav_cachefile is not * storing a cache file fd, open/create a new temp file and return it. * Otherwise, return the stored cache file fd. */ static int get_cachefile(int *fd) { int error, mutexerror; char pathbuf[MAXPATHLEN]; int retrycount; error = 0; error = pthread_mutex_lock(&webdav_cachefile_lock); require_noerr_action(error, pthread_mutex_lock, webdav_kill(-1)); /* did a previous call leave a cache file for us to use? */ if ( webdav_cachefile < 0 ) { /* no, so create a temp file */ retrycount = 0; /* don't get stuck here forever */ while ( retrycount < 5 ) { ++retrycount; error = 0; if ( *gWebdavCachePath == '\0' ) { /* create a template with our pid */ sprintf(gWebdavCachePath, "%s.%lu.XXXXXX", TMP_CACHE_DIR, (unsigned long)getpid()); /* create the cache directory */ require_action(mkdtemp(gWebdavCachePath) != NULL, mkdtemp, error = errno); } /* create a template for the cache file */ sprintf(pathbuf, "%s/%s", gWebdavCachePath, CACHEFILE_TEMPLATE); /* create and open the cache file */ *fd = mkstemp(pathbuf); if ( *fd != -1 ) { /* unlink it so the last close will delete it */ verify_noerr(unlink(pathbuf)); break; /* break with success */ } else { error = errno; if ( ENOENT == error ) { /* the gWebdavCachePath directory is missing, clear the old one and try again */ *gWebdavCachePath = '\0'; continue; } else { debug_string("mkstemp failed"); break; /* break with error */ } } } } else { /* yes, so grab it */ *fd = webdav_cachefile; webdav_cachefile = -1; } mkdtemp: mutexerror = pthread_mutex_unlock(&webdav_cachefile_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, error = mutexerror; webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return ( error ); } /*****************************************************************************/ /* save_cachefile saves a cache file fd that wasn't needed. If there is already * stored a cache file fd, then the input fd is closed (closing will only * happen when there there is a race between multiple open requests so it * should be rare). */ static void save_cachefile(int fd) { int mutexerror; mutexerror = pthread_mutex_lock(&webdav_cachefile_lock); require_noerr_action(mutexerror, pthread_mutex_lock, webdav_kill(-1)); /* are we already storing a cache file that wasn't used? */ if ( webdav_cachefile < 0 ) { /* no, so store this one */ webdav_cachefile = fd; } else { /* yes, so close this one */ close(fd); } mutexerror = pthread_mutex_unlock(&webdav_cachefile_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return; } /*****************************************************************************/ static int associate_cachefile(int ref, int fd) { int error = 0; int mib[5]; /* setup mib for the request */ mib[0] = CTL_VFS; mib[1] = vfc_typenum; mib[2] = WEBDAV_ASSOCIATECACHEFILE_SYSCTL; mib[3] = ref; mib[4] = fd; require_noerr_action(sysctl(mib, 5, NULL, NULL, NULL, 0), sysctl, error = errno); sysctl: return ( error ); } /*****************************************************************************/ int filesystem_init(int typenum) { pthread_mutexattr_t mutexattr; int error; vfc_typenum = typenum; /* Set up the statfs timeout & buffer */ bzero(&statfs_cache_buffer, sizeof(statfs_cache_buffer)); statfs_cache_time = 0; webdav_cachefile = -1; /* closed */ /* set up the lock on the queues */ error = pthread_mutexattr_init(&mutexattr); require_noerr(error, pthread_mutexattr_init); error = pthread_mutex_init(&webdav_cachefile_lock, &mutexattr); require_noerr(error, pthread_mutex_init); pthread_mutex_init: pthread_mutexattr_init: return ( error ); } /*****************************************************************************/ int filesystem_open(struct webdav_request_open *request_open, struct webdav_reply_open *reply_open) { int error; struct node_entry *node; int theCacheFile; char *locktoken; reply_open->pid = 0; locktoken = NULL; require_action(request_open->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_open->obj_ref; /* get a cache file */ error = get_cachefile(&theCacheFile); require_noerr_quiet(error, get_cachefile); if (node->node_type == WEBDAV_FILE_TYPE) { int write_mode; if ( NODE_FILE_IS_CACHED(node) ) { /* save the cache file we didn't need */ save_cachefile(theCacheFile); /* mark the old cache file active again */ node->file_inactive_time = 0; } else { error = nodecache_add_file_cache(node, theCacheFile); require_noerr_action_quiet(error, nodecache_add_file_cache, save_cachefile(theCacheFile)); /* If we get an error beyond this point we need to call nodecache_remove_file_cache() */ } write_mode = ((request_open->flags & O_ACCMODE) != O_RDONLY); if ( write_mode ) { /* If we are opening this file for write access, lock it first, before we copy it into the cache file from the server, or truncate the cache file. */ error = network_lock(request_open->pcr.pcr_uid, FALSE, node); } if ( !error ) { if ( write_mode && (request_open->flags & O_TRUNC) ) { /* * If opened for write and truncate, we can set the length to zero * and not get it from the server. */ require_noerr_action(fchflags(node->file_fd, 0), fchflags, error = errno); require_noerr_action(ftruncate(node->file_fd, 0LL), ftruncate, error = errno); node->file_status = WEBDAV_DOWNLOAD_FINISHED; } else { error = network_open(request_open->pcr.pcr_uid, node, write_mode); } } } else { /* it's a directory */ error = nodecache_add_file_cache(node, theCacheFile); require_noerr_action_quiet(error, nodecache_add_file_cache, save_cachefile(theCacheFile)); /* If we get an error beyond this point we need to call nodecache_remove_file_cache() */ /* Directory opens are always done in the foreground so set the * download status to done */ node->file_status = WEBDAV_DOWNLOAD_FINISHED; } if ( !error ) { /* all good so far -- associate the cachefile with the webdav file */ error = associate_cachefile(request_open->ref, node->file_fd); if ( 0 == error ) { reply_open->pid = getpid(); } else { error = errno; } } fchflags: ftruncate: /* clean up if error */ if ( error ) { /* unlock it if it was locked */ if ( node->file_locktoken != NULL ) { /* nothing we can do network_unlock fails -- the lock will time out eventually */ (void) network_unlock(node); } /* remove it from the file cache */ nodecache_remove_file_cache(node); } nodecache_add_file_cache: get_cachefile: bad_obj_ref: return ( error ); } /*****************************************************************************/ int filesystem_close(struct webdav_request_close *request_close) { int error = 0; struct node_entry *node; require_action(request_close->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_close->obj_ref; /* Trying to close something we did not open? */ require_action(NODE_FILE_IS_CACHED(node), not_open, error = EBADF); /* Kill any threads that may be downloading data for this file */ if ( (node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) == WEBDAV_DOWNLOAD_IN_PROGRESS ) { node->file_status |= WEBDAV_DOWNLOAD_TERMINATED; } while ( (node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) == WEBDAV_DOWNLOAD_IN_PROGRESS ) { /* wait for the downloading thread to acknowledge that we stopped.*/ usleep(10000); /* 10 milliseconds */ } /* set the file_inactive_time */ time(&node->file_inactive_time); /* if the file was locked, unlock it */ if ( node->file_locktoken ) { error = network_unlock(node); } /* * If something went wrong with this file, it was deleted, or it is * a directory, then remove it from the file cache. */ if ( error || NODE_IS_DELETED(node) || (node->node_type == WEBDAV_DIR_TYPE) ) { (void)nodecache_remove_file_cache(node); } not_open: bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_lookup(struct webdav_request_lookup *request_lookup, struct webdav_reply_lookup *reply_lookup) { int error; struct node_entry *node; struct node_entry *parent_node; struct stat statbuf; require_action(request_lookup->dir != 0, bad_obj_ref, error = EIO); parent_node = (struct node_entry *)request_lookup->dir; /* see if we already have a node */ error = nodecache_get_node(parent_node, request_lookup->name_length, request_lookup->name, FALSE, 0, &node); if ( error || !node_attributes_valid(node, request_lookup->pcr.pcr_uid) ) { /* look it up on the server */ error = network_lookup(request_lookup->pcr.pcr_uid, parent_node, request_lookup->name, request_lookup->name_length, &statbuf); if ( !error ) { /* create a new node */ error = nodecache_get_node(parent_node, request_lookup->name_length, request_lookup->name, TRUE, S_ISREG(statbuf.st_mode) ? WEBDAV_FILE_TYPE : WEBDAV_DIR_TYPE, &node); if ( !error ) { /* cache the attributes */ error = nodecache_add_attributes(node, request_lookup->pcr.pcr_uid, &statbuf, NULL); } } } if ( !error ) { /* we have the attributes cached */ reply_lookup->obj_ref = (object_ref)node; reply_lookup->obj_fileid = node->fileid; reply_lookup->obj_type = node->node_type; reply_lookup->obj_atime = node->attr_stat.st_atimespec; reply_lookup->obj_mtime = node->attr_stat.st_mtimespec; reply_lookup->obj_ctime = node->attr_stat.st_ctimespec; reply_lookup->obj_filesize = node->attr_stat.st_size; } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_getattr(struct webdav_request_getattr *request_getattr, struct webdav_reply_getattr *reply_getattr) { int error; struct node_entry *node; struct stat statbuf; require_action(request_getattr->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_getattr->obj_ref; /* see if we have valid attributes */ if ( !node_attributes_valid(node, request_getattr->pcr.pcr_uid) ) { /* no... look it up on the server */ error = network_getattr( request_getattr->pcr.pcr_uid, node, &statbuf); if ( !error ) { /* cache the attributes */ error = nodecache_add_attributes(node, request_getattr->pcr.pcr_uid, &statbuf, NULL); } } else { error = 0; } if ( !error ) { /* we have the attributes cached */ bcopy(&node->attr_stat, &reply_getattr->obj_attr, sizeof(struct stat)); } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_statfs(struct webdav_request_statfs *request_statfs, struct webdav_reply_statfs *reply_statfs) { int error; time_t thetime; int call_server; require_action(request_statfs->root_obj_ref != 0, bad_obj_ref, error = EIO); if ( gSuppressAllUI ) { /* If we're suppressing UI, we don't need statfs to get quota info from the server. */ error = 0; } else { thetime = time(0); if ( thetime != -1 ) { /* do we need to call the server? */ call_server = (statfs_cache_time == 0) || (thetime > (statfs_cache_time + WEBDAV_STATFS_TIMEOUT)); } else { /* if we can't get the time we'll zero thetime and call the server */ thetime = 0; call_server = TRUE; } if ( call_server ) { /* update the cached statfs buffer */ error = network_statfs(request_statfs->pcr.pcr_uid, (struct node_entry *)request_statfs->root_obj_ref, &statfs_cache_buffer); if ( !error ) { /* update the time the cached statfs buffer was updated */ statfs_cache_time = thetime; } } else { error = 0; } if ( !error ) { bcopy(&statfs_cache_buffer, &reply_statfs->fs_attr, sizeof(struct statfs)); } } bad_obj_ref: return (error); } /*****************************************************************************/ /* * The only errors expected from filesystem_mount are: * ECANCELED - the user could not authenticate and cancelled the mount * ENODEV - we could not connect to the server (bad URL, server down, etc.) */ int filesystem_mount(int *a_mount_args) { int error; error = network_mount(getuid(), a_mount_args); return (error); } /*****************************************************************************/ int filesystem_create(struct webdav_request_create *request_create, struct webdav_reply_create *reply_create) { int error; struct node_entry *node; struct node_entry *parent_node; time_t creation_date; require_action(request_create->dir != 0, bad_obj_ref, error = EIO); parent_node = (struct node_entry *)request_create->dir; error = network_create(request_create->pcr.pcr_uid, parent_node, request_create->name, request_create->name_length, &creation_date); if ( !error ) { /* * we just changed the parent_node so update or remove its attributes */ if ( (creation_date != -1) && /* if we know when the creation occurred */ (parent_node->attr_stat.st_mtimespec.tv_sec <= creation_date) && /* and that time is later than what's cached */ node_attributes_valid(parent_node, request_create->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ parent_node->attr_stat.st_mtimespec.tv_sec = creation_date; parent_node->attr_stat.st_atimespec = parent_node->attr_stat.st_ctimespec = parent_node->attr_stat.st_mtimespec; parent_node->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(parent_node); } /* Create a node */ error = nodecache_get_node(parent_node, request_create->name_length, request_create->name, TRUE, WEBDAV_FILE_TYPE, &node); if ( !error ) { reply_create->obj_ref = (object_ref)node; reply_create->obj_fileid = node->fileid; } } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_mkdir(struct webdav_request_mkdir *request_mkdir, struct webdav_reply_mkdir *reply_mkdir) { int error; struct node_entry *node; struct node_entry *parent_node; time_t creation_date; require_action(request_mkdir->dir != 0, bad_obj_ref, error = EIO); parent_node = (struct node_entry *)request_mkdir->dir; error = network_mkdir(request_mkdir->pcr.pcr_uid, parent_node, request_mkdir->name, request_mkdir->name_length, &creation_date); if ( !error ) { /* * we just changed the parent_node so update or remove its attributes */ if ( (creation_date != -1) && /* if we know when the creation occurred */ (parent_node->attr_stat.st_mtimespec.tv_sec <= creation_date) && /* and that time is later than what's cached */ node_attributes_valid(parent_node, request_mkdir->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ parent_node->attr_stat.st_mtimespec.tv_sec = creation_date; parent_node->attr_stat.st_atimespec = parent_node->attr_stat.st_ctimespec = parent_node->attr_stat.st_mtimespec; parent_node->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(parent_node); } /* Create a node */ error = nodecache_get_node(parent_node, request_mkdir->name_length, request_mkdir->name, TRUE, WEBDAV_DIR_TYPE, &node); if ( !error ) { reply_mkdir->obj_ref = (object_ref)node; reply_mkdir->obj_fileid = node->fileid; } } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_read(struct webdav_request_read *request_read, char **a_byte_addr, size_t *a_size) { int error; require_action(request_read->obj_ref != 0, bad_obj_ref, error = EIO); error = network_read(request_read->pcr.pcr_uid, (struct node_entry *)request_read->obj_ref, request_read->offset, request_read->count, a_byte_addr, a_size); bad_obj_ref: return ( error ); } /*****************************************************************************/ int filesystem_rename(struct webdav_request_rename *request_rename) { int error = 0; struct node_entry *f_node; struct node_entry *t_node; struct node_entry *parent_node; time_t rename_date; require_action(request_rename->from_obj_ref != 0, bad_from_obj_ref, error = EIO); require_action(request_rename->to_dir_ref != 0, bad_to_dir_ref, error = EIO); f_node = (struct node_entry *)request_rename->from_obj_ref; parent_node = (struct node_entry *)request_rename->to_dir_ref; if ( request_rename->to_obj_ref != 0 ) { /* "to" exists */ t_node = (struct node_entry *)request_rename->to_obj_ref; /* "from" and "to" must be the same file_type */ if ( f_node->node_type != t_node->node_type ) { /* return the appropriate error */ error = (f_node->node_type == WEBDAV_FILE_TYPE) ? EISDIR : ENOTDIR; } else { error = 0; } } else { t_node = NULL; error = 0; } if ( !error ) { error = network_rename(request_rename->pcr.pcr_uid, f_node, t_node, parent_node, request_rename->to_name, request_rename->to_name_length, &rename_date); if ( !error ) { /* * we just changed the parent node(s) so update or remove their attributes */ if ( (rename_date != -1) && /* if we know when the creation occurred */ (f_node->parent->attr_stat.st_mtimespec.tv_sec <= rename_date) && /* and that time is later than what's cached */ node_attributes_valid(f_node->parent, request_rename->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ f_node->parent->attr_stat.st_mtimespec.tv_sec = rename_date; f_node->parent->attr_stat.st_atimespec = f_node->parent->attr_stat.st_ctimespec = f_node->parent->attr_stat.st_mtimespec; f_node->parent->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(f_node->parent); } if ( f_node->parent != parent_node ) { if ( (rename_date != -1) && /* if we know when the creation occurred */ (parent_node->attr_stat.st_mtimespec.tv_sec <= rename_date) && /* and that time is later than what's cached */ node_attributes_valid(parent_node, request_rename->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ parent_node->attr_stat.st_mtimespec.tv_sec = rename_date; parent_node->attr_stat.st_atimespec = parent_node->attr_stat.st_ctimespec = parent_node->attr_stat.st_mtimespec; parent_node->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(parent_node); } } if ( t_node != NULL ) { if ( nodecache_delete_node(t_node) != 0 ) { debug_string("nodecache_delete_node failed"); } } /* move "from" node into the destination directory (with a possible rename) */ if ( nodecache_move_node(f_node, parent_node, request_rename->to_name_length, request_rename->to_name) != 0 ) { debug_string("nodecache_move_node failed"); } statfs_cache_time = 0; } } bad_to_dir_ref: bad_from_obj_ref: return (error); } /*****************************************************************************/ int filesystem_remove(struct webdav_request_remove *request_remove) { int error; struct node_entry *node; time_t remove_date; require_action(request_remove->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_remove->obj_ref; error = network_remove(request_remove->pcr.pcr_uid, node, &remove_date); if ( !error ) { /* * we just changed the parent_node so update or remove its attributes */ if ( (remove_date != -1) && /* if we know when the creation occurred */ (node->parent->attr_stat.st_mtimespec.tv_sec <= remove_date) && /* and that time is later than what's cached */ node_attributes_valid(node->parent, request_remove->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ node->parent->attr_stat.st_mtimespec.tv_sec = remove_date; node->parent->attr_stat.st_atimespec = node->parent->attr_stat.st_ctimespec = node->parent->attr_stat.st_mtimespec; node->parent->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(node->parent); } if ( nodecache_delete_node(node) != 0 ) { debug_string("nodecache_delete_node failed"); } statfs_cache_time = 0; } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_rmdir(struct webdav_request_rmdir *request_rmdir) { int error; struct node_entry *node; time_t remove_date; require_action(request_rmdir->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_rmdir->obj_ref; /* it must be empty in the node tree */ if ( (node->children).lh_first == NULL ) { error = network_rmdir(request_rmdir->pcr.pcr_uid, node, &remove_date); if ( !error ) { /* * we just changed the parent_node so update or remove its attributes */ if ( (remove_date != -1) && /* if we know when the creation occurred */ (node->parent->attr_stat.st_mtimespec.tv_sec <= remove_date) && /* and that time is later than what's cached */ node_attributes_valid(node->parent, request_rmdir->pcr.pcr_uid) ) /* and the cache is valid */ { /* update the times of the cached attributes */ node->parent->attr_stat.st_mtimespec.tv_sec = remove_date; node->parent->attr_stat.st_atimespec = node->parent->attr_stat.st_ctimespec = node->parent->attr_stat.st_mtimespec; node->parent->attr_time = time(NULL); } else { /* remove the attributes */ (void)nodecache_remove_attributes(node->parent); } if ( nodecache_delete_node(node) != 0 ) { debug_string("nodecache_delete_node failed"); } statfs_cache_time = 0; } } else { error = ENOTEMPTY; } bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_fsync(struct webdav_request_fsync *request_fsync) { int error; struct node_entry *node; require_action(request_fsync->obj_ref != 0, bad_obj_ref, error = EIO); node = (struct node_entry *)request_fsync->obj_ref; /* Trying to fsync something that's not open? */ require_action(NODE_FILE_IS_CACHED(node), not_open, error = EBADF); /* The kernel should not send us an fsync until the file is downloaded */ require_action((node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) == WEBDAV_DOWNLOAD_FINISHED, still_downloading, error = EIO); error = network_fsync(request_fsync->pcr.pcr_uid, node); /* we just changed the file so remove its attributes */ (void)nodecache_remove_attributes(node); /* and we changed the volume so invalidate the statfs cache */ statfs_cache_time = 0; still_downloading: not_open: bad_obj_ref: return ( error ); } /*****************************************************************************/ int filesystem_readdir(struct webdav_request_readdir *request_readdir) { int error; require_action(request_readdir->obj_ref != 0, bad_obj_ref, error = EIO); error = network_readdir(request_readdir->pcr.pcr_uid, request_readdir->cache, (struct node_entry *)request_readdir->obj_ref); bad_obj_ref: return (error); } /*****************************************************************************/ int filesystem_lock(struct node_entry *node) { int error; require_action(node != NULL, null_node, error = EIO); if ( node->file_locktoken != NULL ) { error = network_lock(0, TRUE, node); /* uid for refreshes is ignored */ } else { error = 0; } null_node: return ( error ); } /*****************************************************************************/ int filesystem_invalidate_caches(struct webdav_request_invalcaches *request_invalcaches) { int error; /* only the owner (mounter) can invalidate */ require_action(request_invalcaches->pcr.pcr_uid == gProcessUID, not_permitted, error = EPERM); nodecache_invalidate_caches(); error = 0; not_permitted: return (error); } /*****************************************************************************/