246 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Part of libvboxfs - (c) 2012, D.C. van Moolenbroek */
 | 
						|
 | 
						|
#include "inc.h"
 | 
						|
 | 
						|
/*
 | 
						|
 * Directories will generally be accessed sequentially, but there is no
 | 
						|
 * guarantee that this is actually the case.  In particular, multiple user
 | 
						|
 * processes may iterate over the same directory concurrently, and since
 | 
						|
 * process information is lost in the VFS/FS protocol, the result is a
 | 
						|
 * nonsequential pattern on the same single directory handle.  Therefore, we
 | 
						|
 * must support random access.  Since the VirtualBox shared folders interface
 | 
						|
 * does not allow for random access (the resume point cannot be used for this),
 | 
						|
 * we choose to read in the contents of the directory upon open, and cache it
 | 
						|
 * until the directory is closed again.  The risk is that the cached contents
 | 
						|
 * will go stale.
 | 
						|
 *
 | 
						|
 * The directory will always be closed once one reader finishes going through
 | 
						|
 * the entire directory, so the problem is rather limited anyway.  Ideally, the
 | 
						|
 * directory contents would be refreshed upon any invalidating local action as
 | 
						|
 * well as after a certain time span (since the file system can be changed from
 | 
						|
 * the host as well).  This is currently not implemented, because the odds of
 | 
						|
 * things going wrong are pretty small, and the effects are not devastating.
 | 
						|
 *
 | 
						|
 * The calling functions may also request the same directory entry twice in a
 | 
						|
 * row, because the entry does not fit in the user buffer the first time.  The
 | 
						|
 * routines here optimize for repeat-sequential access, while supporting fully
 | 
						|
 * random access as well.
 | 
						|
 */
 | 
						|
 | 
						|
#define VBOXFS_DIRDATA_SIZE	8192	/* data allocation granularity */
 | 
						|
 | 
						|
typedef struct vboxfs_dirblock_s {
 | 
						|
	STAILQ_ENTRY(vboxfs_dirblock_s) next;
 | 
						|
	unsigned int count;
 | 
						|
	char data[VBOXFS_DIRDATA_SIZE];
 | 
						|
} vboxfs_dirblock_t;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	STAILQ_HEAD(blocks, vboxfs_dirblock_s) blocks;
 | 
						|
	unsigned int index;
 | 
						|
	vboxfs_dirblock_t *block;
 | 
						|
	unsigned int bindex;
 | 
						|
	unsigned int bpos;
 | 
						|
} vboxfs_dirdata_t;
 | 
						|
 | 
						|
/*
 | 
						|
 * Free the memory allocated for the given directory contents storage.
 | 
						|
 */
 | 
						|
static void
 | 
						|
