view src/cs/drivers/drv_app/ffs/board/tcases.c @ 294:e17bdedfbf2b

VIBR SWE initial implementation
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 27 Mar 2022 08:46:10 +0000
parents 4e78acac3d88
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