view src/cs/drivers/drv_app/ffs/board/tffs.c @ 638:cab2f315827e

FFS dev.c: added Spansion PL032J to the "generic" table With the discovery of first GTM900 and then Tango, it now appears that Openmoko was not the only manuf after all who kept TI's TCS211 firmware largely intact (as opposed to changing it beyond all recognition like Compal, Chi-Mei and BenQ did), thus we are now getting new "alien" targets on which we reuse the original manuf's FFS with IMEI and RF calibration tables as if it were native. On these targets we use the original device table for FFS, even though we previously thought that it would never apply to any target other than dsample, leonardo and gtamodem. We have previously added Samsung K5L33xxCAM (a new kind of multi-ID device) to the generic table to support its use in Huawei GTM900-B modules; now we got news that some slightly older GTM900-B specimen used S71PL032J instead, so we are now adding PL032J as well.
author Mychaela Falconia <falcon@freecalypso.org>
date Thu, 30 Jan 2020 17:45:48 +0000
parents 945cf7f506b2
children
line wrap: on
line source

  /******************************************************************************
 * Flash File System (ffs)
 * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com
 *
 * ffs test scaffold/framework
 *
 * $Id: tffs.c 1.12.1.1.1.20 Fri, 19 Dec 2003 12:00:13 +0100 tsj $
 *
 ******************************************************************************/

#ifndef TARGET
#include "ffs.cfg"
#endif

#include "ffs/ffs.h"
#include "ffs/board/core.h"
#include "ffs/board/tffs.h"
#include "ffs/board/tdata.h"
#include "ffs/board/tmffs.h"
#include "ffs/board/ffstrace.h"

#if (TARGET == 1)
#include "ffs/board/task.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>


/******************************************************************************
 * Prototypes and Globals
 ******************************************************************************/

struct dir_s dir;
struct stat_s stat;
struct xstat_s xstat;

int error;
#if (TARGET == 1)
int smallbuf_size = 512;
int bigbuf_size = 65536;

#else
int smallbuf_size = 1024;
int bigbuf_size = 1024*1024*8;
#endif

char *smallbuf = 0;
char *bigbuf = 0;


/******************************************************************************
 * Globals and Main
 ******************************************************************************/

extern struct testcase_s testcase[];

struct test_s {
    char *name;     // name of currently executing test case
    int  numcases;  // number of test cases run so far
    int  numcalls;  // total number of ffs function calls so far
    int  numfails;  // total number of failed test cases
    int  keepgoing; // keep going when a test case fails
} test;

struct ffs_params_s param;


/******************************************************************************
 * Main Functions
 ******************************************************************************/

effs_t ffs_initialize(void);
effs_t ffs_exit(void);

// Each test case returns zero on success, non-zero on failure.
// test_execute() decides whether to continue with remaining test cases or
// not.

#if (TARGET == 0)
void test_listall(void)
{
    struct testcase_s *p;

    printf("Test Cases:\n");
    for (p = testcase; p->name != 0; p++) {
        printf("%8s: %s\n", p->name, p->comment);
    }
}
#endif

// <tests> is a comma-separated string of names of the test cases to
// run. Return number of test cases that failed. This is typically only 1,
// unless arg_keepgoing is set in which case it can be several.
int test_run(char *tests)
{
    struct testcase_s *ptc;
    int i, failed, tparams[FFS_TESTCASE_PARAMS_MAX];
    struct this_test_s this;
    char *pname, *testsnew;

    failed = 0;

    while (*tests != 0 && (failed == 0 || test.keepgoing))
    {
        // Make local copy of test case name. We have to make local copies
        // because we can be recursively called
        pname = this.name;
        while (isalpha(*tests) && *tests != 0 && *tests != ';')
            *pname++ = *tests++;
        *pname = 0;

        // Reset test case parameter(s)
        for (i = 0; i < FFS_TESTCASE_PARAMS_MAX; i++)
            tparams[i] = 0;

        // Collect parameter(s) for test case
        i = 0;
        while (isdigit(*tests)) {
            tparams[i] = strtol(tests, &testsnew, 0);
            if (tests == testsnew)
                break;
            tests = testsnew;
            if (*tests == ',')
                tests++;
            i++;
            if (i > FFS_TESTCASE_PARAMS_MAX) {
                ttw(ttr(TTrTest, "TEST %s has TOO MANY PARAMS" NL, this.name));
                tw(tr(TR_FUNC, TrTest, "TEST %s TOO MANY PARAMS\n", this.name));
                return 1;
            }
        }
                
        if (*tests == ';')
            tests++;

        // Lookup the test name in the array of test cases
        for (ptc = testcase; ptc->name != 0; ptc++) {
            if (strcmp(this.name, ptc->name) == 0)
                break;
        }
        if (ptc->name == 0) {
            ttw(ttr(TTrTest, "TEST %s UNKNOWN" NL, this.name));
            tw(tr(TR_FUNC, TrTest, "TEST %s UNKNOWN\n", this.name));
            return 1;
        }

        this.numcalls = test.numcalls;
        test_begin(this.name, tparams);
        i = ptc->function(tparams[0], tparams[1]);
        if (i != 0) {
            failed++;
            test_error(&this, test.numcalls);
        }
        test_end(&this, test.numcalls);
    }

    return failed;
}

