HelenOS sources

root/uspace/lib/c/generic/vfs/vfs.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_parent_and_child
  2. vfs_absolutize
  3. vfs_clone
  4. vfs_cwd_get
  5. vfs_cwd_set
  6. vfs_exchange_begin
  7. vfs_exchange_end
  8. vfs_fd_session
  9. vfs_fsprobe
  10. vfs_fstypes
  11. vfs_fstypes_free
  12. vfs_link
  13. vfs_link_path
  14. vfs_lookup
  15. vfs_lookup_open
  16. vfs_mount
  17. vfs_mount_path
  18. vfs_open
  19. vfs_pass_handle
  20. vfs_put
  21. vfs_receive_handle
  22. vfs_read
  23. vfs_read_short
  24. vfs_rename_path
  25. vfs_resize
  26. vfs_root
  27. vfs_root_set
  28. vfs_stat
  29. vfs_stat_path
  30. vfs_statfs
  31. vfs_statfs_path
  32. vfs_sync
  33. vfs_unlink
  34. vfs_unlink_path
  35. vfs_unmount
  36. vfs_unmount_path
  37. vfs_walk
  38. vfs_write
  39. vfs_write_short

/*
 * 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 libc
 * @{
 */
/** @file
 */

#include <vfs/vfs.h>
#include <vfs/canonify.h>
#include <vfs/vfs_mtab.h>
#include <vfs/vfs_sess.h>
#include <macros.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <ipc/services.h>
#include <ns.h>
#include <async.h>
#include <fibril_synch.h>
#include <errno.h>
#include <assert.h>
#include <str.h>
#include <loc.h>
#include <ipc/vfs.h>
#include <ipc/loc.h>

/*
 * This file contains the implementation of the native HelenOS file system API.
 *
 * The API supports client-side file system roots, client-side IO cursors and
 * uses file handles as a primary means to refer to files. In order to call the
 * API functions, one just includes vfs/vfs.h.
 *
 * The API functions come in two main flavors:
 *
 * - functions that operate on integer file handles, such as:
 *   vfs_walk(), vfs_open(), vfs_read(), vfs_link(), ...
 *
 * - functions that operate on paths, such as:
 *   vfs_lookup(), vfs_link_path(), vfs_unlink_path(), vfs_rename_path(), ...
 *
 * There is usually a corresponding path function for each file handle function
 * that exists mostly as a convenience wrapper, except for cases when only a
 * path version exists due to file system consistency considerations (see
 * vfs_rename_path()). Sometimes one of the versions does not make sense, in
 * which case it is also omitted.
 *
 * Besides of that, the API provides some convenience wrappers for frequently
 * performed pairs of operations, for example there is a combo API for
 * vfs_lookup() and vfs_open(): vfs_lookup_open().
 *
 * Some of the functions here return a file handle that can be passed to other
 * functions. Note that a file handle does not automatically represent a file
 * from which one can read or to which one can write. In order to do so, the
 * file handle must be opened first for reading/writing using vfs_open().
 *
 * All file handles, no matter whether opened or not, must be eventually
 * returned to the system using vfs_put(). Non-returned file handles are in use
 * and consume system resources.
 *
 * Functions that return errno_t return an error code on error and do not
 * set errno. Depending on function, success is signalled by returning either
 * EOK or a non-negative file handle.
 *
 * An example life-cycle of a file handle is as follows:
 *
 *      #include <vfs/vfs.h>
 *
 *      int file = vfs_lookup("/foo/bar/foobar", WALK_REGULAR);
 *      if (file < 0)
 *              return file;
 *      errno_t rc = vfs_open(file, MODE_READ);
 *      if (rc != EOK) {
 *              (void) vfs_put(file);
 *              return rc;
 *      }
 *      aoff64_t pos = 42;
 *      char buf[512];
 *      size_t nread;
 *      rc = vfs_read(file, &pos, buf, sizeof(buf), &nread);
 *      if (rc != EOK) {
 *              vfs_put(file);
 *              return rc;
 *      }
 *
 *      // buf is now filled with nread bytes from file
 *
 *      vfs_put(file);
 */

static FIBRIL_MUTEX_INITIALIZE(vfs_mutex);
static async_sess_t *vfs_sess = NULL;

static FIBRIL_MUTEX_INITIALIZE(cwd_mutex);

static int cwd_fd = -1;
static char *cwd_path = NULL;
static size_t cwd_size = 0;

static FIBRIL_MUTEX_INITIALIZE(root_mutex);
static int root_fd = -1;

