/*
 * 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 <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <paths.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/sysctl.h>

#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);
}

/*****************************************************************************/
