FreeCalypso > hg > fc-magnetite
diff src/cs/drivers/drv_app/ffs/board/core.c @ 0:945cf7f506b2
src/cs: chipsetsw import from tcs211-fcmodem
binary blobs and LCD demo files have been excluded,
all line endings are LF only
author | Mychaela Falconia <falcon@freecalypso.org> |
---|---|
date | Sun, 25 Sep 2016 22:50:11 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cs/drivers/drv_app/ffs/board/core.c Sun Sep 25 22:50:11 2016 +0000 @@ -0,0 +1,2142 @@ +/****************************************************************************** + * 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 <string.h> +#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 <string.h> +#include <limits.h> + +/****************************************************************************** + * 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)|(0x56); + + +/****************************************************************************** + * 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; + struct inode_s *ip; + 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)); + + // 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.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; + + ip = inode_addr(i); + + // 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 (!is_journal) + ffsdrv.write(dataaddr, buf, size); + 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; + + 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; + } + + segfile_seek(fs.fd[fdi].seghead, fs.fd[fdi].fp, &i, &offset); + + // 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 { + // Data is only in the chunk + size_read = segment_read(i, (char*) src + copied, + size - copied, offset); + } + + 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, OT_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, OT_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, OT_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); + stat->type = ip->flags & OT_MASK;; + stat->flags = ~ip->flags & OF_MASK; + 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, OT_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, OT_DIR)) { + journal_begin(i); + fs.journal.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.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. <newname> 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 + ******************************************************************************/ + +// We can *not* use global variables, only local --- we must be re-entrant! + +#if 0 + +// NEW CODE! + +iref_t ffs_object_lookup_do(const char **path, iref_t *dir, int readlink); + +iref_t ffs_object_lookup_once(const char *path, char **leaf, iref_t *dir) +{ + iref_t i, mydir; + const char *mypath; + + tw(tr(TR_BEGIN, TrLookup, "object_lookup_once('%s', ?, ?) {\n", path)); + ttw(ttr(TTrInode, "olu(%s){" NL, path)); + + mypath = path; + mydir = 0; + i = object_lookup_do(&mypath, &mydir, 0); + if (leaf) *leaf = (char *) mypath; + if (dir) *dir = mydir; + + 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. Symlinks are followed. +iref_t ffs_object_lookup(const char *path, char **leaf, iref_t *dir) +{ + iref_t i, mydir; + const char *mypath; + + tw(tr(TR_BEGIN, TrLookup, "object_lookup('%s', ?, ?) {\n", path)); + ttw(ttr(TTrInode, "olu(%s){" NL, path)); + + mypath = path; + mydir = 0; + i = object_lookup_do(&mypath, &mydir, 1); + + if (is_object(ip, OT_LINK)) { + // If it is a link, we unconditionally follow it + mypath = offset2addr(location2offset(ip->location)); + mypath += ffs_strlen(mypath) + 1; // point to data + if (*mypath == '/') { + mypath++; + depth = 0; + d = fs.root; + } + i = d; + ip = inode_addr(d); + } + + if (leaf) *leaf = (char *) mypath; + if (dir) *dir = mydir; + + tw(tr(TR_END, TrLookup, "} (%d, '%s') %d\n", + (dir ? *dir : 0), (leaf ? *leaf : ""), i)); + ttw(ttr(TTrInode, "} %d" NL, i)); + + return i; +} + +// NEW CODE! + +// Ignore all occurrences of two successive slashes. Accept trailing slash +// in directory name. +iref_t ffs_object_lookup_do(const char **path, iref_t *dir, int followlink) +{ + // int lookup_followed; // number of symlinks followed + iref_t i, j, d; + struct inode_s *ip; + const char *p, *q, *mypath = *path; + uint8 depth = 1; + + tw(tr(TR_FUNC, TrLookup, "object_lookup_do('%s', ?, %d) {\n", + *path, followlink)); + + d = fs.root; + if (*mypath == '/') { + mypath++; // silently ignore and skip prefix slash + // root directory is a special case + if (*mypath == 0) { + j = d; + if (path) *path = mypath; + if (dir) *dir = 0; + tw(tr(TR_NULL, TrLookup, "} ('%s', %d) %d\n", mypath, 0, j)); + return j; + } + } + + // set default return value if root dir is empty (child link empty) + j = EFFS_NOTFOUND; + + ip = inode_addr(d); + + tw(tr(TR_FUNC, TrLookup, "")); + + while ((i = ip->child) != (iref_t) IREF_NULL) + { + j = 0; // default to not found + do { + tw(tr(TR_NULL, TrLookup, " %d", (int) i)); + + p = mypath; + ip = inode_addr(i); + if (is_object_valid(ip) && !is_object(ip, OT_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 = ip->sibling) != (iref_t) IREF_NULL); + + if (j == 0) { + // we did not find this component of the mypath. Let's see if this + // was the leafname component or not... + while (*p != 0 && *p != '/') + p++; + + if (*p == 0) + // The mypath 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 or a symlink... + if (is_object(ip, OT_LINK)) { + // If it is a link, we unconditionally follow it + mypath = offset2addr(location2offset(ip->location)); + mypath += ffs_strlen(mypath) + 1; // point to data + if (*mypath == '/') { + mypath++; + depth = 0; + d = fs.root; + } + i = d; + ip = inode_addr(d); + } + else if (is_object(ip, OT_DIR)) { + mypath = p + 1; + d = i; + } + else { + j = EFFS_NOTADIR; + break; + } + if (++depth > fs.path_depth_max) { + j = EFFS_PATHTOODEEP; + break; + } + + // 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 + if (is_object(ip, OT_LINK) && followlink) { + // If object is a link, we conditionally follow it... + mypath = offset2addr(location2offset(ip->location)); + mypath += ffs_strlen(mypath) + 1; // point to data + if (*mypath == '/') { + mypath++; + depth = 0; + d = fs.root; + i = fs.root; + } + else + i = d; + ip = inode_addr(d); + tw(tr(TR_NULL, TrLookup, " -%d->", d)); + } + else { + break; // Success, we found the object! + } + } + } + + if (path) *path = (char *) mypath; + if (dir) *dir = d; + + tw(tr(TR_NULL, TrLookup, "} (%d, '%s') %d\n", d, mypath, j)); + + return j; +} + +#else + +// 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, OT_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, OT_FILE)) + i = EFFS_NOTAFILE; + } + else { + leaf = 0; + 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 <dir>. If object is not found: Return +// EFFS_NOTFOUND and last directory component of path in <dir> and leafname +// of pathname in <leaf> +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 = ip->child) != (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, OT_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 = ip->sibling) != (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, OT_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; +} + +#endif + + +/****************************************************************************** + * 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, OT_DIR)) + i = EFFS_NOTADIR; + + tw(tr(TR_END, TrDirHigh, "} %d\n", i)); + + return i; +} + +// Return name and iref of next entry in directory <dir>. <i> is the last +// entry we returned from this function. In case this is the first call +// after the initial call to dir_open(), <i> equals <dir>. +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 ? ip->child : ip->sibling); + + 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 = ip->sibling; + } + 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 <i>. If <i> is negative, it +// refers to the actual directory so we start by traversing the child link. +// Otherwise if <i> 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). +// <entries> is number of non-deleted objects in the dir (only valid if +// traversed from the start, eg. with negative <i>). +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 = ip->child; + } + 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(ip, OT_ERASED)) + erased++; + else + invalid++; + + } while ((i = ip->sibling) != (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 <priority> 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, uint8 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)); + + bstat[block].flags = BIT_SET(bstat[block].flags, flags); + ffsdrv.write_halfword((uint16 *) &bhp->flags, bstat[block].flags ); + + 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) { + // FIXME NO we are not always of inodes, maybe dos 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(realsize)) < 0) + return 0; // Data reclaim failed! + } + + return 0; // No space found +} + +// Find free data space of size <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.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)); + + 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 <value> + // argument. + journal_begin(i); + fs.journal.flags |= OF_MASK; // reset all flags + fs.journal.flags = BIT_SET(fs.journal.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; + case Q_LOST_HIGH: *(uint16*)p = fs.lost_threshold; 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.most_unused + + 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 + ******************************************************************************/ + +// Check if an object is read-only. Note that the root inode is always +// readonly, no matter what! Returns error or original <i>. +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, OF_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; +} + +// Note: rename function? like get_fdi.. +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.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; +} + + +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, OT_DIR)) { + tw(tr(TR_END, TrDirHigh, "} 0\n")); + return 0; + } + + // Is this the last/only segment + if ((i = ip->child) == (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 = ip->child; + ip = inode_addr(i); + if (is_object_valid(ip)) { + tw(tr(TR_END, TrDirHigh,"} %d\n", i)); + return i; + } + + while (ip->sibling != (iref_t) IREF_NULL) { + i = ip->sibling; + ip = inode_addr(i); + if (is_object_valid(ip)) { + tw(tr(TR_END, TrDirHigh,"} %d\n", i)); + return i; + } + } + } while (ip->child != (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 +// will inode_offset 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))) { + tw(tr(TR_END, TrAll, "FATAL: Invalid seghead!\n")); + 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, OT_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, OT_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, OT_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 (ip->child == (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 = ip->child; + ip = inode_addr(i); + + while (ip->sibling != (iref_t) IREF_NULL) { + i = ip->sibling; + ip = inode_addr(i); + } + } while (ip->child != (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, OT_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(OT_SEGMENT); + } + fs.fd[fdi].dirty = fs.fd[fdi].wch = 0; + + 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)