static errno_t get_parent_and_child(const char *path, int *parent, char **child)
{
        size_t size;
        char *apath = vfs_absolutize(path, &size);
        if (!apath)
                return ENOMEM;

        char *slash = str_rchr(apath, L'/');
        if (slash == apath) {
                *parent = vfs_root();
                if (*parent < 0) {
                        free(apath);
                        return EBADF;
                }
                *child = apath;
                return EOK;
        } else {
                *slash = '\0';
                errno_t rc = vfs_lookup(apath, WALK_DIRECTORY, parent);
                if (rc != EOK) {
                        free(apath);
                        return rc;
                }
                *slash = '/';
                *child = str_dup(slash);
                free(apath);
                if (!*child) {
                        vfs_put(*parent);
                        return ENOMEM;
                }

                return rc;
        }

}

/** Make a potentially relative path absolute
 *
 * This function coverts a current-working-directory-relative path into a
 * well-formed, absolute path. The caller is responsible for deallocating the
 * returned buffer.
 *
 * @param[in] path      Path to be absolutized
 * @param[out] retlen   Length of the absolutized path
 *
 * @return              New buffer holding the absolutized path or NULL
 */
char *vfs_absolutize(const char *path, size_t *retlen)
{
        char *ncwd_path;
        char *ncwd_path_nc;

        fibril_mutex_lock(&cwd_mutex);
        size_t size = str_size(path);
        if (*path != '/') {
                if (cwd_path == NULL) {
                        fibril_mutex_unlock(&cwd_mutex);
                        return NULL;
                }
                ncwd_path_nc = malloc(cwd_size + 1 + size + 1);
                if (ncwd_path_nc == NULL) {
                        fibril_mutex_unlock(&cwd_mutex);
                        return NULL;
                }
                str_cpy(ncwd_path_nc, cwd_size + 1 + size + 1, cwd_path);
                ncwd_path_nc[cwd_size] = '/';
                ncwd_path_nc[cwd_size + 1] = '\0';
        } else {
                ncwd_path_nc = malloc(size + 1);
                if (ncwd_path_nc == NULL) {
                        fibril_mutex_unlock(&cwd_mutex);
                        return NULL;
                }
                ncwd_path_nc[0] = '\0';
        }
        str_append(ncwd_path_nc, cwd_size + 1 + size + 1, path);
        ncwd_path = canonify(ncwd_path_nc, retlen);
        if (ncwd_path == NULL) {
                fibril_mutex_unlock(&cwd_mutex);
                free(ncwd_path_nc);
                return NULL;
        }
        /*
         * We need to clone ncwd_path because canonify() works in-place and thus
         * the address in ncwd_path need not be the same as ncwd_path_nc, even
         * though they both point into the same dynamically allocated buffer.
         */
        ncwd_path = str_dup(ncwd_path);
        free(ncwd_path_nc);
        if (ncwd_path == NULL) {
                fibril_mutex_unlock(&cwd_mutex);
                return NULL;
        }
        fibril_mutex_unlock(&cwd_mutex);
        return ncwd_path;
}

/** Clone a file handle
 *
 * The caller can choose whether to clone an existing file handle into another
 * already existing file handle (in which case it is first closed) or to a new
 * file handle allocated either from low or high indices.
 *
 * @param file_from     Source file handle
 * @param file_to       Destination file handle or -1
 * @param high          If file_to is -1, high controls whether the new file
 *                      handle will be allocated from high indices
 *
 * @return              New file handle on success or an error code
 */
errno_t vfs_clone(int file_from, int file_to, bool high, int *handle)
{
        assert(handle != NULL);

        async_exch_t *vfs_exch = vfs_exchange_begin();
        sysarg_t ret;
        errno_t rc = async_req_3_1(vfs_exch, VFS_IN_CLONE, (sysarg_t) file_from,
            (sysarg_t) file_to, (sysarg_t) high, &ret);
        vfs_exchange_end(vfs_exch);

        if (rc == EOK) {
                *handle = ret;
        }
        return rc;
}

/** Get current working directory path
 *
 * @param[out] buf      Buffer
 * @param size          Size of @a buf
 *
 * @return              EOK on success or a non-error code
 */
errno_t vfs_cwd_get(char *buf, size_t size)
{
        fibril_mutex_lock(&cwd_mutex);

        if ((cwd_size == 0) || (size < cwd_size + 1)) {
                fibril_mutex_unlock(&cwd_mutex);
                return ERANGE;
        }

        str_cpy(buf, size, cwd_path);
        fibril_mutex_unlock(&cwd_mutex);

        return EOK;
}

