/* * Copyright (c) 2009 Martin Decky * 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 locfs * @{ */ /** * @file locfs_ops.c * @brief Implementation of VFS operations for the locfs file system server. */ #include #include #include #include #include #include #include #include #include #include #include "locfs.h" #include "locfs_ops.h" typedef struct { loc_object_type_t type; service_id_t service_id; } locfs_node_t; /** Opened services structure */ typedef struct { service_id_t service_id; async_sess_t *sess; /**< If NULL, the structure is incomplete. */ size_t refcount; ht_link_t link; fibril_condvar_t cv; /**< Broadcast when completed. */ } service_t; /** Hash table of opened services */ static hash_table_t services; /** Hash table mutex */ static FIBRIL_MUTEX_INITIALIZE(services_mutex); /* Implementation of hash table interface for the nodes hash table. */ static size_t services_key_hash(const void *key) { const service_id_t *k = key; return *k; } static size_t services_hash(const ht_link_t *item) { service_t *dev = hash_table_get_inst(item, service_t, link); return dev->service_id; } static bool services_key_equal(const void *key, size_t hash, const ht_link_t *item) { const service_id_t *k = key; service_t *dev = hash_table_get_inst(item, service_t, link); return (dev->service_id == *k); } static void services_remove_callback(ht_link_t *item) { free(hash_table_get_inst(item, service_t, link)); } static const hash_table_ops_t services_ops = { .hash = services_hash, .key_hash = services_key_hash, .key_equal = services_key_equal, .equal = NULL, .remove_callback = services_remove_callback }; static errno_t locfs_node_get_internal(fs_node_t **rfn, loc_object_type_t type, service_id_t service_id) { locfs_node_t *node = (locfs_node_t *) malloc(sizeof(locfs_node_t)); if (node == NULL) { *rfn = NULL; return ENOMEM; } *rfn = (fs_node_t *) malloc(sizeof(fs_node_t)); if (*rfn == NULL) { free(node); *rfn = NULL; return ENOMEM; } fs_node_initialize(*rfn); node->type = type; node->service_id = service_id; (*rfn)->data = node; return EOK; } static errno_t locfs_root_get(fs_node_t **rfn, service_id_t service_id) { return locfs_node_get_internal(rfn, LOC_OBJECT_NONE, 0); } static errno_t locfs_match(fs_node_t **rfn, fs_node_t *pfn, const char *component) { locfs_node_t *node = (locfs_node_t *) pfn->data; errno_t ret; if (node->service_id == 0) { /* Root directory */ loc_sdesc_t *nspaces; size_t count = loc_get_namespaces(&nspaces); if (count > 0) { size_t pos; for (pos = 0; pos < count; pos++) { /* Ignore root namespace */ if (str_cmp(nspaces[pos].name, "") == 0) continue; if (str_cmp(nspaces[pos].name, component) == 0) { ret = locfs_node_get_internal(rfn, LOC_OBJECT_NAMESPACE, nspaces[pos].id); free(nspaces); return ret; } } free(nspaces); } /* Search root namespace */ service_id_t namespace; loc_sdesc_t *svcs; if (loc_namespace_get_id("", &namespace, 0) == EOK) { count = loc_get_services(namespace, &svcs); if (count > 0) { size_t pos; for (pos = 0; pos < count; pos++) { if (str_cmp(svcs[pos].name, component) == 0) { ret = locfs_node_get_internal(rfn, LOC_OBJECT_SERVICE, svcs[pos].id); free(svcs); return ret; } } free(svcs); } } *rfn = NULL; return EOK; } if (node->type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ loc_sdesc_t *svcs; size_t count = loc_get_services(node->service_id, &svcs); if (count > 0) { size_t pos; for (pos = 0; pos < count; pos++) { if (str_cmp(svcs[pos].name, component) == 0) { ret = locfs_node_get_internal(rfn, LOC_OBJECT_SERVICE, svcs[pos].id); free(svcs); return ret; } } free(svcs); } *rfn = NULL; return EOK; } *rfn = NULL; return EOK; } static errno_t locfs_node_get(fs_node_t **rfn, service_id_t service_id, fs_index_t index) { return locfs_node_get_internal(rfn, loc_id_probe(index), index); } static errno_t locfs_node_open(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; if (node->service_id == 0) { /* Root directory */ return EOK; } loc_object_type_t type = loc_id_probe(node->service_id); if (type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ return EOK; } if (type == LOC_OBJECT_SERVICE) { /* Device node */ fibril_mutex_lock(&services_mutex); ht_link_t *lnk; restart: lnk = hash_table_find(&services, &node->service_id); if (lnk == NULL) { service_t *dev = (service_t *) malloc(sizeof(service_t)); if (dev == NULL) { fibril_mutex_unlock(&services_mutex); return ENOMEM; } dev->service_id = node->service_id; /* Mark as incomplete */ dev->sess = NULL; dev->refcount = 1; fibril_condvar_initialize(&dev->cv); /* * Insert the incomplete device structure so that other * fibrils will not race with us when we drop the mutex * below. */ hash_table_insert(&services, &dev->link); /* * Drop the mutex to allow recursive locfs requests. */ fibril_mutex_unlock(&services_mutex); async_sess_t *sess = loc_service_connect(node->service_id, INTERFACE_FS, 0); fibril_mutex_lock(&services_mutex); /* * Notify possible waiters about this device structure * being completed (or destroyed). */ fibril_condvar_broadcast(&dev->cv); if (!sess) { /* * Connecting failed, need to remove the * entry and free the device structure. */ hash_table_remove(&services, &node->service_id); fibril_mutex_unlock(&services_mutex); return ENOENT; } /* Set the correct session. */ dev->sess = sess; } else { service_t *dev = hash_table_get_inst(lnk, service_t, link); if (!dev->sess) { /* * Wait until the device structure is completed * and start from the beginning as the device * structure might have entirely disappeared * while we were not holding the mutex in * fibril_condvar_wait(). */ fibril_condvar_wait(&dev->cv, &services_mutex); goto restart; } dev->refcount++; } fibril_mutex_unlock(&services_mutex); return EOK; } return ENOENT; } static errno_t locfs_node_put(fs_node_t *fn) { free(fn->data); free(fn); return EOK; } static errno_t locfs_create_node(fs_node_t **rfn, service_id_t service_id, int lflag) { assert((lflag & L_FILE) ^ (lflag & L_DIRECTORY)); *rfn = NULL; return ENOTSUP; } static errno_t locfs_destroy_node(fs_node_t *fn) { return ENOTSUP; } static errno_t locfs_link_node(fs_node_t *pfn, fs_node_t *cfn, const char *nm) { return ENOTSUP; } static errno_t locfs_unlink_node(fs_node_t *pfn, fs_node_t *cfn, const char *nm) { return ENOTSUP; } static errno_t locfs_has_children(bool *has_children, fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; if (node->service_id == 0) { size_t count = loc_count_namespaces(); if (count > 0) { *has_children = true; return EOK; } /* Root namespace */ service_id_t namespace; if (loc_namespace_get_id("", &namespace, 0) == EOK) { count = loc_count_services(namespace); if (count > 0) { *has_children = true; return EOK; } } *has_children = false; return EOK; } if (node->type == LOC_OBJECT_NAMESPACE) { size_t count = loc_count_services(node->service_id); if (count > 0) { *has_children = true; return EOK; } *has_children = false; return EOK; } *has_children = false; return EOK; } static fs_index_t locfs_index_get(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; return node->service_id; } static aoff64_t locfs_size_get(fs_node_t *fn) { return 0; } static unsigned int locfs_lnkcnt_get(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; if (node->service_id == 0) return 0; return 1; } static bool locfs_is_directory(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; return ((node->type == LOC_OBJECT_NONE) || (node->type == LOC_OBJECT_NAMESPACE)); } static bool locfs_is_file(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; return (node->type == LOC_OBJECT_SERVICE); } static service_id_t locfs_service_get(fs_node_t *fn) { locfs_node_t *node = (locfs_node_t *) fn->data; if (node->type == LOC_OBJECT_SERVICE) return node->service_id; return 0; } /** libfs operations */ libfs_ops_t locfs_libfs_ops = { .root_get = locfs_root_get, .match = locfs_match, .node_get = locfs_node_get, .node_open = locfs_node_open, .node_put = locfs_node_put, .create = locfs_create_node, .destroy = locfs_destroy_node, .link = locfs_link_node, .unlink = locfs_unlink_node, .has_children = locfs_has_children, .index_get = locfs_index_get, .size_get = locfs_size_get, .lnkcnt_get = locfs_lnkcnt_get, .is_directory = locfs_is_directory, .is_file = locfs_is_file, .service_get = locfs_service_get }; bool locfs_init(void) { if (!hash_table_create(&services, 0, 0, &services_ops)) return false; return true; } static errno_t locfs_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info) { return ENOTSUP; } static errno_t locfs_mounted(service_id_t service_id, const char *opts, fs_index_t *index, aoff64_t *size) { *index = 0; *size = 0; return EOK; } static errno_t locfs_unmounted(service_id_t service_id) { return ENOTSUP; } static errno_t locfs_read(service_id_t service_id, fs_index_t index, aoff64_t pos, size_t *rbytes) { if (index == 0) { ipc_call_t call; size_t size; if (!async_data_read_receive(&call, &size)) { async_answer_0(&call, EINVAL); return EINVAL; } loc_sdesc_t *desc; size_t count = loc_get_namespaces(&desc); /* Get rid of root namespace */ size_t i; for (i = 0; i < count; i++) { if (str_cmp(desc[i].name, "") == 0) { if (pos >= i) pos++; break; } } if (pos < count) { async_data_read_finalize(&call, desc[pos].name, str_size(desc[pos].name) + 1); free(desc); *rbytes = 1; return EOK; } free(desc); pos -= count; /* Search root namespace */ service_id_t namespace; if (loc_namespace_get_id("", &namespace, 0) == EOK) { count = loc_get_services(namespace, &desc); if (pos < count) { async_data_read_finalize(&call, desc[pos].name, str_size(desc[pos].name) + 1); free(desc); *rbytes = 1; return EOK; } free(desc); } async_answer_0(&call, ENOENT); return ENOENT; } loc_object_type_t type = loc_id_probe(index); if (type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ ipc_call_t call; size_t size; if (!async_data_read_receive(&call, &size)) { async_answer_0(&call, EINVAL); return EINVAL; } loc_sdesc_t *desc; size_t count = loc_get_services(index, &desc); if (pos < count) { async_data_read_finalize(&call, desc[pos].name, str_size(desc[pos].name) + 1); free(desc); *rbytes = 1; return EOK; } free(desc); async_answer_0(&call, ENOENT); return ENOENT; } if (type == LOC_OBJECT_SERVICE) { /* Device node */ fibril_mutex_lock(&services_mutex); service_id_t service_index = index; ht_link_t *lnk = hash_table_find(&services, &service_index); if (lnk == NULL) { fibril_mutex_unlock(&services_mutex); return ENOENT; } service_t *dev = hash_table_get_inst(lnk, service_t, link); assert(dev->sess); ipc_call_t call; if (!async_data_read_receive(&call, NULL)) { fibril_mutex_unlock(&services_mutex); async_answer_0(&call, EINVAL); return EINVAL; } /* Make a request at the driver */ async_exch_t *exch = async_exchange_begin(dev->sess); ipc_call_t answer; aid_t msg = async_send_4(exch, VFS_OUT_READ, service_id, index, LOWER32(pos), UPPER32(pos), &answer); /* Forward the IPC_M_DATA_READ request to the driver */ async_forward_0(&call, exch, 0, IPC_FF_ROUTE_FROM_ME); async_exchange_end(exch); fibril_mutex_unlock(&services_mutex); /* Wait for reply from the driver. */ errno_t rc; async_wait_for(msg, &rc); /* Do not propagate EHANGUP back to VFS. */ if ((errno_t) rc == EHANGUP) rc = ENOTSUP; *rbytes = ipc_get_arg1(&answer); return rc; } return ENOENT; } static errno_t locfs_write(service_id_t service_id, fs_index_t index, aoff64_t pos, size_t *wbytes, aoff64_t *nsize) { if (index == 0) return ENOTSUP; loc_object_type_t type = loc_id_probe(index); if (type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ return ENOTSUP; } if (type == LOC_OBJECT_SERVICE) { /* Device node */ fibril_mutex_lock(&services_mutex); service_id_t service_index = index; ht_link_t *lnk = hash_table_find(&services, &service_index); if (lnk == NULL) { fibril_mutex_unlock(&services_mutex); return ENOENT; } service_t *dev = hash_table_get_inst(lnk, service_t, link); assert(dev->sess); ipc_call_t call; if (!async_data_write_receive(&call, NULL)) { fibril_mutex_unlock(&services_mutex); async_answer_0(&call, EINVAL); return EINVAL; } /* Make a request at the driver */ async_exch_t *exch = async_exchange_begin(dev->sess); ipc_call_t answer; aid_t msg = async_send_4(exch, VFS_OUT_WRITE, service_id, index, LOWER32(pos), UPPER32(pos), &answer); /* Forward the IPC_M_DATA_WRITE request to the driver */ async_forward_0(&call, exch, 0, IPC_FF_ROUTE_FROM_ME); async_exchange_end(exch); fibril_mutex_unlock(&services_mutex); /* Wait for reply from the driver. */ errno_t rc; async_wait_for(msg, &rc); /* Do not propagate EHANGUP back to VFS. */ if ((errno_t) rc == EHANGUP) rc = ENOTSUP; *wbytes = ipc_get_arg1(&answer); *nsize = 0; return rc; } return ENOENT; } static errno_t locfs_truncate(service_id_t service_id, fs_index_t index, aoff64_t size) { return ENOTSUP; } static errno_t locfs_close(service_id_t service_id, fs_index_t index) { if (index == 0) return EOK; loc_object_type_t type = loc_id_probe(index); if (type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ return EOK; } if (type == LOC_OBJECT_SERVICE) { fibril_mutex_lock(&services_mutex); service_id_t service_index = index; ht_link_t *lnk = hash_table_find(&services, &service_index); if (lnk == NULL) { fibril_mutex_unlock(&services_mutex); return ENOENT; } service_t *dev = hash_table_get_inst(lnk, service_t, link); assert(dev->sess); dev->refcount--; if (dev->refcount == 0) { async_hangup(dev->sess); service_id_t service_index = index; hash_table_remove(&services, &service_index); } fibril_mutex_unlock(&services_mutex); return EOK; } return ENOENT; } static errno_t locfs_sync(service_id_t service_id, fs_index_t index) { if (index == 0) return EOK; loc_object_type_t type = loc_id_probe(index); if (type == LOC_OBJECT_NAMESPACE) { /* Namespace directory */ return EOK; } if (type == LOC_OBJECT_SERVICE) { fibril_mutex_lock(&services_mutex); service_id_t service_index = index; ht_link_t *lnk = hash_table_find(&services, &service_index); if (lnk == NULL) { fibril_mutex_unlock(&services_mutex); return ENOENT; } service_t *dev = hash_table_get_inst(lnk, service_t, link); assert(dev->sess); /* Make a request at the driver */ async_exch_t *exch = async_exchange_begin(dev->sess); ipc_call_t answer; aid_t msg = async_send_2(exch, VFS_OUT_SYNC, service_id, index, &answer); async_exchange_end(exch); fibril_mutex_unlock(&services_mutex); /* Wait for reply from the driver */ errno_t rc; async_wait_for(msg, &rc); return rc; } return ENOENT; } static errno_t locfs_destroy(service_id_t service_id, fs_index_t index) { return ENOTSUP; } vfs_out_ops_t locfs_ops = { .fsprobe = locfs_fsprobe, .mounted = locfs_mounted, .unmounted = locfs_unmounted, .read = locfs_read, .write = locfs_write, .truncate = locfs_truncate, .close = locfs_close, .destroy = locfs_destroy, .sync = locfs_sync, }; /** * @} */