www.pudn.com > drivers.rar > fsck.c
/****************************************************************************** * 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#include #include "ffs/ffs.h" #include "ffs/board/core.h" #include "ffs/board/drv.h" #include "ffs/board/ffstrace.h" #if (TARGET == 0 || WITH_TFFS == 1) #include "ffs/board/tffs.h" #endif #include "swconfig.cfg" /****************************************************************************** * Functions ******************************************************************************/ bref_t blocks_fsck(void); iref_t inodes_fsck(void); /****************************************************************************** * Init and Exit ******************************************************************************/ char ffs_initerror[5]; 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 ffs_initerror[0] = fs.initerror;//fangcjdebug 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 < 3; i++) { tlw(led_on(LED_BLOCKS_FSCK)); fs.initerror = EFFS_INVALID; fs.initerror = b = blocks_fsck(); ttr(TTrFatal, "xxx: blocks_fsck() return: %d,for(%d)" NL, fs.initerror, i); ffs_initerror[1] = fs.initerror;//fangcjdebug 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(); ffs_initerror[2] = fs.initerror; 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) { ffs_initerror[3] = fs.initerror; 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) #if (WITH_TFFS == 1) blocks_reclaim(); // target and test if (fs.repi_table[0] != 0) inodes_reclaim(); #endif #else blocks_reclaim(); // not target if (fs.repi_table[0] != 0) inodes_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; fs.is_reclaim_running = 0; // Flag that it not has been changed by an input arg. fs.block_files_max = 0; // 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 (LOCOSTO_LITE) // fs.chunk_size_max = 2048; //#else // The buffer size is optimized to 8k, since not much performance improvement seen with 16 k. // fs.chunk_size_max = 8192; //#endif 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 '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.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) { #if (OP_WCP == 1) // Make FFS inaccessible. fs.initerror = EFFS_AGAIN; // Finish pending commits/writes. if (ffs_remove("dummy") != EFFS_AGAIN) return EFFS_CORRUPTED; #else tw(tr(TR_FUNC, TrFsck, "exit() 0\n")); #endif return EFFS_OK; } /****************************************************************************** * 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; uint16 flags_msb, flags_lsb; 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); fs.format = bhp->version; // Format check is performed early because this new version thinks // that the old state is in an intermediate state thus it tries to // fix it and we can not allow that if ((bhp->magic_low == BLOCK_MAGIC_LOW && bhp->magic_high == BLOCK_MAGIC_HIGH) && (bhp->version >> 8) != (FFS_FORMAT_VERSION >> 8)) { tw(tr(TR_END, TrFsck, "} %d\n", EFFS_BADFORMAT)); ttw(ttr(TTrInitLow, "} %d" NL, EFFS_BADFORMAT)); return EFFS_BADFORMAT; } tw(tr(TR_FUNC, TrFsck, "Block %d, flags 0x%x\n", b, bhp->flags)); #if 0 // Check block header flag for intermediate states (01 or 10) // NOTEME add further comments? if ((bhp->flags & 0xAAAA) != ((bhp->flags & 0x5555) << 1)) { tw(tr(TR_FUNC, TrFsck,"Intermediate state detected in block header:\n")); // Push the flags to well defined states (01,10 -> 00) flags_msb = (bhp->flags << 1) & 0xAAAA & bhp->flags; flags_lsb = (bhp->flags >> 1) & 0x5555 & bhp->flags; tw(tr(TR_FUNC, TrFsck," blk %d (0x%x->0x%x) \n", b, bhp->flags, BIT_SET(bstat[b].flags, ~(flags_msb | flags_lsb)))); block_flags_write(b, ~(flags_msb | flags_lsb)); } #endif bstat[b].flags = bhp->flags; bstat[b].used = dev.blocksize; bstat[b].lost = bstat[b].used; 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. // 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 { 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; } } // 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, nrepi = 0; 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) memset(fs.repi_table, 0, sizeof(fs.repi_table)); // 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++; #if 1 // Check ERASE flag for intermediate states (10 or 01) if ((ip->flags & OT_ERASED_MSb) != ((ip->flags & (OT_ERASED_MSb >> 1)) << 1)) { tw(tr(TR_FUNC, TrFsck,"Intermediate state detected in inode flag:\n")); tw(tr(TR_FUNC, TrFsck, " inode %d (0x%x->0x%x)\n", i, ip->flags, ip->flags & OT_ERASED)); ffsdrv_write_byte(&ip->flags, ip->flags & OT_ERASED); } #endif tw(tr(TR_FUNC, TrFsck, "%3d 0x%05X %3d %3d %4d %3d %s%s%s%s%s%s%s %6d %s\n", i, location2offset(ip->location), ip->child, ip->sibling, ip->sequence, ip->updates, is_object(ip, OTE_DIR) ? " d" : "", is_object(ip, OTE_LINK) ? " l" : "", is_object(ip, OTE_FILE) ? " f" : "", is_object(ip, OTE_SEGMENT) ? " s" : "", is_object(ip, OT_REP) ? "rp" : "", is_object_erased(ip) ? " " : "", IS_BIT_SET(ip->flags, OFE_READONLY) && !is_object_erased(ip) ? "r" : " ", ip->size, // Erased chunks do not have any name so we cannot trace erased objects! (ip->size && !is_object(ip, OTE_SEGMENT) && !is_object_erased(ip) ? 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. Only exception is the // replacement inode, it is not counted as an object if (!is_object(ip, OT_REP)) { bstat[block].lost -= ip->size; bstat[block].objects++; } // test if this is the root inode. store index if it is. if (!is_object(ip, OTE_SEGMENT) && !is_object(ip, OT_REP)) { 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_erased(ip)) { // 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 = 0x%x)\n", ip->flags)); } if (is_object(ip, OT_REP)) { // Add the replacement inode in the ram table fs.repi_table[nrepi] = i; nrepi++; if (nrepi == FFS_REPLACEMENT_INODES_MAX) ffs_panic(EFFS_2MANYREPI); } } } 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 bhb; 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)); POWERFAIL_DOMAIN_BEGIN(PFM_BLOCKHEADER); POWERFAIL_SET_ADDR((int)bhp); 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)); } } bstat[b].flags = BF_IS_EMPTY; bstat[b].used = 0; bstat[b].lost = 0; bstat[b].objects = 0; block_flags_write(b, BF_WRITING); // Write block header from local buffer bhb.magic_low = BLOCK_MAGIC_LOW; bhb.magic_high = BLOCK_MAGIC_HIGH; bhb.version = FFS_FORMAT_VERSION; bhb.age = age; // Do not write flag and reserved ffsdrv.write(bhp, &bhb, sizeof(bhb.magic_low) + sizeof(bhb.magic_high) + sizeof(bhb.version) + sizeof(age_t)); block_flags_write(b, BF_FREE); POWERFAIL_DOMAIN_END(); 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(OTE_DIR); if ((fs.ijournal = journal_create(0)) < 0) { tw(tr(TR_END, TrFormat, "} %d\n", fs.ijournal)); return fs.ijournal; } fs.initerror = ffs_initialize(); ffs_initerror[4] = fs.initerror; 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_ram.repli = 0; fs.link_child = 1; //Default link child in journal_commit() if (oldi == 0) { fs.journal.diri = 0; fs.journal.oldi = 0; fs.journal_ram.flags = 0xFF; fs.journal_ram.location = 0; fs.journal_ram.size = 0; } else { struct inode_s *oldip = inode_addr(oldi); fs.journal.diri = oldi; fs.journal.oldi = oldi; fs.journal_ram.flags = oldip->flags | OF_MASK; // Clean valid flag fs.journal_ram.location = oldip->location; fs.journal_ram.size = oldip->size; } } 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))); POWERFAIL_DOMAIN_BEGIN(PFM_JOURNAL); POWERFAIL_SET_ADDR(0); // If this is a create, set the object type if (type != 0 && fs.journal.oldi == 0) fs.journal_ram.flags = (fs.journal_ram.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; } POWERFAIL_BOUNDARY(JOURNAL_TEST_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)); } POWERFAIL_BOUNDARY(JOURNAL_TEST_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); } POWERFAIL_BOUNDARY(JOURNAL_TEST_READY); journal_commit(0); // journal_commit() change the domain thus we must set it again POWERFAIL_DOMAIN_BEGIN(PFM_JOURNAL); POWERFAIL_SET_ADDR(0); POWERFAIL_BOUNDARY(JOURNAL_TEST_COMMITTED); // Advance journal file's state ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); POWERFAIL_BOUNDARY(JOURNAL_TEST_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) { if (fs.journal_pos == fs.journal_size - sizeof(struct journal_s)) ffs_panic(EFFS_JNLFULL); 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)); } POWERFAIL_DOMAIN_END(); tw(tr(TR_END, TrJournal, "}\n")); } // Write contents of fs.journal to FFS meta data (inodes). 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)); POWERFAIL_DOMAIN_BEGIN(PFM_INODES); POWERFAIL_SET_ADDR(0); if (fs.journal.i) { struct inode_s ib; // Inode buffer // Set all elements to 0xFF.. NOTEME: do this in another way? memcpy(&ib, ip, sizeof(ib)); #if (LINEAR_FILE_SYSTEM) if(fs.journal.align == 0xee) { ib.reserved = 0xee; fs.journal.align = 0xff; } #endif // If this is a create, set the object type if (type != 0 && fs.journal.oldi == 0) fs.journal_ram.flags = (fs.journal_ram.flags & OF_MASK) | type; tw(tr(TR_FUNC, TrJournal, "loc = 0x%04x, size = %d\n", fs.journal_ram.location, fs.journal_ram.size)); ib.location = fs.journal_ram.location; ib.size = fs.journal_ram.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! ib.child = get_child(oldip); 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) ib.sequence = fs.sequence++; if (fs.journal.oldi == 0) ib.updates = 0; else ib.updates = oldip->updates + 1; // 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_ram.flags)); ib.flags = ip->flags & fs.journal_ram.flags; // Write the inode to flash ffsdrv.write(ip, &ib, sizeof(ib)); // 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(get_repi(-fs.journal.diri)); ffsdrv.write_halfword((uint16 *) &dp->child, fs.journal.i); } else { tw(tr(TR_NULL, TrJournal, "sibling\n")); dp = inode_addr(get_repi(fs.journal.diri)); ffsdrv.write_halfword((uint16 *) &dp->sibling, fs.journal.i); } } // 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)); POWERFAIL_BOUNDARY(JOURNAL_TEST_COMMITTING); // Validate the object (write valid flags) ffsdrv_write_byte(&ip->flags, (ip->flags & OT_VALID)); } 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 & oldip->flags); // 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_ram.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_ram.repli > 0) object_remove(fs.journal_ram.repli); // Ignore error! } POWERFAIL_DOMAIN_END(); 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)); memcpy(&fs.ojournal_ram, &fs.journal_ram, sizeof(struct journal_ram_s)); fs.journal_depth++; if (fs.journal_depth > 1) { ffs_panic(EFFS_JNLPUSH2DPTH); 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) { ffs_panic(EFFS_JNLPOP2DPTH); return -1; } memcpy(&fs.journal, &fs.ojournal, sizeof(struct journal_s)); memcpy(&fs.journal_ram, &fs.ojournal_ram, sizeof(struct journal_ram_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; 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; } } ip = inode_addr(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) { struct inode_s *obj_ip = inode_addr(addr->i); tw(tr(TR_FUNC, TrJournal, "First non-complete journal is in READY state\n")); // Is the object valid? if (is_object_valid(obj_ip)) { struct inode_s *ip; // Copy the entry into fs.journal. tw(tr(TR_FUNC, TrJournal, "Last journal is READY/PREVALID\n")); memcpy(&fs.journal, addr, sizeof(fs.journal)); ip = inode_addr(fs.journal.i); // The existing fields 'flags', 'location' and 'size' is already // written but they are re-written in journal_commit() thus the // journal_ram struct must be filled with valid data. fs.journal_ram.flags = ip->flags ; fs.journal_ram.location = ip->location; fs.journal_ram.size = ip->size; journal_end(0); // Validate obj and maybe erase old obj i = 1; } else { // Skip last modify operation and replace the diri with a // replacement inode. Furthermore, clean the last allocated inode struct inode_s ib; // Clean inode memset(&ib, 0, sizeof(ib)); ffsdrv.write(obj_ip, &ib, sizeof(ib)); create_replacementinode(addr); ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); 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 WRITING and READY\n")); ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); fs.journal_pos += sizeof(fs.journal); } // Never create a new journal file if a replacementinode just have been // made because the new replacementinode is not yet added in the table // fs.repi_table[] and it could be the journal which is replaced. if (i == EFFS_OK) { 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 there is use an old FFS image with a newer FFS // version. if ((i = journal_create(fs.ijournal)) <= 0) { fs.ijournal = 0; return i; } } // Minimize the risk of running out of journal space. if (fs.journal_pos >= fs.journal_size - FFS_JOURNAL_MARGIN * sizeof(struct journal_s)) { tw(tr(TR_FUNC, TrJournal, "Journal file (near) full! Create new\n")); 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_ram.flags = BIT_SET(fs.journal_ram.flags, OFE_READONLY); // commit the creation or relocation if (oldi != 0) journal_end(0); else { journal_commit(OTE_FILE); fs.journal_pos = JOURNAL_POS_INITIAL; } tw(tr(TR_END, TrJournal, "} %d\n", i)); return i; } /****************************************************************************** * Replacementinode ******************************************************************************/ // Validate the replacement inode, mark old diri as replaced and close // journal entry void validate_replacementinode(struct journal_s *addr) { struct inode_s *ip, *dir_ip; iref_t zero[2] = {0, 0}; ip = inode_addr(addr->i); if (addr->oldi > 0) dir_ip = inode_addr(addr->oldi); else dir_ip = inode_addr(-addr->oldi); // Validate replacementinode ffsdrv_write_byte(&ip->flags, (OT_VALID & ip->flags)); // Mark directory inode as replaced (set child and sibling to zero) ffsdrv.write(&dir_ip->child, zero, sizeof(zero)); // Mark the creation of the replacementinode as done ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); fs.journal_pos++; if (fs.journal_pos == fs.journal_size) ffs_panic(EFFS_JNLFULL); } // Create a replacementinode for diri. It is possible that there exist a // non-complete replacement inode because the previous creation of the inode // was interrupted. In this case the inode will be finalized (if prevalid) // or removed so there can be made a new. void create_replacementinode(struct journal_s *old_addr) { int j; struct inode_s *ip, *dir_ip, ib; struct journal_s *addr = old_addr; tw(tr(TR_BEGIN, TrJournal, "create_repi(0x%x) {\n", old_addr)); tw(tr(TR_NULL, TrJournal, "Search for next non-complete journal entry:")); addr++; // Advance one journal entry (the current state is READY) for (j = 1 + fs.journal_pos / sizeof(fs.journal);/* 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); if ((addr->state == (uint8) JOURNAL_IS_READY) && is_object_valid(inode_addr(addr->i))) { validate_replacementinode(addr); return; } // If the journal entry not is EMPTY it must be a non completed // replacement inode which must be remove before there is created a new if (addr->state != (uint8) JOURNAL_IS_EMPTY) { tw(tr(TR_FUNC, TrJournal, "Remove old repi\n")); ffsdrv_write_byte(&addr->state, JOURNAL_IS_DONE); // Advance journal. addr++; fs.journal_pos++; if (fs.journal_pos == fs.journal_size) ffs_panic(EFFS_JNLFULL); } tw(tr(TR_FUNC, TrJournal, "create replacementinode\n")); journal_push(); journal_begin(0); // Alloc an inode. Note fs.inodes_max is temporarily added // a margin because this alloc is allowed to use the last inode fs.inodes_max += FFS_INODES_MARGIN; fs.journal.i = inode_alloc_try(); fs.inodes_max -= FFS_INODES_MARGIN; fs.journal.oldi = get_repi(old_addr->diri); fs.journal_ram.location = fs.journal.oldi > 0 ? fs.journal.oldi : -fs.journal.oldi; fs.journal_ram.flags = OT_REP; ffsdrv_write_byte(&addr->state, JOURNAL_IS_WRITING); fs.journal.state = JOURNAL_IS_WRITING; // Write ram journal to flash and advance journal to READY tw(tr(TR_FUNC, TrJournal, "Write ram journal to flash\n")); ffsdrv.write(addr, &fs.journal, sizeof(fs.journal)); ffsdrv_write_byte(&addr->state, JOURNAL_IS_READY); ip = inode_addr(fs.journal.i); // NOTEME: change the macro inode_addr() to handle a negative inode? if (addr->oldi > 0) dir_ip = inode_addr(addr->oldi); else dir_ip = inode_addr(-addr->oldi); tw(tr(TR_FUNC, TrJournal,"Replace i: %d (%s%s%s%s%s %s) with %d\n", fs.journal.oldi, is_object(dir_ip, OTE_DIR) ? " d" : "", is_object(dir_ip, OTE_LINK) ? " l" : "", is_object(dir_ip, OTE_FILE) ? " f" : "", is_object(dir_ip, OTE_SEGMENT) ? " s" : "", is_object(dir_ip, OT_REP) ? "rp" : "", (dir_ip->size && !is_object(dir_ip, OTE_SEGMENT) && !is_object_erased(dir_ip) && is_object_valid(dir_ip) ? addr2name(offset2addr(location2offset(dir_ip->location))) : ""), fs.journal.i)); // Init inode buffer // Set all elements to 0xFF.. NOTEME: do this in another way? memcpy(&ib, ip, sizeof(ib)); ib.size = ib.sequence = ib.updates = 0; ib.flags = fs.journal_ram.flags | OF_MASK; ib.location = fs.journal_ram.location; // Write the replacement inodes Child or Sibling field if (fs.journal.oldi < 0) { // diri child is maybe invalid thus we keep it as empty and only // write sibling tw(tr(TR_FUNC, TrJournal, "copy sibling: 0x%x\n", dir_ip->sibling)); ib.sibling = dir_ip->sibling; } else { // diri sibling is maybe invalid thus we keep it as empty and only // write child tw(tr(TR_FUNC, TrJournal, "copy child: 0x%x\n", dir_ip->child)); ib.child = dir_ip->child; } // Write the inode to flash ffsdrv.write(ip, &ib, sizeof(ib)); journal_pop(); validate_replacementinode(addr); tw(tr(TR_END, TrJournal, "} 0\n")); } /****************************************************************************** * 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(); } // Suspend a write in progress (only applicable if we are using a // multi-bank device driver) else if (dev.state == DEV_WRITE) { ffsdrv.write_suspend(); } 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(); // Resume a write in progress (only applicable if we are using a // multi-bank device driver) else if (dev.state == DEV_WRITE_SUSPEND) ffsdrv.write_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 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; }