view src/cs/drivers/drv_app/ffs/board/tcases.c @ 134:7d50d8d13711

FFS code sync with Magnetite + gcc version fix This change brings the new flash autodetection for FC and Pirelli targets from Magnetite, and should also fix the gcc version for C1xx and gtamodem targets, which were previously broken because they used TI's original flash autodetect code (which operates at address 0) while the boot ROM is mapped there.
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 11 Dec 2018 08:43:25 +0000
parents b6a5e36de839
children
line wrap: on
line source

/******************************************************************************
 * Flash File System (ffs)
 * Idea, design and coding by Mads Meisner-Jensen, mmj@ti.com
 *
 * ffs test cases
 *
 * $Id: tcases.c 1.13.1.1.1.66 Thu, 08 Jan 2004 15:05:23 +0100 tsj $
 *
 ******************************************************************************/

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

#include "ffs/ffs_api.h"  // Temp 
#include "ffs/ffs.h"

#include "ffs/board/tffs.h"
#include "ffs/board/core.h" // only for block/object recovery test flags
#include "ffs/board/tdata.h"
#include "ffs/board/ffstrace.h"
#include "ffs/board/drv.h"
#include "ffs/pcm.h"

#if((TARGET == 1) || (RIV_ENV==1))
#include "rvf/rvf_api.h"  // this include rv_general.h and rvf_target.h
#include "rvm/rvm_use_id_list.h"

#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>

#define LUDIR  "/europe/sweden"
#define MFDIR "/pcm"

// TODO: reimplement test case bfull (not valid any more).

// TODO: Cleanup in beginning of each test case for as many cases as
// possible, so they can be re-run.

// TODO: Make as many cases as possible fully independent of number of
// blocks and size of blocks in ffs.

// TODO: Every testcase should test if it is applicable to the current
// environment, e.g. some tests are impossible if e have a 2 or 3 block
// file system.

// TODO: test case for testing age functionality
// TODO: Implement case_ren
// TODO: Implement case_rm

// NOTEME: DO something with test case flags (PC, IT, RND)?

// Should we make a test case where we use case_lsr to make map of objects (
// ensuring mapsize is prime). In a loop, we select a random object (object
// index[rnd%size]) and perform a random operation on that object?

// Should test data (tdata[]) be of sizes 2^n+n or Xn+n where n = 0..15

// Should all test files have suffix ".<tdata-index>"? This would make very
// easy data checking!

// Add compiler define: WITH_PCM

//unsigned char ffs_image[4*4*1024];
//int ffs_ram_image_address = (int) &ffs_image;


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

// Helper functions
int case_cleanup(int min_space);
int case_mk_rand_file(char *dirname, int max_size, int min_size);
int ignore_file(char *pathname);
int case_trace_mask(int p0, int p1);
int case_reinit(int p0, int p1);
int case_debug_help(int p0, int p1);

struct object_s {
    char *name;
    struct xstat_s stat;
};

const struct testcase_s testcase[];

// Benchmark (not in use yet)
struct results_s {
    int w16B;
    int w256B;
    int w4096B;
    int rew4096B;
    int r16B;
    int r256B;
    int r4096B;
    int lfile;
    int ldir;
    int lonef;
};

/******************************************************************************
 * Collective Test Cases
 ******************************************************************************/

// test cases: all, alot, most, much + specific ones

int case_all(int p0, int p1)
{
#if (TARGET == 1)
    UINT32 time_begin, elapsed;
    time_begin = tffs_timer_begin();
#endif
    // We have to run test case 'init' after test case 'format'. This is
    // because 'init' test case calls test_ffs_params_get() which
    // initializes variables used in many of the testcases.
    
    error = test_run("ninit;format;i;world;eu;pcm;apiexc;"
                     "bigf;open;rw;seek;append;ren;mopen;"
                     "jnl;irec;drec;trunc;brec;stat;"
                     "lu;fc;root;dirs;frd;bfull;dsync;"
                     "ssym;find;ri;x;fwflags;query;octrl");
#if (TARGET == 1)
    elapsed = tffs_timer_end(time_begin);
    ttw(ttr(TTrAll, "Time elapsed %ds" NL, elapsed / 1000));
#endif

    test_statistics_print();

    return error;
    // Not implemented test cases...
    return test_run("rm;pcm;");
}

int case_alot(int p0, int p1)
{
    return test_run("ninit;format;ri;i;ri;world;ri;eu;ri;fwflags;ri;"
                    "bigf;ri;open;ri;rw;ri;seek;ri;append;ri;ren;ri;"
                    "jnl;ri;irec;ri;brec;ri;stat;ri;trunc;ri;mopen;ri;"
                    "lu;ri;fc;ri;root;ri;dirs;ri;frd;ri;bfull;ri;"
                    "ssym;ri;find;ri;x;ri;dsync;ri;pcm;ri;query;ri;octrl");
}

int case_tall(int p0, int p1)
{
    return test_run("ninit;format;i;world;eu;fwflags;"
                    "bigf;jnl;irec;drec;ren;apiexc;"
                    "stat;open;rw;seek;trunc;append;octrl;"
                    "lu;fc;root;dirs;frd;bfull;pcm;query;"
                    "ssym;find;x;ex;bf;nb;mopen;dsync;brec");
}

// Ad hoc test case
int case_test(int p0, int p1)
{
    return test_run("format;i;world;eu;ri;mopen1;ri;fwflags;ri;"
                    "bigf;ri;jnl;ri;irec;ri;drec;ri;brec;ri;pcm;ri;"
                    "trunc1;ri;stat;open1;ri;rw1;ri;seek1;ri;ren;ri;"
                    "lu;ri;fc;ri;root;ri;dirs;ri;frd;ri;bfull;ri;"
                    "ssym;ri;find;ri;x;ri;append;ri;dsync;ri;");
}

extern struct dev_s dev;
// Agressive all. Run case 'all' for dev.numblocks in the range dev.numblocks..4
int case_aall(int p0, int p1)
{
    char myname[20];

    int i, failed = 0;
   
    if (dev.numblocks * dev.blocksize > 1024*1024)
        strcpy(myname, "/ffs/b999");
    else
        strcpy(myname, "/ffs/b99");

    /** There's no need to test for i=127,126,125,124..3,2,1. So for i
     * >= 20 we progress a little faster. */
    for (i = dev.numblocks; i >= 3; i -= (i >= 20 ? i/4 : 1))
    {
        tw(tr(TR_FUNC, TrTest, "TEST aall. %d\n", i));
        ttw(ttr(TTrTest, "TEST aall. %d" NL, i));

        error = tffs_preformat(0xDEAD);
        expect(error, EFFS_OK);

        sprintf(myname, "/ffs/b%d", i);
        tffs_format(myname, 0x2BAD);
        expect(error, EFFS_OK);
        
        failed += test_run("i;world;eu;bigf;bfull;"
                           "jnl;irec;drec;brec;stat;dsync;"
                           "open;rw;seek;trunc;ren;mopen;"
                           "lu;fc;root;dirs;frd;append;" 
                           "ssym;find;ri;x");

    }
    return failed;
}

// This is a collection of all failing testcases to be investigated.
int case_fail(int p0, int p1)
{
    int result;
    const char imeifile[] = "/europe/norway/IMEI";

    switch (p0) {
    case 1:
        tw(tr(TR_FUNC, TrTestHigh, 
              "remember to run case_fcontrol before this one\n"));
        // Make symlink to imeifile and try to update it
        error = tffs_symlink("/europe/imie", imeifile);
        expect(error, EFFS_ACCESS);

        error = tffs_fwrite("/europe/imie", TDATA(1));
        expect(error, EFFS_ACCESS);
        break;
    case 2:    
        if (1) {
            const char bigfile[] = "/iceberg";
            int bytes_max, file_size;
            char myname[] = "/ffs/b7";

            error = tffs_preformat(0xDEAD);
            expect(error, EFFS_OK);

            error = tffs_format(myname, 0x2BAD);
            expect(error, EFFS_OK);

            ffs_query(Q_BYTES_FREE, (uint32 *) &bytes_max);
  
// File the system with this huge file
            file_size = bytes_max;

            ttw(ttr(TTrTest, "Bigfile of size %d" NL, file_size));
            tw(tr(TR_FUNC, TrTestHigh, "Bigfile of size %d\n", file_size));

            error = tffs_fcreate(bigfile, (char *) tdata[TDATA_HUGE], file_size);
            expect(error, EFFS_OK);
        }
        break;
            
    default:
        result = 1;
    }
        return result;
}

// zzz to easy search ;-)
// NOTE: Move the below tests
extern char *tdata_huge;
// Test that a FFS API blocking call that not is able to send a mail to FFS fails as expected and don't lock the system (it must unlock and delete the mutex). 
int case_okay(int p0, int p1)
{

    error = tffs_fwrite("/test", tdata[TDATA_HUGE], 20);
    expect_ok(error);

    error = tffs_fread("/test", bigbuf, 20);
    expect_ok(error);

    bigbuf[20] = 0;

    tw(tr(TR_FUNC, TrTestHigh, "String '%s'\n", bigbuf));

    return EFFS_OK;
}

// Run testcases in a random order. Only the test cases in the test case
// table that are marked as re-runnable, are run.
int case_rand(int p0, int p1)
{
    int i, n, seed, max = 0, error = 0;
    const struct testcase_s *p;
// This is a way to activate trace at a defined test number. If rand
// test number 134 fail it is possible to activate trace at test number
// 133 etc. Note we have to change activate_trace_nr manual before compile.
    int activate_trace_nr = 0;       
    int trace_mask = 0xFFDFF;
// NOTE: use p1 as active_trace_nr? or make it

    p0 = (p0 == 0 ? 117 : p0); // Number of test cases to run
    p1 = (p1 == 0 ? 567 : p1); // Initial seed
    seed = p1;
    if (seed == 1) {
        ; // TODO: Set seed as a variable of current time
    }

    // TODO: Initialize seed from p1.

    // First count total number of test cases
    for (p = testcase; p->name; p++)
        max++;

    tw(tr(TR_FUNC, TrTestHigh,
          "Number of available random test cases = %d\n", max));

    for (i = 0; i < p0; i++)
    {
        do {
            n = rand() % max;
        } while ((testcase[n].flags & RND) == 0);

        if ((i + 1) == activate_trace_nr) {
#if (TARGET == 0)
            tr_init(trace_mask, 2, 0 );
#else
            ttr_init(trace_mask);
#endif
            tw(tr_bstat());
        }

        tw(tr(TR_FUNC, TrTest, "Nr: %d", i + 1));
        ttw(ttr(TTrTest, "Nr: %d" NL, i + 1));
        
        if ((error = test_run(testcase[n].name)))
            break;
    }
    
    if (p1 == 1)
        tw(tr(TR_FUNC, TrTestHigh, "Initial seed = %d\n", seed));
    
    test_statistics_print();
    return error;
}


/******************************************************************************
 * Population Tests
 ******************************************************************************/

int case_world(int p0, int p1)
{
    int i;
    const char *dirs[] = { "/antarctica",    "/africa",
                           "/asia",          "/europe",
                           "/north-america", "/south-america",
                           "/australia" };

    // Cleanup
    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        tffs_remove(dirs[i]);
    }

    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        error = tffs_mkdir(dirs[i]);
        expect(error, EFFS_OK);
    }
    return 0;
}


int case_europe(int p0, int p1)
{
    int i;
    const char *dirs[] = { "/europe/denmark", "/europe/sweden",
                           "/europe/norway",  "/europe/finland" };

    // Cleanup
    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        error = tffs_remove(dirs[i]);
    }

    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        error = tffs_mkdir(dirs[i]);
        expect(error, EFFS_OK);
    }
    return 0;
}

int case_denmark(int p0, int p1)
{
    // Cleanup
    tffs_remove("/europe/denmark/jutland");
    tffs_remove("/europe/denmark/sealand");

    error = tffs_mkdir("/europe/denmark/jutland");
    expect(error, EFFS_OK);
    error = tffs_mkdir("/europe/denmark/sealand");
    expect(error, EFFS_OK);

    return 0;
}


/******************************************************************************
 * Atomic Tests
 ******************************************************************************/

int case_init(int p0, int p1)
{
    error = tffs_initialize();
    // ignore error
    error = test_ffs_params_get();
    expect(error, EFFS_OK);
    return 0;
}

int case_exit(int p0, int p1)
{
    error = tffs_exit();
    expect(error, EFFS_OK);
    return 0;
}

int case_only_preformat(int p0, int p1)
{
    tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    return 0;
}

int case_only_format(int p0, int p1)
{
    tffs_format("/ffs", 0x2BAD);
    expect(error, EFFS_OK);
    return 0;
}

int case_reset(int p0, int p1)
{
    error = tffs_initialize();
    // ignore result
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs", 0x2BAD);
    expect(error, EFFS_OK);
    error = tffs_exit();
    expect(error, EFFS_OK);
    return 0;
}

int case_status(int p0, int p1)
{
    error = test_run("lsr");
    return error;
}


/******************************************************************************
 * Special Tests
 ******************************************************************************/

// Test FFS with only 2 blocks in device
int case_twob(int p0, int p1)
{
    int space_left;
    int size, i;
    char myname[] = "/two_block99";

    // Format ffs to use only 2 block no journal file and no reserved space.
    // Make some directoryes.
    // Fill ffs allmost to the edge and try to write over the edge.
    // Read back all the files.
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs/b2j0m0", 0x2BAD);
    expect(error, EFFS_OK);

    error = test_run("i;world;eu;x");
    if(error) return 1;

    // Calculate the amount of space that is left to user data because some space
    // is used to directoryes
    space_left = dev.blocksize - (13 * 20);
    tw(tr(TR_FUNC, TrTestHigh, "Space left: %d\n", space_left));
    
    size = space_left;
    for(i = 0; i < 6; i++) {
        size = size/2;
        sprintf(myname, "/two_block%d", i);
        error = tffs_fwrite(myname, (char*)tdata[TDATA_HUGE], 
                            size >= 20 ? size - 20 : 0);
        expect(error, EFFS_OK);
    }

    error = tffs_fwrite(myname, (char*)tdata[TDATA_HUGE], space_left/5);
    expect(error, EFFS_NOSPACE);
    
    // 20 is the average space used to the file name + word align. 
    size = space_left;
    for(i = 0; i < 6; i++) {
        size = size/2;
        sprintf(myname, "/two_block%d", i);
        error = tffs_fread(myname, bigbuf, bigbuf_size);
        expect(error, size >= 20 ? size - 20 : 0);
    }
    
    //error = ffs_object_control(0, OC_DEV_DEVICE, 0x080D);
    //if (error < 0) return error;

    return 0;
}

// Test FFS with only 3 blocks in device
int case_threeb(int p0, int p1)
{
    char mytest[20];
    
    // Format ffs to use only 3 blocks, make directoryes and run test case
    // rand <p0> number of times.
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs/b3r24", 0x2BAD);
    expect(error, EFFS_OK);

    error = test_run("i;world;eu;x");
    if (error) return 1;

    if(p0 == 0) p0 = 233;

    sprintf(mytest, "rand%d", p0);
    return test_run(mytest);
}


// Simulate a typical usage of ffs. We expect one part of the data will be
// what we call 'static' data (calibration files etc) the other part is the
// 'dynamic' data (wap, sms etc) we do also expect that they don't fill FFS
// completely thus we have a 'free' part.
int case_customer(int nblocks, int nloops)
{
    int free_part = 5, static_part = 47, dynamic_part = 47;
    int free_size, static_size, dynamic_size; 
    int bytes_free, bytes_max, bytes_used;
    int file_size_max, file_size_min, file_size_avg;
    int i, j, max_obj_block, num_static_files;
    char myname[] = "/ffs/bxxx/";
    char file_name[FFS_FILENAME_MAX + 1];
    char path[FFS_FILENAME_MAX * 2];
    char static_dir[] ="/READONLY";
    char dynamic_dir[] = "/rand";
    struct dir_s dir;

    if (nblocks == 0)
        nblocks = 7;
    if (nloops == 0)
        nloops = 300;

    error = tffs_preformat(0xDEAD);      expect(error, EFFS_OK);
    sprintf(myname, "/ffs/b%d", nblocks);
    error = tffs_format(myname, 0x2BAD); expect(error, EFFS_OK);

    tffs_mkdir(dynamic_dir); expect(error, EFFS_OK);
    tffs_mkdir(static_dir);  expect(error, EFFS_OK);

    ffs_query(Q_BYTES_FREE, &bytes_max);
    free_size = bytes_max * free_part / 100;
    static_size = bytes_max * static_part / 100;
    dynamic_size = bytes_max * dynamic_part / 100;
    tw(tr(TR_FUNC, TrTest, "Free:%d,%dkB. Static:%d,%dkB, Dynamic:%d,%dkB\n", 
          free_part, free_size/1024, static_part, static_size/1024, 
          dynamic_part, dynamic_size/1024));

// Find max average objects per block
    max_obj_block = fs.block_files_max;
    if (max_obj_block > fs.objects_max / (dev.numblocks -1 - fs.blocks_free_min))
        max_obj_block = fs.objects_max / (dev.numblocks -1 - fs.blocks_free_min);
    tw(tr(TR_FUNC, TrTest, "Max average objects per block %d\n", max_obj_block));
    ttw(ttr(TTrTest, "Max avg obj per block %d" NL, max_obj_block));

// Average (minimum) file size (avoid to reach block_files_max and
// objects_max)
    file_size_avg = dev.blocksize / max_obj_block; 

    ffs_query(Q_BYTES_USED, &bytes_used);
    tw(tr(TR_FUNC, TrTest, "Write static files (avg size %d)\n", file_size_avg));
    ttw(ttr(TTrTest, "avg size %d" NL, file_size_avg));
    do {
        i++;
        file_size_min = 5;
// For each 5 file we make one huge.
        file_size_max = i % 5 ? file_size_avg * 2 : file_size_avg * 15;
// For each 6 file we make one small.
        file_size_max = i % 6 ? file_size_max : 5;

// Saturate, avoid to make one file that parses the total static_size
        if (file_size_max > static_size - bytes_used)
            file_size_max = file_size_min = static_size - bytes_used;

// For each 7 file we create one dynamic (If the block is full with only
// valid objects do we risk that this block never will be reclaimed!).
        if (i % 7)
            error = case_mk_rand_file(dynamic_dir, file_size_max, file_size_min);
        else
            error = case_mk_rand_file(static_dir, file_size_max, file_size_min);
        expect_ok(error);
        ffs_query(Q_BYTES_USED, &bytes_used);
    } while (bytes_used < static_size); 

    num_static_files = tffs_opendir(static_dir, &dir);
    expect_ok(num_static_files);
    tw(tr(TR_FUNC, TrTest, "Written %d files\n", num_static_files));
    ttw(ttr(TTrTest, "Written %d files" NL, num_static_files));
// In a loop we continue to write data until only the wanted free_part is
// left, after that we remove half of the just created files and then write
// again and again...
    j = 0;
    tw(tr(TR_FUNC, TrTest, "Write and remove dynamic files\n"));
    ttw(ttr(TTrTest, "Write and remove dynamic files" NL));
    for (i = 0; i < nloops; i++) {
        if (i % 10 == 0) {
            tw(tr(TR_FUNC, TrTest, "loop %d\n", i));
            ttw(ttr(TTrTest, "loop %d" NL, i));
        }
        ffs_query(Q_BYTES_FREE, &bytes_free);
        do {
            j++;
            file_size_min = 5;
            file_size_max = j % 5 ? file_size_avg * 2 : file_size_avg * 15;
            file_size_max = j % 6 ? file_size_max : 5;
// Saturate, avoid using the free space.
            if (file_size_max > bytes_free - free_size)
                file_size_max = file_size_min = bytes_free - free_size;

            if (file_size_max < 0)
                break;

            error = case_mk_rand_file(dynamic_dir, file_size_max, file_size_min);
// NOTE it is very difficult to avoid EFFS_FSFULL because cleanup will
// sometimes only remove the big files thus there will exist a lot of small
// that use all the inodes.
            if (error == EFFS_FSFULL) {
                tw(tr(TR_FUNC, TrTest, "Warning no free inodes\n"));
                ttw(ttr(TTrTest, "Warning no free inodes" NL));
                error = case_cleanup(free_size + dynamic_size);    
            }
            else
                expect_ok(error);

            ffs_query(Q_BYTES_FREE, &bytes_free);
        } while (bytes_free > free_size); 

        error = case_cleanup(free_size + dynamic_size / 2);    
        expect_ok(error);
    }

    error = case_reinit(0, 0);
    expect(EFFS_OK, error);

    tw(tr(TR_FUNC, TrTest, "Verify all files\n"));
    ttw(ttr(TTrTest, "Verify all files" NL));
    for (i = 0; i < 2; i++) {
        if (i == 0) {
            error = tffs_opendir(static_dir, &dir);
            expect_eq(error, num_static_files);
        }
        else {
            error = tffs_opendir(dynamic_dir, &dir);
            expect_ok(error);
        }

        while ((error = tffs_readdir(&dir, file_name, sizeof(file_name))) > 0) {
            if (i == 0)
                strcpy(path, static_dir);
            else 
                strcpy(path, dynamic_dir);
            strcat(path, "/");
            strcat(path, file_name);

            error = tffs_stat(path, &stat);
            expect_ok(error);
            error = test_expect_file(path, tdata[TDATA_HUGE], stat.size); 
            if (error) return 1;
        }
    }

    test_statistics_print();
    return 0;
}


// TODO cleanup this test. Use stat and verify some of the files etc.

// Used to test ConQuest issue FFS_FIX-11368 We fill FFS with a lot of small
// files. Then by turn remove some files and write new files, always very
// close to max avaible bytes.

// Note the below test is not able to run in big configuration because it
// creates a lot of small files thus we reach EFFS_FSFULL 'out of inodes'
int case_ffull(int p0, int p1)
{       
    int error, i, bytes_free, file_size, max_write_size = 1000;
    char myname[] = "/ffs/bxxx/";

    if (p0 == 0) 
        p0 = 100; // Default 100 loops
    if (p1 == 0 || p1 > 999)
        p1 = 3;   // Default run in 3 blocks
   
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);

    sprintf(myname, "/ffs/b%d", p1);
    error = tffs_format(myname, 0x2BAD);
    expect(error, EFFS_OK);

    tffs_mkdir("/rand");
    expect(error, EFFS_OK);

    tw(tr(TR_FUNC, TrTest, "Fill it with files until it fails\n"));
    ttw(ttr(TTrTest, "Fill it with files until it fails" NL));

// We have to avoid too many small files or else do we reach objects_max
    ffs_query(Q_BYTES_FREE, &bytes_free);
    max_write_size = bytes_free / fs.objects_max * 2.5;

    do {
        error = case_mk_rand_file("/rand", max_write_size, 0);
    } while (error >= 0 );

    tw(tr(TR_FUNC, TrTest, "Fill done (%d)\n" ,error));
    ttw(ttr(TTrTest, "Fill done (%d)" NL, error));
    expect(error, EFFS_NOSPACE);  // Now are we sure that it is full

    tw(tr(TR_FUNC, TrTest, "Cleanup and fill again \n"));
    for (i = 0; i < (1 + p0); i++) {
        tw(tr(TR_FUNC, TrTestHigh, "Loop %d \n", i + 1));
        ttw(ttr(TTrTest, "Loop %d" NL, i + 1));

        tw(tr_bstat());
        error = case_cleanup(max_write_size * 3);    
        tw(tr_bstat());

        do {
            ffs_query(Q_BYTES_FREE, &bytes_free);
// Saturate size
            file_size = bytes_free > max_write_size ? max_write_size :bytes_free;
// Query BYTES_FREE don't count the below data use.
//file_size -= (FFS_FILENAME_MAX + 1 + dev.atomsize);
            if (file_size < 0)
                break;

            error = case_mk_rand_file("/rand", file_size - (FFS_FILENAME_MAX+1), 0);

            if (error == EFFS_FSFULL) { // Out of inodes
                error = case_cleanup(max_write_size * 6);    
                // Create bigger files to avoid '-11', auto fit :-)
                max_write_size *= 1.1;  // Add 10%
                tw(tr(TR_FUNC, TrTestHigh, "Out of inodes, write size: %d \n", 
                      max_write_size));
                continue;
            }

            expect_ok(error);  

        } while (file_size != bytes_free);  
    }

    tw(tr(TR_FUNC, TrTest, "Test finish (%d) \n", error)); 
    ttw(ttr(TTrTest, "Test finish (%d)" NL, error));

    if (error >= 0)
        return EFFS_OK;

    return error;    
}

int case_rivtype(int p0, int p1)
{
    // Use the compiler to test if the types are correctly defined. We don't
    // need to run this test just make a compilation.
    T_FFS_OPEN_FLAGS flags = FFS_O_RDONLY;
    T_FFS_SIZE size = 20;
    T_FFS_RET error = -10;
    T_FFS_WHENCE whence = FFS_SEEK_SET;
    T_FFS_STAT stat;
    
    T_FFS_DIR dir;
    T_FFS_FD fd = FFS_FD_OFFSET;
    T_RV_RETURN callback;
    T_FFS_FILE_CNF confirm;
    T_FFS_OBJECT_TYPE objt = OT_FILE;

    // Use the defined variables to avoid the boring warning: unused
    // variable 'xx'
    if (flags != FFS_O_RDONLY)  return 1;
    if (size != 20)             return 1;
    if (error != -10)           return 1;
    if (whence != FFS_SEEK_SET) return 1;
    if (fd != FFS_FD_OFFSET)    return 1;
    if (objt != OT_FILE)        return 1;
   
    stat.type = 1;
    if (stat.type != 1) return 1;
    dir.this = 5;
    if (dir.this != 5) return 1;
    callback.addr_id = 3;
    if (callback.addr_id != 3) return 1;
    confirm.error = -1;
    if (confirm.error != -1) return 1;

    return 0;
}

/******************************************************************************
 * Pseudo Tests
 ******************************************************************************/

// Recursively read objects in directory. Does NOT check for buffer
// overflow!
int case_dir_list(char *dirname, struct object_s **pplist, char **ppnames)
{
    struct object_s *plist, *plist_start, *plist_end;
    char pathname[6 * 20];
    struct dir_s dir;
    char *pnames, *pn;
    int i, pathlen;

    //tw(tr(TR_BEGIN, TrTestHigh, "dir_list('%s', %d, 0x%x)\n",
    //      dirname, (int)*pplist/sizeof(struct object_s), *ppnames));
    plist_start = plist = *pplist;
    pnames = *ppnames;

    strcpy(pathname, dirname);
    pathlen = strlen(pathname);

    // remove trailing slash. It is tiring to handle the root directory
    // differently from other directories. In a future ffs revision, this
    // trailing slash should be allowed!
    if (pathname[pathlen - 1] == '/') {
        pathname[pathlen - 1] = 0;
        pathlen--;
    }
        
    if (strlen(pathname) == 0)
        error = tffs_opendir("/", &dir);
    else
        error = tffs_opendir(pathname, &dir);
    expect_ok(error);

    pn = pathname + strlen(pathname);
    *pn++ = '/';
    *pn = 0;

    error = 1;
    for (i = 0; (error = tffs_readdir(&dir, pn, 21)) > 0; i++)
    {
        error = tffs_xlstat(pathname, &plist->stat);
        expect(error, EFFS_OK);

        // Copy full object pathname to buffer, working downwards.
        pnames -= strlen(pathname) + 1;
        // Check for buffer overflow (if pnames <= plist)
        expect_gt((int) pnames, (int) plist);
        strcpy(pnames, pathname);
        plist->name = pnames;

        plist++;
    }
    *pplist = plist;
    *ppnames = pnames;
    
    // For each directory in the retrieved list, recurse.
    plist_end = plist;
    for (plist = plist_start; plist < plist_end; plist++) {
        if (plist->stat.type == OT_DIR)
            i += case_dir_list(plist->name, pplist, ppnames);
    }
    // tw(tr(TR_END, TrTestHigh, "} %d\n", i));
    return i;
}

int case_find(int p0, int p1)
{
    struct object_s *plist, *plist_start;
    char *pnames, *pnames_start;
    int n, names_used, list_used;

    plist  = plist_start  = (struct object_s *) bigbuf;
    pnames = pnames_start = bigbuf + bigbuf_size;
    n = case_dir_list("/", &plist, &pnames);

    list_used = n * sizeof(struct object_s);
    names_used = pnames_start - pnames;

    tw(tr(TR_FUNC, TrTestHigh, "Buffer space used: %d + %d = %d\n",
          list_used, names_used, list_used + names_used));
    ttw(ttr(TTrTest, "Buffer space used: %d + %d = %d" NL,
            list_used, names_used, list_used + names_used));

    return 0;
}