// Overall test initialization. Read static ffs params with ffs_query()
void test_init(int keepgoing)
{
    test.numcases = 0;
    test.numcalls = 0;
    test.numfails = 0;
    test.keepgoing = keepgoing;

    memset(&param, 0, sizeof(struct ffs_params_s));

    if (smallbuf == 0) {
#if (TARGET == 1)
        smallbuf = (char*)target_malloc(smallbuf_size);
#else
        smallbuf = malloc(smallbuf_size);
#endif
        tw(tr(TR_FUNC, TrTest, "smallbuf = 0x%X, %d\n",
              smallbuf, smallbuf_size));
    }
    if (bigbuf == 0) {
#if (TARGET == 1)
        // We continuously halve the buffer size until we succeed to allocate
        // it.
        while(1) {
            if ((bigbuf = (char*)target_malloc(bigbuf_size)) != 0)
                break;
            bigbuf_size /= 2;
        } 
#else
        bigbuf = malloc(bigbuf_size);
#endif
        tw(tr(TR_FUNC, TrTest, "bigbuf   = 0x%X, %d\n", bigbuf, bigbuf_size));
    }

    test_tdata_init();

    tffs_initialize();
}

void test_exit(void)
{
    test_state_print(0);
}


// Begin new test case
void test_begin(char *name, int *params)
{
    test.numcases++;

    tw(tr(TR_BEGIN, TrTest, "TEST %s(%d,%d)\n", name, params[0], params[1]));
    ttw(ttr(TTrTest, "TEST %s(%d,%d)" NL, name, params[0], params[1]));
}

void test_end(struct this_test_s *test, int n)
{
    int objects = 0;

    ffs_query(Q_TOTAL_OBJECTS, (uint16 *) &objects);
    tw(tr(TR_FUNC, TrTestHigh, "(total objects = %d)\n", objects));
    tw(tr(TR_END, TrTest, ""));
    //tw(tr(TR_END, TrTest, "TEST END   %s (calls = %d)\n",
    //      test->name, n - test->numcalls));
}

void test_error(struct this_test_s *test, int n)
{
    ttw(ttr(TTrTest, "TEST FAIL %s, call %d" NL,
            test->name, n - test->numcalls));
    tw(tr(TR_FUNC, TrTest, "TEST FAIL %s, call %d\n",
          test->name, n - test->numcalls));
}


/******************************************************************************
 * Miscellaneous
 ******************************************************************************/

int test_ffs_state_get(struct ffs_state_s *s)
{
    memset(s, 0, sizeof(struct ffs_state_s));

    error  = ffs_query(Q_INODES_USED,  (uint16 *) &s->inodes_used);
    error += ffs_query(Q_INODES_LOST,  (uint16 *) &s->inodes_lost);
    error += ffs_query(Q_OBJECTS_FREE,  (uint16 *) &s->objects_free);
    error += ffs_query(Q_TOTAL_OBJECTS, (uint16 *) &s->objects_total);

    error += ffs_query(Q_BYTES_USED, (uint16 *) &s->bytes_used);
    error += ffs_query(Q_BYTES_LOST, (uint16 *) &s->bytes_lost);
    error += ffs_query(Q_BYTES_FREE, (uint16 *) &s->bytes_free);

    error += ffs_query(Q_BLOCKS_FREE, (uint16 *) &s->blocks_free);

    return error;
}