/** Change working directory
 *
 * @param path  Path of the new working directory
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_cwd_set(const char *path)
{
        size_t abs_size;
        char *abs_path = vfs_absolutize(path, &abs_size);
        if (abs_path == NULL)
                return ENOMEM;

        int fd;
        errno_t rc = vfs_lookup(abs_path, WALK_DIRECTORY, &fd);
        if (rc != EOK) {
                free(abs_path);
                return rc;
        }

        fibril_mutex_lock(&cwd_mutex);

        if (cwd_fd >= 0)
                vfs_put(cwd_fd);

        if (cwd_path)
                free(cwd_path);

        cwd_fd = fd;
        cwd_path = abs_path;
        cwd_size = abs_size;

        fibril_mutex_unlock(&cwd_mutex);
        return EOK;
}

/** Start an async exchange on the VFS session
 *
 * @return      New exchange
 */
async_exch_t *vfs_exchange_begin(void)
{
        fibril_mutex_lock(&vfs_mutex);

        while (vfs_sess == NULL) {
                vfs_sess = service_connect_blocking(SERVICE_VFS, INTERFACE_VFS,
                    0, NULL);
        }

        fibril_mutex_unlock(&vfs_mutex);

        return async_exchange_begin(vfs_sess);
}

/** Finish an async exchange on the VFS session
 *
 * @param exch  Exchange to be finished
 */
void vfs_exchange_end(async_exch_t *exch)
{
        async_exchange_end(exch);
}

/** Open session to service represented by a special file
 *
 * Given that the file referred to by @a file represents a service,
 * open a session to that service.
 *
 * @param file  File handle representing a service
 * @param iface Interface to connect to (XXX Should be automatic)
 *
 * @return      Session pointer on success.
 * @return      @c NULL or error.
 */
async_sess_t *vfs_fd_session(int file, iface_t iface)
{
        vfs_stat_t stat;
        errno_t rc = vfs_stat(file, &stat);
        if (rc != EOK)
                return NULL;

        if (stat.service == 0)
                return NULL;

        return loc_service_connect(stat.service, iface, 0);
}

/** Determine if a device contains the specified file system type. If so,
 * return identification information.
 *
 * @param fs_name File system name
 * @param serv    Service representing the mountee
 * @param info    Place to store volume identification information
 *
 * @return                      EOK on success or an error code
 */
errno_t vfs_fsprobe(const char *fs_name, service_id_t serv,
    vfs_fs_probe_info_t *info)
{
        errno_t rc;

        ipc_call_t answer;
        async_exch_t *exch = vfs_exchange_begin();
        aid_t req = async_send_1(exch, VFS_IN_FSPROBE, serv, &answer);

        rc = async_data_write_start(exch, (void *) fs_name,
            str_size(fs_name));

        async_wait_for(req, &rc);

        if (rc != EOK) {
                vfs_exchange_end(exch);
                return rc;
        }

        rc = async_data_read_start(exch, info, sizeof(*info));
        vfs_exchange_end(exch);

        return rc;
}

/** Return a list of currently available file system types
 *
 * @param fstypes Points to structure where list of filesystem types is
 *        stored. It is read as a null-terminated list of strings
 *        fstypes->fstypes[0..]. To free the list use vfs_fstypes_free().
 *
 * @return                      EOK on success or an error code
 */
errno_t vfs_fstypes(vfs_fstypes_t *fstypes)
{
        sysarg_t size;
        char *buf;
        char dummybuf[1];
        size_t count, i;

        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_0_1(exch, VFS_IN_FSTYPES, &size);

        if (rc != EOK) {
                vfs_exchange_end(exch);
                return rc;
        }

        buf = malloc(size);
        if (buf == NULL) {
                buf = dummybuf;
                size = 1;
        }

        rc = async_data_read_start(exch, buf, size);
        vfs_exchange_end(exch);

        if (buf == dummybuf)
                return ENOMEM;

        /*
         * Buffer should contain a number of null-terminated strings.
         * Count them so that we can allocate an index
         */
        count = 0;
        i = 0;
        while (i < size) {
                if (buf[i] == '\0')
                        ++count;
                ++i;
        }

        if (count == 0) {
                free(buf);
                return EIO;
        }

        fstypes->fstypes = calloc(sizeof(char *), count + 1);
        if (fstypes->fstypes == NULL) {
                free(buf);
                return ENOMEM;
        }

        /* Now fill the index */
        if (buf[0] != '\0')
                fstypes->fstypes[0] = &buf[0];
        count = 0;
        i = 0;
        while (i < size) {
                if (buf[i] == '\0')
                        fstypes->fstypes[++count] = &buf[i + 1];
                ++i;
        }
        fstypes->fstypes[count] = NULL;
        fstypes->buf = buf;
        fstypes->size = size;

        return rc;
}

/** Free list of file system types.
 *
 * @param fstypes List of file system types
 */