// TODO: We should accumulate all stat.space and check it vs. the number of
// bytes used!
int case_lsr(int p0, int p1)
{
    struct object_s *plist, *plist_start;
    char *pnames, *pnames_start;
    char of[3];
    int i, n;

    plist  = plist_start  = (struct object_s *) bigbuf;
    pnames = pnames_start = bigbuf + bigbuf_size;
    n = case_dir_list("/", &plist, &pnames);
    tw(tr(TR_FUNC, TrTestHigh, "Total %d objects.\n", n));
    ttw(ttr(TTrTest, "Total %d objects" NL, n));

    plist = plist_start;
    for (i = 0; i < n; i++, plist++) {
        strcpy(of, "  ");
        switch (plist->stat.type) {
        case OT_FILE: of[0] = ' '; break;
        case OT_DIR:  of[0] = 'd'; break;
        case OT_LINK: of[0] = 'l'; break;
        }
        if (plist->stat.flags & OF_READONLY)
            of[1] = 'r';
            
#if (TARGET == 0)
        printf("%3d: %s %3d %2d/%04X (%4d,%4d) %5d %s\n",
               i, of, 
               plist->stat.inode, plist->stat.block, 
               plist->stat.location, plist->stat.sequence, plist->stat.updates, 
               plist->stat.size, &plist->name[1]);
#else
        ttw(ttr(TTrTest, "%3d: %s %3d %2d/%04X (%4d,%4d) %5d %s" NL,
                i, of, 
                plist->stat.inode, plist->stat.block, 
                plist->stat.location, plist->stat.sequence, plist->stat.updates, 
                plist->stat.size, &plist->name[1]));
#endif
    }

    return 0;
}


/******************************************************************************
 * Normal Tests
 ******************************************************************************/

