diff packages/fs/yaffs/current/tests/fops.c @ 300:9c8ff034abb0

Graft in 20090826 source snapshot
author Ross Younger <wry@ecoscentric.com>
date Tue, 03 Nov 2009 12:15:09 +0000
parents
children ba05a98df7e4
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/fs/yaffs/current/tests/fops.c
@@ -0,0 +1,783 @@
+//=============================================================================
+//
+//      ops.c
+//
+//      Filesystem, file and directory operation tests for YAFFS on eCos
+//
+//=============================================================================
+// ####ECOSGPLCOPYRIGHTBEGIN####                                            
+// -------------------------------------------                              
+// This file is part of eCos, the Embedded Configurable Operating System.   
+// Copyright (C) 2009 Free Software Foundation, Inc.
+//
+// eCos is free software; you can redistribute it and/or modify it under    
+// the terms of the GNU General Public License as published by the Free     
+// Software Foundation; either version 2 or (at your option) any later      
+// version.                                                                 
+//
+// eCos is distributed in the hope that it will be useful, but WITHOUT      
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
+// for more details.                                                        
+//
+// You should have received a copy of the GNU General Public License        
+// along with eCos; if not, write to the Free Software Foundation, Inc.,    
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
+//
+// As a special exception, if other files instantiate templates or use      
+// macros or inline functions from this file, or you compile this file      
+// and link it with other works to produce a work based on this file,       
+// this file does not by itself cause the resulting work to be covered by   
+// the GNU General Public License. However the source code for this file    
+// must still be made available in accordance with section (3) of the GNU   
+// General Public License v2.                                               
+//
+// This exception does not invalidate any other reasons why a work based    
+// on this file might be covered by the GNU General Public License.         
+// -------------------------------------------                              
+// ####ECOSGPLCOPYRIGHTEND####                                              
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):   wry
+// Date:        2009-05-11
+// Description: A good (read/write) shakedown of the filesystem ops.
+//              On the SYNTH device, includes a long test which fills up
+//              the filesystem.
+//
+//####DESCRIPTIONEND####
+//=============================================================================
+
+#include <cyg/infra/testcase.h>
+#include <pkgconf/system.h>
+
+#if !defined(CYGPKG_LIBC_TIME) || !defined(CYGPKG_ERROR) || !defined(CYGPKG_LIBC_STDIO)
+
+void cyg_user_start(void)
+{
+    CYG_TEST_NA("Needs CYGPKG_LIBC_TIME, CYGPKG_ERROR and CYGPKG_LIBC_STDIO");
+}
+
+#else
+
+
+#include <cyg/infra/diag.h>
+#include <cyg/fileio/fileio.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MUST(what) do { CYG_TEST_CHECK((what), #what); } while(0)
+
+#define MUSTOS(what) do {       \
+    if (0 != what) {            \
+        perror(#what);          \
+        CYG_TEST_FAIL(#what);   \
+        cyg_test_exit();        \
+    }                           \
+} while(0)
+
+#define EXPECT(what,val) do { CYG_TEST_CHECK((what) == (val), #what "==" #val); } while(0)
+
+#ifdef CYGPKG_DEVS_NAND_SYNTH
+#define DEVICE "synth/0"
+#else
+#define DEVICE "onboard/0"
+#endif
+#define MOUNTPOINT "/nand"
+#define FSTYPE "yaffs"
+
+MTAB_ENTRY(my_auto, MOUNTPOINT, FSTYPE, DEVICE, 0/*priv set up by mount*/);
+
+void list_dir_contents(const char *dir) 
+{
+    DIR *d = opendir(dir);
+    if (!d) {
+        diag_printf("opendir %s: %s (%d)\n", dir, strerror(errno), errno);
+        return;
+    }
+    diag_printf("Directory %s contains:\n", dir);
+
+    struct dirent *de;
+    int n = 0;
+    while ((de=readdir(d))) {
+        diag_printf("\t%s\n", de->d_name);
+        ++n;
+    }
+    diag_printf("(ends)\n");
+
+    // Rewind and try again
+    int m = 0;
+    rewinddir(d);
+    while ((de=readdir(d)))
+        ++m;
+    if (n != m)
+        CYG_TEST_FAIL_EXIT("dir reread check failed - rewinddir broken?");
+
+    closedir(d);
+}
+
+#if 0
+void MKDIR(const char *dir)
+{
+    MUSTOS(mkdir(dir,0777));
+}
+#else
+#define MKDIR(d) MUSTOS(mkdir(d,0777))
+#endif
+
+#if 0
+void RMDIR(const char *dir)
+{
+    MUSTOS(rmdir(dir));
+}
+#else
+#define RMDIR(d) MUSTOS(rmdir(d))
+#endif
+
+#define RENAME(d,d2) MUSTOS(rename(d,d2))
+#define UNLINK(p) MUSTOS(unlink(p))
+#define LINK(p1,p2) MUSTOS(link(p1,p2))
+
+void CHDIR(const char *dir)
+{
+    MUSTOS(chdir(dir));
+}
+
+void WANTDIR(const char *d)
+{
+    struct stat st;
+    MUSTOS(stat(d,&st));
+    EXPECT(S_ISDIR(st.st_mode),1);
+}
+
+void DONOTWANT(const char *f)
+{
+    struct stat st;
+    if (stat(f,&st) != -1) {
+        diag_printf("Do not want %s, but it is there\n",f);
+        CYG_TEST_FAIL_EXIT("Unexpected entity present");
+    }
+    EXPECT(errno,ENOENT);
+}
+
+void RM_RF_work(const char *f, int recurse)
+{
+    //diag_printf("chdir %s <r=%d>\n",f,recurse);
+    MUSTOS(chdir(f));
+    DIR *d = opendir(".");
+    if (!d) {
+        diag_printf("opendir %s: %s\n", f, strerror(errno));
+        return;
+    }
+    //diag_printf("opened dir %s\n",f);
+    struct dirent de_stack;
+    struct dirent *de_res;
+    while (0==readdir_r(d, &de_stack, &de_res) && de_res) {
+        struct stat st;
+        if (0==strcmp(de_res->d_name,".")) continue;
+        if (0==strcmp(de_res->d_name,"..")) continue;
+        MUSTOS(stat(de_res->d_name,&st));
+        if (S_ISDIR(st.st_mode)) {
+            RM_RF_work(de_res->d_name, recurse+1);
+            //diag_printf("rmdir %s\n", de_res->d_name);
+            if (rmdir(de_res->d_name))
+                perror("rmdir");
+        } else {
+            // diag_printf("unlink %s\n", de_res->d_name);
+            MUSTOS(unlink(de_res->d_name));
+        }
+        // N.B. We have just unlinked a file or deleted a subdir
+        // during a live opendir/readdir cycle. What we get from readdir
+        // isn't (strictly) well-defined, but we expect to not crash.
+        // In fact what we get from YAFFS is probably the best
+        // possible behaviour: there's no explosion, and when we
+        // next say readdir() we get the next entry!
+    }
+    //diag_printf("rm -rf closes %s\n",f);
+    closedir(d);
+    MUSTOS(chdir(".."));
+    //diag_printf("chdir ..\n");
+}
+
+void RM_RF(const char *p)
+{
+    //diag_printf("chdir /\n");
+    //MUSTOS(chdir(MOUNTPOINT));
+    diag_printf("rm -rf %s\n",p);
+    RM_RF_work(p,0);
+    //diag_printf("rmdir %s\n",p);
+    RMDIR(p);
+}
+
+void checklen(const char *f, off_t size)
+{
+    struct stat st;
+    MUSTOS(stat(f, &st));
+    if (st.st_size != size) {
+        diag_printf("!! file %s size by stat is %lu, expect %lu\n", f, st.st_size, size);
+        CYG_TEST_FAIL_EXIT("file size readback mismatch");
+    }
+}
+
+// --------------------------------------------------------------
+
+// Function inspiration borrowed from fileio1.c and just about 
+// every other filesystem in eCos... Only this time, we're 
+// sufficiently paranoid to want to use arc4 to generate our file data.
+
+#define SHOW_RESULT(fn,res) do {                    \
+    diag_printf("%s returned %d, errno %d (%s)\n", #fn, (int)res, errno, strerror(errno));   \
+    CYG_TEST_FAIL("Unexpected result from "#fn);    \
+    cyg_test_exit();                                \
+} while(0)
+
+#define IOSIZE 100
+
+#include "ar4prng.inl"
+
+#define SEEDLEN (sizeof(time_t))
+unsigned char seed[SEEDLEN];
+
+ar4ctx prng;
+
+static void reinit_prng(void)
+{
+    static int seed_inited = 0;
+
+    if (!seed_inited) {
+        // This is a bit cruddy, but will introduce sufficient variation to make it interesting.
+        *(time_t*)seed = time(0);
+        seed_inited=1;
+    }
+
+    ar4prng_init(&prng, seed, SEEDLEN);
+}
+
+static void createfile( char *name, size_t size )
+{
+    unsigned char buf[IOSIZE];
+    int fd;
+    ssize_t wrote;
+    int err;
+    size_t savesize = size;
+
+    diag_printf("<INFO>: create file %s size %zd\n",name,size);
+
+    err = access( name, F_OK );
+    if( err < 0 && errno != EACCES ) SHOW_RESULT( access, err );
+    
+    reinit_prng();
+
+ 
+    fd = open( name, O_WRONLY|O_CREAT );
+    if( fd < 0 ) SHOW_RESULT( open, fd );
+
+    while( size > 0 )
+    {
+        ssize_t len = size;
+        if ( len > IOSIZE ) len = IOSIZE;
+        
+        ar4prng_many(&prng, buf, len);
+        wrote = write( fd, buf, len );
+        if( wrote != len ) SHOW_RESULT( write, wrote );        
+
+        size -= wrote;
+    }
+
+    err = fsync(fd);
+    if (err < 0) SHOW_RESULT(fsync, err);
+
+    struct stat st;
+    err = fstat(fd, &st);
+    if (err < 0) SHOW_RESULT(fstat, err);
+    if (st.st_size != savesize)
+        CYG_TEST_FAIL_EXIT("fstat size mismatched");
+
+    err = close( fd );
+    if( err < 0 ) SHOW_RESULT( close, err );
+}
+
+static void checkfile( char *name )
+{
+    unsigned char buf[IOSIZE], testbuf[IOSIZE];
+    int fd;
+    ssize_t done, pos=0, lastread=-1;
+    int i;
+    int err;
+
+    diag_printf("<INFO>: check file %s\n",name);
+    
+    reinit_prng();
+
+    err = access( name, F_OK );
+    if( err != 0 ) SHOW_RESULT( access, err );
+
+    fd = open( name, O_RDONLY );
+    if( fd < 0 ) SHOW_RESULT( open, fd );
+
+    for(;;)
+    {
+        done = read( fd, buf, IOSIZE );
+        pos += done;
+        if( done < 0 ) SHOW_RESULT( read, done );
+
+        if( done == 0 ) break;
+        lastread = done;
+
+        ar4prng_many(&prng, testbuf, done);
+
+        for( i = 0; i < done; i++ )
+            if( buf[i] != testbuf[i] )
+            {
+                diag_printf("buf[%d](%02x) != %02x\n",i,buf[i],testbuf[i]);
+                CYG_TEST_FAIL_EXIT("Data read not equal to data written");
+            }
+    }
+
+    // While we're here, let's have a play with lseek.
+#define LSEEK(f, o, w, expect) do {             \
+    err = lseek(f, o, w);                       \
+    if (err != expect) SHOW_RESULT(fseek, err); \
+} while(0)
+
+    LSEEK(fd, -lastread, SEEK_END, pos-lastread);
+    done = read(fd, buf, IOSIZE);
+    CYG_ASSERT(done == lastread, "reread last chunk size mismatch - lseek broken?");
+    for (i=0; i<IOSIZE; i++) {
+        if (buf[i] != testbuf[i]) {
+            diag_printf("buf[%d](%02x) != %02x\n",i,buf[i],testbuf[i]);
+            CYG_TEST_FAIL_EXIT("Data mismatch - lseek broken?");
+        }
+    }
+
+    reinit_prng();
+    ar4prng_many(&prng, testbuf, IOSIZE);
+
+    LSEEK(fd, 10, SEEK_SET, 10);
+    done = read(fd, &buf[10], IOSIZE-10);
+    CYG_ASSERT(done == IOSIZE-10, "reading after fseek");
+    LSEEK(fd, -IOSIZE, SEEK_CUR, 0); // i.e. to beginning of file
+    done = read(fd, buf, 10);
+    CYG_ASSERT(done == 10, "reading after 2nd fseek");
+    for (i=0; i<IOSIZE; i++) {
+        if (buf[i] != testbuf[i]) {
+            diag_printf("buf[%d](%02x) != %02x\n",i,buf[i],testbuf[i]);
+            CYG_TEST_FAIL_EXIT("Data mismatch - lseek broken?");
+        }
+    }
+
+    err = close( fd );
+    if( err < 0 ) SHOW_RESULT( close, err );
+}
+
+static void copyfile( char *name2, char *name1 )
+{
+
+    int err;
+    char buf[IOSIZE];
+    int fd1, fd2;
+    ssize_t done, wrote;
+
+    diag_printf("<INFO>: copy file %s -> %s\n",name2,name1);
+
+    err = access( name1, F_OK );
+    if( err < 0 && errno != EACCES ) SHOW_RESULT( access, err );
+
+    err = access( name2, F_OK );
+    if( err != 0 ) SHOW_RESULT( access, err );
+    
+    fd1 = open( name1, O_WRONLY|O_CREAT );
+    if( fd1 < 0 ) SHOW_RESULT( open, fd1 );
+
+    fd2 = open( name2, O_RDONLY );
+    if( fd2 < 0 ) SHOW_RESULT( open, fd2 );
+    
+    for(;;)
+    {
+        done = read( fd2, buf, IOSIZE );
+        if( done < 0 ) SHOW_RESULT( read, done );
+
+        if( done == 0 ) break;
+
+        wrote = write( fd1, buf, done );
+        if( wrote != done ) SHOW_RESULT( write, wrote );
+
+        if( wrote != done ) break;
+    }
+
+    err = close( fd1 );
+    if( err < 0 ) SHOW_RESULT( close, err );
+
+    err = close( fd2 );
+    if( err < 0 ) SHOW_RESULT( close, err );
+    
+}
+
+static void comparefiles( char *name2, char *name1 )
+{
+    int err;
+    char buf1[IOSIZE];
+    char buf2[IOSIZE];
+    int fd1, fd2;
+    ssize_t done1, done2;
+    int i;
+
+    diag_printf("<INFO>: compare files %s == %s\n",name2,name1);
+
+    err = access( name1, F_OK );
+    if( err != 0 ) SHOW_RESULT( access, err );
+
+    err = access( name1, F_OK );
+    if( err != 0 ) SHOW_RESULT( access, err );
+    
+    fd1 = open( name1, O_RDONLY );
+    if( fd1 < 0 ) SHOW_RESULT( open, fd1 );
+
+    fd2 = open( name2, O_RDONLY );
+    if( fd2 < 0 ) SHOW_RESULT( open, fd2 );
+
+    for(;;)
+    {
+        done1 = read( fd1, buf1, IOSIZE );
+        if( done1 < 0 ) SHOW_RESULT( read, done1 );
+
+        done2 = read( fd2, buf2, IOSIZE );
+        if( done2 < 0 ) SHOW_RESULT( read, done2 );
+
+        if( done1 != done2 )
+            diag_printf("Files different sizes\n");
+        
+        if( done1 == 0 ) break;
+
+        for( i = 0; i < done1; i++ )
+            if( buf1[i] != buf2[i] )
+            {
+                diag_printf("buf1[%d](%02x) != buf1[%d](%02x)\n",i,buf1[i],i,buf2[i]);
+                CYG_TEST_FAIL("Data in files not equal\n");
+            }
+    }
+
+    err = close( fd1 );
+    if( err < 0 ) SHOW_RESULT( close, err );
+
+    err = close( fd2 );
+    if( err < 0 ) SHOW_RESULT( close, err );
+    
+}
+
+#ifdef CYGPKG_DEVS_NAND_SYNTH
+
+#include <pkgconf/devs_nand_synth.h>
+
+static void maxfile( char *name )
+{
+    unsigned char buf[IOSIZE];
+    int fd;
+    ssize_t wrote;
+    int err;
+    size_t size = 0;
+    
+    diag_printf("<INFO>: create maximal file %s\n",name);
+
+    err = access( name, F_OK );
+    if( err < 0 && errno != EACCES ) SHOW_RESULT( access, err );
+
+    reinit_prng();
+
+    fd = open( name, O_WRONLY|O_CREAT );
+    if( fd < 0 ) SHOW_RESULT( open, fd );
+
+    do
+    {
+        ar4prng_many(&prng, buf, IOSIZE);
+        wrote = write( fd, buf, IOSIZE );
+        if( wrote < 0 ) SHOW_RESULT( write, wrote );        
+
+        CYG_ASSERT(size <= (CYGNUM_NAND_SYNTH_CHIPSIZE*1024), "filesystem overfull");
+        size += wrote;
+        
+    } while( wrote == IOSIZE );
+
+    diag_printf("<INFO>: file size == %zd\n",size);
+
+    err = close( fd );
+    cyg_fs_setinfo(MOUNTPOINT, FS_INFO_SYNC, NULL, 0); // sync();
+    if( err < 0 ) SHOW_RESULT( close, err );
+}
+#endif // CYGPKG_DEVS_NAND_SYNTH
+
+// --------------------------------------------------------------
+
+void cyg_user_start(void)
+{
+    CYG_TEST_INIT();
+
+    // 1. Mounting and unmounting. We should already be mounted
+    // by the time we get here (MTAB_ENTRY).
+    struct stat st;
+    MUSTOS(stat(MOUNTPOINT, &st));
+    diag_printf("got mountpoint ino %08x\n",st.st_ino);
+
+    EXPECT(mount(DEVICE, MOUNTPOINT, FSTYPE), -1);
+    MUSTOS(umount(MOUNTPOINT));
+    EXPECT(umount(MOUNTPOINT),-1);
+    MUSTOS(mount(DEVICE, MOUNTPOINT, FSTYPE));
+    EXPECT(mount(DEVICE, MOUNTPOINT, FSTYPE), -1);
+    CYG_TEST_INFO("mount, umount OK");
+
+    // 2. Directory creation, deletion and rename
+    // In fact we start by deleting if our test dir was already there...
+#define DIR1 MOUNTPOINT"/testdir"
+#define DIR2 MOUNTPOINT"/testdir2"
+    if (0==stat(DIR1, &st)) {
+        CYG_TEST_INFO("dir " DIR1 " already present - attempting to nuke");
+        RM_RF(DIR1);
+    }
+    if (0==stat(DIR2, &st)) {
+        CYG_TEST_INFO("dir " DIR2 " already present - attempting to nuke");
+        RM_RF(DIR2);
+    }
+    EXPECT(rmdir(DIR1),-1);
+    MKDIR(DIR1);
+    RMDIR(DIR1);
+    EXPECT(rmdir(DIR1),-1);
+    MKDIR(DIR1);
+    EXPECT(mkdir(DIR1,0777),-1);
+
+    MKDIR(DIR1"/foo////");
+    EXPECT(rmdir(DIR1),-1);
+    EXPECT(rmdir(DIR1"/."),-1);
+    EXPECT(rmdir(DIR1"/foo/.."),-1);
+    EXPECT(rmdir(DIR1"/./foo/."),-1); // Linux doesn't allow this either.
+    RMDIR(DIR1"/./foo/"); // Trailing slash on a dir op shouldn't catch us out
+
+    RENAME(DIR1"/", MOUNTPOINT"/newdir/");
+    WANTDIR(MOUNTPOINT"/newdir");
+    WANTDIR(MOUNTPOINT"/newdir/////");
+    DONOTWANT(DIR1);
+    RMDIR(MOUNTPOINT"/newdir");
+
+    EXPECT(rmdir(MOUNTPOINT"/"),-1);
+    EXPECT(rmdir(MOUNTPOINT),-1);
+
+    CYG_TEST_INFO("directory creation, deletion OK");
+
+    // 3. chdir. We'll make a simple hierarchy:
+    //      DIR1
+    //      DIR1/1
+    //      DIR1/1a
+    //      DIR1/foo/1foo
+    //      DIR2
+    //      DIR2/2
+    //      DIR2/bar/2bar
+    //      DIR2/bar/2baz
+    // .. then chdir into each in turn and confirm that the contents are as expected.
+    MKDIR(DIR1);
+    MKDIR(DIR1"/1");
+    MKDIR(DIR1"/1a");
+    MKDIR(DIR1"/foo");
+    MKDIR(DIR1"/foo/1foo");
+    MKDIR(DIR2);
+    MKDIR(DIR2"/2");
+    MKDIR(DIR2"/bar");
+    MKDIR(DIR2"/bar/2bar");
+    MKDIR(DIR2"/bar/2baz");
+
+    CHDIR(DIR1);
+    WANTDIR("1");
+    WANTDIR("1a");
+    WANTDIR("foo");
+    DONOTWANT("2");
+    DONOTWANT("1foo");
+    DONOTWANT("bar");
+    DONOTWANT("2bar");
+    DONOTWANT("2baz");
+
+    CHDIR("foo");
+    WANTDIR("1foo");
+    DONOTWANT("1");
+    DONOTWANT("1a");
+    DONOTWANT("foo");
+
+    CHDIR("..");
+    WANTDIR("1");
+    WANTDIR("1a");
+    WANTDIR("foo");
+    DONOTWANT("bar");
+    CHDIR(".");
+    WANTDIR("1");
+    WANTDIR("1a");
+    WANTDIR("foo");
+    DONOTWANT("bar");
+    WANTDIR("foo/1foo");
+    RMDIR("foo/1foo");
+    DONOTWANT("foo/1foo");
+
+    CYG_TEST_INFO("chdir, opendir, readdir OK");
+
+    // 4. File creation, copying, renaming, deletion - throughout dirs
+
+    CHDIR(DIR1);
+    createfile("floop", 202);
+    checklen("floop", 202);
+    checkfile(DIR1"/floop");
+    copyfile("floop", DIR2"/fee");
+    CHDIR(DIR2);
+    checkfile("fee");
+    checklen("fee", 202);
+    comparefiles("fee", DIR1"/floop");
+
+    copyfile("fee", DIR1"/fum");
+    checkfile(DIR1"/fum");
+    chdir(DIR1);
+    checkfile("fum");
+    comparefiles("floop", "fum");
+    comparefiles("fum", DIR2"/fee");
+
+    // Rename ...
+    RENAME("fum", DIR2"/fum2");
+    DONOTWANT("fum");
+    DONOTWANT("fum2");
+    DONOTWANT(DIR1"/fum");
+    checkfile(DIR2"/fum2");
+    CHDIR(DIR2);
+    checkfile("fum2");
+    CHDIR(DIR1);
+    RENAME(DIR2"/fum2", "fum3");
+    checkfile("fum3");
+    checkfile(DIR1"/fum3");
+    DONOTWANT(DIR2"/fum3");
+    comparefiles(DIR1"/fum3", DIR1"/floop");
+    CHDIR(DIR2);
+    comparefiles(DIR1"/fum3", DIR1"/floop");
+
+    // A brief diversion to check rewinddir...
+    list_dir_contents(DIR1);
+
+    // Create-over, rename-over
+    CHDIR(DIR1);
+    checklen("floop", 202);
+    checklen(DIR2"/fee", 202);
+    createfile(DIR2"/fee", 4242);
+    checklen(DIR2"/fee", 4242);
+    checklen("floop", 202);
+    RENAME(DIR2"/fee", "floop");
+    checklen("floop", 4242);
+
+    // Unlink
+    CHDIR(DIR2);
+    UNLINK(DIR1"/fum3");
+    DONOTWANT(DIR1"/fum3");
+    DONOTWANT("fum3");
+    CHDIR(DIR1);
+    DONOTWANT("fum3");
+
+    CYG_TEST_INFO("file create/read/write, copy, delete OK");
+
+    // 5. File hard links (and directory, which aren't supported)
+
+    LINK("floop","floop2");
+    checklen("floop2", 4242);
+    LINK("floop",DIR2"/floop3");
+    CHDIR(DIR2);
+    LINK(DIR1"/floop", "floop4");
+    LINK(DIR1"/floop", DIR1"/floop5");
+    DONOTWANT("floop2");
+    checkfile("floop3");
+    checkfile("floop4");
+    DONOTWANT("floop5");
+    CHDIR(DIR1);
+    checkfile("floop2");
+    DONOTWANT("floop3");
+    DONOTWANT("floop4");
+    checkfile("floop5");
+
+    checkfile(DIR1"/floop2");
+    checkfile(DIR2"/floop3");
+    checkfile(DIR2"/floop4");
+    checkfile(DIR1"/floop5");
+
+    UNLINK("floop2");
+    UNLINK(DIR2"/floop3");
+    CHDIR(DIR2);
+    UNLINK("floop4");
+    UNLINK(DIR1"/floop5");
+
+    DONOTWANT(DIR1"/floop2");
+    DONOTWANT(DIR2"/floop3");
+    DONOTWANT(DIR2"/floop4");
+    DONOTWANT(DIR1"/floop5");
+
+    CHDIR(DIR1);
+    checkfile("floop");
+    // we'll leave floop lying around for fpathconf() test
+
+    EXPECT(link(DIR1, DIR2"/dir-hardlink-should-fail"), -1);
+    EXPECT(errno, EPERM);
+
+    CYG_TEST_INFO("hard links OK");
+
+#ifdef CYGPKG_DEVS_NAND_SYNTH
+    // 6. EOF-on-write (by filling the filesystem!)
+    CYG_TEST_INFO("bigfile test, this might take a few seconds");
+
+    // To speed this test up tune your synth nand device to not be so big.
+    maxfile(DIR1"/bigfile");
+    checkfile(DIR1"/bigfile");
+    {
+        struct stat st;
+        stat(DIR1"/bigfile", &st);
+    }
+    UNLINK(DIR1"/bigfile");
+    CYG_TEST_INFO("EOF on write OK");
+#else
+    CYG_TEST_INFO("SKIPPING EOF-on-write test (only applied to synth nand)");
+#endif
+
+    // 7. Ancillary checks - getinfo, setinfo (no ioctls supported as yet)
+
+#ifdef CYGSEM_FILEIO_INFO_DISK_USAGE
+    struct cyg_fs_disk_usage usage;
+    MUSTOS(cyg_fs_getinfo(MOUNTPOINT, FS_INFO_DISK_USAGE, &usage, sizeof(usage)));
+    diag_printf("getinfo/disk_usage says:\n\ttotal blocks %llu\n\tfree blocks  %llu\n\tblock size   %u\n", usage.total_blocks, usage.free_blocks, usage.block_size);
+#endif
+
+    diag_printf("pathconf says:\n\tNAME_MAX %ld\n\tPATH_MAX %ld\n\tSYNC_IO %ld\n", 
+            pathconf(DIR1, _PC_NAME_MAX),
+            pathconf(DIR1, _PC_PATH_MAX),
+            pathconf(DIR1, _PC_SYNC_IO));
+
+    MUSTOS(cyg_fs_setinfo(MOUNTPOINT, FS_INFO_SYNC, 0, 0));
+
+    int fd = open(DIR1"/floop", O_RDONLY);
+    CYG_ASSERT(fd >= 0, "open");
+    CYG_ASSERT(fpathconf(fd, _PC_NAME_MAX) != -1, "fpathconf PC_NAME_MAX");
+    close(fd);
+
+    CYG_TEST_INFO("getinfo, setinfo OK");
+
+    // Clean up.
+
+    CHDIR(MOUNTPOINT);
+    RM_RF(DIR1);
+    RM_RF(DIR2);
+    DONOTWANT(DIR1);
+    DONOTWANT(DIR2);
+    CYG_TEST_INFO("recursive deletion OK");
+
+    MUSTOS(umount(MOUNTPOINT));
+#ifndef CYGDBG_USE_ASSERTS
+    CYG_TEST_INFO("WARNING: asserts disabled, test INCOMPLETE, try switching on CYGPKG_INFRA_DEBUG");
+#else
+    CYG_TEST_PASS("All OK");
+#endif
+}
+
+#endif
+