void vfs_fstypes_free(vfs_fstypes_t *fstypes)
{
        free(fstypes->buf);
        fstypes->buf = NULL;
        free(fstypes->fstypes);
        fstypes->fstypes = NULL;
        fstypes->size = 0;
}

/** Link a file or directory
 *
 * Create a new name and an empty file or an empty directory in a parent
 * directory. If child with the same name already exists, the function returns
 * a failure, the existing file remains untouched and no file system object
 * is created.
 *
 * @param parent        File handle of the parent directory node
 * @param child         New name to be linked
 * @param kind          Kind of the object to be created: KIND_FILE or
 *                      KIND_DIRECTORY
 * @param[out] linkedfd If not NULL, will receive a file handle to the linked
 *                      child
 * @return              EOK on success or an error code
 */
errno_t vfs_link(int parent, const char *child, vfs_file_kind_t kind, int *linkedfd)
{
        int flags = (kind == KIND_DIRECTORY) ? WALK_DIRECTORY : WALK_REGULAR;
        int file = -1;
        errno_t rc = vfs_walk(parent, child, WALK_MUST_CREATE | flags, &file);
        if (rc != EOK)
                return rc;

        if (linkedfd)
                *linkedfd = file;
        else
                vfs_put(file);

        return EOK;
}

/** Link a file or directory
 *
 * Create a new name and an empty file or an empty directory at given path.
 * If a link with the same name already exists, the function returns
 * a failure, the existing file remains untouched and no file system object
 * is created.
 *
 * @param path          New path to be linked
 * @param kind          Kind of the object to be created: KIND_FILE or
 *                      KIND_DIRECTORY
 * @param[out] linkedfd If not NULL, will receive a file handle to the linked
 *                      child
 * @return              EOK on success or an error code
 */
errno_t vfs_link_path(const char *path, vfs_file_kind_t kind, int *linkedfd)
{
        char *child;
        int parent;
        errno_t rc = get_parent_and_child(path, &parent, &child);
        if (rc != EOK)
                return rc;

        rc = vfs_link(parent, child, kind, linkedfd);

        free(child);
        vfs_put(parent);
        return rc;
}

/** Lookup a path relative to the local root
 *
 * @param path  Path to be looked up
 * @param flags Walk flags
 * @param[out] handle Pointer to variable where handle is to be written.
 *
 * @return      EOK on success or an error code.
 */
errno_t vfs_lookup(const char *path, int flags, int *handle)
{
        size_t size;
        char *p = vfs_absolutize(path, &size);
        if (!p)
                return ENOMEM;

        int root = vfs_root();
        if (root < 0) {
                free(p);
                return ENOENT;
        }

        // XXX: Workaround for GCC diagnostics.
        *handle = -1;

        errno_t rc = vfs_walk(root, p, flags, handle);
        vfs_put(root);
        free(p);
        return rc;
}

/** Lookup a path relative to the local root and open the result
 *
 * This function is a convenience combo for vfs_lookup() and vfs_open().
 *
 * @param path  Path to be looked up
 * @param flags Walk flags
 * @param mode  Mode in which to open file in
 * @param[out] handle Pointer to variable where handle is to be written.
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_lookup_open(const char *path, int flags, int mode, int *handle)
{
        int file;
        errno_t rc = vfs_lookup(path, flags, &file);
        if (rc != EOK)
                return rc;

        rc = vfs_open(file, mode);
        if (rc != EOK) {
                vfs_put(file);
                return rc;
        }

        *handle = file;
        return EOK;
}

/** Mount a file system
 *
 * @param[in] mp                File handle representing the mount-point
 * @param[in] fs_name           File system name
 * @param[in] serv              Service representing the mountee
 * @param[in] opts              Mount options for the endpoint file system
 * @param[in] flags             Mount flags
 * @param[in] instance          Instance number of the file system server
 * @param[out] mountedfd        File handle of the mounted root if not NULL
 *
 * @return                      EOK on success or an error code
 */
errno_t vfs_mount(int mp, const char *fs_name, service_id_t serv, const char *opts,
    unsigned int flags, unsigned int instance, int *mountedfd)
{
        errno_t rc, rc1;

        if (!mountedfd)
                flags |= VFS_MOUNT_NO_REF;
        if (mp < 0)
                flags |= VFS_MOUNT_CONNECT_ONLY;

        ipc_call_t answer;
        async_exch_t *exch = vfs_exchange_begin();
        aid_t req = async_send_4(exch, VFS_IN_MOUNT, mp, serv, flags, instance,
            &answer);

        rc1 = async_data_write_start(exch, (void *) opts, str_size(opts));
        if (rc1 == EOK) {
                rc1 = async_data_write_start(exch, (void *) fs_name,
                    str_size(fs_name));
        }

        vfs_exchange_end(exch);

        async_wait_for(req, &rc);

        if (mountedfd)
                *mountedfd = (int) ipc_get_arg1(&answer);

        if (rc != EOK)
                return rc;
        return rc1;
}