void test_ffs_state_copy(struct ffs_state_s *dst, struct ffs_state_s *src)
{
    memcpy(dst, src, sizeof(struct ffs_state_s));
}

void test_state_print(struct ffs_state_s *state)
{
    struct ffs_state_s mystate;

    if (state == 0) {
        state = &mystate;
        test_ffs_state_get(state);
    }

    tw(tr(TR_FUNC, TrTest, "\nFFS State Summary:\n\n"));
    ttw(str(TTrTest, NL "FFS State Summary:" NL NL));

    tw(tr(TR_FUNC, TrTest, "  block_size = %d\n", param.block_size));
    tw(tr(TR_FUNC, TrTest, "  bytes_avail = %d\n\n", param.bytes_avail));

    ttw(ttr(TTrTest, "  block_size = %d" NL, param.block_size));
    ttw(ttr(TTrTest, "  bytes_avail = %d" NL NL, param.bytes_avail));

    test_state_bytes_print(state, 0);
    test_state_objects_print(state, 0);

}

void test_state_objects_print(struct ffs_state_s *old, struct ffs_state_s *new)
{
    ttw(str(TTrTest, "              inodes            objects" NL));
    ttw(str(TTrTest, "  -------------------------------------------" NL));
    ttw(str(TTrTest, "  objects:    used     lost     free    total" NL));
    ttw(ttr(TTrTest, "  old:      %6d   %6d   %6d   %6d" NL,
          old->inodes_used, old->inodes_lost,
          old->objects_free, old->objects_total));

    tw(tr(TR_FUNC, TrTest, "              inodes            objects\n"));
    tw(tr(TR_FUNC, TrTest, "  -------------------------------------------\n"));
    tw(tr(TR_FUNC, TrTest, "  objects:    used     lost     free    total\n"));
    tw(tr(TR_FUNC, TrTest, "  old:      %6d   %6d   %6d   %6d\n",
          old->inodes_used, old->inodes_lost,
          old->objects_free, old->objects_total));

    if (new != NULL)
    {
        ttw(ttr(TTrTest,
              "  new:      %6d   %6d   %6d   %6d" NL,
              new->inodes_used, new->inodes_lost,
              new->objects_free, new->objects_total));
        ttw(ttr(TTrTest,
              "  diff:     %6d   %6d   %6d   %6d" NL,
              new->inodes_used - old->inodes_used,
              new->inodes_lost - old->inodes_lost,
              new->objects_free - old->objects_free,
              new->objects_total - old->objects_total));

        tw(tr(TR_FUNC, TrTest,
              "  new:      %6d   %6d   %6d   %6d\n",
              new->inodes_used, new->inodes_lost,
              new->objects_free, new->objects_total));
        tw(tr(TR_FUNC, TrTest,
              "  diff:     %6d   %6d   %6d   %6d\n",
              new->inodes_used - old->inodes_used,
              new->inodes_lost - old->inodes_lost,
              new->objects_free - old->objects_free,
              new->objects_total - old->objects_total));
    }
    ttw(str(TTrTest, "" NL));
    tw(tr(TR_FUNC, TrTest, "\n"));
}

void test_state_bytes_print(struct ffs_state_s *old, struct ffs_state_s *new)
{
    tw(tr(TR_FUNC, TrTest, "  bytes:      used     lost     free    total\n"));
    tw(tr(TR_FUNC, TrTest, "  old:    %8d %8d %8d %8d\n",
          old->bytes_used, old->bytes_lost,
          old->bytes_free, param.bytes_max));
    tw(tr(TR_FUNC, TrTest, "  +/-:    %8d          %8d\n",
          old->bytes_used - old->bytes_lost,
          old->bytes_free + old->bytes_lost));

    ttw(str(TTrTest, "  bytes:      used     lost     free    total" NL));
    ttw(ttr(TTrTest, "  old:    %8d %8d %8d %8d" NL,
          old->bytes_used, old->bytes_lost,
          old->bytes_free, param.bytes_max));
    ttw(ttr(TTrTest, "  +/-:    %8d          %8d" NL,
          old->bytes_used - old->bytes_lost,
          old->bytes_free + old->bytes_lost));

    if (new != NULL) {
        tw(tr(TR_FUNC, TrTest, "  new:    %8d %8d %8d\n",
              new->bytes_used, new->bytes_lost,
              new->bytes_free));
        tw(tr(TR_FUNC, TrTest, "  diff:   %8d %8d %8d\n",
              new->bytes_used - old->bytes_used,
              new->bytes_lost - old->bytes_lost,
              new->bytes_free - old->bytes_free));

        ttw(ttr(TTrTest, "  new:    %8d %8d %8d" NL,
              new->bytes_used, new->bytes_lost,
              new->bytes_free));
        ttw(ttr(TTrTest, "  diff:   %8d %8d %8d" NL,
              new->bytes_used - old->bytes_used,
              new->bytes_lost - old->bytes_lost,
              new->bytes_free - old->bytes_free));
    }
    tw(tr(TR_FUNC, TrTest, "\n"));
    ttw(str(TTrTest, "" NL));
}