int case_format(int p0, int p1)
{
    error = tffs_preformat(0xD00D);
    expect(error, EFFS_INVALID);
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs/", 0xDEAD);
    expect(error, EFFS_INVALID);
    error = tffs_format("ffs", 0x2BAD);
    expect(error, EFFS_BADNAME);
    error = tffs_format("", 0x2BAD);
    expect(error, EFFS_BADNAME);
    error = tffs_format("/", 0x2BAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs", 0x2BAD);
    expect(error, EFFS_NOPREFORMAT);
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format(0, 0x2BAD);
//error = tffs_format("/ffs/i256o128", 0x2BAD);
    expect(error, EFFS_OK);
    return 0;
}

// Test that it is illegal to modify root inode as if it was a normal file
// or directory
int case_root(int p0, int p1)
{
    error = tffs_opendir("/", &dir);
    expect_ok(error);
    error = tffs_fcreate("/", "foo", 3);
    expect(error, EFFS_EXISTS);
    error = tffs_fupdate("/", "bar", 3);
    expect(error, EFFS_NOTAFILE);
    error = tffs_fwrite("/", "foo", 3);
    expect(error, EFFS_NOTAFILE);
    error = tffs_remove("/");
    expect(error, EFFS_ACCESS);
    return 0;
}

// Test object lookup, object names etc.
int case_lookup(int p0, int p1)
{
    // Swedish Provinces: Blekinge, Smaaland, Halland, Vaermland, Dalarna,
    // Dalsland, Gotland, Gaestrikland, Haelsingland, Bohuslaen,
    // Haerjedalen, Jaemtland, Lappland, Medelpad, Norrbotten, Naerke,
    // Soedermanland, Uppland, Vaesterbotten

    // Smaaland Cities: Vetlanda, Bodafors, Rottne, Ljungby, Nybro,
    // Hultsfred, Oskarshamn, Vimmerby, Hyltebruk, Joenkoeping, Vaexjoe

    tffs_mkdir("/europe");
    tffs_mkdir("/europe/sweden");

    // test init
    error = tffs_mkdir(LUDIR "/Smaaland");
    expect(error, EFFS_OK);
    // test BAD_FILENAME
    error = tffs_fcreate("", TDATA(0));
    expect(error, EFFS_BADNAME);
    // test EFFS_EXISTS
    error = tffs_fcreate(LUDIR "/Smaaland/vetlanda", TDATA(0));
    expect(error, EFFS_OK);
    error = tffs_fcreate(LUDIR "/Smaaland/vetlanda", TDATA(0));
    expect(error, EFFS_EXISTS);
    error = tffs_mkdir(LUDIR "/Smaaland");
    expect(error, EFFS_EXISTS);
    error = tffs_fwrite(LUDIR "/Smaaland/vetlanda", TDATA(1));
    expect(error, EFFS_OK);
    // test EFFS_BADNAME
    error = tffs_fcreate(LUDIR "/Smaaland/A_Zaz.0+9-7!", TDATA(2));
    expect(error, EFFS_BADNAME);
    error = tffs_fcreate(LUDIR "/Smaaland/A_Zaz.0+9-7#%$", TDATA(2));
    expect(error, EFFS_OK);
    // test ending slash
    error = tffs_mkdir(LUDIR "/Smaaland/Vaexjoe/");
    expect(error, EFFS_NOTADIR);
    error = tffs_fcreate(LUDIR "/Smaaland/Vaexjoe/", TDATA(3));
    expect(error, EFFS_NOTADIR);
    // test EFFS_NAMETOOLONG
    error = tffs_fcreate(LUDIR "/Smaaland/Hultsfred-is-21-chars", TDATA(4));
    expect(error, EFFS_NAMETOOLONG);
    error = tffs_fcreate(LUDIR "/Smaaland/Bodafors-is-20-chars", TDATA(4));
    expect(error, EFFS_OK);
    error = tffs_mkdir(LUDIR "/Vaermland-is-21-chars");
    expect(error, EFFS_NAMETOOLONG);
    error = tffs_mkdir(LUDIR "/Dalsland-is-20-chars");
    expect(error, EFFS_OK);
    // test EFFS_NOTADIR
    error = tffs_mkdir(LUDIR "/DontMakeSeveral/DirsAt/TheSameTime");
    expect(error, EFFS_NOTADIR);
    error = tffs_fread(LUDIR "/Lappland/Bodafors-is-20-chars", bigbuf, 1024);
    tw(tr(TR_END, TrApi, "} %d\n", error)); // Avoid wrong indent 
    expect(error, EFFS_NOTADIR);
    error = tffs_fread(LUDIR "/Lappland/", bigbuf, 1024);
    tw(tr(TR_END, TrApi, "} %d\n", error)); // Avoid wrong indent 
    expect(error, EFFS_NOTADIR);
    error = tffs_fread(LUDIR "/Smaaland/Bodafors", TDATA(4));
    tw(tr(TR_END, TrApi, "} %d\n", error)); // Avoid wrong indent 
    expect(error, EFFS_NOTFOUND);
    // test EFFS_PATHTOODEEP
    error = tffs_mkdir(LUDIR "/Gotland"); // 3. level
    expect(error, EFFS_OK);
    error = tffs_mkdir(LUDIR "/Gotland/Visby"); // 4. level
    expect(error, EFFS_OK);
    error = tffs_mkdir(LUDIR "/Gotland/Visby/level5"); // 5. level
    expect(error, EFFS_OK);
    error = tffs_mkdir(LUDIR "/Gotland/Visby/level5/level6"); // 6. level
    expect(error, EFFS_OK);
    error = tffs_mkdir(LUDIR "/Gotland/Visby/level5/level6/level7"); // 7. level
    expect(error, EFFS_PATHTOODEEP);
    error = tffs_fcreate(LUDIR "/Gotland/Visby/level5/level6/level7",
                         TDATA(5)); // 7. level
    expect(error, EFFS_PATHTOODEEP);

    // final checks
    error = test_expect_file(LUDIR "/Smaaland/vetlanda", TDATA(1));
    if (error) return 1;
    error = test_expect_file(LUDIR "/Smaaland/Bodafors-is-20-chars", TDATA(4));
    if (error) return 1;
    error = tffs_opendir(LUDIR "/Dalsland-is-20-chars", &dir);
    expect_ok(error);
    error = tffs_opendir(LUDIR "/Gotland/Visby/level5/level6", &dir);
    expect_ok(error);
    
    // cleanup
    error = tffs_remove(LUDIR "/Smaaland/A_Zaz.0+9-7");
    return 0;
}


// Test fcontrol and read-only semantics. TODO: We still need to perform
// same tests on a dir and a symlink and thru a symlink.
int case_fcontrol(int p0, int p1)
{
    struct ffs_state_s old, new;
    const char rofile[]   = "/europe/norway/rofile";
    const char imeifile[] = "/europe/norway/IMEI";
    fd_t fdi;

    // Cleanup
    tffs_remove(rofile);
    tffs_remove("/europe/norway");
    tffs_remove("/europe");

    // Initialize
    error = tffs_mkdir("/europe");
    error = tffs_mkdir("/europe/norway");
    error = tffs_fcreate(rofile, TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_stat(rofile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, 0);

    test_ffs_state_get(&old);

    // set read-only flag
    error = tffs_fcontrol(rofile, OC_FLAGS, OF_READONLY);
    expect(error, EFFS_OK);
    error = tffs_stat(rofile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, OF_READONLY);

    test_ffs_state_get(&new);
    expect_eq(new.objects_total, old.objects_total);
    expect_ne(new.inodes_used,  old.inodes_used);
    test_ffs_state_copy(&old, &new);

    // Set illegal flags. Try to fupdate file. Then try to set read-only
    // flag again.
    error = tffs_fcontrol(rofile, OC_FLAGS, 1<<0);
    expect(error, EFFS_INVALID);
    error = tffs_fcontrol(rofile, OC_FLAGS, 1<<3);
    expect(error, EFFS_INVALID);
    error = tffs_fcontrol(rofile, OC_FLAGS, 1<<5);
    expect(error, EFFS_INVALID);
    error = tffs_fcontrol(rofile, OC_FLAGS, 1<<6);
    expect(error, EFFS_INVALID);
    error = tffs_fupdate(rofile, TDATA(3));
    expect(error, EFFS_OK);
    
    error = tffs_fcreate("/europe/norway/tease", TDATA(2));
    expect(error, EFFS_OK);
    tffs_remove("/europe/norway/tease");

    error = tffs_fcontrol(rofile, OC_FLAGS, OF_READONLY);
    expect(error, EFFS_OK);
    error = tffs_stat(rofile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, OF_READONLY);

    test_ffs_state_get(&new);
    expect_eq(new.objects_total, old.objects_total);
    expect_ne(new.inodes_used,  old.inodes_used);
    test_ffs_state_copy(&old, &new);

    // clear read-only flag (this works because ffs_is_modifiable() by
    // default returns true for all objects except for object names ending
    // in "IMEI".
    error = tffs_fcontrol(rofile, OC_FLAGS, 0);
    expect(error, EFFS_OK);
    error = tffs_stat(rofile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, 0);

    test_ffs_state_get(&new);
    expect_eq(new.objects_total, old.objects_total);
    expect_ne(new.inodes_used,  old.inodes_used);
    test_ffs_state_copy(&old, &new);

    // Set read-only flag (again)
    error = tffs_fcontrol(rofile, OC_FLAGS, OF_READONLY);
    expect(error, EFFS_OK);
    error = tffs_stat(rofile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, OF_READONLY);

    test_ffs_state_get(&new);
    expect_eq(new.objects_total, old.objects_total);
    expect_ne(new.inodes_used,  old.inodes_used);
    test_ffs_state_copy(&old, &new);

    // Set read-only flag of IMEI file
    error = tffs_fcreate(imeifile, TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_stat(imeifile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, 0);
    error = tffs_fcontrol(imeifile, OC_FLAGS, OF_READONLY);
    expect(error, EFFS_OK);
    error = tffs_stat(imeifile, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.flags, OF_READONLY);

    test_ffs_state_get(&new);
    expect_eq(new.objects_total, old.objects_total + 1);
    expect_ne(new.inodes_used,  old.inodes_used);
    test_ffs_state_copy(&old, &new);

    // Try to remove, fupdate, fwrite and fcontrol IMEI file.
    error = tffs_remove(imeifile);
    expect(error, EFFS_ACCESS);
    error = tffs_fupdate(imeifile, TDATA(0));
    expect(error, EFFS_ACCESS);
    error = tffs_fwrite(imeifile, TDATA(0));
    expect(error, EFFS_ACCESS);
    error = tffs_fcontrol(imeifile, OC_FLAGS, 0);
    expect(error, EFFS_ACCESS);

    // Try to open IMEI file in write-only and read-only
    fdi = tffs_open(imeifile, FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, EFFS_ACCESS);

    fdi = tffs_open(imeifile, FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    error = tffs_close(fdi);
    expect(error, EFFS_OK);

    return 0;
}

// Test symlink functionality of simple symlink implementation.
// Fixme: add remove file through symlink, stat a dir through a symlink.
int case_ssym(int p0, int p1)
{
    int size;
    fd_t fdi;
    // two links, read files thru links
    // link to link to file, open file
    // link to non-valid object
    // 
    // make three test files, a link to each of these, one link to dir, one
    // link to non-existing object, one link to link to file

    tffs_mkdir("/europe");
    tffs_mkdir("/europe/denmark");

    error = tffs_fwrite("/europe/denmark/aalborg", TDATA(1));
    expect(error, EFFS_OK);
    error = tffs_symlink("/europe/aal", "/europe/denmark/aalborg");
    expect(error, EFFS_OK);
    error = tffs_symlink("/dk", "/europe/denmark");
    expect(error, EFFS_OK);
    error = tffs_fwrite("/europe/denmark/aarhus", TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_symlink("/europe/aar", "/europe/denmark/aarhus");
    expect(error, EFFS_OK);
    error = tffs_symlink("/europe/se", "/europe/non-existing");
    expect(error, EFFS_OK);
    error = tffs_fwrite("/europe/denmark/billund", TDATA(3));
    expect(error, EFFS_OK);
    error = tffs_symlink("/europe/bil", "/europe/denmark/billund");
    expect(error, EFFS_OK);
    error = tffs_symlink("/lego", "/europe/bil");
    expect(error, EFFS_OK);
    error = tffs_fwrite("/europe/denmark/norresundby", TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_symlink("/europe/nor", "/europe/denmark/norresundby");
    expect(error, EFFS_OK);

    // Test link to dir
    error = tffs_opendir("/dk", &dir);
    expect(error, EFFS_NOTAFILE); // TODO: strange error!
    error = tffs_stat("/dk", &stat);
    expect(error, EFFS_NOTAFILE); // TODO: strange error!
    error = tffs_linkstat("/dk", &stat);
    expect(error, EFFS_OK);

    // Test link to link to file
    error = tffs_stat("/lego", &stat);
    expect(error, EFFS_NOTAFILE); // TODO: strange error?!
    error = tffs_linkstat("/lego", &stat);
    expect(error, EFFS_OK);
    error = tffs_fread("/lego", bigbuf, bigbuf_size);
    tw(tr(TR_END, TrApi, "} %d\n", error));  // Avoid wrong indent 
    expect(error, EFFS_NOTAFILE);

    // Test link to non-existing object
    error = tffs_opendir("/europe/se", &dir);
    expect(error, EFFS_NOTFOUND);
    error = tffs_stat("/europe/se", &stat);
    expect(error, EFFS_NOTFOUND);
    error = tffs_linkstat("/europe/se", &stat);
    expect(error, EFFS_OK);

    // Read files through links
    error = test_expect_file("/europe/aal", TDATA(1));
    if (error) return 1;
    error = test_expect_file("/europe/aar", TDATA(2));
    if (error) return 1;
    error = test_expect_file("/europe/bil", TDATA(3));
    if (error) return 1;

    // Write files through links
    error = tffs_fwrite("/europe/aal", TDATA(3));
    expect(error, EFFS_OK);
    error = tffs_fupdate("/europe/aar", TDATA(4));
    expect(error, EFFS_OK);
    error = tffs_fupdate("/europe/bil", TDATA(5));
    expect(error, EFFS_OK);

    // Open file and write and read files through links
    fdi = tffs_open("/europe/nor", FFS_O_WRONLY | FFS_O_TRUNC | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);    
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 50);
    expect(size, 50);
    error = tffs_close(fdi);
    expect(error, 0);

    fdi = tffs_open("/europe/nor", FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);    
    size = tffs_read(fdi, bigbuf, 62);
    expect(size, 50);
    error = test_expect_data((char*)tdata[TDATA_HUGE], bigbuf, 50);
    if (error) return 1;
    error = tffs_close(fdi);
    expect(error, 0);

    // remove "lego" link, recreate it to link to "billund", read file data
    // through link, re-write file data through link
    error = tffs_symlink("/lego", "/europe/denmark/billund");
    expect(error, EFFS_NOTAFILE); // TODO: strange error?!
    error = tffs_fupdate("/lego", "/europe/denmark/billund",
                         strlen("/europe/denmark/billund"));
    expect(error, EFFS_NOTAFILE);
    error = tffs_remove("/lego");
    expect(error, EFFS_OK);
    error = tffs_symlink("/lego", "/europe/denmark/billund");
    expect(error, EFFS_OK);
    error = test_expect_file("/lego", TDATA(5));
    if (error) return 1;
    error = tffs_fupdate("/lego", TDATA(2));
    expect(error, EFFS_OK);

    // Re-Read files through links
    error = test_expect_file("/europe/aal", TDATA(3));
    if (error) return 1;
    error = test_expect_file("/europe/aar", TDATA(4));
    if (error) return 1;
    error = test_expect_file("/europe/bil", TDATA(2));
    if (error) return 1;

    // Clean up
    error = tffs_remove("/europe/aal");
    expect(error, EFFS_OK);
    error = tffs_remove("/dk");
    expect(error, EFFS_OK);
    error = tffs_remove("/europe/aar");
    expect(error, EFFS_OK);
    error = tffs_remove("/europe/se");
    expect(error, EFFS_OK);
    error = tffs_remove("/europe/bil");
    expect(error, EFFS_OK);
    error = tffs_remove("/lego");
    expect(error, EFFS_OK);
    error = tffs_remove("/europe/nor");
    expect(error, EFFS_OK);

    return 0;
}

// Test symlink functionality of full symlink implementation
int case_fsym(int p0, int p1)
{
    fd_t fdi;
    // make relative link to directory, stat link and directory
    error = tffs_symlink("/sa", "/south-america"); // relative link
    expect(error, EFFS_OK);

    error = tffs_linkstat("/sa", &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.type, OT_LINK);

    error = tffs_stat("/sa", &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.type, OT_DIR);

    // create directory thru a symlink
    error = tffs_mkdir("/sa/brazil");
    expect(error, EFFS_OK);
    error = tffs_symlink("/br.ba", "/brazil/buenos-aires"); // relative link
    expect(error, EFFS_OK);

    // create file via symlink, stat the new file
    error = tffs_fcreate("/sa/brazil/buenos-aires", TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_stat("/sa/br.ba", &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.type, OT_FILE);

    fdi = tffs_open("/sa/peru", FFS_O_WRONLY | FFS_O_TRUNC | 
                    FFS_O_APPEND | FFS_O_CREATE);
    expect(fdi, FFS_FD_OFFSET);    
    error = tffs_close(fdi);
    expect(error, 0);

    // create file thru a symlink, stat the new file. Will this work?
    error = tffs_symlink("/br.br", "/brazil/brasilia"); // relative link
    expect(error, EFFS_OK);
    error = tffs_fcreate("/sa/br.br", TDATA(3)); // ???
    expect(error, EFFS_OK);
    error = tffs_stat("/sa/br.br", &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.type, OT_FILE);
    
    // Create symlink that is a link to absolute link path
    error = tffs_symlink("/south-america/cape-horn", "/sa/ar"); // absolute link
    expect(error, EFFS_OK);

    error = tffs_fcreate("/south-america/argentina", TDATA(0));

    expect(error, EFFS_OK);
    error = tffs_symlink("/sa/ar", "/south-america/argentina");
    expect(error, EFFS_OK);
    
    // TODO: Test very deep path

    // TODO: Test circular link

    // TODO: Test if ending slash is allowed on dirs and symlinks

    tw(tr(TR_FUNC, TrTest, "WARNING: Test case not implemented!\n"));
    ttw(str(TTrTest, "Test case not implemented!" NL));

    return 0;
}

// Check every combination of name length and data size within the device
// atomsize,
int case_fread(int p0, int p1)
{
    const char fullname[] = "123456789ABCDEF0123456789ABCDEF";
    int i, j, dirlen, mysize;
    char *mydata;
    char myname[20+20+32] = "/australia/f";

    case_cleanup(0x11 * param.atomsize * param.atomsize); 

    tffs_mkdir("/australia");
 
    mydata = (char *) tdata[TDATA_HUGE]; 
    dirlen = strlen(myname);

    for (j = 0; j < param.atomsize; j++) {
        mysize = j * 0x11 /* + 0x100 */;
        ttw(ttr(TTrTest, "frd: size = %x" NL, mysize));
        tw(tr(TR_FUNC, TrTestHigh, "frd: size = %x\n", mysize));
        // remove files
        for (i = 0; i < param.atomsize; i++) {
            strncpy(&myname[dirlen], fullname, i);
            myname[dirlen + i] = 0;
            error = tffs_remove(myname);
        }
        // fcreate files with varying name lengths but same alignment.
        for (i = 0; i < param.atomsize; i++) {
            strncpy(&myname[dirlen], fullname, i);
            myname[dirlen + i] = 0;
            error = tffs_fwrite(myname, mydata, mysize);
            expect(error, EFFS_OK);
        }
        // Now check all the files written
        for (i = 0; i < param.atomsize; i++) {
            strncpy(&myname[dirlen], fullname, i);
            myname[dirlen + i] = 0;
            //tw(tr(TR_FUNC, TrTestHigh, "frd: txf('f...', %x, %d)\n",
            //      mydata, mysize));
            error = test_expect_file(myname, mydata, mysize);
            if (error) return 1;
        }
        mydata += 0x10;
    }
    
    for (i = 0; i < param.atomsize; i++) {
        strncpy(&myname[dirlen], fullname, i);
        myname[dirlen + i] = 0;
        error = tffs_remove(myname);
        expect(error, EFFS_OK);
    }
    return 0;
}


/******************************************************************************
 * Non-finished
 ******************************************************************************/

// Test Directories
int case_dirs(int p0, int p1)
{
    // remove empty dir, non-empty dir
    // open/readdir empty dir, non-empty dir
    char name[21];
    int i, j;
    struct dir_s dir[3];
    const char *names[3][5] = {
        { "china",    "japan",    "korea", "india", 0 },
        { "bombay",   "newdelhi", 0,       0,       0 },
        { "hongkong", "shanghai", "hk",    0,       0 }
    };
    
    // Cleanup
    tffs_mkdir("/asia");
    tffs_remove("/asia/india/bombay");
    tffs_remove("/asia/india/newdelhi");
    tffs_remove("/asia/india");

    error = tffs_mkdir("/asia/china");
    expect(error, EFFS_OK);
    error = tffs_mkdir("/asia/china/beijing");
    expect(error, EFFS_OK);
    error = tffs_mkdir("/asia/china/hongkong");
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/china/hongkong/hkfile1", TDATA(1));
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/china/shanghai", TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_symlink("/asia/china/hk", "/asia/china/hongkong");
    expect(error, EFFS_OK);
    error = tffs_mkdir("/asia/japan");
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/thailand", TDATA(0));
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/korea", TDATA(2));
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/japan/tokyo", TDATA(3));
    expect(error, EFFS_OK);
    error = tffs_fupdate("/asia/japan/tokyo", TDATA(4));
    expect(error, EFFS_OK);
    error = tffs_mkdir("/asia/india");
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/india/bombay", TDATA(0));
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/india/newdelhi", TDATA(1));
    expect(error, EFFS_OK);
    error = tffs_fcreate("/asia/india/calcutta", TDATA(2));
    expect(error, EFFS_OK);

    error = tffs_opendir("/asia", &dir[0]);
    expect(error, 5);
    error = tffs_opendir("/asia/india", &dir[1]);
    expect(error, 3);
    error = tffs_opendir("/asia/china", &dir[2]);
    expect(error, 4);

    // remove first, middle and last entry in a dir
    error = tffs_remove("/asia/china/beijing");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/thailand");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/india/calcutta");
    expect(error, EFFS_OK);

    for (j = 0; j < 5; j++) {
        for (i = 0; i < 3; i++) {
            error = tffs_readdir(&dir[i], name, 21);
            if (names[i][j] == NULL) {
                expect(error, EFFS_OK);
            }
            else {
                expect_gt(error, EFFS_OK);
                tw(tr(TR_FUNC, TrTestHigh, "dir[%d]: %10s, expected: %s\n",
                      i, name, names[i][j]));
                test_expect_data(name, TDATA_STRING(names[i][j]));
            }
        }
    }

    error = tffs_remove("/asia/china");
    expect(error, EFFS_DIRNOTEMPTY);
    error = tffs_remove("/asia/china/hongkong");
    expect(error, EFFS_DIRNOTEMPTY);
    error = tffs_remove("/asia/china/hongkong/hkfile1");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/china/shanghai");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/china/hk");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/china");
    expect(error, EFFS_DIRNOTEMPTY);
    error = tffs_remove("/asia/china/hongkong");
    expect(error, EFFS_OK);
    error = tffs_opendir("/asia/china", &dir[2]);
    expect(error, 0);
    error = tffs_remove("/asia/china");
    expect(error, EFFS_OK);

    error = tffs_remove("/asia/korea");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/japan/tokyo");
    expect(error, EFFS_OK);
    error = tffs_remove("/asia/japan");
    expect(error, EFFS_OK);

    return 0;
}

// Check that expected stat data was read
int case_expect_stat(const char *name, int n)
{
    error = tffs_stat(name, &stat);
    expect(error, EFFS_OK);
    // test type, size, flags, space?
    // test range of location, inode, block
    return 0;
}

// Test stat
int case_stat(int p0, int p1)
{
    struct xstat_s xstat;
    struct stat_s stat;
    char myname[] = "/Stat_file";
    char sym_name[] = "/sf";
    char stream_name[] = "/Stat_stream";
    fd_t fdi;
    int size, i, file_size = 0;

    // test stat on dirs, symlinks and files
    // check stat.block (by writing two HUGE files)

    case_cleanup(fs.chunk_size_max / 5 * 20);

    // NOTEME: this is a very limited test
    error = tffs_fwrite(myname, (char *)tdata[TDATA_HUGE], 99); 
    expect_ok(error);

    error = tffs_stat(myname, &stat);
    expect(error, EFFS_OK);
    expect(stat.type, OT_FILE);
    expect(stat.flags, 0);
    expect_ok(stat.inode);
    expect(stat.size, 99);
    
//    error = tffs_xstat(myname, &xstat);
//    expect(error, EFFS_OK);
    
//    expect(error, EFFS_OK);
//    expect(xstat.type, OT_FILE);
//    expect(xstat.flags, 0);
//    expect_ok(xstat.inode);
//    expect(xstat.size, 99);
    
//    expect(xstat.space, 112);
//    expect_ok(xstat.location);
//   expect_ok(xstat.block);
//    expect_ok(xstat.sequence);
//    expect_ok(xstat.updates);

    error = tffs_linkstat(myname, &stat);
    expect(error, EFFS_OK);
    
    expect(error, EFFS_OK);
    expect(stat.type, OT_FILE);
    expect(stat.flags, 0);
    expect_ok(stat.inode);
    expect(stat.size, 99);
    
    error = tffs_xlstat(myname, &xstat);
    expect(error, EFFS_OK);

    expect(error, EFFS_OK);
    expect(xstat.type, OT_FILE);
    expect(xstat.flags, 0);
    expect_ok(xstat.inode);
    expect(xstat.size, 99);

    expect(xstat.space, 112);
    expect_ok(xstat.location);
    expect_ok(xstat.block);
    expect_ok(xstat.sequence);
    expect_ok(xstat.updates);

    // Symlink
    tffs_symlink(sym_name, myname);  
        
    error = tffs_xlstat(sym_name, &xstat);
    expect(error, EFFS_OK);

    expect(error, EFFS_OK);
    expect(xstat.type, OT_LINK);
    expect(xstat.flags, 0);
    expect_ok(xstat.inode);
    expect(xstat.size, sizeof(myname));

    expect(xstat.space, 16);
    expect_ok(xstat.location);
    expect_ok(xstat.block);
    expect_ok(xstat.sequence);
    expect_ok(xstat.updates);

    // Stream stat test
    // Use xstat to return the size of the file
    fdi = tffs_open(stream_name, FFS_O_WRONLY | FFS_O_CREATE | 
                    FFS_O_TRUNC  | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);

    for (i = 0; i < 20; i++) {
        size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 
                          fs.chunk_size_max / 5);

        expect(size, fs.chunk_size_max / 5);
        file_size +=size;

        error = tffs_xlstat(stream_name, &xstat);
        expect(error, EFFS_OK);
        expect_eq(xstat.size, file_size);

        error = tffs_fstat(fdi, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, file_size);
    }
            
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    // Test if the file size is right if opened in read-only
    fdi = tffs_open(stream_name, FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    
    error = tffs_xlstat(stream_name, &xstat);
    expect(error, EFFS_OK);
    expect_eq(xstat.size, file_size);
    
    // Test a lot of difference fdi
    for (i = -3; i < FFS_FD_OFFSET + fs.fd_max + 3; i++) {
        error = tffs_fstat(i, &stat);
        if (i == FFS_FD_OFFSET) { 
            expect(error, EFFS_OK);
        }
        else expect(error, EFFS_BADFD);
    }    
    error = tffs_close(fdi);
    expect(error, EFFS_OK);

    return 0;
}

int case_remove(int p0, int p1)
{
    tw(tr(TR_FUNC, TrTest, "WARNING: Test case not implemented!\n"));
    ttw(str(TTrTest, "Test case not implemented!" NL));

    return 0;
}

int case_rename(int p0, int p1)
{
    fd_t fdi;
    int size, i;
    struct dir_s dir;
    // create file A
    // rename A to B
    // check file A is gone
    // check file B is same as file A
    // do all the same for directory
    // do all the same for symlink
    // test and ensure that dir rename "foo" to "foo/bar" is impossible!
    
    // Cleanup before run
    tffs_remove("/Walt_Disney/RUP");
    tffs_remove("/Uncle_Scrooge");
    tffs_remove("/Walt_Disney/Minnie");
    tffs_remove("/Walt_Disney");
    tffs_remove("/Duck");
    
    // Init
    tffs_mkdir("/Disney");
    tffs_mkdir("/Hell");

    error = tffs_fwrite("/IMEI", TDATA(2));
    if (error < 0 && error != EFFS_ACCESS) return 1;
    error = tffs_fcontrol("/IMEI", OC_FLAGS, OF_READONLY);
    if (error < 0 && error != EFFS_ACCESS) return 1;

    tw(tr(TR_FUNC, TrTestHigh, "Rename file\n")); /* Rename file */

    error = tffs_fwrite("/Mickey", TDATA(3));
    expect(error, EFFS_OK);

    error = tffs_fwrite("/Pluto", TDATA(2));
    expect(error, EFFS_OK);

    error = tffs_rename("/Pluto", "/Dog");
    expect(error, EFFS_OK);

    error = tffs_fupdate("/Pluto", TDATA(2));
    expect(error, EFFS_NOTFOUND);

    error = test_expect_file("/Dog", TDATA(2));
    if (error) return 1;

    error = tffs_rename("/Dog", "/Hell/RIP");
    expect(error, EFFS_OK);

    error = tffs_rename("/Hell/RIP", "/RAP");
    expect(error, EFFS_OK);

    error = tffs_rename("/RAP", "/Disney/RUP");
    expect(error, EFFS_OK);

    error = test_expect_file("/Disney/RUP", TDATA(2));
    if (error) return 1;

    // Rename a file to an existing file
    error = tffs_rename("/Mickey", "/Disney/RUP");
    expect(error, EFFS_OK);

    // Have the data changed?
    error = test_expect_file("/Disney/RUP", TDATA(3));
    if (error) return 1;

    // Try rename a file to an exisitng read-only file
    error = tffs_rename("/Disney/RUP", "/IMEI");
    expect(error, EFFS_ACCESS);

    // Try rename a file to an existing dir
    error = tffs_rename("/Disney/RUP", "/Hell");
    expect(error, EFFS_NOTAFILE);

    error = tffs_rename("/Disney/RUP", "/BADNAME?");
    expect(error, EFFS_BADNAME);

    error = tffs_remove("/Hell");
    expect(error, EFFS_OK);

    tw(tr(TR_FUNC, TrTestHigh, "Rename symlink\n")); /* Rename symlink */

    tffs_symlink("/Goose", "/Disney/RUP"); // 5

    error = tffs_rename("/Goose", "/Duck");
    expect(error, EFFS_OK);

    error = tffs_fupdate("/Goose", TDATA(2));
    expect(error, EFFS_NOTFOUND);

    error = test_expect_file("/Duck", TDATA(3));
    if (error) return 1;

    error = test_expect_file("/Disney/RUP", TDATA(3));
    if (error) return 1;

    tw(tr(TR_FUNC, TrTestHigh, "Rename dir\n"));     /* Rename dir */

// * FIXME BUG * FIXME BUG * FIXME BUG * The below test to not fail instead
// the directory is removed 
// error = tffs_rename("/Disney", "/Disney/foo");
// expect(error, EFFS_OK);
    
    tffs_mkdir("/Disney/Donald_Duck");
    expect(error, EFFS_OK);
    error = ffs_rename("/Disney/Donald_Duck", "/Uncle_Scrooge");
    expect(error, EFFS_OK);
    error = tffs_opendir("/Uncle_Scrooge", &dir);
    expect(error, EFFS_OK);
    error = tffs_opendir("/Disney/Donald_Duck", &dir);
    expect(error, EFFS_NOTFOUND);

    error = tffs_rename("/Disney", "/Walt_Disney");
    expect(error, EFFS_OK);

    tffs_mkdir("/Disney");  // Create 'Disney' dir again
    expect(error, EFFS_OK);

    // Try rename to existing dir
    error = tffs_rename("/Disney", "/Walt_Disney");
    expect(error, EFFS_EXISTS);

    // Try rename to existing file
    error = tffs_rename("/Disney", "/Walt_Disney/RUP"); 
    expect(error, EFFS_EXISTS);

    tw(tr(TR_FUNC, TrTestHigh, "Rename seghead\n")); /* Rename seghead */
    
    fdi = tffs_open("/Walt_Disney/Mickey", FFS_O_WRONLY | FFS_O_CREATE);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], fs.chunk_size_max + 1);
    expect(size, fs.chunk_size_max + 1);  
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + size, fs.chunk_size_max);
    expect(size, fs.chunk_size_max);  
    
    error = tffs_rename("/Walt_Disney/Mickey", "/Walt_Disney/Minnie");
    expect(error, EFFS_LOCKED);
    tffs_close(fdi);
    
    error = tffs_rename("/Walt_Disney/Mickey", "/Walt_Disney/Minnie");
    expect(error, EFFS_OK);

    fdi = tffs_open("/Walt_Disney/Minnie", FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    i = 0;
    do {
        size = tffs_read(fdi, bigbuf, bigbuf_size);
        error = test_expect_data((char*)tdata[TDATA_HUGE] + i, bigbuf, size);
        if (error) return 1;
        i += size;
    } while (size);
    error = tffs_close(fdi);
    expect(error, 0);

    error = test_expect_file("/Walt_Disney/Minnie", (char *)tdata[TDATA_HUGE], 
                             2 * fs.chunk_size_max + 1);
    if (error) return 1;

    // Rename a big file to an existing big file
    error = tffs_fwrite("/Mickey", (char *)tdata[TDATA_HUGE] + 5, 
                        2.5 * fs.chunk_size_max);
    expect(error, EFFS_OK);

    error = tffs_rename("/Mickey", "/Walt_Disney/Minnie");
    expect(error, EFFS_OK);

    error = test_expect_file("/Walt_Disney/Minnie", (char *)tdata[TDATA_HUGE] + 5, 
                             2.5 * fs.chunk_size_max);
    if (error) return 1;

    error = tffs_fread("/Mickey", 0 , 0);
    expect(error, EFFS_NOTFOUND);

    return 0;
}

// One of the problems with the rename function is that we need to copy the
// data part from the old obj to the new obj. When we allocate data for the
// new obj we risk that the data alloc caused a data reclaim which relocated
// the old obj thus the source have been moved! 
int case_rename_extended(int p0, int p1)
{       
    int i, old_drec_most_lost, rename_file_relocated = 0;
    int fsize, offset;  
    char myname1[] = "/rename1/rename1/foo";
    char myname2[] = "/rename2/rename2/bar";

    if (p0 == 0) p0 = 100;
    if (p1 == 0) p1 = 5;

    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK); 
    error = tffs_format("/ffs/b3r24", 0x2BAD); 
    expect(error, EFFS_OK);

    fsize = 2.5 * fs.chunk_size_max; // Test with more than one chunk

    tffs_mkdir("/rename1");
    tffs_mkdir("/rename1/rename1");
    tffs_mkdir("/rename2");
    tffs_mkdir("/rename2/rename2");

    error = tffs_file_write(myname1, (char*)tdata[TDATA_HUGE], fsize, 
                            FFS_O_CREATE | FFS_O_TRUNC);

    // Test if rename can handle when the objects are relocaded while the
    // rename is in progress
    for (i = 0; i < p0; i++) {
        tw(tr(TR_FUNC, TrTest, "Rename number %d\n", i));

        offset = i % 2 ? 3 : 9; 
        error = tffs_file_write(myname2, (char*)tdata[TDATA_HUGE] + offset, fsize, 
                            FFS_O_CREATE | FFS_O_TRUNC);

        // Get data reclaim candidate most_lost
        old_drec_most_lost = stats.drec.most_lost;
        error = tffs_rename(myname2, myname1);
        expect(error, EFFS_OK);

        error = test_expect_file(myname1, tdata[TDATA_HUGE] + offset, fsize); 
        expect(error, EFFS_OK);

        error = tffs_fread(myname2, 0, 0);
        expect(error, EFFS_NOTFOUND);

        // Has the rename triggered a data_reclaim()?
        if (old_drec_most_lost != stats.drec.most_lost) {
            rename_file_relocated++;
            tw(tr(TR_FUNC, TrTest, "Rename objects relocated %d\n", 
                  rename_file_relocated));
            if (rename_file_relocated >= p1)
                return EFFS_OK;
        }
    }
    
    return 1;
}


// Test Big files
int case_bigfile(int p0, int p1)
{
    struct ffs_state_s old, new;
    const char bigfile[] = "/antarctica/iceberg";
    int bytes_max, file_size;

#if 1
    ttw(ttr(TTrTest, "WARNING: We need to re-implement this test. Skip test" NL));
    tw(tr(TR_FUNC, TrTest, "WARNING: We need to re-implement this test. Skip test\n"));
    return 0;
#endif

    if (param.data_blocks <= 1) {
        ttw(ttr(TTrTest, "WARNING: Too few blocks to run. Skip test" NL));
        tw(tr(TR_FUNC, TrTest, "WARNING: Too few blocks to run. Skip test\n"));
        return 0;
    }

    error = case_cleanup(fs.filesize_max);
    expect(error, EFFS_NOSPACE); // We can not cleanup this amount.

    ffs_query(Q_BYTES_FREE, (uint32 *) &bytes_max);
    test_ffs_state_get(&old);

// We don't have enough space for this amount because the name dos also
// use some space
    file_size = bytes_max + 1;

// Try to make a file of a size that is bigger than the total free space
    // in the file system.
    ttw(ttr(TTrTest, "Bigfile of size %d" NL, file_size));
    tw(tr(TR_FUNC, TrTestHigh, "Bigfile of size %d\n", file_size));
    error = tffs_fcreate(bigfile,
                         (char *) tdata[TDATA_HUGE], file_size);
    expect(error, EFFS_NOSPACE);

    test_ffs_state_get(&new);
    expect_eq(old.bytes_free, new.bytes_free);

// File the system with this huge file
    file_size = bytes_max - FFS_FILENAME_MAX - dev.atomsize;
    ttw(ttr(TTrTest, "Bigfile of size %d" NL, file_size));
    tw(tr(TR_FUNC, TrTestHigh, "Bigfile of size %d\n", file_size));
    error = tffs_fcreate(bigfile,
                         (char *) tdata[TDATA_HUGE], file_size);
    expect(error, EFFS_OK);
    
    error = test_expect_file((char *) bigfile,
                             (char *) tdata[TDATA_HUGE], file_size);
    if (error) return 1;

    test_ffs_state_get(&new);
    expect_gt(old.bytes_free, new.bytes_free - file_size);
    
    tffs_remove(bigfile);

    return 0;
}


/******************************************************************************
 * Reclaim Test Cases
 ******************************************************************************/

// Test that ffs internal state is same after a re-ffs_init()
int case_reinit(int p0, int p1)
{
    struct ffs_state_s old, new;

    test_ffs_state_get(&old);
    error = tffs_initialize();
    expect(error, EFFS_OK);
    test_ffs_state_get(&new);
    error = test_expect_state(&old, &new);
    if (error) return 1;

    return 0;
}
    
// Test inodes reclaim
int case_irec(int p0, int p1)
{
    // African states: kenya, egypt, marocco, namibia, nigeria, mozambique,
    // tanzania, ghana, togo, liberia, mali, congo
    struct ffs_state_s old, new;
    int i, j, updates;

    if (p0 == 0)
        p0 = 7;

    tffs_mkdir("/africa");

    // Initialize
    tffs_fwrite("/africa/kenya", TDATA(0)); // 1
    tffs_mkdir("/africa/east"); // 2
    tffs_fwrite("/africa/tanzania", TDATA(1)); // 3
    tffs_fwrite("/africa/east/egypt", TDATA(2)); // 4
    tffs_symlink("/africa/et", "africa/east/egypt"); // 5
    tffs_mkdir("/africa/west"); // 7
    tffs_fupdate("/africa/et", TDATA(3)); // 6
    tffs_fwrite("/africa/west/liberia", TDATA(0)); // 8

    for (j = 0; j < p0; j++) {
        test_ffs_state_get(&old);
        updates = param.inodes_max - old.inodes_used;
        ttw(ttr(TTrTest, "loop %d: updates = %d" NL, j, updates));
        tw(tr(TR_FUNC, TrTestHigh, "loop %d: updates = %d\n", j, updates));
        for (i = 0; i < (updates / 10) + 10; i++) {
            // cleanup...
            error = tffs_remove("/africa/east/egypt");
            expect(error, EFFS_OK);
            error = tffs_remove("/africa/east");
            expect(error, EFFS_OK);
            error = tffs_remove("/africa/et");
            expect(error, EFFS_OK);
            error = tffs_remove("/africa/west/liberia");
            expect(error, EFFS_OK);
            error = tffs_remove("/africa/west");
            expect(error, EFFS_OK);
            // use 10 inodes...
            error = tffs_fwrite("/africa/kenya", TDATA(0)); // 1
            expect(error, EFFS_OK);
            error = tffs_mkdir("/africa/east"); // 2
            expect(error, EFFS_OK);
            error = tffs_fwrite("/africa/tanzania", (char *) tdata[0], 0); // 3
            expect(error, EFFS_OK);
            error = tffs_fcreate("/africa/east/egypt", TDATA(2)); // 4
            expect(error, EFFS_OK);
            error = tffs_symlink("/africa/et", "/africa/east/egypt"); // 5
            expect(error, EFFS_OK);
            error = tffs_mkdir("/africa/west"); // 7
            expect(error, EFFS_OK);
            error = tffs_fupdate("/africa/et", TDATA(3)); // 6
            expect(error, EFFS_OK);
            error = tffs_fcreate("/africa/west/liberia", TDATA(0)); // 8
            expect(error, EFFS_OK);
            error = tffs_fupdate("/africa/west/liberia", TDATA(1)); // 9
            expect(error, EFFS_OK);
            error = tffs_fupdate("/africa/tanzania", TDATA(2)); // 10
            expect(error, EFFS_OK);
        }
        test_ffs_state_get(&new);
        expect_eq(old.objects_total, new.objects_total);
        error = test_expect_file("/africa/tanzania", TDATA(2));
        if (error) return 1;
        error = test_expect_file("/africa/west/liberia", TDATA(1));
        if (error) return 1;
        error = test_expect_file("/africa/east/egypt", TDATA(3));
        if (error) return 1;
        error = test_expect_file("/africa/kenya", TDATA(0));
        if (error) return 1;
    }

    return 0;
}

#if 1
// FIXME: Is this test case still valid after the new ffs_file_write() which
// makes several chunks instead of on big?

// Test data reclaim. Use up to maximum half the total space available.
// drec params: percentage of avail to use, numupdates. We must not have too
// big files due to fragmentation problems
#define DREC_DIR "/north-america/usa"
#define DREC_CHUNKS 6
int case_drec(int p0, int p1)
{
    static struct test_file_s files[] =
        {
            { DREC_DIR "/alaska", 0, 0 },
            { DREC_DIR "/arkansas", 0, 0 },
            { DREC_DIR "/california", 0, 0 },
            { DREC_DIR "/colorado", 0, 0 },
            { DREC_DIR "/dakota", 0, 0 }, // north, south?
            { DREC_DIR "/DistrictOfColumbia", 0, 0 }, // state?
            { DREC_DIR "/florida", 0, 0 },
            { DREC_DIR "/Georgia", 0, 0 },
            { DREC_DIR "/hawaii", 0, 0 },
            { DREC_DIR "/Idaho", 0, 0 },
            { DREC_DIR "/Illinois", 0, 0 },
            { DREC_DIR "/Iowa", 0, 0 },
            { DREC_DIR "/kentucky", 0, 0 },
            { DREC_DIR "/maine", 0, 0 },
            { DREC_DIR "/Massachusettes", 0, 0 }, // spelling?
            { DREC_DIR "/michigan", 0, 0 },
            { DREC_DIR "/minnesota", 0, 0 },
            { DREC_DIR "/mississippi", 0, 0 },
            { DREC_DIR "/missouri", 0, 0 },
            { DREC_DIR "/Montana", 0, 0 },
            { DREC_DIR "/Nevada", 0, 0 },
            { DREC_DIR "/NewHampshire", 0, 0 },
            { DREC_DIR "/NewJersey", 0, 0 },
            { DREC_DIR "/NewMexico", 0, 0 },
            { DREC_DIR "/NewYork", 0, 0 }, // state?
            { DREC_DIR "/north-carolina", 0, 0 },
            { DREC_DIR "/ohio", 0, 0 },
            { DREC_DIR "/oklahoma", 0, 0 },
            { DREC_DIR "/Oregon", 0, 0 },
            { DREC_DIR "/Pensylvania", 0, 0 },
            { DREC_DIR "/RhodeIsland", 0, 0 }, // state?
            { DREC_DIR "/south-carolina", 0, 0 },
            { DREC_DIR "/Tennesee", 0, 0 },
            { DREC_DIR "/texas", 0, 0 },
            { DREC_DIR "/utah", 0, 0 },
            { DREC_DIR "/Vermont", 0, 0 },
            { DREC_DIR "/virginia", 0, 0 },
            { DREC_DIR "/washington", 0, 0 },
            { DREC_DIR "/Wyoming", 0, 0 },

            { DREC_DIR "/40", 0, 0 },
            { DREC_DIR "/41", 0, 0 },
            { DREC_DIR "/42", 0, 0 },
            { DREC_DIR "/43", 0, 0 },
            { DREC_DIR "/44", 0, 0 },
            { DREC_DIR "/45", 0, 0 },
            { DREC_DIR "/46", 0, 0 },
            { DREC_DIR "/47", 0, 0 },
            { DREC_DIR "/48", 0, 0 },
            { DREC_DIR "/49", 0, 0 },
            { DREC_DIR "/50", 0, 0 },
            { DREC_DIR "/51", 0, 0 },
            { DREC_DIR "/52", 0, 0 },
        };
    struct chunk_s {
        int ratio;
        int high;
        int low;
    };
    const struct chunk_s chunk[DREC_CHUNKS] = {
        { 15, 65536, 32768 },
        { 15, 32768, 16384 },
        { 25, 16384,  8192 },
        { 20,  8192,  4096 },
        { 15,  4096,  2048 },
        { 10,  2048,     0 }
    };

    int i, j, n, num_files;
    int num, pct;
    int size_used, size_max, size_chunk;
    int size_chunk_used, size_chunk_low, size_chunk_high;

    num = (p0 == 0 ? 11 : p0);
    pct = (p1 == 0 ? 50 : p1);

    tffs_mkdir("/north-america");
    tffs_mkdir("/north-america/usa");

    size_max   = pct * param.bytes_avail / 100;
    size_used  = 0;
    num_files  = sizeof(files) / sizeof(struct test_file_s);
    n = 0;

    tw(tr(TR_FUNC, TrTestHigh, "pct = %d%% = %dk\n",  pct, size_max/1024));

    for (i = 0; i < DREC_CHUNKS; i++) {
        size_chunk = chunk[i].ratio * size_max / 100;
        size_chunk_low  = chunk[i].low  / 256 * fs.filesize_max / 256;
        size_chunk_high = chunk[i].high / 256 * fs.filesize_max / 256;
        tw(tr(TR_FUNC, TrTestHigh, "%4dk of [%d..%d]: ",
              size_chunk/1024, size_chunk_high, size_chunk_low));
        ttw(ttr(TTrTest, "%4dk of [%d..%d]: ",
                size_chunk/1024, size_chunk_high, size_chunk_low));
        size_chunk_used = 0;
        // If the total chunk size can guaranteeable be contained...
        if (size_chunk >= chunk[i].high) {
            for (j = 0; j < size_chunk / size_chunk_high; j++) {
                if (n >= num_files) {
                    tw(tr(TR_FUNC, TrTestHigh, "j too big\n"));
                    ttw(str(TTrTest, "j too big" NL));
                    break;
                }
                files[n].size = size_chunk_low +
                    rand() % (size_chunk_high - size_chunk_low);
#ifdef WIN32
                /* due to limitation in MS Visual C */
                if( files[n].size  > 65535 )
                  files[n].size = 65535;
#endif //WIN32
                files[n].data = (char *) tdata[TDATA_HUGE] + n;
                tw(tr(TR_NULL, TrTestHigh, "%d:%s ",
                      files[n].size, &files[n].name[strlen(DREC_DIR)+1]));
                ttw(ttr(TTrTest, "%d:%s " NL,
                        files[n].size, &files[n].name[strlen(DREC_DIR)+1]));
                size_chunk_used += files[n].size;
                n++;
                // FIXME: We should let all the unused bytes from this
                // chunk spill over into the next chunk so we use the
                // full percentage specified by p1.
            }
            size_used += size_chunk_used;
        }
        tw(tr(TR_NULL, TrTestHigh, "(%dk)\n", size_chunk_used/1024));
        ttw(ttr(TTrTest, "(%dk)" NL, size_chunk_used/1024));
    }
    tw(tr(TR_FUNC, TrTestHigh, "pct = %d%% = %dk\n",
          100 * size_used/param.bytes_avail, size_used/1024));
    ttw(ttr(TTrTest, "pct = %d%% = %dk" NL,
            100 * size_used/param.bytes_avail, size_used/1024));
    
    for (j = 0; j < num; j++) {
        ttw(ttr(TTrTest, "drec: %d. write" NL, j+1));
        tw(tr(TR_FUNC, TrTestHigh, "drec: %d. write\n", j+1));
        for (i = 0; i < n; i++) {
            error = tffs_fwrite(files[i].name, files[i].data, files[i].size);
            expect(error, EFFS_OK);
        }
    }
    
    for (i = 0; i < n; i++) {
        error = test_expect_file(files[i].name, files[i].data, files[i].size);
        if (error) 
            return 1;
        error = tffs_remove(files[i].name);
        expect(error, EFFS_OK);
    }
    tffs_remove("/north-america/usa");
    tffs_remove("/north-america");

    return 0;
}

#else

// Test data reclaim. Use up to maximum half the total space available.
// drec params: percentage of avail to use, numupdates. We must not have too
// big files due to fragmentation problems
int case_drec(int p0, int p1)
{
    struct blabla_s {
        int ratio;
        int high;
        int low;
    };

    struct blabla_s sizes[4] =
    {
        {  10, 65536, 32768 },
        {  40, 21800, 16384 },
        {  45, 16384,  2048 },
        {   5,  2048,     0 }
    };

    static struct test_file_s files[] =
    {
        { DRECDIR "/alaska", 0, 0 },
        { DRECDIR "/arkansas", 0, 0 },
        { DRECDIR "/california", 0, 0 },
        { DRECDIR "/colorado", 0, 0 },
        { DRECDIR "/dakota", 0, 0 }, // north, south?
        { DRECDIR "/DistrictOfColumbia", 0, 0 }, // state?
        { DRECDIR "/florida", 0, 0 },
        { DRECDIR "/Georgia", 0, 0 },
        { DRECDIR "/hawaii", 0, 0 },
        { DRECDIR "/Idaho", 0, 0 },
        { DRECDIR "/Illinois", 0, 0 },
        { DRECDIR "/Iowa", 0, 0 },
        { DRECDIR "/kentucky", 0, 0 },
        { DRECDIR "/maine", 0, 0 },
        { DRECDIR "/Massachusettes", 0, 0 }, // spelling?
        { DRECDIR "/michigan", 0, 0 },
        { DRECDIR "/minnesota", 0, 0 },
        { DRECDIR "/mississippi", 0, 0 },
        { DRECDIR "/missouri", 0, 0 },
        { DRECDIR "/Montana", 0, 0 },
        { DRECDIR "/Nevada", 0, 0 },
        { DRECDIR "/NewHampshire", 0, 0 },
        { DRECDIR "/NewJersey", 0, 0 },
        { DRECDIR "/NewMexico", 0, 0 },
        { DRECDIR "/NewYork", 0, 0 }, // state?
        { DRECDIR "/north-carolina", 0, 0 },
        { DRECDIR "/ohio", 0, 0 },
        { DRECDIR "/oklahoma", 0, 0 },
        { DRECDIR "/Oregon", 0, 0 },
        { DRECDIR "/Pensylvania", 0, 0 },
        { DRECDIR "/RhodeIsland", 0, 0 }, // state?
        { DRECDIR "/south-carolina", 0, 0 },
        { DRECDIR "/Tennesee", 0, 0 },
        { DRECDIR "/texas", 0, 0 },
        { DRECDIR "/utah", 0, 0 },
        { DRECDIR "/Vermont", 0, 0 },
        { DRECDIR "/virginia", 0, 0 },
        { DRECDIR "/washington", 0, 0 },
        { DRECDIR "/Wyoming", 0, 0 },

        { DRECDIR "/40", 0, 0 },
        { DRECDIR "/41", 0, 0 },
        { DRECDIR "/42", 0, 0 },
        { DRECDIR "/43", 0, 0 },
        { DRECDIR "/44", 0, 0 },
        { DRECDIR "/45", 0, 0 },
        { DRECDIR "/46", 0, 0 },
        { DRECDIR "/47", 0, 0 },
        { DRECDIR "/48", 0, 0 },
        { DRECDIR "/49", 0, 0 },
        { DRECDIR "/50", 0, 0 },
        { DRECDIR "/51", 0, 0 },
        { DRECDIR "/52", 0, 0 },
    };

    int i, j, n, size, size_target;
    int num, percentage;

    error = tffs_mkdir("/north-america");

    percentage = (p1 == 0 ? 50 : p1);
    n = sizeof(files)/sizeof(struct test_file_s);
    size_target = percentage * param.bytes_avail / 100;
    size = 0;
    for (i = 0; i < 4; i++) {
        sizes[i].ratio = sizes[i].ratio * size_target / 100;
        sizes[i].high  = sizes[i].high / 256 * fs.filesize_max / 256;
        sizes[i].low   = sizes[i].low  / 256 * fs.filesize_max / 256;
        size += sizes[i].ratio;
    }
    ttw(ttr(TTrTest, "%4dk in total" NL, size / 1024));
    tw(tr(TR_FUNC, TrTestHigh, "%4dk in total\n", size / 1024));

    j = 0;
    size = size_target = 0;
    for (i = 0; i < 4; i++) {
        ttw(ttr(TTrTest, "%4dk: %d..%d = ",
              sizes[i].ratio / 1024, sizes[i].high, sizes[i].low));
        tw(tr(TR_FUNC, TrTestHigh, "%4dk: %d..%d = ",
              sizes[i].ratio / 1024, sizes[i].high, sizes[i].low));
        size_target += sizes[i].ratio;
        while (size < size_target) {
            files[j].data = (char *) tdata[TDATA_HUGE] + j;
            files[j].size = rand() % (sizes[i].high - sizes[i].low)
                + sizes[i].low;
            size += files[j].size;
            ttw(ttr(TTrTest, "%d:%s, ",
                    files[j].size, &files[j].name[strlen(DRECDIR)+1]));
            tw(tr(TR_NULL, TrTestHigh, "%d:%s, ",
                  files[j].size, &files[j].name[strlen(DRECDIR)+1]));
            j++;
            if (j >= n) {
                // TODO: better error handling?
                ttw(str(TTrTest, "j too big" NL));
                tw(tr(TR_FUNC, TrTestHigh, "j too big\n"));
                return -1;
            }
        }
        ttw(str(TTrTest, "" NL));
        tw(tr(TR_NULL, TrTestHigh, "\n"));
    }
    n = j;

    error = tffs_mkdir(DRECDIR);

    num = (p0 == 0 ? 5 : p0);
    for (j = 0; j < num; j++) {
        ttw(ttr(TTrTest, "drec: %d. write" NL, j+1));
        tw(tr(TR_FUNC, TrTestHigh, "drec: %d. write\n", j+1));
        for (i = 0; i < n; i++) {
            error = tffs_fwrite(files[i].name, files[i].data, files[i].size);
            expect(error, EFFS_OK);
        }
        // error = test_run("ri");
        // if (error) return 1;
    }
    
    for (i = 0; i < n; i++) {
        error = test_expect_file(files[i].name, files[i].data, files[i].size);
        if (error) return 1;
        error = tffs_remove(files[i].name);
        expect(error, EFFS_OK);
    }

    return 0;
}

#endif

void save_block_ages(int *age)
{
    int i;
    struct block_header_s *bhp;

    for (i = 0; i < dev.numblocks; i++)
    {
        bhp = (struct block_header_s *) offset2addr(dev.binfo[i].offset);
        age[i] = bhp->age;
        tw(tr(TR_FUNC, TrTestHigh, "b,age(%d,%d)\n", i, age[i]));
    }
} 

// NOTE: This chech includes the inode block thus the inode block also needs
// to be reclaimed before this functions returns ok
int has_all_block_ages_changed(int *age)
{
    int i;
    struct block_header_s *bhp;

    for (i = 0; i < dev.numblocks; i++)
    {
        bhp = (struct block_header_s *) offset2addr(dev.binfo[i].offset);
        if (age[i] == bhp->age) 
            return 0; // Age have not changed
    }
  
    return 1;
}

#define chunkalign(size) (((size) + fs.chunk_size_max-1) & ~(fs.chunk_size_max-1))

// Fill a data blocks with the maximum possible numbers of objects
int fill_block_with_max_objects(void)
{
    static int n = 1000; // Avoid white spaces in the filename
    int fsize, i, b, nobjects, bfree_space;
    char myname[] = "Fb_max-xxxx";
    char *mydata = (char *) tdata[TDATA_HUGE];

    // Find an approximate objects size 
    fsize = dev.blocksize / fs.block_files_max - sizeof(myname) - 1; 

    // Fill approximate 75% of a data block
    for (i = 0; i < fs.block_files_max / 4 * 3; i++, n++) {
        sprintf(myname, "/Fb_max-%4d", n);
        error = tffs_file_write(myname,(char*) mydata, fsize, FFS_O_CREATE);
        expect_ok(error);
    }

    // Find the block we currently are filling with objects.
    error = tffs_xlstat(myname, &xstat);
    expect_ok(error);
    
    b = xstat.block;
    bfree_space = dev.blocksize - bstat[b].used;
    tw(tr(TR_FUNC, TrTestHigh, "Block: %d, Free space:%dkB\n", b ,bfree_space>>10));

    // Calculate the exact file size to fill a block with max number of files  
    nobjects = fs.block_files_max - bstat[b].objects - fs.block_files_reserved;

    fsize = bfree_space / nobjects - sizeof(myname);
    fsize &= ~dev.atomnotmask;     // Avoid padding
    tw(tr(TR_FUNC, TrTestHigh, "Exact file size (%d)\n", fsize));
    
    // Fill the rest of the block
    for (i = 0; i < nobjects; i++, n++) {
        sprintf(myname, "/Fb_max-%4d", n);
        error = tffs_file_write(myname, (char*) mydata, fsize, FFS_O_CREATE);
        expect_ok(error);
    }

    tw(tr(TR_FUNC, TrTest, "Filled block %d done\n", b));

    return EFFS_OK;
}

// Stress test data reclaim and test wearleveling.
// NOTE: This test can not run in less than 7 blocks 
int case_adrec(int nwrites, int nage_change)
{
    int i, j, n, inodes_reserved, nobjects, bytes_max;
    int blocks, fsize;
    int dynamic_fsize = 1000;
    char static_name[] = "/Static-xxxx";
    char dynamic_name[] = "/Dynamic";
    int *age = (int*) smallbuf;

/* Flow:
   Format ffs.
   Fill N number of blocks with the maximum possible numbers of objects.
   Fill the rest of FFS except from a few kbytes. Use almost all the inodes.
   Trigger data reclaim by continue to update a file.
   Test if all flash blocks are used for wearleveling. 
*/
    if (nwrites == 0)  // Number of updates of the dynamic file (timeout)
        nwrites = 1000000;
    if (nage_change == 0) // 
        nage_change = 2;

    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs", 0x2BAD);
    expect(error, EFFS_OK);

    // Major hack, disable journal and reserved space or else there will be
    // created new journals inside the blocks where we wish to have only
    // static objects
    fs.ijournal = 0;
    fs.reserved_space = 0;

    // Minimum amount of inodes to fill FFS:
    ffs_query(Q_BYTES_FREE, &bytes_max);
    inodes_reserved = bytes_max / fs.chunk_size_max;

    // We need to calculate the amount of inodes we can use and still
    // garanti that we have enough inodes to fill all data blocks with data.
    nobjects  = fs.objects_max - inodes_reserved;

    // How many blocks can we fill?    
    blocks = nobjects / fs.block_files_max;

    // Fill N number of data blocks with the maximum possible numbers of objects
    tw(tr(TR_FUNC, TrTest, "Fill %d blocks with max obj \n", blocks));
    n = 1000; // Avoid white spaces in the filename
    for (j = 0; j < blocks; j++) {  
        error = fill_block_with_max_objects();
        expect_ok(error);
    }
    
    // Restore journal and reserved space
    error = tffs_initialize();
    expect_ok(error);

    // Fill the rest of FFS except from 3 times the dynamic size, futhermore
    // keep 4 objects 'free'
    ffs_query(Q_OBJECTS_FREE, &nobjects);
    tw(tr(TR_FUNC, TrTest, "Fill almost the rest of FFS\n"));
    n = 1000;        // Avoid white spaces in the filename
    while (nobjects - 4 > 0) {    
        ffs_query(Q_BYTES_FREE, &bytes_max);
        fsize = (bytes_max - dynamic_fsize * 3) / (nobjects - 4);

        tw(tr(TR_FUNC, TrTestHigh, "space,obj,fsize (%dk,%d,%d)\n", 
              bytes_max/1024, nobjects, fsize));

        if (fsize > fs.chunk_size_max) {
            // Roundup to the closet complete chunk or else we use to many inodes
            fsize = chunkalign(fsize);
            tw(tr(TR_FUNC, TrTestHigh, "Chunkalign %d\n", fsize));
        }
        sprintf(static_name, "/Static-%4d", n++);
        error = tffs_file_write(static_name, (char*)tdata[TDATA_HUGE], fsize, 
                                FFS_O_CREATE);
        if (error == EFFS_NOSPACE) {
            tw(tr(TR_FUNC, TrTest, "WARNING: Fill failed with out of data space\n"));
            break;
        }
        else
            expect_ok(error);

        ffs_query(Q_OBJECTS_FREE, &nobjects);
    }
    tw(tr(TR_FUNC, TrTest, "Fill done, all static objects are written\n"));

    case_debug_help(2,0);  // bstat trace

    // Save the age of all blocks
    save_block_ages(age);

    tw(tr(TR_FUNC, TrTest, "Update the dynamic file max %d times\n", nwrites));

    for (i = 0, j = 0; i < nwrites; i++) {
        error = tffs_file_write(dynamic_name, (char*)tdata[TDATA_HUGE], 
                                dynamic_fsize, 
                                FFS_O_CREATE | FFS_O_TRUNC);
        expect_ok(error);
        
        if (i%1000 == 0)
            tw(tr(TR_FUNC, TrTest, "Write number %d\n", i));

         if (has_all_block_ages_changed(age) == 1) {
            tw(tr(TR_FUNC, TrTest,"All block ages have changed %d\n", j));
            j++;
            save_block_ages(age);
        }

        if (j >= nage_change) 
            break;
    }
    
    test_statistics_print();

    case_debug_help(2,0);  // bstat trace

    if (j >= nage_change)
        return 0;
    
    return 1;  //Error
}

/******************************************************************************
 * Journalling and Recover Test Cases
 ******************************************************************************/

// Test journalling. Both for normal objects but also for journal file
// itself! The following operations are tested for proper recovery: fcreate,
// fupdate, relocate, delete, clean, fcontrol
int case_journal(int p0, int p1)
{
    struct test_file_s tf;
    struct ffs_state_s old, new;
    struct xstat_s stat_old, stat_new;
    int i, myspace = 0, mytmpspace, offset;
    char mytmpname[] = "/test-journal-tmp";
    int testflags[] = {
        JOURNAL_TEST_EMPTY,      // object not committed
        JOURNAL_TEST_WRITING,    // object not committed
        JOURNAL_TEST_READY,      // object ok
        JOURNAL_TEST_COMMITTING, // object ok
        JOURNAL_TEST_COMMITTED,  // object ok
        JOURNAL_TEST_DONE,       // object ok
    };

    tf.name = "/test-journal";
    tf.data = (char *) tdata[2];
    tf.size = sdata[2];

#if 1
// NOTEME: Workaround, the below use of state_get can not handle if we
// create a new journal while we run the test. Instead we create a
// new journal before the test if we are close to full.
    if (fs.journal_pos >= fs.journal_size - (FFS_JOURNAL_MARGIN + 12) * 
        sizeof(struct journal_s)) {
        tw(tr(TR_FUNC, TrTest, "Journal file (near) full!\n"));
        journal_create(fs.ijournal);
    }

// NOTEME: Workaround, this test is unabel to handle a
// block_alloc(). The 'extra' amount of lost space caused by a
// block_alloc() and its block header dos cheat the test to fail.
    offset = data_prealloc(1024);
    expect_gt(offset, 0);
#endif

    // init
#if 1
    error = tffs_fwrite(mytmpname, tf.data, tf.size);
    expect(error, EFFS_OK);
    error = tffs_xlstat(mytmpname, &stat_old);
    expect(error, EFFS_OK);
    mytmpspace = stat_old.space;
    ttw(ttr(TTrTest, "file '%s' uses %d bytes" NL, mytmpname, mytmpspace));
    tw(tr(TR_FUNC, TrTestHigh, "file '%s' uses %d bytes\n", mytmpname, mytmpspace));
#endif
    error = tffs_fcreate(tf.name, tf.data, tf.size);
    expect(error, EFFS_OK);
    error = tffs_xlstat(tf.name, &stat_old);
    expect(error, EFFS_OK);
    myspace = stat_old.space;
    error = tffs_fcreate("/dummy", 0, 0);
    expect(error, EFFS_OK);
    ttw(ttr(TTrTest, "file '%s' uses %d bytes" NL, tf.name, myspace));
    tw(tr(TR_FUNC, TrTestHigh, "file '%s' uses %d bytes\n", tf.name, myspace));

    ttw(str(TTrTest, "fcreate:" NL));
    tw(tr(TR_BEGIN, TrTestHigh, "fcreate:\n"));
    stat_old.inode = 0;
    for (i = 0; i < sizeof(testflags)/sizeof(int); i++)
    {
        // clean up from operation being tested
        tffs_remove(tf.name);

        ttw(ttr(TTrTest, "fs.testflags = 0x%x" NL, testflags[i]));
        tw(tr(TR_FUNC, TrTestHigh, "fs.testflags = 0x%x\n", testflags[i]));
        test_ffs_state_get(&old);

        error = tffs_fcontrol("/dummy", OC_FS_TESTFLAGS, testflags[i]);
        expect(error, EFFS_OK);

        error = tffs_fcreate(tf.name, tf.data, tf.size);
        expect(error, EFFS_OK);
        
        error = tffs_initialize();
        expect(error, EFFS_OK);

        test_ffs_state_get(&new);

        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_used new = %7d, old = %7d, diff = %d\n",
              new.bytes_used, old.bytes_used, new.bytes_used - old.bytes_used));
        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_lost new = %7d, old = %7d, diff = %d\n",
              new.bytes_lost, old.bytes_lost,
              new.bytes_lost - old.bytes_lost));

        expect_eq(new.bytes_used, old.bytes_used + myspace);

        error = tffs_xlstat(tf.name, &stat_new);
        if (i < 2) {
            // object was not committed, so object must not exist
            expect(error, EFFS_NOTFOUND);
            expect_eq(new.objects_total, old.objects_total);
            expect_eq(new.bytes_lost,    old.bytes_lost + myspace);
        }
        else {
            // object was committed, so object must exist
            expect(error, EFFS_OK);
            expect_ne(stat_old.inode, stat_new.inode);
            expect_eq(new.objects_total, old.objects_total + 1);
            expect_eq(new.bytes_lost,    old.bytes_lost);
        }
    }
    ttw(str(TTrTest, "" NL));
    tw(tr(TR_END, TrTestHigh, ""));

    ttw(str(TTrTest, "fupdate:" NL));
    tw(tr(TR_BEGIN, TrTestHigh, "fupdate:\n"));
    error = tffs_xlstat(tf.name, &stat_old);
    expect(error, EFFS_OK);
    for (i = 0; i < sizeof(testflags)/sizeof(int); i++)
    {
        ttw(ttr(TTrTest, "fs.testflags = 0x%x" NL, testflags[i]));
        tw(tr(TR_FUNC, TrTestHigh, "fs.testflags = 0x%x\n", testflags[i]));
        test_ffs_state_get(&old);

        error = tffs_fcontrol("/dummy", OC_FS_TESTFLAGS, testflags[i]);
        expect(error, EFFS_OK);

        error = tffs_fupdate(tf.name, tf.data, tf.size);
 
        error = tffs_initialize();
        expect(error, EFFS_OK);

        error = tffs_xlstat(tf.name, &stat_new);
        expect(error, EFFS_OK);

        if (i < 2) {
            // object was not committed, so stats must be equal
            error = test_expect_data(&stat_new, &stat_old, sizeof(stat));
            if (error) return 1;
        }
        else {
            // object was committed, so inodes must not be equal
            expect_ne(stat_old.inode, stat_new.inode);
        }

        test_ffs_state_get(&new);

        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_used new = %7d, old = %7d, diff = %d\n",
              new.bytes_used, old.bytes_used, new.bytes_used - old.bytes_used));
        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_lost new = %7d, old = %7d, diff = %d\n",
              new.bytes_lost, old.bytes_lost,
              new.bytes_lost - old.bytes_lost));

        expect_eq(new.objects_total, old.objects_total);
        expect_eq(new.bytes_used,    old.bytes_used + myspace);
        expect_eq(new.bytes_lost,    old.bytes_lost + myspace);
    }
    ttw(str(TTrTest, "" NL));
    tw(tr(TR_END, TrTestHigh, ""));

    ttw(str(TTrTest, "rename:" NL));
    tw(tr(TR_BEGIN, TrTestHigh, "rename:\n"));
    error = tffs_fwrite(tf.name, tf.data, tf.size);
    expect(error, EFFS_OK);
    error = tffs_xlstat(tf.name, &stat_old);
    expect(error, EFFS_OK);

    for (i = 0; i < sizeof(testflags)/sizeof(int); i++)
    {
        ttw(ttr(TTrTest, "fs.testflags = 0x%x" NL, testflags[i]));
        tw(tr(TR_FUNC, TrTestHigh, "fs.testflags = 0x%x\n", testflags[i]));

        error = tffs_fwrite(mytmpname, tf.data, tf.size);
        expect_ok(error);
        error = tffs_fcontrol("/dummy", OC_FS_TESTFLAGS, testflags[i]);
        expect(error, EFFS_OK);

        test_ffs_state_get(&old);
        error = tffs_rename(mytmpname, tf.name);
        expect_ok(error);
        error = tffs_initialize();
        expect(error, EFFS_OK);
        test_ffs_state_get(&new);

        // tf.name MUST always exist
        error = tffs_xlstat(tf.name, &stat_new);
        expect(error, EFFS_OK);

        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_used new = %7d, old = %7d, diff = %d\n",
              new.bytes_used, old.bytes_used, new.bytes_used - old.bytes_used));
        tw(tr(TR_FUNC, TrTestHigh,
              "bytes_lost new = %7d, old = %7d, diff = %d\n",
              new.bytes_lost, old.bytes_lost,
              new.bytes_lost - old.bytes_lost));

        error = tffs_xlstat(mytmpname, &stat_new);
        if (i < 2) {
            // Journal not READY, so test-journal-tmp must exist
            expect(error, EFFS_OK); 
            expect_eq(new.objects_total, old.objects_total);
            expect_eq(new.bytes_used,    old.bytes_used + myspace);
            expect_eq(new.bytes_lost,    old.bytes_lost + myspace);
        }
        else {
            // Journal ready, so test-journal-tmp must not exist 
            expect(error, EFFS_NOTFOUND);
            expect_eq(new.objects_total, old.objects_total - 1);
            expect_eq(new.bytes_used,    old.bytes_used + myspace);
            expect_eq(new.bytes_lost,    old.bytes_lost + myspace + mytmpspace);
            expect_ne(stat_old.inode, stat_new.inode);
        }
    }

    ttw(str(TTrTest, "" NL));
    tw(tr(TR_END, TrTestHigh, ""));

    // cleanup
    tffs_remove(tf.name);
    tffs_remove("/dummy");

    return 0;
}