/** Mount a file system
 *
 * @param[in] mp                Path representing the mount-point
 * @param[in] fs_name           File system name
 * @param[in] fqsn              Fully qualified service name of the mountee
 * @param[in] opts              Mount options for the endpoint file system
 * @param[in] flags             Mount flags
 * @param[in] instance          Instance number of the file system server
 *
 * @return                      EOK on success or an error code
 */
errno_t vfs_mount_path(const char *mp, const char *fs_name, const char *fqsn,
    const char *opts, unsigned int flags, unsigned int instance)
{
        int null_id = -1;
        char null[LOC_NAME_MAXLEN];

        if (str_cmp(fqsn, "") == 0) {
                /*
                 * No device specified, create a fresh null/%d device instead.
                 */
                null_id = loc_null_create();

                if (null_id == -1)
                        return ENOMEM;

                snprintf(null, LOC_NAME_MAXLEN, "null/%d", null_id);
                fqsn = null;
        }

        if (flags & IPC_FLAG_BLOCKING)
                flags = VFS_MOUNT_BLOCKING;
        else
                flags = 0;

        service_id_t service_id;
        errno_t res = loc_service_get_id(fqsn, &service_id, flags);
        if (res != EOK) {
                if (null_id != -1)
                        loc_null_destroy(null_id);

                return res;
        }

        size_t mpa_size;
        char *mpa = vfs_absolutize(mp, &mpa_size);
        if (mpa == NULL) {
                if (null_id != -1)
                        loc_null_destroy(null_id);

                return ENOMEM;
        }

        fibril_mutex_lock(&root_mutex);

        errno_t rc;

        if (str_cmp(mpa, "/") == 0) {
                /* Mounting root. */

                if (root_fd >= 0) {
                        fibril_mutex_unlock(&root_mutex);
                        if (null_id != -1)
                                loc_null_destroy(null_id);
                        return EBUSY;
                }

                int root;
                rc = vfs_mount(-1, fs_name, service_id, opts, flags, instance,
                    &root);
                if (rc == EOK)
                        root_fd = root;
        } else {
                if (root_fd < 0) {
                        fibril_mutex_unlock(&root_mutex);
                        if (null_id != -1)
                                loc_null_destroy(null_id);
                        return EINVAL;
                }

                int mpfd;
                rc = vfs_walk(root_fd, mpa, WALK_DIRECTORY, &mpfd);
                if (rc == EOK) {
                        rc = vfs_mount(mpfd, fs_name, service_id, opts, flags,
                            instance, NULL);
                        vfs_put(mpfd);
                }
        }

        fibril_mutex_unlock(&root_mutex);

        if ((rc != EOK) && (null_id != -1))
                loc_null_destroy(null_id);

        return (errno_t) rc;
}

/** Open a file handle for I/O
 *
 * @param file  File handle to enable I/O on
 * @param mode  Mode in which to open file in
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_open(int file, int mode)
{
        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_2_0(exch, VFS_IN_OPEN, file, mode);
        vfs_exchange_end(exch);

        return rc;
}

/** Pass a file handle to another VFS client
 *
 * @param vfs_exch      Donor's VFS exchange
 * @param file          Donor's file handle to pass
 * @param exch          Exchange to the acceptor
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_pass_handle(async_exch_t *vfs_exch, int file, async_exch_t *exch)
{
        return async_state_change_start(exch, VFS_PASS_HANDLE, (sysarg_t) file,
            0, vfs_exch);
}

/** Stop working with a file handle
 *
 * @param file  File handle to put
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_put(int file)
{
        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_1_0(exch, VFS_IN_PUT, file);
        vfs_exchange_end(exch);

        return rc;
}

/** Receive a file handle from another VFS client
 *
 * @param high   If true, the received file handle will be allocated from high
 *               indices
 * @param[out] handle  Received handle.
 *
 * @return       EOK on success or an error code
 */
errno_t vfs_receive_handle(bool high, int *handle)
{
        ipc_call_t call;
        if (!async_state_change_receive(&call)) {
                async_answer_0(&call, EINVAL);
                return EINVAL;
        }

        async_exch_t *vfs_exch = vfs_exchange_begin();

        async_state_change_finalize(&call, vfs_exch);

        sysarg_t ret;
        errno_t rc = async_req_1_1(vfs_exch, VFS_IN_WAIT_HANDLE, high, &ret);

        async_exchange_end(vfs_exch);

        if (rc == EOK) {
                *handle = (int) ret;
        }

        return rc;
}