// Retrieve all static ffs parameters with ffs_query()
int test_ffs_params_get(void)
{
    error  = ffs_query(Q_FILENAME_MAX,    &param.filename_max);
    error += ffs_query(Q_PATH_DEPTH_MAX,  &param.pathdepth_max);
    error += ffs_query(Q_INODES_MAX,      &param.inodes_max);
    error += ffs_query(Q_BYTES_MAX,       &param.bytes_max);
    error += ffs_query(Q_DEV_BLOCKS,      &param.numblocks);
    error += ffs_query(Q_DEV_ATOMSIZE,    &param.atomsize);
    error += ffs_query(Q_BLOCKS_FREE_MIN, &param.blocks_free_min);

    // Compute block size
    param.block_size = param.bytes_max / param.numblocks;

    // Compute total number of available storage space, subtracting
    // fs.blocks_free_min plus one block for inodes
    param.bytes_avail =
        param.bytes_max - (param.block_size * (1 + param.blocks_free_min));

    // Compute number of blocks available for data storage
    param.data_blocks = param.numblocks - (1 + param.blocks_free_min);

    return error;
}

void test_statistics_print(void)
{
    tw(tr(TR_FUNC, TrTest, "Data allocated(%dMB)\n", stats.data_allocated>>20)); 
    tw(tr(TR_FUNC, TrTest, "Reclaim candidates: most-lost(%d), most-unused(%d), youngest(%d)\n", stats.drec.most_lost, stats.drec.most_unused, stats.drec.youngest)); 

    tw(tr(TR_FUNC, TrTest, "Data reclaimed:     lost(%dMB), valid(%dMB)\n", 
          stats.drec.lost[0]>>20 , stats.drec.valid[0]>>20)); 

    tw(tr(TR_FUNC, TrTest, "Inodes reclaimed:   num(%d), valid(%d), lost(%d)\n",
          stats.irec.num, stats.irec.valid, stats.irec.lost));

    ttw(ttr(TTrTest, "Data allocated(%dMB)\n" NL, stats.data_allocated>>20)); 
    ttw(ttr(TTrTest, "Reclaim candidates: most-lost(%d), most-unused(%d), youngest(%d)\n" NL, stats.drec.most_lost, stats.drec.most_unused, stats.drec.youngest)); 

    ttw(ttr(TTrTest, "Data reclaimed:     lost(%dMB), valid(%dMB)\n" NL, 
          stats.drec.lost[0]>>20 , stats.drec.valid[0]>>20)); 

    ttw(ttr(TTrTest, "Inodes reclaimed:   num(%d), valid(%d), lost(%d)\n" NL,
          stats.irec.num, stats.irec.valid, stats.irec.lost));
}

/******************************************************************************
 * Test and Expect Functions
 ******************************************************************************/

int test_expect(int n, int xn)
{
    if (n == xn)
        return 0;

    tw(tr(TR_FUNC, TrTest,
          "ERROR: expect(%d,%d): got %d, '%s', expected %d, '%s'\n",
          n, xn, n, ffs_strerror(n), xn, ffs_strerror(xn)));
    ttw(ttr(TTrTest, "ERROR: expect(%d,%d)" NL, n, xn));

    return -1;
}

// Expect a return code >= 0 meaning EFFS_OK.
int test_expect_ok(int n)
{
    if (n >= 0)
        return 0;

    tw(tr(TR_FUNC, TrTest,
          "ERROR: expect_ok(%d) got %d, '%s', expected >= EFFS_OK\n",
          n, ffs_strerror(n)));
    ttw(ttr(TTrTest, "ERROR: expect_ok(%d)" NL, n));

    return -1;
}