// Test block recovery
int case_brecover(int p0, int p1)
{
    struct test_file_s tf;
    struct ffs_state_s old, new;
    int j, inodes_block_old = 0, inodes_block_new = 0;
    int i, error;
    uint16 free_blocks;

    int testflags[] = {
        BLOCK_COMMIT_BEFORE,
        BLOCK_COMMIT_NO_VALID,
        BLOCK_COMMIT_OLD_FREE,
        BLOCK_COMMIT_AFTER
    };

    int testflags_2[] = {
        BLOCK_RECLAIM_ALLOC,
        BLOCK_RECLAIM_CLEANING,
        BLOCK_RECLAIM_NO_CLEAN,
    };

    tf.name = "/dummy4fctrl";
    tf.data = (char *) tdata[0];
    tf.size = sdata[0];

    error = tffs_fwrite(tf.name, tf.data, tf.size);
    expect(error, EFFS_OK);

    for (j = 0; j < 3; j++)
    {
        test_ffs_state_get(&old);
//case_lsr(0, 0);  // temp

        error = ffs_query(Q_FS_INODES, (uint16 *) &inodes_block_old);
        expect(error, EFFS_OK);

        // Trigger block_commit() to fail
        error = tffs_fcontrol(tf.name, OC_FS_TESTFLAGS, testflags[j]);
        expect(error, EFFS_OK);
        
        inodes_reclaim();
        
        error = tffs_initialize();
        expect(error, EFFS_OK);
        
        error = ffs_query(Q_FS_INODES, (uint16 *) &inodes_block_new);
        expect(error, EFFS_OK);

        ttw(ttr(TTrTest, "inodes block %d -> %d" NL,
                inodes_block_old, inodes_block_new));
        tw(tr(TR_FUNC, TrTestHigh, "inodes block %d -> %d\n",
              inodes_block_old, inodes_block_new));
        
        switch (j) {
        case 0: expect(inodes_block_old, inodes_block_new); break;
        case 1: expect_ne(inodes_block_old, inodes_block_new); break;
        case 2: expect_ne(inodes_block_old, inodes_block_new); break;
        case 3: expect_ne(inodes_block_old, inodes_block_new); break;
        }

        test_ffs_state_get(&new);

        if (test_expect_objects(&old, &new))
            return 1;

    }

// FIXME: The below test often fails (recalaim data block and create new
// jouranl file makes it to fail)
    if (p0 == 9) {
// Test BLOCK_RECLAIM Fill up all data blocks until only one is
// free. 
        tw(tr(TR_FUNC, TrTest, "Test Block reclaim\n"));
        ttw(ttr(TTrTest, "Test Block reclaim..." ));
        tffs_mkdir("/rand");

        ffs_query(Q_BLOCKS_FREE, (uint16 *) &free_blocks);
        while (free_blocks > 1) {
            error = case_mk_rand_file("/rand", 10000, 0);
            if (error < 0) {
                tw(tr(TR_FUNC, TrTest, "ERROR: case_mk_rand_file failed\n"));
                return 1;
            }
            ffs_query(Q_BLOCKS_FREE, (uint16 *) &free_blocks);
        } 

// case_mk_rand_file() will trigger a block_reclaim which will be
// interruptet by the activated testflags. This will cause the FFS to
// use the last free block! After a new init of ffs must it have
// recovered.

        ttw(ttr(TTrTest, "use last block..."));
        for (i = 0; i < sizeof(testflags_2)/sizeof(int); i++)
        {
            error = tffs_fcontrol(tf.name, OC_FS_TESTFLAGS, testflags_2[i]);
            expect(error, EFFS_OK);

            do {
                case_cleanup(20000);
                error = case_mk_rand_file("/rand", 7000, 0);
            } while (error >= 0);
            expect(error, EFFS_NOSPACE);

            ffs_query(Q_BLOCKS_FREE, (uint16 *) &free_blocks);
            if (free_blocks != 0) {
                tw(tr(TR_FUNC, TrTestHigh, "ERROR: We still have free blocks!\n"));
                return 1;
            }

            error = tffs_initialize();// NOTE: error "WARNING: block_alloc failed" is okay.
            expect_ok(error);

            ffs_query(Q_BLOCKS_FREE, (uint16 *) &free_blocks);
            if (free_blocks != 1) {
                tw(tr(TR_FUNC, TrTestHigh,
                      "ERROR: Wrong number of blocks free: %d\n", free_blocks));
                return 1;
            }
        }
// NOTE: We have to make this clenaup because we can not run if FFS is
// near full
        case_cleanup(dev.blocksize);

// This test will make data_block_reclaim() to fail before it has
// relocated all objects
        ttw(ttr(TTrTest, "interrupt block_reclaim()...")); 
        for (i = 0; i < 4; i++)
        {
            error = tffs_fcontrol(tf.name, OC_FS_TESTFLAGS, BLOCK_RECOVER_OBJECTS);
            expect(error, EFFS_OK);

            do {
                case_cleanup(20000);
                error = case_mk_rand_file("/rand", 7000, 0);
            } while (error >= 0);
            expect(error, EFFS_NOSPACE);
// NOTE: error "WARNING: block_alloc failed" is okay.
            error = tffs_initialize();  
            expect_ok(error);
        }

// Free up some space to the next test case.
        case_cleanup(dev.blocksize * 4);

        ttw(ttr(TTrTest, "Done" NL));
    }

    return 0;
}


/******************************************************************************
 * Specific/Special Test Cases
 ******************************************************************************/

// FIXME: Is this test case still valid after the new ffs_file_write() which
// makes seveal chunks instead of on big?

// Test filling a block to the full block size. We write N files of
// decreasing powers of two, starting at half the block size. At the end we
// are guaranteed to have at least one completelt full block. Also, we know
// we have used at least <block_size> bytes of storage.
int case_bfull(int p0, int p1)
{
    struct ffs_state_s old, new;
    char myname[40] = "/antarctica/ice-";
    int i, size, mysize, dirlen, space;

    if (param.data_blocks <= 1) {
        ttw(ttr(TTrTest, "WARNING: Too few blocks to run. Skip test" NL));
        tw(tr(TR_FUNC, TrTest, "WARNING: Too few blocks to run. Skip test\n"));
        return 0;
    }

    error = tffs_mkdir("/antarctica");

    error = case_cleanup(param.block_size + 10000);
    expect_ok(error);

    test_ffs_state_get(&old);
    dirlen = strlen(myname);
    space = 0;

    size = param.block_size/2;
    for (i = 0; size > 0; i++) {
        mysize = size - param.atomsize;
        if (mysize < 0)
            mysize = size;
        sprintf(&myname[dirlen], "%d", size);
        error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], mysize);
        expect(error, EFFS_OK);
        error = tffs_xlstat(myname, &xstat);
        expect(error, EFFS_OK);
        ttw(ttr(TTrTest, "%6d: %s" NL, xstat.space, myname));
        tw(tr(TR_FUNC, TrTestHigh, "%6d: %s\n", xstat.space, myname));
        space += xstat.space;
        size >>= 1;
    }

    test_ffs_state_get(&new);
    // Check space used. We have used a little more than <space> because we
    // have probably allocated one or two new blocks, and that is
    // dev.atomsize more bytes per block.
    tw(tr(TR_FUNC, TrTestHigh,
          "old.free = %d, new.free = %d, space used in test = %d\n",
          old.bytes_free, new.bytes_free, space));
    expect_gt(old.bytes_free, new.bytes_free + space - 1);

    // remove half of the files
    size = param.block_size/2;
    for (i = 0; size > 0; i++) {
        if (i & 1) {
            sprintf(&myname[dirlen], "%d", size);
            error = tffs_remove(myname);
            expect(error, EFFS_OK);
        }
        size >>= 1;
    }

    return 0;
}


int case_list(int p0, int p1)
{
    const struct testcase_s *p;

    tw(tr(TR_FUNC, TrTestHigh, "bigbuf   = 0x%X, %d\n", bigbuf,   bigbuf_size));
    tw(tr(TR_FUNC, TrTestHigh, "smallbuf = 0x%X, %d\n", smallbuf, smallbuf_size));

    ttw(ttr(TTrTest, "bigbuf   = 0x%X, %d" NL, bigbuf,   bigbuf_size));
    ttw(ttr(TTrTest, "smallbuf = 0x%X, %d" NL, smallbuf, smallbuf_size));

    ttw(str(TTrTest, "Test Cases:" NL));
    for (p = testcase; p->name != 0; p++) {
        tw(tr(TR_FUNC, TrTest, "%8s: %s\n", p->name, p->comment));
        ttw(ttr(TTrTest, "%8s: %s" NL, p->name, p->comment));
    }
    return 0;
}

// Stress test the target.
// p0 is number of times to run. Default is 10.
// p1 is number of milli-seconds to wait between each file update.
int case_stress(int p0, int p1)
{
    char myname[20];
    int i, mysize;
    char *mydata;
 
    mydata = (char *) tdata[TDATA_HUGE];
    mysize = 9 * param.block_size / 16;

    if (p0 == 0) p0 = 10;
    if (p1 == 0) p1 = 1000;

    for (i = 0; i < p0; i++) {
        sprintf(myname, "/stress-%d", i);
        ttw(ttr(TTrTest, "/stress %d" NL, i));
        error = tffs_fcreate(myname, (char *)mydata, mysize);
        expect(error, EFFS_OK);
        tffs_delay(p1);
        error = tffs_remove(myname);
        expect(error, EFFS_OK);
    }

    return 0;
}

// Test ffs with many (small) files. p0 is number of times to write all the
// files. Default is two e.g. one create and one update
int case_mfiles(int p0, int p1)
{
    char myname[40];
    int i, j;
 
    static struct test_file_s tf[] =
        {
            { MFDIR "/MSCAP",   0,    6 },
            { MFDIR "/IMEI",    0,    8 },
            { MFDIR "/CLASS2",  0,    3 },
            { MFDIR "/CLASS3",  0,    2 },
            { MFDIR "/MSSUP",   0,    5 },
            { MFDIR "/MSSET",   0,   83 },
            { MFDIR "/UPN",     0,  165 },
            { MFDIR "/CTIM",    0,   92 },
            { MFDIR "/CCNT",    0,   52 },
            { MFDIR "/ECC",     0,   15 },
            { MFDIR "/ORG",     0, 2650 },
            { MFDIR "/BCCHINF", 0,   54 },
            { MFDIR "/CCP",     0,    7 },
            { MFDIR "/EXT1",    0,   13 },
            { MFDIR "/SIMLCK",  0,   62 },
            { MFDIR "/SIMLCKEXT",0, 172 },
            { MFDIR "/SECURITY", 0,   8 },
            { MFDIR "/MAIN",    0,    8 },
            { MFDIR "/SFK",     0,    8 },
            { MFDIR "/FAULT",   0,    8 },
            { MFDIR "/DEBUG",   0,    8 },
            { MFDIR "/POWER",   0,    8 },
            { MFDIR "/KEYB",    0,   64 },
            { MFDIR "/RADIO",   0,    8 },
            { MFDIR "/CGMI",    0,   20 },
            { MFDIR "/CGMM",    0,   20 },
            { MFDIR "/CGMR",    0,   20 },
            { MFDIR "/CGSN",    0,   20 },
            { MFDIR "/SMSPRFL", 0,  206 },
            { MFDIR "/PLMN",    0,   68 },
            { MFDIR "/ATRADIO", 0, 2700 },
            { MFDIR "/GROUP",   0, 1122 }
        };

    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/ffs/i2048o1024", 0x2BAD);
    expect(error, EFFS_OK);
    error = tffs_mkdir(MFDIR);
    expect(error, EFFS_OK);

    if (p0 == 0) p0 = 2;

    for (j = 0; j < p0; j++) {
        tw(tr(TR_FUNC, TrTestHigh, "mf: Writing many files, loop %d\n", j));

        tw(tr(TR_FUNC, TrTestHigh, "SMS000..SMS099\n"));
        for (i = 0; i < 100; i++) {
            sprintf(myname, MFDIR "/SMS%03d", i);
            error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], 176);
            expect(error, EFFS_OK);
        }

        tw(tr(TR_FUNC, TrTestHigh, "LDN/LRN/LMN000..LDN/LRN/LMN009\n"));
        for (i = 0; i < 10; i++) {
            sprintf(myname, MFDIR "/LDN%03d", i);
            error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], 31);
            expect(error, EFFS_OK);
            sprintf(myname, MFDIR "/LRN%03d", i);
            error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], 32);
            expect(error, EFFS_OK);
            sprintf(myname, MFDIR "/LMN%03d", i);
            error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], 30);
            expect(error, EFFS_OK);
        }

        tw(tr(TR_FUNC, TrTestHigh, "ADN000..ADN149\n"));
        for (i = 0; i < 150; i++) {
            sprintf(myname, MFDIR "/ADN%03d", i);
            error = tffs_fwrite(myname, (char *) tdata[TDATA_HUGE], 43);
            expect(error, EFFS_OK);
        }

        tw(tr(TR_FUNC, TrTestHigh, "PCM Files...\n"));
        for (i = 0; i < sizeof(tf)/sizeof(struct test_file_s); i++) {
            error = tffs_fwrite(tf[i].name,
                                (char *) tdata[TDATA_HUGE], tf[i].size);
            expect(error, EFFS_OK);
        }
    }

    error = tffs_exit();
    expect(error, EFFS_OK);

    return 0;
}