/** Read data
 *
 * Read up to @a nbytes bytes from file if available. This function always reads
 * all the available bytes up to @a nbytes.
 *
 * @param file          File handle to read from
 * @param[inout] pos    Position to read from, updated by the actual bytes read
 * @param buf           Buffer, @a nbytes bytes long
 * @param nbytes        Number of bytes to read
 * @param nread         Place to store number of bytes actually read
 *
 * @return              On success, EOK and @a *nread is filled with number
 *                      of bytes actually read.
 * @return              On failure, an error code
 */
errno_t vfs_read(int file, aoff64_t *pos, void *buf, size_t nbyte, size_t *nread)
{
        ssize_t cnt = 0;
        size_t nr = 0;
        uint8_t *bp = (uint8_t *) buf;
        errno_t rc;

        do {
                bp += cnt;
                nr += cnt;
                *pos += cnt;
                rc = vfs_read_short(file, *pos, bp, nbyte - nr, &cnt);
        } while (rc == EOK && cnt > 0 && (nbyte - nr - cnt) > 0);

        if (rc != EOK) {
                *nread = nr;
                return rc;
        }

        nr += cnt;
        *pos += cnt;
        *nread = nr;
        return EOK;
}

/** Read bytes from a file
 *
 * Read up to @a nbyte bytes from file. The actual number of bytes read
 * may be lower, but greater than zero if there are any bytes available.
 * If there are no bytes available for reading, then the function will
 * return success with zero bytes read.
 *
 * @param file          File handle to read from
 * @param[in] pos       Position to read from
 * @param buf           Buffer to read from
 * @param nbyte         Maximum number of bytes to read
 * @param[out] nread    Actual number of bytes read (0 or more)
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_read_short(int file, aoff64_t pos, void *buf, size_t nbyte,
    ssize_t *nread)
{
        errno_t rc;
        ipc_call_t answer;
        aid_t req;

        if (nbyte > DATA_XFER_LIMIT)
                nbyte = DATA_XFER_LIMIT;

        async_exch_t *exch = vfs_exchange_begin();

        req = async_send_3(exch, VFS_IN_READ, file, LOWER32(pos),
            UPPER32(pos), &answer);
        rc = async_data_read_start(exch, (void *) buf, nbyte);

        vfs_exchange_end(exch);

        if (rc == EOK)
                async_wait_for(req, &rc);
        else
                async_forget(req);

        if (rc != EOK)
                return rc;

        *nread = (ssize_t) ipc_get_arg1(&answer);
        return EOK;
}

/** Rename a file or directory
 *
 * There is no file-handle-based variant to disallow attempts to introduce loops
 * and breakage in the directory tree when relinking eg. a node under its own
 * descendant.  The path-based variant is not susceptible because the VFS can
 * prevent this lexically by comparing the paths.
 *
 * @param old   Old path
 * @param new   New path
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_rename_path(const char *old, const char *new)
{
        errno_t rc;
        errno_t rc_orig;
        aid_t req;

        size_t olda_size;
        char *olda = vfs_absolutize(old, &olda_size);
        if (olda == NULL)
                return ENOMEM;

        size_t newa_size;
        char *newa = vfs_absolutize(new, &newa_size);
        if (newa == NULL) {
                free(olda);
                return ENOMEM;
        }

        async_exch_t *exch = vfs_exchange_begin();
        int root = vfs_root();
        if (root < 0) {
                free(olda);
                free(newa);
                return ENOENT;
        }

        req = async_send_1(exch, VFS_IN_RENAME, root, NULL);
        rc = async_data_write_start(exch, olda, olda_size);
        if (rc != EOK) {
                vfs_exchange_end(exch);
                free(olda);
                free(newa);
                vfs_put(root);
                async_wait_for(req, &rc_orig);
                if (rc_orig != EOK)
                        rc = rc_orig;
                return rc;
        }
        rc = async_data_write_start(exch, newa, newa_size);
        if (rc != EOK) {
                vfs_exchange_end(exch);
                free(olda);
                free(newa);
                vfs_put(root);
                async_wait_for(req, &rc_orig);
                if (rc_orig != EOK)
                        rc = rc_orig;
                return rc;
        }
        vfs_exchange_end(exch);
        free(olda);
        free(newa);
        vfs_put(root);
        async_wait_for(req, &rc);

        return rc;
}

/** Resize file to a specified length
 *
 * Resize file so that its size is exactly @a length.
 *
 * @param file          File handle to resize
 * @param length        New length
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_resize(int file, aoff64_t length)
{
        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_3_0(exch, VFS_IN_RESIZE, file, LOWER32(length),
            UPPER32(length));
        vfs_exchange_end(exch);

        return rc;
}

/** Return a new file handle representing the local root
 *
 * @return      A clone of the local root file handle or -1
 */
