[db9aa04] | 1 | /*
|
---|
| 2 | * Copyright (c) 2008 Jakub Jermar
|
---|
| 3 | * Copyright (c) 2011 Oleg Romanenko
|
---|
| 4 | * All rights reserved.
|
---|
| 5 | *
|
---|
| 6 | * Redistribution and use in source and binary forms, with or without
|
---|
| 7 | * modification, are permitted provided that the following conditions
|
---|
| 8 | * are met:
|
---|
| 9 | *
|
---|
| 10 | * - Redistributions of source code must retain the above copyright
|
---|
| 11 | * notice, this list of conditions and the following disclaimer.
|
---|
| 12 | * - Redistributions in binary form must reproduce the above copyright
|
---|
| 13 | * notice, this list of conditions and the following disclaimer in the
|
---|
| 14 | * documentation and/or other materials provided with the distribution.
|
---|
| 15 | * - The name of the author may not be used to endorse or promote products
|
---|
| 16 | * derived from this software without specific prior written permission.
|
---|
| 17 | *
|
---|
| 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
---|
| 19 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
---|
| 20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
---|
| 21 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
---|
| 22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
---|
| 23 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
| 24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
| 25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
| 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
---|
| 27 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
| 28 | */
|
---|
| 29 |
|
---|
[b1834a01] | 30 | /** @addtogroup exfat
|
---|
[db9aa04] | 31 | * @{
|
---|
| 32 | */
|
---|
| 33 |
|
---|
| 34 | /**
|
---|
| 35 | * @file exfat_ops.c
|
---|
[0dbe5ac] | 36 | * @brief Implementation of VFS operations for the exFAT file system
|
---|
| 37 | * server.
|
---|
[db9aa04] | 38 | */
|
---|
| 39 |
|
---|
| 40 | #include "exfat.h"
|
---|
[4c3c4a5] | 41 | #include "exfat_fat.h"
|
---|
[81d773f] | 42 | #include "exfat_dentry.h"
|
---|
[a86e4f8] | 43 | #include "exfat_directory.h"
|
---|
[5d5863c] | 44 | #include "exfat_bitmap.h"
|
---|
[db9aa04] | 45 | #include "../../vfs/vfs.h"
|
---|
| 46 | #include <libfs.h>
|
---|
[f73b291] | 47 | #include <block.h>
|
---|
[db9aa04] | 48 | #include <ipc/services.h>
|
---|
[375ab5e] | 49 | #include <ipc/loc.h>
|
---|
[db9aa04] | 50 | #include <macros.h>
|
---|
| 51 | #include <async.h>
|
---|
| 52 | #include <errno.h>
|
---|
| 53 | #include <str.h>
|
---|
| 54 | #include <byteorder.h>
|
---|
| 55 | #include <adt/hash_table.h>
|
---|
[062d900] | 56 | #include <adt/hash.h>
|
---|
[db9aa04] | 57 | #include <adt/list.h>
|
---|
| 58 | #include <assert.h>
|
---|
| 59 | #include <fibril_synch.h>
|
---|
| 60 | #include <align.h>
|
---|
[4c3c4a5] | 61 | #include <stdio.h>
|
---|
[38d150e] | 62 | #include <stdlib.h>
|
---|
[db9aa04] | 63 |
|
---|
[4c3c4a5] | 64 | /** Mutex protecting the list of cached free FAT nodes. */
|
---|
| 65 | static FIBRIL_MUTEX_INITIALIZE(ffn_mutex);
|
---|
| 66 |
|
---|
| 67 | /** List of cached free FAT nodes. */
|
---|
[e97b8c6] | 68 | static LIST_INITIALIZE(ffn_list);
|
---|
[6d57e1c] | 69 |
|
---|
| 70 | /*
|
---|
[4c3c4a5] | 71 | * Forward declarations of FAT libfs operations.
|
---|
| 72 | */
|
---|
[5d5863c] | 73 |
|
---|
[8a8771c] | 74 | static void exfat_fsinfo(exfat_bs_t *, service_id_t);
|
---|
[b7fd2a0] | 75 | static errno_t exfat_root_get(fs_node_t **, service_id_t);
|
---|
| 76 | static errno_t exfat_match(fs_node_t **, fs_node_t *, const char *);
|
---|
| 77 | static errno_t exfat_node_get(fs_node_t **, service_id_t, fs_index_t);
|
---|
| 78 | static errno_t exfat_node_open(fs_node_t *);
|
---|
| 79 | /* static errno_t exfat_node_put(fs_node_t *); */
|
---|
| 80 | static errno_t exfat_create_node(fs_node_t **, service_id_t, int);
|
---|
| 81 | static errno_t exfat_destroy_node(fs_node_t *);
|
---|
| 82 | static errno_t exfat_link(fs_node_t *, fs_node_t *, const char *);
|
---|
| 83 | static errno_t exfat_unlink(fs_node_t *, fs_node_t *, const char *);
|
---|
| 84 | static errno_t exfat_has_children(bool *, fs_node_t *);
|
---|
[4c3c4a5] | 85 | static fs_index_t exfat_index_get(fs_node_t *);
|
---|
| 86 | static aoff64_t exfat_size_get(fs_node_t *);
|
---|
| 87 | static unsigned exfat_lnkcnt_get(fs_node_t *);
|
---|
| 88 | static bool exfat_is_directory(fs_node_t *);
|
---|
| 89 | static bool exfat_is_file(fs_node_t *node);
|
---|
[b33870b] | 90 | static service_id_t exfat_service_get(fs_node_t *node);
|
---|
[b7fd2a0] | 91 | static errno_t exfat_size_block(service_id_t, uint32_t *);
|
---|
| 92 | static errno_t exfat_total_block_count(service_id_t, uint64_t *);
|
---|
| 93 | static errno_t exfat_free_block_count(service_id_t, uint64_t *);
|
---|
[4c3c4a5] | 94 |
|
---|
| 95 | /*
|
---|
| 96 | * Helper functions.
|
---|
| 97 | */
|
---|
| 98 | static void exfat_node_initialize(exfat_node_t *node)
|
---|
| 99 | {
|
---|
| 100 | fibril_mutex_initialize(&node->lock);
|
---|
| 101 | node->bp = NULL;
|
---|
| 102 | node->idx = NULL;
|
---|
[81d773f] | 103 | node->type = EXFAT_UNKNOW;
|
---|
[4c3c4a5] | 104 | link_initialize(&node->ffn_link);
|
---|
| 105 | node->size = 0;
|
---|
| 106 | node->lnkcnt = 0;
|
---|
| 107 | node->refcnt = 0;
|
---|
| 108 | node->dirty = false;
|
---|
[998a78f] | 109 | node->fragmented = false;
|
---|
[4c3c4a5] | 110 | node->lastc_cached_valid = false;
|
---|
| 111 | node->lastc_cached_value = 0;
|
---|
| 112 | node->currc_cached_valid = false;
|
---|
| 113 | node->currc_cached_bn = 0;
|
---|
| 114 | node->currc_cached_value = 0;
|
---|
| 115 | }
|
---|
| 116 |
|
---|
[b7fd2a0] | 117 | static errno_t exfat_node_sync(exfat_node_t *node)
|
---|
[4c3c4a5] | 118 | {
|
---|
[b7fd2a0] | 119 | errno_t rc;
|
---|
[151a4e2] | 120 | exfat_directory_t di;
|
---|
| 121 | exfat_file_dentry_t df;
|
---|
| 122 | exfat_stream_dentry_t ds;
|
---|
| 123 |
|
---|
| 124 | if (!(node->type == EXFAT_DIRECTORY || node->type == EXFAT_FILE))
|
---|
| 125 | return EOK;
|
---|
| 126 |
|
---|
| 127 | if (node->type == EXFAT_DIRECTORY)
|
---|
| 128 | df.attr = EXFAT_ATTR_SUBDIR;
|
---|
| 129 | else
|
---|
| 130 | df.attr = 0;
|
---|
| 131 |
|
---|
[c56c4576] | 132 | ds.firstc = node->firstc;
|
---|
[151a4e2] | 133 | if (node->size == 0 && node->firstc == 0) {
|
---|
| 134 | ds.flags = 0;
|
---|
| 135 | } else {
|
---|
| 136 | ds.flags = 1;
|
---|
| 137 | ds.flags |= (!node->fragmented << 1);
|
---|
| 138 | }
|
---|
[c56c4576] | 139 | ds.valid_data_size = node->size;
|
---|
| 140 | ds.data_size = node->size;
|
---|
[151a4e2] | 141 |
|
---|
[1b20da0] | 142 | exfat_directory_open_parent(&di, node->idx->service_id, node->idx->pfc,
|
---|
[151a4e2] | 143 | node->idx->parent_fragmented);
|
---|
[93e12f3] | 144 | rc = exfat_directory_seek(&di, node->idx->pdi);
|
---|
| 145 | if (rc != EOK) {
|
---|
| 146 | (void) exfat_directory_close(&di);
|
---|
| 147 | return rc;
|
---|
| 148 | }
|
---|
| 149 |
|
---|
[151a4e2] | 150 | rc = exfat_directory_sync_file(&di, &df, &ds);
|
---|
| 151 | if (rc != EOK) {
|
---|
| 152 | (void) exfat_directory_close(&di);
|
---|
[f061de75] | 153 | return rc;
|
---|
| 154 | }
|
---|
[151a4e2] | 155 | return exfat_directory_close(&di);
|
---|
[4c3c4a5] | 156 | }
|
---|
| 157 |
|
---|
[b7fd2a0] | 158 | static errno_t exfat_node_fini_by_service_id(service_id_t service_id)
|
---|
[4c3c4a5] | 159 | {
|
---|
[b7fd2a0] | 160 | errno_t rc;
|
---|
[4c3c4a5] | 161 |
|
---|
| 162 | /*
|
---|
| 163 | * We are called from fat_unmounted() and assume that there are already
|
---|
| 164 | * no nodes belonging to this instance with non-zero refcount. Therefore
|
---|
| 165 | * it is sufficient to clean up only the FAT free node list.
|
---|
| 166 | */
|
---|
| 167 |
|
---|
| 168 | restart:
|
---|
| 169 | fibril_mutex_lock(&ffn_mutex);
|
---|
[feeac0d] | 170 | list_foreach(ffn_list, ffn_link, exfat_node_t, nodep) {
|
---|
[4c3c4a5] | 171 | if (!fibril_mutex_trylock(&nodep->lock)) {
|
---|
| 172 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 173 | goto restart;
|
---|
| 174 | }
|
---|
| 175 | if (!fibril_mutex_trylock(&nodep->idx->lock)) {
|
---|
| 176 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 177 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 178 | goto restart;
|
---|
| 179 | }
|
---|
[375ab5e] | 180 | if (nodep->idx->service_id != service_id) {
|
---|
[4c3c4a5] | 181 | fibril_mutex_unlock(&nodep->idx->lock);
|
---|
| 182 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 183 | continue;
|
---|
| 184 | }
|
---|
| 185 |
|
---|
| 186 | list_remove(&nodep->ffn_link);
|
---|
| 187 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 188 |
|
---|
| 189 | /*
|
---|
| 190 | * We can unlock the node and its index structure because we are
|
---|
| 191 | * the last player on this playground and VFS is preventing new
|
---|
| 192 | * players from entering.
|
---|
| 193 | */
|
---|
| 194 | fibril_mutex_unlock(&nodep->idx->lock);
|
---|
| 195 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 196 |
|
---|
| 197 | if (nodep->dirty) {
|
---|
| 198 | rc = exfat_node_sync(nodep);
|
---|
| 199 | if (rc != EOK)
|
---|
| 200 | return rc;
|
---|
| 201 | }
|
---|
| 202 | nodep->idx->nodep = NULL;
|
---|
| 203 | free(nodep->bp);
|
---|
| 204 | free(nodep);
|
---|
| 205 |
|
---|
[e97b8c6] | 206 | /* Need to restart because we changed the ffn_list. */
|
---|
[4c3c4a5] | 207 | goto restart;
|
---|
| 208 | }
|
---|
| 209 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 210 |
|
---|
| 211 | return EOK;
|
---|
| 212 | }
|
---|
| 213 |
|
---|
[b7fd2a0] | 214 | static errno_t exfat_node_get_new(exfat_node_t **nodepp)
|
---|
[81d773f] | 215 | {
|
---|
| 216 | fs_node_t *fn;
|
---|
| 217 | exfat_node_t *nodep;
|
---|
[b7fd2a0] | 218 | errno_t rc;
|
---|
[81d773f] | 219 |
|
---|
| 220 | fibril_mutex_lock(&ffn_mutex);
|
---|
[e97b8c6] | 221 | if (!list_empty(&ffn_list)) {
|
---|
[81d773f] | 222 | /* Try to use a cached free node structure. */
|
---|
| 223 | exfat_idx_t *idxp_tmp;
|
---|
[e97b8c6] | 224 | nodep = list_get_instance(list_first(&ffn_list), exfat_node_t,
|
---|
| 225 | ffn_link);
|
---|
[81d773f] | 226 | if (!fibril_mutex_trylock(&nodep->lock))
|
---|
| 227 | goto skip_cache;
|
---|
| 228 | idxp_tmp = nodep->idx;
|
---|
| 229 | if (!fibril_mutex_trylock(&idxp_tmp->lock)) {
|
---|
| 230 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 231 | goto skip_cache;
|
---|
| 232 | }
|
---|
| 233 | list_remove(&nodep->ffn_link);
|
---|
| 234 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 235 | if (nodep->dirty) {
|
---|
| 236 | rc = exfat_node_sync(nodep);
|
---|
| 237 | if (rc != EOK) {
|
---|
| 238 | idxp_tmp->nodep = NULL;
|
---|
| 239 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 240 | fibril_mutex_unlock(&idxp_tmp->lock);
|
---|
| 241 | free(nodep->bp);
|
---|
| 242 | free(nodep);
|
---|
| 243 | return rc;
|
---|
| 244 | }
|
---|
| 245 | }
|
---|
| 246 | idxp_tmp->nodep = NULL;
|
---|
| 247 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 248 | fibril_mutex_unlock(&idxp_tmp->lock);
|
---|
| 249 | fn = FS_NODE(nodep);
|
---|
| 250 | } else {
|
---|
[18b6a88] | 251 | skip_cache:
|
---|
[81d773f] | 252 | /* Try to allocate a new node structure. */
|
---|
| 253 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 254 | fn = (fs_node_t *)malloc(sizeof(fs_node_t));
|
---|
| 255 | if (!fn)
|
---|
| 256 | return ENOMEM;
|
---|
| 257 | nodep = (exfat_node_t *)malloc(sizeof(exfat_node_t));
|
---|
| 258 | if (!nodep) {
|
---|
| 259 | free(fn);
|
---|
| 260 | return ENOMEM;
|
---|
| 261 | }
|
---|
| 262 | }
|
---|
| 263 | exfat_node_initialize(nodep);
|
---|
| 264 | fs_node_initialize(fn);
|
---|
| 265 | fn->data = nodep;
|
---|
| 266 | nodep->bp = fn;
|
---|
| 267 |
|
---|
| 268 | *nodepp = nodep;
|
---|
| 269 | return EOK;
|
---|
| 270 | }
|
---|
| 271 |
|
---|
[1b20da0] | 272 | static errno_t exfat_node_get_new_by_pos(exfat_node_t **nodepp,
|
---|
[375ab5e] | 273 | service_id_t service_id, exfat_cluster_t pfc, unsigned pdi)
|
---|
[a86e4f8] | 274 | {
|
---|
[375ab5e] | 275 | exfat_idx_t *idxp = exfat_idx_get_by_pos(service_id, pfc, pdi);
|
---|
[a86e4f8] | 276 | if (!idxp)
|
---|
| 277 | return ENOMEM;
|
---|
| 278 | if (exfat_node_get_new(nodepp) != EOK)
|
---|
| 279 | return ENOMEM;
|
---|
| 280 | (*nodepp)->idx = idxp;
|
---|
| 281 | idxp->nodep = *nodepp;
|
---|
| 282 | return EOK;
|
---|
| 283 | }
|
---|
| 284 |
|
---|
[81d773f] | 285 | /** Internal version of exfat_node_get().
|
---|
| 286 | *
|
---|
| 287 | * @param idxp Locked index structure.
|
---|
| 288 | */
|
---|
[b7fd2a0] | 289 | static errno_t exfat_node_get_core(exfat_node_t **nodepp, exfat_idx_t *idxp)
|
---|
[81d773f] | 290 | {
|
---|
[f01fea3] | 291 | exfat_dentry_t *d;
|
---|
[81d773f] | 292 | exfat_node_t *nodep = NULL;
|
---|
[cd860fc] | 293 | exfat_directory_t di;
|
---|
[b7fd2a0] | 294 | errno_t rc;
|
---|
[81d773f] | 295 |
|
---|
| 296 | if (idxp->nodep) {
|
---|
| 297 | /*
|
---|
| 298 | * We are lucky.
|
---|
| 299 | * The node is already instantiated in memory.
|
---|
| 300 | */
|
---|
| 301 | fibril_mutex_lock(&idxp->nodep->lock);
|
---|
| 302 | if (!idxp->nodep->refcnt++) {
|
---|
| 303 | fibril_mutex_lock(&ffn_mutex);
|
---|
| 304 | list_remove(&idxp->nodep->ffn_link);
|
---|
| 305 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 306 | }
|
---|
| 307 | fibril_mutex_unlock(&idxp->nodep->lock);
|
---|
| 308 | *nodepp = idxp->nodep;
|
---|
| 309 | return EOK;
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | /*
|
---|
| 313 | * We must instantiate the node from the file system.
|
---|
| 314 | */
|
---|
| 315 |
|
---|
| 316 | assert(idxp->pfc);
|
---|
| 317 |
|
---|
| 318 | rc = exfat_node_get_new(&nodep);
|
---|
| 319 | if (rc != EOK)
|
---|
| 320 | return rc;
|
---|
| 321 |
|
---|
[1b20da0] | 322 | exfat_directory_open_parent(&di, idxp->service_id, idxp->pfc,
|
---|
[cd860fc] | 323 | idxp->parent_fragmented);
|
---|
| 324 | rc = exfat_directory_seek(&di, idxp->pdi);
|
---|
| 325 | if (rc != EOK) {
|
---|
| 326 | (void) exfat_directory_close(&di);
|
---|
| 327 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 328 | return rc;
|
---|
| 329 | }
|
---|
| 330 | rc = exfat_directory_get(&di, &d);
|
---|
[f01fea3] | 331 | if (rc != EOK) {
|
---|
[cd860fc] | 332 | (void) exfat_directory_close(&di);
|
---|
[f01fea3] | 333 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 334 | return rc;
|
---|
| 335 | }
|
---|
[81d773f] | 336 |
|
---|
[f01fea3] | 337 | switch (exfat_classify_dentry(d)) {
|
---|
[81d773f] | 338 | case EXFAT_DENTRY_FILE:
|
---|
[0dbe5ac] | 339 | nodep->type =
|
---|
[1b20da0] | 340 | (uint16_t_le2host(d->file.attr) & EXFAT_ATTR_SUBDIR) ?
|
---|
[81d773f] | 341 | EXFAT_DIRECTORY : EXFAT_FILE;
|
---|
[cd860fc] | 342 | rc = exfat_directory_next(&di);
|
---|
[f01fea3] | 343 | if (rc != EOK) {
|
---|
[cd860fc] | 344 | (void) exfat_directory_close(&di);
|
---|
[f01fea3] | 345 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 346 | return rc;
|
---|
| 347 | }
|
---|
[cd860fc] | 348 | rc = exfat_directory_get(&di, &d);
|
---|
[f01fea3] | 349 | if (rc != EOK) {
|
---|
[cd860fc] | 350 | (void) exfat_directory_close(&di);
|
---|
[f01fea3] | 351 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 352 | return rc;
|
---|
| 353 | }
|
---|
[c56c4576] | 354 | nodep->firstc = uint32_t_le2host(d->stream.firstc);
|
---|
| 355 | nodep->size = uint64_t_le2host(d->stream.data_size);
|
---|
[f01fea3] | 356 | nodep->fragmented = (d->stream.flags & 0x02) == 0;
|
---|
[81d773f] | 357 | break;
|
---|
| 358 | case EXFAT_DENTRY_BITMAP:
|
---|
| 359 | nodep->type = EXFAT_BITMAP;
|
---|
[c56c4576] | 360 | nodep->firstc = uint32_t_le2host(d->bitmap.firstc);
|
---|
| 361 | nodep->size = uint64_t_le2host(d->bitmap.size);
|
---|
[f01fea3] | 362 | nodep->fragmented = true;
|
---|
[81d773f] | 363 | break;
|
---|
| 364 | case EXFAT_DENTRY_UCTABLE:
|
---|
| 365 | nodep->type = EXFAT_UCTABLE;
|
---|
[c56c4576] | 366 | nodep->firstc = uint32_t_le2host(d->uctable.firstc);
|
---|
| 367 | nodep->size = uint64_t_le2host(d->uctable.size);
|
---|
[f01fea3] | 368 | nodep->fragmented = true;
|
---|
[81d773f] | 369 | break;
|
---|
| 370 | default:
|
---|
| 371 | case EXFAT_DENTRY_SKIP:
|
---|
| 372 | case EXFAT_DENTRY_LAST:
|
---|
| 373 | case EXFAT_DENTRY_FREE:
|
---|
| 374 | case EXFAT_DENTRY_VOLLABEL:
|
---|
| 375 | case EXFAT_DENTRY_GUID:
|
---|
| 376 | case EXFAT_DENTRY_STREAM:
|
---|
| 377 | case EXFAT_DENTRY_NAME:
|
---|
[cd860fc] | 378 | (void) exfat_directory_close(&di);
|
---|
[f01fea3] | 379 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
[81d773f] | 380 | return ENOENT;
|
---|
| 381 | }
|
---|
| 382 |
|
---|
| 383 | nodep->lnkcnt = 1;
|
---|
| 384 | nodep->refcnt = 1;
|
---|
| 385 |
|
---|
[cd860fc] | 386 | rc = exfat_directory_close(&di);
|
---|
[81d773f] | 387 | if (rc != EOK) {
|
---|
| 388 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 389 | return rc;
|
---|
| 390 | }
|
---|
| 391 |
|
---|
| 392 | /* Link the idx structure with the node structure. */
|
---|
| 393 | nodep->idx = idxp;
|
---|
| 394 | idxp->nodep = nodep;
|
---|
| 395 |
|
---|
| 396 | *nodepp = nodep;
|
---|
| 397 | return EOK;
|
---|
| 398 | }
|
---|
| 399 |
|
---|
[b7fd2a0] | 400 | errno_t exfat_node_expand(service_id_t service_id, exfat_node_t *nodep,
|
---|
[0dbe5ac] | 401 | exfat_cluster_t clusters)
|
---|
[998a78f] | 402 | {
|
---|
| 403 | exfat_bs_t *bs;
|
---|
[b7fd2a0] | 404 | errno_t rc;
|
---|
[375ab5e] | 405 | bs = block_bb_get(service_id);
|
---|
[998a78f] | 406 |
|
---|
[75513701] | 407 | if (!nodep->fragmented) {
|
---|
[e738d56] | 408 | rc = exfat_bitmap_append_clusters(bs, nodep, clusters);
|
---|
[998a78f] | 409 | if (rc != ENOSPC)
|
---|
| 410 | return rc;
|
---|
| 411 | if (rc == ENOSPC) {
|
---|
| 412 | nodep->fragmented = true;
|
---|
| 413 | nodep->dirty = true; /* need to sync node */
|
---|
[e738d56] | 414 | rc = exfat_bitmap_replicate_clusters(bs, nodep);
|
---|
[998a78f] | 415 | if (rc != EOK)
|
---|
| 416 | return rc;
|
---|
| 417 | }
|
---|
| 418 | }
|
---|
| 419 |
|
---|
| 420 | /* If we cant linear expand the node, we should use FAT instead */
|
---|
| 421 | exfat_cluster_t mcl, lcl;
|
---|
| 422 |
|
---|
| 423 | /* create an independent chain of nclsts clusters in all FATs */
|
---|
[375ab5e] | 424 | rc = exfat_alloc_clusters(bs, service_id, clusters, &mcl, &lcl);
|
---|
[998a78f] | 425 | if (rc != EOK)
|
---|
| 426 | return rc;
|
---|
[375ab5e] | 427 | rc = exfat_zero_cluster(bs, service_id, mcl);
|
---|
[d9aeab3] | 428 | if (rc != EOK) {
|
---|
[375ab5e] | 429 | (void) exfat_free_clusters(bs, service_id, mcl);
|
---|
[d9aeab3] | 430 | return rc;
|
---|
| 431 | }
|
---|
[998a78f] | 432 | /*
|
---|
| 433 | * Append the cluster chain starting in mcl to the end of the
|
---|
| 434 | * node's cluster chain.
|
---|
| 435 | */
|
---|
| 436 | rc = exfat_append_clusters(bs, nodep, mcl, lcl);
|
---|
| 437 | if (rc != EOK) {
|
---|
[375ab5e] | 438 | (void) exfat_free_clusters(bs, service_id, mcl);
|
---|
[998a78f] | 439 | return rc;
|
---|
| 440 | }
|
---|
| 441 |
|
---|
| 442 | return EOK;
|
---|
| 443 | }
|
---|
| 444 |
|
---|
[b7fd2a0] | 445 | static errno_t exfat_node_shrink(service_id_t service_id, exfat_node_t *nodep,
|
---|
[0dbe5ac] | 446 | aoff64_t size)
|
---|
[998a78f] | 447 | {
|
---|
| 448 | exfat_bs_t *bs;
|
---|
[b7fd2a0] | 449 | errno_t rc;
|
---|
[375ab5e] | 450 | bs = block_bb_get(service_id);
|
---|
[998a78f] | 451 |
|
---|
[75513701] | 452 | if (!nodep->fragmented) {
|
---|
[998a78f] | 453 | exfat_cluster_t clsts, prev_clsts, new_clsts;
|
---|
| 454 | prev_clsts = ROUND_UP(nodep->size, BPC(bs)) / BPC(bs);
|
---|
| 455 | new_clsts = ROUND_UP(size, BPC(bs)) / BPC(bs);
|
---|
| 456 |
|
---|
| 457 | assert(new_clsts < prev_clsts);
|
---|
| 458 |
|
---|
| 459 | clsts = prev_clsts - new_clsts;
|
---|
[e738d56] | 460 | rc = exfat_bitmap_free_clusters(bs, nodep, clsts);
|
---|
[998a78f] | 461 | if (rc != EOK)
|
---|
| 462 | return rc;
|
---|
| 463 | } else {
|
---|
| 464 | /*
|
---|
| 465 | * The node will be shrunk, clusters will be deallocated.
|
---|
| 466 | */
|
---|
| 467 | if (size == 0) {
|
---|
| 468 | rc = exfat_chop_clusters(bs, nodep, 0);
|
---|
| 469 | if (rc != EOK)
|
---|
| 470 | return rc;
|
---|
| 471 | } else {
|
---|
| 472 | exfat_cluster_t lastc;
|
---|
[375ab5e] | 473 | rc = exfat_cluster_walk(bs, service_id, nodep->firstc,
|
---|
[998a78f] | 474 | &lastc, NULL, (size - 1) / BPC(bs));
|
---|
| 475 | if (rc != EOK)
|
---|
| 476 | return rc;
|
---|
| 477 | rc = exfat_chop_clusters(bs, nodep, lastc);
|
---|
| 478 | if (rc != EOK)
|
---|
| 479 | return rc;
|
---|
| 480 | }
|
---|
| 481 | }
|
---|
| 482 |
|
---|
| 483 | nodep->size = size;
|
---|
| 484 | nodep->dirty = true; /* need to sync node */
|
---|
| 485 | return EOK;
|
---|
| 486 | }
|
---|
[81d773f] | 487 |
|
---|
[4c3c4a5] | 488 | /*
|
---|
[81d773f] | 489 | * EXFAT libfs operations.
|
---|
[4c3c4a5] | 490 | */
|
---|
| 491 |
|
---|
[b7fd2a0] | 492 | errno_t exfat_root_get(fs_node_t **rfn, service_id_t service_id)
|
---|
[4c3c4a5] | 493 | {
|
---|
[375ab5e] | 494 | return exfat_node_get(rfn, service_id, EXFAT_ROOT_IDX);
|
---|
[81d773f] | 495 | }
|
---|
| 496 |
|
---|
[b7fd2a0] | 497 | errno_t exfat_bitmap_get(fs_node_t **rfn, service_id_t service_id)
|
---|
[81d773f] | 498 | {
|
---|
[375ab5e] | 499 | return exfat_node_get(rfn, service_id, EXFAT_BITMAP_IDX);
|
---|
[81d773f] | 500 | }
|
---|
[5d5863c] | 501 |
|
---|
[b7fd2a0] | 502 | errno_t exfat_uctable_get(fs_node_t **rfn, service_id_t service_id)
|
---|
[81d773f] | 503 | {
|
---|
[375ab5e] | 504 | return exfat_node_get(rfn, service_id, EXFAT_UCTABLE_IDX);
|
---|
[4c3c4a5] | 505 | }
|
---|
[b289005] | 506 |
|
---|
[b7fd2a0] | 507 | errno_t exfat_match(fs_node_t **rfn, fs_node_t *pfn, const char *component)
|
---|
[4c3c4a5] | 508 | {
|
---|
[0bd5ff1] | 509 | exfat_node_t *parentp = EXFAT_NODE(pfn);
|
---|
[0dbe5ac] | 510 | char name[EXFAT_FILENAME_LEN + 1];
|
---|
[0bd5ff1] | 511 | exfat_file_dentry_t df;
|
---|
| 512 | exfat_stream_dentry_t ds;
|
---|
[375ab5e] | 513 | service_id_t service_id;
|
---|
[b7fd2a0] | 514 | errno_t rc;
|
---|
[0bd5ff1] | 515 |
|
---|
| 516 | fibril_mutex_lock(&parentp->idx->lock);
|
---|
[375ab5e] | 517 | service_id = parentp->idx->service_id;
|
---|
[0bd5ff1] | 518 | fibril_mutex_unlock(&parentp->idx->lock);
|
---|
[a35b458] | 519 |
|
---|
[0bd5ff1] | 520 | exfat_directory_t di;
|
---|
| 521 | rc = exfat_directory_open(parentp, &di);
|
---|
| 522 | if (rc != EOK)
|
---|
| 523 | return rc;
|
---|
| 524 |
|
---|
[0dbe5ac] | 525 | while (exfat_directory_read_file(&di, name, EXFAT_FILENAME_LEN, &df,
|
---|
| 526 | &ds) == EOK) {
|
---|
[0e7c3d9] | 527 | if (str_casecmp(name, component) == 0) {
|
---|
[0bd5ff1] | 528 | /* hit */
|
---|
| 529 | exfat_node_t *nodep;
|
---|
[0dbe5ac] | 530 | aoff64_t o = di.pos %
|
---|
| 531 | (BPS(di.bs) / sizeof(exfat_dentry_t));
|
---|
[375ab5e] | 532 | exfat_idx_t *idx = exfat_idx_get_by_pos(service_id,
|
---|
[18b6a88] | 533 | parentp->firstc, di.bnum * DPS(di.bs) + o);
|
---|
[0bd5ff1] | 534 | if (!idx) {
|
---|
| 535 | /*
|
---|
| 536 | * Can happen if memory is low or if we
|
---|
| 537 | * run out of 32-bit indices.
|
---|
| 538 | */
|
---|
| 539 | rc = exfat_directory_close(&di);
|
---|
| 540 | return (rc == EOK) ? ENOMEM : rc;
|
---|
| 541 | }
|
---|
| 542 | rc = exfat_node_get_core(&nodep, idx);
|
---|
| 543 | fibril_mutex_unlock(&idx->lock);
|
---|
| 544 | if (rc != EOK) {
|
---|
| 545 | (void) exfat_directory_close(&di);
|
---|
| 546 | return rc;
|
---|
| 547 | }
|
---|
| 548 | *rfn = FS_NODE(nodep);
|
---|
| 549 | rc = exfat_directory_close(&di);
|
---|
| 550 | if (rc != EOK)
|
---|
| 551 | (void) exfat_node_put(*rfn);
|
---|
| 552 | return rc;
|
---|
| 553 | } else {
|
---|
| 554 | rc = exfat_directory_next(&di);
|
---|
| 555 | if (rc != EOK)
|
---|
| 556 | break;
|
---|
| 557 | }
|
---|
| 558 | }
|
---|
| 559 | (void) exfat_directory_close(&di);
|
---|
[4c3c4a5] | 560 | *rfn = NULL;
|
---|
| 561 | return EOK;
|
---|
| 562 | }
|
---|
| 563 |
|
---|
| 564 | /** Instantiate a exFAT in-core node. */
|
---|
[b7fd2a0] | 565 | errno_t exfat_node_get(fs_node_t **rfn, service_id_t service_id, fs_index_t index)
|
---|
[4c3c4a5] | 566 | {
|
---|
[81d773f] | 567 | exfat_node_t *nodep;
|
---|
| 568 | exfat_idx_t *idxp;
|
---|
[b7fd2a0] | 569 | errno_t rc;
|
---|
[81d773f] | 570 |
|
---|
[375ab5e] | 571 | idxp = exfat_idx_get_by_index(service_id, index);
|
---|
[81d773f] | 572 | if (!idxp) {
|
---|
| 573 | *rfn = NULL;
|
---|
| 574 | return EOK;
|
---|
| 575 | }
|
---|
| 576 | /* idxp->lock held */
|
---|
| 577 | rc = exfat_node_get_core(&nodep, idxp);
|
---|
| 578 | fibril_mutex_unlock(&idxp->lock);
|
---|
| 579 | if (rc == EOK)
|
---|
| 580 | *rfn = FS_NODE(nodep);
|
---|
| 581 | return rc;
|
---|
[4c3c4a5] | 582 | }
|
---|
| 583 |
|
---|
[b7fd2a0] | 584 | errno_t exfat_node_open(fs_node_t *fn)
|
---|
[4c3c4a5] | 585 | {
|
---|
| 586 | /*
|
---|
| 587 | * Opening a file is stateless, nothing
|
---|
| 588 | * to be done here.
|
---|
| 589 | */
|
---|
| 590 | return EOK;
|
---|
| 591 | }
|
---|
| 592 |
|
---|
[b7fd2a0] | 593 | errno_t exfat_node_put(fs_node_t *fn)
|
---|
[4c3c4a5] | 594 | {
|
---|
[18902ca6] | 595 | if (fn == NULL)
|
---|
| 596 | return EOK;
|
---|
| 597 |
|
---|
[8f54827] | 598 | exfat_node_t *nodep = EXFAT_NODE(fn);
|
---|
| 599 | bool destroy = false;
|
---|
| 600 |
|
---|
| 601 | fibril_mutex_lock(&nodep->lock);
|
---|
| 602 | if (!--nodep->refcnt) {
|
---|
| 603 | if (nodep->idx) {
|
---|
| 604 | fibril_mutex_lock(&ffn_mutex);
|
---|
[e97b8c6] | 605 | list_append(&nodep->ffn_link, &ffn_list);
|
---|
[8f54827] | 606 | fibril_mutex_unlock(&ffn_mutex);
|
---|
| 607 | } else {
|
---|
| 608 | /*
|
---|
| 609 | * The node does not have any index structure associated
|
---|
| 610 | * with itself. This can only mean that we are releasing
|
---|
| 611 | * the node after a failed attempt to allocate the index
|
---|
| 612 | * structure for it.
|
---|
| 613 | */
|
---|
| 614 | destroy = true;
|
---|
| 615 | }
|
---|
| 616 | }
|
---|
| 617 | fibril_mutex_unlock(&nodep->lock);
|
---|
| 618 | if (destroy) {
|
---|
| 619 | free(nodep->bp);
|
---|
| 620 | free(nodep);
|
---|
| 621 | }
|
---|
[4c3c4a5] | 622 | return EOK;
|
---|
| 623 | }
|
---|
| 624 |
|
---|
[b7fd2a0] | 625 | errno_t exfat_create_node(fs_node_t **rfn, service_id_t service_id, int flags)
|
---|
[4c3c4a5] | 626 | {
|
---|
[998a78f] | 627 | exfat_idx_t *idxp;
|
---|
| 628 | exfat_node_t *nodep;
|
---|
[93e12f3] | 629 | exfat_bs_t *bs;
|
---|
[b7fd2a0] | 630 | errno_t rc;
|
---|
[998a78f] | 631 |
|
---|
[375ab5e] | 632 | bs = block_bb_get(service_id);
|
---|
[998a78f] | 633 | rc = exfat_node_get_new(&nodep);
|
---|
| 634 | if (rc != EOK)
|
---|
| 635 | return rc;
|
---|
| 636 |
|
---|
[375ab5e] | 637 | rc = exfat_idx_get_new(&idxp, service_id);
|
---|
[998a78f] | 638 | if (rc != EOK) {
|
---|
| 639 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 640 | return rc;
|
---|
| 641 | }
|
---|
| 642 |
|
---|
| 643 | nodep->firstc = 0;
|
---|
| 644 | nodep->size = 0;
|
---|
| 645 | nodep->fragmented = false;
|
---|
| 646 | nodep->lnkcnt = 0; /* not linked anywhere */
|
---|
| 647 | nodep->refcnt = 1;
|
---|
| 648 | nodep->dirty = true;
|
---|
| 649 |
|
---|
| 650 | nodep->idx = idxp;
|
---|
| 651 | idxp->nodep = nodep;
|
---|
| 652 | fibril_mutex_unlock(&idxp->lock);
|
---|
[93e12f3] | 653 |
|
---|
| 654 | if (flags & L_DIRECTORY) {
|
---|
| 655 | nodep->type = EXFAT_DIRECTORY;
|
---|
[375ab5e] | 656 | rc = exfat_node_expand(service_id, nodep, 1);
|
---|
[93e12f3] | 657 | if (rc != EOK) {
|
---|
| 658 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 659 | return rc;
|
---|
| 660 | }
|
---|
[80ec9b8] | 661 |
|
---|
| 662 | rc = exfat_zero_cluster(bs, service_id, nodep->firstc);
|
---|
| 663 | if (rc != EOK) {
|
---|
| 664 | (void) exfat_node_put(FS_NODE(nodep));
|
---|
| 665 | return rc;
|
---|
| 666 | }
|
---|
| 667 |
|
---|
[93e12f3] | 668 | nodep->size = BPC(bs);
|
---|
| 669 | } else {
|
---|
| 670 | nodep->type = EXFAT_FILE;
|
---|
| 671 | }
|
---|
| 672 |
|
---|
[998a78f] | 673 | *rfn = FS_NODE(nodep);
|
---|
[4c3c4a5] | 674 | return EOK;
|
---|
| 675 | }
|
---|
| 676 |
|
---|
[b7fd2a0] | 677 | errno_t exfat_destroy_node(fs_node_t *fn)
|
---|
[4c3c4a5] | 678 | {
|
---|
[998a78f] | 679 | exfat_node_t *nodep = EXFAT_NODE(fn);
|
---|
| 680 | exfat_bs_t *bs;
|
---|
| 681 | bool has_children;
|
---|
[b7fd2a0] | 682 | errno_t rc;
|
---|
[998a78f] | 683 |
|
---|
| 684 | /*
|
---|
| 685 | * The node is not reachable from the file system. This means that the
|
---|
| 686 | * link count should be zero and that the index structure cannot be
|
---|
| 687 | * found in the position hash. Obviously, we don't need to lock the node
|
---|
| 688 | * nor its index structure.
|
---|
| 689 | */
|
---|
| 690 | assert(nodep->lnkcnt == 0);
|
---|
| 691 |
|
---|
| 692 | /*
|
---|
| 693 | * The node may not have any children.
|
---|
| 694 | */
|
---|
| 695 | rc = exfat_has_children(&has_children, fn);
|
---|
| 696 | if (rc != EOK)
|
---|
| 697 | return rc;
|
---|
| 698 | assert(!has_children);
|
---|
| 699 |
|
---|
[375ab5e] | 700 | bs = block_bb_get(nodep->idx->service_id);
|
---|
[998a78f] | 701 | if (nodep->firstc != 0) {
|
---|
| 702 | assert(nodep->size);
|
---|
| 703 | /* Free all clusters allocated to the node. */
|
---|
| 704 | if (nodep->fragmented)
|
---|
[375ab5e] | 705 | rc = exfat_free_clusters(bs, nodep->idx->service_id,
|
---|
[18b6a88] | 706 | nodep->firstc);
|
---|
[998a78f] | 707 | else
|
---|
[1b20da0] | 708 | rc = exfat_bitmap_free_clusters(bs, nodep,
|
---|
[998a78f] | 709 | ROUND_UP(nodep->size, BPC(bs)) / BPC(bs));
|
---|
[1b20da0] | 710 | }
|
---|
[998a78f] | 711 |
|
---|
| 712 | exfat_idx_destroy(nodep->idx);
|
---|
| 713 | free(nodep->bp);
|
---|
| 714 | free(nodep);
|
---|
| 715 | return rc;
|
---|
[4c3c4a5] | 716 | }
|
---|
| 717 |
|
---|
[b7fd2a0] | 718 | errno_t exfat_link(fs_node_t *pfn, fs_node_t *cfn, const char *name)
|
---|
[4c3c4a5] | 719 | {
|
---|
[998a78f] | 720 | exfat_node_t *parentp = EXFAT_NODE(pfn);
|
---|
| 721 | exfat_node_t *childp = EXFAT_NODE(cfn);
|
---|
| 722 | exfat_directory_t di;
|
---|
[b7fd2a0] | 723 | errno_t rc;
|
---|
[998a78f] | 724 |
|
---|
| 725 | fibril_mutex_lock(&childp->lock);
|
---|
| 726 | if (childp->lnkcnt == 1) {
|
---|
| 727 | /*
|
---|
[f7d90eb] | 728 | * We don't support multiple hard links.
|
---|
[998a78f] | 729 | */
|
---|
| 730 | fibril_mutex_unlock(&childp->lock);
|
---|
| 731 | return EMLINK;
|
---|
| 732 | }
|
---|
| 733 | assert(childp->lnkcnt == 0);
|
---|
| 734 | fibril_mutex_unlock(&childp->lock);
|
---|
| 735 |
|
---|
| 736 | if (!exfat_valid_name(name))
|
---|
| 737 | return ENOTSUP;
|
---|
| 738 |
|
---|
| 739 | fibril_mutex_lock(&parentp->idx->lock);
|
---|
| 740 | rc = exfat_directory_open(parentp, &di);
|
---|
| 741 | if (rc != EOK)
|
---|
| 742 | return rc;
|
---|
| 743 | /*
|
---|
| 744 | * At this point we only establish the link between the parent and the
|
---|
| 745 | * child. The dentry, except of the name and the extension, will remain
|
---|
| 746 | * uninitialized until the corresponding node is synced. Thus the valid
|
---|
| 747 | * dentry data is kept in the child node structure.
|
---|
| 748 | */
|
---|
| 749 | rc = exfat_directory_write_file(&di, name);
|
---|
[8d2cd8b] | 750 | if (rc != EOK) {
|
---|
| 751 | (void) exfat_directory_close(&di);
|
---|
| 752 | fibril_mutex_unlock(&parentp->idx->lock);
|
---|
[998a78f] | 753 | return rc;
|
---|
[8d2cd8b] | 754 | }
|
---|
[998a78f] | 755 | rc = exfat_directory_close(&di);
|
---|
[8d2cd8b] | 756 | if (rc != EOK) {
|
---|
| 757 | fibril_mutex_unlock(&parentp->idx->lock);
|
---|
[998a78f] | 758 | return rc;
|
---|
[8d2cd8b] | 759 | }
|
---|
[998a78f] | 760 |
|
---|
| 761 | fibril_mutex_unlock(&parentp->idx->lock);
|
---|
| 762 | fibril_mutex_lock(&childp->idx->lock);
|
---|
| 763 |
|
---|
| 764 | childp->idx->pfc = parentp->firstc;
|
---|
| 765 | childp->idx->parent_fragmented = parentp->fragmented;
|
---|
[93e12f3] | 766 | childp->idx->pdi = di.pos;
|
---|
[998a78f] | 767 | fibril_mutex_unlock(&childp->idx->lock);
|
---|
| 768 |
|
---|
| 769 | fibril_mutex_lock(&childp->lock);
|
---|
| 770 | childp->lnkcnt = 1;
|
---|
| 771 | childp->dirty = true; /* need to sync node */
|
---|
| 772 | fibril_mutex_unlock(&childp->lock);
|
---|
| 773 |
|
---|
| 774 | /*
|
---|
| 775 | * Hash in the index structure into the position hash.
|
---|
| 776 | */
|
---|
| 777 | exfat_idx_hashin(childp->idx);
|
---|
| 778 |
|
---|
[4c3c4a5] | 779 | return EOK;
|
---|
[998a78f] | 780 |
|
---|
[4c3c4a5] | 781 | }
|
---|
| 782 |
|
---|
[b7fd2a0] | 783 | errno_t exfat_unlink(fs_node_t *pfn, fs_node_t *cfn, const char *nm)
|
---|
[4c3c4a5] | 784 | {
|
---|
[998a78f] | 785 | exfat_node_t *parentp = EXFAT_NODE(pfn);
|
---|
| 786 | exfat_node_t *childp = EXFAT_NODE(cfn);
|
---|
| 787 | bool has_children;
|
---|
[b7fd2a0] | 788 | errno_t rc;
|
---|
[998a78f] | 789 |
|
---|
| 790 | if (!parentp)
|
---|
| 791 | return EBUSY;
|
---|
| 792 |
|
---|
| 793 | rc = exfat_has_children(&has_children, cfn);
|
---|
| 794 | if (rc != EOK)
|
---|
| 795 | return rc;
|
---|
| 796 | if (has_children)
|
---|
| 797 | return ENOTEMPTY;
|
---|
| 798 |
|
---|
| 799 | fibril_mutex_lock(&parentp->lock);
|
---|
| 800 | fibril_mutex_lock(&childp->lock);
|
---|
| 801 | assert(childp->lnkcnt == 1);
|
---|
| 802 | fibril_mutex_lock(&childp->idx->lock);
|
---|
[a35b458] | 803 |
|
---|
[998a78f] | 804 | exfat_directory_t di;
|
---|
[18b6a88] | 805 | rc = exfat_directory_open(parentp, &di);
|
---|
[998a78f] | 806 | if (rc != EOK)
|
---|
| 807 | goto error;
|
---|
| 808 | rc = exfat_directory_erase_file(&di, childp->idx->pdi);
|
---|
| 809 | if (rc != EOK)
|
---|
| 810 | goto error;
|
---|
| 811 | rc = exfat_directory_close(&di);
|
---|
| 812 | if (rc != EOK)
|
---|
| 813 | goto error;
|
---|
| 814 |
|
---|
| 815 | /* remove the index structure from the position hash */
|
---|
| 816 | exfat_idx_hashout(childp->idx);
|
---|
| 817 | /* clear position information */
|
---|
| 818 | childp->idx->pfc = 0;
|
---|
| 819 | childp->idx->pdi = 0;
|
---|
| 820 | fibril_mutex_unlock(&childp->idx->lock);
|
---|
| 821 | childp->lnkcnt = 0;
|
---|
| 822 | childp->refcnt++; /* keep the node in memory until destroyed */
|
---|
| 823 | childp->dirty = true;
|
---|
| 824 | fibril_mutex_unlock(&childp->lock);
|
---|
| 825 | fibril_mutex_unlock(&parentp->lock);
|
---|
| 826 |
|
---|
[4c3c4a5] | 827 | return EOK;
|
---|
[998a78f] | 828 |
|
---|
| 829 | error:
|
---|
| 830 | (void) exfat_directory_close(&di);
|
---|
| 831 | fibril_mutex_unlock(&childp->idx->lock);
|
---|
| 832 | fibril_mutex_unlock(&childp->lock);
|
---|
| 833 | fibril_mutex_unlock(&parentp->lock);
|
---|
| 834 | return rc;
|
---|
| 835 |
|
---|
[4c3c4a5] | 836 | }
|
---|
| 837 |
|
---|
[b7fd2a0] | 838 | errno_t exfat_has_children(bool *has_children, fs_node_t *fn)
|
---|
[4c3c4a5] | 839 | {
|
---|
[59c07773] | 840 | exfat_directory_t di;
|
---|
| 841 | exfat_dentry_t *d;
|
---|
| 842 | exfat_node_t *nodep = EXFAT_NODE(fn);
|
---|
[b7fd2a0] | 843 | errno_t rc;
|
---|
[59c07773] | 844 |
|
---|
[4c3c4a5] | 845 | *has_children = false;
|
---|
[59c07773] | 846 |
|
---|
| 847 | if (nodep->type != EXFAT_DIRECTORY)
|
---|
| 848 | return EOK;
|
---|
| 849 |
|
---|
| 850 | fibril_mutex_lock(&nodep->idx->lock);
|
---|
| 851 |
|
---|
| 852 | rc = exfat_directory_open(nodep, &di);
|
---|
| 853 | if (rc != EOK) {
|
---|
| 854 | fibril_mutex_unlock(&nodep->idx->lock);
|
---|
| 855 | return rc;
|
---|
| 856 | }
|
---|
| 857 |
|
---|
| 858 | do {
|
---|
| 859 | rc = exfat_directory_get(&di, &d);
|
---|
| 860 | if (rc != EOK) {
|
---|
| 861 | (void) exfat_directory_close(&di);
|
---|
| 862 | fibril_mutex_unlock(&nodep->idx->lock);
|
---|
| 863 | return rc;
|
---|
| 864 | }
|
---|
| 865 | switch (exfat_classify_dentry(d)) {
|
---|
| 866 | case EXFAT_DENTRY_SKIP:
|
---|
| 867 | case EXFAT_DENTRY_FREE:
|
---|
| 868 | continue;
|
---|
| 869 | case EXFAT_DENTRY_LAST:
|
---|
| 870 | *has_children = false;
|
---|
| 871 | goto exit;
|
---|
| 872 | default:
|
---|
| 873 | *has_children = true;
|
---|
| 874 | goto exit;
|
---|
| 875 | }
|
---|
| 876 | } while (exfat_directory_next(&di) == EOK);
|
---|
| 877 |
|
---|
| 878 | exit:
|
---|
| 879 | rc = exfat_directory_close(&di);
|
---|
| 880 | fibril_mutex_unlock(&nodep->idx->lock);
|
---|
| 881 | return rc;
|
---|
[4c3c4a5] | 882 | }
|
---|
| 883 |
|
---|
| 884 | fs_index_t exfat_index_get(fs_node_t *fn)
|
---|
| 885 | {
|
---|
| 886 | return EXFAT_NODE(fn)->idx->index;
|
---|
| 887 | }
|
---|
| 888 |
|
---|
| 889 | aoff64_t exfat_size_get(fs_node_t *fn)
|
---|
| 890 | {
|
---|
| 891 | return EXFAT_NODE(fn)->size;
|
---|
| 892 | }
|
---|
| 893 |
|
---|
| 894 | unsigned exfat_lnkcnt_get(fs_node_t *fn)
|
---|
| 895 | {
|
---|
| 896 | return EXFAT_NODE(fn)->lnkcnt;
|
---|
| 897 | }
|
---|
| 898 |
|
---|
| 899 | bool exfat_is_directory(fs_node_t *fn)
|
---|
| 900 | {
|
---|
| 901 | return EXFAT_NODE(fn)->type == EXFAT_DIRECTORY;
|
---|
| 902 | }
|
---|
| 903 |
|
---|
| 904 | bool exfat_is_file(fs_node_t *fn)
|
---|
| 905 | {
|
---|
| 906 | return EXFAT_NODE(fn)->type == EXFAT_FILE;
|
---|
| 907 | }
|
---|
| 908 |
|
---|
[b33870b] | 909 | service_id_t exfat_service_get(fs_node_t *node)
|
---|
[4c3c4a5] | 910 | {
|
---|
| 911 | return 0;
|
---|
| 912 | }
|
---|
| 913 |
|
---|
[b7fd2a0] | 914 | errno_t exfat_size_block(service_id_t service_id, uint32_t *size)
|
---|
[951f32ce] | 915 | {
|
---|
| 916 | exfat_bs_t *bs;
|
---|
| 917 | bs = block_bb_get(service_id);
|
---|
[3dd148d] | 918 | *size = BPC(bs);
|
---|
| 919 |
|
---|
| 920 | return EOK;
|
---|
[951f32ce] | 921 | }
|
---|
[4c3c4a5] | 922 |
|
---|
[b7fd2a0] | 923 | errno_t exfat_total_block_count(service_id_t service_id, uint64_t *count)
|
---|
[87159eb8] | 924 | {
|
---|
| 925 | exfat_bs_t *bs;
|
---|
| 926 | bs = block_bb_get(service_id);
|
---|
[3dd148d] | 927 | *count = DATA_CNT(bs);
|
---|
[a35b458] | 928 |
|
---|
[3dd148d] | 929 | return EOK;
|
---|
[87159eb8] | 930 | }
|
---|
| 931 |
|
---|
[b7fd2a0] | 932 | errno_t exfat_free_block_count(service_id_t service_id, uint64_t *count)
|
---|
[87159eb8] | 933 | {
|
---|
[18902ca6] | 934 | fs_node_t *node = NULL;
|
---|
[0e976d9b] | 935 | exfat_node_t *bmap_node;
|
---|
[87159eb8] | 936 | exfat_bs_t *bs;
|
---|
[0e976d9b] | 937 | uint64_t free_block_count = 0;
|
---|
| 938 | uint64_t block_count;
|
---|
| 939 | unsigned sector;
|
---|
[b7fd2a0] | 940 | errno_t rc;
|
---|
[0e976d9b] | 941 |
|
---|
[3dd148d] | 942 | rc = exfat_total_block_count(service_id, &block_count);
|
---|
| 943 | if (rc != EOK)
|
---|
| 944 | goto exit;
|
---|
[0e976d9b] | 945 |
|
---|
[87159eb8] | 946 | bs = block_bb_get(service_id);
|
---|
[0e976d9b] | 947 | rc = exfat_bitmap_get(&node, service_id);
|
---|
| 948 | if (rc != EOK)
|
---|
| 949 | goto exit;
|
---|
| 950 |
|
---|
| 951 | bmap_node = (exfat_node_t *) node->data;
|
---|
| 952 |
|
---|
[6b6f394d] | 953 | unsigned const bmap_sectors = ROUND_UP(bmap_node->size, BPS(bs)) /
|
---|
| 954 | BPS(bs);
|
---|
| 955 |
|
---|
| 956 | for (sector = 0; sector < bmap_sectors; ++sector) {
|
---|
[0e976d9b] | 957 |
|
---|
| 958 | block_t *block;
|
---|
| 959 | uint8_t *bitmap;
|
---|
| 960 | unsigned bit;
|
---|
| 961 |
|
---|
[6b6f394d] | 962 | rc = exfat_block_get(&block, bs, bmap_node, sector,
|
---|
[0e976d9b] | 963 | BLOCK_FLAGS_NONE);
|
---|
| 964 | if (rc != EOK) {
|
---|
| 965 | free_block_count = 0;
|
---|
| 966 | goto exit;
|
---|
| 967 | }
|
---|
| 968 |
|
---|
| 969 | bitmap = (uint8_t *) block->data;
|
---|
| 970 |
|
---|
| 971 | for (bit = 0; bit < BPS(bs) * 8 && block_count > 0;
|
---|
| 972 | ++bit, --block_count) {
|
---|
| 973 | if (!(bitmap[bit / 8] & (1 << (bit % 8))))
|
---|
| 974 | ++free_block_count;
|
---|
| 975 | }
|
---|
| 976 |
|
---|
| 977 | block_put(block);
|
---|
| 978 |
|
---|
| 979 | if (block_count == 0) {
|
---|
| 980 | /* Reached the end of the bitmap */
|
---|
| 981 | goto exit;
|
---|
| 982 | }
|
---|
| 983 | }
|
---|
| 984 |
|
---|
| 985 | exit:
|
---|
| 986 | exfat_node_put(node);
|
---|
[3dd148d] | 987 | *count = free_block_count;
|
---|
| 988 | return rc;
|
---|
[87159eb8] | 989 | }
|
---|
[4c3c4a5] | 990 |
|
---|
| 991 | /** libfs operations */
|
---|
[6d57e1c] | 992 | libfs_ops_t exfat_libfs_ops = {
|
---|
| 993 | .root_get = exfat_root_get,
|
---|
| 994 | .match = exfat_match,
|
---|
| 995 | .node_get = exfat_node_get,
|
---|
| 996 | .node_open = exfat_node_open,
|
---|
| 997 | .node_put = exfat_node_put,
|
---|
| 998 | .create = exfat_create_node,
|
---|
| 999 | .destroy = exfat_destroy_node,
|
---|
| 1000 | .link = exfat_link,
|
---|
| 1001 | .unlink = exfat_unlink,
|
---|
| 1002 | .has_children = exfat_has_children,
|
---|
| 1003 | .index_get = exfat_index_get,
|
---|
| 1004 | .size_get = exfat_size_get,
|
---|
| 1005 | .lnkcnt_get = exfat_lnkcnt_get,
|
---|
| 1006 | .is_directory = exfat_is_directory,
|
---|
| 1007 | .is_file = exfat_is_file,
|
---|
[951f32ce] | 1008 | .service_get = exfat_service_get,
|
---|
[87159eb8] | 1009 | .size_block = exfat_size_block,
|
---|
[c84146d3] | 1010 | .total_block_count = exfat_total_block_count,
|
---|
| 1011 | .free_block_count = exfat_free_block_count
|
---|
[6d57e1c] | 1012 | };
|
---|
[4c3c4a5] | 1013 |
|
---|
[b7fd2a0] | 1014 | static errno_t exfat_fs_open(service_id_t service_id, enum cache_mode cmode,
|
---|
[f3504c1] | 1015 | fs_node_t **rrfn, exfat_idx_t **rridxp, vfs_fs_probe_info_t *info)
|
---|
[6d57e1c] | 1016 | {
|
---|
[b7fd2a0] | 1017 | errno_t rc;
|
---|
[0dbe5ac] | 1018 | exfat_node_t *rootp = NULL, *bitmapp = NULL, *uctablep = NULL;
|
---|
[6d57e1c] | 1019 | exfat_bs_t *bs;
|
---|
| 1020 |
|
---|
| 1021 | /* initialize libblock */
|
---|
[fc22069] | 1022 | rc = block_init(service_id, BS_SIZE);
|
---|
[e97b8c6] | 1023 | if (rc != EOK)
|
---|
| 1024 | return rc;
|
---|
[6d57e1c] | 1025 |
|
---|
| 1026 | /* prepare the boot block */
|
---|
[375ab5e] | 1027 | rc = block_bb_read(service_id, BS_BLOCK);
|
---|
[6d57e1c] | 1028 | if (rc != EOK) {
|
---|
[375ab5e] | 1029 | block_fini(service_id);
|
---|
[e97b8c6] | 1030 | return rc;
|
---|
[6d57e1c] | 1031 | }
|
---|
| 1032 |
|
---|
| 1033 | /* get the buffer with the boot sector */
|
---|
[375ab5e] | 1034 | bs = block_bb_get(service_id);
|
---|
[6d57e1c] | 1035 |
|
---|
[395df52] | 1036 | /* Do some simple sanity checks on the file system. */
|
---|
| 1037 | rc = exfat_sanity_check(bs);
|
---|
[6d57e1c] | 1038 | if (rc != EOK) {
|
---|
[395df52] | 1039 | (void) block_cache_fini(service_id);
|
---|
[375ab5e] | 1040 | block_fini(service_id);
|
---|
[e97b8c6] | 1041 | return rc;
|
---|
[6d57e1c] | 1042 | }
|
---|
| 1043 |
|
---|
[395df52] | 1044 | /* Initialize the block cache */
|
---|
| 1045 | rc = block_cache_init(service_id, BPS(bs), 0 /* XXX */, cmode);
|
---|
[4c3c4a5] | 1046 | if (rc != EOK) {
|
---|
[375ab5e] | 1047 | block_fini(service_id);
|
---|
[e97b8c6] | 1048 | return rc;
|
---|
[4c3c4a5] | 1049 | }
|
---|
| 1050 |
|
---|
[375ab5e] | 1051 | rc = exfat_idx_init_by_service_id(service_id);
|
---|
[4c3c4a5] | 1052 | if (rc != EOK) {
|
---|
[375ab5e] | 1053 | (void) block_cache_fini(service_id);
|
---|
| 1054 | block_fini(service_id);
|
---|
[e97b8c6] | 1055 | return rc;
|
---|
[4c3c4a5] | 1056 | }
|
---|
| 1057 |
|
---|
| 1058 | /* Initialize the root node. */
|
---|
[1b20da0] | 1059 | rc = exfat_node_get_new_by_pos(&rootp, service_id, EXFAT_ROOT_PAR,
|
---|
[a86e4f8] | 1060 | EXFAT_ROOT_POS);
|
---|
[18b6a88] | 1061 | if (rc != EOK) {
|
---|
[375ab5e] | 1062 | (void) block_cache_fini(service_id);
|
---|
| 1063 | block_fini(service_id);
|
---|
| 1064 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1065 | return ENOMEM;
|
---|
[4c3c4a5] | 1066 | }
|
---|
[a86e4f8] | 1067 | assert(rootp->idx->index == EXFAT_ROOT_IDX);
|
---|
| 1068 |
|
---|
| 1069 | rootp->type = EXFAT_DIRECTORY;
|
---|
| 1070 | rootp->firstc = ROOT_FC(bs);
|
---|
| 1071 | rootp->fragmented = true;
|
---|
| 1072 | rootp->refcnt = 1;
|
---|
| 1073 | rootp->lnkcnt = 0; /* FS root is not linked */
|
---|
[4c3c4a5] | 1074 |
|
---|
[a86e4f8] | 1075 | uint32_t clusters;
|
---|
[375ab5e] | 1076 | rc = exfat_clusters_get(&clusters, bs, service_id, rootp->firstc);
|
---|
[a86e4f8] | 1077 | if (rc != EOK) {
|
---|
| 1078 | free(rootp);
|
---|
[375ab5e] | 1079 | (void) block_cache_fini(service_id);
|
---|
| 1080 | block_fini(service_id);
|
---|
| 1081 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1082 | return ENOTSUP;
|
---|
[4c3c4a5] | 1083 | }
|
---|
[a86e4f8] | 1084 | rootp->size = BPS(bs) * SPC(bs) * clusters;
|
---|
| 1085 | fibril_mutex_unlock(&rootp->idx->lock);
|
---|
[4c3c4a5] | 1086 |
|
---|
[a86e4f8] | 1087 | /* Open root directory and looking for Bitmap and UC-Table */
|
---|
| 1088 | exfat_directory_t di;
|
---|
| 1089 | exfat_dentry_t *de;
|
---|
| 1090 | rc = exfat_directory_open(rootp, &di);
|
---|
| 1091 | if (rc != EOK) {
|
---|
| 1092 | free(rootp);
|
---|
[375ab5e] | 1093 | (void) block_cache_fini(service_id);
|
---|
| 1094 | block_fini(service_id);
|
---|
| 1095 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1096 | return ENOTSUP;
|
---|
[a86e4f8] | 1097 | }
|
---|
[4c3c4a5] | 1098 |
|
---|
[a86e4f8] | 1099 | /* Initialize the bitmap node. */
|
---|
| 1100 | rc = exfat_directory_find(&di, EXFAT_DENTRY_BITMAP, &de);
|
---|
| 1101 | if (rc != EOK) {
|
---|
| 1102 | free(rootp);
|
---|
[375ab5e] | 1103 | (void) block_cache_fini(service_id);
|
---|
| 1104 | block_fini(service_id);
|
---|
| 1105 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1106 | return ENOTSUP;
|
---|
[a86e4f8] | 1107 | }
|
---|
| 1108 |
|
---|
[1b20da0] | 1109 | rc = exfat_node_get_new_by_pos(&bitmapp, service_id, rootp->firstc,
|
---|
[a86e4f8] | 1110 | di.pos);
|
---|
[0dbe5ac] | 1111 | if (rc != EOK) {
|
---|
[4c3c4a5] | 1112 | free(rootp);
|
---|
[375ab5e] | 1113 | (void) block_cache_fini(service_id);
|
---|
| 1114 | block_fini(service_id);
|
---|
| 1115 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1116 | return ENOMEM;
|
---|
[4c3c4a5] | 1117 | }
|
---|
[a86e4f8] | 1118 | assert(bitmapp->idx->index == EXFAT_BITMAP_IDX);
|
---|
| 1119 | fibril_mutex_unlock(&bitmapp->idx->lock);
|
---|
| 1120 |
|
---|
| 1121 | bitmapp->type = EXFAT_BITMAP;
|
---|
[c56c4576] | 1122 | bitmapp->firstc = uint32_t_le2host(de->bitmap.firstc);
|
---|
[a86e4f8] | 1123 | bitmapp->fragmented = true;
|
---|
[1f1d96a] | 1124 | bitmapp->idx->parent_fragmented = true;
|
---|
[a86e4f8] | 1125 | bitmapp->refcnt = 1;
|
---|
| 1126 | bitmapp->lnkcnt = 0;
|
---|
[c56c4576] | 1127 | bitmapp->size = uint64_t_le2host(de->bitmap.size);
|
---|
[a86e4f8] | 1128 |
|
---|
| 1129 | /* Initialize the uctable node. */
|
---|
| 1130 | rc = exfat_directory_seek(&di, 0);
|
---|
| 1131 | if (rc != EOK) {
|
---|
| 1132 | free(rootp);
|
---|
| 1133 | free(bitmapp);
|
---|
[375ab5e] | 1134 | (void) block_cache_fini(service_id);
|
---|
| 1135 | block_fini(service_id);
|
---|
| 1136 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1137 | return ENOTSUP;
|
---|
[a86e4f8] | 1138 | }
|
---|
[4c3c4a5] | 1139 |
|
---|
[a86e4f8] | 1140 | rc = exfat_directory_find(&di, EXFAT_DENTRY_UCTABLE, &de);
|
---|
| 1141 | if (rc != EOK) {
|
---|
| 1142 | free(rootp);
|
---|
| 1143 | free(bitmapp);
|
---|
[375ab5e] | 1144 | (void) block_cache_fini(service_id);
|
---|
| 1145 | block_fini(service_id);
|
---|
| 1146 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1147 | return ENOTSUP;
|
---|
[a86e4f8] | 1148 | }
|
---|
[4c3c4a5] | 1149 |
|
---|
[1b20da0] | 1150 | rc = exfat_node_get_new_by_pos(&uctablep, service_id, rootp->firstc,
|
---|
[a86e4f8] | 1151 | di.pos);
|
---|
[0dbe5ac] | 1152 | if (rc != EOK) {
|
---|
[a86e4f8] | 1153 | free(rootp);
|
---|
| 1154 | free(bitmapp);
|
---|
[375ab5e] | 1155 | (void) block_cache_fini(service_id);
|
---|
| 1156 | block_fini(service_id);
|
---|
| 1157 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1158 | return ENOMEM;
|
---|
[a86e4f8] | 1159 | }
|
---|
| 1160 | assert(uctablep->idx->index == EXFAT_UCTABLE_IDX);
|
---|
| 1161 | fibril_mutex_unlock(&uctablep->idx->lock);
|
---|
| 1162 |
|
---|
| 1163 | uctablep->type = EXFAT_UCTABLE;
|
---|
[c56c4576] | 1164 | uctablep->firstc = uint32_t_le2host(de->uctable.firstc);
|
---|
[a86e4f8] | 1165 | uctablep->fragmented = true;
|
---|
[1f1d96a] | 1166 | uctablep->idx->parent_fragmented = true;
|
---|
[a86e4f8] | 1167 | uctablep->refcnt = 1;
|
---|
| 1168 | uctablep->lnkcnt = 0;
|
---|
[c56c4576] | 1169 | uctablep->size = uint64_t_le2host(de->uctable.size);
|
---|
[a86e4f8] | 1170 |
|
---|
[f3504c1] | 1171 | if (info != NULL) {
|
---|
| 1172 | /* Read volume label. */
|
---|
| 1173 | rc = exfat_directory_read_vollabel(&di, info->label,
|
---|
| 1174 | FS_LABEL_MAXLEN + 1);
|
---|
| 1175 | if (rc != EOK) {
|
---|
| 1176 | free(rootp);
|
---|
| 1177 | free(bitmapp);
|
---|
| 1178 | free(uctablep);
|
---|
| 1179 | (void) block_cache_fini(service_id);
|
---|
| 1180 | block_fini(service_id);
|
---|
| 1181 | exfat_idx_fini_by_service_id(service_id);
|
---|
[18b6a88] | 1182 | return ENOTSUP;
|
---|
[f3504c1] | 1183 | }
|
---|
| 1184 | }
|
---|
| 1185 |
|
---|
[a86e4f8] | 1186 | rc = exfat_directory_close(&di);
|
---|
[0dbe5ac] | 1187 | if (rc != EOK) {
|
---|
[a86e4f8] | 1188 | free(rootp);
|
---|
| 1189 | free(bitmapp);
|
---|
| 1190 | free(uctablep);
|
---|
[375ab5e] | 1191 | (void) block_cache_fini(service_id);
|
---|
| 1192 | block_fini(service_id);
|
---|
| 1193 | exfat_idx_fini_by_service_id(service_id);
|
---|
[e97b8c6] | 1194 | return ENOMEM;
|
---|
[a86e4f8] | 1195 | }
|
---|
| 1196 |
|
---|
[e8975278] | 1197 | if (0)
|
---|
| 1198 | exfat_fsinfo(bs, service_id);
|
---|
[4c3c4a5] | 1199 |
|
---|
[f3504c1] | 1200 | *rrfn = FS_NODE(rootp);
|
---|
| 1201 | *rridxp = rootp->idx;
|
---|
| 1202 |
|
---|
| 1203 | if (info != NULL) {
|
---|
[3fafe5e0] | 1204 | #if 0
|
---|
| 1205 | str_cpy(info->label, FS_LABEL_MAXLEN + 1, label);
|
---|
| 1206 | #endif
|
---|
[f3504c1] | 1207 | }
|
---|
| 1208 |
|
---|
[e97b8c6] | 1209 | return EOK;
|
---|
[6d57e1c] | 1210 | }
|
---|
| 1211 |
|
---|
[f3504c1] | 1212 | static void exfat_fs_close(service_id_t service_id, fs_node_t *rfn)
|
---|
[6d57e1c] | 1213 | {
|
---|
[4c3c4a5] | 1214 | /*
|
---|
| 1215 | * Put the root node and force it to the FAT free node list.
|
---|
| 1216 | */
|
---|
[f3504c1] | 1217 | (void) exfat_node_put(rfn);
|
---|
| 1218 | (void) exfat_node_put(rfn);
|
---|
[6d57e1c] | 1219 |
|
---|
| 1220 | /*
|
---|
| 1221 | * Perform cleanup of the node structures, index structures and
|
---|
| 1222 | * associated data. Write back this file system's dirty blocks and
|
---|
| 1223 | * stop using libblock for this instance.
|
---|
| 1224 | */
|
---|
[375ab5e] | 1225 | (void) exfat_node_fini_by_service_id(service_id);
|
---|
| 1226 | exfat_idx_fini_by_service_id(service_id);
|
---|
| 1227 | (void) block_cache_fini(service_id);
|
---|
| 1228 | block_fini(service_id);
|
---|
[f3504c1] | 1229 | }
|
---|
| 1230 |
|
---|
| 1231 | /*
|
---|
| 1232 | * VFS_OUT operations.
|
---|
| 1233 | */
|
---|
| 1234 |
|
---|
| 1235 | /* Print debug info */
|
---|
[8a8771c] | 1236 | static void exfat_fsinfo(exfat_bs_t *bs, service_id_t service_id)
|
---|
[f3504c1] | 1237 | {
|
---|
| 1238 | printf("exFAT file system mounted\n");
|
---|
| 1239 | printf("Version: %d.%d\n", bs->version.major, bs->version.minor);
|
---|
| 1240 | printf("Volume serial: %d\n", uint32_t_le2host(bs->volume_serial));
|
---|
| 1241 | printf("Volume first sector: %lld\n", VOL_FS(bs));
|
---|
| 1242 | printf("Volume sectors: %lld\n", VOL_CNT(bs));
|
---|
| 1243 | printf("FAT first sector: %d\n", FAT_FS(bs));
|
---|
| 1244 | printf("FAT sectors: %d\n", FAT_CNT(bs));
|
---|
| 1245 | printf("Data first sector: %d\n", DATA_FS(bs));
|
---|
| 1246 | printf("Data sectors: %d\n", DATA_CNT(bs));
|
---|
| 1247 | printf("Root dir first cluster: %d\n", ROOT_FC(bs));
|
---|
| 1248 | printf("Bytes per sector: %d\n", BPS(bs));
|
---|
| 1249 | printf("Sectors per cluster: %d\n", SPC(bs));
|
---|
[e8975278] | 1250 | printf("KBytes per cluster: %d\n", SPC(bs) * BPS(bs) / 1024);
|
---|
[6d57e1c] | 1251 |
|
---|
[f3504c1] | 1252 | int i, rc;
|
---|
| 1253 | exfat_cluster_t clst;
|
---|
[e8975278] | 1254 | for (i = 0; i <= 7; i++) {
|
---|
[f3504c1] | 1255 | rc = exfat_get_cluster(bs, service_id, i, &clst);
|
---|
| 1256 | if (rc != EOK)
|
---|
| 1257 | return;
|
---|
| 1258 | printf("Clst %d: %x", i, clst);
|
---|
[8a8771c] | 1259 | if (i >= 2) {
|
---|
| 1260 | printf(", Bitmap: %d\n", exfat_bitmap_is_free(bs,
|
---|
| 1261 | service_id, i) != EOK);
|
---|
| 1262 | } else {
|
---|
[f3504c1] | 1263 | printf("\n");
|
---|
[8a8771c] | 1264 | }
|
---|
[f3504c1] | 1265 | }
|
---|
| 1266 | }
|
---|
| 1267 |
|
---|
[b7fd2a0] | 1268 | static errno_t exfat_fsprobe(service_id_t service_id, vfs_fs_probe_info_t *info)
|
---|
[f3504c1] | 1269 | {
|
---|
[b7fd2a0] | 1270 | errno_t rc;
|
---|
[f3504c1] | 1271 | exfat_idx_t *ridxp;
|
---|
| 1272 | fs_node_t *rfn;
|
---|
| 1273 |
|
---|
| 1274 | rc = exfat_fs_open(service_id, CACHE_MODE_WT, &rfn, &ridxp, info);
|
---|
| 1275 | if (rc != EOK)
|
---|
| 1276 | return rc;
|
---|
| 1277 |
|
---|
| 1278 | exfat_fs_close(service_id, rfn);
|
---|
| 1279 | return EOK;
|
---|
| 1280 | }
|
---|
| 1281 |
|
---|
[b7fd2a0] | 1282 | static errno_t
|
---|
[f3504c1] | 1283 | exfat_mounted(service_id_t service_id, const char *opts, fs_index_t *index,
|
---|
| 1284 | aoff64_t *size)
|
---|
| 1285 | {
|
---|
[b7fd2a0] | 1286 | errno_t rc;
|
---|
[f3504c1] | 1287 | enum cache_mode cmode;
|
---|
| 1288 | exfat_idx_t *ridxp;
|
---|
| 1289 | fs_node_t *rfn;
|
---|
| 1290 |
|
---|
| 1291 | /* Check for option enabling write through. */
|
---|
| 1292 | if (str_cmp(opts, "wtcache") == 0)
|
---|
| 1293 | cmode = CACHE_MODE_WT;
|
---|
| 1294 | else
|
---|
| 1295 | cmode = CACHE_MODE_WB;
|
---|
| 1296 |
|
---|
| 1297 | rc = exfat_fs_open(service_id, cmode, &rfn, &ridxp, NULL);
|
---|
| 1298 | if (rc != EOK)
|
---|
| 1299 | return rc;
|
---|
| 1300 |
|
---|
| 1301 | *index = ridxp->index;
|
---|
| 1302 | *size = EXFAT_NODE(rfn)->size;
|
---|
| 1303 |
|
---|
| 1304 | return EOK;
|
---|
| 1305 | }
|
---|
| 1306 |
|
---|
[b7fd2a0] | 1307 | static errno_t exfat_unmounted(service_id_t service_id)
|
---|
[f3504c1] | 1308 | {
|
---|
| 1309 | fs_node_t *rfn;
|
---|
[b7fd2a0] | 1310 | errno_t rc;
|
---|
[f3504c1] | 1311 |
|
---|
| 1312 | rc = exfat_root_get(&rfn, service_id);
|
---|
| 1313 | if (rc != EOK)
|
---|
| 1314 | return rc;
|
---|
| 1315 |
|
---|
| 1316 | exfat_fs_close(service_id, rfn);
|
---|
[e97b8c6] | 1317 | return EOK;
|
---|
[6d57e1c] | 1318 | }
|
---|
[db9aa04] | 1319 |
|
---|
[b7fd2a0] | 1320 | static errno_t
|
---|
[375ab5e] | 1321 | exfat_read(service_id_t service_id, fs_index_t index, aoff64_t pos,
|
---|
[e97b8c6] | 1322 | size_t *rbytes)
|
---|
[36c2679] | 1323 | {
|
---|
| 1324 | fs_node_t *fn;
|
---|
| 1325 | exfat_node_t *nodep;
|
---|
| 1326 | exfat_bs_t *bs;
|
---|
[0dbe5ac] | 1327 | size_t bytes = 0;
|
---|
[36c2679] | 1328 | block_t *b;
|
---|
[b7fd2a0] | 1329 | errno_t rc;
|
---|
[36c2679] | 1330 |
|
---|
[375ab5e] | 1331 | rc = exfat_node_get(&fn, service_id, index);
|
---|
[e97b8c6] | 1332 | if (rc != EOK)
|
---|
| 1333 | return rc;
|
---|
| 1334 | if (!fn)
|
---|
| 1335 | return ENOENT;
|
---|
[36c2679] | 1336 | nodep = EXFAT_NODE(fn);
|
---|
| 1337 |
|
---|
[984a9ba] | 1338 | ipc_call_t call;
|
---|
[36c2679] | 1339 | size_t len;
|
---|
[984a9ba] | 1340 | if (!async_data_read_receive(&call, &len)) {
|
---|
[36c2679] | 1341 | exfat_node_put(fn);
|
---|
[984a9ba] | 1342 | async_answer_0(&call, EINVAL);
|
---|
[e97b8c6] | 1343 | return EINVAL;
|
---|
[36c2679] | 1344 | }
|
---|
| 1345 |
|
---|
[375ab5e] | 1346 | bs = block_bb_get(service_id);
|
---|
[36c2679] | 1347 |
|
---|
| 1348 | if (nodep->type == EXFAT_FILE) {
|
---|
| 1349 | /*
|
---|
| 1350 | * Our strategy for regular file reads is to read one block at
|
---|
| 1351 | * most and make use of the possibility to return less data than
|
---|
| 1352 | * requested. This keeps the code very simple.
|
---|
| 1353 | */
|
---|
| 1354 | if (pos >= nodep->size) {
|
---|
| 1355 | /* reading beyond the EOF */
|
---|
| 1356 | bytes = 0;
|
---|
[984a9ba] | 1357 | (void) async_data_read_finalize(&call, NULL, 0);
|
---|
[36c2679] | 1358 | } else {
|
---|
| 1359 | bytes = min(len, BPS(bs) - pos % BPS(bs));
|
---|
| 1360 | bytes = min(bytes, nodep->size - pos);
|
---|
| 1361 | rc = exfat_block_get(&b, bs, nodep, pos / BPS(bs),
|
---|
| 1362 | BLOCK_FLAGS_NONE);
|
---|
| 1363 | if (rc != EOK) {
|
---|
| 1364 | exfat_node_put(fn);
|
---|
[984a9ba] | 1365 | async_answer_0(&call, rc);
|
---|
[e97b8c6] | 1366 | return rc;
|
---|
[36c2679] | 1367 | }
|
---|
[984a9ba] | 1368 | (void) async_data_read_finalize(&call,
|
---|
[36c2679] | 1369 | b->data + pos % BPS(bs), bytes);
|
---|
| 1370 | rc = block_put(b);
|
---|
| 1371 | if (rc != EOK) {
|
---|
| 1372 | exfat_node_put(fn);
|
---|
[e97b8c6] | 1373 | return rc;
|
---|
[36c2679] | 1374 | }
|
---|
| 1375 | }
|
---|
| 1376 | } else {
|
---|
| 1377 | if (nodep->type != EXFAT_DIRECTORY) {
|
---|
[984a9ba] | 1378 | async_answer_0(&call, ENOTSUP);
|
---|
[e97b8c6] | 1379 | return ENOTSUP;
|
---|
[36c2679] | 1380 | }
|
---|
[f3504c1] | 1381 |
|
---|
[36c2679] | 1382 | aoff64_t spos = pos;
|
---|
[0dbe5ac] | 1383 | char name[EXFAT_FILENAME_LEN + 1];
|
---|
[36c2679] | 1384 | exfat_file_dentry_t df;
|
---|
| 1385 | exfat_stream_dentry_t ds;
|
---|
| 1386 |
|
---|
| 1387 | assert(nodep->size % BPS(bs) == 0);
|
---|
| 1388 | assert(BPS(bs) % sizeof(exfat_dentry_t) == 0);
|
---|
| 1389 |
|
---|
| 1390 | exfat_directory_t di;
|
---|
| 1391 | rc = exfat_directory_open(nodep, &di);
|
---|
[76da580a] | 1392 | if (rc != EOK)
|
---|
| 1393 | goto err;
|
---|
| 1394 |
|
---|
[36c2679] | 1395 | rc = exfat_directory_seek(&di, pos);
|
---|
| 1396 | if (rc != EOK) {
|
---|
| 1397 | (void) exfat_directory_close(&di);
|
---|
| 1398 | goto err;
|
---|
| 1399 | }
|
---|
| 1400 |
|
---|
[0dbe5ac] | 1401 | rc = exfat_directory_read_file(&di, name, EXFAT_FILENAME_LEN,
|
---|
| 1402 | &df, &ds);
|
---|
| 1403 | if (rc == EOK)
|
---|
[76da580a] | 1404 | goto hit;
|
---|
[6722766] | 1405 | else if (rc == ENOENT)
|
---|
[76da580a] | 1406 | goto miss;
|
---|
[36c2679] | 1407 |
|
---|
[6722766] | 1408 | (void) exfat_directory_close(&di);
|
---|
| 1409 |
|
---|
[18b6a88] | 1410 | err:
|
---|
[36c2679] | 1411 | (void) exfat_node_put(fn);
|
---|
[984a9ba] | 1412 | async_answer_0(&call, rc);
|
---|
[e97b8c6] | 1413 | return rc;
|
---|
[36c2679] | 1414 |
|
---|
[18b6a88] | 1415 | miss:
|
---|
[36c2679] | 1416 | rc = exfat_directory_close(&di);
|
---|
[76da580a] | 1417 | if (rc != EOK)
|
---|
[36c2679] | 1418 | goto err;
|
---|
| 1419 | rc = exfat_node_put(fn);
|
---|
[984a9ba] | 1420 | async_answer_0(&call, rc != EOK ? rc : ENOENT);
|
---|
[e97b8c6] | 1421 | *rbytes = 0;
|
---|
| 1422 | return rc != EOK ? rc : ENOENT;
|
---|
[36c2679] | 1423 |
|
---|
[18b6a88] | 1424 | hit:
|
---|
[36c2679] | 1425 | pos = di.pos;
|
---|
| 1426 | rc = exfat_directory_close(&di);
|
---|
[0dbe5ac] | 1427 | if (rc != EOK)
|
---|
[36c2679] | 1428 | goto err;
|
---|
[984a9ba] | 1429 | (void) async_data_read_finalize(&call, name,
|
---|
[0dbe5ac] | 1430 | str_size(name) + 1);
|
---|
| 1431 | bytes = (pos - spos) + 1;
|
---|
[36c2679] | 1432 | }
|
---|
| 1433 |
|
---|
| 1434 | rc = exfat_node_put(fn);
|
---|
[e97b8c6] | 1435 | *rbytes = bytes;
|
---|
| 1436 | return rc;
|
---|
[f6641d2] | 1437 | }
|
---|
| 1438 |
|
---|
[b7fd2a0] | 1439 | static errno_t exfat_close(service_id_t service_id, fs_index_t index)
|
---|
[d8634a79] | 1440 | {
|
---|
[e97b8c6] | 1441 | return EOK;
|
---|
[d8634a79] | 1442 | }
|
---|
| 1443 |
|
---|
[b7fd2a0] | 1444 | static errno_t exfat_sync(service_id_t service_id, fs_index_t index)
|
---|
[6f60727] | 1445 | {
|
---|
| 1446 | fs_node_t *fn;
|
---|
[b7fd2a0] | 1447 | errno_t rc = exfat_node_get(&fn, service_id, index);
|
---|
[e97b8c6] | 1448 | if (rc != EOK)
|
---|
| 1449 | return rc;
|
---|
| 1450 | if (!fn)
|
---|
| 1451 | return ENOENT;
|
---|
[6f60727] | 1452 |
|
---|
| 1453 | exfat_node_t *nodep = EXFAT_NODE(fn);
|
---|
| 1454 |
|
---|
| 1455 | nodep->dirty = true;
|
---|
| 1456 | rc = exfat_node_sync(nodep);
|
---|
| 1457 |
|
---|
| 1458 | exfat_node_put(fn);
|
---|
[e97b8c6] | 1459 | return rc;
|
---|
| 1460 | }
|
---|
| 1461 |
|
---|
[b7fd2a0] | 1462 | static errno_t
|
---|
[375ab5e] | 1463 | exfat_write(service_id_t service_id, fs_index_t index, aoff64_t pos,
|
---|
[e97b8c6] | 1464 | size_t *wbytes, aoff64_t *nsize)
|
---|
| 1465 | {
|
---|
[998a78f] | 1466 | fs_node_t *fn;
|
---|
| 1467 | exfat_node_t *nodep;
|
---|
| 1468 | exfat_bs_t *bs;
|
---|
| 1469 | size_t bytes;
|
---|
| 1470 | block_t *b;
|
---|
| 1471 | aoff64_t boundary;
|
---|
| 1472 | int flags = BLOCK_FLAGS_NONE;
|
---|
[b7fd2a0] | 1473 | errno_t rc;
|
---|
[998a78f] | 1474 |
|
---|
[375ab5e] | 1475 | rc = exfat_node_get(&fn, service_id, index);
|
---|
[998a78f] | 1476 | if (rc != EOK)
|
---|
| 1477 | return rc;
|
---|
| 1478 | if (!fn)
|
---|
| 1479 | return ENOENT;
|
---|
| 1480 | nodep = EXFAT_NODE(fn);
|
---|
| 1481 |
|
---|
[984a9ba] | 1482 | ipc_call_t call;
|
---|
[998a78f] | 1483 | size_t len;
|
---|
[984a9ba] | 1484 | if (!async_data_write_receive(&call, &len)) {
|
---|
[998a78f] | 1485 | (void) exfat_node_put(fn);
|
---|
[984a9ba] | 1486 | async_answer_0(&call, EINVAL);
|
---|
[998a78f] | 1487 | return EINVAL;
|
---|
| 1488 | }
|
---|
| 1489 |
|
---|
[375ab5e] | 1490 | bs = block_bb_get(service_id);
|
---|
[998a78f] | 1491 |
|
---|
| 1492 | /*
|
---|
| 1493 | * In all scenarios, we will attempt to write out only one block worth
|
---|
| 1494 | * of data at maximum. There might be some more efficient approaches,
|
---|
| 1495 | * but this one greatly simplifies fat_write(). Note that we can afford
|
---|
| 1496 | * to do this because the client must be ready to handle the return
|
---|
| 1497 | * value signalizing a smaller number of bytes written.
|
---|
| 1498 | */
|
---|
| 1499 | bytes = min(len, BPS(bs) - pos % BPS(bs));
|
---|
| 1500 | if (bytes == BPS(bs))
|
---|
| 1501 | flags |= BLOCK_FLAGS_NOREAD;
|
---|
| 1502 |
|
---|
| 1503 | boundary = ROUND_UP(nodep->size, BPC(bs));
|
---|
| 1504 | if (pos >= boundary) {
|
---|
| 1505 | unsigned nclsts;
|
---|
| 1506 | nclsts = (ROUND_UP(pos + bytes, BPC(bs)) - boundary) / BPC(bs);
|
---|
[375ab5e] | 1507 | rc = exfat_node_expand(service_id, nodep, nclsts);
|
---|
[998a78f] | 1508 | if (rc != EOK) {
|
---|
| 1509 | /* could not expand node */
|
---|
| 1510 | (void) exfat_node_put(fn);
|
---|
[984a9ba] | 1511 | async_answer_0(&call, rc);
|
---|
[998a78f] | 1512 | return rc;
|
---|
| 1513 | }
|
---|
| 1514 | }
|
---|
| 1515 |
|
---|
| 1516 | if (pos + bytes > nodep->size) {
|
---|
| 1517 | nodep->size = pos + bytes;
|
---|
| 1518 | nodep->dirty = true; /* need to sync node */
|
---|
| 1519 | }
|
---|
| 1520 |
|
---|
| 1521 | /*
|
---|
| 1522 | * This is the easier case - we are either overwriting already
|
---|
| 1523 | * existing contents or writing behind the EOF, but still within
|
---|
| 1524 | * the limits of the last cluster. The node size may grow to the
|
---|
| 1525 | * next block size boundary.
|
---|
| 1526 | */
|
---|
| 1527 | rc = exfat_block_get(&b, bs, nodep, pos / BPS(bs), flags);
|
---|
| 1528 | if (rc != EOK) {
|
---|
| 1529 | (void) exfat_node_put(fn);
|
---|
[984a9ba] | 1530 | async_answer_0(&call, rc);
|
---|
[998a78f] | 1531 | return rc;
|
---|
| 1532 | }
|
---|
| 1533 |
|
---|
[984a9ba] | 1534 | (void) async_data_write_finalize(&call,
|
---|
[76da580a] | 1535 | b->data + pos % BPS(bs), bytes);
|
---|
[998a78f] | 1536 | b->dirty = true; /* need to sync block */
|
---|
| 1537 | rc = block_put(b);
|
---|
| 1538 | if (rc != EOK) {
|
---|
| 1539 | (void) exfat_node_put(fn);
|
---|
| 1540 | return rc;
|
---|
| 1541 | }
|
---|
| 1542 |
|
---|
| 1543 | *wbytes = bytes;
|
---|
| 1544 | *nsize = nodep->size;
|
---|
| 1545 | rc = exfat_node_put(fn);
|
---|
| 1546 | return rc;
|
---|
[e97b8c6] | 1547 | }
|
---|
| 1548 |
|
---|
[b7fd2a0] | 1549 | static errno_t
|
---|
[375ab5e] | 1550 | exfat_truncate(service_id_t service_id, fs_index_t index, aoff64_t size)
|
---|
[e97b8c6] | 1551 | {
|
---|
[998a78f] | 1552 | fs_node_t *fn;
|
---|
| 1553 | exfat_node_t *nodep;
|
---|
| 1554 | exfat_bs_t *bs;
|
---|
[b7fd2a0] | 1555 | errno_t rc;
|
---|
[998a78f] | 1556 |
|
---|
[375ab5e] | 1557 | rc = exfat_node_get(&fn, service_id, index);
|
---|
[998a78f] | 1558 | if (rc != EOK)
|
---|
| 1559 | return rc;
|
---|
| 1560 | if (!fn)
|
---|
| 1561 | return ENOENT;
|
---|
| 1562 | nodep = EXFAT_NODE(fn);
|
---|
| 1563 |
|
---|
[375ab5e] | 1564 | bs = block_bb_get(service_id);
|
---|
[998a78f] | 1565 |
|
---|
| 1566 | if (nodep->size == size) {
|
---|
| 1567 | rc = EOK;
|
---|
| 1568 | } else if (nodep->size < size) {
|
---|
| 1569 | /*
|
---|
| 1570 | * The standard says we have the freedom to grow the node.
|
---|
| 1571 | * For now, we simply return an error.
|
---|
| 1572 | */
|
---|
| 1573 | rc = EINVAL;
|
---|
| 1574 | } else if (ROUND_UP(nodep->size, BPC(bs)) == ROUND_UP(size, BPC(bs))) {
|
---|
| 1575 | /*
|
---|
| 1576 | * The node will be shrunk, but no clusters will be deallocated.
|
---|
| 1577 | */
|
---|
| 1578 | nodep->size = size;
|
---|
| 1579 | nodep->dirty = true; /* need to sync node */
|
---|
| 1580 | rc = EOK;
|
---|
| 1581 | } else {
|
---|
[375ab5e] | 1582 | rc = exfat_node_shrink(service_id, nodep, size);
|
---|
[998a78f] | 1583 | }
|
---|
| 1584 |
|
---|
[b7fd2a0] | 1585 | errno_t rc2 = exfat_node_put(fn);
|
---|
[7cede12c] | 1586 | if (rc == EOK && rc2 != EOK)
|
---|
| 1587 | rc = rc2;
|
---|
| 1588 |
|
---|
[998a78f] | 1589 | return rc;
|
---|
[e97b8c6] | 1590 | }
|
---|
[998a78f] | 1591 |
|
---|
[b7fd2a0] | 1592 | static errno_t exfat_destroy(service_id_t service_id, fs_index_t index)
|
---|
[e97b8c6] | 1593 | {
|
---|
[998a78f] | 1594 | fs_node_t *fn;
|
---|
| 1595 | exfat_node_t *nodep;
|
---|
[b7fd2a0] | 1596 | errno_t rc;
|
---|
[998a78f] | 1597 |
|
---|
[375ab5e] | 1598 | rc = exfat_node_get(&fn, service_id, index);
|
---|
[998a78f] | 1599 | if (rc != EOK)
|
---|
| 1600 | return rc;
|
---|
| 1601 | if (!fn)
|
---|
| 1602 | return ENOENT;
|
---|
| 1603 |
|
---|
| 1604 | nodep = EXFAT_NODE(fn);
|
---|
| 1605 | /*
|
---|
| 1606 | * We should have exactly two references. One for the above
|
---|
| 1607 | * call to fat_node_get() and one from fat_unlink().
|
---|
| 1608 | */
|
---|
| 1609 | assert(nodep->refcnt == 2);
|
---|
| 1610 |
|
---|
| 1611 | rc = exfat_destroy_node(fn);
|
---|
| 1612 | return rc;
|
---|
[6f60727] | 1613 | }
|
---|
| 1614 |
|
---|
[e97b8c6] | 1615 | vfs_out_ops_t exfat_ops = {
|
---|
[d2c8533] | 1616 | .fsprobe = exfat_fsprobe,
|
---|
[e97b8c6] | 1617 | .mounted = exfat_mounted,
|
---|
| 1618 | .unmounted = exfat_unmounted,
|
---|
| 1619 | .read = exfat_read,
|
---|
| 1620 | .write = exfat_write,
|
---|
| 1621 | .truncate = exfat_truncate,
|
---|
| 1622 | .close = exfat_close,
|
---|
| 1623 | .destroy = exfat_destroy,
|
---|
| 1624 | .sync = exfat_sync,
|
---|
| 1625 | };
|
---|
| 1626 |
|
---|
[db9aa04] | 1627 | /**
|
---|
| 1628 | * @}
|
---|
| 1629 | */
|
---|