int case_open(int p0, int p1)
{
    fd_t fdi;
    char myname[20];
    int i, size;

#define RDONLY FFS_O_RDONLY
#define WRONLY FFS_O_WRONLY
#define APPEND FFS_O_APPEND
#define CREATE FFS_O_CREATE
#define EXCL   FFS_O_EXCL
#define TRUNC  FFS_O_TRUNC

    ffs_options_t non_exist_invalid[] = {                      
        ( 0 ),
        ( RDONLY |                   CREATE                ),                  
        ( RDONLY |                   CREATE | EXCL         ),
        ( RDONLY |                   CREATE |        TRUNC ),
        ( RDONLY |                   CREATE | EXCL | TRUNC ),
        ( RDONLY |                            EXCL         ),
        ( RDONLY |                            EXCL | TRUNC ),
        ( RDONLY |                                   TRUNC ),
        (                                     EXCL         ),
        (                                     EXCL | TRUNC ),
        (                                            TRUNC ),
        (                            CREATE |        TRUNC ),
        (                            CREATE | EXCL | TRUNC ),
        (                            CREATE | EXCL         ),
        (                            CREATE                ),
        (                   APPEND                         ),
        (                   APPEND |          EXCL         ),
        (                   APPEND |          EXCL | TRUNC ),
        (                   APPEND |                 TRUNC ),
        (                   APPEND | CREATE |        TRUNC ),
        (                   APPEND | CREATE | EXCL         ),
        (                   APPEND | CREATE | EXCL         ),
        (                   APPEND | CREATE                ),
        ( RDONLY |          APPEND                         ),
        ( RDONLY |          APPEND |          EXCL         ),
        ( RDONLY |          APPEND |          EXCL | TRUNC ),
        ( RDONLY |          APPEND |                 TRUNC ),
        ( RDONLY |          APPEND | CREATE |        TRUNC ),
        ( RDONLY |          APPEND | CREATE | EXCL | TRUNC ),
        ( RDONLY |          APPEND | CREATE | EXCL         ),
        ( RDONLY |          APPEND | CREATE                )
    };
    
    ffs_options_t non_exist_notfound[] = {                      
        (          WRONLY | APPEND                         ),
        (          WRONLY | APPEND |          EXCL         ),
        (          WRONLY | APPEND |          EXCL | TRUNC ),
        (          WRONLY | APPEND |                 TRUNC ),
        (          WRONLY                                  ),
        (          WRONLY |                   EXCL         ),
        (          WRONLY |                   EXCL | TRUNC ),
        (          WRONLY |                          TRUNC ),
        ( RDONLY | WRONLY                                  ),
        ( RDONLY | WRONLY |                   EXCL         ),
        ( RDONLY | WRONLY |                   EXCL | TRUNC ),
        ( RDONLY | WRONLY |                          TRUNC ),
        ( RDONLY | WRONLY | APPEND                         ),
        ( RDONLY | WRONLY | APPEND |          EXCL         ),
        ( RDONLY | WRONLY | APPEND |          EXCL | TRUNC ),
        ( RDONLY | WRONLY | APPEND |                 TRUNC ),
        ( RDONLY                                           )
    };
    
    ffs_options_t non_exist_fd_offset[] = {                      
        (          WRONLY | APPEND | CREATE |        TRUNC ),
        (          WRONLY | APPEND | CREATE | EXCL | TRUNC ),
        (          WRONLY | APPEND | CREATE | EXCL         ),
        (          WRONLY | APPEND | CREATE                ),
        (          WRONLY |          CREATE |        TRUNC ),
        (          WRONLY |          CREATE | EXCL | TRUNC ),
        (          WRONLY |          CREATE | EXCL         ),
        (          WRONLY |          CREATE                ),
        ( RDONLY | WRONLY |          CREATE |        TRUNC ),
        ( RDONLY | WRONLY |          CREATE | EXCL | TRUNC ),
        ( RDONLY | WRONLY |          CREATE | EXCL         ),
        ( RDONLY | WRONLY |          CREATE                ),    
 
        ( RDONLY | WRONLY | APPEND | CREATE |        TRUNC ),
        ( RDONLY | WRONLY | APPEND | CREATE | EXCL | TRUNC ),
        ( RDONLY | WRONLY | APPEND | CREATE | EXCL         ),
        ( RDONLY | WRONLY | APPEND | CREATE                )    
    };          

    ffs_options_t exist_invalid[] = {                      
        (                                     EXCL         ),
        (                                     EXCL | TRUNC ),
        (                                            TRUNC ),
        (                            CREATE |        TRUNC ),
        (                            CREATE | EXCL | TRUNC ),
        (                            CREATE | EXCL         ),
        (                            CREATE                ),
        (                   APPEND                         ),
        (                   APPEND |          EXCL         ),
        (                   APPEND |          EXCL | TRUNC ),
        (                   APPEND |                 TRUNC ),
        (                   APPEND | CREATE |        TRUNC ),
        (                   APPEND | CREATE | EXCL | TRUNC ),
        (                   APPEND | CREATE | EXCL         ),
        (                   APPEND | CREATE                ),
        ( RDONLY |          APPEND                         ),
        ( RDONLY |          APPEND |          EXCL         ),
        ( RDONLY |          APPEND |          EXCL | TRUNC ),
        ( RDONLY |          APPEND |                 TRUNC ),
        ( RDONLY |          APPEND | CREATE |        TRUNC ),
        ( RDONLY |          APPEND | CREATE | EXCL | TRUNC ),
        ( RDONLY |          APPEND | CREATE | EXCL         ),
        ( RDONLY |          APPEND | CREATE                ),
        ( RDONLY |                            EXCL         ),
        ( RDONLY |                            EXCL | TRUNC ),
        ( RDONLY |                                   TRUNC ),
        ( RDONLY |                   CREATE |        TRUNC ),
        ( RDONLY |                   CREATE | EXCL | TRUNC ),
        ( RDONLY |                   CREATE | EXCL         ),
        ( RDONLY |                   CREATE                )
};
    
    ffs_options_t exist_exist[] = {                      
        (          WRONLY | APPEND | CREATE | EXCL | TRUNC ),
        (          WRONLY | APPEND | CREATE | EXCL         ),
        (          WRONLY |          CREATE | EXCL | TRUNC ),
        (          WRONLY |          CREATE | EXCL         ),
        ( RDONLY | WRONLY |          CREATE | EXCL | TRUNC ),
        ( RDONLY | WRONLY |          CREATE | EXCL         ),
        ( RDONLY | WRONLY | APPEND | CREATE | EXCL | TRUNC ),
        ( RDONLY | WRONLY | APPEND | CREATE | EXCL         )
    };
    
    ffs_options_t exist_fd_offset[] = {                      
        (          WRONLY | APPEND                         ),
        (          WRONLY | APPEND |          EXCL         ),
        (          WRONLY | APPEND |          EXCL | TRUNC ),
        (          WRONLY | APPEND |                 TRUNC ),
        (          WRONLY | APPEND | CREATE |        TRUNC ),
        (          WRONLY | APPEND | CREATE                ),
        ( RDONLY                                           ),
        ( RDONLY | WRONLY                                  ),
        ( RDONLY | WRONLY |                   EXCL         ),
        ( RDONLY | WRONLY |                   EXCL | TRUNC ),
        ( RDONLY | WRONLY |                          TRUNC ),
        ( RDONLY | WRONLY |          CREATE |        TRUNC ),
        ( RDONLY | WRONLY |          CREATE                ),    
        ( RDONLY | WRONLY | APPEND                         ),
        ( RDONLY | WRONLY | APPEND |          EXCL         ),
        ( RDONLY | WRONLY | APPEND |          EXCL | TRUNC ),
        ( RDONLY | WRONLY | APPEND |                 TRUNC ),
        ( RDONLY | WRONLY | APPEND | CREATE |        TRUNC ),
        ( RDONLY | WRONLY | APPEND | CREATE                ),    
        (          WRONLY                                  ),
        (          WRONLY |                   EXCL         ),
        (          WRONLY |                   EXCL | TRUNC ),
        (          WRONLY |                          TRUNC ),
        ( WRONLY |                   CREATE |        TRUNC ),
        ( WRONLY |                   CREATE                )

    };

    // Cleanup
    for (i = 0; i < 20; i++) {
        sprintf(myname, "/stream%d", i);
        tffs_remove(myname);
    }

    tw(tr(TR_FUNC, TrTestHigh, "Various open options, file non-existing\n"));
    ttw(str(TTrTest, "Various open options, file non-existing" NL));
    
    for (i = 0; i < sizeof(non_exist_invalid)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/20 * 20); 
        fdi = tffs_open(myname, non_exist_invalid[i]);
        expect(fdi, EFFS_INVALID);
    }

    for (i = 0; i < sizeof(non_exist_notfound)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/20 * 20); 
        fdi = tffs_open(myname, non_exist_notfound[i]);
        expect(fdi, EFFS_NOTFOUND);
    }

    for (i = 0; i < sizeof(non_exist_fd_offset)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/20 * 20); 
        fdi = tffs_open(myname, non_exist_fd_offset[i]);
        expect(fdi, FFS_FD_OFFSET);
        tffs_close(fdi);
    }

    // Make FFS_FD_MAX number of files and write some data for later use
    tw(tr(TR_FUNC, TrTestHigh, "Create %d files with data \n", fs.fd_max)); 
    for (i = 0; i < fs.fd_max; i++) {
        sprintf(myname, "/stream%d", i);
        fdi = tffs_open(myname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC);
        expect(fdi, i + FFS_FD_OFFSET);
        size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 31);
        expect(size, 31);
    }
   
    // Try to open one more file, this will make a error!
    sprintf(myname, "/stream%d", i++);
    fdi = tffs_open(myname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC);
    expect(fdi, EFFS_NUMFD);

    for (i = 0; i < fs.fd_max; i++) {
        error = tffs_close(i + FFS_FD_OFFSET);
        expect(error, EFFS_OK);
    }

    tw(tr(TR_FUNC, TrTestHigh,
          "Open same file multiple times in RDONLY mode\n"));
    for (i = 0; i < fs.fd_max; i++) {
        fdi = tffs_open("/stream2", FFS_O_RDONLY);
        expect(fdi, i + FFS_FD_OFFSET);
    }

    for (i = 0; i < fs.fd_max; i++) {
        error = tffs_close(i + FFS_FD_OFFSET);
        expect(error, 0);
    }

    tw(tr(TR_FUNC, TrTestHigh,
          "Open in WRONLY and try to open same file in WR or RD\n"));
    fdi = tffs_open("/stream1", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    fdi = tffs_open("/stream1", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, EFFS_LOCKED);
    fdi = tffs_open("/stream1", FFS_O_RDONLY );
    expect(fdi, EFFS_LOCKED);
    error = tffs_close(FFS_FD_OFFSET);
    expect(error, 0);

    tw(tr(TR_FUNC, TrTestHigh,
          "Open in READONLY and try to open same file WRONLY\n"));
    fdi = tffs_open("/stream1", FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    fdi = tffs_open("/stream1", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, EFFS_LOCKED);
    error = tffs_close(FFS_FD_OFFSET);
    expect(error, 0);

    tw(tr(TR_FUNC, TrTestHigh, "Various open options, file exists\n"));
    ttw(str(TTrTest, "Various open options, file exists" NL));

    for (i = 0; i < sizeof(exist_invalid)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/20 * 20); 
        fdi = tffs_open(myname, exist_invalid[i]);
        expect(fdi, EFFS_INVALID);
    }

    for (i = 0; i < sizeof(exist_exist)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/20 * 20); 
        fdi = tffs_open(myname, exist_exist[i]);
        expect(fdi, EFFS_EXISTS);
        tffs_close(fdi);
    }

    for (i = 0; i < sizeof(exist_fd_offset)/sizeof(ffs_options_t); i++) {
        sprintf(myname, "/stream%d", i - i/10 * 10); 
        fdi = tffs_open(myname, exist_fd_offset[i]);
        expect(fdi, FFS_FD_OFFSET);
        tffs_close(fdi);
    }

    tw(tr(TR_FUNC, TrTestHigh, "Try to create a file with a dir name\n"));
    error = tffs_mkdir("/streams");

    fdi = tffs_open("/streams", FFS_O_WRONLY | FFS_O_APPEND | FFS_O_CREATE);
    expect(fdi, EFFS_NOTAFILE);

    tw(tr(TR_FUNC, TrTestHigh, 
          "Try to use fwrite, fcreate, fread and remove on a open file\n"));
    fdi = tffs_open("/stream1", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    
    error = tffs_fcreate("/stream1", TDATA(2));
    expect(error, EFFS_EXISTS);

    error = tffs_file_write("/stream1", (char *) tdata[TDATA_HUGE], 31, 
                            FFS_O_CREATE | FFS_O_TRUNC);
    expect(error, EFFS_LOCKED);

    error = tffs_file_read("/stream1", bigbuf, 31);
    expect(error, EFFS_LOCKED);

    error = tffs_remove("/stream1");
    expect(error, EFFS_LOCKED);

    error = tffs_close(fdi);
    expect(error, 0);

#undef RDONLY 
#undef WRONLY 
#undef APPEND 
#undef CREATE 
#undef EXCL   
#undef TRUNC  

    return 0;
}

int twrite_seek(fd_t fdi, offset_t offset, int whence)
{
    int size;

    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE] + 10, 100);
    if (test_expect(size, 100) < 0) return -1;
    
    offset = tffs_seek(fdi, offset, whence);
    if (test_expect_ok(offset)) return -1;

    return offset;
}

int tseek_read(fd_t fdi, offset_t offset, int whence, offset_t src_offset)
{
    int size;

    offset = tffs_seek(fdi, offset, whence);
    if (test_expect_ok(offset)) return -1;

    size = tffs_read(fdi, bigbuf, 100);     
    if (test_expect(size, 100) < 0) return -1;

    error = test_expect_data((char*)tdata[TDATA_HUGE] + src_offset, bigbuf, size);
    if (error) return error;

    return offset;
}

int tseek_write(fd_t fdi, offset_t offset, int whence, offset_t src_offset)
{
    int size;

    offset = tffs_seek(fdi, offset, whence);
    if (test_expect_ok(offset)) return -1;

    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE] + src_offset + offset, 
                      fs.chunk_size_max / 2);
    if (test_expect(size, fs.chunk_size_max / 2) < 0) return -1;
    
    return offset;
}

int case_rw(int p0, int p1)
{
    int i, size, offset;
    fd_t fdi;

    const char *dirs[] = { "/streams", "/streams/rw", "/streams/rw/multi", 
                           "/streams/rw/multi/write" };

    error = case_cleanup(5 * fs.chunk_size_max);
    expect_ok(error);

    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        error = tffs_mkdir(dirs[i]);
    }

    tw(tr(TR_FUNC, TrTestHigh, 
          "Test create and append of file, small and huge\n"));
    ttw(str(TTrTest, "Test create and append of file, small and huge" NL));
    
    fdi = tffs_open("/streams/rw_test", 
                    FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 31);
    expect(size, 31);
    error = tffs_close(fdi); expect(error, EFFS_OK);     

    fdi = tffs_open("/streams/rw_test", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE] + 31, 2 * fs.chunk_size_max);
    expect(size, 2 * fs.chunk_size_max);

    error = tffs_close(fdi); expect(error, EFFS_OK);     

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));
    ttw(ttr(TTrTest, "Validate data" NL)); 
    size = tffs_fread("/streams/rw_test", bigbuf, bigbuf_size);
    expect(size, 31 + 2 * fs.chunk_size_max); 
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;

    // Provoke EFFS_FILETOOBIG
    error = tffs_fread("/streams/rw_test", bigbuf, size - 1);
    expect(error, EFFS_FILETOOBIG); 
    error = tffs_fread("/streams/rw_test", bigbuf, size + 1);
    expect_ok(error); 
    error = tffs_fread("/streams/rw_test", bigbuf, size);
    expect_ok(error);

    // Update file from zero, middle (between to chunks) and end. File with
    // and without data in seghead. Update the first 100 bytes (new data
    // have a offset of 10 so the data not is identical with the existing
    // ones).
    fdi = tffs_open("/streams/rw_test", FFS_O_RDWR);  
    expect(fdi, FFS_FD_OFFSET);

    // write 100 bytes and seek to 50 bytes before end of the first chunk
    expect_ok(twrite_seek(fdi, fs.chunk_size_max - 50, FFS_SEEK_SET));

    // write 100 bytes, seek to 50 bytes from the end of the file
    expect_ok(twrite_seek(fdi, -50, FFS_SEEK_END));

    // Write 100 bytes. This will update the last 19 bytes of second chunk,
    // update and append data to the last chunk.  Read the last data but
    // only 80 bytes because they are in the buffer
    expect_ok(twrite_seek(fdi, -80, FFS_SEEK_CUR));

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));
    ttw(ttr(TTrTest, "Validate data" NL)); 

    size = tffs_read(fdi, bigbuf, 100); 
    expect(size, 80);
    error = test_expect_data((char *)tdata[TDATA_HUGE] + 10 + 20, bigbuf, size);
    if (error) return 1;

    // Seek to start of file and read the first 100 bytes that was updated
    offset = tseek_read(fdi, 0, FFS_SEEK_SET, 10);
    expect_ok(offset);

    // Read 100 bytes more to make sure that the old data still is valid
    offset = tseek_read(fdi, 0, FFS_SEEK_CUR, 100);
    expect_ok(offset);

    // Seek to 50 bytes before end of the first chunk and read 100 bytes
    offset = tseek_read(fdi, fs.chunk_size_max - 50, FFS_SEEK_SET, 10);
    expect_ok(offset);

    // Read 100 bytes more to make sure that the old data still is valid
    offset = tseek_read(fdi, 0, FFS_SEEK_CUR, offset + 100);
    expect_ok(offset);

    // Read the last 100 bytes (updated data from 2 chunks)
    offset = tseek_read(fdi, -100, FFS_SEEK_END, 10);
    expect_ok(offset);

    error = tffs_close(fdi);
    expect(error, EFFS_OK);     
    
    // Test where there is data in the seghead
    size = tffs_file_write("/streams/rw_test", (char*) tdata[TDATA_HUGE], 
                           2 * fs.chunk_size_max, FFS_O_CREATE | FFS_O_TRUNC);
    
    fdi = tffs_open("/streams/rw_test", FFS_O_RDWR);
    expect(fdi, FFS_FD_OFFSET);

    // Update the last half of the last chunk
    expect_ok(tseek_write(fdi, -fs.chunk_size_max / 2, FFS_SEEK_END, 10));

    // Update the first half of the last chunk
    expect_ok(tseek_write(fdi, fs.chunk_size_max, FFS_SEEK_SET, 10));

    // Update the first half of the seghead
    expect_ok(tseek_write(fdi, 0, FFS_SEEK_SET, 10));

    // Update the last half of the seghead, but flush the buffer so it have
    // to write and read the previous updated data
    error = tffs_fdatasync(fdi);
    expect(error, EFFS_OK);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE] + 10 + fs.chunk_size_max/2, 
                      fs.chunk_size_max/2);
    expect(size, fs.chunk_size_max/2);

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));
    ttw(ttr(TTrTest, "Validate data" NL)); 
    // Read the complete file, all the data should have a offset of 10 bytes now!
    offset = tffs_seek(fdi, 0, FFS_SEEK_SET);
    expect_ok(offset);

    size = tffs_read(fdi, bigbuf, bigbuf_size); 
    expect(size, 2 * fs.chunk_size_max); 
    error = test_expect_data((char *)tdata[TDATA_HUGE] + 10, bigbuf, size);
    if (error) return 1;

    error = tffs_close(fdi); expect(error, 0);
    
    // Test bad fdi, write in RDONLY and read from WRONLY
    fdi = tffs_open("/streams/rw_test", FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_read(fdi + 1, bigbuf, 31);
    expect(size, EFFS_BADFD);
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 31);
    tw(tr(TR_END, TrApi, "} %d\n", size)); // Avoid wrong indent 
    expect(size, EFFS_INVALID);
    error = tffs_close(fdi);  expect(error, 0);

    fdi = tffs_open("/streams/rw_test", FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_read(fdi, bigbuf, 31);
    expect(size, EFFS_INVALID);
    error = tffs_close(fdi); expect(error, 0);

    // Test for bad file name
    fdi = tffs_open("/stream&!'*", FFS_O_WRONLY | FFS_O_CREATE);
    expect(fdi, EFFS_BADNAME);

    // Test for bad file name
    fdi = tffs_open("/%$#.+-_,Z19", FFS_O_WRONLY | FFS_O_CREATE);
    expect(fdi, FFS_FD_OFFSET);
    tffs_close(fdi);

    return 0;
}

int case_multi_open(int p0, int p1)
{
    int i, j, size;
    fd_t fdi;

    const char *dirs[] = { "/streams", "/streams/rw", "/streams/rw/multi", 
                           "/streams/rw/multi/write" };

    const char *file[] = { "/x-rw", "/streams/rw/x-rw", 
                           "/streams/rw/multi/x-rw", 
                           "/streams/rw/multi/write/x-rw" };

    error = case_cleanup(fs.fd_max * 50 * 10 + 5000);
    expect_ok(error);

    for (i = 0; i < sizeof(dirs)/sizeof(char *); i++) {
        error = tffs_mkdir(dirs[i]);
    }

    // Open multiply files from difference subdirectorys 
    // and write/read X times to/from file (this also test truncate option)
    for (i = 0; i < fs.fd_max; i++) {   
        fdi = tffs_open( file[i], FFS_O_WRONLY | FFS_O_APPEND | 
                         FFS_O_CREATE | FFS_O_TRUNC);
        expect(fdi, i + FFS_FD_OFFSET);
    }

    for (i = 0; i < fs.fd_max; i++) {
        for (j = 0; j < 10; j++) {
            size = tffs_write(i + FFS_FD_OFFSET, (char *) tdata[TDATA_HUGE], 50);
            expect(size, 50);
        }
    }
    // Run test with p0 != 0 to stress the test
    if (p0) { error = test_run("irec;drec;"); if (error > 0) return 1; }

    for (i = 0; i < fs.fd_max; i++) {
        error = tffs_close(i + FFS_FD_OFFSET);
        expect(error, 0);
    }

    for (i = 0; i < fs.fd_max; i++) {   
        fdi = tffs_open( file[i], FFS_O_RDONLY);
        expect(fdi, i + FFS_FD_OFFSET);
    }
    
    if(p0) { error = test_run("irec;drec;"); if (error > 0) return 1; }

    for (i = 0; i < fs.fd_max; i++) {
        for (j = 0; j < 10; j++) {
            size = tffs_read(i + FFS_FD_OFFSET, bigbuf, 50);
            expect(size, 50);
            error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
            if (error) return 1;
        }
    }

    for (i = 0; i < fs.fd_max; i++) {
        error = tffs_close(i + FFS_FD_OFFSET);
        expect(error, 0);
    }

    return 0;
}


int case_seek(int p0, int p1)
{
    fd_t fdi;
    int size, offset;

    error = case_cleanup(3 * fs.chunk_size_max);
    expect_ok(error);

// The seek function itself don't care if the file is open in read or
    // write mode.
    error = tffs_mkdir("/streams");
    
    offset = tffs_seek(FFS_FD_OFFSET, 0, FFS_SEEK_SET);
    expect(offset, EFFS_BADFD);

    /* Make file to seek on */
    fdi = tffs_open("/streams/seek", FFS_O_RDWR | FFS_O_CREATE | FFS_O_TRUNC);
    expect(fdi, FFS_FD_OFFSET);    
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 3 * fs.chunk_size_max);
    expect(size, 3 * fs.chunk_size_max);
    
    /*  Simple test in RDWR without any writes. Test good and bad offset */
    tw(tr(TR_FUNC, TrTestHigh, "seek and RDWR \n"));
    ttw(str(TTrTest, "seek and RDWR" NL));

    offset = ffs_seek(fdi, 0, 5);
    expect(offset, EFFS_INVALID );
    
    tw(tr(TR_FUNC, TrTestHigh, "ffs_seek(FFS_SEEK_SET)\n"));
    ttw(str(TTrTest, "ffs_seek(FFS_SEEK_SET)" NL));
    offset = tffs_seek(fdi, -1, FFS_SEEK_SET);
    expect(offset, EFFS_INVALID);
    offset = tffs_seek(fdi, 3 * fs.chunk_size_max + 1, FFS_SEEK_SET);
    expect(offset, EFFS_INVALID);

    offset = tffs_seek(fdi, 15, FFS_SEEK_SET);
    expect(offset, 15);
    size = tffs_read(fdi, bigbuf, fs.chunk_size_max/10);
    expect(size, fs.chunk_size_max/10);
    error = test_expect_data((char *)tdata[TDATA_HUGE] + 15, 
                             bigbuf, fs.chunk_size_max/10);
    if (error) return 1;

    offset = tffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(offset, 0);
   
    size = tffs_read(fdi, bigbuf, bigbuf_size);
    error = test_expect_data((char*)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;
   
    tw(tr(TR_FUNC, TrTestHigh, "ffs_seek(FFS_SEEK_CUR)\n"));
    ttw(str(TTrTest, "ffs_seek(FFS_SEEK_CUR)" NL));
    offset = tffs_seek(fdi, -(3 * fs.chunk_size_max + 1), FFS_SEEK_CUR);
    expect(offset, EFFS_INVALID);
    offset = tffs_seek(fdi, 1, FFS_SEEK_CUR);
    expect(offset, EFFS_INVALID);    
    offset = tffs_seek(fdi, 0, FFS_SEEK_CUR);
    expect(offset, 3 * fs.chunk_size_max);
    offset = tffs_seek(fdi, -30, FFS_SEEK_CUR);
    expect(offset, 3 * fs.chunk_size_max - 30);
    offset = tffs_seek(fdi, 20, FFS_SEEK_CUR);
    expect(offset, 3 * fs.chunk_size_max - 30 + 20);
    size = tffs_read(fdi, bigbuf, 3 * fs.chunk_size_max);
    expect(size, 10);
    error = test_expect_data((char *)tdata[TDATA_HUGE] + 
                             (3 * fs.chunk_size_max - 10), bigbuf, 10);
    if (error) return 1;

    tw(tr(TR_FUNC, TrTestHigh, "ffs_seek(FFS_SEEK_END)\n"));
    ttw(str(TTrTest, "ffs_seek(FFS_SEEK_END)" NL));
    offset = tffs_seek(fdi, -(3 * fs.chunk_size_max + 1), FFS_SEEK_END);
    expect(offset, EFFS_INVALID);
    offset = tffs_seek(fdi, 1, FFS_SEEK_END);
    expect(offset, EFFS_INVALID);
    offset = tffs_seek(fdi, 0, FFS_SEEK_END);
    expect(offset, 3 * fs.chunk_size_max);
    offset = tffs_seek(fdi, -15, FFS_SEEK_END);
    expect(offset, 3 * fs.chunk_size_max - 15);
    size = tffs_read(fdi, bigbuf, 20);
    expect(size, 15);
    error = test_expect_data((char *)tdata[TDATA_HUGE] + 
                             (3 * fs.chunk_size_max - 15), bigbuf, size);
    if (error) return 1;
    
    error = tffs_close(fdi);
    expect(error, 0);

    // Seek in write-mode with write in different places of the file. Test
    // on the dirty flag to se if the buffer is flushed ad the rigth time
    tw(tr(TR_FUNC, TrTestHigh, "seek and WRONLY \n"));
    ttw(str(TTrTest, "seek and WRONLY" NL));

    fdi = tffs_open("/streams/seek", FFS_O_WRONLY);

    // Write and seek in the last chunk, buffer must be dirty so long the fp
    // not have been moved away from that chunk!

    offset = tffs_seek(fdi, -20, FFS_SEEK_END); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 0);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 10); expect(size, 10);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, -100, FFS_SEEK_CUR); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 10); expect(size, 10);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, -fs.chunk_size_max/4, FFS_SEEK_CUR); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 10); expect(size, 10);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, offset + 10, FFS_SEEK_SET); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 10); expect(size, 10);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    // Write and seek across several chunks, the buffer must be flushed after
    // leaving a 'dirty' chunk.
    
    offset = tffs_seek(fdi, 10, FFS_SEEK_SET); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 0);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 10); expect(size, 10);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, -50, FFS_SEEK_END); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 0);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 100); expect(size, 100);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, -fs.chunk_size_max, FFS_SEEK_CUR); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 0);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], fs.chunk_size_max / 2); 
    expect(size, fs.chunk_size_max / 2);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    offset = tffs_seek(fdi, fs.chunk_size_max, FFS_SEEK_SET); expect_ok(offset);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 0);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 50); expect(size, 50);
    expect(fs.fd[fdi - FFS_FD_OFFSET].dirty, 1);

    error = tffs_close(fdi);
    expect(error, 0);

    return 0;
}