int vfs_root(void)
{
        fibril_mutex_lock(&root_mutex);
        int fd;
        if (root_fd < 0) {
                fd = -1;
        } else {
                errno_t rc = vfs_clone(root_fd, -1, true, &fd);
                if (rc != EOK) {
                        fd = -1;
                }
        }
        fibril_mutex_unlock(&root_mutex);
        return fd;
}

/** Set a new local root
 *
 * Note that it is still possible to have file handles for other roots and pass
 * them to the API functions. Functions like vfs_root() and vfs_lookup() will
 * however consider the file set by this function to be the root.
 *
 * @param nroot The new local root file handle
 *
 * @return  Error code
 */
errno_t vfs_root_set(int nroot)
{
        int new_root;
        errno_t rc = vfs_clone(nroot, -1, true, &new_root);
        if (rc != EOK) {
                return rc;
        }

        fibril_mutex_lock(&root_mutex);
        if (root_fd >= 0)
                vfs_put(root_fd);
        root_fd = new_root;
        fibril_mutex_unlock(&root_mutex);

        return EOK;
}

/** Get file information
 *
 * @param file          File handle to get information about
 * @param[out] stat     Place to store file information
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_stat(int file, vfs_stat_t *stat)
{
        errno_t rc;
        aid_t req;

        async_exch_t *exch = vfs_exchange_begin();

        req = async_send_1(exch, VFS_IN_STAT, file, NULL);
        rc = async_data_read_start(exch, (void *) stat, sizeof(vfs_stat_t));
        if (rc != EOK) {
                vfs_exchange_end(exch);

                errno_t rc_orig;
                async_wait_for(req, &rc_orig);

                if (rc_orig != EOK)
                        rc = rc_orig;

                return rc;
        }

        vfs_exchange_end(exch);
        async_wait_for(req, &rc);

        return rc;
}

/** Get file information
 *
 * @param path          File path to get information about
 * @param[out] stat     Place to store file information
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_stat_path(const char *path, vfs_stat_t *stat)
{
        int file;
        errno_t rc = vfs_lookup(path, 0, &file);
        if (rc != EOK)
                return rc;

        rc = vfs_stat(file, stat);

        vfs_put(file);

        return rc;
}

/** Get filesystem statistics
 *
 * @param file          File located on the queried file system
 * @param[out] st       Buffer for storing information
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_statfs(int file, vfs_statfs_t *st)
{
        errno_t rc, ret;
        aid_t req;

        async_exch_t *exch = vfs_exchange_begin();

        req = async_send_1(exch, VFS_IN_STATFS, file, NULL);
        rc = async_data_read_start(exch, (void *) st, sizeof(*st));

        vfs_exchange_end(exch);
        async_wait_for(req, &ret);

        rc = (ret != EOK ? ret : rc);

        return rc;
}

/** Get filesystem statistics
 *
 * @param file          Path pointing to the queried file system
 * @param[out] st       Buffer for storing information
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_statfs_path(const char *path, vfs_statfs_t *st)
{
        int file;
        errno_t rc = vfs_lookup(path, 0, &file);
        if (rc != EOK)
                return rc;

        rc = vfs_statfs(file, st);

        vfs_put(file);

        return rc;
}

/** Synchronize file
 *
 * @param file  File handle to synchronize
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_sync(int file)
{
        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_1_0(exch, VFS_IN_SYNC, file);
        vfs_exchange_end(exch);

        return rc;
}

/** Unlink a file or directory
 *
 * Unlink a name from a parent directory. The caller can supply the file handle
 * of the unlinked child in order to detect a possible race with vfs_link() and
 * avoid unlinking a wrong file. If the last link for a file or directory is
 * removed, the FS implementation will deallocate its resources.
 *
 * @param parent        File handle of the parent directory node
 * @param child         Old name to be unlinked
 * @param expect        File handle of the unlinked child
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_unlink(int parent, const char *child, int expect)
{
        errno_t rc;
        aid_t req;

        async_exch_t *exch = vfs_exchange_begin();

        req = async_send_2(exch, VFS_IN_UNLINK, parent, expect, NULL);
        rc = async_data_write_start(exch, child, str_size(child));

        vfs_exchange_end(exch);

        errno_t rc_orig;
        async_wait_for(req, &rc_orig);

        if (rc_orig != EOK)
                return (errno_t) rc_orig;
        return rc;
}

/** Unlink a file or directory
 *
 * Unlink a path. If the last link for a file or directory is removed, the FS
 * implementation will deallocate its resources.
 *
 * @param path          Old path to be unlinked
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_unlink_path(const char *path)
{
        int expect;
        errno_t rc = vfs_lookup(path, 0, &expect);
        if (rc != EOK)
                return rc;

        char *child;
        int parent;
        rc = get_parent_and_child(path, &parent, &child);
        if (rc != EOK) {
                vfs_put(expect);
                return rc;
        }

        rc = vfs_unlink(parent, child, expect);

        free(child);
        vfs_put(parent);
        vfs_put(expect);
        return rc;
}

/** Unmount a file system
 *
 * @param mp    File handle representing the mount-point
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_unmount(int mp)
{
        async_exch_t *exch = vfs_exchange_begin();
        errno_t rc = async_req_1_0(exch, VFS_IN_UNMOUNT, mp);
        vfs_exchange_end(exch);
        return rc;
}

/** Unmount a file system
 *
 * @param mpp   Mount-point path
 *
 * @return      EOK on success or an error code
 */
