FreeCalypso > hg > fc-tourmaline
diff src/cs/drivers/drv_app/ffs/board/tcases.c @ 0:4e78acac3d88
src/{condat,cs,gpf,nucleus}: import from Selenite
author | Mychaela Falconia <falcon@freecalypso.org> |
---|---|
date | Fri, 16 Oct 2020 06:23:26 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cs/drivers/drv_app/ffs/board/tcases.c Fri Oct 16 06:23:26 2020 +0000 @@ -0,0 +1,6488 @@ +/****************************************************************************** + * 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 + + + + + + + + + + + +