// NOTEME: move to other place later
int case_trunc(int p0, int p1)
{
    fd_t fdi;
    int size, f_size, trunc_size, i;
    T_FFS_STAT stat;
    char fname[] = "/truncate/normal_file";
    char sname[] = "/truncate/stream_file";
    char imeifile[] = "/truncate/IMEI"; 
    
    tffs_remove(fname);
    tffs_remove(sname);

    error = case_cleanup(5 * fs.chunk_size_max);
    expect_ok(error);

    tffs_mkdir("/truncate");

    // Try to truncate a non existing file
    error = tffs_truncate(fname, 0);
    expect(error, EFFS_NOTFOUND);

    // Try to truncate a directory
    error = tffs_truncate("/truncate", 0);
    expect(error, EFFS_NOTAFILE);

    f_size = fs.chunk_size_max + 100;
    error = tffs_file_write(fname, (char *)tdata[TDATA_HUGE], f_size, 
                            FFS_O_TRUNC | FFS_O_WRONLY | FFS_O_CREATE);
    expect(error, EFFS_OK);
    
    /* Test truncate from file size + 3 to file size - 3 */
    for (i = 0; i < 6; i++) {
        trunc_size = f_size + 3 - i;
        tw(tr(TR_FUNC, TrTestLow, "TRUNC file to size %d \n", trunc_size));
        error = tffs_truncate(fname, trunc_size);
        expect(error, EFFS_OK);
        error = tffs_lstat(fname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, (f_size < trunc_size ? f_size : trunc_size));
    }
    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(fname, bigbuf, f_size);
    expect(size, trunc_size);  // ++ because of the last -- in for()
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;

    tw(tr(TR_FUNC, TrTestLow, "Truncate file while it is open \n"));
    fdi = tffs_open(fname, FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_write(fdi, (char *) &tdata[TDATA_HUGE][trunc_size], 31);
    expect(size, 31);
    f_size = trunc_size + 31;

    // Truncate to a size less than fp, this will fail
    error = tffs_ftruncate(fdi, f_size - 1);
    expect(error, EFFS_INVALID);

    // Move fp otherwise it will not be possible to truncate
    error = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(error, 0);

    // Test truncate from file size + 3 to file size - 3 
    for (i = 0; i < 6; i++) {
        trunc_size = f_size + 3 - i;    
        tw(tr(TR_FUNC, TrTestLow, "TRUNC file to size %d \n", trunc_size));
        error = tffs_ftruncate(fdi, trunc_size);
        expect(error, EFFS_OK);
        error = tffs_lstat(fname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, (f_size < trunc_size ? f_size : trunc_size));
    }
    
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(fname, bigbuf, f_size);
    expect(size, trunc_size);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;
    
    // We need to test ftruncate with a size less than the first chunk
    fdi = tffs_open(fname, FFS_O_WRONLY);
    expect(fdi, FFS_FD_OFFSET);
    
    error = tffs_ftruncate(fdi, 10);
    expect(error, EFFS_OK);
    error = tffs_ftruncate(fdi, 10);  // same size twice to complicate it
    expect(error, EFFS_OK);
    error = tffs_lstat(fname, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.size, 10);
    error = tffs_ftruncate(fdi, 0);
    expect(error, EFFS_OK);
    error = tffs_ftruncate(fdi, 0);
    expect(error, EFFS_OK);
    error = tffs_lstat(fname, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.size, 0);
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    // Stream file test, make 2 segments and test around the end of segment
    // 1 (file closed)
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC  
                    | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);

    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], fs.chunk_size_max + 1);
    expect(size, fs.chunk_size_max + 1);
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 31);
    expect(size, 31);
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    f_size = fs.chunk_size_max + 1 + 31;

    for (i = 0; i < 6; i++) {
        trunc_size = fs.chunk_size_max + 1 + 3 - i;
        tw(tr(TR_FUNC, TrTestLow, "TRUNC stream to size %d \n", trunc_size));
        error = tffs_truncate(sname, trunc_size);
        expect(error, EFFS_OK);
        error = tffs_lstat(sname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, trunc_size);
    }

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(sname, bigbuf, f_size);
    expect(size, trunc_size);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;

    // Try to enlarge the file.
    tw(tr(TR_FUNC, TrTestLow, "Try to enlarge the file \n"));    
    error = tffs_truncate(sname, trunc_size + 1);
    expect(error, EFFS_OK);
    error = tffs_lstat(sname, &stat);
    expect(error, EFFS_OK);
    expect_eq(stat.size, trunc_size);

    fdi = tffs_open(sname, FFS_O_WRONLY);
    expect(fdi, FFS_FD_OFFSET);
    error = tffs_ftruncate(fdi, trunc_size + 1);
    expect(error, EFFS_OK);
    error = tffs_lstat(sname, &stat); 
    expect(error, EFFS_OK);
    expect_eq(stat.size, trunc_size);
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    // Make a file with 2 segments + some data in the buffer, test around
    // end of segment 2 and the begining of segment 1 (file open).
    tw(tr(TR_FUNC, TrTestLow, "Truncate stream while it is open \n"));
    // Make segment 1
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_APPEND | FFS_O_TRUNC);
    expect(fdi, FFS_FD_OFFSET);
    f_size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 
                        2 * fs.chunk_size_max + 1);
    expect(f_size, 2 * fs.chunk_size_max + 1);
    
    // Move fp otherwise it will not be possible to truncate
    error = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(error, 0);

    // Test around end of segment 2
    for (i = 0; i < 16; i++) {
        trunc_size = f_size + 3 - i;
        tw(tr(TR_FUNC, TrTestLow, "TRUNC stream to size %d \n", 
              trunc_size));
        error = tffs_ftruncate(fdi, trunc_size);
        expect(error, EFFS_OK);
        error = tffs_lstat(sname, &stat); expect(error, EFFS_OK);
        expect_eq(stat.size, (f_size < trunc_size ? f_size : trunc_size));
    }

    // Test around begining of segment 1
    for (i = 0; i < 6; i++) {
        trunc_size = 3 - i;
        tw(tr(TR_FUNC, TrTestLow, "TRUNC stream to size %d \n", 
              trunc_size));
        error = tffs_ftruncate(fdi, trunc_size);
        if (trunc_size >= 0) {
            expect(error, EFFS_OK);
        }
        else {
            expect(error, EFFS_INVALID);
            break;
        }
        error = tffs_lstat(sname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, (trunc_size));
    }

    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    // Make, truncate and append to a file (use ffs_ftruncate()) 
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC  
                    | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);

    f_size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], fs.chunk_size_max);
    expect(f_size, fs.chunk_size_max );
    
    tw(tr(TR_FUNC, TrTestLow, "write 1/2 buf size, trunc 1/4 buf size\n"));
    // write 1/2 buf size and truncate 1/4 buf size x times
    for (i = 0; i < 8; i++) {
        size = tffs_write(fdi, (char *) &tdata[TDATA_HUGE][f_size], 
                          fs.chunk_size_max / 2);
        if (size < 0) return size;
        f_size += size;
        size = tffs_seek(fdi, - fs.chunk_size_max / 4, FFS_SEEK_CUR);
        expect(size, f_size - fs.chunk_size_max / 4);

        error = tffs_ftruncate(fdi, f_size - fs.chunk_size_max / 4);
        expect(error, EFFS_OK);
        f_size -= fs.chunk_size_max / 4;
        
        error = tffs_lstat(sname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, f_size);
    }
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     
    
    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(sname, bigbuf, bigbuf_size);
    expect(size, f_size);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, f_size);
    if (error) return 1;

    // backwards trunc 1/2 write 1/4 x times
    tw(tr(TR_FUNC, TrTestLow, "trunc 1/2 buf size, write 1/4 buf size\n"));
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);

    for (i = 0; i < 8; i++) {
        size = tffs_seek(fdi, - fs.chunk_size_max / 2, FFS_SEEK_CUR);
        expect(size, f_size - fs.chunk_size_max / 2);

        error = tffs_ftruncate(fdi, f_size - fs.chunk_size_max / 2);
        expect(error, EFFS_OK);
        f_size -= fs.chunk_size_max / 2;
        
        f_size += tffs_write(fdi, (char *) &tdata[TDATA_HUGE][f_size], 
                             fs.chunk_size_max / 4);
        error = tffs_lstat(sname, &stat);
        expect(error, EFFS_OK);
        expect_eq(stat.size, f_size);
    }
    
    if (p0) { error = test_run("irec"); if (error > 0) return 1; }
    
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     
    
    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(sname, bigbuf, bigbuf_size);
    expect(size, f_size);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, f_size);
    if (error) return 1;

    tw(tr(TR_FUNC, TrTestLow, "Make 2 segments, truncate to zero and append\n"));
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC  
                    | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);
    size = tffs_write(fdi, (char*) tdata[TDATA_HUGE], 2 * fs.chunk_size_max + 10);
    expect(size, 2 * fs.chunk_size_max + 10);
    
    // Move fp otherwise it will not be possible to truncate
    error = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(error, 0);

    error = tffs_ftruncate(fdi, 0);
    expect(error, EFFS_OK);
    
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 150);
    expect(size, 150 );

    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    if (p0) { error = test_run("irec;brec"); if (error > 0) return 1; }

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    f_size = tffs_fread(sname, bigbuf, bigbuf_size);
    expect(f_size, size);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, f_size);
    if (error) return 1;
    
    // Truncate through a symlink
    error = tffs_symlink("/str", sname); 
    expect(error, EFFS_OK);
    error = tffs_truncate("/str", 100);
    expect(error, EFFS_OK);
    f_size = tffs_fread(sname, bigbuf, bigbuf_size);
    expect(f_size, 100);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, f_size);
    if (error) return 1;
    tffs_remove("/str");

    // truncate a file which only have data in the stream buffer
    fdi = tffs_open(sname, FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC  
                    | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);

    f_size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], 3);
    expect(f_size, 3);

    // Move fp otherwise it will not be possible to truncate
    error = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(error, 0);
    
    tw(tr(TR_FUNC, TrTestLow, "Truncate in buffer\n"));
    for (i = 0; i < 4; i++) {
        error = tffs_ftruncate(fdi, --f_size);
        if (f_size >= 0) { 
            expect(error, EFFS_OK);
        
            error = tffs_lstat(sname, &stat);
            expect(error, EFFS_OK);
            expect_eq(stat.size, f_size); 
        }
        else {
            expect(error, EFFS_INVALID);
        }
    }
    error = tffs_close(fdi);
    expect(error, EFFS_OK);     

    // Try to truncate a read-only file
    error = tffs_fwrite(imeifile, TDATA(0));
    // The file is maybe already created!
    error = tffs_fcontrol(imeifile, OC_FLAGS, OF_READONLY);
    error = tffs_truncate(imeifile, 0);
    expect(error, EFFS_ACCESS);
        
    // Use ffs_truncate() on a file open in read mode
    fdi = tffs_open(fname, FFS_O_RDONLY);
    error = tffs_truncate(fname, 0);
    expect(error, EFFS_LOCKED);
    error = tffs_ftruncate(fdi, 0);
    expect(error, EFFS_INVALID);
    error = tffs_close(fdi);
    expect_ok(error);

    return 0;
}


int case_append(int p0, int p1)
{
    fd_t fdi;
    int size, fsize;
    char sname[] = "/stream/append1";
    char fname[] = "/stream/append2";
    int offset = 0; 
  
    if (param.data_blocks <= 1) {
        ttw(ttr(TTrTest, "WARNING: Too few blocks to run. Skip test" NL));
        tw(tr(TR_FUNC, TrTest, "WARNING: Too few blocks to run. Skip test\n"));
        return 0;
    }
 
    case_cleanup(fs.chunk_size_max * 5);
 
    tffs_mkdir("/stream");
 
    fdi = tffs_open(sname, FFS_O_RDWR | FFS_O_APPEND | FFS_O_TRUNC | 
                    FFS_O_CREATE);
    expect(fdi, FFS_FD_OFFSET);    

    // Write data in buf and seek in the buf
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE], fs.chunk_size_max / 2);
    expect_gt(size, 0);

    offset = ffs_seek(fdi, -fs.chunk_size_max / 4, FFS_SEEK_CUR);
    expect_gt(offset, 0);
    expect(1, fs.fd[fdi - FFS_FD_OFFSET].dirty); 

    // Append data and seek in the buffer ad the end of the last chunk
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + 
                      fs.fd[fdi - FFS_FD_OFFSET].size, fs.chunk_size_max);
    expect_gt(size, 0);
    
    offset = ffs_seek(fdi, -fs.chunk_size_max / 4, FFS_SEEK_END);
    expect_gt(offset, 0);

    size = tffs_read(fdi, bigbuf, fs.chunk_size_max / 4);
    expect(size, fs.chunk_size_max / 4);
    expect(1, fs.fd[fdi - FFS_FD_OFFSET].dirty);

    // Append data and seek away from buf 
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + 
                      fs.fd[fdi - FFS_FD_OFFSET].size, fs.chunk_size_max * 1.5);
    expect_gt(size, 0);
    
    offset = ffs_seek(fdi, -fs.chunk_size_max * 2, FFS_SEEK_END);
    expect_gt(offset, 0);

    size = tffs_read(fdi, bigbuf, fs.chunk_size_max / 4);
    expect(size, fs.chunk_size_max / 4);

    // Append again and validate the data
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + 
                      fs.fd[fdi - FFS_FD_OFFSET].size, fs.chunk_size_max / 2);
    expect_gt(size, 0);

    offset = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(0, offset);

    size = tffs_read(fdi, bigbuf, fs.fd[fdi - FFS_FD_OFFSET].size);
    expect_gt(size, 0);

    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;

    error = tffs_close(fdi);
    expect_ok(error);

    // Append on a file with data in the seghead
    fsize = fs.chunk_size_max / 4;
    error = tffs_file_write(fname, (char *)tdata[TDATA_HUGE], fsize, 
                            FFS_O_TRUNC | FFS_O_WRONLY | FFS_O_CREATE);
    expect(error, EFFS_OK);
    
    fdi = tffs_open(fname, FFS_O_RDWR | FFS_O_APPEND);
    expect(fdi, FFS_FD_OFFSET);    

    // Write data in buf and seek in the buf
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + fsize, 
                      fs.chunk_size_max / 2);
    expect_gt(size, 0);
    fsize += size;

    offset = ffs_seek(fdi, 0, FFS_SEEK_SET);
    expect(offset, 0);
    expect(1, fs.fd[fdi - FFS_FD_OFFSET].dirty); 

    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + fsize, 
                      fs.chunk_size_max / 2);
    expect_gt(size, 0);
    fsize += size;

    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, fsize);
    if (error) return 1;

    error = tffs_close(fdi);
    expect_ok(error);

    return 0;
}

int case_datasync(int p0, int p1)
{
    fd_t fdi;
    int size, fsize, offset, i;
    char myname[] = "/datasync"; 
   
    error = case_cleanup(4 * fs.chunk_size_max);
    expect_ok(error);

    fdi = tffs_open(myname, FFS_O_RDWR | FFS_O_TRUNC | FFS_O_CREATE);
    expect(fdi, FFS_FD_OFFSET);    

    fsize = 0;
    for (i = 0; i < 10; i++) {
        // Write data in buf and use datasync
        size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + fsize, 
                          fs.chunk_size_max / 3);
        expect_gt(size, 0);
        fsize += size; 

        error = tffs_fdatasync(fdi);
        expect(error, EFFS_OK);
        expect(0, fs.fd[fdi - FFS_FD_OFFSET].dirty);
    }

    error = tffs_fdatasync(fdi + 1);
    expect(error, EFFS_BADFD);
  
    // Seek to chunk 2, write data, seek, use fdatasync
    offset = tffs_seek(fdi, fs.chunk_size_max * 2.5, FFS_SEEK_SET);
    expect_gt(offset, 0);
   
    size = tffs_write(fdi, (char *) tdata[TDATA_HUGE] + offset, 
                      fs.chunk_size_max / 4);
    expect_gt(size, 0);
    
    offset = tffs_seek(fdi, -fs.chunk_size_max / 2, FFS_SEEK_CUR);
    expect_gt(offset, 0);

    error = tffs_fdatasync(fdi);
    expect(error, EFFS_OK);
    expect(0, fs.fd[fdi - FFS_FD_OFFSET].dirty);

    error = tffs_close(fdi);
    expect_ok(error);

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread(myname, bigbuf, fsize);
    tw(tr(TR_FUNC, TrTestLow, "size: %d fsize: %d \n", size, fsize));    
    expect(size, fsize);
    error = test_expect_data((char *)tdata[TDATA_HUGE], bigbuf, size);
    if (error) return 1;

    return 0;
}

int case_fw_flags(int p0, int p1)
{       
    int i;
    char myname[] = "/file_write";

    tffs_remove(myname);

    tw(tr(TR_FUNC, TrTestHigh, "Test ffs_file_write() flags, file non-exist\n"));
    for (i = 0; i < 0xFE; i++) {
        error = tffs_file_write(myname, 0,0, i);

        if (is_open_option(i, FFS_O_CREATE)) {
            expect_ok(error);
            error = tffs_remove(myname);
            expect_ok(error);
        }
        else 
            expect(EFFS_NOTFOUND, error);
    }

    tw(tr(TR_FUNC, TrTestHigh, "Test ffs_file_write() flags, file exist\n"));
    error = tffs_file_write(myname, 0,0, FFS_O_CREATE);
    expect_ok(error);

    for (i = 0; i < 0xFF; i++) {
        error = tffs_file_write(myname, 0,0, i);

        if (is_open_option(i, FFS_O_EXCL) && is_open_option(i, FFS_O_CREATE)) {
            expect(error, EFFS_EXISTS);
        }

        else 
            expect_ok(error);
    }

    return 0;    
}


drv_Return_Type pcm_init(void);

int case_pcm(int p0, int p1)
{       
    UBYTE version;
    UBYTE imei[] = "12345678";
    int size;

    ffs_mkdir("/pcm");

// Write imei file to ffs. Run init (this reads the imei file), remove
// the file from flash, write the imei to flash from pcm and validate
// data.
    error = ffs_file_write("/pcm/IMEI", &imei, sizeof(imei), FFS_O_CREATE);
    expect_ok(error);

    error = pcm_init();
    expect(PCM_INITIALIZED, error);

    error = pcm_ReadFile((UBYTE *) EF_IMEI_ID, SIZE_EF_IMEI, imei, &version); 
    expect(PCM_OK, error);

    error = ffs_remove("/pcm/IMEI");
    expect_ok(error);

    error =  pcm_WriteFile((UBYTE *) EF_IMEI_ID, SIZE_EF_IMEI, imei);
    expect(PCM_OK, error);

    tw(tr(TR_FUNC, TrTestLow, "Validate data \n"));    
    size = tffs_fread("/pcm/IMEI", bigbuf, bigbuf_size);
    expect(size, SIZE_EF_IMEI);
    error = test_expect_data(imei, bigbuf, size);
    if (error) return 1;

// TODO: test pcm_WriteRecord and pcm_ReadRecord.
    return 0;    
}


int case_api_exceptions(int p0, int p1)
{       
    int i;
    T_FFS_DIR dir;
    T_FFS_FD myfdi;
    char myname[] = "/BAD";
    char mydir[] = "/tdir";
    char mylink[] = "/tlink";
    char mytfile[] = "/tfile";
    char buf[] = "foobar";

    // Create test file and dir
    error = tffs_file_write(mytfile, buf, sizeof(buf), FFS_O_CREATE);
    expect_ok(error);
 
    error = tffs_mkdir(mydir);
    if (error < 0 && error != EFFS_EXISTS) return 1;

    error = tffs_symlink(mytfile, mylink);
    if (error < 0 && error != EFFS_EXISTS) return 1;

    error = tffs_opendir(mydir, &dir);
    expect_ok(error);

    myfdi = tffs_open("/tsream", FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC);
    expect(myfdi, FFS_FD_OFFSET);

    expect(tffs_write(myfdi, buf, sizeof(buf)), sizeof(buf));

    // Test all API functions for pathname with null pointers.  
    for (i = 0; i < 17; i++) {
        switch (i) {
        case 0: error = tffs_open(0, FFS_O_WRONLY | FFS_O_CREATE); break;
        case 1: error = tffs_truncate(0, 1);    break;
        case 2: error = tffs_file_write(0, buf, sizeof(buf), FFS_O_CREATE); break;
        case 3: error = tffs_stat(0, &stat);    break;
        case 4: error = tffs_lstat(0, &stat);   break;
        case 5: error = tffs_xlstat(0, &xstat); break;
        case 6: error = tffs_remove(0);         break;
        case 7: error = tffs_mkdir(0);          break;
        case 8: error = tffs_opendir(0, &dir);  break;
        case 9: error = tffs_symlink(0, myname); break;
        case 10: error = tffs_symlink("/link", 0); break;
        case 11: error = tffs_readlink(0, buf, sizeof(buf)); break;
        case 12: error = tffs_rename(0, "/test"); break;
        case 13: error = tffs_rename(mytfile, 0); break;
        case 14: error = tffs_file_write(0, buf, sizeof(buf), FFS_O_CREATE); break;
        case 15: error = tffs_file_read(0, buf, sizeof(buf)); break;
        case 16: error = tffs_fcontrol(0, OC_FLAGS, OF_READONLY); break;
        default: error = EFFS_INVALID;
        }
        expect(error, EFFS_BADNAME);
    }

    // Test all API functions with buffer null pointers.  
    for (i = 0; i < 13; i++) {
        switch (i) {
        case 0: error = tffs_file_write(myname, 0, 100, FFS_O_CREATE); break;
        case 1: error = tffs_file_read(mytfile, 0, 40); break;
        case 2: error = tffs_opendir(mydir, 0); break;
        case 3: error = ffs_readdir (0, buf, sizeof(buf)); break;
        case 4: error = ffs_readdir (&dir, 0, 100); break;
        case 5: error = tffs_readlink(mylink, 0, 1); break;
        case 6: error = tffs_stat(mytfile, 0); break;
        case 7: error = tffs_lstat(mylink, 0); break;
        case 8: error = tffs_xlstat(mylink, 0); break;
        case 9: error = tffs_query(Q_BYTES_FREE, 0); break;
        case 10: error = tffs_fstat(myfdi, 0); break;
        case 11: error = tffs_write(myfdi, 0, 10); break;
        case 12: error = tffs_read(myfdi, 0, 40); break;
        default: error = 0;
        }
        expect(error, EFFS_INVALID);
    }
    expect_ok(tffs_close(myfdi));

    // Make sure we still can create empty files!
    error = tffs_file_write("/empty_file", 0, 0, FFS_O_CREATE);
    expect_ok(error);

    // Open file for test of negative size
    myfdi = tffs_open(myname, FFS_O_RDWR | FFS_O_CREATE | 
                    FFS_O_TRUNC | FFS_O_APPEND);
    expect(myfdi, FFS_FD_OFFSET);

    // Test all API 'size' parameters with a negative size
    for (i = 0; i < 11; i++) {
        switch (i) {
            // Try modify
        case 0: error = tffs_write(myfdi, buf, -1); break;
        case 1: error = tffs_truncate(myname, -1); break;
        case 2: error = tffs_fwrite(myname, buf, -2); break;
        case 3: error = tffs_fcreate(myname, buf, -3); break;
        case 4: error = tffs_fupdate(myname, buf, -56); break;
        case 5: error = tffs_file_write(myname, buf, -100, FFS_O_CREATE); break;
        // Try read
        case 6: error = tffs_fread(mytfile, bigbuf, -1); break;
        case 7: error = tffs_file_read(mytfile, bigbuf, -2); break;
        case 8: error = tffs_readdir(&dir, bigbuf, -3); break;
        case 9: error = tffs_readlink(mytfile, bigbuf, -4); break;
        case 10: error = tffs_read(myfdi, bigbuf, -5); break;
        default: error = 0;
        }
        expect(error, EFFS_INVALID);
    }

    error = tffs_close(myfdi);
    expect_ok(error);

    return EFFS_OK;
}

int case_api_notformated(int p0, int p1)
{
    int i;
    struct dir_s dir;
    fd_t fdi;
    char buf[20];
    uint16 q_out;
    struct xstat_s xstat;

    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);

    for(i=1; i<24; i++)
    {
        switch(i)
        {
        case 1:  error = tffs_open("apinf", FFS_O_WRONLY);          break;
        case 2:  error = tffs_close(fdi);                         break;
        case 3:  error = tffs_write(fdi, "abcdefghij", 10);       break;
        case 4:  error = tffs_read(fdi, buf, 2);                  break;
        case 5:  error = tffs_seek(fdi, 0, FFS_SEEK_SET);         break;
        case 6:  error = tffs_truncate("apinf", 0);                 break;
        case 7:  error = tffs_fdatasync(fdi);                     break;
        case 8:  error = tffs_stat("apinf", &stat);                 break;
        case 9:  error = tffs_fstat(fdi, &stat);                  break;
        case 10: error = tffs_lstat("apinf", &stat);                break;
        case 11: error = tffs_xlstat("apinf", &xstat);              break;
        case 12: error = ffs_remove("/apinf");                      break;
        case 13: error = ffs_mkdir("/apinf");                       break;
        case 14: error = ffs_opendir("/apinf", &dir);               break;
        case 15: error = tffs_readdir(&dir, "/ffs", 21);          break;
        case 16: error = tffs_symlink("/europe/imie", "imei");    break;
        case 17: error = ffs_rename("/Pluto", "/Dog");            break;
        case 18: error = ffs_file_write("/non-exist", "1234567890", 10, FFS_O_TRUNC);          break;
        case 19: error = tffs_file_read("/stream1", buf, 10);     break;
        case 20: error = tffs_fcreate("/", "apinf", 3);             break;
        case 21: error = tffs_fupdate("/", "bar", 3);             break;
        case 22: error = tffs_fwrite("/europe/imie","0123456789", 10);          break;
        case 23: error = tffs_fcontrol("/apinf", OC_FLAGS, OF_READONLY);          break;
      }

      expect(error, EFFS_NOFORMAT);

    }
    /* query must return information even if the device isnt formated */
    error = tffs_query(Q_FS_INODES, (uint16 *) &q_out);
    expect(error, EFFS_OK);

    return 0;
}

int case_query(int p0, int p1)
{
    int i;
    uint32 ret_val32;
    uint16 ret_val16;

    for(i=1; i<27; i++)
    {
      switch(i)
      {
        case 1:  error = ffs_query(Q_TM_BUFADDR, (uint32 *)&ret_val32); break;
        case 2:  error = ffs_query(Q_TM_BUFSIZE, (uint32 *)&ret_val32); break;
        case 3:  error = ffs_query(Q_DEV_BASE, (uint32 *)&ret_val32); break;
        case 4:  error = ffs_query(Q_FFS_API_VERSION, (uint16 *)&ret_val16); break;
        case 5:  error = ffs_query(Q_FFS_DRV_VERSION, (uint16 *)&ret_val16); break;
        case 6:  error = ffs_query(Q_FFS_REVISION, (uint16 *)&ret_val16); break;
        case 7:  error = ffs_query(Q_FFS_FORMAT_WRITE, (uint16 *)&ret_val16); break;
        case 8:  error = ffs_query(Q_FFS_FORMAT_READ, (uint16 *)&ret_val16); break;
        case 9:  error = ffs_query(Q_FFS_LASTERROR, (uint16 *)&ret_val16); break;
        case 10:  error = ffs_query(Q_FFS_TM_VERSION, (uint16 *)&ret_val16); break;
        case 11:  error = ffs_query(Q_OBJECTS_MAX, (uint16 *)&ret_val16); break;
        case 12:  error = ffs_query(Q_CHUNK_SIZE_MAX, (uint16 *)&ret_val16); break;
        case 13:  error = ffs_query(Q_FD_BUF_SIZE, (uint32 *)&ret_val32); break;
        case 14:  error = ffs_query(Q_FD_MAX, (uint16 *)&ret_val16); break;
        case 15:  error = ffs_query(Q_DEV_MANUFACTURER, (uint16 *)&ret_val16); break;
        case 16:  error = ffs_query(Q_DEV_DEVICE, (uint16 *)&ret_val16); break;
        case 17:  error = ffs_query(Q_DEV_DRIVER, (uint16 *)&ret_val16); break;
        case 18:  error = ffs_query(Q_LOST_HIGH, (uint16 *)&ret_val16); break;
        case 19:  error = ffs_query(Q_FS_FLAGS, (uint16 *)&ret_val16); break;
        case 20:  error = ffs_query(Q_FS_ROOT, (uint16 *)&ret_val16); break;
        case 21:  error = ffs_query(Q_STATS_DRECLAIMS, (uint32 *)&ret_val32); break;
        case 22:  error = ffs_query(Q_STATS_IRECLAIMS, (uint32 *)&ret_val32); break;
        case 23:  error = ffs_query(Q_STATS_DATA_RECLAIMED, (uint32 *)&ret_val32); break;
        case 24:  error = ffs_query(Q_STATS_INODES_RECLAIMED, (uint32 *)&ret_val32); break;
        case 25:  error = ffs_query(Q_STATS_DATA_ALLOCATED, (uint32 *)&ret_val32); break;
        case 26:  error = ffs_query(Q_REQUEST_ID_LAST, (uint32 *)&ret_val32); break;
      }

      expect(error, EFFS_OK);

    }

    return 0;
}

extern uint16 ffs_flash_device;
extern uint16 ffs_flash_manufact;

int case_octrl(int p0, int p1)
{
    int org_fsflags = fs.flags, org_manufact = ffs_flash_manufact;
    int org_device = ffs_flash_device, org_testf = fs.testflags;

    error = object_control(0, OC_FS_FLAGS, 0x02);
    expect(error, EFFS_OK);
    error = object_control(0, OC_DEV_MANUFACT, 'T');
    expect(error, EFFS_OK);
    error = object_control(0, OC_DEV_DEVICE, 0x0F12);
    expect(error, EFFS_OK);
    error = object_control(0, OC_DEBUG_0, 0xAFFE);
    expect(error, EFFS_OK);

    error = object_control(0, OC_DEBUG_LAST+1, 0);
    expect(error, EFFS_INVALID);

    // Restore the values or else it mess up the other tests
    fs.flags = org_fsflags;
    fs.testflags = org_testf;
    ffs_flash_manufact = org_manufact;
    ffs_flash_device = org_device;

    return 0;
}

/***************************************************************************/
/*          Examples from RIV111                                           */
/***************************************************************************/
#if (TARGET == 0)
int case_examples(int p0, int p1)
{
    return 0;
}
#else

static req_id_t my_request_id;
static int removed_greeting;