errno_t vfs_unmount_path(const char *mpp)
{
        int mp;
        errno_t rc = vfs_lookup(mpp, WALK_MOUNT_POINT | WALK_DIRECTORY, &mp);
        if (rc != EOK)
                return rc;

        rc = vfs_unmount(mp);
        vfs_put(mp);
        return rc;
}

/** Walk a path starting in a parent node
 *
 * @param parent        File handle of the parent node where the walk starts
 * @param path          Parent-relative path to be walked
 * @param flags         Flags influencing the walk
 * @param[out] handle   File handle representing the result on success.
 *
 * @return              Error code.
 */
errno_t vfs_walk(int parent, const char *path, int flags, int *handle)
{
        async_exch_t *exch = vfs_exchange_begin();

        ipc_call_t answer;
        aid_t req = async_send_2(exch, VFS_IN_WALK, parent, flags, &answer);
        errno_t rc = async_data_write_start(exch, path, str_size(path));
        vfs_exchange_end(exch);

        errno_t rc_orig;
        async_wait_for(req, &rc_orig);

        if (rc_orig != EOK)
                return (errno_t) rc_orig;

        if (rc != EOK)
                return (errno_t) rc;

        *handle = (int) ipc_get_arg1(&answer);
        return EOK;
}

/** Write data
 *
 * This function fails if it cannot write exactly @a len bytes to the file.
 *
 * @param file          File handle to write to
 * @param[inout] pos    Position to write to, updated by the actual bytes
 *                      written
 * @param buf           Data, @a nbytes bytes long
 * @param nbytes        Number of bytes to write
 * @param nwritten      Place to store number of bytes written
 *
 * @return              On success, EOK, @a *nwr is filled with number
 *                      of bytes written
 * @return              On failure, an error code
 */
errno_t vfs_write(int file, aoff64_t *pos, const void *buf, size_t nbyte,
    size_t *nwritten)
{
        ssize_t cnt = 0;
        ssize_t nwr = 0;
        const uint8_t *bp = (uint8_t *) buf;
        errno_t rc;

        do {
                bp += cnt;
                nwr += cnt;
                *pos += cnt;
                rc = vfs_write_short(file, *pos, bp, nbyte - nwr, &cnt);
        } while (rc == EOK && ((ssize_t)nbyte - nwr - cnt) > 0);

        if (rc != EOK) {
                *nwritten = nwr;
                return rc;
        }

        nwr += cnt;
        *pos += cnt;
        *nwritten = nwr;
        return EOK;
}

/** Write bytes to a file
 *
 * Write up to @a nbyte bytes from file. The actual number of bytes written
 * may be lower, but greater than zero.
 *
 * @param file          File handle to write to
 * @param[in] pos       Position to write to
 * @param buf           Buffer to write to
 * @param nbyte         Maximum number of bytes to write
 * @param[out] nread    Actual number of bytes written (0 or more)
 *
 * @return              EOK on success or an error code
 */
errno_t vfs_write_short(int file, aoff64_t pos, const void *buf, size_t nbyte,
    ssize_t *nwritten)
{
        errno_t rc;
        ipc_call_t answer;
        aid_t req;

        if (nbyte > DATA_XFER_LIMIT)
                nbyte = DATA_XFER_LIMIT;

        async_exch_t *exch = vfs_exchange_begin();

        req = async_send_3(exch, VFS_IN_WRITE, file, LOWER32(pos),
            UPPER32(pos), &answer);
        rc = async_data_write_start(exch, (void *) buf, nbyte);

        vfs_exchange_end(exch);

        if (rc == EOK)
                async_wait_for(req, &rc);
        else
                async_forget(req);

        if (rc != EOK)
                return rc;

        *nwritten = (ssize_t) ipc_get_arg1(&answer);
        return EOK;
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub