/*
* Copyright (c) 2008 Jakub Jermar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/** @addtogroup vfs
* @{
*/
/**
* @file vfs_lookup.c
* @brief
*/
#include "vfs.h"
#include <macros.h>
#include <async.h>
#include <errno.h>
#include <str.h>
#include <stdarg.h>
#include <stdbool.h>
#include <fibril_synch.h>
#include <adt/list.h>
#include <vfs/canonify.h>
#include <dirent.h>
#include <assert.h>
FIBRIL_MUTEX_INITIALIZE(plb_mutex);
LIST_INITIALIZE(plb_entries); /**< PLB entry ring buffer. */
uint8_t *plb = NULL;
static errno_t plb_insert_entry(plb_entry_t *entry, char *path, size_t *start,
size_t len)
{
fibril_mutex_lock(&plb_mutex);
link_initialize(&entry->plb_link);
entry->len = len;
size_t first; /* the first free index */
size_t last; /* the last free index */
if (list_empty(&plb_entries)) {
first = 0;
last = PLB_SIZE - 1;
} else {
plb_entry_t *oldest = list_get_instance(
list_first(&plb_entries), plb_entry_t, plb_link);
plb_entry_t *newest = list_get_instance(
list_last(&plb_entries), plb_entry_t, plb_link);
first = (newest->index + newest->len) % PLB_SIZE;
last = (oldest->index - 1) % PLB_SIZE;
}
if (first <= last) {
if ((last - first) + 1 < len) {
/*
* The buffer cannot absorb the path.
*/
fibril_mutex_unlock(&plb_mutex);
return ELIMIT;
}
} else {
if (PLB_SIZE - ((first - last) + 1) < len) {
/*
* The buffer cannot absorb the path.
*/
fibril_mutex_unlock(&plb_mutex);
return ELIMIT;
}
}
/*
* We know the first free index in PLB and we also know that there is
* enough space in the buffer to hold our path.
*/
entry->index = first;
entry->len = len;
/*
* Claim PLB space by inserting the entry into the PLB entry ring
* buffer.
*/
list_append(&entry->plb_link, &plb_entries);
fibril_mutex_unlock(&plb_mutex);
/*
* Copy the path into PLB.
*/
size_t cnt1 = min(len, (PLB_SIZE - first) + 1);
size_t cnt2 = len - cnt1;
memcpy(&plb[first], path, cnt1);
memcpy(plb, &path[cnt1], cnt2);
*start = first;
return EOK;
}
static void plb_clear_entry(plb_entry_t *entry, size_t first, size_t len)
{
fibril_mutex_lock(&plb_mutex);
list_remove(&entry->plb_link);
/*
* Erasing the path from PLB will come handy for debugging purposes.
*/
size_t cnt1 = min(len, (PLB_SIZE - first) + 1);
size_t cnt2 = len - cnt1;
memset(&plb[first], 0, cnt1);
memset(plb, 0, cnt2);
fibril_mutex_unlock(&plb_mutex);
}
errno_t vfs_link_internal(vfs_node_t *base, char *path, vfs_triplet_t *child)
{
assert(base != NULL);
assert(child != NULL);
assert(base->fs_handle);
assert(child->fs_handle);
assert(path != NULL);
vfs_lookup_res_t res;
char component[NAME_MAX + 1];
errno_t rc;
size_t len;
char *npath = canonify(path, &len);
if (!npath) {
rc = EINVAL;
goto out;
}
path = npath;
vfs_triplet_t *triplet;
char *slash = str_rchr(path, L'/');
if (slash && slash != path) {
if (slash[1] == 0) {
rc = EINVAL;
goto out;
}
memcpy(component, slash + 1, str_size(slash));
*slash = 0;
rc = vfs_lookup_internal(base, path, L_DIRECTORY, &res);
if (rc != EOK)
goto out;
triplet = &res.triplet;
*slash = '/';
} else {
if (base->mount != NULL) {
rc = EINVAL;
goto out;
}
memcpy(component, path + 1, str_size(path));
triplet = (vfs_triplet_t *) base;
}
if (triplet->fs_handle != child->fs_handle ||
triplet->service_id != child->service_id) {
rc = EXDEV;
goto out;
}
async_exch_t *exch = vfs_exchange_grab(triplet->fs_handle);
aid_t req = async_send_3(exch, VFS_OUT_LINK, triplet->service_id,
triplet->index, child->index, NULL);
rc = async_data_write_start(exch, component, str_size(component) + 1);
errno_t orig_rc;
async_wait_for(req, &orig_rc);
vfs_exchange_release(exch);
if (orig_rc != EOK)
rc = orig_rc;
out:
return rc;
}
static errno_t out_lookup(vfs_triplet_t *base, size_t *pfirst, size_t *plen,
int lflag, vfs_lookup_res_t *result)
{
assert(base);
assert(result);
errno_t rc;
ipc_call_t answer;
async_exch_t *exch = vfs_exchange_grab(base->fs_handle);
aid_t req = async_send_5(exch, VFS_OUT_LOOKUP, (sysarg_t) *pfirst,
(sysarg_t) *plen, (sysarg_t) base->service_id,
(sysarg_t) base->index, (sysarg_t) lflag, &answer);
async_wait_for(req, &rc);
vfs_exchange_release(exch);
if (rc != EOK)
return rc;
unsigned last = *pfirst + *plen;
*pfirst = ipc_get_arg3(&answer) & 0xffff;
*plen = last - *pfirst;
result->triplet.fs_handle = (fs_handle_t) ipc_get_arg1(&answer);
result->triplet.service_id = base->service_id;
result->triplet.index = (fs_index_t) ipc_get_arg2(&answer);
result->size = MERGE_LOUP32(ipc_get_arg4(&answer), ipc_get_arg5(&answer));
result->type = (ipc_get_arg3(&answer) >> 16) ?
VFS_NODE_DIRECTORY : VFS_NODE_FILE;
return EOK;
}
static errno_t _vfs_lookup_internal(vfs_node_t *base, char *path, int lflag,
vfs_lookup_res_t *result, size_t len)
{
size_t first;
errno_t rc;
plb_entry_t entry;
rc = plb_insert_entry(&entry, path, &first, len);
if (rc != EOK)
return rc;
size_t next = first;
size_t nlen = len;
vfs_lookup_res_t res;
/* Resolve path as long as there are mount points to cross. */
while (nlen > 0) {
while (base->mount) {
if (lflag & L_DISABLE_MOUNTS) {
rc = EXDEV;
goto out;
}
base = base->mount;
}
rc = out_lookup((vfs_triplet_t *) base, &next, &nlen, lflag,
&res);
if (rc != EOK)
goto out;
if (nlen > 0) {
base = vfs_node_peek(&res);
if (!base) {
rc = ENOENT;
goto out;
}
if (!base->mount) {
vfs_node_put(base);
rc = ENOENT;
goto out;
}
vfs_node_put(base);
if (lflag & L_DISABLE_MOUNTS) {
rc = EXDEV;
goto out;
}
}
}
assert(nlen == 0);
rc = EOK;
if (result != NULL) {
/* The found file may be a mount point. Try to cross it. */
if (!(lflag & (L_MP | L_DISABLE_MOUNTS))) {
base = vfs_node_peek(&res);
if (base && base->mount) {
while (base->mount) {
vfs_node_addref(base->mount);
vfs_node_t *nbase = base->mount;
vfs_node_put(base);
base = nbase;
}
result->triplet = *((vfs_triplet_t *) base);
result->type = base->type;
result->size = base->size;
vfs_node_put(base);
goto out;
}
if (base)
vfs_node_put(base);
}
*result = res;
}
out:
plb_clear_entry(&entry, first, len);
return rc;
}
/** Perform a path lookup.
*
* @param base The file from which to perform the lookup.
* @param path Path to be resolved; it must be a NULL-terminated
* string.
* @param lflag Flags to be used during lookup.
* @param result Empty structure where the lookup result will be stored.
* Can be NULL.
*
* @return EOK on success or an error code from errno.h.
*
*/
errno_t vfs_lookup_internal(vfs_node_t *base, char *path, int lflag,
vfs_lookup_res_t *result)
{
assert(base != NULL);
assert(path != NULL);
size_t len;
errno_t rc;
char *npath = canonify(path, &len);
if (!npath) {
rc = EINVAL;
return rc;
}
path = npath;
assert(path[0] == '/');
if (lflag & (L_CREATE | L_UNLINK)) {
/*
* Creation and destruction of names must be done in two
* separate steps: lookup of the parent node and the name
* link/unlink operation itself. Otherwise the parent
* filesystem would not be able to tell when a mountpoint is
* crossed. It would attempt to perform the link/unlink in
* itself instead of letting the mounted filesystem do it,
* resulting in wrong behavior. This is the wages of server-side
* mountpoints.
*/
char *slash = str_rchr(path, L'/');
vfs_node_t *parent = base;
if (slash != path) {
int tflag = lflag;
vfs_lookup_res_t tres;
tflag &= ~(L_CREATE | L_EXCLUSIVE | L_UNLINK | L_FILE);
tflag |= L_DIRECTORY;
rc = _vfs_lookup_internal(base, path, tflag, &tres,
slash - path);
if (rc != EOK)
return rc;
parent = vfs_node_get(&tres);
if (!parent)
return ENOMEM;
} else
vfs_node_addref(parent);
rc = _vfs_lookup_internal(parent, slash, lflag, result,
len - (slash - path));
vfs_node_put(parent);
} else {
rc = _vfs_lookup_internal(base, path, lflag, result, len);
}
return rc;
}
/**
* @}
*/