www.pudn.com > drivers.rar > core.c
/****************************************************************************** * Flash File System (ffs) * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com * * FFS core functions (not public) * * $Id: core.c 1.156.1.13.1.1.1.50 Thu, 08 Jan 2004 15:05:23 +0100 tsj $ * ******************************************************************************/ #ifndef TARGET #include "ffs.cfg" #endif #include#include "ffs/ffs.h" #include "ffs/board/core.h" #include "ffs/board/drv.h" #include "ffs/board/ffstrace.h" #include "ffs/board/tmffs.h" #include #include #if (TARGET == 0) #include "ffs/board/tffs.h" #endif #include "swconfig.cfg" /****************************************************************************** * Globals ******************************************************************************/ struct fs_s fs; struct block_stat_s bstat[FFS_BLOCKS_MAX]; struct ffs_stats_s stats; // The following line is automatically expanded by the revision control // system to make a unique ffs revision. The revision can be retrieved by // ffs_query(). //$Format: "static const uint16 ffs_revision = ($ProjectMajorVersion$<<12)|(0x$ProjectMinorVersion$);"$ static const uint16 ffs_revision = (5<<12)|(0x60); /****************************************************************************** * Main Functions ******************************************************************************/ // Create a new ffs object (object type is undefined) iref_t object_create(const char *name, const char *buf, int size, iref_t dir) { iref_t i; int realsize, namelength; offset_t offset; char *dataaddr; char is_journal; char name_copy[FFS_FILENAME_MAX + 1]; // NOTEME: make dynamic? ttw(ttr(TTrObj, "ocr(%s){" NL, name)); tw(tr(TR_BEGIN, TrObject, "object_create('%s', ?, %d, %d) {\n", name, size, dir)); POWERFAIL_DOMAIN_BEGIN(PFM_CHUNK); // NOTEME: special case just for format()!? if (dir == 0) namelength = ffs_strlen(name); else namelength = is_filename(name); if (namelength < 0) { tw(tr(TR_END, TrObject, "} %d\n", namelength)); ttw(ttr(TTrObj, "} %d" NL, namelength)); return namelength; } is_journal = (name[0] == '.' && ffs_strcmp(name, FFS_JOURNAL_NAME) == 0); if (is_journal) tw(tr(TR_FUNC, TrObject, "Journal file creation!\n")); else if (buf == NULL && size > 0) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); ttw(ttr(TTrObj, "} %d" NL, EFFS_INVALID)); return EFFS_INVALID; } // We don't write the data null_terminator if no data exists realsize = namelength + 1 + size + (size > 0 ? 1 : 0); fs.journal_ram.size = realsize = atomalign(realsize); // We save the diri in the ram journal because this will be updated if // chunk_alloc trigger a data_reclaim fs.journal.diri = dir; // We have to make a local copy of name because name can be destroyed if // it points into an object that is relocated by an ffs_data_reclaim. memcpy(name_copy, name, ffs_strlen(name) + 1); if ((i = chunk_alloc(realsize, is_journal, &offset)) < 0) return i; POWERFAIL_SET_ADDR((uint32)addr2name(offset2addr(offset))); // Write filename including null-terminator ffsdrv.write(addr2name(offset2addr(offset)), name_copy, namelength + 1); // Write data and null terminator. We null-terminate the data block, // such that blocks_fsck() can determine the amount of used data block // space correctly. Note that we don't write null-terminator for objects // with no data, e.g. empty files and directories. if (size > 0) { dataaddr = addr2name(offset2addr(offset)) + namelength + 1; // Do NOT write data if we are creating the journal file --- it must // be created as empty! #if (LINEAR_FILE_SYSTEM) if (!is_journal && (unsigned long)(buf) != 0xFFFFFFFFUL) ffsdrv.write(dataaddr, buf, size); #else if (!is_journal) ffsdrv.write(dataaddr, buf, size); #endif ffsdrv_write_byte(dataaddr + size, 0); } // Insert object in parent directory if this is not the root dir if (dir != 0) fs.journal.diri = dir_traverse(fs.journal.diri, 0); else fs.journal.diri = 0; POWERFAIL_DOMAIN_END(); tw(tr(TR_END, TrObject, "} %d\n", i)); ttw(ttr(TTrObj, "} %d" NL,i)); return i; } int file_read(const char *name, void *addr, int size) { int size_read, object_size, total_read = 0; iref_t i, dir; char *not_used; fd_t fdi; if (size < 0) return EFFS_INVALID; if ((i = object_lookup(name, ¬_used, &dir)) < 0) return i; if ((fdi = get_fdi(i)) >= 0) if (is_open_option(fs.fd[fdi].options, FFS_O_WRONLY)) return EFFS_LOCKED; object_size = object_datasize(i); do { size_read = segment_read(i, (char*)addr + total_read, size - total_read, 0); total_read += size_read; } while ((i = segment_next(i)) != 0 && size > total_read); // Did we read the comlete object? if (object_size > size) return EFFS_FILETOOBIG; return total_read; // number of bytes read } int stream_read(fd_t fdi, void *src, int size) { int offset, size_read = 0, copied = 0; iref_t i; if (!is_fd_valid(fdi)) return EFFS_BADFD; if (!is_open_option(fs.fd[fdi].options, FFS_O_RDONLY)) return EFFS_INVALID; if (src == NULL || size < 0) return EFFS_INVALID; // NOTEME: do this in another way? // No data to read because fp is ad eof. if (fs.fd[fdi].fp >= fs.fd[fdi].size) { tw(tr(TR_FUNC, TrObject, "eof(no data read)\n")); return 0; } #if (FFS_SEEK_OPT == 1) i =fs.fd[fdi].rch; offset = fs.fd[fdi].roffset; #else segfile_seek(fs.fd[fdi].seghead, fs.fd[fdi].fp, &i, &offset); #endif // Read data from chunks or buffer until all data is read or eof is reach. do { if (is_offset_in_buf(fs.fd[fdi].fp, fdi)) { offset = fs.fd[fdi].fp - fs.fd[fdi].wfp; size_read = size - copied; // requested data that is left // Saturate size to max left in buf or max left to eof if (size_read > (fs.chunk_size_max - offset)) size_read = fs.chunk_size_max - offset; if (size_read > (fs.fd[fdi].size - fs.fd[fdi].fp)) size_read = fs.fd[fdi].size - fs.fd[fdi].fp; memcpy((char*)src + copied, fs.fd[fdi].buf + offset, size_read); } else { #if (FFS_READ_CACHE_ENBALE == 1) size_read = segment_read_cache(fdi, i, (char*)src+copied, size-copied, offset); #else // Data is only in the chunk size_read = segment_read(i, (char*) src + copied, size - copied, offset); #endif #if (FFS_SEEK_OPT == 1) /* record the current chunk & offset */ fs.fd[fdi].rch = i; fs.fd[fdi].roffset = (offset+size_read); #endif } offset = 0; fs.fd[fdi].fp += size_read; copied += size_read; if ((i = segment_next(i)) < 0) return i; } while (copied != size && fs.fd[fdi].fp < fs.fd[fdi].size); if (copied == size) { tw(tr(TR_FUNC, TrObject, "All requested data has been read\n")); } if (fs.fd[fdi].fp >= fs.fd[fdi].size) { tw(tr(TR_FUNC, TrObject, "eof\n")); } return copied; // number of bytes read } int object_read(const char *name, char *buf, int size, int linkflag) { iref_t i; struct inode_s *ip; struct xstat_s stat; char *p; tw(tr(TR_BEGIN, TrObject, "object_read('%s', 0x%x, %d, %d) {\n", name, buf, size, linkflag)); if (buf == NULL || size < 0) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); return EFFS_INVALID; } i = object_stat(name, &stat, linkflag, 0, 0); if (i < 0) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_NOTFOUND)); return i; } ip = inode_addr(i); if (stat.size > size) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_FILETOOBIG)); return EFFS_FILETOOBIG; } // return error if called as readlink() and object is not a link if (!is_object(ip, OTE_LINK) && linkflag) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); return EFFS_INVALID; } // Even though the ffs architecture allows to have data in directory // objects, we don't want to complicate matters, so we return an error if (is_object(ip, OTE_DIR) && !(fs.flags & FS_DIR_DATA)) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_NOTAFILE)); return EFFS_NOTAFILE; } p = offset2addr(location2offset(ip->location)); size = stat.size; p = addr2data(p, ip); // Copy data. NOTEME: Should be optimized! while (size--) *buf++ = *p++; tw(tr(TR_END, TrObject, "} %d\n", stat.size)); return stat.size; } // Convert an object data addres to pure data address char *addr2data(const char *addr, const struct inode_s *ip) { // OT_SEGMENT is pure data so it do not have any name to skip if (!is_object(ip, OTE_SEGMENT)) { while (*addr++) ; } return (char *) addr; } // Calculate exact size of file data; without filename and null terminator // and without data null terminator and succeeding alignment padding. // NOTEME: Does this also work for empty files and directories? int object_datasize(iref_t i) { iref_t not_used; return segfile_seek(i, INT_MAX, ¬_used, 0); } iref_t object_stat(const char *name, struct xstat_s *stat, int linkflag, int fdi, int extended) { iref_t i; fd_t other_fdi; struct inode_s *ip; tw(tr(TR_BEGIN, TrObject, "object_stat('%s', ?, %x, %d, %d) {\n", name, linkflag, fdi, extended)); if (stat == NULL) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); return EFFS_INVALID; } if (linkflag) i = object_lookup_once(name, 0, 0); else if (name == 0) { fdi -= FFS_FD_OFFSET; if (!is_fd_valid(fdi)) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_BADFD)); return EFFS_BADFD; } i = fs.fd[fdi].seghead; } else i = object_lookup(name, 0, 0); if (i > 0) { ip = inode_addr(i); switch (ip->flags & OT_MASK) { case OTE_FILE: stat->type = OT_FILE; break; case OTE_SEGMENT: stat->type = OT_SEGMENT; break; case OTE_DIR: stat->type = OT_DIR; break; case OTE_LINK: stat->type = OT_LINK; break; default: stat->type = ip->flags & OT_MASK; } switch (~ip->flags & 0x1) { case OFE_READONLY: stat->flags = OF_READONLY; break; default: stat->flags = ~ip->flags & 0x1; } stat->inode = i; // If the file is open so get the size from the file descriptor if ((other_fdi = get_fdi(i)) >= 0) { if (i == fs.fd[other_fdi].seghead) { stat->size = fs.fd[other_fdi].size; } } else stat->size = object_datasize(i); if (extended) { stat->location = ip->location; stat->block = offset2block(location2offset(stat->location)); stat->space = ip->size; while ((i = segment_next(i)) > 0) { ip = inode_addr(i); stat->space += ip->size; } stat->reserved = 0; stat->sequence = ip->sequence; stat->updates = ip->updates; } } tw(tr(TR_END, TrObject, "} %d\n", i)); return i; } /****************************************************************************** * Remove and Rename ******************************************************************************/ // Delete a ffs object effs_t object_remove(iref_t i) { struct inode_s *ip = inode_addr(i); iref_t entries; tw(tr(TR_BEGIN, TrObject, "object_remove(%d) {\n", i)); // if object is a dir, ensure it is empty if (is_object(ip, OTE_DIR)) { dir_traverse(-i, &entries); if (entries) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_DIRNOTEMPTY)); return EFFS_DIRNOTEMPTY; } } // We don't actually journal deletions, this is why we call // journal_commit() instead of journal_end(). We have to set // journal.location to something else, otherwise journal_commit() will // not discount the number of bytes lost by this delete. if (is_object(ip, OTE_DIR)) { journal_begin(i); fs.journal_ram.location = 0; journal_commit(0); } else { // NOTE: This is not nice if we get a break down however the // remaning chunks will be removed later by a block reclaim. do { journal_begin(i); fs.journal_ram.location = 0; journal_commit(0); } while ((i = segment_next(i)) != 0); } tw(tr(TR_END, TrObject, "} %d\n", EFFS_OK)); return EFFS_OK; } // Rename an object. is the new name. iref_t object_rename(iref_t oldi, const char *newname, iref_t newdir) { iref_t newi; struct inode_s *oldip; char *olddata; int oldsize, namelength, realsize, offset; tw(tr(TR_BEGIN, TrObject, "object_rename(%d, '%s', %d) {\n", oldi, newname, newdir)); oldip = inode_addr(oldi); oldsize = segment_datasize(oldip); // Make sure that there is enough space to make the rename without // object_create() trigger a data_reclaim() (awoid relocate oldi/data // source) namelength = is_filename(newname); realsize = namelength + 1 + oldsize + (oldsize > 0 ? 1 : 0); realsize = atomalign(realsize); // Save newdir in fs.xx because it will be updated if it is relocated. fs.i_backup = newdir; if ((offset = data_prealloc(realsize)) <= 0) return EFFS_NOSPACE; // Use fs.journal.oldi because i would have been updated if // data_reclaim() relocate oldi oldip = inode_addr(fs.journal.oldi); olddata = offset2addr(location2offset(oldip->location)); olddata = addr2data(olddata, oldip); newi = object_create(newname, olddata, oldsize, fs.i_backup); tw(tr(TR_END, TrObject, "} %d\n", newi)); return newi; } /****************************************************************************** * Object Lookup ******************************************************************************/ // Lookup an object. Symlinks are followed. iref_t object_lookup(const char *path, char **leaf, iref_t *dir) { iref_t i; struct inode_s *ip; tw(tr(TR_BEGIN, TrLookup, "object_lookup('%s', ?, ?) {\n", path)); ttw(ttr(TTrInode, "olu(%s){" NL, path)); i = object_lookup_once(path, leaf, dir); ip = inode_addr(i); if (i > 0 && is_object(ip, OTE_LINK)) { path = offset2addr(location2offset(ip->location)); path += ffs_strlen(path) + 1; // point to data portion i = object_lookup_once(path, leaf, dir); // Links may only point to regular files... ip = inode_addr(i); if (+i > 0 && !is_object(ip, OTE_FILE)) i = EFFS_NOTAFILE; } else { if (leaf) leaf = 0; if (dir) dir = 0; } tw(tr(TR_END, TrLookup, "} (%d, '%s') %d\n", (dir ? *dir : 0), (leaf ? *leaf : ""), i)); ttw(ttr(TTrInode, "} %d" NL, i)); return i; } // Lookup an object. If object is found: Return iref of object and // directory of object in . If object is not found: Return // EFFS_NOTFOUND and last directory component of path in and leafname // of pathname in iref_t object_lookup_once(const char *path, char **leaf, iref_t *dir) { iref_t i, j, d; struct inode_s *ip; const char *p, *q; uint8 depth = 1; tw(tr(TR_FUNC, TrLookup, "object_lookup_once('%s', ?, ?) { ", path)); if (path == NULL) return EFFS_BADNAME; d = fs.root; if (*path == '/') { path++; // silently ignore and skip prefix slash // root directory is a special case if (*path == 0) { j = d; if (leaf) *leaf = (char *) path; if (dir) *dir = 0; tw(tr(TR_NULL, TrLookup, "} ('%s', %d) %d\n", path, 0, j)); return j; } } else return EFFS_BADNAME; // set default return value if root dir is completely empty // (child link empty) j = EFFS_NOTFOUND; ip = inode_addr(d); while ((i = get_child(ip)) != (iref_t) IREF_NULL) { j = 0; // default to not found do { tw(tr(TR_NULL, TrLookup, "i%d ", (int) i)); p = path; ip = inode_addr(i); if (is_object_valid(ip) && !is_object(ip, OTE_SEGMENT)) { q = addr2name(offset2addr(location2offset(ip->location))); tw(tr(TR_NULL, TrLookup, "%s ", q)); while (*p == *q && *p != 0 && *q != 0) { p++; q++; } if (*q == 0 && (*p == 0 || *p == '/')) { j = i; break; } } } while ((i = get_sibling(ip)) != (iref_t) IREF_NULL); if (j == 0) { // we did not find this component of the path. Let's // see if this was the leafname component or not... while (*p != 0 && *p != '/') p++; if (*p == 0) // The path component was indeed the leafname j = EFFS_NOTFOUND; else // The path component was not the last, so it // obviously contained an object that was not a // directory. j = EFFS_NOTADIR; break; } if (*p == '/') { // if there are more path components, the object found // must be a directory... if (!is_object(ip, OTE_DIR)) { j = EFFS_NOTADIR; break; } if (++depth > fs.path_depth_max) { j = EFFS_PATHTOODEEP; break; } path = p + 1; d = i; // if this dir inode has no children, we will leave the // while loop, so we preset the return error code. NOTEME: // Not strictly correct because if we still have a lot of // the pathname left, it should return the error // EFFS_NOTADIR j = EFFS_NOTFOUND; tw(tr(TR_NULL, TrLookup, "/ ")); } else { // It is a fact that *p == 0. So we found the object! break; } } if (leaf) *leaf = (char *) path; if (dir) *dir = d; tw(tr(TR_NULL, TrLookup, "} (%d, '%s') %d\n", d, path, j)); return j; } /****************************************************************************** * Directory Operations ******************************************************************************/ // Open a directory, returning the iref of the directory's inode. iref_t dir_open(const char *name) { iref_t i; struct inode_s *ip; tw(tr(TR_BEGIN, TrDirHigh, "dir_open('%s') {\n", name)); if ((i = object_lookup(name, 0, 0)) < 0) { tw(tr(TR_END, TrDirHigh, "} %d\n", i)); return i; } ip = inode_addr(i); if (!is_object(ip, OTE_DIR)) i = EFFS_NOTADIR; tw(tr(TR_END, TrDirHigh, "} %d\n", i)); return i; } // Return name and iref of next entry in directory . is the last // entry we returned from this function. In case this is the first call // after the initial call to dir_open(), equals . iref_t dir_next(iref_t dir, iref_t i, char *name, int8 size) { struct inode_s *ip = inode_addr(i); char *p; tw(tr(TR_BEGIN, TrDirHigh, "dir_next(%d, %d, ?, %d) {\n", dir, i, size)); i = (i == dir ? get_child(ip) : get_sibling(ip)); while (i != (iref_t) IREF_NULL) { ip = inode_addr(i); if (is_object_valid(ip)) { p = offset2addr(location2offset(ip->location)); while (size-- && (*name++ = *p++)) ; break; } i = get_sibling(ip); } if (i == (iref_t) IREF_NULL) i = 0; tw(tr(TR_END, TrDirHigh, "} %d\n", i)); return i; } // Traverse a directory given by inode reference . If is negative, it // refers to the actual directory so we start by traversing the child link. // Otherwise if is positive, it refers to an entry within the directory // and we only traverse sibling links. Returns iref of last object in // directory (or negative iref of directory if the child link is empty). // is number of non-deleted objects in the dir (only valid if // traversed from the start, eg. with negative ). iref_t dir_traverse(iref_t i, iref_t *entries) { iref_t j = 0, valid = 0, erased = 0, invalid = 0; struct inode_s *ip; tw(tr(TR_FUNC, TrDirLow, "dir_traverse(%d, ?) { ", i)); if (i < 0) { // If directory's child is empty, this is a virgin directory and we // return negative iref of the directory itself. j = i; i = -i; ip = inode_addr(i); i = get_child(ip); } if (i != (iref_t) IREF_NULL) { do { if (j == i) { tw(tr(TR_NULL, TrDirLow, "LOOP! ")); return EFFS_SIBLINGLOOP; } j = i; ip = inode_addr(j); tw(tr(TR_NULL, TrDirLow, "%d/%x ", j, ip->flags)); if (is_object_valid(ip)) valid++; else if (is_object_erased(ip)) erased++; else invalid++; } while ((i = get_sibling(ip)) != (iref_t) IREF_NULL); } if (entries != 0) *entries = valid; tw(tr(TR_NULL, TrDirLow, "} (valid = %d, erased = %d, invalid = %d) %d\n", valid, erased, invalid, j)); return j; } /****************************************************************************** * Block, Inode and Data Allocation ******************************************************************************/ // Find the youngest free block. Return block index on success. If the // argument is zero, this is a normal alloc and it will leave at // least fs.blocks_free_min spare blocks. Otherwise, if it is non-zero, it // is a privileged alloc (initiated by a reclaim operation) and it will not // necessarily leave any spare blocks. bref_t block_alloc(bref_t priority, uint16 flags) { bref_t i, b, b_min, b_max, blocks_free; struct block_header_s *bhp; age_t age, age_min, age_max; tw(tr(TR_BEGIN, TrBlock, "block_alloc(%d, 0x%x) {\n", priority, flags)); ttw(ttr(TTrData, "ba(%d,0x%x) {" NL, priority, flags)); age_min = BLOCK_AGE_MAX; age_max = 0; blocks_free = 0; b_min = b_max = -1; tw(tr(TR_FUNC, TrBlock, "blocks(age): ")); for (i = dev.numblocks - 1; i >= 0; i--) { if (is_block(i, BF_IS_FREE)) { blocks_free++; bhp = (struct block_header_s *) offset2addr(dev.binfo[i].offset); age = bhp->age; tw(tr(TR_NULL, TrBlock, "%d(%d) ", i, age)); // Remember index of block found. We use '<=' and '>=' operators // (instead of '<' and '>') to ensure we have both limits // properly set on exit from this loop. if (age <= age_min) { b_min = i; age_min = age; } if (age >= age_max) { b_max = i; age_max = age; } } } tw(tr(TR_NULL, TrBlock, "\n")); // Handle age wrap around b = b_min; if (b_min != -1) { // Either age_max really is max age, so b_min is youngest block OR // age_max really is min age, so b_max is youngest block b = (age_max - age_min) < 0x8000 ? b_min : b_max; } // Only privileged allocs will get the last free block if (blocks_free <= fs.blocks_free_min - priority) { b = -1; tw(tr(TR_FUNC, TrBlock, "Only %d block(s) left, required = %d\n", blocks_free, fs.blocks_free_min - priority)); } else { // Prepare/format the block for holding data/inodes if (flags == BF_DATA) { bstat[b].used = BHEADER_SIZE; bstat[b].lost = 0; bstat[b].objects = 0; block_flags_write(b, BF_DATA); } else if (flags == BF_COPYING) { // This code is used on a fresh format and when allocating a new // block for reclaiming inodes block_flags_write(b, BF_COPYING); bstat[b].used = 0; bstat[b].lost = 0; bstat[b].objects = 1; // first inode to be allocated } else { tw(tr(TR_FUNC, TrBlock, "FATAL: Bad input (flags = 0x%X)\n", flags)); } } tw(tr(TR_END, TrBlock, "} (%d) %d\n", blocks_free, b)); ttw(ttr(TTrData, "} 0x%x" NL, b)); return b; } // Free and schedule a block for erase. void block_free(bref_t b) { tw(tr(TR_BEGIN, TrBlock, "block_free(%d) {\n", b)); // mark block as invalid and schedule erasure block_flags_write(b, BF_LOST); block_reclaim(b); tw(tr(TR_END, TrBlock, "}\n")); } void block_flags_write(uint8 block, uint16 flags) { struct block_header_s *bhp = (struct block_header_s *) offset2addr(dev.binfo[block].offset); tw(tr(TR_BEGIN, TrBlock, "block_flags_write(%d, 0x%x)\n", block, flags)); POWERFAIL_DOMAIN_BEGIN(PFM_BLOCKHEADER); POWERFAIL_SET_ADDR((int)bhp); bstat[block].flags = BIT_SET(bstat[block].flags, flags); ffsdrv.write_halfword((uint16 *) &bhp->flags, bstat[block].flags ); POWERFAIL_DOMAIN_END(); tw(tr(TR_END, TrBlock, "")); } // Allocate an inode for a new object. We use bstat[fs.inodes].objects to // start our scan for a free inode instead of starting from the first time // each time. iref_t inode_alloc(void) { iref_t i; tw(tr(TR_BEGIN, TrInode, "inode_alloc() {\n")); ttw(ttr(TTrInode, "i_a() {" NL)); if ((i = inode_alloc_try()) == 0) { // NOTEME we are not always out of inodes, maybe there exist to many // objects! It will not help to reclaim the inodes in that case! tw(tr(TR_FUNC, TrInode, "NOTE: Out of free inodes...\n")); inodes_reclaim(); i = inode_alloc_try(); } tw(tr(TR_END, TrInode, "} %d\n", i)); ttw(ttr(TTrInode, "} %d" NL, i)); return i; } iref_t inode_alloc_try(void) { iref_t i = fs.inodes_max; struct inode_s *ip; // If we have not yet reached the maximum allowed number of objects, // search for next free inode... if (bstat[fs.inodes].used - bstat[fs.inodes].lost < fs.objects_max) { ip = inode_addr(bstat[fs.inodes].objects); for (i = bstat[fs.inodes].objects; i < fs.inodes_max - FFS_INODES_MARGIN; i++, ip++) { if (ip->location == FLASH_NULL32) { bstat[fs.inodes].objects = i; bstat[fs.inodes].used++; break; } } } if (i >= fs.inodes_max - FFS_INODES_MARGIN) i = 0; tw(tr(TR_FUNC, TrInode, "inode_alloc_try() %d\n", i)); ttw(ttr(TTrInode, "i_a_t() %d" NL, i)); return i; } // NOTEME: Should file data be word aligned to enable faster reads and // writes in word quantities AND to be more compatible with the inherent // 16-bit access width of flash memories? offset_t data_alloc(int size) { offset_t offset = 0; bref_t b; tw(tr(TR_BEGIN, TrData, "data_alloc(%d) {\n", size)); ttw(ttr(TTrData, "da(%d) {" NL, size)); offset = data_prealloc(size); // If we did allocate the space, we update bstat[] if (offset > 0) { b = offset2block(offset); bstat[b].used += size; stats.data_allocated += size; // STATS } tw(tr(TR_END, TrData, "} 0x%04x\n", offset)); ttw(ttr(TTrData, "} %x" NL, offset)); return offset; } offset_t data_prealloc(int realsize) { int result, i, bytes_free; offset_t offset; // Is it possible to get this amount of free space and still have enough // reserved space? ffs_query(Q_BYTES_FREE_RAW, &bytes_free); if (realsize > (bytes_free + FFS_FILENAME_MAX + dev.atomsize)) return 0; // Not enough unused space for (i = 0; i < dev.numblocks; i++) { if ((offset = data_alloc_try(realsize)) > 0) return offset; // Space found if ((result = data_reclaim()) < 0) return 0; // Data reclaim failed! } return 0; // No space found } // Find free data space of size . Return zero if no space available. // Note that we ensure that we always have space immediately available for a // privileged data_alloc(), e.g. a data_alloc() that allocates data space // without performing a data_reclaim(). This is important when // re-creating/re-locating the journal file. offset_t data_alloc_try(int size) { bref_t b; int free; offset_t offset_big = 0, offset_small = 0; int size_big_ok = 0, size_small_ok = 0; int size_big, size_small; int reserved; tw(tr(TR_FUNC, TrData, "data_alloc_try(%d) { ", size)); ttw(ttr(TTrData, "dat(%d) {" NL, size)); // NOTE when we alloc do we only need to have reserved space for X // number of journal files, where X is the max number of used journals // per data reclaim. The only exception is when an object_relocate has // failed thus we set reserved_space to zero. reserved = RESERVED_LOW; if (fs.reserved_space < reserved) reserved = fs.reserved_space; // Set size_big to the grater of the sizes and size_small to the lesser. size_big = (size > reserved ? size : reserved); size_small = (size > reserved ? reserved : size); tw(tr(TR_NULL, TrData, "(size_big, small = %d, %d) ", size_big, size_small)); // First search for free space in data blocks tw(tr(TR_NULL, TrData, "block:free,objects: ")); for (b = 0; b < dev.numblocks; b++) { if (is_block(b, BF_IS_DATA)) { free = dev.blocksize - bstat[b].used; tw(tr(TR_NULL, TrData, "%d:%d,%d ", b, free, bstat[b].objects)); if (bstat[b].objects < fs.block_files_max - fs.block_files_reserved) { if (!size_big_ok && !size_small_ok && (free >= size_big + size_small)) { size_big_ok = size_small_ok = 1; offset_big = offset_small = dev.binfo[b].offset + bstat[b].used; tw(tr(TR_NULL, TrData, "big/small_ok ")); break; } else if (!size_big_ok && free >= size_big) { size_big_ok = 1; offset_big = dev.binfo[b].offset + bstat[b].used; tw(tr(TR_NULL, TrData, "big_ok ")); } else if (!size_small_ok && free >= size_small) { size_small_ok = 1; offset_small = dev.binfo[b].offset + bstat[b].used; tw(tr(TR_NULL, TrData, "small_ok ")); } } } if (size_small_ok && size_big_ok) break; } if (size_big_ok && size_small_ok) offset_big = (size > reserved ? offset_big : offset_small); else offset_big = 0; tw(tr(TR_NULL, TrData, "} 0x%x\n", offset_big)); ttw(ttr(TTrData, "} %x " NL, offset_big)); return offset_big; } offset_t data_reserved_alloc(int size) { bref_t b; offset_t offset = 0; int free; tw(tr(TR_BEGIN, TrData, "data_reserved_alloc(%d) {\n", size)); ttw(ttr(TTrData, "dra(%d) {" NL, size)); tw(tr(TR_NULL, TrData, "block:free,objects: ")); for (b = 0; b < dev.numblocks; b++) { if (is_block(b, BF_IS_DATA)) { free = dev.blocksize - bstat[b].used; tw(tr(TR_NULL, TrData, "%d:%d,%d ", b, free, bstat[b].objects)); if (free >= size) { offset = dev.binfo[b].offset + bstat[b].used; break; } } } // If we did allocate the space, we update bstat[] if (offset != 0) { b = offset2block(offset); bstat[b].used += size; stats.data_allocated += size; // STATS } tw(tr(TR_END, TrData, "} 0x%04x\n", offset)); ttw(ttr(TTrData, "} %x" NL, offset)); return offset; } iref_t chunk_alloc(int realsize, int is_journal, offset_t *offset) { iref_t i; if (realsize < 0) return EFFS_INVALID; // Have we reached objects_max? We make a similar test in // inode_alloc_try(), however we need to do it here or else we risk to start // a data_reclaim we not can finish. if (bstat[fs.inodes].used - bstat[fs.inodes].lost >= fs.objects_max) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_FSFULL)); ttw(ttr(TTrObj, "} %d" NL, EFFS_FSFULL)); return EFFS_FSFULL; } // Allocate space for the object name (and object data) if (is_journal) *offset = data_reserved_alloc(realsize); else *offset = data_alloc(realsize); if (*offset == 0) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_NOSPACE)); ttw(ttr(TTrObj, "} %d" NL, EFFS_NOSPACE)); return EFFS_NOSPACE; } fs.journal_ram.location = offset2location(*offset); // Allocate an inode for the object i = fs.journal.i = inode_alloc(); if (i == 0) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_FSFULL)); ttw(ttr(TTrObj, "} %d" NL, EFFS_FSFULL)); return EFFS_FSFULL; } return i; } /****************************************************************************** * query and fcontrol ******************************************************************************/ extern uint16 ffs_flash_device; extern uint16 ffs_flash_manufact; effs_t object_control(iref_t i, int8 action, int value) { effs_t error = EFFS_OK; tw(tr(TR_BEGIN, TrOther, "object_control(%d, %d, 0x%x) {\n", i, action, value)); ttw(ttr(TTrApi, "obj_control(%d,%d,0x%x)" NL, i, action, value)); // Convert the flag from 'API' flag to internal flag switch (value) { case OF_READONLY: value = OFE_READONLY; } switch (action) { case OC_FLAGS: // Set/clear object flags. Attempting to modify the "/dev/ffs" // object (i = 0) or any non-defined flags is an invalid operation. if (i <= 0 || value & ~OF_ALL) { error = EFFS_INVALID; } else { // there are two cases; either we only set bits in the flags. // This is simple, as we just have to update the flags byte. The // other case is harder because we have to clear bits and for // this we have to copy the old inode to a new inode, setting // the flags appropriately. For now we always just allocate a // new inode and set the flags according to the // argument. journal_begin(i); fs.journal_ram.flags |= OF_MASK; // reset all flags fs.journal_ram.flags = BIT_SET(fs.journal_ram.flags, value); if ((fs.journal.i = inode_alloc()) == 0) error = EFFS_FSFULL; else { fs.journal.diri = dir_traverse(fs.journal.diri, 0); journal_end(0); } } break; case OC_FS_FLAGS: fs.flags = value; break; case OC_DEV_MANUFACT: ffs_flash_manufact = value; break; case OC_DEV_DEVICE: ffs_flash_device = value; break; case OC_FS_TESTFLAGS: fs.testflags = value; break; case OC_DEBUG_0: case OC_DEBUG_1: case OC_DEBUG_2: case OC_DEBUG_3: fs.debug[action - OC_DEBUG_FIRST] = value; break; case OC_TRACE_INIT: #if (TARGET == 1) ttr_init(value); #endif break; default: error = EFFS_INVALID; } tw(tr(TR_END, TrOther, "} %d\n", error)); return error; } extern int tmffs_bufsize(void); // used by ffs_query() extern unsigned char *tmffs_bufaddr(void); // used by ffs_query() #if (TARGET == 1) // request_id_last is only used in TARGET not to any use on the PC side extern req_id_t request_id_last; // from task.c #else req_id_t request_id_last; #endif // If tmffs not is represented we define a dummy tm version #ifndef FFS_TM_VERSION #define FFS_TM_VERSION ((uint16) 0x0BAD) #endif effs_t ffs_query(int8 query, void *p) { tw(tr(TR_FUNC, TrOther, "query(%d) (?)\n", query)); if (p == NULL) return EFFS_INVALID; switch (query) { case Q_BYTES_FREE: case Q_BYTES_USED: case Q_BYTES_LOST: case Q_BYTES_MAX: case Q_OBJECTS_TOTAL: case Q_BLOCKS_FREE: case Q_BYTES_FREE_RAW: { bref_t b; bref_t blocks_free = 0; iref_t objects = 0; offset_t max, used = 0, lost = 0; struct block_stat_s *bp; // Don't count free blocks, inode block, block header and reserved space. max = (dev.numblocks - fs.blocks_free_min - 1) * (dev.blocksize - BHEADER_SIZE) - fs.reserved_space; // Furthermore don't count the ovewrhead from each chunk (alignment) // NOTE: If we call query while FFS not is formatted there is a risk // of deviding with zero! if (fs.chunk_size_max > 0) max -= ((max / fs.chunk_size_max + 1) * dev.atomsize); for (b = 0, bp = &bstat[0]; b < dev.numblocks; b++, bp++) { if (is_block(b, BF_IS_FREE)) blocks_free++; if (is_block(b, BF_IS_DATA)) { objects += bp->objects; used += bp->used; lost += bp->lost; } } switch (query) { case Q_BYTES_FREE: *(uint32*)p = max - (used - lost) - FFS_FILENAME_MAX; break; case Q_BYTES_FREE_RAW:*(uint32*)p = max - (used - lost); break; case Q_BYTES_USED: *(uint32*)p = used; break; case Q_BYTES_LOST: *(uint32*)p = lost; break; case Q_BYTES_MAX: *(uint32*)p = max; break; case Q_OBJECTS_TOTAL: *(uint16*)p = objects; break; case Q_BLOCKS_FREE: *(uint16*)p = blocks_free; break; } break; } case Q_TM_BUFADDR: *(uint32*)p = (uint32) tmffs_bufaddr(); break; case Q_TM_BUFSIZE: *(uint32*)p = tmffs_bufsize(); break; case Q_DEV_BASE: *(uint32*)p = (uint32) dev.base; break; // FFS versions case Q_FFS_API_VERSION: *(uint16*)p = FFS_API_VERSION; break; case Q_FFS_DRV_VERSION: *(uint16*)p = FFS_DRV_VERSION; break; case Q_FFS_REVISION: *(uint16*)p = ffs_revision; break; case Q_FFS_FORMAT_WRITE: *(uint16*)p = FFS_FORMAT_VERSION; break; case Q_FFS_FORMAT_READ: *(uint16*)p = fs.format; break; case Q_FFS_LASTERROR: *(int16*)p = fs.initerror; break; case Q_FFS_TM_VERSION: *(int16*)p = FFS_TM_VERSION; break; // File system queries case Q_FILENAME_MAX: *(uint16*)p = fs.filename_max; break; case Q_PATH_DEPTH_MAX: *(uint16*)p = fs.path_depth_max; break; case Q_OBJECTS_FREE: *(uint16*)p = fs.objects_max - (bstat[fs.inodes].used - bstat[fs.inodes].lost); break; case Q_INODES_USED: *(uint16*)p = bstat[fs.inodes].used; break; case Q_INODES_LOST: *(uint16*)p = bstat[fs.inodes].lost; break; case Q_OBJECTS_MAX: *(uint16*)p = fs.objects_max; break; case Q_INODES_MAX: *(uint16*)p = fs.inodes_max; break; case Q_CHUNK_SIZE_MAX: *(uint16*)p = fs.chunk_size_max; break; // File descriptor queris case Q_FD_BUF_SIZE: *(uint32*)p = fs.fd_buf_size; break; case Q_FD_MAX: *(uint16*)p = fs.fd_max; break; // device queries case Q_DEV_MANUFACTURER: *(uint16*)p = dev.manufact; break; case Q_DEV_DEVICE: *(uint16*)p = dev.device; break; case Q_DEV_BLOCKS: *(uint16*)p = dev.numblocks; break; case Q_DEV_ATOMSIZE: *(uint16*)p = dev.atomsize; break; case Q_DEV_DRIVER: *(uint16*)p = dev.driver; break; // Miscellaneous/Internal case Q_BLOCKS_FREE_MIN: *(uint16*)p = fs.blocks_free_min; break; // Debug queries case Q_FS_FLAGS: *(uint16*)p = fs.flags; break; case Q_FS_INODES: *(uint16*)p = fs.inodes; break; case Q_FS_ROOT: *(uint16*)p = fs.root; break; case Q_STATS_DRECLAIMS: *(uint32*)p = stats.drec.most_lost + stats.drec.youngest; break; case Q_STATS_IRECLAIMS: *(uint32*)p = stats.irec.num; break; case Q_STATS_DATA_RECLAIMED: *(uint32*)p = stats.drec.valid[0] + stats.drec.lost[0]; break; case Q_STATS_INODES_RECLAIMED: *(uint32*)p = stats.irec.valid + stats.irec.lost; break; case Q_STATS_DATA_ALLOCATED: *(uint32*)p = stats.data_allocated; break; case Q_REQUEST_ID_LAST: *(uint32*)p = request_id_last; break; default: if (query >= Q_BSTAT && (query - Q_BSTAT) < dev.numblocks) { struct block_header_s *bhp; uint32 *myp = p; query -= Q_BSTAT; bhp = (struct block_header_s *) offset2addr(dev.binfo[query].offset); *myp++ = bstat[query].used; *myp++ = bstat[query].lost; // If we are in READ mode or this block is not lost, we can // safely read the age. Otherwise it is maybe currently erasing // and thus we cannot read the age. // NOTEME: Should this not have been handled by a driver function? if (dev.state == DEV_READ || !is_block_flag(query, BF_LOST)) *myp++ = (bhp->age << 16) | bstat[query].flags; else *myp++ = ( 0xFFFE << 16) | bstat[query].flags; *myp++ = bstat[query].objects; } else if (query >= Q_DEBUG_FIRST && query < Q_DEBUG_LAST) { *(uint32*)p = fs.debug[query - Q_DEBUG_FIRST]; } else return EFFS_INVALID; } return EFFS_OK; } /****************************************************************************** * Miscellaneous Helper Functions ******************************************************************************/ // Handle FATAL core errors void ffs_panic(int error) { tw(tr(TR_FUNC, TrAll, "** FATAL core error: %d **\n", error)); ttw(ttr(TTrTest, "** FATAL core error: %d **" NL, error)); #if (TARGET == 0) exit(1); #endif fs.initerror = error; } iref_t lookup_repi(iref_t i) { iref_t j; struct inode_s *rep_ip; tw(tr(TR_NULL, TrLookup, "Lookup repi..")); for (j = 0; j < FFS_REPLACEMENT_INODES_MAX; j++) { rep_ip = inode_addr(fs.repi_table[j]); if (i == rep_ip->location) { tw(tr(TR_NULL, TrLookup, "found repi %d\n", i)); return fs.repi_table[j]; } } ffs_panic(EFFS_NOREPINODE); return 0; } // If ip points to a inode which is marked as replaced (child 0x0), lookup // the replacement inode in the ram table and return the replacement inodes // child. iref_t lookup_child(struct inode_s *ip) { struct inode_s *rep_ip; iref_t i; // The object is marked as replaced thus the replacement inode must be // found. Note the replacement inode can also be replaced thus it is // necessary to continue lookup until a valid repi is found i = ip - fs.inodes_addr; do { i = lookup_repi(i); if (i == 0) return 0; // FIXME howto handle this internal error? rep_ip = inode_addr(i); } while (rep_ip->child == 0); return rep_ip->child; } iref_t lookup_sibling(struct inode_s *ip) { struct inode_s *rep_ip; iref_t i; // The object is marked as replaced thus the replacement inode must be // found. Note the replacement inode can also be replaced thus it is // necessary to continue lookup until a valid repi is found i = ip - fs.inodes_addr; do { i = lookup_repi(i); if (i == 0) return 0; // FIXME howto handle this internal error? rep_ip = inode_addr(i); } while (rep_ip->sibling == 0); return rep_ip->sibling; } // If the object has a replacement inode then return it else return i iref_t get_repi(iref_t i) { struct inode_s *ip; if (i > 0) ip = inode_addr(i); else ip = inode_addr(-i); while (ip->child == 0) { i = lookup_repi(i); if (i == 0) return 0; // FIXME howto handle this internal error? ip = inode_addr(i); } return i; } // Check if an object is read-only. Note that the root inode is always // readonly, no matter what! Returns error or original . iref_t is_readonly(iref_t i, const char *path) { struct inode_s *ip = inode_addr(i); tw(tr(TR_FUNC, TrObject, "is_readonly(%d, '%s') ", i, path)); if (i == fs.root || i == fs.ijournal || (IS_BIT_SET(ip->flags, OFE_READONLY) && !ffs_is_modifiable(path))) i = EFFS_ACCESS; tw(tr(TR_NULL, TrObject, "(0x%X) %d\n", ip->flags, i)); return i; } // Check if filename is valid. Return EFFS_BADNAME if name contains // invalid chars. Return RFFS_NAMETOOLONG if name is too // long. Otherwise return filename length. effs_t is_filename(const char *s) { char *p = (char *) s; int n = 0; while ( (*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9') || *s == '.' || *s == ',' || *s == '_' || *s == '-' || *s == '+' || *s == '%' || *s == '$' || *s == '#' ) { s++; } if (*s != 0) n = EFFS_BADNAME; // invalid file name character found else { n = s - p; if (n > fs.filename_max) n = EFFS_NAMETOOLONG; if (n == 0) n = EFFS_BADNAME; } tw(tr(TR_FUNC, TrUtil, "is_filename('%s') %d\n", p, n)); return n; } int ffs_strlen(const char *s) { const char *p = s; while (*p++) ; tw(tr(TR_FUNC, TrUtil, "strlen('%s') %d\n", s, p-s-1)); return p-s-1; } // Return zero if strings are equal, otherwise return non-zero. int ffs_strcmp(const char *s, const char *p) { int8 n = 1; tw(tr(TR_FUNC, TrUtil, "strcmp('%s', '%s') ", s, p)); while (*s == *p && *p != 0) { s++; p++; } if (*s == *p) n = 0; tw(tr(TR_NULL, TrUtil, "(%d)\n", n)); return n; } fd_t get_fdi(iref_t i) { int j; tw(tr(TR_FUNC, TrUtil, "get_fdi(%d)\n", i)); if (i > 0) { for (j = 0; j < fs.fd_max; j++) { if (i == fs.fd[j].seghead) { return j; // Return fdi without offset } } } return -1; } effs_t is_fd_valid(fd_t fdi) { if (fdi >= fs.fd_max || fdi < 0 || fs.fd[fdi].options == 0) return 0; // Not valid! return 1; } effs_t is_offset_in_buf(int offset, fd_t fdi) { if (fs.fd[fdi].dirty == 1) if (offset >= fs.fd[fdi].wfp && offset < fs.fd[fdi].wfp + fs.chunk_size_max) return 1; return 0; } /****************************************************************************** * Chunk Operations ******************************************************************************/ iref_t segment_create(const char *buf, int size, iref_t dir) { iref_t i; struct inode_s *ip; int realsize; offset_t offset; char *dataaddr; ttw(ttr(TTrObj, "segc(%d, %d){" NL, size, dir)); tw(tr(TR_BEGIN, TrObject, "segment_create( ?, %d, %d) {\n", size, dir)); fs.journal_ram.size = realsize = atomalign(size + 1); // Init journal.diri before chunk_alloc() because it might trigger a // data_reclaim() which can relocate the dir inode fs.journal.diri = dir; if ((i = chunk_alloc(realsize, 0, &offset)) < 0) return i; ip = inode_addr(i); dataaddr = offset2addr(offset); // Write data and null terminator. We null-terminate the data block, // such that blocks_fsck() can determine the amount of used data block // space correctly. ffsdrv.write(dataaddr, buf, size); dataaddr += size; ffsdrv_write_byte(dataaddr, 0); // Segments is linked together by the child link(create) or by the // sibling link(update or relocate). A negativ dir indicate that it is a // update or relocate and the sign must be reversed so the journal // system will use the sibling link to link the inode together. if (dir > 0) fs.journal.diri = chunk_traverse(fs.journal.diri); fs.journal.diri = -fs.journal.diri; tw(tr(TR_END, TrObject, "} %d\n", i)); ttw(ttr(TTrObj, "} %d" NL,i)); return i; } #if (FFS_READ_CACHE_ENBALE == 1) /* Segment read with read buffering */ int segment_read_cache(fd_t fdi,iref_t i, char *buf, int size, int offset) { struct inode_s *ip; char *p; if (buf == NULL) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); return EFFS_INVALID; } if((fs.fd[fdi].cache_i == i) &&(fs.fd[fdi].cache_read_buf != NULL)) { /* The data is avilable in read buffer */ if (size > fs.fd[fdi].cache_read_buf_size - offset) size = fs.fd[fdi].cache_read_buf_size - offset; memcpy(buf, &(fs.fd[fdi].cache_read_buf[offset]), size); } else { ip = inode_addr(i); fs.fd[fdi].cache_i = i; /* Cache the inode */ fs.fd[fdi].cache_read_buf_size = segment_datasize(ip); p = offset2addr(location2offset(ip->location)); p = addr2data(p, ip); //OMAPS78600 start // On first call to segment_read_cache() for a file the cache buffer has not been created yet, // so if cache_read_buf is NULL then do not free it. if (fs.fd[fdi].cache_read_buf != NULL) { target_free(fs.fd[fdi].cache_read_buf); fs.fd[fdi].cache_read_buf = NULL; } //OMAPS78600 end /* check the size */ if (size > fs.fd[fdi].cache_read_buf_size - offset) { /* Filling the buffer is not required , here */ /* saturate the size */ size = fs.fd[fdi].cache_read_buf_size - offset; memcpy(buf, &p[offset], size); } else /* read size is less than chunk size, so buffer the chunk data */ { /* Allocate a memory for the buffer */ fs.fd[fdi].cache_read_buf = (char*)target_malloc(fs.fd[fdi].cache_read_buf_size); if(fs.fd[fdi].cache_read_buf == NULL) { ttw(ttr(TTrMisc,"segment_read_cache: Memory Error \n\r")); /* No RAM memory , read it from Nor flash it self */ memcpy(buf, &p[offset], size); } else { /* cache the complete chunk data from nor flash */ memcpy(fs.fd[fdi].cache_read_buf,p,fs.fd[fdi].cache_read_buf_size); /* copy from buf --- parameter to this function */ /* At this point , offset will be zero */ memcpy(buf,fs.fd[fdi].cache_read_buf+offset, size); } /* End of if (fs.fd[fdi])*/ } /* End of if (size > ...)*/ } return size; /* return the size read */ } #endif int segment_read(iref_t i, char *buf, int size, int offset) { struct inode_s *ip; char *p; int chunk_size; tw(tr(TR_BEGIN, TrObject, "segment_read(%d, 0x%x, %d, %d) {\n", i, buf, offset, size)); if (buf == NULL) { tw(tr(TR_END, TrObject, "} %d\n", EFFS_INVALID)); return EFFS_INVALID; } ip = inode_addr(i); chunk_size = segment_datasize(ip); // Saturate read buffer if (size > chunk_size - offset) size = chunk_size - offset; p = offset2addr(location2offset(ip->location)); p = addr2data(p, ip); memcpy(buf, &p[offset], size); tw(tr(TR_END, TrObject, "} %d\n", size)); return size; } // Find next valid chunk iref_t segment_next(iref_t i) { struct inode_s *ip = inode_addr(i); tw(tr(TR_BEGIN, TrDirHigh, "ffs_segment_next(%d) {\n", i)); // Dir is not allowed to contain data if (is_object(ip, OTE_DIR)) { tw(tr(TR_END, TrDirHigh, "} 0\n")); return 0; } // Is this the last/only segment if ((i = get_child(ip)) == (iref_t) IREF_NULL) { tw(tr(TR_END, TrDirHigh, "} 0\n")); return 0; } // Get child (is valid?), search though segment by sibling link(is // valid?), and again.. do { i = get_child(ip); ip = inode_addr(i); if (is_object_valid(ip)) { tw(tr(TR_END, TrDirHigh,"} %d\n", i)); return i; } while (get_sibling(ip) != (iref_t) IREF_NULL) { i = get_sibling(ip); ip = inode_addr(i); if (is_object_valid(ip)) { tw(tr(TR_END, TrDirHigh,"} %d\n", i)); return i; } } } while (get_child(ip) != (iref_t) IREF_NULL); // No segment found tw(tr(TR_END, TrDirHigh,"} %d\n", i)); return 0; } // The output "inode" will be the inode that contains the requested data or // the last inode in the segmentfile. The segmenthead will be skiped if it // don't contains any data. inode_offset is the offset in the found inode // pointed to by target_offset. If target_offset point past the last segment // inode_offset will be the size of the last inode. The return value will be // the same as target_offset but maximum the total size of the object. int segfile_seek(iref_t seghead, int target_offset, iref_t *inode, int *inode_offset) { int priv_count = 0, count_size = 0; iref_t i = seghead; struct inode_s *ip; tw(tr(TR_BEGIN, TrObject, "segfile_seek(%d, %d, ?, ?) {\n", seghead, target_offset)); if (!is_object_valid(inode_addr(seghead))) { ffs_panic(EFFS_INVSEGHEAD); return 0; } *inode = seghead; while (1) { ip = inode_addr(i); count_size += segment_datasize(ip); // Seghead will be skiped if it don't contain any data if (count_size > target_offset && count_size != 0) { if (inode_offset != 0) *inode_offset = target_offset - priv_count; tw(tr(TR_END, TrObject, "} %d\n", target_offset)); return target_offset; } if ((i = segment_next(i)) == 0) { tw(tr(TR_END, TrObject, "} (eof!?) %d\n", count_size)); if (inode_offset != 0) *inode_offset = count_size - priv_count; // *inode = 0; return count_size; // No more segments } priv_count = count_size; *inode = i; } } // Calculate exact size of file data; without filename and null terminator // and without data null terminator and succeeding alignment padding. // NOTEME: Does this also work for empty files and directories? int segment_datasize(const struct inode_s *ip) { char *p, *q; int size; p = offset2addr(location2offset(ip->location)); q = p + ip->size - 1; // Segments is not allowed to contain any name if (!is_object(ip, OTE_SEGMENT)) { // skip filename at start of data while (*p) p++; } else // If it contained a name would p pointe to the null terminator of // the name but because chunks don't have a name decrement we p to get // the size correct p--; // skip padding at end of data while (*q) q--; // If there are data, there is also a null-terminator. Otherwise // there is no null-terminator size = q - p; if (size > 0) size--; tw(tr(TR_FUNC, TrObject, "segment_datasize(0x%x) %d\n", ip, size)); return size; } int object_truncate(const char *pathname, fd_t fdi, offset_t length) { int segment_offset, flength, realsize, offset; iref_t i, dir, next_i; char *name = 0, *olddata; struct inode_s *oldip; effs_t error; tw(tr(TR_FUNC, TrObject, "ffs_object_truncate('%s', %d, %d) \n", pathname, fdi, length)); if (length < 0) return EFFS_INVALID; if (pathname == 0) { // File descriptor must be open and it have to be in write mode if (!is_fd_valid(fdi)) return EFFS_BADFD;; if (!is_open_option(fs.fd[fdi].options, FFS_O_WRONLY)) return EFFS_INVALID; // It is not possible to truncate an open file to a size less than // the current file pointer if (length < fs.fd[fdi].fp) return EFFS_INVALID; i = fs.fd[fdi].seghead; } else { // File must exists and not be open if ((i = object_lookup(pathname, &name, &dir)) < 0) return i; if (get_fdi(i) >= 0) return EFFS_LOCKED; oldip = inode_addr(i); // Even though the ffs architecture allows to have data in directory // objects, we don't want to complicate matters, so we return an error if (is_object(oldip, OTE_DIR) && !(fs.flags & FS_DIR_DATA)) return EFFS_NOTAFILE; if ((i = is_readonly(i, pathname)) < 0) return i; } // Find the segment which length points in to flength = segfile_seek(i, length, &i, &segment_offset); if (pathname == 0) { if (is_offset_in_buf(length, fdi) == 1) { fs.fd[fdi].size = (length > fs.fd[fdi].size ? fs.fd[fdi].size : length); // Truncate the buffer if (i == fs.fd[fdi].wch) { next_i = segment_next(i); if (next_i > 0) if ((error = object_remove(next_i)) < 0) return error; } return EFFS_OK; } } if (flength < length) return EFFS_OK; journal_begin(i); // Realsize do not always need to include a name but we simplify it. realsize = atomalign(segment_offset + 1 + fs.filename_max + 1); // Make sure that there is enough space to make the rename without // object_create() trigger a data_reclaim() (awoid relocate oldi/data // source) if ((offset = data_prealloc(realsize)) <= 0) return EFFS_NOSPACE; // Find the next segment if any. next_i = segment_next(fs.journal.oldi); // Find old data source oldip = inode_addr(fs.journal.oldi); olddata = offset2addr(location2offset(oldip->location)); name = addr2name(olddata); // reinit name (maybe relocated) olddata = addr2data(olddata, oldip); if (is_object(oldip, OTE_SEGMENT)) { if (segment_offset == 0) next_i = fs.journal.oldi; // Remove the found object else { if ((i = segment_create(olddata, segment_offset, -fs.journal.oldi)) < 0) return i; fs.link_child = 0; //Do not link child journal_end(0); } } else { if ((i = object_create(name, olddata, length, fs.journal.oldi)) < 0) return i; fs.link_child = 0; //Do not link child journal_end(0); if (is_fd_valid(fdi)) fs.fd[fdi].seghead = i; } if (is_fd_valid(fdi)) fs.fd[fdi].size = length; // If any remaning segment exists then remove them if (next_i > 0) if ((error = object_remove(next_i)) < 0) return error; return EFFS_OK; } // Find the last segment valid or not valid iref_t chunk_traverse(iref_t i) { struct inode_s *ip = inode_addr(i); tw(tr(TR_BEGIN, TrDirHigh, "ffs_chunk_traverse(%d) {\n", i)); // Is this the last/only segment? if (get_child(ip) == (iref_t) IREF_NULL) { tw(tr(TR_END, TrDirHigh, "} %d\n", i)); return i; } // Get child, find the last segment by sibling link, and again.. do { i = get_child(ip); ip = inode_addr(i); while (get_sibling(ip) != (iref_t) IREF_NULL) { i = get_sibling(ip); ip = inode_addr(i); } } while (get_child(ip) != (iref_t) IREF_NULL); tw(tr(TR_END, TrDirHigh, "} %d\n", i)); return i; } // fdi include offset now but change this so core use pure fdi. effs_t datasync(fd_t fdi) { int chunk_size; iref_t i; struct inode_s *ip; char *name; tw(tr(TR_FUNC, TrObject, "datasync(%d) \n", fdi)); ttw(ttr(TTrApi, "datasync(%d) {" NL, fdi)); // NOTEME: is this necessary? if (!is_fd_valid(fdi)) return EFFS_BADFD; if (fs.fd[fdi].dirty == 0) return EFFS_OK; // If size - wfp is more than max is the complete buffer valid or else // is it only a part of it that consist valid data chunk_size = fs.fd[fdi].size - fs.fd[fdi].wfp; if (chunk_size > fs.chunk_size_max) chunk_size = fs.chunk_size_max; ip = inode_addr(fs.fd[fdi].wch); // Create new chunk or update a old one if (fs.fd[fdi].wch > 0) { // Update existing chunk // Negativ dir input because it is a update (do not traverse) if (is_object(ip, OTE_SEGMENT)) { journal_begin(fs.fd[fdi].wch); if ((i = segment_create(fs.fd[fdi].buf, chunk_size, -fs.fd[fdi].wch)) < 0) return i; } else { // Seghead update (like a normal file) ip = inode_addr(fs.fd[fdi].seghead); name = addr2name(offset2addr(location2offset(ip->location))); journal_begin(fs.fd[fdi].seghead); if ((i = object_create(name, fs.fd[fdi].buf, chunk_size, fs.fd[fdi].seghead)) < 0) return i; fs.fd[fdi].seghead = i; } journal_end(0); } else { // Create new chunk at the end of the existing ones. // BTW: A seghead will always have been made before this one. journal_begin(0); if ((i = segment_create(fs.fd[fdi].buf, chunk_size, fs.fd[fdi].seghead)) < 0) return i; journal_end(OTE_SEGMENT); } fs.fd[fdi].dirty = fs.fd[fdi].wch = 0; #if (FFS_SEEK_OPT == 1) fs.fd[fdi].rch = i; fs.fd[fdi].roffset = (fs.fd[fdi].fp - fs.fd[fdi].wfp); #endif ttw(ttr(TTrApi, "} 0" NL)); return EFFS_OK; } /****************************************************************************** * Development and Tracing ******************************************************************************/ #if (TARGET == 0) void tr_bstat(void) { int i, n; struct block_header_s *bhp; struct block_stat_s *bsp; tw(tr(TR_BEGIN, TrBstat, "bstat = {\n")); bsp = &bstat[0]; tw(tr(TR_FUNC, TrBstat, " bf used lost free n age state\n")); for (i = 0, n = 0; i < dev.numblocks; i++, bsp++) { bhp = (struct block_header_s *) offset2addr(dev.binfo[i].offset); tw(tr(TR_FUNC, TrBstat, "%2d %02x %6d %6d %6d %3d %5d %s%s%s%s%s%s\n", i, bsp->flags & 0xFF, bsp->used, bsp->lost, dev.blocksize - bsp->used, bsp->objects, bhp->age, (is_block(i, BF_IS_FREE) ? "FREE " : ""), (is_block(i, BF_IS_DATA) ? "DATA " : ""), (is_block(i, BF_IS_CLEANING) ? "CLEANING " : ""), (is_block(i, BF_IS_COPYING) ? "COPYING " : ""), (is_block(i, BF_IS_INODES) ? "INODES " : ""), (is_block_flag(i, BF_LOST) ? "lost " : "") )); if (is_block(i, BF_IS_DATA)) n += bsp->objects; } i = bstat[fs.inodes].used - bstat[fs.inodes].lost; tw(tr(TR_FUNC, TrBstat, " %3d (used-lost = %d)\n", n, i)); if (n != i) { tw(tr(TR_FUNC, TrAll, "WARNING: sum(bstat[x].objects) != bstat[fs.inodes].used - bstat[fs.inodes].lost\n")); } tw(tr(TR_END, TrBstat, "}\n")); } #else // (TARGET == 1) void tr_bstat(void) { int i; struct block_stat_s *bsp = &bstat[0]; for (i = 0; i < dev.numblocks; i++, bsp++) { ttw(ttr(TTrBstat, "%2d (%2x) u/l/f/n %6d %6d %6d %2d" NL, i, bsp->flags, bsp->used, bsp->lost, dev.blocksize - bsp->used, bsp->objects )); } ttw(str(TTrBstat,"" NL)); } void tr_fd(fd_t fdi) { tw(tr(TR_BEGIN, TrHelper, "tr_fd(%d) {\n", fdi)); tw(tr(TR_FUNC, TrHelper, "options: 0x%x \n", fd[fdi].options)); tw(tr(TR_FUNC, TrHelper, "inode : %d \n", fd[fdi].inode_first)); tw(tr(TR_FUNC, TrHelper, "fp : %d \n", fd[fdi].fp)); tw(tr(TR_FUNC, TrHelper, "size : %d \n", fd[fdi].size)); tw(tr(TR_FUNC, TrHelper, "dir : %d \n", fd[fdi].dir)); tw(tr(TR_FUNC, TrHelper, "name : %s \n", fd[fdi].name)); tw(tr(TR_END, TrHelper, "}\n", fdi)); } #endif // (TARGET == 0)