245 lines
6.3 KiB
C
245 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;
|
|
}
|