int test_expect_equal(int n, int xn)
{
    if (n == xn)
        return 0;

    tw(tr(TR_FUNC, TrTest, "ERROR: got %d, expected %d\n", n, xn));
    ttw(ttr(TTrTest, "ERROR: expect_eq(%d,%d" NL, n, xn));

    return -1;
}

int test_expect_not_equal(int n, int xn)
{
    if (n != xn)
        return 0;

    tw(tr(TR_FUNC, TrTest, "ERROR: expect_ne(%d)\n", n));
    ttw(ttr(TTrTest, "ERROR: expect_ne(%d)" NL, n));

    return -1;
}

int test_expect_greater_than(int n, int xn)
{
    if (n > xn)
        return 0;

    tw(tr(TR_FUNC, TrTest, "ERROR: expect_gt(%d,%d) got %d but expected > %d\n",
          n, xn, n, xn));
    ttw(ttr(TTrTest, "ERROR: expect_gt(%d,%d)" NL, n, xn));

    return -1;
}

int test_expect_data(const void *data1, const void *data2, int size)
{
    if (memcmp(data1, data2, size) == 0)
        return 0;
    
    tw(tr(TR_FUNC, TrTest,
          "ERROR: expect_data(%d) got unexpected data\n", size));
    ttw(ttr(TTrTest, "ERROR: expect_data(%d)" NL, size));

    return -1;
}

// Check that contents of file with name <name> is the same as <data> of
// size <size>.
int test_expect_file(const char *name, const void *data, int size)
{
    test.numcalls++;
    if (size > bigbuf_size) {
        tw(tr(TR_FUNC, TrTest, "WARNING: expect_file(%d) buffer too small\n",
              size));
        ttw(ttr(TTrTest, "WARNING: expect_file(%d) buffer too small" NL, size));
#if (TARGET == 1)
        return 0;
#endif
        return -1;
    }
        
    error = ffs_file_read(name, bigbuf, size);
    if (test_expect_greater_than(error, EFFS_OK - 1))
        return -1;
    return test_expect_data(bigbuf, data, size);
}

int test_expect_state(struct ffs_state_s *old, struct ffs_state_s *new)
{
    int old_total, new_total;

    old_total = old->inodes_used - old->inodes_lost;
    new_total = new->inodes_used - new->inodes_lost;

    if (old->objects_total == new->objects_total &&
        old->objects_total == new_total &&
        new->objects_total == old_total &&
        old->bytes_used == new->bytes_used &&
        old->bytes_lost == new->bytes_lost &&
        old->bytes_free == new->bytes_free) {
        return 0;
    }

    ttw(str(TTrTest, "ERROR: ffs state mismatch:" NL NL));
    tw(tr(TR_FUNC, TrTest, "ERROR: ffs state mismatch:\n\n"));
    test_state_objects_print(old, new);
    test_state_bytes_print(old, new);

    return -1;
}

// Check if number of objects is unchanged
int test_expect_objects(struct ffs_state_s *old, struct ffs_state_s *new)
{
    int old_total, new_total;

    test_ffs_state_get(new);

    old_total = old->inodes_used - old->inodes_lost;
    new_total = new->inodes_used - new->inodes_lost;

    if (old->objects_total == new->objects_total &&
        old->objects_total == new_total &&
        new->objects_total == old_total) {
        return 0;
    }

    ttw(ttr(TTrTest, "ERROR: expect_objects(%d, %d, %d, %d, %d, %d)" NL));
    tw(tr(TR_FUNC, TrTest, "ERROR: ffs state mismatch:\n\n"));
    test_state_objects_print(old, new);

    return -1;
}


/******************************************************************************
 * FFS Functions
 ******************************************************************************/

effs_t tffs_fcreate(const char *name, void *addr, int size)
{
    test.numcalls++;
    return ffs_fcreate(name, addr, size);
}

effs_t tffs_fupdate(const char *name, void *addr, int size)
{
    test.numcalls++;
    return ffs_fupdate(name, addr, size);
}

effs_t tffs_fwrite(const char *name, void *addr, int size)
{
    test.numcalls++;
    return ffs_fwrite(name, addr, size);
}