free_dir(vboxfs_dirdata_t *dirdata)
 | 
						|
{
 | 
						|
	vboxfs_dirblock_t *block;
 | 
						|
 | 
						|
	while (!STAILQ_EMPTY(&dirdata->blocks)) {
 | 
						|
		block = STAILQ_FIRST(&dirdata->blocks);
 | 
						|
 | 
						|
		STAILQ_REMOVE_HEAD(&dirdata->blocks, next);
 | 
						|
 | 
						|
		free(block);
 | 
						|
	}
 | 
						|
 | 
						|
	free(dirdata);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Read all the contents of the given directory, allocating memory as needed to
 | 
						|
 * store the data.
 | 
						|
 */
 | 
						|
static int
 | 
						|
read_dir(vboxfs_handle_t handle, sffs_dir_t *dirp)
 | 
						|
{
 | 
						|
	vboxfs_dirdata_t *dirdata;
 | 
						|
	vboxfs_dirblock_t *block;
 | 
						|
	vbox_param_t param[8];
 | 
						|
	unsigned int count;
 | 
						|
	int r;
 | 
						|
 | 
						|
	dirdata = (vboxfs_dirdata_t *) malloc(sizeof(vboxfs_dirdata_t));
 | 
						|
	if (dirdata == NULL)
 | 
						|
		return ENOMEM;
 | 
						|
 | 
						|
	memset(dirdata, 0, sizeof(*dirdata));
 | 
						|
	STAILQ_INIT(&dirdata->blocks);
 | 
						|
 | 
						|
	r = OK;
 | 
						|
 | 
						|
	do {
 | 
						|
		block =
 | 
						|
		    (vboxfs_dirblock_t *) malloc(sizeof(vboxfs_dirblock_t));
 | 
						|
		if (block == NULL) {
 | 
						|
			r = ENOMEM;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		vbox_set_u32(¶m[0], vboxfs_root);
 | 
						|
		vbox_set_u64(¶m[1], handle);
 | 
						|
		vbox_set_u32(¶m[2], 0);		/* flags */
 | 
						|
		vbox_set_u32(¶m[3], sizeof(block->data));
 | 
						|
		vbox_set_ptr(¶m[4], NULL, 0, VBOX_DIR_OUT);
 | 
						|
		vbox_set_ptr(¶m[5], block->data, sizeof(block->data),
 | 
						|
		    VBOX_DIR_IN);
 | 
						|
		vbox_set_u32(¶m[6], 0);		/* resume point */
 | 
						|
		vbox_set_u32(¶m[7], 0);		/* number of files */
 | 
						|
 | 
						|
		/* If the call fails, stop. */
 | 
						|
		if ((r = vbox_call(vboxfs_conn, VBOXFS_CALL_LIST, param, 8,
 | 
						|
		    NULL)) != OK) {
 | 
						|
			free(block);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/* If the number of returned files is zero, stop. */
 | 
						|
		if ((count = vbox_get_u32(¶m[7])) == 0) {
 | 
						|
			free(block);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Add the block to the list. We could realloc() the block to
 | 
						|
		 * free unused tail space, but this is not likely to gain us
 | 
						|
		 * much in general.
 | 
						|
		 */
 | 
						|
		block->count = count;
 | 
						|
		STAILQ_INSERT_TAIL(&dirdata->blocks, block, next);
 | 
						|
 | 
						|
		/* Continue until a zero resume point is returned. */
 | 
						|
	} while (vbox_get_u32(¶m[6]) != 0);
 | 
						|
 | 
						|
	if (r != OK) {
 | 
						|
		free_dir(dirdata);
 | 
						|
 | 
						|
		return r;
 | 
						|
	}
 | 
						|
 | 
						|
	dirdata->block = STAILQ_FIRST(&dirdata->blocks);
 | 
						|
 | 
						|
	*dirp = (sffs_dir_t) dirdata;
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Open a directory.
 | 
						|
 */
 | 
						|
int
 | 
						|
vboxfs_opendir(char *path, sffs_dir_t *handle)
 | 
						|
{
 | 
						|
	vboxfs_handle_t h;
 | 
						|
	int r;
 | 
						|
 | 
						|
	/* Open the directory. */
 | 
						|
	if ((r = vboxfs_open_file(path, O_RDONLY, S_IFDIR, &h, NULL)) != OK)
 | 
						|
		return r;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Upon success, read in the full contents of the directory right away.
 | 
						|
	 * If it succeeds, this will also set the caller's directory handle.
 | 
						|
	 */
 | 
						|
	r = read_dir(h, handle);
 | 
						|
 | 
						|
	/* We do not need the directory to be open anymore now. */
 | 
						|
	vboxfs_close_file(h);
 | 
						|
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Read one entry from a directory.  On success, copy the name into buf, and
 | 
						|
 * return the requested attributes.  If the name (including terminating null)
 | 
						|
 * exceeds size, return ENAMETOOLONG.  Do not return dot and dot-dot entries.
 | 
						|
 * Return ENOENT if the index exceeds the number of files.
 | 
						|
 */
 | 
						|
int
 | 
						|
vboxfs_readdir(sffs_dir_t handle, unsigned int index, char *buf, size_t size,
 | 
						|
	struct sffs_attr *attr)
 | 
						|
{
 | 
						|
	vboxfs_dirdata_t *dirdata;
 | 
						|
	vboxfs_dirinfo_t *dirinfo;
 | 
						|
	int r;
 | 
						|
 | 
						|
	dirdata = (vboxfs_dirdata_t *) handle;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the saved index exceeds the requested index, start from the
 | 
						|
	 * beginning.
 | 
						|
	 */
 | 
						|
	if (dirdata->index > index) {
 | 
						|
		dirdata->index = 0;
 | 
						|
		dirdata->bindex = 0;
 | 
						|
		dirdata->bpos = 0;
 | 
						|
		dirdata->block = STAILQ_FIRST(&dirdata->blocks);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Loop until we find the requested entry or we run out of entries. */
 | 
						|
	while (dirdata->block != NULL) {
 | 
						|
		dirinfo =
 | 
						|
		    (vboxfs_dirinfo_t *) &dirdata->block->data[dirdata->bpos];
 | 
						|
 | 
						|
		/* Consider all entries that are not dot or dot-dot. */
 | 
						|
		if (dirinfo->name.len > 2 || dirinfo->name.data[0] != '.' ||
 | 
						|
		    (dirinfo->name.len == 2 && dirinfo->name.data[1] != '.')) {
 | 
						|
 | 
						|
			if (dirdata->index == index)
 | 
						|
				break;
 | 
						|
 | 
						|
			dirdata->index++;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Advance to the next entry. */
 | 
						|
		dirdata->bpos += offsetof(vboxfs_dirinfo_t, name) +
 | 
						|
		    offsetof(vboxfs_path_t, data) + dirinfo->name.size;
 | 
						|
		if (++dirdata->bindex >= dirdata->block->count) {
 | 
						|
			dirdata->block = STAILQ_NEXT(dirdata->block, next);
 | 
						|
			dirdata->bindex = 0;
 | 
						|
			dirdata->bpos = 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Not enough files to satisfy the request? */
 | 
						|
	if (dirdata->block == NULL)
 | 
						|
		return ENOENT;
 | 
						|
 | 
						|
	/* Return the information for the file we found. */
 | 
						|
	if ((r = vboxfs_get_path(&dirinfo->name, buf, size)) != OK)
 | 
						|
		return r;
 | 
						|
 | 
						|
	vboxfs_get_attr(attr, &dirinfo->info);
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Close a directory.
 | 
						|
 */
 | 
						|
int
 | 
						|
vboxfs_closedir(sffs_dir_t handle)
 | 
						|
{
 | 
						|
	vboxfs_dirdata_t *dirdata;
 | 
						|
 | 
						|
	dirdata = (vboxfs_dirdata_t *) handle;
 | 
						|
 | 
						|
	free_dir(dirdata);
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 |