diff gsm-fw/services/ffs/core.c @ 211:847e2585a0f2

gsm-fw/services/ffs: core.c integrated
author Michael Spacefalcon <msokolov@ivan.Harhan.ORG>
date Thu, 26 Dec 2013 07:23:38 +0000
parents
children 2beb88a3d528
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gsm-fw/services/ffs/core.c	Thu Dec 26 07:23:38 2013 +0000
@@ -0,0 +1,2141 @@
+/******************************************************************************
+ * 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 $
+ *
+ ******************************************************************************/
+
+#include "ffs.h"
+#include "core.h"
+#include "drv.h"
+#include "ffstrace.h"
+#include "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, &not_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, &not_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
+ ******************************************************************************/
+
+#if 0
+extern uint16 ffs_flash_device;
+extern uint16 ffs_flash_manufact;
+#endif
+
+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;
+#if 0
+    case OC_DEV_MANUFACT:  ffs_flash_manufact = value; break;
+    case OC_DEV_DEVICE:    ffs_flash_device   = value; break;
+#endif
+    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)