effs_t tffs_file_write(const char *name, void *addr, int size, uint16 option)
{
    test.numcalls++;
    return ffs_file_write(name, addr, size, option);
}

effs_t tffs_mkdir(const char *name)
{
    test.numcalls++;
    return ffs_mkdir(name);
}

effs_t tffs_symlink(const char *name, const char *actualpath)
{
    test.numcalls++;
    return ffs_symlink(name, actualpath);
}

effs_t tffs_remove(const char *name)
{
    test.numcalls++;
    return ffs_remove(name);
}

effs_t tffs_fcontrol(const char *name, int8 action, uint16 param)
{
    test.numcalls++;
    return ffs_fcontrol(name, action, param);
}

effs_t tffs_preformat(uint16 magic)
{
    test.numcalls++;
    return ffs_preformat(magic);
}

effs_t tffs_format(const char *name, uint16 magic)
{
    test.numcalls++;
    return ffs_format(name, magic);
}


int tffs_fread(const char *name, void *addr, int size)
{
    test.numcalls++;
    return ffs_file_read(name, addr, size);
}

int tffs_file_read(const char *name, void *addr, int size)
{
    test.numcalls++;
    return ffs_file_read(name, addr, size);
}

int tffs_opendir(const char *name, struct dir_s *dir)
{
    test.numcalls++;
    return ffs_opendir(name, dir);
}

int tffs_readdir (struct dir_s *dir, char *name, int8 size)
{
    test.numcalls++;
    return ffs_readdir(dir, name, size);
}

int tffs_readlink(const char *name, char *addr, int size)
{
    test.numcalls++;
    return ffs_readlink(name, addr, size);
}

int tffs_rename(const char *oldname, const char *newname)
{
    test.numcalls++;
    return ffs_rename(oldname, newname);
}

effs_t tffs_stat(const char *name, struct stat_s *stat)
{
    test.numcalls++;
    return ffs_stat(name, stat);
}

effs_t tffs_fstat(fd_t fdi, struct stat_s *stat)
{
    test.numcalls++;
    return ffs_fstat(fdi, stat);
}

effs_t tffs_linkstat(const char *name, struct stat_s *stat)
{
    test.numcalls++;
    return ffs_lstat(name, stat);
}

effs_t tffs_lstat(const char *name, struct stat_s *stat)
{
    test.numcalls++;
    return ffs_lstat(name, stat);
}

effs_t tffs_xlstat(const char *name, struct xstat_s *stat)
{
    test.numcalls++;
    return ffs_xlstat(name, stat);
}

effs_t tffs_query(int8 query, void *p)
{
    return ffs_query(query, p);
}


effs_t tffs_initialize(void)
{
    effs_t myerror;
    struct ffs_stats_s old_stats;

    test.numcalls++;

    memcpy(&old_stats, &stats, sizeof(struct ffs_stats_s));
    myerror = ffs_initialize();
    memcpy(&stats, &old_stats, sizeof(struct ffs_stats_s));

    test_ffs_params_get();
    return myerror;
}

effs_t tffs_exit(void)
{
    test.numcalls++;
    return ffs_exit();
}

fd_t tffs_open(const char *pathname, ffs_options_t options)
{
    test.numcalls++;
    return ffs_open(pathname, options);
}

effs_t tffs_close(fd_t fdi)
{
    test.numcalls++;
    return ffs_close(fdi);
}

int tffs_write(fd_t fdi, void *addr, int size)
{
   test.numcalls++;
   return ffs_write(fdi, addr, size);
}

int tffs_read(fd_t fdi, void *addr, int size)
{
   test.numcalls++;
   return ffs_read(fdi, addr, size);
}

int tffs_seek(fd_t fdi, int offset, int whence)
{
  test.numcalls++;
  return ffs_seek(fdi, offset, whence);
}

effs_t tffs_truncate(const char *path, offset_t length) 
{
    test.numcalls++;
    return ffs_truncate(path, length); 
}

effs_t tffs_ftruncate(fd_t fdi, offset_t length) 
{
    test.numcalls++;
    return ffs_ftruncate(fdi, length); 
}

effs_t tffs_fdatasync(fd_t fdi) 
{
    test.numcalls++;
    return ffs_fdatasync(fdi); 
}