FreeCalypso > hg > tcs211-l1-reconst
diff chipsetsw/drivers/drv_app/ffs/board/fsck.c @ 0:509db1a7b7b8
initial import: leo2moko-r1
author | Space Falcon <falcon@ivan.Harhan.ORG> |
---|---|
date | Mon, 01 Jun 2015 03:24:05 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chipsetsw/drivers/drv_app/ffs/board/fsck.c Mon Jun 01 03:24:05 2015 +0000 @@ -0,0 +1,1475 @@ +/****************************************************************************** + * Flash File System (ffs) + * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com + * + * FFS file system integrity checking, journalling, init and exit + * + * $Id: fsck.c 1.3.1.1.1.33 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ + * + ******************************************************************************/ + +#ifndef TARGET +#include "ffs.cfg" +#endif + +#include <string.h> +#include <assert.h> + +#include "ffs/ffs.h" +#include "ffs/board/core.h" +#include "ffs/board/drv.h" +#include "ffs/board/ffstrace.h" + +/****************************************************************************** + * Functions + ******************************************************************************/ + +bref_t blocks_fsck(void); +iref_t inodes_fsck(void); + +/****************************************************************************** + * Init and Exit + ******************************************************************************/ + +effs_t ffs_initialize(void) +{ + bref_t b; + struct inode_s *ip; + int i; + + tlw(led_set(0)); + tlw(led_on(LED_INIT)); + ttw(str(TTrInit, "initialize {" NL)); + tw(tr(TR_BEGIN, TrFsck, "ffs_initialize() {\n")); + + // default to non-initialized ffs + fs.root = 0; + fs.debug[0] = fs.debug[1] = fs.debug[2] = fs.debug[3] = 0; + fs.testflags = 0; + + tlw(led_on(LED_DRV_INIT)); + fs.initerror = ffsdrv_init(); // read manufacturer and device ID + tlw(led_off(LED_DRV_INIT)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + for (i = 0; i < 2; i++) + { + tlw(led_on(LED_BLOCKS_FSCK)); + fs.initerror = EFFS_INVALID; + fs.initerror = b = blocks_fsck(); + tlw(led_off(LED_BLOCKS_FSCK)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + tlw(led_on(LED_INODES_FSCK)); + fs.initerror = EFFS_INVALID; + fs.initerror = inodes_fsck(); + tlw(led_off(LED_INODES_FSCK)); + if (fs.initerror < 0) { + tlw(led_off(0)); + tw(tr(TR_END, TrFsck, "} %d\n", fs.initerror)); + ttw(ttr(TTrInit, "} %d" NL, fs.initerror)); + return fs.initerror; + } + + // parse the fs options in the root inode's name + ip = inode_addr(fs.root); + fs_params_init(addr2name(offset2addr(location2offset(ip->location)))); + + if ((fs.initerror = journal_init(fs.ijournal)) == 0) + break; + } + + // Init all file_descriptors to zero + memset(fs.fd, 0, sizeof(struct file_descriptor_s) * fs.fd_max); + + // If blocks_fsck() found a block that needs cleaning, we do it, now + // that all the file system has been initialized. + if (b > 0) { + block_clean(b - 1); + block_free(b - 1); + } + + statistics_init(); + + // In target, we do this before entering the task event loop... + // Otherwise we would in some cases impose a long reboot delay if we did + // it here. If we test in target it is nessesary to call + // blocks_reclaim() anyway because we re-init ffs. + +#if (TARGET == 1) //NOTEME: can this be done in another/better way? +#if (WITH_TFFS == 1) + blocks_reclaim(); +#endif +#else + blocks_reclaim(); +#endif + tlw(led_off(LED_INIT)); + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_OK)); + ttw(str(TTrInit, "} 0" NL)); + + return EFFS_OK; +} + +void fs_params_init(const char *p) +{ + uint8 opt, digit; + uint32 n; + int numdatablocks; + + tw(tr(TR_BEGIN, TrFsck, "fsparams_init('%s') {\n", p)); + + // Compiled default values + fs.filename_max = FFS_FILENAME_MAX; + fs.path_depth_max = FFS_PATH_DEPTH_MAX; + fs.fd_max = FFS_FD_MAX; + fs.journal_size = FFS_JOURNAL_SIZE_IN256THS; + fs.flags = 0; + fs.testflags = 0; + + // Flag that it not has been changed by an input arg. + fs.block_files_max = 0; + + // The default lost bytes percentage of a block before it is reclaimed + // is approx. 90%. + fs.lost_threshold = (256 - 256/10); + + // If we only have two blocks, we cannot make any reclaims and thus we + // have a write-once FFS system. + fs.blocks_free_min = (dev.numblocks > 2 ? 1 : 0); + + // Don't count free and inodes blocks + numdatablocks = dev.numblocks - fs.blocks_free_min - 1; + + // Abselute max number of inodes. + fs.inodes_max = dev.blocksize / sizeof(struct inode_s); + if (fs.inodes_max > FFS_INODES_MAX) + fs.inodes_max = FFS_INODES_MAX; + + // MUST be true: objects_max <= inodes_max - block_files_max, this is do + // to the fact that we always need to have block_files_max number of + // inodes left when we run a data reclaim. + fs.objects_max = fs.inodes_max / 2; + + // Find a suitable chunk_size + if (dev.numblocks*dev.blocksize > 1024*1024) + fs.chunk_size_max = 8192; + else + fs.chunk_size_max = (2048 > (dev.blocksize / 8) + ? (dev.blocksize / 8) + : 2048); + fs.fd_buf_size = fs.chunk_size_max; + + fs.journal_size = fs.journal_size * dev.blocksize / 256; + if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) + fs.journal_size = FFS_JOURNAL_SIZE_MIN; + + // Set it just below the same amount as entries in one journal file + fs.block_files_max = (fs.journal_size / sizeof(struct journal_s) + - FFS_JOURNAL_MARGIN - 2); + + // MUST be true: block_files_max < objects_max / 2. But if we want + // to reach objects_max must block_files_max >= objects_max / number + // of datablocks, however a big block_files_max require higher + // reserved_space. + if (fs.block_files_max > fs.objects_max / 2) + fs.block_files_max = fs.objects_max / 2 - 4; + + // Are we able to reach objects_max? If not then lower the number + if (fs.objects_max > numdatablocks * fs.block_files_max) + fs.objects_max = numdatablocks * fs.block_files_max + 10; + + // Absolute minimum is RESERVED_LOW the rest is 'workspace' which is + // needed to have a reasonable performance. + fs.reserved_space = dev.blocksize / 2 + + numdatablocks * dev.blocksize / 16 + RESERVED_LOW; + + // skip to first char following second slash in name + n = 0; + while (*p) { + if (*p++ == '/') { + n++; + if (n == 2) + break; + } + } + if (n == 2) { + // while still options to process... + while (*p) { + opt = *p++; // save option letter for later + // collect option value... + n = 0; + while ((digit = *p)) { + if (digit >= '0' && digit <= '9') { + n = 10 * n + digit - '0'; + p++; + } + else + break; + } + switch (opt) { + case 'b': dev.numblocks = n; break; + case 'm': fs.blocks_free_min = n; break; + case 'i': fs.inodes_max = n; break; + case 'o': fs.objects_max = n; break; + case 'n': fs.filename_max = n; break; + case 'f': fs.block_files_max = n; break; + case 'd': fs.fd_max = n; break; + case 's': fs.fd_buf_size = n; break; + case 't': fs.lost_threshold = n; break; + case 'z': fs.flags = n; break; + case 'j': fs.journal_size = n; break; + case 'c': fs.chunk_size_max = n; break; + case 'r': fs.reserved_space = n; break; + // d = &fs.path_depth_max; // really necessary? + default: + break; + } + } + } + + // Now recompute a few parameters based on adjusted values. + + // No journal file thuse no reserved space. + if (fs.journal_size == 0) { + fs.block_files_max = fs.objects_max / 2; + fs.reserved_space = 0; + fs.block_files_reserved = 0; + } + + else { + // If journal size is less than minimum must it have been changed by an + // input arg, recalculate. + if (fs.journal_size < FFS_JOURNAL_SIZE_MIN) + fs.journal_size = fs.journal_size * dev.blocksize / 256; + + if (fs.reserved_space < RESERVED_LOW) + fs.reserved_space = fs.reserved_space * dev.blocksize / 256; + + // Only one reserved is needed however we want a margin and set it to two + fs.block_files_reserved = 2; + } + + // Don't count free blocks, inode block, reserved space, block headers + // and the size of one filename. + fs.filesize_max = numdatablocks * dev.blocksize - fs.reserved_space - + numdatablocks * BHEADER_SIZE - FFS_FILENAME_MAX; + + // Furthermore don't count the overhead from each chunk (alignment) + fs.filesize_max -= ((fs.filesize_max / fs.chunk_size_max) * dev.atomsize + + dev.atomsize); + + // NOTEME: chunk_size_min is never used + fs.chunk_size_min = numdatablocks / fs.objects_max; + + tw(tr(TR_FUNC, TrFsck, "dev.numblocks = %d\n", dev.numblocks)); + tw(tr(TR_FUNC, TrFsck, "fs.blocks_free_min = %d\n", fs.blocks_free_min)); + tw(tr(TR_FUNC, TrFsck, "fs.inodes_max = %d\n", fs.inodes_max)); + tw(tr(TR_FUNC, TrFsck, "fs.objects_max = %d\n", fs.objects_max)); + tw(tr(TR_FUNC, TrFsck, "fs.block_files_max = %d\n", fs.block_files_max)); + tw(tr(TR_FUNC, TrFsck, "fs.block_files_reserved = %d\n", fs.block_files_reserved)); + tw(tr(TR_FUNC, TrFsck, "fs.chunk_size_max = %d\n", fs.chunk_size_max)); + tw(tr(TR_FUNC, TrFsck, "fs.filename_max = %d\n", fs.filename_max)); + tw(tr(TR_FUNC, TrFsck, "fs.lost_threshold = %d\n", fs.lost_threshold)); + tw(tr(TR_FUNC, TrFsck, "fs.path_depth_max = %d\n", fs.path_depth_max)); + tw(tr(TR_FUNC, TrFsck, "fs.journal_size = %d\n", fs.journal_size)); + tw(tr(TR_FUNC, TrFsck, "fs.reserved_space = %d\n", fs.reserved_space)); + tw(tr(TR_FUNC, TrFsck, "fs.fd_max = %d\n", fs.fd_max)); + tw(tr(TR_FUNC, TrFsck, "fs.fd_buf_size = 0x%02x\n", fs.fd_buf_size)); + tw(tr(TR_FUNC, TrFsck, "fs.flags = 0x%02x\n", fs.flags)); + tw(tr(TR_END, TrFsck, "}\n")); +} + +// TODO: Finish pending commits/writes. +effs_t ffs_exit(void) +{ + tw(tr(TR_FUNC, TrFsck, "exit() 0\n")); + + + return EFFS_OK; +} + +#if 0 // Not used in this version +// Purely for core internal use; Read a file. +effs_t file_read_int(const char *path, void *src, int size) +{ + if (fs.initerror != EFFS_OK) + return fs.initerror; + + return object_read(path, src, size, 0); +} + +// Purely for core internal use; Update a file. +effs_t file_update(const char *path, void *src, int size) +{ + char *name; + iref_t i, dir; + + if (fs.initerror != EFFS_OK) + return fs.initerror; + + if ((i = object_lookup(path, &name, &dir)) < 0) + return i; + + journal_begin(i); + + if ((i = object_create(name, src, size, -dir)) < 0) + return i; + + journal_end(0); + + return EFFS_OK; +} +#endif + +/****************************************************************************** + * blocks_fsck() + ******************************************************************************/ + +blocksize_t block_used(bref_t b) +{ + blocksize_t used; + uint32 *p, *q; + + tlw(led_toggle(LED_BLOCKS_FSCK)); + + // We search backwards through block to find the last used byte and + // thus the total number of used bytes. Note that this code depends + // on the fact that an erased flash location is 0xFF! + p = (uint32 *) offset2addr(dev.binfo[b].offset); + for (q = p + dev.blocksize/4 - 4; q > p; q -= 4) { + if ( ~(q[0] & q[1] & q[2] & q[3]) ) + break; + } + + if ( ~(q[0] & q[1] & q[2] & q[3]) ) + q += 4; + used = atomalign((char *) q - (char *) p); + + tw(tr(TR_FUNC, TrFsckLow, "ffs_block_used(%d) %d\n", b, used)); + + return used; +} + + +age_t age_distance(age_t x, age_t y) +{ + age_t a = x - y; + + if (a > 0x8000) + a = -a; + + tw(tr(TR_FUNC, TrFsckLow, "age_distance(%d, %d) %d\n", x, y, a)); + + return a; +} + +// For each ffs block, we initialise the basic bstat array information, +// namely the number of used bytes. Also, we locate the inodes block and if +// a previous operation was interrupted by a powerfail, we clean it up. +// +// We return EFFS_OK if all is fine. If a positive integer is returned, it +// denotes a block that needs to be cleaned by block_clean() once FFS +// has been properly intialized (we actually return the block number + 1 +// because otherwise it would clash with EFFS_OK return code). If no inodes +// block is found or another error occurs, we return the error code. +bref_t blocks_fsck(void) +{ + bref_t b, b_to_clean, b_inode_lost; + int age_valid; + age_t age_min, age_max, age_dist, age_dist_min, age_dist_max; + struct block_header_s *bhp; + struct block_header_old_s *obhp; + + ttw(str(TTrInitLow, "blocks_fsck {" NL)); + tw(tr(TR_BEGIN, TrFsck, "blocks_fsck() {\n")); + + // initialize ages to the illegal/unset value + age_min = age_max = age_dist = 0; + + fs.format = 0; + fs.inodes = -1; + fs.newinodes = -1; + b_inode_lost = -1; + b_to_clean = EFFS_OK; + + for (b = 0; b < dev.numblocks; b++) + { + tlw(led_toggle(LED_DRV_INIT)); + + // read block flags from flash + bhp = (struct block_header_s *) offset2addr(dev.binfo[b].offset); + obhp = (struct block_header_old_s *) bhp; + + bstat[b].used = dev.blocksize; + bstat[b].lost = bstat[b].used; + bstat[b].flags = bhp->flags; + bstat[b].objects = 0; + + age_valid = 0; + + if (bhp->magic_low != BLOCK_MAGIC_LOW || + bhp->magic_high != BLOCK_MAGIC_HIGH) { + // The block magic as bad! It *could* be because the flash + // memory map is incorrect or because another application has + // spuriously written to the flash or ... who knows what. First + // we check to see if the reason is that we are dealing with a + // (really) old ffs format version. + if (obhp->magic_low == OLD_BLOCK_MAGIC_LOW && + obhp->magic_high == OLD_FFS_FORMAT_VERSION) { + tw(tr(TR_FUNC, TrFsck, "OLD ")); + fs.format = obhp->magic_high; + // We simulate that all the blocks are data blocks, in order + // to have some well-defined state that preformat() can work + // on. Later we will return EFFS_BADFORMAT and otherwise + // leave everything as it is, *without* modifying anything! + bstat[b].flags = BF_IS_DATA; + } + else { + // Quickly test if block is in empty state. We do not make a + // full check with block_used() because that takes too + // long --- we let preformat() do that. + if (bhp->magic_low == FLASH_NULL16 && + bhp->magic_high == FLASH_NULL16 && + bhp->age == FLASH_NULL16 && + bhp->version == FLASH_NULL16 && + bhp->flags == FLASH_NULL16) + { + bstat[b].used = 0; + bstat[b].lost = 0; + bstat[b].flags = BF_IS_EMPTY; + tw(tr(TR_FUNC, TrFsck, "EMPTY ")); + } + else { + // If the block is not free, it is probably corrupted. + // Thus we reset its age and free it. + tw(tr(TR_FUNC, TrFsck, "magic = 0x%08x\n", + bhp->magic_low | (bhp->magic_high << 16))); + ffsdrv.write_halfword(&bhp->age, 0); + block_free(b); + tw(tr(TR_FUNC, TrFsck, "BAD ")); + } + } + } + else { + fs.format = bhp->version; + age_valid = 1; + + if (!is_block(b, BF_IS_FREE)) { + bstat[b].used = block_used(b); + bstat[b].lost = bstat[b].used - BHEADER_SIZE; + } + + if (is_block(b, BF_IS_FREE)) { + // The only case where we do not call block_used() is + // when the block is truly free. + bstat[b].used = 0; + bstat[b].lost = 0; + tw(tr(TR_FUNC, TrFsck, "FREE ")); + ttw(ttr(TTrInitLow, "FREE" NL)); + + } + else if (is_block(b, BF_IS_DATA)) { + tw(tr(TR_FUNC, TrFsck, "DATA ")); + ttw(ttr(TTrInitLow, "DATA" NL)); + } + else if (is_block(b, BF_IS_CLEANING)) { + // Here we schedule a block_clean(). Note that we can + // and do not execute the block cleaning now, as the info + // that block_clean() needs is not at all ready at this + // point in the initialization. So we set a flag and then + // clean the block at the end of ffs_initialize() + tw(tr(TR_FUNC, TrFsck, "CLEANING ")); + ttw(ttr(TTrInitLow, "CLEANING" NL)); + b_to_clean = b + 1; + } + else if (is_block(b, BF_IS_COPYING)) { + tw(tr(TR_FUNC, TrFsck, "COPYING ")); + ttw(ttr(TTrInitLow, "COPYING" NL)); + fs.newinodes = b; + } + else if (is_block(b, BF_IS_INODES)) { + tw(tr(TR_FUNC, TrFsck, "INODES ")); + ttw(ttr(TTrInitLow, "INODES" NL)); + fs.inodes = b; + } + else if (is_block(b, BF_IS_INODES_LOST)) { + tw(tr(TR_FUNC, TrFsck, "INODESLOST")); + ttw(ttr(TTrInitLow, "INODESLOST" NL)); + b_inode_lost = b; + } + else { + block_free(b); + tw(tr(TR_FUNC, TrFsck, "INVALID ")); + ttw(ttr(TTrInitLow, "INVALID" NL)); + } + } + + tw(tr(TR_NULL, TrFsck, " %2d: (0x%05x) %02x, used = %6d\n", + b, dev.binfo[b].offset, bstat[b].flags & 0xFF, bstat[b].used)); + + if (age_valid) { + if (age_min == 0) { + // Initialize minimum and maximum block ages + age_min = age_max = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_min/max = %d\n", age_min)); + } + else { + age_dist_min = age_distance(bhp->age, age_min); + age_dist_max = age_distance(bhp->age, age_max); + if (age_dist_min > age_dist || + age_dist_max > age_dist) { + if (age_dist_max > age_dist_min) { + age_dist = age_dist_max; + age_min = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_min = %d (dist = %d)\n", + age_min, age_dist)); + } + else { + age_dist = age_dist_min; + age_max = bhp->age; + tw(tr(TR_FUNC, TrFsckLow, "age_max = %d (dist = %d)\n", + age_max, age_dist)); + } + } + } + } + } + tlw(led_off(LED_DRV_INIT)); + tw(tr(TR_FUNC, TrFsck, "age min, max, max-min = %d, %d, %d\n", + age_min, age_max, (uint16) (age_max-age_min))); + // If age_max is untouched is is because all blocks were in the 'Empty' + // state. In this case we let the age be as it is (0xFFFF). + if (age_max == 0) + age_max = age_min = BLOCK_AGE_MAX; + + // Handle age wrap around thus ensuring fs.age_max is set correctly. We + // have to type-cast the whole computation, otherwise it will be + // incorrect. + if ((age_t) (age_max - age_min) > 0x8000) { + age_dist = age_max; + age_max = age_min; + age_min = age_dist; + } + + // save maximum age found for the case of a bad block that is going to + // be reclaimed later on by blocks_reclaim() + fs.age_max = age_max; + + tw(tr(TR_FUNC, TrFsck, "fs.format = 0x%04x\n", fs.format)); + tw(tr(TR_FUNC, TrFsck, "fs.inodes, newinodes = %d, %d\n", + fs.inodes, fs.newinodes)); + ttw(ttr(TTrInit, "fs.inodes, newinodes = %d, %d" NL, + fs.inodes, fs.newinodes)); + tw(tr(TR_FUNC, TrFsck, "age min, max = %d, %d\n", age_min, age_max)); + + // If any blocks were in the EMPTY state, now is the time to bring them + // into the FREE state. Note that we must only do this *after* + // fs.age_max has been initialized. + for (b = 0; b < dev.numblocks; b++) { + if (is_block(b, BF_IS_EMPTY)) { + if ((bstat[b].used = block_used(b)) == 0) + block_preformat(b, 0); + else + block_free(b); + } + } + + if (fs.inodes >= 0) { + // The 'old' inode block is still valid thus we keep it. + if (fs.newinodes >= 0) + // The copying of inodes to the new block was not finished thus + // we free the block + block_free(fs.newinodes); + inodes_set(fs.inodes); + } + else { + // Copying must have been finished + if (fs.newinodes >= 0 && b_inode_lost >= 0) { + // The inode reclaim did finish but currently there is no valid + // inode block thus the operation must be finished by committing + // the new block as the valid inode block. + fs.inodes = b_inode_lost; + block_commit(); + + } + else { + // No old or new Inode block! + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_NOFORMAT)); + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); + return EFFS_NOFORMAT; + } + } + + if ((fs.format >> 8) != (FFS_FORMAT_VERSION >> 8)) { + tw(tr(TR_END, TrFsck, "} %d\n", EFFS_BADFORMAT)); + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_BADFORMAT)); + return EFFS_BADFORMAT; + } + + // FIXME: Insert age sanity check; age distance must not be too big (> 2 + // * FFS_AGE_DISTANCE)? + + tw(tr(TR_END, TrFsck, "} %d\n", b_to_clean)); + ttw(ttr(TTrInitLow, "} %d" NL, b_to_clean)); + + return b_to_clean; +} + +// Set fs.inodes and fs.inodes_addr +void inodes_set(iref_t i) +{ + fs.inodes = i; + fs.inodes_addr = (struct inode_s *) + (offset2addr(dev.binfo[fs.inodes].offset) + + dev.atomsize - sizeof(struct inode_s)); +} + + +/****************************************************************************** + * inodes_fsck() + ******************************************************************************/ + +// Now for each inode in the inodes block, update the bstat array +// information: free, used, objects. Also, locate the root inode. We could +// optimize this a little, because bstat[binodes].used gives an inidication +// of how many inodes are actually present in the system. +iref_t inodes_fsck(void) +{ + iref_t i; + struct inode_s *ip; + char *addr; + bref_t block; + + ttw(str(TTrInitLow, "inodes_fsck {" NL)); + tw(tr(TR_BEGIN, TrFsck, "inodes_fsck() {\n")); + tw(tr(TR_FUNC, TrFsck, "inodes in block %d:\n", fs.inodes)); + + // the fields of the bstat entry for the inodes have the meaning: + // used = total number of used inodes (valid, erased, invalid) + // lost = total number of lost inodes (erased, invalid) + // objects = index of first free inode (used by inode_alloc()) + + fs.root = 0; // default to root inode not found + fs.ijournal = 0; // default to journal file inode not found + bstat[fs.inodes].objects = 1; + bstat[fs.inodes].used = 0; + bstat[fs.inodes].lost = 0; + fs.sequence = 0; // just for debug (fun) + + // we must set some default value for this, so we set it to max possible! + fs.inodes_max = dev.blocksize / sizeof(struct inode_s); + + ip = inode_addr(1); + tw(tr(TR_FUNC, TrFsck, " i addr cld sib seq upd flag size name\n")); + for (i = 1; i < fs.inodes_max; i++, ip++) + { + // just for debug (fun) + if (ip->sequence > fs.sequence) + fs.sequence = ip->sequence; + + // compute block index and total data space occupied + block = offset2block(location2offset(ip->location)); + + // Only scan used inodes. blocks_fsck() accounted all used space as + // also being lost space, so now we subtract from the lost space, + // the space used by valid objects + if (ip->location != FLASH_NULL32) + { + bstat[fs.inodes].used++; + + tw(tr(TR_FUNC, TrFsck, "%3d 0x%05X %3d %3d %4d %3d %s%s%s%s%s%s %6d %s\n", + i, + location2offset(ip->location), + ip->child, ip->sibling, + ip->sequence, ip->updates, + is_object(ip, OT_DIR) ? "d" : "", + is_object(ip, OT_LINK) ? "l" : "", + is_object(ip, OT_FILE) ? "f" : "", + is_object(ip, OT_SEGMENT) ? "s" : "", + is_object(ip, OT_ERASED) ? " " : "", + IS_BIT_SET(ip->flags, OF_READONLY) && !is_object(ip, OT_ERASED) ? + "r" : " ", + ip->size, + // Erased chunks do not have any name so we can not trace erased objects! + (ip->size && !is_object(ip, OT_SEGMENT) && !is_object(ip, OT_ERASED) ? + addr2name(offset2addr(location2offset(ip->location))) : "") + )); + + if (is_object_valid(ip)) { + // This inode is valid, so we account the data space as used + // and the inode as used too. + bstat[block].lost -= ip->size; + bstat[block].objects++; + // test if this is the root inode. store index if it is. + if (!is_object(ip, OT_SEGMENT)) { + addr = addr2name(offset2addr(location2offset(ip->location))); + if (*addr == '/') + fs.root = i; + else if (*addr == '.' && + ffs_strcmp(addr, FFS_JOURNAL_NAME) == 0) { + fs.ijournal = i; + } + } + } + else if (is_object(ip, OT_ERASED)) { + // this inode's data is deleted, so we account the data + // space as used and lost and the inode as lost too. + bstat[fs.inodes].lost++; + } + else { + // This is an invalid object, so we account the data space + // as used and lost and the inode as lost too. NOTEME: error + // what should we do? Perhaps we should record semi-lost + // inodes? Can we safely account for it here if this is an + // object to be recovered because another inode.copied is + // referring to this? Will used/lost etc. be updated + // correctly then? + bstat[fs.inodes].lost++; + tw(tr(TR_NULL, TrFsck, "(invalid = %d)\n", ip->flags & OT_MASK)); + } + } + } + ttw(ttr(TTrInit, "fs.root=%d, journal=%d" NL, fs.root, fs.ijournal)); + tw(tr(TR_END, TrFsck, "} used: %d, lost: %d, root: %d, journal: %d\n", + bstat[fs.inodes].used, bstat[fs.inodes].lost, fs.root, fs.ijournal)); + + fs.sequence++; + + tw(tr_bstat()); + + if (fs.root == 0) { + ttw(ttr(TTrInitLow, "} %d" NL, EFFS_NOFORMAT)); + return EFFS_NOFORMAT; + } + + ttw(str(TTrInitLow, "} 0" NL)); + + return EFFS_OK; +} + + +/****************************************************************************** + * Preformat and format + ******************************************************************************/ + +// Prepare all blocks for fs_format(). Because ffs_is_formattable() has +// already been called prior to this function, we know that no sector erase +// is in progress! The blocks are prepared by putting them into the 'Free' +// state. +effs_t fs_preformat(void) +{ + bref_t b; + + ttw(str(TTrFormat, "preformat {" NL)); + tw(tr(TR_BEGIN, TrFormat, "fs_preformat() {\n")); + + // Mark ffs as being non-formatted from now on. + fs.root = 0; + + // We must initialize bstat[fs.inodes].used and inodes_high, such that + // inodes_reclaim() isn't triggered in reclaim() on the following + // fs_format(). + inodes_set(0); + bstat[fs.inodes].used = 0; + bstat[fs.inodes].lost = 0; + bstat[fs.inodes].objects = 0; + + // While format is in progress, we make FFS inaccessible to other + // functions... + fs.initerror = EFFS_NOFORMAT; + + if (dev.manufact == 0) { + b = EFFS_NODEVICE; + } + else { + for (b = 0; b < dev.numblocks; b++) { + if (is_block(b, BF_IS_EMPTY)) { + if ((bstat[b].used = block_used(b)) == 0) + block_preformat(b, 0); + else + block_free(b); + } + else if (!is_block(b, BF_IS_FREE)) { + block_free(b); + } + } + b = EFFS_OK; + } + + tw(tr(TR_END, TrFormat, "} %d\n", b)); + ttw(ttr(TTrFormat, "} %d" NL, b)); + + return b; +} + +// Preformat a single block thus taking it from the 'Empty' state into +// 'Free' state. +void block_preformat(bref_t b, age_t age) +{ + int set_age_max; + struct block_header_s *bhp = + (struct block_header_s *) offset2addr(dev.binfo[b].offset); + + tw(tr(TR_BEGIN, TrFormat, "fs_block_preformat(%d, %d)\n", b, age)); + + if (age == 0) { + age = fs.age_max; + } + else { + // We schedule an update of fs.age_max. Due to proper handling of + // age wrap-around, we can not actually set it now. + set_age_max = (age == fs.age_max); + age++; + if (age == 0) + age++; + if (set_age_max) { + fs.age_max = age; + tw(tr(TR_FUNC, TrFormat, "new fs.age_max = %d\n", fs.age_max)); + } + } + + ffsdrv.write_halfword(&bhp->age, age); + ffsdrv.write_halfword(&bhp->version, FFS_FORMAT_VERSION); + ffsdrv.write_halfword(&bhp->magic_low, BLOCK_MAGIC_LOW); + ffsdrv.write_halfword(&bhp->magic_high, BLOCK_MAGIC_HIGH); + + bstat[b].flags = BF_IS_EMPTY; + bstat[b].used = 0; + bstat[b].lost = 0; + bstat[b].objects = 0; + + block_flags_write(b, BF_FREE); + + tw(tr(TR_END, TrFormat, "")); +} + +// After preformat() has erased two blocks, this function can be called to +// initialize ffs by writing fs data and metadata. Note that ffs_begin() is +// *not* called before this function in ffs.c. Otherwise we would never +// enter this function because fs.root is zero. NOTEME: this is also a bug +// as this means we risk that this operation is started while an erase (or a +// write) is in progress! How the flash device reacts to this is currently +// unknown. +effs_t fs_format(const char *name) +{ + bref_t i, b; + + ttw(str(TTrFormat, "format {" NL)); + tw(tr(TR_BEGIN, TrFormat, "fs_format('%s') {\n", name)); + + // Initialize file system parameters. It should be safe to change these + // now, as the format cannot fail at this point onwards. + fs_params_init(name); + + // Make the first block be the inodes block + if ((fs.inodes = block_alloc(1, BF_COPYING)) < 0) + return EFFS_AGAIN; + block_flags_write(fs.inodes, BF_INODES); + inodes_set(fs.inodes); + + // Make all block as data blocks except from the free_min and inode block + for (i = 0; i < dev.numblocks - fs.blocks_free_min - 1; i++) + if ((b = block_alloc(0, BF_DATA)) < 0) + return EFFS_AGAIN; + + // Restart object sequencing (debug feature only) + fs.sequence = 0; + + // Create root directory + journal_begin(0); + if ((fs.root = object_create(name, 0, 0, 0)) < 0) { + tw(tr(TR_END, TrFormat, "} %d\n", fs.root)); + return fs.root; + } + journal_commit(OT_DIR); + + if ((fs.ijournal = journal_create(0)) < 0) { + tw(tr(TR_END, TrFormat, "} %d\n", fs.ijournal)); + return fs.ijournal; + } + + fs.initerror = ffs_initialize(); + + ttw(ttr(TTrFormat, "} %d" NL, fs.initerror)); + tw(tr(TR_END, TrFormat, "} %d\n", fs.initerror)); + + return fs.initerror; +} + +// Check if we are ready to preformat (flag = 0) or format (flag = 1) +// +// For a format, we must first ensure no blocks are valid e.g. a preformat +// has already been run. Next, we must ensure we have preformatted all +// blocks e.g. all blocks are in the 'Free' state. This is actually the same +// thing but it sure helps the user because it yields a more precise error +// code when the format fails. In future we might be able to start a format +// when only two blocks have been preformatted, but this is harder because +// we have to make sure not to read from the physical sector that we are +// erasing, and this is exactly what ffs_ffs_initialize() currently does +// (when it is called at the end of format()). +// +// For a preformat, we must ensure an erase is not in progress (because we +// don't know how the device will react to a new erase when an erase is +// currently suspended). +effs_t is_formattable(int8 flag) +{ + bref_t i, free, valid; + effs_t error = EFFS_OK; + + tw(tr(TR_FUNC, TrFormat, "is_formattable() ")); + + // Count the number of valid and free blocks. These numbers will later + // be checked to see if we are really ready for a (pre)format(). Note + // that we *only* read block flags from the bstat[] array. We must not + // read directly from the flash sectors because an erase might be in + // progress! + for (i = 0, free = 0, valid = 0; i < dev.numblocks; i++) { + if (is_block(i, BF_IS_DATA) || is_block(i, BF_IS_INODES)) + valid++; + if (is_block(i, BF_IS_FREE)) + free++; + } + if (flag == 0) { + // In the case of a preformat, ensure an erase is not in + // progress (because we don't know how the device will react to a new + // erase when an erase is currently suspended). + if (dev.state == DEV_ERASE || dev.state == DEV_ERASE_SUSPEND) { + tw(tr(TR_NULL, TrFormat, "(%d)\n", EFFS_AGAIN)); + return EFFS_AGAIN; + } + } + else { + if (valid > 0) + // Ensure we have preformatted prior to a format. + error = EFFS_NOPREFORMAT; + else if (free < dev.numblocks) + // Ensure all blocks are free before a format(). If not, a + // preformat() is currently in progress. + error = EFFS_AGAIN; + } + + tw(tr(TR_NULL, TrFormat, "(%d)\n", error)); + return error; +} + + +/****************************************************************************** + * Journalling + ******************************************************************************/ + +// The following matrix illustrates how the members of an inode change for +// the various (journalled) operations: +// +// | flags | size | loc | child | siblg | dir | oldi | updates +// ---------+-------+------+-----+-------+-------+-----+------+-------- +// create | new | new | new | - | - | ins | n/a | 0 +// fupdate | o | new | new | o | - | ins | del | old+1 +// relocate | o | o | new | o | - | ins | del | old+1 +// fctrl | new | o | o | o | - | ins | del | old+1 +// remove | n/a | n/a | n/a | n/a | n/a | n/a | del | n/a +// +// - = leave empty (0xFFFF) +// ins = insert/append into directory +// o = old value +// +// We don't have to store child member in the journal entry because either +// it is EMPTY (fs.journal.oldi = 0) or it is retrieved from oldip->child. + +// NOTEME: With journalling implemented, object_relocate might be able just +// to make a simple data copy! + +// block_clean() is safe (without journalling), now that only ip->size is +// set to zero. + +// Begin a new journal. Either a fresh object create (oldi == 0) or an +// update of an existing object (oldi == iref of old object) +void journal_begin(iref_t oldi) +{ + tw(tr(TR_FUNC, TrJournal, "journal_begin(%d)\n", oldi)); + + fs.journal.i = 0; + fs.journal.state = JOURNAL_IS_EMPTY; + fs.journal.repli = 0; + fs.link_child = 1; //Default link child in journal_commit() + + if (oldi == 0) { + fs.journal.flags = 0xFF; + fs.journal.diri = 0; + fs.journal.oldi = 0; + fs.journal.location = 0; + fs.journal.size = 0; + } + else { + struct inode_s *oldip = inode_addr(oldi); + fs.journal.flags = oldip->flags; + fs.journal.diri = oldi; + fs.journal.oldi = oldi; + fs.journal.location = oldip->location; + fs.journal.size = oldip->size; + } +} + +// NOTEME: We have compressed the macro code because it will NOT compile on +// Unix otherwise. So until we find out why, we use this as a work-around. +#if (FFS_TEST == 1) +#define JOURNAL_TEST(testcase, text) if (fs.testflags == testcase) { tw(tr(TR_END, TrJournal, "} (" text ")\n")); return; } +#else +#define JOURNAL_TEST(testcase, text) +#endif + +// NOTEME: Should we empty journal file when we are anyway relocating it in +// data_reclaim()? +void journal_end(uint8 type) +{ + struct inode_s *ip = inode_addr(fs.ijournal); + struct journal_s *addr = (struct journal_s *) + offset2addr(location2offset(ip->location) + fs.journal_pos); + + tw(tr(TR_BEGIN, TrJournal, "journal_end(0x%x) {\n", type)); + tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x (%d)\n", fs.journal_pos, + (fs.journal_pos - JOURNAL_POS_INITIAL) / sizeof(struct journal_s))); + + // If this is a create, set the object type + if (type != 0 && fs.journal.oldi == 0) + fs.journal.flags = (fs.journal.flags & OF_MASK) | type; + + // If there is no journal file, we can do without it, although we + // certainly don't like it! + if (fs.ijournal == 0) { + journal_commit(0); + tw(tr(TR_END, TrJournal, "} No jounal file\n")); + return; + } + + JOURNAL_TEST(JOURNAL_TEST_EMPTY, "Oops in JOURNAL_IS_EMPTY"); + + // Write RAM journal to journal file. + if (fs.journal.state == (uint8) JOURNAL_IS_EMPTY) { + fs.journal.state = JOURNAL_IS_WRITING; + ffsdrv.write(addr, &fs.journal, sizeof(fs.journal)); + } + + JOURNAL_TEST(JOURNAL_TEST_WRITING, "Oops in JOURNAL_IS_WRITING"); + + // Advance journal file's state + if (fs.journal.state == (uint8) JOURNAL_IS_WRITING) { + fs.journal.state = JOURNAL_IS_READY; + ffsdrv_write_byte(&addr->state, fs.journal.state); + } + + JOURNAL_TEST(JOURNAL_TEST_READY, "Oops in JOURNAL_IS_READY"); + + journal_commit(0); + + JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING"); + JOURNAL_TEST(JOURNAL_TEST_COMMITTED, "Oops in JOURNAL_COMMITTED"); + + // Advance journal file's state + ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); + + JOURNAL_TEST(JOURNAL_TEST_DONE, "Oops in JOURNAL_IS_DONE"); + + // Advance journal + fs.journal_pos += sizeof(struct journal_s); + + // Unless we are currently relocating the journal file itself, check if + // journal file is near full and relocate it if it is. + if (fs.journal_pos >= fs.journal_size - FFS_JOURNAL_MARGIN * + sizeof(struct journal_s) && fs.journal.oldi != fs.ijournal) { + tw(tr(TR_FUNC, TrJournal, "Journal file (near) full!\n")); + journal_create(fs.ijournal); + } + + // Check if we have just committed the journal file itself + if (fs.journal.oldi == fs.ijournal) { + fs.journal_pos = JOURNAL_POS_INITIAL; + fs.ijournal = fs.journal.i; + tw(tr(TR_FUNC, TrJournal, "Journal file re-created, fs.ijournal = %d\n", + fs.ijournal)); + } + tw(tr(TR_END, TrJournal, "}\n")); +} + +// Write contents of fs.journal to FFS meta data (inodes). Note that we do +// NOT traverse ip->copied as we used to do in the old +// object_update_commit(). Also, we do not check if object has been +// erased after traversing ip->copied. All this code has been removed +// because we will very soon have full callback functionality and thus the +// code is redundant. +void journal_commit(uint8 type) +{ + struct inode_s *ip = inode_addr(fs.journal.i); + struct inode_s *oldip = inode_addr(fs.journal.oldi); + struct inode_s *dp; + bref_t b; + + tw(tr(TR_BEGIN, TrJournal, "journal_commit(%d) {\n", type)); + tw(tr(TR_FUNC, TrJournal, "i = %d\n", fs.journal.i)); + ttw(ttr(TTrObj, "jc(){" NL)); + + if (fs.journal.i) + { + // If this is a create, set the object type + if (type != 0 && fs.journal.oldi == 0) + fs.journal.flags = (fs.journal.flags & OF_MASK) | type; + + tw(tr(TR_FUNC, TrJournal, "loc = 0x%04x, size = %d\n", + fs.journal.location, fs.journal.size)); + ffsdrv.write((uint32 *) &ip->location, (uint32 *) &fs.journal.location, sizeof(location_t)); + ffsdrv.write_halfword((uint16 *) &ip->size, fs.journal.size); + + if (fs.journal.oldi != 0 && fs.link_child != 0) + // If this is an update, we copy the child member from old + // inode. We must do this before we validate the new object, + // otherwise an intermediate readdir() will detect an empty + // directory! + ffsdrv.write_halfword((uint16*) &ip->child, oldip->child); + + tw(tr(TR_FUNC, TrJournal, "seq = %d\n", fs.sequence)); + // We must check if sequence is already written because if this + // commit was inititiated by journal_init(), we don't know exactly + // what was written + if (ip->sequence == FLASH_NULL16) + ffsdrv.write_halfword(&ip->sequence, fs.sequence++); + if (fs.journal.oldi == 0) + ffsdrv.write_halfword(&ip->updates, 0); + else + ffsdrv.write_halfword(&ip->updates, oldip->updates + 1); + + JOURNAL_TEST(JOURNAL_TEST_COMMITTING, "Oops in JOURNAL_TEST_COMMITTING") + + // Insert object into directory structure. We must do this before + // deleting old object, otherwise an intermediate readdir() will + // fail with EFFS_NOTFOUND. Note that when the root directory is + // created, fs.journal.diri is zero --- thus the test! + if (fs.journal.diri != 0) { + tw(tr(TR_FUNC, TrJournal, "diri = %d ", fs.journal.diri)); + if (fs.journal.diri < 0) { + tw(tr(TR_NULL, TrJournal, "child\n")); + dp = inode_addr(-fs.journal.diri); + ffsdrv.write_halfword((uint16 *) &dp->child, fs.journal.i); + } + else { + tw(tr(TR_NULL, TrJournal, "sibling\n")); + dp = inode_addr(fs.journal.diri); + ffsdrv.write_halfword((uint16 *) &dp->sibling, fs.journal.i); + } + } + + // The new object is validated before the old object is deleted. + // This is in order to avoid an interrupting stat or read operation + // to fail with EFFS_NOTFOUND + tw(tr(TR_FUNC, TrJournal, "flags = 0x%02x\n", fs.journal.flags)); + ffsdrv_write_byte(&ip->flags, fs.journal.flags); + + // Update bstat[] appropriately + b = offset2block(location2offset(ip->location)); + bstat[b].objects++; + tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); + } + + tw(tr(TR_FUNC, TrJournal, "oldi = %d\n", fs.journal.oldi)); + if (fs.journal.oldi != 0) + { + // If this is an update or an erase, we erase the old object + ffsdrv_write_byte(&oldip->flags, OT_ERASED); + + // Update bstat according to deletion of the old object. + b = offset2block(location2offset(oldip->location)); + bstat[b].objects--; + tw(tr(TR_FUNC, TrJournal, "bstat[%d].objects = %d\n", b, bstat[b].objects)); + + // If we moved the data (all cases, except fcontrol), update lost + if (fs.journal.location != oldip->location) + bstat[b].lost += oldip->size; + + bstat[fs.inodes].lost++; + + // If we renamed a file to an existing filename, remove the replaced file. + if (fs.journal.repli > 0) + object_remove(fs.journal.repli); // Ignore error! + } + + tw(tr(TR_END, TrJournal, "}\n")); + ttw(ttr(TTrObj, "}" NL)); +} + +// Save the current journal into "old" journal. We need this because an +// object_create() can call data_reclaim() which can call object_relocate() +// which uses the journal system. +int journal_push(void) +{ + memcpy(&fs.ojournal, &fs.journal, sizeof(struct journal_s)); + fs.journal_depth++; + if (fs.journal_depth > 1) { + tw(tr(TR_FUNC, TrAll, "FATAL: journal_push() to depth %d\n", + fs.journal_depth)); + return -1; + } + + tw(tr(TR_FUNC, TrJournal, "journal_push() to depth %d\n", + fs.journal_depth)); + + return EFFS_OK; +} + +// Recall "old" journal into current journal +int journal_pop(void) +{ + tw(tr(TR_FUNC, TrJournal, "journal_pop() from depth %d\n", + fs.journal_depth)); + + fs.journal_depth--; + if (fs.journal_depth < 0) { + tw(tr(TR_FUNC, TrAll, "FATAL: journal_pop() to depth %d\n", + fs.journal_depth)); + return -1; + } + memcpy(&fs.journal, &fs.ojournal, sizeof(struct journal_s)); + + return EFFS_OK; +} + +// Initialize the journalling system. Create journal file if it not already +// exist. Commit/write pending journal if such exists --- return 1 in that +// case. Otherwise, if journal file is clean (no journals pending) and all +// is fine, return EFFS_OK. +effs_t journal_init(iref_t i) +{ + int j; + struct inode_s *ip = inode_addr(i); + struct journal_s *addr; + + if (i == 0) { + // Journal file does not exist, so create it + if ((i = journal_create(0)) <= 0) { + fs.ijournal = 0; + return i; + } + } + + fs.journal_depth = 0; + fs.journal_pos = JOURNAL_POS_INITIAL; + + addr = (struct journal_s *) + offset2addr(location2offset(ip->location) + fs.journal_pos); + + tw(tr(TR_BEGIN, TrJournal, "journal_init(%d) {\n", i)); + + fs.ijournal = i; + + // Search for first non-completed journal entry. + for (j = 0; /* FIXME: limit to end of journal */; j++, addr++) { + if (addr->state != (uint8) JOURNAL_IS_DONE) + break; + } + tw(tr(TR_FUNC, TrJournal, "entry %d is in state 0x%x\n", j, addr->state)); + + fs.journal_pos += j * sizeof(fs.journal); + i = EFFS_OK; + + if (addr->state == (uint8) JOURNAL_IS_EMPTY) { + tw(tr(TR_FUNC, TrJournal, "Last journal is in EMPTY state\n")); + // Journal file is proper, so just record position + } + else if (addr->state == (uint8) JOURNAL_IS_READY) { + // Copy the entry into fs.journal. + tw(tr(TR_FUNC, TrJournal, "Last journal is in READY state\n")); + memcpy(&fs.journal, addr, sizeof(fs.journal)); + journal_end(0); + i = 1; + } + else { + // Journal entry wasn't finished, so just ignore it after updating + // its state to JOURNAL_IS_DONE. + tw(tr(TR_FUNC, TrJournal, "Last journal is between EMPTY and READY\n")); + ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); + fs.journal_pos += sizeof(fs.journal); + } + + if (ip->size != fs.journal_size + atomalign(sizeof(FFS_JOURNAL_NAME) + 1)) { + tw(tr(TR_FUNC, TrJournal, "Wrong journal size, create new\n")); + // Journal size do not match default size, so create new. This + // should only happen if we use an old FFS image with a newer FFS + // version. + if ((i = journal_create(fs.ijournal)) <= 0) { + fs.ijournal = 0; + return i; + } + } + + tw(tr(TR_FUNC, TrJournal, "journal_pos = 0x%04x\n", fs.journal_pos)); + tw(tr(TR_END, TrJournal, "} %d\n", i)); + + return i; +} + +// Create the journal file from scratch or relocate an existing one. It is +// marked read-only just for clarity --- it cannot be deleted anyway! +// fs_format() calls this function. Note that no data are written in +// object_create() because the journal file is handled specially in that +// function. +iref_t journal_create(iref_t oldi) +{ + iref_t i; + + tw(tr(TR_BEGIN, TrJournal, "journal_create(%d) {\n", oldi)); + tw(tr(TR_FUNC, TrJournal, "journal file size = %d\n", fs.journal_size)); + + if (fs.journal_size == 0) { + tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.journal_size = 0 (No journal file wanted)\n")); + tw(tr(TR_END, TrJournal, "} %d\n", 0)); + return 0; + } + + // If we are working on a write-once file system, we do not need a + // journal. + if (fs.blocks_free_min == 0) { + tw(tr(TR_FUNC, TrJournal, "Journal file creation aborted because fs.blocks_free_min = 0 (write-once system)\n")); + tw(tr(TR_END, TrJournal, "} %d\n", 0)); + return 0; + } + + journal_begin(oldi); + + i = object_create(FFS_JOURNAL_NAME, 0, fs.journal_size, -fs.root); + if (i < 0) { + tw(tr(TR_END, TrJournal, "} %d\n", i)); + return i; + } + fs.journal.flags = BIT_SET(fs.journal.flags, OF_READONLY); + + // commit the creation or relocation + if (oldi != 0) + journal_end(0); + else { + journal_commit(OT_FILE); + fs.journal_pos = JOURNAL_POS_INITIAL; + } + + tw(tr(TR_END, TrJournal, "} %d\n", i)); + + return i; +} + +/****************************************************************************** + * FFS Begin and End + ******************************************************************************/ + +// The following two functions should surround the code of every API +// function in ffs.c (except preformat and format). The functions +// ensures that the operation about to be executed can be made without +// race-conditions or other problems. +#if (TARGET == 0) +int debug_suspend = 0; +#endif + + +// Check if ffs has been initialized. Suspend an erase operation. +effs_t ffs_begin(void) +{ +#if (TARGET == 0) + if (debug_suspend > 0) { + tw(tr(TR_FUNC, TrAll, "FATAL: Previous erase_suspend was not resumed\n")); + return EFFS_CORRUPTED; + } +// tw(tr(TR_FUNC, TrHelper, "Set debug_suspend\n")); + debug_suspend = 1; +#endif + + if (fs.initerror != EFFS_OK) + return fs.initerror; + + // Suspend an erase in progress (only applicable if we are using a + // multi-bank device driver) + if (dev.state == DEV_ERASE) { + ffsdrv.erase_suspend(); + } + else if (dev.state == DEV_WRITE) { + ffsdrv.write_end(); + } + + return EFFS_OK; +} + +// Resume an erase operation that was in progress. +int ffs_end(int error) +{ +#if (TARGET == 1) + // Resume an erase in progress (only applicable if we are using a + // multi-bank device driver) + if (dev.state == DEV_ERASE_SUSPEND) { + ffsdrv.erase_resume(); + } +#else + debug_suspend = 0; +#endif + + return error; +} + +/****************************************************************************** + * FFS Statistics functions + ******************************************************************************/ + +// Not implemented: +int statistics_file_create(void) +{ + return 0; +} + +// Not implemented: +// Rewrite the statistics file if it exists. Otherwise return error +// code. The function is called after each data and inodes reclaim (after +// writing the file that provoked the reclaim). +int statistics_write(void) +{ + return 0; +} + +// Read the statistics file if it exists. Otherwise reset all statistics to +// zero and set the magic. This function is called from ffs_init(). +void statistics_init(void) +{ + memset(&stats, 0, sizeof(struct ffs_stats_s)); +} + +void statistics_update_drec(int valid, int lost, int candidate) +{ + unsigned int old; + + switch (candidate) { + case MOST_LOST: stats.drec.most_lost++; break; + case MOST_UNUSED: stats.drec.most_unused++; break; + case YOUNGEST: stats.drec.youngest++; break; + } + + // Increment Most Significant Word if overflow is detected + old = stats.drec.valid[0]; + stats.drec.valid[0] += valid; + if (old > stats.drec.valid[0]) + stats.drec.valid[1]++; + + old = stats.drec.lost[0]; + stats.drec.lost[0] += lost; + if (old > stats.drec.lost[0]) + stats.drec.lost[1]++; +} + +void statistics_update_irec(int valid, int lost) +{ + stats.irec.num++; + stats.irec.valid += valid; + stats.irec.lost += lost; +} +