static void my_callback(T_FFS_FILE_CNF *confirm)
{
    ttw(ttr(TTrTest, "my_callback(%d, %s, %d)" NL, 
            confirm->error, confirm->path, confirm->request_id));
    
    if (my_request_id !=  confirm->request_id) {
        ttw(ttr(TTrFatal, "error (%d, %d)" NL, confirm->request_id, my_request_id));
        return;
    }

    if (confirm->error < 0) {
        ttw(ttr(TTrTest, "FATAL: my_callback error: %d" NL, confirm->error));
    }
    else {
        ttw(ttr(TTrTest, "Remove 'greeting'" NL));
        ffs_remove_nb("/greeting", 0);
        removed_greeting = 1;
    }

    error = rvf_free_buf(confirm);
    if (error < 0)
        ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error);
}

// Using Function Callback
T_RV_RETURN my_fc_cnfpath = {0, (CALLBACK_FUNC)my_callback};

// Using Mail Callback
#define MY_TASK_ID (MAX_RVF_TASKS - 1 - 1)  // same as FFS_TEST_TASK_ID
T_RV_RETURN my_mail_cnfpath = {MY_TASK_ID, 0};

int case_examples(int p0, int p1)
{
    if(1) {
        // MMI Configuration scenario
        struct {
            int volume;
            uint32 ringtype;
        } mmi;
    
        int temp;
        ttw(ttr(TTrTest, "Run MMI conf." NL));
        mmi.volume = temp = 3;
        mmi.ringtype = 1111;
        ffs_mkdir("/mmi");
        ffs_fwrite("/mmi/volume", &mmi.volume, sizeof(mmi.volume));
        error = ffs_file_read("/mmi/volume", &mmi.volume, sizeof(mmi.volume));
        if (error < 0)
            return 1;    
        error = test_expect_data(&temp, &mmi.volume, sizeof(mmi.volume));
        if (error < 0)
            return 1;
    }

    if(1) {
        // Using Mail Callback
        T_FFS_FILE_CNF *confirm_file;
        T_FFS_STREAM_CNF *confirm_stream;
        T_FFS_RET error;
        T_FFS_FD fdi;
        char hello[] = "Hello, world";
        T_FFS_SIZE size;

        ttw(ttr(TTrTest, "Use Mail Callback" NL));
        if ((my_request_id = ffs_open_nb("/mmi/sound", 
                                         FFS_O_WRONLY | FFS_O_CREATE | 
                                         FFS_O_APPEND | FFS_O_TRUNC, 
                                         &my_mail_cnfpath)) < 0)
            return my_request_id;
        
        ttw(ttr(TTrTest, "Wait..." NL));
        rvf_wait(RVF_TASK_MBOX_0_EVT_MASK, 0);

        ttw(ttr(TTrTest, "Get mail" NL));
        confirm_file = (T_FFS_FILE_CNF *) rvf_read_mbox(RVF_TASK_MBOX_0);

        fdi = confirm_file->error;
        expect(FFS_FD_OFFSET, fdi);
        expect(my_request_id, confirm_file->request_id);
        expect(FFS_MESSAGE_OFFSET, confirm_file->header.msg_id);
        ttw(ttr(TTrTest, "Path '%s'" NL, confirm_file->path));

        error = rvf_free_buf(confirm_file);
        if (error < 0)
            ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error);

        if ((my_request_id = ffs_write_nb(fdi, hello, strlen(hello) ,
                                          &my_mail_cnfpath)) < 0)
            return my_request_id;

        ttw(ttr(TTrTest, "Wait..." NL));
        rvf_wait(RVF_TASK_MBOX_0_EVT_MASK, 0);

        ttw(ttr(TTrTest, "Get mail" NL));
        confirm_stream = (T_FFS_STREAM_CNF *) rvf_read_mbox(RVF_TASK_MBOX_0);
        size = confirm_stream->error;
        expect(strlen(hello), size);
        expect(my_request_id, confirm_stream->request_id);
        ttw(ttr(TTrTest, "fdi: %d" NL, confirm_stream->fdi));
        error = rvf_free_buf(confirm_stream);
        if (error < 0)
            ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error);
        
        ttw(ttr(TTrTest, "Close file" NL));
        if ((error = ffs_close(fdi)) < 0)
            return error;
    }

    if(1) {
        // Using Function Callback
        char hello[] = "Hello, world";
        T_FFS_STAT stat;

        ttw(ttr(TTrTest, "Use Function Callback" NL));

        if ((my_request_id = ffs_fcreate_nb("/greeting", hello, strlen(hello), 
                                            &my_fc_cnfpath)) < 0)
            return my_request_id;

// Wait until my_callback() has removed the file "greeting"
        removed_greeting = 0;
        while (removed_greeting == 0)
            tffs_delay(10);
// NOTE: This is importent because if the test brec runs immediately
// after his one will the brec test fail because the "greeting" file
// is removed inside the brec test which don't expect that files is
// removed.
    }

    return 0;
}
#endif
/******************************************************************************
 * Non blocking test ffs_xx_nb()
 ******************************************************************************/

#if (TARGET == 0)
int case_nonblock(int p0, int p1)
{
    tw(tr(TR_FUNC, TrTest, 
          "WARNING: This test can only run in target. Skip test\n"));
    return 0;
}

#else
extern int request_id_last;  // from task.c

#define WAIT_GET_TEST      ttw(ttr(TTrTest, "Wait..." NL)); rvf_wait(RVF_TASK_MBOX_0_EVT_MASK, 0); ttw(ttr(TTrTest, "Get mail" NL)); confirm_file = (T_FFS_FILE_CNF *) rvf_read_mbox(RVF_TASK_MBOX_0); ttw(ttr(TTrTest, "Path '%s'" NL, confirm_file->path)); expect(FFS_MESSAGE_OFFSET, confirm_file->header.msg_id); expect(my_id, confirm_file->request_id); 

#define WAIT_GET_TEST_FDI  ttw(ttr(TTrTest, "Wait..." NL)); rvf_wait(RVF_TASK_MBOX_0_EVT_MASK, 0); confirm_stream = (T_FFS_STREAM_CNF *) rvf_read_mbox(RVF_TASK_MBOX_0); ttw(ttr(TTrTest, "Get mail" NL)); ttw(ttr(TTrTest, "fdi: %d" NL, confirm_stream->fdi)); expect(FFS_MESSAGE_OFFSET, confirm_stream->header.msg_id); expect(FFS_FD_OFFSET, confirm_stream->fdi); expect(my_id, confirm_stream->request_id);

#define FREE error = rvf_free_buf(confirm_file); if (error < 0) { ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error); return 1;}

#define FREE_FDI error = rvf_free_buf(confirm_stream); if (error < 0) { ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error); return 1;}

