36,8 → 36,11 |
*/ |
|
#include "fat.h" |
#include "fat_dentry.h" |
#include "fat_fat.h" |
#include "../../vfs/vfs.h" |
#include <libfs.h> |
#include <libblock.h> |
#include <ipc/ipc.h> |
#include <ipc/services.h> |
#include <ipc/devmap.h> |
50,10 → 53,8 |
#include <assert.h> |
#include <futex.h> |
#include <sys/mman.h> |
#include <align.h> |
|
#define BS_BLOCK 0 |
#define BS_SIZE 512 |
|
/** Futex protecting the list of cached free FAT nodes. */ |
static futex_t ffn_futex = FUTEX_INITIALIZER; |
|
60,216 → 61,6 |
/** List of cached free FAT nodes. */ |
static LIST_INITIALIZE(ffn_head); |
|
#define FAT_NAME_LEN 8 |
#define FAT_EXT_LEN 3 |
|
#define FAT_PAD ' ' |
|
#define FAT_DENTRY_UNUSED 0x00 |
#define FAT_DENTRY_E5_ESC 0x05 |
#define FAT_DENTRY_DOT 0x2e |
#define FAT_DENTRY_ERASED 0xe5 |
|
#define min(a, b) ((a) < (b) ? (a) : (b)) |
|
static void dentry_name_canonify(fat_dentry_t *d, char *buf) |
{ |
int i; |
|
for (i = 0; i < FAT_NAME_LEN; i++) { |
if (d->name[i] == FAT_PAD) |
break; |
if (d->name[i] == FAT_DENTRY_E5_ESC) |
*buf++ = 0xe5; |
else |
*buf++ = d->name[i]; |
} |
if (d->ext[0] != FAT_PAD) |
*buf++ = '.'; |
for (i = 0; i < FAT_EXT_LEN; i++) { |
if (d->ext[i] == FAT_PAD) { |
*buf = '\0'; |
return; |
} |
if (d->ext[i] == FAT_DENTRY_E5_ESC) |
*buf++ = 0xe5; |
else |
*buf++ = d->ext[i]; |
} |
*buf = '\0'; |
} |
|
static int dev_phone = -1; /* FIXME */ |
static void *dev_buffer = NULL; /* FIXME */ |
|
/* TODO move somewhere else */ |
typedef struct { |
void *data; |
size_t size; |
} block_t; |
|
static block_t *block_get(dev_handle_t dev_handle, off_t offset, size_t bs) |
{ |
/* FIXME */ |
block_t *b; |
off_t bufpos = 0; |
size_t buflen = 0; |
off_t pos = offset * bs; |
|
assert(dev_phone != -1); |
assert(dev_buffer); |
|
b = malloc(sizeof(block_t)); |
if (!b) |
return NULL; |
|
b->data = malloc(bs); |
if (!b->data) { |
free(b); |
return NULL; |
} |
b->size = bs; |
|
if (!libfs_blockread(dev_phone, dev_buffer, &bufpos, &buflen, &pos, |
b->data, bs, bs)) { |
free(b->data); |
free(b); |
return NULL; |
} |
|
return b; |
} |
|
static void block_put(block_t *block) |
{ |
/* FIXME */ |
free(block->data); |
free(block); |
} |
|
#define FAT_BS(b) ((fat_bs_t *)((b)->data)) |
|
#define FAT_CLST_RES0 0x0000 |
#define FAT_CLST_RES1 0x0001 |
#define FAT_CLST_FIRST 0x0002 |
#define FAT_CLST_BAD 0xfff7 |
#define FAT_CLST_LAST1 0xfff8 |
#define FAT_CLST_LAST8 0xffff |
|
/* internally used to mark root directory's parent */ |
#define FAT_CLST_ROOTPAR FAT_CLST_RES0 |
/* internally used to mark root directory */ |
#define FAT_CLST_ROOT FAT_CLST_RES1 |
|
#define fat_block_get(np, off) \ |
_fat_block_get((np)->idx->dev_handle, (np)->firstc, (off)) |
|
static block_t * |
_fat_block_get(dev_handle_t dev_handle, fat_cluster_t firstc, off_t offset) |
{ |
block_t *bb; |
block_t *b; |
unsigned bps; |
unsigned spc; |
unsigned rscnt; /* block address of the first FAT */ |
unsigned fatcnt; |
unsigned rde; |
unsigned rds; /* root directory size */ |
unsigned sf; |
unsigned ssa; /* size of the system area */ |
unsigned clusters; |
fat_cluster_t clst = firstc; |
unsigned i; |
|
bb = block_get(dev_handle, BS_BLOCK, BS_SIZE); |
bps = uint16_t_le2host(FAT_BS(bb)->bps); |
spc = FAT_BS(bb)->spc; |
rscnt = uint16_t_le2host(FAT_BS(bb)->rscnt); |
fatcnt = FAT_BS(bb)->fatcnt; |
rde = uint16_t_le2host(FAT_BS(bb)->root_ent_max); |
sf = uint16_t_le2host(FAT_BS(bb)->sec_per_fat); |
block_put(bb); |
|
rds = (sizeof(fat_dentry_t) * rde) / bps; |
rds += ((sizeof(fat_dentry_t) * rde) % bps != 0); |
ssa = rscnt + fatcnt * sf + rds; |
|
if (firstc == FAT_CLST_ROOT) { |
/* root directory special case */ |
assert(offset < rds); |
b = block_get(dev_handle, rscnt + fatcnt * sf + offset, bps); |
return b; |
} |
|
clusters = offset / spc; |
for (i = 0; i < clusters; i++) { |
unsigned fsec; /* sector offset relative to FAT1 */ |
unsigned fidx; /* FAT1 entry index */ |
|
assert(clst >= FAT_CLST_FIRST && clst < FAT_CLST_BAD); |
fsec = (clst * sizeof(fat_cluster_t)) / bps; |
fidx = clst % (bps / sizeof(fat_cluster_t)); |
/* read FAT1 */ |
b = block_get(dev_handle, rscnt + fsec, bps); |
clst = uint16_t_le2host(((fat_cluster_t *)b->data)[fidx]); |
assert(clst != FAT_CLST_BAD); |
assert(clst < FAT_CLST_LAST1); |
block_put(b); |
} |
|
b = block_get(dev_handle, ssa + (clst - FAT_CLST_FIRST) * spc + |
offset % spc, bps); |
|
return b; |
} |
|
/** Return number of blocks allocated to a file. |
* |
* @param dev_handle Device handle of the device with the file. |
* @param firstc First cluster of the file. |
* |
* @return Number of blocks allocated to the file. |
*/ |
static uint16_t |
_fat_blcks_get(dev_handle_t dev_handle, fat_cluster_t firstc) |
{ |
block_t *bb; |
block_t *b; |
unsigned bps; |
unsigned spc; |
unsigned rscnt; /* block address of the first FAT */ |
unsigned clusters = 0; |
fat_cluster_t clst = firstc; |
|
bb = block_get(dev_handle, BS_BLOCK, BS_SIZE); |
bps = uint16_t_le2host(FAT_BS(bb)->bps); |
spc = FAT_BS(bb)->spc; |
rscnt = uint16_t_le2host(FAT_BS(bb)->rscnt); |
block_put(bb); |
|
if (firstc == FAT_CLST_RES0) { |
/* No space allocated to the file. */ |
return 0; |
} |
|
while (clst < FAT_CLST_LAST1) { |
unsigned fsec; /* sector offset relative to FAT1 */ |
unsigned fidx; /* FAT1 entry index */ |
|
assert(clst >= FAT_CLST_FIRST); |
fsec = (clst * sizeof(fat_cluster_t)) / bps; |
fidx = clst % (bps / sizeof(fat_cluster_t)); |
/* read FAT1 */ |
b = block_get(dev_handle, rscnt + fsec, bps); |
clst = uint16_t_le2host(((fat_cluster_t *)b->data)[fidx]); |
assert(clst != FAT_CLST_BAD); |
block_put(b); |
clusters++; |
} |
|
return clusters * spc; |
} |
|
static void fat_node_initialize(fat_node_t *node) |
{ |
futex_initialize(&node->lock, 1); |
282,54 → 73,35 |
node->dirty = false; |
} |
|
static uint16_t fat_bps_get(dev_handle_t dev_handle) |
static void fat_node_sync(fat_node_t *node) |
{ |
block_t *bb; |
block_t *b; |
fat_bs_t *bs; |
fat_dentry_t *d; |
uint16_t bps; |
unsigned dps; |
|
bb = block_get(dev_handle, BS_BLOCK, BS_SIZE); |
assert(bb != NULL); |
bps = uint16_t_le2host(FAT_BS(bb)->bps); |
block_put(bb); |
assert(node->dirty); |
|
return bps; |
} |
bs = block_bb_get(node->idx->dev_handle); |
bps = uint16_t_le2host(bs->bps); |
dps = bps / sizeof(fat_dentry_t); |
|
/* Read the block that contains the dentry of interest. */ |
b = _fat_block_get(bs, node->idx->dev_handle, node->idx->pfc, |
(node->idx->pdi * sizeof(fat_dentry_t)) / bps); |
|
typedef enum { |
FAT_DENTRY_SKIP, |
FAT_DENTRY_LAST, |
FAT_DENTRY_VALID |
} fat_dentry_clsf_t; |
d = ((fat_dentry_t *)b->data) + (node->idx->pdi % dps); |
|
static fat_dentry_clsf_t fat_classify_dentry(fat_dentry_t *d) |
{ |
if (d->attr & FAT_ATTR_VOLLABEL) { |
/* volume label entry */ |
return FAT_DENTRY_SKIP; |
} |
if (d->name[0] == FAT_DENTRY_ERASED) { |
/* not-currently-used entry */ |
return FAT_DENTRY_SKIP; |
} |
if (d->name[0] == FAT_DENTRY_UNUSED) { |
/* never used entry */ |
return FAT_DENTRY_LAST; |
} |
if (d->name[0] == FAT_DENTRY_DOT) { |
/* |
* Most likely '.' or '..'. |
* It cannot occur in a regular file name. |
*/ |
return FAT_DENTRY_SKIP; |
} |
return FAT_DENTRY_VALID; |
d->firstc = host2uint16_t_le(node->firstc); |
if (node->type == FAT_FILE) |
d->size = host2uint32_t_le(node->size); |
/* TODO: update other fields? (e.g time fields, attr field) */ |
|
b->dirty = true; /* need to sync block */ |
block_put(b); |
} |
|
static void fat_node_sync(fat_node_t *node) |
{ |
/* TODO */ |
} |
|
/** Internal version of fat_node_get(). |
* |
* @param idxp Locked index structure. |
337,6 → 109,7 |
static void *fat_node_get_core(fat_idx_t *idxp) |
{ |
block_t *b; |
fat_bs_t *bs; |
fat_dentry_t *d; |
fat_node_t *nodep = NULL; |
unsigned bps; |
389,11 → 162,12 |
} |
fat_node_initialize(nodep); |
|
bps = fat_bps_get(idxp->dev_handle); |
bs = block_bb_get(idxp->dev_handle); |
bps = uint16_t_le2host(bs->bps); |
dps = bps / sizeof(fat_dentry_t); |
|
/* Read the block that contains the dentry of interest. */ |
b = _fat_block_get(idxp->dev_handle, idxp->pfc, |
b = _fat_block_get(bs, idxp->dev_handle, idxp->pfc, |
(idxp->pdi * sizeof(fat_dentry_t)) / bps); |
assert(b); |
|
410,8 → 184,8 |
* defined for the directory entry type. We must determine the |
* size of the directory by walking the FAT. |
*/ |
nodep->size = bps * _fat_blcks_get(idxp->dev_handle, |
uint16_t_le2host(d->firstc)); |
nodep->size = bps * _fat_blcks_get(bs, idxp->dev_handle, |
uint16_t_le2host(d->firstc), NULL); |
} else { |
nodep->type = FAT_FILE; |
nodep->size = uint32_t_le2host(d->size); |
479,6 → 253,7 |
|
static void *fat_match(void *prnt, const char *component) |
{ |
fat_bs_t *bs; |
fat_node_t *parentp = (fat_node_t *)prnt; |
char name[FAT_NAME_LEN + 1 + FAT_EXT_LEN + 1]; |
unsigned i, j; |
489,17 → 264,13 |
block_t *b; |
|
futex_down(&parentp->idx->lock); |
bps = fat_bps_get(parentp->idx->dev_handle); |
bs = block_bb_get(parentp->idx->dev_handle); |
bps = uint16_t_le2host(bs->bps); |
dps = bps / sizeof(fat_dentry_t); |
blocks = parentp->size / bps + (parentp->size % bps != 0); |
blocks = parentp->size / bps; |
for (i = 0; i < blocks; i++) { |
unsigned dentries; |
|
b = fat_block_get(parentp, i); |
dentries = (i == blocks - 1) ? |
parentp->size % sizeof(fat_dentry_t) : |
dps; |
for (j = 0; j < dentries; j++) { |
b = fat_block_get(bs, parentp, i); |
for (j = 0; j < dps; j++) { |
d = ((fat_dentry_t *)b->data) + j; |
switch (fat_classify_dentry(d)) { |
case FAT_DENTRY_SKIP: |
542,6 → 313,7 |
} |
block_put(b); |
} |
|
futex_up(&parentp->idx->lock); |
return NULL; |
} |
566,6 → 338,7 |
|
static bool fat_has_children(void *node) |
{ |
fat_bs_t *bs; |
fat_node_t *nodep = (fat_node_t *)node; |
unsigned bps; |
unsigned dps; |
575,22 → 348,19 |
|
if (nodep->type != FAT_DIRECTORY) |
return false; |
|
|
futex_down(&nodep->idx->lock); |
bps = fat_bps_get(nodep->idx->dev_handle); |
bs = block_bb_get(nodep->idx->dev_handle); |
bps = uint16_t_le2host(bs->bps); |
dps = bps / sizeof(fat_dentry_t); |
|
blocks = nodep->size / bps + (nodep->size % bps != 0); |
blocks = nodep->size / bps; |
|
for (i = 0; i < blocks; i++) { |
unsigned dentries; |
fat_dentry_t *d; |
|
b = fat_block_get(nodep, i); |
dentries = (i == blocks - 1) ? |
nodep->size % sizeof(fat_dentry_t) : |
dps; |
for (j = 0; j < dentries; j++) { |
b = fat_block_get(bs, nodep, i); |
for (j = 0; j < dps; j++) { |
d = ((fat_dentry_t *)b->data) + j; |
switch (fat_classify_dentry(d)) { |
case FAT_DENTRY_SKIP: |
658,50 → 428,27 |
void fat_mounted(ipc_callid_t rid, ipc_call_t *request) |
{ |
dev_handle_t dev_handle = (dev_handle_t) IPC_GET_ARG1(*request); |
block_t *bb; |
fat_bs_t *bs; |
uint16_t bps; |
uint16_t rde; |
int rc; |
|
/* |
* For now, we don't bother to remember dev_handle, dev_phone or |
* dev_buffer in some data structure. We use global variables because we |
* know there will be at most one mount on this file system. |
* Of course, this is a huge TODO item. |
*/ |
dev_buffer = mmap(NULL, BS_SIZE, PROTO_READ | PROTO_WRITE, |
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); |
|
if (!dev_buffer) { |
ipc_answer_0(rid, ENOMEM); |
return; |
} |
|
dev_phone = ipc_connect_me_to(PHONE_NS, SERVICE_DEVMAP, |
DEVMAP_CONNECT_TO_DEVICE, dev_handle); |
|
if (dev_phone < 0) { |
munmap(dev_buffer, BS_SIZE); |
ipc_answer_0(rid, dev_phone); |
return; |
} |
|
rc = ipc_share_out_start(dev_phone, dev_buffer, |
AS_AREA_READ | AS_AREA_WRITE); |
/* initialize libblock */ |
rc = block_init(dev_handle, BS_SIZE, BS_BLOCK * BS_SIZE, BS_SIZE); |
if (rc != EOK) { |
munmap(dev_buffer, BS_SIZE); |
ipc_answer_0(rid, rc); |
ipc_answer_0(rid, 0); |
return; |
} |
|
/* get the buffer with the boot sector */ |
bs = block_bb_get(dev_handle); |
|
/* Read the number of root directory entries. */ |
bb = block_get(dev_handle, BS_BLOCK, BS_SIZE); |
bps = uint16_t_le2host(FAT_BS(bb)->bps); |
rde = uint16_t_le2host(FAT_BS(bb)->root_ent_max); |
block_put(bb); |
bps = uint16_t_le2host(bs->bps); |
rde = uint16_t_le2host(bs->root_ent_max); |
|
if (bps != BS_SIZE) { |
munmap(dev_buffer, BS_SIZE); |
block_fini(dev_handle); |
ipc_answer_0(rid, ENOTSUP); |
return; |
} |
708,7 → 455,7 |
|
rc = fat_idx_init_by_dev_handle(dev_handle); |
if (rc != EOK) { |
munmap(dev_buffer, BS_SIZE); |
block_fini(dev_handle); |
ipc_answer_0(rid, rc); |
return; |
} |
716,7 → 463,7 |
/* Initialize the root node. */ |
fat_node_t *rootp = (fat_node_t *)malloc(sizeof(fat_node_t)); |
if (!rootp) { |
munmap(dev_buffer, BS_SIZE); |
block_fini(dev_handle); |
fat_idx_fini_by_dev_handle(dev_handle); |
ipc_answer_0(rid, ENOMEM); |
return; |
725,7 → 472,7 |
|
fat_idx_t *ridxp = fat_idx_get_by_pos(dev_handle, FAT_CLST_ROOTPAR, 0); |
if (!ridxp) { |
munmap(dev_buffer, BS_SIZE); |
block_fini(dev_handle); |
free(rootp); |
fat_idx_fini_by_dev_handle(dev_handle); |
ipc_answer_0(rid, ENOMEM); |
763,7 → 510,8 |
fs_index_t index = (fs_index_t)IPC_GET_ARG2(*request); |
off_t pos = (off_t)IPC_GET_ARG3(*request); |
fat_node_t *nodep = (fat_node_t *)fat_node_get(dev_handle, index); |
uint16_t bps = fat_bps_get(dev_handle); |
fat_bs_t *bs; |
uint16_t bps; |
size_t bytes; |
block_t *b; |
|
781,6 → 529,9 |
return; |
} |
|
bs = block_bb_get(dev_handle); |
bps = uint16_t_le2host(bs->bps); |
|
if (nodep->type == FAT_FILE) { |
/* |
* Our strategy for regular file reads is to read one block at |
787,11 → 538,18 |
* most and make use of the possibility to return less data than |
* requested. This keeps the code very simple. |
*/ |
bytes = min(len, bps - pos % bps); |
b = fat_block_get(nodep, pos / bps); |
(void) ipc_data_read_finalize(callid, b->data + pos % bps, |
bytes); |
block_put(b); |
if (pos >= nodep->size) { |
/* reading beyond the EOF */ |
bytes = 0; |
(void) ipc_data_read_finalize(callid, NULL, 0); |
} else { |
bytes = min(len, bps - pos % bps); |
bytes = min(bytes, nodep->size - pos); |
b = fat_block_get(bs, nodep, pos / bps); |
(void) ipc_data_read_finalize(callid, b->data + pos % bps, |
bytes); |
block_put(b); |
} |
} else { |
unsigned bnum; |
off_t spos = pos; |
812,7 → 570,7 |
while (bnum < nodep->size / bps) { |
off_t o; |
|
b = fat_block_get(nodep, bnum); |
b = fat_block_get(bs, nodep, bnum); |
for (o = pos % (bps / sizeof(fat_dentry_t)); |
o < bps / sizeof(fat_dentry_t); |
o++, pos++) { |
847,6 → 605,116 |
ipc_answer_1(rid, EOK, (ipcarg_t)bytes); |
} |
|
void fat_write(ipc_callid_t rid, ipc_call_t *request) |
{ |
dev_handle_t dev_handle = (dev_handle_t)IPC_GET_ARG1(*request); |
fs_index_t index = (fs_index_t)IPC_GET_ARG2(*request); |
off_t pos = (off_t)IPC_GET_ARG3(*request); |
fat_node_t *nodep = (fat_node_t *)fat_node_get(dev_handle, index); |
fat_bs_t *bs; |
size_t bytes; |
block_t *b; |
uint16_t bps; |
unsigned spc; |
off_t boundary; |
|
if (!nodep) { |
ipc_answer_0(rid, ENOENT); |
return; |
} |
|
/* XXX remove me when you are ready */ |
{ |
ipc_answer_0(rid, ENOTSUP); |
fat_node_put(nodep); |
return; |
} |
|
ipc_callid_t callid; |
size_t len; |
if (!ipc_data_write_receive(&callid, &len)) { |
fat_node_put(nodep); |
ipc_answer_0(callid, EINVAL); |
ipc_answer_0(rid, EINVAL); |
return; |
} |
|
/* |
* In all scenarios, we will attempt to write out only one block worth |
* of data at maximum. There might be some more efficient approaches, |
* but this one greatly simplifies fat_write(). Note that we can afford |
* to do this because the client must be ready to handle the return |
* value signalizing a smaller number of bytes written. |
*/ |
bytes = min(len, bps - pos % bps); |
|
bs = block_bb_get(dev_handle); |
bps = uint16_t_le2host(bs->bps); |
spc = bs->spc; |
|
boundary = ROUND_UP(nodep->size, bps * spc); |
if (pos < boundary) { |
/* |
* This is the easier case - we are either overwriting already |
* existing contents or writing behind the EOF, but still within |
* the limits of the last cluster. The node size may grow to the |
* next block size boundary. |
*/ |
fat_fill_gap(bs, nodep, FAT_CLST_RES0, pos); |
b = fat_block_get(bs, nodep, pos / bps); |
(void) ipc_data_write_finalize(callid, b->data + pos % bps, |
bytes); |
b->dirty = true; /* need to sync block */ |
block_put(b); |
if (pos + bytes > nodep->size) { |
nodep->size = pos + bytes; |
nodep->dirty = true; /* need to sync node */ |
} |
fat_node_put(nodep); |
ipc_answer_1(rid, EOK, bytes); |
return; |
} else { |
/* |
* This is the more difficult case. We must allocate new |
* clusters for the node and zero them out. |
*/ |
int status; |
unsigned nclsts; |
fat_cluster_t mcl, lcl; |
|
nclsts = (ROUND_UP(pos + bytes, bps * spc) - boundary) / |
bps * spc; |
/* create an independent chain of nclsts clusters in all FATs */ |
status = fat_alloc_clusters(bs, dev_handle, nclsts, &mcl, |
&lcl); |
if (status != EOK) { |
/* could not allocate a chain of nclsts clusters */ |
fat_node_put(nodep); |
ipc_answer_0(callid, status); |
ipc_answer_0(rid, status); |
return; |
} |
/* zero fill any gaps */ |
fat_fill_gap(bs, nodep, mcl, pos); |
b = _fat_block_get(bs, dev_handle, lcl, |
(pos / bps) % spc); |
(void) ipc_data_write_finalize(callid, b->data + pos % bps, |
bytes); |
b->dirty = true; /* need to sync block */ |
block_put(b); |
/* |
* Append the cluster chain starting in mcl to the end of the |
* node's cluster chain. |
*/ |
fat_append_clusters(bs, nodep, mcl); |
nodep->size = pos + bytes; |
nodep->dirty = true; /* need to sync node */ |
fat_node_put(nodep); |
ipc_answer_1(rid, EOK, bytes); |
return; |
} |
} |
|
/** |
* @} |
*/ |