int case_nonblock(int p0, int p1)
{
    T_FFS_FILE_CNF *confirm_file;
    T_FFS_STREAM_CNF *confirm_stream;
    T_FFS_RET error;
    T_FFS_FD fdi;
    char hello[] = "Hello, world";
    char myfname[] = "/non_block/file";
    char mysname[] = "/non_block/stream";
    T_FFS_SIZE size;
    int i;
    T_FFS_REQ_ID my_id;
    // mail confirm
    T_RV_RETURN cnfpath = {MY_TASK_ID, 0};

    // Test wrap arround in request_id_get(). Set id to 5 from max, the following
    // function calls will trigger the wrap arround
    request_id_last = 0x7FFFFFFF - 5;
    ttw(ttr(TTrTest, "request :%d" NL, request_id_last));

    // preformat, format, mkdir 
    // open, write, ftruncate, close, remove,
    // file_write, symlink, rename, truncate, fcontrol

    my_id = ffs_preformat_nb(0xDEAD, &cnfpath);
    expect_gt(my_id, -1);
    WAIT_GET_TEST; FREE;

    my_id = ffs_format_nb("/ffs", 0x2BAD, &cnfpath);
    expect_gt(my_id, -1);
    WAIT_GET_TEST; FREE;

    my_id = ffs_mkdir_nb("/non_block", &cnfpath);
    expect_gt(my_id, -1);
    WAIT_GET_TEST; FREE;

    if ((my_id = ffs_open_nb(mysname, FFS_O_WRONLY | FFS_O_CREATE,  
                             &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST;   
    fdi = confirm_file->error;
    expect(FFS_FD_OFFSET, fdi);
    FREE;
    
    if ((my_id = ffs_write_nb(fdi, hello, strlen(hello), &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST_FDI; FREE_FDI;

    expect(1, fs.fd[fdi - FFS_FD_OFFSET].dirty);
    if ((my_id = ffs_fdatasync_nb(fdi, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST_FDI; FREE_FDI;
    expect(0, fs.fd[fdi - FFS_FD_OFFSET].dirty);

    if ((my_id = ffs_seek_nb(fdi, 2, FFS_SEEK_SET, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST_FDI; FREE_FDI;
    expect(2, fs.fd[fdi - FFS_FD_OFFSET].fp);

    if ((my_id = ffs_ftruncate_nb(fdi, 3, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST_FDI; 
    expect(EFFS_OK, confirm_stream->error);
    FREE_FDI;

    if ((my_id = ffs_close_nb(fdi, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST_FDI; 
    expect(EFFS_OK, confirm_stream->error);
    FREE_FDI;

    if ((my_id = ffs_remove_nb(mysname, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    if ((my_id = ffs_file_write_nb(myfname, hello, strlen(hello), 
                                   FFS_O_WRONLY | FFS_O_CREATE, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    if ((my_id = ffs_symlink_nb("/nb_file", myfname, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    if ((my_id = ffs_rename_nb("/nb_file", "/nbf", &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    if ((my_id = ffs_truncate_nb(myfname, 0, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    if ((my_id = ffs_fcontrol_nb(myfname, OC_FLAGS, OF_READONLY, &cnfpath)) < 0)
        return my_id;
    WAIT_GET_TEST; 
    expect(EFFS_OK, confirm_file->error);
    FREE;

    ttw(ttr(TTrTest, "request :%d" NL, request_id_last));

    // This should make a EFFS_MEMORY fail
    for(i = 0; i < 10000; i++) {
        my_id = ffs_fwrite_nb("/mem_test", TDATA(0), 0);
        if (my_id < 0)
            break;
    }
    ttw(ttr(TTrTest, "write nr: %d my_id: %d" NL, i, my_id));
    expect(my_id, EFFS_MEMORY);
    // All memory has been used so wait a while (let some of the writes finish)
    tffs_delay(50);
    // Make a call to a blocking function will make it possible for ffs to
    // finish all the non blocking writes.
    error = ffs_fwrite("/mem_test", TDATA(0));
    expect_ok(error);

    return 0;
}
#endif

/******************************************************************************
 * Blocking test 
 ******************************************************************************/

#if (TARGET == 0)
int case_blocking(int p0, int p1)
{
    tw(tr(TR_FUNC, TrTest, 
          "WARNING: This test can only run in target. Skip test\n"));
    return 0;
}
#else

#include "ffs/board/task.h"
void my_mb_callback(T_RVF_MB_ID id)
{ // Dummy callback function 
}


// Test that a FFS API blocking call that not is able to send a mail to FFS fails as expected and don't lock the system (it must unlock and delete the mutex). 
int case_blocking(int p0, int p1)
{       
    UINT32 mem_unused;
    T_RVF_MB_STATUS status;
    char *buf;
    extern T_OS_MB_ID ffs_mb_id;

    // Later when all the memory is used the memory bank will be marked as
    // RED by Riviera. The RED flag is erased when the buffer is freed but
    // only if a callback function is applied.
    rvf_set_callback_func(ffs_mb_id, my_mb_callback);  // Ignore error

    mem_unused = rvf_get_mb_unused_mem(ffs_mb_id);
    ttw(ttr(TTrTest,"Unused memory: %d" NL, mem_unused));

    // When all the memory is used the blocking functions will not be able
    // to allocate memory to the FFS mail thus it must fail. After the
    // buffer has been freed the block call must again be fully functional.
    if ((buf = (char *) target_malloc(mem_unused)) == 0)
        return EFFS_MEMORY;
 
    error = tffs_fwrite("/blocking_test", (char*)tdata[TDATA_HUGE], 20);

    target_free(buf);

    expect(error, EFFS_MEMORY);

    error = tffs_fwrite("/blocking_test", (char*)tdata[TDATA_HUGE], 20);
    expect_ok(error);

    return EFFS_OK;
}
#endif

/******************************************************************************
 * Test erase_suspend and erase_resume 
 ******************************************************************************
 * Test erase suspend and resume by making a call to ffs_begin() followed by
 * an ffs_read() while we are erasing. We trigger the erase by sending
 * several write mails to FFS. We let FFS get the CPU for less time than an
 * erase, which will guarantee that we interrupt the erase when we call
 * ffs_begin().
 ******************************************************************************/
#if (TARGET == 1)
static int write_mails_left;

static void driver_callback(T_FFS_FILE_CNF *confirm)
{
    if (confirm->error < 0) {
        ttw(ttr(TTrTest, "FATAL: driver_callback error: %d" NL, confirm->error));
    }

    error = rvf_free_buf(confirm);
    if (error < 0)
        ttr(TTrFatal, "FFS FATAL: os_free() %d" NL, error);

    write_mails_left--;
}
#endif

#if (TARGET == 0) 
int case_erase_suspend(int p0, int p1)
{
    tw(tr(TR_FUNC, TrTestHigh, "Erase_suspend test not supported on PC sim\n")); 
    return 1;
}
#else
// TEST erase_suspend and erase_resume
int case_erase_suspend(int p0, int p1)
{
    int i, j, error, nsuspend;
    char myname[] = "/ffs/b4";      // Run test with only 4 block.
    char myfname[] = "/driver_test";
    int file_size = 1024;

    T_RV_RETURN cnfpath = {0, (CALLBACK_FUNC)driver_callback};

    ttw(ttr(TTrTest, "Erase suspend-resume test" NL));

// To minimize the numbers of required writes before we are guarantied an
// erase we run this test in only 4 blocks
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format(myname, 0x2BAD);
    expect(error, EFFS_OK);

    if (p0 == 0) p0 = 1;
    for (j = 0; j < p0; j++) {
        write_mails_left = 0;
// Write 2 blocks of data (must trigger an erase)
        for (i = 0; i < 2 * (dev.blocksize/file_size); i++)  
        {
// Non blockin write
            if ((error = ffs_file_write_nb(myfname, (char *) tdata[TDATA_HUGE], 
                                           file_size, 
                                           FFS_O_WRONLY | FFS_O_CREATE | FFS_O_TRUNC,
                                           &cnfpath)) < 0)
                return error;
            write_mails_left++;
        }
        ttw(ttr(TTrTest, "Finish write calls(%d)" NL, i));

// NOTE only tested with AMD MB driver
        switch (dev.driver) {
        case FFS_DRIVER_AMD:
        case FFS_DRIVER_SST:
        case FFS_DRIVER_INTEL:
        case FFS_DRIVER_AMD_SB:
            ttw(ttr(TTrTest, "Start read test" NL));
            nsuspend = 0;
            do {
// NOTEME: We can also test if we get the CPU and not is delay
// by the erase

// This will change state to ERASE_SUSPEND if we are in
// ERASE mode (only valid for MB driver)
                error = ffs_begin();
                expect(error, EFFS_OK);

                if (dev.state == DEV_ERASE_SUSPEND) {
                    nsuspend++;
                    // Read... Note it will also resume the erase (MB driver).
                    error = tffs_file_read(myfname, bigbuf, file_size/4);
                    expect(error, EFFS_FILETOOBIG);
                }
                rvf_delay(5);  // Let FFS get the CPU for some time
            } while (write_mails_left > 0);  

            if (nsuspend <= 0) {
                ttw(ttr(TTrTest, "Arg no erase suspend" NL));
                return 1;
            }
            ttw(ttr(TTrTest, "Erase suspended %d times" NL, nsuspend));
            break;

        case FFS_DRIVER_SST_SB:
        case FFS_DRIVER_INTEL_SB:
        default:
            ttw(ttr(TTrTest, "Driver not supported by this test" NL));
            // Do not return before the cnfpath resource not is used anymore
            while (write_mails_left > 0) rvf_delay(100);  
            return 1;
        }
    }
    return 0;
}
#endif // TARGET == 0

/******************************************************************************
 * The 3 below testh_.. function is helper function used for the low level
 * driver tests.
 ******************************************************************************/
#if (TARGET == 1)

uint32 int_disable(void);
void int_enable(uint32 tmp);

// Only start an erase, don't wait until it is finish 
// Note to use this function with SB drivers require a multi bank flash device
int testh_nb_erase(uint8 block)
{
    volatile char *flash = dev.base;
    volatile char *addr;
    uint32 cpsr;

    ttw(ttr(TTrTest, "e(%d)" NL, block));

    addr = block2addr(block);
    cpsr = int_disable();
    switch (dev.driver) {
    case FFS_DRIVER_AMD:
    case FFS_DRIVER_AMD_SB:
    case FFS_DRIVER_AMD_PSEUDO_SB:
        flash[0xAAAA] = 0xAA; // AMD unlock cycle 1
        flash[0x5555] = 0x55; // AMD unlock cycle 2
        flash[0xAAAA] = 0x80; 
        flash[0xAAAA] = 0xAA; // AMD unlock cycle 1
        flash[0x5555] = 0x55; // AMD unlock cycle 2
        *addr         = 0x30; // AMD erase sector command
        int_enable(cpsr);  
        break;
    default:
        return -1; // Not yet supported
    }
    return 0;
}

// Note to use this function with SB drivers require a multi bank flash device
int testh_erase_suspend(uint8 block)
{
    volatile char *flash = dev.base;
    volatile char *addr;

    ttw(ttr(TTrTest, "esusp(%d)" NL, block));

    addr = block2addr(block);
    switch (dev.driver) {
    case FFS_DRIVER_AMD:
    case FFS_DRIVER_AMD_SB:
    case FFS_DRIVER_AMD_PSEUDO_SB:
        *addr         = 0xB0; // AMD erase suspend command
        break;
    default:
        return -1; // Not yet supported
    }
    return 0;
}

int testh_get_free_block(void)
{
    uint8 b;

    for (b = 0; b < dev.numblocks; b++) 
    {
        if (is_block(b, BF_IS_FREE) || is_block(b, BF_IS_EMPTY))
            return b;
    }
    return -1;
}
#endif // TARGET == 1

// NOTEME: The below test have not been used in this FFS version
// NOTEME: We don't have an AMD init function 
/******************************************************************************
 * Test drv.init (low level driver test)
 ******************************************************************************
 * Test drv.init by start an erase, call drv.init and make sure that the
 * erase is finish after we return from drv.init.
 *
 * Test drv.init by start erase, suspend the erase and make sure that the
 * erase is resumed and finish before we return from drv.init. The test is
 * valid for both AMD SB and MB driver
 ******************************************************************************/

#if (TARGET == 0) 
int case_drv_init(int p0, int p1)
{
    tw(tr(TR_FUNC, TrTestHigh, "drv_init test not supported on PC sim\n")); 
    return 1;
}
#else
int case_drv_init(int p0, int p1)
{
    volatile char *flash = dev.base;
    volatile char *addr;
    char myname[] = "/drv_init";
    uint32 cpsr;
    uint8 block;
    int free_b, i, j, command;
    enum command { ERASE, WRITE, READ };
    char action[6][3] = 
        { 
            { WRITE, READ, ERASE },
            { WRITE, ERASE, READ },
            { READ, WRITE, ERASE },
            { READ, ERASE, WRITE },
            { ERASE, READ, WRITE },
            { ERASE, WRITE, READ }
        };

    ttw(ttr(TTrTest, "Test ffsdrv.init()" NL));

// Test drv.init 6 times there the flash is in erase mode and 6 times
// there the flash is in erase suspend mode.
    for (i = 0; i < 12; i++) 
    {
        free_b = testh_get_free_block();
        expect_ok(free_b);

        fs.debug[0] = 0;
        fs.debug[1] = 0;

// Start erase, run driver init.
        error = testh_nb_erase(free_b);
        expect_ok(error);
        rvf_delay(20); // Make sure that the erase has started

        if (i > 6) {
            error = testh_erase_suspend(free_b);
            expect_ok(error);
            rvf_delay(20); // Make sure that the erase is suspended
        }

        error = ffsdrv.init();  // Call ffsdrv_init() instead? (inc. autodetect)
        expect_ok(error);

        if (i > 6) {
            expect(fs.debug[0], 1);    // Test that the erase was resumed
            expect_gt(fs.debug[1], 0); // Test what we did wait for erase to finish
        }
        else {
            expect(fs.debug[0], 0);    // Erase not resumed
            expect_gt(fs.debug[1], 0); // We did wait for an erase to finish
        }

// To test that any combination of erase, read or write can be
// performed after drv.init do we make the test below. The first
// complete drv.init test will be like this: Start an erase, call
// drv.init(), write a file, read the file and erase a free block.
        for (j = 0; j < 3; j++) {
            if (i < 6)
                command = action[i][j];
            else
                command =  action[i-6][j];
            switch (command) {
            case ERASE: 
                free_b = testh_get_free_block();
                expect_ok(free_b);
                ffsdrv.erase(free_b);
                break;
            case READ: 
                error = test_expect_file(myname, TDATA(2));
                if (error) return 1;
                break;
            case WRITE:
                error = tffs_fwrite(myname, TDATA(2));
                expect(error, EFFS_OK);
                break;
            default:
                return 1;  
            }
        }
    }
    return 0;
}
#endif 

uint32 get_cpsr(void);

// Test disable and enable of interrupt (Note only valid for MB drivers)
int case_interrupt(int p0, int p1)
{       
    extern void int_enable(uint32 tmp);
    extern uint32 int_disable(void);
    uint32 cpsr;
    int i;
    uint32 xcpsr;

    switch (dev.driver) {
    case FFS_DRIVER_AMD:
    case FFS_DRIVER_INTEL:
		break;
	default:
		return 1; // Not supported
	}

    p0 *= 100;
  
    ttw(ttr(TTrTest, "Test interrupt code" NL));

    for (i = 0; i < p0; i++) {
        cpsr = int_disable();
        xcpsr = get_cpsr();
        if ((xcpsr & 0xC0) != 0xC0) {
            ttw(ttr(TTrTest, "FATAL int not disablet!" NL));
            return 1;
        }
        int_enable(cpsr);
        xcpsr = get_cpsr();
        if ((xcpsr & 0xC0) != 0) {
            ttw(ttr(TTrTest, "FATAL int not enablet!" NL));
            return 1;
        }                
        if ((i % 1000) == 0) 
            ttw(ttr(TTrTest, "loop (%d)" NL, i));
    }

    return 0;    
}


/******************************************************************************
 * Benchmarking
 ******************************************************************************/
// Below define only for PC compile
#if (TARGET == 0)
#define UINT32   int
#define tffs_timer_begin() 0 
#define tffs_timer_end(time) 5000 - time 
#endif

const struct results_s old_results[] = 
{
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

// NOTEME: Bencmark_results() not finished.
int bencmark_results(int manufact, int driver, int sample, 
                     struct results_s *meas_res)
{
    switch(manufact) {
    case MANUFACT_AMD:
        switch(driver) {
//        case FFS_DRIVER_AMD: 
        case FFS_DRIVER_AMD_SB:
        default: return 0;
        }
    case MANUFACT_FUJITSU:
        switch(driver) {
//        case FFS_DRIVER_AMD:
        case FFS_DRIVER_AMD_SB:
        default: return 0;
        }
    default: return 0;
    }
}

// Benchmark write and read operation. Measure write throughput as kBps in
// idle and loopback mode. Both for an empty ffs where we don't have to
// reclaim blocks and for a full ffs where block reclaims/erasures affect
// the overall throughput. For SB flash driver as well as for DB driver. For
// files of size:16, 256, 4096, bytes.
int case_bm_stream_rw(int p0, int p1)
{
#if (TARGET == 1)
    UINT32 time_begin, elapsed, bytes_max;
    char myname[] = "/Benchmark0";
    int i, j, k, nm_files, file_size;
    int size, write_size[] = {16, 256, 4096}; 
    fd_t fdi;

    //ttw(ttr(TTrTest, "RVF_POOL_0_SIZE: %d" NL, RVF_POOL_0_SIZE));

    // 1 block is used for inodes, 1 have to be free, 1/4 is used for
    // journal and 1/2 is used as a margin

// FIXME: changed to 4.75 because the SW TCS2.1 dos make some files and
// because of this is there not enough data space left to this test.
// NOTEME: Instead of hardwire this value then query it
    bytes_max = (dev.numblocks - 4.75) * dev.blocksize;
    file_size = bytes_max / dev.numblocks;
    ttw(ttr(TTrTest, "bytes_max: %d(- 2.75 block)" NL, bytes_max));

    // Format, measure write of 16B.
    // Format, measure write of 256B.
    // Format, measure write of 4096B.

    for (j = 0; j < 3; j++) {
        ttw(ttr(TTrTest, "Format, measure write of %dB" NL, write_size[j])); 
       
        error = tffs_preformat(0xDEAD);
        expect(error, EFFS_OK);
        error = tffs_format("/foo", 0x2BAD);
        expect(error, EFFS_OK);
    
        time_begin = tffs_timer_begin();
        for (i = 0; i < dev.numblocks; i++) {
            myname[10] = i + (i <= 9 ? '0': 'A' -10);
   
            fdi = tffs_open(myname, FFS_O_WRONLY | FFS_O_CREATE);
            expect(fdi, FFS_FD_OFFSET);
 
            size = 0;
            do {
                size += error = tffs_write(fdi, (char*)tdata[TDATA_HUGE], 
                                           write_size[j]);
                if (error < 0) {
                    if (error == EFFS_NOSPACE) {
                        ttw(ttr(TTrTest, "Aarg out of space" NL)); 
                        bytes_max = size;
                        break;
                    }
                    else
                        expect_ok(error);
                }
            } while (size < file_size);
         
            error = tffs_close(fdi);
            expect_ok(error);
        }
    
        elapsed = tffs_timer_end(time_begin);
// FFS has used the CPU for a long time so let the trace task get
// the CPU for a while
        tffs_delay(50);  
        ttw(ttr(TTrTest, "Write %dBytes %d times in %dms, %dkBps" NL NL, 
                write_size[j], file_size/write_size[j], elapsed, 
                (bytes_max * 1000) / (elapsed * 1024)));
    }


    ttw(ttr(TTrTest, "Rm all files, measure write of 4kB" NL)); 
    for (i = 0; i < dev.numblocks; i++) {
        myname[10] = i + (i <= 9 ? '0': 'A' -10);
        error = tffs_remove(myname);
        expect_ok(error);
    }

    time_begin = tffs_timer_begin();
    for (i = 0; i < dev.numblocks; i++) {
        myname[10] = i + (i <= 9 ? '0': 'A' -10);
        fdi = tffs_open(myname, FFS_O_WRONLY | FFS_O_CREATE);
        expect(fdi, FFS_FD_OFFSET);
 
        size = 0;
        do {
            size += tffs_write(fdi, (char*)tdata[TDATA_HUGE], write_size[2]);
        } while (size < file_size);
         
        error = tffs_close(fdi);
        expect_ok(error);
    }
    
    elapsed = tffs_timer_end(time_begin);
    tffs_delay(50);  
    ttw(ttr(TTrTest, "Write %dBytes %d times in %dms, %dkBps" NL NL, 
            write_size[2], file_size/write_size[2], elapsed, 
            (bytes_max * 1000) / (elapsed * 1024)));


// Read all files with 16B at a time
    for (j = 0; j < 3; j++) {
        ttw(ttr(TTrTest, "Read all files with %dB at a time" NL, write_size[j])); 
        time_begin = tffs_timer_begin();

        for (i = 0; i < dev.numblocks; i++) {
            myname[10] = i + (i <= 9 ? '0': 'A' -10);
            fdi = tffs_open(myname, FFS_O_RDONLY);
            expect(fdi, FFS_FD_OFFSET);
            size = 0;
            do {
                size += tffs_read(fdi, bigbuf, write_size[j]);
            } while (size < file_size);
        
            error = tffs_close(fdi);
            expect(error, 0);
        }
        elapsed = tffs_timer_end(time_begin);

        ttw(ttr(TTrTest, "Read %d files of %dkB in %dms, %dkBps," NL NL, 
                dev.numblocks, size/1024, elapsed, 
                (dev.numblocks * size * 1000) / (elapsed * 1024))); 
    }


// Measure write and read of one big file
    ttw(ttr(TTrTest, "Format, Write and read one big file" NL)); 

    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);
    
    time_begin = tffs_timer_begin();

    fdi = tffs_open(myname, FFS_O_WRONLY | FFS_O_CREATE);
        
    size = 0;
    do {
        size += tffs_write(fdi, bigbuf, write_size[2]);
    } while (size < bytes_max);
        
    error = tffs_close(fdi);
    expect(error, 0);

    elapsed = tffs_timer_end(time_begin);

    ttw(ttr(TTrTest, "Write %dkB in %dms, %dkBps" NL, 
            size/1024 , elapsed, (size * 1000) / (elapsed * 1024)));


    time_begin = tffs_timer_begin();

// read one big file (reading one big file take more time than reading
// many files of the same total size because there is a lot more inode
// to look through)
    fdi = tffs_open(myname, FFS_O_RDONLY);
    expect(fdi, FFS_FD_OFFSET);
    size = 0;
    do {
        size += tffs_read(fdi, bigbuf, write_size[2]);
    } while (size < bytes_max);
        
    error = tffs_close(fdi);
    expect(error, 0);
        
    elapsed = tffs_timer_end(time_begin);

    ttw(ttr(TTrTest, "Read %dkB in %dms, %dkBps," NL, 
            size/1024, elapsed, (size * 1000) / (elapsed * 1024)));

    // Make a data base of old measurements her? compare with old data and
    // make a warning if the performance has decreased?

    return 0;
#else    
    return 1;
#endif
}

// Benchmark ffs_file_write() and ffs_file_read() 
int case_bm_file_rw(int p0, int p1)
{
    UINT32 time_begin, elapsed;
    char myname[] = "/Benchmark";
    int i, j;
  
    struct test_info_s {
        int fsize;
        int fnumber;
    };
    
    struct test_info_s tinfo[] = {
        {   16, 500  },
        {  256, 200  },
        { 4096, 200  },
        {32768, 10   }
    }; 

    for (j = 0; j < 4; j++) {
        ttw(ttr(TTrTest, "Format, measure write of %dB" NL, tinfo[j].fsize)); 
        
        error = tffs_preformat(0xDEAD);
        expect(error, EFFS_OK);
        error = tffs_format("/foo", 0x2BAD);
        expect(error, EFFS_OK);
    
        time_begin = tffs_timer_begin();
        for (i = 0; i < tinfo[j].fnumber; i++) {
            error = tffs_file_write(myname, (char*)tdata[TDATA_HUGE], 
                                    tinfo[j].fsize, FFS_O_CREATE);
            expect_ok(error);
        }
        elapsed = tffs_timer_end(time_begin);

        // FFS has used the CPU for a long time so let the trace task get
        // the CPU for a while
        tffs_delay(50);  
        ttw(ttr(TTrTest, "Write %dBytes %d times in %dms, %dkBps" NL NL, 
                tinfo[j].fsize, i, elapsed, 
                (i * tinfo[j].fsize * 1000) / (elapsed * 1024)));
        
        time_begin = tffs_timer_begin();
        for (i = 0; i < tinfo[j].fnumber; i++) {
            error = tffs_file_read(myname, bigbuf, tinfo[j].fsize);
            expect_ok(error);
        }
        elapsed = tffs_timer_end(time_begin);

        ttw(ttr(TTrTest, "Read %dBytes %d times in %dms, %dkBps" NL NL, 
                tinfo[j].fsize, i, elapsed, 
                (i * tinfo[j].fsize * 1000) / (elapsed * 1024)));
    }
    tffs_delay(3000);  

    return 0;
}

// We banchmark lookups by using ffs_stat()
int case_bm_lookup(int p0, int p1)
{
    //format, update file x times, measure lookup with ffs_stat()
    //format, make x difference files, measure lookup with ffs_stat()
    //format, make a file in max directory depth, measure lookup with ffs_stat() 
    //format, make one file, measure lookup with ffs_stat() (no traverse)
    //format, make x dirs with x files, measure lookup of last file in last dir 
#if (TARGET == 1)
    UINT32 time_begin, elapsed;
    int i, j;
    char myname[] = "/benchmark";
    char mydir[100] = "";
    char depth[] = "/depth99";
    char mypath[100] = "";

    //format, update file 100 times, measure lookup with ffs_stat()
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);

    for (i = 0; i < 100; i++) {
        error = tffs_file_write(myname, 0,0, FFS_O_CREATE);
        expect_ok(error);
    }
    
    time_begin = tffs_timer_begin();
    for (i = 0; i < 100; i++) {
        error = tffs_stat(myname, &stat);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    ttw(ttr(TTrTest, "Lookup through 100 deleted objects 100 times in %dms(%d obj/s)" NL, elapsed, 100 * 1000 / elapsed));
    
    //format, make 100 difference files, measure lookup with ffs_stat()
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);

    for (i = 0; i < 100; i++) {
        sprintf(myname, "/benchm%d", i);
        error = tffs_file_write(myname, 0,0, FFS_O_CREATE);
        expect_ok(error);
    }
    
    time_begin = tffs_timer_begin();
    for (i = 0; i < 100; i++) {
        error = tffs_stat(myname, &stat);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    ttw(ttr(TTrTest, "Lookup through 100 difference objects 100 times in %dms(%d obj/s)" 
            NL, elapsed, 100 * 1000 / elapsed));
    
    //format, make a file in max directory depth, measure lookup with ffs_stat() 
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);

    for (i = 0; i < fs.path_depth_max - 1; i++) {
        sprintf(depth, "/depth%d", i + 1);
        strcat(mydir, depth);
        error = tffs_mkdir(mydir);
        expect_ok(error);
    }

    strcat(mydir, myname);
    error = tffs_file_write(mydir, 0,0, FFS_O_CREATE);
    expect_ok(error);

    time_begin = tffs_timer_begin();
    for (i = 0; i < 1000; i++) {
        error = tffs_stat(mydir, &stat);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    ttw(ttr(TTrTest, "Lookup througt %d directorys 1000 times in %dms(%d obj/s)" 
            NL, fs.path_depth_max, elapsed, 1000 * 1000 / elapsed));

    //format, make x files in each directory, measure lookup with ffs_stat() 
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);
    
    mydir[0] = 0;  // Reset/empty buffer

    for (i = 0; i < fs.path_depth_max - 1; i++) {
        sprintf(depth, "/depth%d", i + 1);
        strcat(mydir, depth);
        error = tffs_mkdir(mydir);
        expect_ok(error);
        
        for (j = 0; j < 5; j++){
            sprintf(myname, "/benchm%d", j);
            strcpy(mypath, mydir);
            strcat(mypath, myname);
            error = tffs_file_write(mypath, 0,0, FFS_O_CREATE);
            expect_ok(error);
        }
    }

    time_begin = tffs_timer_begin();
    for (i = 0; i < 100; i++) {
        error = tffs_stat(mypath, &stat);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    ttw(ttr(TTrTest, "Lookup througt %d directorys with %d files in each times in %dms(%d obj/s)" 
            NL, fs.path_depth_max, j, elapsed, 100 * 1000 / elapsed));

    //format, make one file, measure lookup with ffs_stat() (no traverse)
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);

    error = tffs_file_write(myname, 0,0, FFS_O_CREATE);
    expect_ok(error);

    time_begin = tffs_timer_begin();
    for (i = 0; i < 10000; i++) {
        error = tffs_stat(myname, &stat);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    ttw(ttr(TTrTest, "Lookup one file 10000 times in %dms(%d obj/s)" 
            NL, elapsed, 1000 * 10000 / elapsed));

    //format, make x dirs with x files, measure lookup of last file in last dir 
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);
    return 0;
#else    
    return 1;
#endif
}

int case_bm_seek(int p0, int p1) 
{
    UINT32 time_begin, elapsed;
    int i, pos, fsize = 20000;
    char myname[] = "/benchmark";
    fd_t fdi;

    //format, update file 100 times, measure lookup with ffs_stat()
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    error = tffs_format("/foo", 0x2BAD);
    expect(error, EFFS_OK);
    
    // Create test file
    error = tffs_file_write(myname, (char*)tdata[TDATA_HUGE], fsize, FFS_O_CREATE);
    expect_ok(error);
    
    fdi = tffs_open(myname, FFS_O_RDWR);
    expect(fdi, FFS_FD_OFFSET);
    
    tw(tr(TR_FUNC, TrTest, "Run read reference\n"));
    ttw(ttr(TTrTest, "Run read reference" NL));
    
    // Reference
    time_begin = tffs_timer_begin();
    for (i = 0; i < fsize/4; i++) {
        error = tffs_read(fdi, bigbuf, 4);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);
    
    tw(tr(TR_FUNC, TrTest, "Read %d entries of 4 bytes (bytes/s %d)\n", 
          fsize/4, (fsize * 1000 / elapsed)));

    ttw(ttr(TTrTest, "Read %d entries of 4 bytes in %dms (bytes/s %d)" NL, 
            fsize/4, elapsed, (fsize * 1000 / elapsed)));
    
    tw(tr(TR_FUNC, TrTest, "Benchmark seek\n"));
    ttw(ttr(TTrTest, "Benchmark seek" NL));
    
    error = tffs_seek(fdi, 0, FFS_SEEK_SET);
    expect_ok(error);
    pos = 0;

    // Benchmark seek
    time_begin = tffs_timer_begin();
    for (i = 0; i < fsize/4; i++) {
        error = tffs_seek(fdi, pos, FFS_SEEK_SET);
        expect_ok(error);
        pos += 4;
        error = tffs_read(fdi, bigbuf, 4);
        expect_ok(error);
    }
    elapsed = tffs_timer_end(time_begin);

    tw(tr(TR_FUNC, TrTest, "Seek and Read %d entries of 4 bytes (bytes/s %d)\n", 
            fsize/4, (fsize * 1000 / elapsed)));

    ttw(ttr(TTrTest, "Seek and Read %d entries of 4 bytes in %dms (bytes/s %d)" NL, 
            fsize/4, elapsed, (fsize * 1000 / elapsed)));

    error = tffs_close(fdi);
    expect_ok(error);
    
    return EFFS_OK;
}

// Benchmark the blocking 'system'. Make n number of calls to ffs_format()
// with bad magic.
int case_bm_blocking(int p0, int p1)
{
    int i;
    int time_begin, elapsed;
 
    time_begin = tffs_timer_begin();
    for (i = 0; i < 10000; i++)
        tffs_format("/ffs", 0);
    elapsed = tffs_timer_end(time_begin);

    ttw(ttr(TTrTest, "Made %d blocking calls in %dms (ms/call %d)" NL, 
            i, elapsed, elapsed / i));

    // Make sure that it did fail as we expected
    error = tffs_format("/ffs", 0);
    expect(error, EFFS_INVALID);

    return EFFS_OK;
}
/******************************************************************************
 * Core test case
 ******************************************************************************/

int case_seekfile(int p0, int p1)
{
    // NOTEME: this is only a visual test add some expect() to finish it.
    char *name;
    iref_t i, dir, i_out;
    int length, flength;
    int segment_offset;
    fd_t fdi;

    error = tffs_fwrite("/File_seg", (char*)tdata[TDATA_HUGE], 50);
    expect(error, EFFS_OK);

    if ((i = object_lookup("/File_seg", &name, &dir)) < 0)
        return i;

    for (length = 52; length > 48; length--) {
        flength = segfile_seek(i, length, &i_out, &segment_offset);
        tw(tr(TR_FUNC, TrTest, 
              "*** Length %d, Flength %d, Seg_offset %2d, i %d ***\n",
              length, flength, segment_offset, i_out));
    }

    fdi = tffs_open("/Stream_seg", FFS_O_WRONLY | FFS_O_CREATE);
    tffs_write(fdi, (char*)tdata[TDATA_HUGE], 55);
    tffs_close(fdi);

    fdi = tffs_open("/Stream_seg", FFS_O_WRONLY | FFS_O_APPEND);
    tffs_write(fdi, (char*)tdata[TDATA_HUGE], 50);
    tffs_close(fdi);

    fdi = tffs_open("/Stream_seg", FFS_O_WRONLY | FFS_O_APPEND);
    tffs_write(fdi, (char*)tdata[TDATA_HUGE], 45);
    tffs_close(fdi);

    if ((i = object_lookup("/Stream_seg", &name, &dir)) < 0)
        return i;

    for (length = 153; length > 148; length--) {
        flength = segfile_seek(i, length, &i_out, &segment_offset);
        tw(tr(TR_FUNC, TrTest, 
              "*** Length %d, Flength %d, Seg_offset %2d, i %d ***\n",
              length, flength, segment_offset, i_out));
    }

    for (length = 107; length > 103; length--) {
        flength = segfile_seek(i, length, &i_out, &segment_offset);
        tw(tr(TR_FUNC, TrTest, 
              "*** Length %d, Flength %d, Seg_offset %2d, i %d ***\n",
              length, flength, segment_offset, i_out));
    }

    for (length = 58; length > 53; length--) {
        flength = segfile_seek(i, length, &i_out, &segment_offset);
        tw(tr(TR_FUNC, TrTest, 
              "*** Length %d, Flength %d, Seg_offset %2d, i %d ***\n",
              length, flength, segment_offset, i_out));
    }

    return 0;
}

void display_fs_params(void) 
{
    int numdatablocks;

    numdatablocks = dev.numblocks - fs.blocks_free_min - 1;

    // Note expect only one free block!
    tw(tr(TR_FUNC, TrTest, "Data blocks %d, size %dkB\n", 
          numdatablocks, dev.blocksize / 1024));
    tw(tr(TR_FUNC, TrTest, "    User data:       %dkB\n", 
          (numdatablocks * dev.blocksize - fs.reserved_space) / 1024));
    tw(tr(TR_FUNC, TrTest, "    reserved_space:  %dkB\n", fs.reserved_space/ 1024));
    tw(tr(TR_FUNC, TrTest, "    inodes_max:      %d\n", fs.inodes_max));
    tw(tr(TR_FUNC, TrTest, "    journal_size:    %d\n", fs.journal_size));
    tw(tr(TR_FUNC, TrTest, "    objects_max:     %d\n", fs.objects_max));
    tw(tr(TR_FUNC, TrTest, "    block_files_max: %d\n", fs.block_files_max));
    tw(tr(TR_FUNC, TrTest, "    chunk_size:      %d\n\n", fs.chunk_size_max));
}

// NOTE only visual test
int case_param_init(int p0, int p1)
{
    char myname[] = "/ffs/xx/xx/xx";
    int numblocks[] = { 8, 3, 7, 15, 31, 63, 96, 127, 61, 127};
    int i;

    for (i = 0; i < sizeof(numblocks) / sizeof(int); i++)
    {
        dev.numblocks = numblocks[i];

        if (i == 0)
            dev.blocksize = 1024 * 8;
        else if (i > 0 && i < 8)
            dev.blocksize = 1024 * 64;
        else 
            dev.blocksize = 1024 * 128;

        fs_params_init(myname);
        display_fs_params();
        
// TODO add some checks, test format("/ffs/bx/ox") etc.
    }

    error = tffs_initialize(); // Use the 'real' blocksize
    expect(error, EFFS_OK);

// Test param input from the format string
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    tffs_format("/ffs/i4000/o3000", 0x2BAD);
    expect(error, EFFS_OK);

    display_fs_params();

// Test small conf.
    tw(tr(TR_FUNC, TrTest, "Test small conf\n"));
    error = tffs_preformat(0xDEAD);
    expect(error, EFFS_OK);
    tffs_format("/ffs/b3/j8/r24/f100/o100", 0x2BAD);
    expect(error, EFFS_OK);
    display_fs_params();

    return 0;
}

/******************************************************************************
 * Trace mask
 ******************************************************************************/

// This is not a test case but a way to change the trace mask on the fly
int case_trace_mask(int p0, int p1)
{
    unsigned int temp;
    
    // Because p0 and p1 only is of the type int and trace_mask is a
    // unsigned int is p0 bit 1-28 and p1 is bit 29-32
    temp = p1 << 28;
    temp |= p0;

#if (TARGET == 0)
    tr_init(temp, 2, NULL );
    tw(tr(TR_FUNC, TrAll, "trace mask = %x\n", temp));
#else    
    ttr_init(temp);
    ttw(ttr(TTrTest, "trase mask = %x" NL, temp));
#endif
    return 0;
}

/******************************************************************************
 * Helper function
 ******************************************************************************/

#define CLEANUP_MIN_SIZE 1

int ignore_file(char *pathname)
{
    int i;
    const char *ignore[] = {
        "/.journal", "/.ffs-stats", "/.ffs-bstat.old","/dummy4fctrl" ,
        "/truncate/IMEI", "/truncate/stream_file", "/gsm/com/rfcap", 
        "/var/dbg/dar" };
    const char readonly[] = "/READONLY"; 

    for (i = 0; i < sizeof(ignore)/sizeof(char *); i++) {
        if (ffs_strcmp(pathname, ignore[i]) == 0) {
            return 0;
        }
    }

// Special case, we have one directory where we don't delete any objects
    if (memcmp(pathname, readonly, sizeof(readonly) - 1) == 0)
        return 0;

    return 1;
}

// Remove files in FFS until there is min_space left. Ignore all files that
// are listed in the ignore string above.
int case_cleanup(int min_space)
{
    int free, i, n, error;
    struct object_s *plist, *plist_start;
    char *pnames, *pnames_start;

    tw(tr(TR_FUNC, TrTestLow, "case_cleanup(%d)?\n", min_space));

    ffs_query(Q_BYTES_FREE, (uint32 *) &free); 
    if (free > min_space) {
        tw(tr(TR_FUNC, TrTestLow, "Total data free: %d\n", free));
        return free;    // Nothing to do
    }

    plist  = plist_start  = (struct object_s *) bigbuf;
    pnames = pnames_start = bigbuf + bigbuf_size;
    n = case_dir_list("/", &plist, &pnames);
    plist = plist_start;

    for (i = 0; i < n; i++, plist++)
    {
        ffs_query(Q_BYTES_FREE, (uint32 *) &free); 
        if (free > min_space) {
            tw(tr(TR_FUNC, TrTestLow, "Total data free: %d\n", free));
            return free;
        }

        if (plist->stat.size > CLEANUP_MIN_SIZE && ignore_file(plist->name)) {
            if ((error = ffs_remove(plist->name)) < 0)
                return error;
        }
    }
    return EFFS_NOSPACE;
}

// Make a random file. Random filename length, random filename, random filesize.
// Note it is up to the caller that the directory exists.
int case_mk_rand_file(char *dirname, int max_size, int min_size)
{
    int size, diff, filenamelen, pathlen = 0, i, error;
    char pathname[(FFS_FILENAME_MAX + 1) * FFS_PATH_DEPTH_MAX];
// All valid filename chars
    char tabel[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrtsuvwyz_-.,+%$#";

    diff = max_size - min_size;
    if (diff < 0)
        return EFFS_INVALID;
    else if (diff == 0)
        size = max_size;
    else
        size = (rand() % diff) + min_size;

    filenamelen = (rand() % (FFS_FILENAME_MAX - 1)) + 1;  // We need min one char
    tw(tr(TR_FUNC, TrTestLow, "size:%5d, filenamelen:%3d,",size, filenamelen));

    if (dirname != 0) {
        strcpy(pathname, dirname);
        pathlen = strlen(pathname);
    }

    pathname[pathlen++] = '/';

    for (i = 0; i < filenamelen; pathlen++, i++) {
        pathname[pathlen] = tabel[rand() % strlen(tabel)];
    }
    pathname[pathlen] = 0;  // Zero terminate

    tw(tr(TR_FUNC, TrTestLow, "pathname:'%s'\n", pathname, size));

    error = tffs_file_write(pathname, (char *) tdata[TDATA_HUGE], size, 
                            FFS_O_CREATE | FFS_O_TRUNC);
    return error;

}

// Various helper functions for debug 
int case_debug_help(int p0, int p1)
{
    int tr_mask;

    switch(p0) {
    case 1: 
    {
// Write rfcap file
        char rfcap_data[] = { 0, 0xf, 0x41, 0x10, 0, 0, 0, 0, 0x50, 0, 
                              0, 0xa5, 0x5, 0, 0xc0, 0 };

        ttw(ttr(TTrTest, "Write rfcap file..."));
        tffs_mkdir("/gsm");  // ignore error
        tffs_mkdir("/gsm/com");
        error = tffs_file_write("/gsm/com/rfcap", rfcap_data, 
                                sizeof(rfcap_data), FFS_O_CREATE | FFS_O_TRUNC);
        expect_ok(error);
        ttw(ttr(TTrTest, "done" NL));
    }
    break;
    case 2:
        tr_mask = tr_query(INT_MAX);
#if (TARGET == 0)
        tr_init(TrBstat, 2, NULL );
        tr_bstat();   
        tr_init(tr_mask, 2, NULL );
#else    
        ttr_init(TTrBstat);
        tr_bstat();    
        ttr_init(tr_mask);
#endif
        break;

    case 3:
// Make mem dump to screen
#if (TARGET == 1)
        rvf_dump_mem();
        break;
#else
        tw(tr(TR_FUNC, TrTestLow, "Not supported on PC\n"));
        return 1;  
#endif

    case 4:
#if (TARGET == 1)
        rvf_dump_tasks(); break;
#else
        tw(tr(TR_FUNC, TrTestLow, "Not supported on PC\n"));
        return 1;  
#endif
    case 5:
#if (TARGET == 1)
        rvf_dump_pool(); break;
#else
        tw(tr(TR_FUNC, TrTestLow, "Not supported on PC\n"));
        return 1;  
#endif
    default:
        tw(tr(TR_FUNC, TrTest, "Available helper functions:\n"));
        tw(tr(TR_FUNC, TrTest, "    1: Write rfcap file\n"));
        tw(tr(TR_FUNC, TrTest, "    2: Display bstat trace\n"));

        ttw(ttr(TTrTest, "Available helper functions:" NL));
        ttw(ttr(TTrTest, "    1: Write rfcap file" NL));
        ttw(ttr(TTrTest, "    2: Display bstat trace" NL));
        ttw(ttr(TTrTest, "    3: Memory dump to screen" NL));
        ttw(ttr(TTrTest, "    4: Tasks dump to screen" NL));
        ttw(ttr(TTrTest, "    5: Pool dump to screen" NL));
        return 0;
    }

    return 0;
}

/******************************************************************************
 * Hexdumping
 ******************************************************************************/
#if 0
void hexdump(const char *buf, int size, unsigned int address, int unitsize)
{
    char string[(8+1+1) + (1+16+1+1) + (3*16) + 1];
    int n, i;
    char *s;

    while (size > 0)
    {
        s = string;
        s += sprintf(s, "%8x: ", address); // print offset

        n = (size > 16 ? 16 : size);

        // print the textual representation
        for (i = 0; i < n; i++) {
            if (buf[i] >= ' ' && buf[i] < 127)
                *s++ = buf[i];
            else
                *s++ = '.';
        }
        // pad textual representation with spaces
        for (i = 0; i < 16 - n; i++) {
            *s++ = ' ';
        }
        *s++ = ' ';

        // print hexedecimal representation
        for (i = 0; i < n; i += unitsize) {
            switch (unitsize) {
            case 1: s += sprintf(s, "%02x ", *(unsigned char  *) (buf+i)); break;
            case 2: s += sprintf(s, "%04x ", *(unsigned short *) (buf+i)); break;
            case 4: s += sprintf(s, "%08x ", *(unsigned int *) (buf+i)); break;
            }
        }
        buf += 16;
        address += 16;
        size -= 16;
        puts(string);
    }
}
#endif

/******************************************************************************
 * Test Cases
 ******************************************************************************/

const struct testcase_s testcase[] =
{
    // Collective test cases
    { "all",    PC       , case_all,      "All" },
    { "alot",   PC       , case_alot,     "Almost all" },
    { "tall",      IT    , case_tall,     "Target all" },
    { "aall",   PC       , case_aall,     "Agressive all" },
    { "list",   PC|IT    , case_list,     "List all testcases" },
    { "lsr",    PC|IT    , case_lsr,      "Recursively list all objects" },
    { "test",   PC|IT    , case_test,     "Ad hoc test" },
    { "rand",   PC|IT    , case_rand,     "Random tests (NOT FINISHED)" },
    { "fail",   PC|IT    , case_fail,     "Fail (just fail)" },
    { "okay",   PC|IT    , case_okay,     "Okay (just succeed)" },

    // Atomic/small test cases
    { "s",      PC|IT    , case_status,   "Status: ffs_init(), lsr" },
    { "r",      PC|IT    , case_reset,    "preformat(), format(), init(), exit()" },
    { "i",      PC|IT    , case_init,           "ffs_init()" },
    { "x",      PC|IT    , case_exit,           "ffs_exit()" },
    { "p",      PC|IT    , case_only_preformat, "ffs_preformat()" },
    { "f",      PC|IT    , case_only_format,    "ffs_format()" },

    // Populate test cases
    { "world",  PC|IT    , case_world,    "Make world" },
    { "eu",     PC|IT    , case_europe,   "Make europe" },
    { "dk",     PC|IT    , case_denmark,  "Make denmark" },

    // Special test cases
    { "mf",     PC|IT    , case_mfiles,   "Test FFS with many (small) files" },
    { "threeb", PC|IT    , case_threeb,   "Test ffs in only three blocks" },
    { "twob",   PC|IT    , case_twob,     "Test ffs in only two blocks" },
    { "pcm",    PC|IT    , case_pcm,      "Test PCM interface" },
    { "stress",    IT    , case_stress,   "Stress test erase/write" },
    { "find",   PC|IT|RND, case_find,     "Recursively traverse all objects" },
    { "nb",     IT       , case_nonblock, "Test non block functions" },
    { "bf",     IT|RND   , case_blocking, "Test blocking function" },
    { "cust",   PC       , case_customer, "Test customer typical use"},
    { "esus",   IT       , case_erase_suspend, "Test erase suspend and resume" },

    // Driver test cases
    { "int",    IT       , case_interrupt,     "Test INT disable and enable" },  

    // Benchmark test cases
    { "bmsrw",     IT    , case_bm_stream_rw, "Benchmark stream read and writes" },
    { "bmfrw",     IT    , case_bm_file_rw,   "Benchmark file read and writes" },
    { "bmlu",      IT    , case_bm_lookup,    "Benchmark lookups" },
    { "bmsk",    PC|IT   , case_bm_seek,      "Benchmark ffs_seek()" },
    { "bmbl",    PC|IT   , case_bm_blocking,  "Benchmark blocking 'system'" },

    // Normal test cases
    { "format", PC       , case_format,   "Test ffs_pre/format()" },
    { "lu",     PC|IT    , case_lookup,   "Test object_lookup()" },
    { "fc",     PC|IT    , case_fcontrol, "Test fcontrol() and read-only" },
    { "root",   PC|IT|RND, case_root,     "Test root inode is non-modifiable" },
    { "dirs",   PC|IT|RND, case_dirs,     "Test Directories" },
    { "frd",    PC|IT|RND, case_fread,    "Test fread() with varying sizes/names" },
    { "bfull",  PC|IT    , case_bfull,    "Test filling a block completely" },
    { "ffull",  PC|IT    , case_ffull,    "Test filling ffs completely" },
    { "bigf",   PC       , case_bigfile,  "Test big files" },
    { "stat",   PC|IT|RND, case_stat,     "Test ffs_stat()" },
    { "rm",     PC|IT    , case_remove,   "Test ffs_remove()" },
    { "ren",    PC|IT|RND, case_rename,   "Test ffs_rename()" },
    { "renext", PC|IT    , case_rename_extended, "Extended test of ffs_rename()" },

    { "irec",   PC|IT    , case_irec,     "Test inodes reclaim" },
    { "drec",   PC       , case_drec,     "Test data reclaim" },
    { "adrec",  PC       , case_adrec,    "Test xxxx reclaim" },

    { "jnl",    PC|IT    , case_journal,  "Test journalling" },
    { "brec",   PC|IT|RND, case_brecover, "Test block recovery" },

    { "ssym",   PC|IT|RND, case_ssym,     "Test Simple Symlinks" },
    { "fsym",   PC|IT    , case_fsym,     "Test Full Symlinks (NOT IMPLEMENTED)" },
    { "ri",     PC|IT|RND, case_reinit,   "Test re-ffs_init()" },
    { "open",   PC|IT|RND, case_open,     "Test open flags" }, 
    { "rw",     PC|IT|RND, case_rw,       "Test read and write files" }, 
    { "mopen",  PC|IT|RND, case_multi_open, "Test open of multiply files" }, 
    { "seek",   PC|IT|RND, case_seek,     "Test seek on files " }, 
    { "trunc",  PC|IT|RND, case_trunc,    "Test ffs_truncate() " }, 
    { "append", PC|IT|RND, case_append,   "Test append on a open file" }, 
    { "dsync",  PC|IT|RND, case_datasync, "Test ffs_fdatasync" }, 
    { "ex",     IT|RND,    case_examples, "Test examples from RIV111 " }, 
    { "fwflags",PC|IT|RND, case_fw_flags, "Test ffs_file_write() flags" }, 
    { "pcm"    ,PC|IT|RND, case_pcm,      "Test pcm interface" }, 
    { "apiexc" ,PC|IT,     case_api_exceptions, "Test API exceptions" }, 
    { "ninit"  ,PC|IT,     case_api_notformated, "Test API on a nformated flash"},
    { "query"  ,PC|IT|RND, case_query,    "Test query function" },
    { "octrl"  ,PC|IT,     case_octrl,    "Test of object control" },

    // Core test cases
    { "seekf",  PC,        case_seekfile,  "Test core segfile_seek" },      
    { "param",  PC,        case_param_init,"Test core param_init" },      

    // Helper test case
    { "tr",     PC|IT    , case_trace_mask, "Change trace mask on the fly " },
    { "dh",     PC|IT    , case_debug_help, "Difference help functions " }, 
    { 0,        0,         0,             0 }
};

#if (TARGET == 1)
// Note like the interrupt functions in drv.c this asm needs to be places ad
// the buttom of the file or else it crach!
uint32 get_cpsr(void)
{
    asm("        .state16");
    asm("        adr       A2, get_cpsr32");
    asm("        bx        A2");
    asm("        .state32");
    asm("get_cpsr32");
    asm("        mrs       A1,cpsr      ; get current CPSR");
}
#else
uint32 get_cpsr(void){return 0;}
#endif