/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.  Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
 */

/** @file
 *  @brief File system stress test.
 *
 *  This version of SGI fsstress has been modified to be single-threaded and to
 *  work with the Reliance Edge POSIX-like API.
 */
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

#include <redposix.h>
#include <redtests.h>

#if FSSTRESS_SUPPORTED

    #include "redposixcompat.h"

    #include <redosserv.h>
    #include <redutils.h>
    #include <redmacs.h>
    #include <redvolume.h>
    #include <redgetopt.h>
    #include <redtoolcmn.h>

    #if REDCONF_CHECKER == 1
        #include <redcoreapi.h>
    #endif


/*  Create POSIX types.  Use #define to avoid name conflicts in those
 *  environments where the type names already exist.
 */
    #define off_t        int64_t
    #define off64_t      off_t
    #define ino_t        uint32_t
    #define mode_t       uint16_t
    #define __int64_t    int64_t


/** @brief Generate a random number.
 *
 *  @return A nonnegative random number.
 */
    #define random()    ( ( int ) ( RedRand32( NULL ) & 0x7FFFFFFF ) )


/** @brief Seed the random number generator.
 */
    #define srandom( seed )    RedRandSeed( seed )


    #define _exit( status )    exit( status )
    #define getpagesize()      4096U
    #define getpid()           1


/** @brief Determine the maximum file size.
 *
 *  This is used for the MAXFSSIZE macro.
 */
    static uint64_t MaxFileSize( void )
    {
        REDSTATFS info;
        int32_t iStatus;
        REDSTATUS errnoSave = errno;
        uint64_t ullMaxFileSize;

        iStatus = red_statvfs( "", &info );

        if( iStatus == 0 )
        {
            ullMaxFileSize = info.f_maxfsize;
        }
        else
        {
            /*  This function does not change errno.
             */
            errno = errnoSave;

            ullMaxFileSize = 0x7FFFFFFFU;
        }

        return ullMaxFileSize;
    }


/*-------------------------------------------------------------------
 *   Simulated current working directory support
 *  -------------------------------------------------------------------*/


/*  Forward declaration for red_chdir().
 */
    static int red_stat( const char * pszPath,
                         REDSTAT * pStat );

/*  The simulated CWD functions.
 */
    #undef chdir
    #undef getcwd
    #define chdir( path )          red_chdir( path )
    #define getcwd( buf, size )    red_getcwd( buf, size )


/*  Redefine the path-based APIs to call MakeFullPath() on their arguments
 *  since there is no CWD support in the red_*() APIs.
 */
    #undef open
    #undef unlink
    #undef mkdir
    #undef rmdir
    #undef rename
    #undef link
    #undef opendir
    #define open( path, oflag )       red_open( MakeFullPath( path ), oflag )
    #define unlink( path )            red_unlink( MakeFullPath( path ) )
    #define mkdir( path )             red_mkdir( MakeFullPath( path ) )
    #define rmdir( path )             red_rmdir( MakeFullPath( path ) )
    #define rename( old, new )        red_rename( MakeFullPath( old ), MakeFullPath( new ) )
    #define link( path, hardlink )    red_link( MakeFullPath( path ), MakeFullPath( hardlink ) )
    #define opendir( path )           red_opendir( MakeFullPath( path ) )

    #define FSSTRESS_BUF_SIZE    1024U

/*  Stores the simulated current working directory.
 */
    static char szLocalCwd[ FSSTRESS_BUF_SIZE ] = "/";


/** @brief Change the current working directory.
 *
 *  This function only supports a subset of what is possible with POSIX chdir().
 *
 *  @param pszPath  The new current working directory.
 *
 *  @return Upon successful completion, 0 shall be returned.  Otherwise, -1
 *          shall be returned, and errno shall be set to indicate the error.
 */
    static int red_chdir( const char * pszPath )
    {
        uint32_t ulIdx;
        int iErrno = 0;

        if( strcmp( pszPath, ".." ) == 0 )
        {
            uint32_t ulLastSlashIdx = 0U;

            /*  Chop off the last path separator and everything after it, so that
             *  "/foo/bar/baz" becomes "/foo/bar", moving the CWD up one directory.
             */
            for( ulIdx = 0U; szLocalCwd[ ulIdx ] != '\0'; ulIdx++ )
            {
                if( szLocalCwd[ ulIdx ] == '/' )
                {
                    ulLastSlashIdx = ulIdx;
                }
            }

            if( ulLastSlashIdx != 0U )
            {
                szLocalCwd[ ulLastSlashIdx ] = '\0';
            }
        }
        else
        {
            char szOldCwd[ FSSTRESS_BUF_SIZE ];

            /*  chdir() must have no effect on the CWD if it fails, so save the CWD
             *  so we can revert it if necessary.
             */
            strcpy( szOldCwd, szLocalCwd );

            if( pszPath[ 0U ] == '/' )
            {
                if( strlen( pszPath ) >= sizeof( szLocalCwd ) )
                {
                    iErrno = RED_ENAMETOOLONG;
                }
                else
                {
                    strcpy( szLocalCwd, pszPath );
                }
            }
            else
            {
                ulIdx = strlen( szLocalCwd );

                if( ( ulIdx + 1U + strlen( pszPath ) ) >= sizeof( szLocalCwd ) )
                {
                    iErrno = RED_ENAMETOOLONG;
                }
                else
                {
                    if( szLocalCwd[ 1U ] != '\0' )
                    {
                        szLocalCwd[ ulIdx ] = '/';
                        ulIdx++;
                    }

                    strcpy( &szLocalCwd[ ulIdx ], pszPath );
                }
            }

            if( iErrno == 0 )
            {
                REDSTAT s;
                int iStatus;

                iStatus = red_stat( szLocalCwd, &s );

                if( iStatus != 0 )
                {
                    iErrno = errno;
                }
                else if( !S_ISDIR( s.st_mode ) )
                {
                    iErrno = RED_ENOTDIR;
                }
                else
                {
                    /*  No error, new CWD checks out.
                     */
                }
            }

            if( iErrno != 0 )
            {
                strcpy( szLocalCwd, szOldCwd );
            }
        }

        if( iErrno != 0 )
        {
            errno = iErrno;
        }

        return iErrno == 0 ? 0 : -1;
    }


/** @brief Retrieve the current working directory.
 *
 *  @param pszBuf   On successful return, populated with the current working
 *                  directory.  If NULL, memory will be allocated for the CWD
 *                  and returned by this function.
 *  @param nSize    The size of @p pszBuf.
 *
 *  @return On success, if @p pszBuf was non-NULL, returns @p pszBuf; if
 *          @p pszBuf was NULL, returns an allocated buffer populated with the
 *          CWD which must be freed by the caller.  On failure, returns NULL
 *          and errno will be set.
 */
    static char * red_getcwd( char * pszBuf,
                              size_t nSize )
    {
        char * pszRet;

        if( pszBuf == NULL )
        {
            pszRet = malloc( strlen( szLocalCwd ) + 1U );

            if( pszRet == NULL )
            {
                errno = RED_ENOMEM;
            }
            else
            {
                strcpy( pszRet, szLocalCwd );
            }
        }
        else if( nSize < strlen( szLocalCwd ) + 1U )
        {
            errno = RED_ERANGE;
            pszRet = NULL;
        }
        else
        {
            strcpy( pszBuf, szLocalCwd );
            pszRet = pszBuf;
        }

        return pszRet;
    }


/** @brief Make a relative path into a fully qualified path.
 *
 *  @param pszName  The relative path.
 *
 *  @return On success, a pointer to a fully qualified path.  On error, NULL.
 */
    static const char * MakeFullPath( const char * pszName )
    {
    #define         MAXVOLNAME    64U /* Enough for most configs. */
        static char aszFullPath[ 2U ][ MAXVOLNAME + 1U + FSSTRESS_BUF_SIZE ];
        static uint32_t ulWhich = 0U;

        char * pszFullPath = aszFullPath[ ulWhich ];
        const char * pszVolume = gpRedVolConf->pszPathPrefix;
        int32_t iLen;

        if( pszName[ 0U ] == '/' )
        {
            iLen = RedSNPrintf( pszFullPath, sizeof( aszFullPath[ 0U ] ), "%s%s", pszVolume, pszName );
        }
        else if( strcmp( pszName, "." ) == 0U )
        {
            iLen = RedSNPrintf( pszFullPath, sizeof( aszFullPath[ 0U ] ), "%s%s", pszVolume, szLocalCwd );
        }
        else if( ( szLocalCwd[ 0U ] == '/' ) && ( szLocalCwd[ 1U ] == '\0' ) )
        {
            iLen = RedSNPrintf( pszFullPath, sizeof( aszFullPath[ 0U ] ), "%s/%s", pszVolume, pszName );
        }
        else
        {
            iLen = RedSNPrintf( pszFullPath, sizeof( aszFullPath[ 0U ] ), "%s%s/%s", pszVolume, szLocalCwd, pszName );
        }

        if( iLen == -1 )
        {
            /*  Insufficient path buffer space.
             */
            pszFullPath = NULL;
        }
        else
        {
            /*  Toggle between two full path arrays; a kluge to make rename() and
             *  link() work correctly.
             */
            ulWhich ^= 1U;
        }

        return pszFullPath;
    }


/*-------------------------------------------------------------------
 *   POSIX functions not implemented by the RED POSIX-like API
 *  -------------------------------------------------------------------*/

    #define stat( p, s )          red_stat( p, s )
    #define stat64( p, s )        stat( p, s )
    #define lstat( p, s )         stat( p, s )
    #define lstat64( p, s )       stat( p, s )
    #define truncate( p, s )      red_truncate( p, s )
    #define truncate64( p, s )    truncate( p, s )


/** @brief Get the status of a file or directory.
 */
    static int red_stat( const char * pszPath,
                         REDSTAT * pStat )
    {
        int iFd;
        int iRet;

        iFd = open( pszPath, O_RDONLY );
        iRet = iFd;

        if( iFd != -1 )
        {
            iRet = fstat( iFd, pStat );

            ( void ) close( iFd );
        }

        return iRet;
    }


/** @brief Truncate a file to a specified length.
 */
    static int red_truncate( const char * pszPath,
                             off_t llSize )
    {
        int iFd;
        int iRet;

        iFd = open( pszPath, O_WRONLY );
        iRet = iFd;

        if( iFd != -1 )
        {
            iRet = ftruncate( iFd, llSize );

            ( void ) close( iFd );
        }

        return iRet;
    }


/*-------------------------------------------------------------------
 *   Begin ported fsstress code
 *  -------------------------------------------------------------------*/

/* Stuff from xfscompat.h */

    #define MAXNAMELEN    ( REDCONF_NAME_MAX + 1U ) /* Assumed to include NUL */

    struct dioattr
    {
        int d_miniosz, d_maxiosz, d_mem;
    };

    #define MIN( a, b )    ( ( a ) < ( b ) ? ( a ) : ( b ) )
    #define MAX( a, b )    ( ( a ) > ( b ) ? ( a ) : ( b ) )

/* End xfscompat.h */


    typedef enum
    {
        OP_CREAT,
        OP_FDATASYNC,
        OP_FSYNC,
        OP_GETDENTS,
        OP_LINK,
        OP_MKDIR,
        OP_READ,
        OP_RENAME,
        OP_RMDIR,
        OP_STAT,
        OP_TRUNCATE,
        OP_UNLINK,
        OP_WRITE,
        #if REDCONF_CHECKER == 1
            OP_CHECK,
        #endif
        OP_LAST
    } opty_t;

    typedef void (* opfnc_t) ( int,
                               long );

    typedef struct opdesc
    {
        opty_t op;
        const char * name;
        opfnc_t func;
        int freq;
        int iswrite;
    } opdesc_t;

    typedef struct fent
    {
        int id;
        int parent;
    } fent_t;

    typedef struct flist
    {
        int nfiles;
        int nslots;
        int tag;
        fent_t * fents;
    } flist_t;

    typedef struct pathname
    {
        int len;
        char * path;
    } pathname_t;

    #define FT_DIR             0
    #define FT_DIRm            ( 1 << FT_DIR )
    #define FT_REG             1
    #define FT_REGm            ( 1 << FT_REG )
    #define FT_SYM             2
    #define FT_SYMm            ( 1 << FT_SYM )
    #define FT_DEV             3
    #define FT_DEVm            ( 1 << FT_DEV )
    #define FT_RTF             4
    #define FT_RTFm            ( 1 << FT_RTF )
    #define FT_nft             5
    #define FT_ANYm            ( ( 1 << FT_nft ) - 1 )
    #define FT_REGFILE         ( FT_REGm | FT_RTFm )
    #define FT_NOTDIR          ( FT_ANYm & ~FT_DIRm )

    #define FLIST_SLOT_INCR    16
    #define NDCACHE            64

    #define MAXFSIZE           MaxFileSize()

    static void creat_f( int opno,
                         long r );
    static void fdatasync_f( int opno,
                             long r );
    static void fsync_f( int opno,
                         long r );
    static void getdents_f( int opno,
                            long r );
    static void link_f( int opno,
                        long r );
    static void mkdir_f( int opno,
                         long r );
    static void read_f( int opno,
                        long r );
    static void rename_f( int opno,
                          long r );
    static void rmdir_f( int opno,
                         long r );
    static void stat_f( int opno,
                        long r );
    static void truncate_f( int opno,
                            long r );
    static void unlink_f( int opno,
                          long r );
    static void write_f( int opno,
                         long r );
    #if REDCONF_CHECKER == 1
        static void check_f( int opno,
                             long r );
    #endif

    static opdesc_t ops[] =
    {
        { OP_CREAT,     "creat",     creat_f,     4, 1 },
        { OP_FDATASYNC, "fdatasync", fdatasync_f, 1, 1 },
        { OP_FSYNC,     "fsync",     fsync_f,     1, 1 },
        { OP_GETDENTS,  "getdents",  getdents_f,  1, 0 },
        { OP_LINK,      "link",      link_f,      1, 1 },
        { OP_MKDIR,     "mkdir",     mkdir_f,     2, 1 },
        { OP_READ,      "read",      read_f,      1, 0 },
        { OP_RENAME,    "rename",    rename_f,    2, 1 },
        { OP_RMDIR,     "rmdir",     rmdir_f,     1, 1 },
        { OP_STAT,      "stat",      stat_f,      1, 0 },
        { OP_TRUNCATE,  "truncate",  truncate_f,  2, 1 },
        { OP_UNLINK,    "unlink",    unlink_f,    1, 1 },
        { OP_WRITE,     "write",     write_f,     4, 1 },
        #if REDCONF_CHECKER == 1
        { OP_CHECK,     "check",     check_f,     1, 1 },
        #endif
    }, * ops_end;

    static flist_t flist[ FT_nft ] =
    {
        { 0, 0, 'd', NULL },
        { 0, 0, 'f', NULL },
        { 0, 0, 'l', NULL },
        { 0, 0, 'c', NULL },
        { 0, 0, 'r', NULL },
    };

    static int dcache[ NDCACHE ];
    static opty_t * freq_table;
    static int freq_table_size;
    static char * homedir;
    static int * ilist;
    static int ilistlen;
    static off64_t maxfsize;
    static int namerand;
    static int nameseq;
    static int nops;
    static int operations = 1;
    static int procid;
    static int rtpct;
    static unsigned long seed = 0;
    static ino_t top_ino;
    static int verbose = 0;

    static int delete_tree( const char * path );
    static void add_to_flist( int fd,
                              int it,
                              int parent );
    static void append_pathname( pathname_t * name,
                                 const char * str );
    static void check_cwd( void );
    static int creat_path( pathname_t * name,
                           mode_t mode );
    static void dcache_enter( int dirid,
                              int slot );
    static void dcache_init( void );
    static fent_t * dcache_lookup( int dirid );
    static void dcache_purge( int dirid );
    static void del_from_flist( int ft,
                                int slot );
    static void doproc( void );
    static void fent_to_name( pathname_t * name,
                              flist_t * flp,
                              fent_t * fep );
    static void fix_parent( int oldid,
                            int newid );
    static void free_pathname( pathname_t * name );
    static int generate_fname( fent_t * fep,
                               int ft,
                               pathname_t * name,
                               int * idp,
                               int * v );
    static int get_fname( int which,
                          long r,
                          pathname_t * name,
                          flist_t ** flpp,
                          fent_t ** fepp,
                          int * v );
    static void init_pathname( pathname_t * name );
    static int link_path( pathname_t * name1,
                          pathname_t * name2 );
    static int lstat64_path( pathname_t * name,
                             REDSTAT * sbuf );
    static void make_freq_table( void );
    static int mkdir_path( pathname_t * name,
                           mode_t mode );
    static void namerandpad( int id,
                             char * buf,
                             int len );
    static int open_path( pathname_t * name,
                          int oflag );
    static DIR * opendir_path( pathname_t * name );
    static int rename_path( pathname_t * name1,
                            pathname_t * name2 );
    static int rmdir_path( pathname_t * name );
    static void separate_pathname( pathname_t * name,
                                   char * buf,
                                   pathname_t * newname );
    static int stat64_path( pathname_t * name,
                            REDSTAT * sbuf );
    static int truncate64_path( pathname_t * name,
                                off64_t length );
    static int unlink_path( pathname_t * name );
    static void usage( const char * progname );


/** @brief Parse parameters for fsstress.
 *
 *  @param argc         The number of arguments from main().
 *  @param argv         The vector of arguments from main().
 *  @param pParam       Populated with the fsstress parameters.
 *  @param pbVolNum     If non-NULL, populated with the volume number.
 *  @param ppszDevice   If non-NULL, populated with the device name argument or
 *                      NULL if no device argument is provided.
 *
 *  @return The result of parsing the parameters.
 */
    PARAMSTATUS FsstressParseParams( int argc,
                                     char * argv[],
                                     FSSTRESSPARAM * pParam,
                                     uint8_t * pbVolNum,
                                     const char ** ppszDevice )
    {
        int c;
        uint8_t bVolNum;
        const REDOPTION aLongopts[] =
        {
            { "no-cleanup", red_no_argument,       NULL, 'c' },
            { "loops",      red_required_argument, NULL, 'l' },
            { "nops",       red_required_argument, NULL, 'n' },
            { "namepad",    red_no_argument,       NULL, 'r' },
            { "seed",       red_required_argument, NULL, 's' },
            { "verbose",    red_no_argument,       NULL, 'v' },
            { "dev",        red_required_argument, NULL, 'D' },
            { "help",       red_no_argument,       NULL, 'H' },
            { NULL }
        };

        /*  If run without parameters, treat as a help request.
         */
        if( argc <= 1 )
        {
            goto Help;
        }

        /*  Assume no device argument to start with.
         */
        if( ppszDevice != NULL )
        {
            *ppszDevice = NULL;
        }

        /*  Set default parameters.
         */
        FsstressDefaultParams( pParam );

        while( ( c = RedGetoptLong( argc, argv, "cl:n:rs:vD:H", aLongopts, NULL ) ) != -1 )
        {
            switch( c )
            {
                case 'c': /* --no-cleanup */
                    pParam->fNoCleanup = true;
                    break;

                case 'l': /* --loops */
                    pParam->ulLoops = RedAtoI( red_optarg );
                    break;

                case 'n': /* --nops */
                    pParam->ulNops = RedAtoI( red_optarg );
                    break;

                case 'r': /* --namepad */
                    pParam->fNamePad = true;
                    break;

                case 's': /* --seed */
                    pParam->ulSeed = RedAtoI( red_optarg );
                    break;

                case 'v': /* --verbose */
                    pParam->fVerbose = true;
                    break;

                case 'D': /* --dev */

                    if( ppszDevice != NULL )
                    {
                        *ppszDevice = red_optarg;
                    }

                    break;

                case 'H': /* --help */
                    goto Help;

                case '?': /* Unknown or ambiguous option */
                case ':': /* Option missing required argument */
                default:
                    goto BadOpt;
            }
        }

        /*  RedGetoptLong() has permuted argv to move all non-option arguments to
         *  the end.  We expect to find a volume identifier.
         */
        if( red_optind >= argc )
        {
            RedPrintf( "Missing volume argument\n" );
            goto BadOpt;
        }

        bVolNum = RedFindVolumeNumber( argv[ red_optind ] );

        if( bVolNum == REDCONF_VOLUME_COUNT )
        {
            RedPrintf( "Error: \"%s\" is not a valid volume identifier.\n", argv[ red_optind ] );
            goto BadOpt;
        }

        if( pbVolNum != NULL )
        {
            *pbVolNum = bVolNum;
        }

        red_optind++; /* Move past volume parameter. */

        if( red_optind < argc )
        {
            int32_t ii;

            for( ii = red_optind; ii < argc; ii++ )
            {
                RedPrintf( "Error: Unexpected command-line argument \"%s\".\n", argv[ ii ] );
            }

            goto BadOpt;
        }

        return PARAMSTATUS_OK;

BadOpt:

        RedPrintf( "%s - invalid parameters\n", argv[ 0U ] );
        usage( argv[ 0U ] );
        return PARAMSTATUS_BAD;

Help:

        usage( argv[ 0U ] );
        return PARAMSTATUS_HELP;
    }


/** @brief Set default fsstress parameters.
 *
 *  @param pParam   Populated with the default fsstress parameters.
 */
    void FsstressDefaultParams( FSSTRESSPARAM * pParam )
    {
        RedMemSet( pParam, 0U, sizeof( *pParam ) );
        pParam->ulLoops = 1U;
        pParam->ulNops = 10000U;
    }


/** @brief Start fsstress.
 *
 *  @param pParam   fsstress parameters, either from FsstressParseParams() or
 *                  constructed programatically.
 *
 *  @return Zero on success, otherwise nonzero.
 */
    int FsstressStart( const FSSTRESSPARAM * pParam )
    {
        char buf[ 10 ];
        int fd;
        int i;
        int cleanup;
        int loops;
        int loopcntr = 1;

        nops = sizeof( ops ) / sizeof( ops[ 0 ] );
        ops_end = &ops[ nops ];

        /*  Copy the already-parsed parameters into the traditional variables.
         */
        cleanup = pParam->fNoCleanup ? 1 : 0;
        loops = pParam->ulLoops;
        operations = pParam->ulNops;
        namerand = pParam->fNamePad ? 1 : 0;
        seed = pParam->ulSeed;
        verbose = pParam->fVerbose ? 1 : 0;

        make_freq_table();

        while( ( loopcntr <= loops ) || ( loops == 0 ) )
        {
            RedSNPrintf( buf, sizeof( buf ), "fss%x", getpid() );
            fd = creat( buf, 0666 );
            maxfsize = ( off64_t ) MAXFSIZE;
            dcache_init();

            if( !seed )
            {
                seed = ( unsigned long ) RedOsClockGetTime();
                RedPrintf( "seed = %ld\n", seed );
            }

            close( fd );
            unlink( buf );
            procid = 0;
            doproc();

            if( cleanup == 0 )
            {
                delete_tree( "/" );

                for( i = 0; i < FT_nft; i++ )
                {
                    flist[ i ].nslots = 0;
                    flist[ i ].nfiles = 0;
                    free( flist[ i ].fents );
                    flist[ i ].fents = NULL;
                }
            }

            loopcntr++;
        }

        return 0;
    }

    static int delete_tree( const char * path )
    {
        REDSTAT sb;
        DIR * dp;
        REDDIRENT * dep;
        char * childpath;
        size_t len;
        int e;

        e = stat( path, &sb );

        if( e )
        {
            return errno;
        }

        if( !S_ISDIR( sb.st_mode ) )
        {
            return unlink( path ) ? errno : 0;
        }

        dp = opendir( path );

        if( dp == NULL )
        {
            return errno;
        }

        while( ( dep = readdir( dp ) ) != NULL )
        {
            len = strlen( path ) + 1 + strlen( dep->d_name ) + 1;
            childpath = malloc( len );

            strcpy( childpath, path );

            if( childpath[ strlen( childpath ) - 1 ] != '/' )
            {
                strcat( childpath, "/" );
            }

            strcat( childpath, dep->d_name );

            e = delete_tree( childpath );

            free( childpath );

            if( e )
            {
                break;
            }
        }

        if( ( e == 0 ) && ( strcmp( path, "/" ) != 0 ) )
        {
            e = rmdir( path ) ? errno : 0;
        }

        closedir( dp );
        return e;
    }

    static void add_to_flist( int ft,
                              int id,
                              int parent )
    {
        fent_t * fep;
        flist_t * ftp;

        ftp = &flist[ ft ];

        if( ftp->nfiles == ftp->nslots )
        {
            ftp->nslots += FLIST_SLOT_INCR;
            ftp->fents = realloc( ftp->fents, ftp->nslots * sizeof( fent_t ) );
        }

        fep = &ftp->fents[ ftp->nfiles++ ];
        fep->id = id;
        fep->parent = parent;
    }

    static void append_pathname( pathname_t * name,
                                 const char * str )
    {
        int len;

        len = strlen( str );
        #ifdef DEBUG
            if( len && ( *str == '/' ) && ( name->len == 0 ) )
            {
                RedPrintf( "fsstress: append_pathname failure\n" );
                chdir( homedir );
                abort();
            }
        #endif
        name->path = realloc( name->path, name->len + 1 + len );
        strcpy( &name->path[ name->len ], str );
        name->len += len;
    }

    static void check_cwd( void )
    {
        #ifdef DEBUG
            REDSTAT statbuf;

            if( ( stat64( ".", &statbuf ) == 0 ) && ( statbuf.st_ino == top_ino ) )
            {
                return;
            }

            chdir( homedir );
            RedPrintf( "fsstress: check_cwd failure\n" );
            abort();
        #endif /* ifdef DEBUG */
    }

    static int creat_path( pathname_t * name,
                           mode_t mode )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = creat( name->path, mode );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = creat_path( &newname, mode );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static void dcache_enter( int dirid,
                              int slot )
    {
        dcache[ dirid % NDCACHE ] = slot;
    }

    static void dcache_init( void )
    {
        int i;

        for( i = 0; i < NDCACHE; i++ )
        {
            dcache[ i ] = -1;
        }
    }

    static fent_t * dcache_lookup( int dirid )
    {
        fent_t * fep;
        int i;

        i = dcache[ dirid % NDCACHE ];

        if( ( i >= 0 ) && ( ( fep = &flist[ FT_DIR ].fents[ i ] )->id == dirid ) )
        {
            return fep;
        }

        return NULL;
    }

    static void dcache_purge( int dirid )
    {
        int * dcp;

        dcp = &dcache[ dirid % NDCACHE ];

        if( ( *dcp >= 0 ) && ( flist[ FT_DIR ].fents[ *dcp ].id == dirid ) )
        {
            *dcp = -1;
        }
    }

    static void del_from_flist( int ft,
                                int slot )
    {
        flist_t * ftp;

        ftp = &flist[ ft ];

        if( ft == FT_DIR )
        {
            dcache_purge( ftp->fents[ slot ].id );
        }

        if( slot != ftp->nfiles - 1 )
        {
            if( ft == FT_DIR )
            {
                dcache_purge( ftp->fents[ ftp->nfiles - 1 ].id );
            }

            ftp->fents[ slot ] = ftp->fents[ --ftp->nfiles ];
        }
        else
        {
            ftp->nfiles--;
        }
    }

    static fent_t * dirid_to_fent( int dirid )
    {
        fent_t * efep;
        fent_t * fep;
        flist_t * flp;

        if( ( fep = dcache_lookup( dirid ) ) )
        {
            return fep;
        }

        flp = &flist[ FT_DIR ];

        for( fep = flp->fents, efep = &fep[ flp->nfiles ]; fep < efep; fep++ )
        {
            if( fep->id == dirid )
            {
                dcache_enter( dirid, ( int ) ( fep - flp->fents ) );
                return fep;
            }
        }

        return NULL;
    }

    static void doproc( void )
    {
        REDSTAT statbuf;
        char buf[ 10 ];
        int opno;
        opdesc_t * p;

        RedSNPrintf( buf, sizeof( buf ), "p%x", procid );
        ( void ) mkdir( buf );

        if( ( chdir( buf ) < 0 ) || ( stat64( ".", &statbuf ) < 0 ) )
        {
            perror( buf );
            _exit( 1 );
        }

        top_ino = statbuf.st_ino;
        homedir = getcwd( NULL, 0 );
        seed += procid;
        srandom( seed );

        if( namerand )
        {
            namerand = random();
        }

        for( opno = 0; opno < operations; opno++ )
        {
            p = &ops[ freq_table[ random() % freq_table_size ] ];

            if( ( unsigned long ) p->func < 4096 )
            {
                abort();
            }

            p->func( opno, random() );
        }

        free( homedir );
    }

    static void fent_to_name( pathname_t * name,
                              flist_t * flp,
                              fent_t * fep )
    {
        char buf[ MAXNAMELEN ];
        int i;
        fent_t * pfep;

        if( fep == NULL )
        {
            return;
        }

        if( fep->parent != -1 )
        {
            pfep = dirid_to_fent( fep->parent );
            fent_to_name( name, &flist[ FT_DIR ], pfep );
            append_pathname( name, "/" );
        }

        i = RedSNPrintf( buf, sizeof( buf ), "%c%x", flp->tag, fep->id );
        namerandpad( fep->id, buf, i );
        append_pathname( name, buf );
    }

    static void fix_parent( int oldid,
                            int newid )
    {
        fent_t * fep;
        flist_t * flp;
        int i;
        int j;

        for( i = 0, flp = flist; i < FT_nft; i++, flp++ )
        {
            for( j = 0, fep = flp->fents; j < flp->nfiles; j++, fep++ )
            {
                if( fep->parent == oldid )
                {
                    fep->parent = newid;
                }
            }
        }
    }

    static void free_pathname( pathname_t * name )
    {
        if( name->path )
        {
            free( name->path );
            name->path = NULL;
            name->len = 0;
        }
    }

    static int generate_fname( fent_t * fep,
                               int ft,
                               pathname_t * name,
                               int * idp,
                               int * v )
    {
        char buf[ MAXNAMELEN ];
        flist_t * flp;
        int id;
        int j;
        int len;

        flp = &flist[ ft ];
        len = RedSNPrintf( buf, sizeof( buf ), "%c%x", flp->tag, id = nameseq++ );
        namerandpad( id, buf, len );

        if( fep )
        {
            fent_to_name( name, &flist[ FT_DIR ], fep );
            append_pathname( name, "/" );
        }

        append_pathname( name, buf );
        *idp = id;
        *v = verbose;

        for( j = 0; !*v && j < ilistlen; j++ )
        {
            if( ilist[ j ] == id )
            {
                *v = 1;
                break;
            }
        }

        return 1;
    }

    static int get_fname( int which,
                          long r,
                          pathname_t * name,
                          flist_t ** flpp,
                          fent_t ** fepp,
                          int * v )
    {
        int c;
        fent_t * fep;
        flist_t * flp;
        int i;
        int j;
        int x;

        for( i = 0, c = 0, flp = flist; i < FT_nft; i++, flp++ )
        {
            if( which & ( 1 << i ) )
            {
                c += flp->nfiles;
            }
        }

        if( c == 0 )
        {
            if( flpp )
            {
                *flpp = NULL;
            }

            if( fepp )
            {
                *fepp = NULL;
            }

            *v = verbose;
            return 0;
        }

        x = ( int ) ( r % c );

        for( i = 0, c = 0, flp = flist; i < FT_nft; i++, flp++ )
        {
            if( which & ( 1 << i ) )
            {
                if( x < c + flp->nfiles )
                {
                    fep = &flp->fents[ x - c ];

                    if( name )
                    {
                        fent_to_name( name, flp, fep );
                    }

                    if( flpp )
                    {
                        *flpp = flp;
                    }

                    if( fepp )
                    {
                        *fepp = fep;
                    }

                    *v = verbose;

                    for( j = 0; !*v && j < ilistlen; j++ )
                    {
                        if( ilist[ j ] == fep->id )
                        {
                            *v = 1;
                            break;
                        }
                    }

                    return 1;
                }

                c += flp->nfiles;
            }
        }

        #ifdef DEBUG
            RedPrintf( "fsstress: get_fname failure\n" );
            abort();
        #endif
        return -1;
    }

    static void init_pathname( pathname_t * name )
    {
        name->len = 0;
        name->path = NULL;
    }

    static int link_path( pathname_t * name1,
                          pathname_t * name2 )
    {
        char buf1[ MAXNAMELEN ];
        char buf2[ MAXNAMELEN ];
        int down1;
        pathname_t newname1;
        pathname_t newname2;
        int rval;

        rval = link( name1->path, name2->path );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name1, buf1, &newname1 );
        separate_pathname( name2, buf2, &newname2 );

        if( strcmp( buf1, buf2 ) == 0 )
        {
            if( chdir( buf1 ) == 0 )
            {
                rval = link_path( &newname1, &newname2 );
                chdir( ".." );
            }
        }
        else
        {
            if( strcmp( buf1, ".." ) == 0 )
            {
                down1 = 0;
            }
            else if( strcmp( buf2, ".." ) == 0 )
            {
                down1 = 1;
            }
            else if( strlen( buf1 ) == 0 )
            {
                down1 = 0;
            }
            else if( strlen( buf2 ) == 0 )
            {
                down1 = 1;
            }
            else
            {
                down1 = MAX( newname1.len, 3 + name2->len ) <=
                        MAX( 3 + name1->len, newname2.len );
            }

            if( down1 )
            {
                free_pathname( &newname2 );
                append_pathname( &newname2, "../" );
                append_pathname( &newname2, name2->path );

                if( chdir( buf1 ) == 0 )
                {
                    rval = link_path( &newname1, &newname2 );
                    chdir( ".." );
                }
            }
            else
            {
                free_pathname( &newname1 );
                append_pathname( &newname1, "../" );
                append_pathname( &newname1, name1->path );

                if( chdir( buf2 ) == 0 )
                {
                    rval = link_path( &newname1, &newname2 );
                    chdir( ".." );
                }
            }
        }

        free_pathname( &newname1 );
        free_pathname( &newname2 );
        return rval;
    }

    static int lstat64_path( pathname_t * name,
                             REDSTAT * sbuf )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = lstat64( name->path, sbuf );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = lstat64_path( &newname, sbuf );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static void make_freq_table( void )
    {
        int f;
        int i;
        opdesc_t * p;

        for( p = ops, f = 0; p < ops_end; p++ )
        {
            f += p->freq;
        }

        freq_table = malloc( f * sizeof( *freq_table ) );
        freq_table_size = f;

        for( p = ops, i = 0; p < ops_end; p++ )
        {
            for( f = 0; f < p->freq; f++, i++ )
            {
                freq_table[ i ] = p->op;
            }
        }
    }

    static int mkdir_path( pathname_t * name,
                           mode_t mode )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = mkdir( name->path );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = mkdir_path( &newname, mode );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static void namerandpad( int id,
                             char * buf,
                             int len )
    {
        int bucket;
        static int buckets[ 8 ] = { 0 };
        static int bucket_count = 0;
        int bucket_value;
        int i;
        int padlen;
        int padmod;

        if( namerand == 0 )
        {
            return;
        }

        /*  buckets[] used to be a statically initialized array with the following
         *  initializer: { 2, 4, 8, 16, 32, 64, 128, MAXNAMELEN - 1 }
         *
         *  The problem is that with Reliance Edge, the maximum name length might be
         *  less than 128.  So the below code populates buckets[] in a similar
         *  fashion but avoids name lengths longer than the maximum.  For example,
         *  if the max name is 20, the resulting array is { 2, 4, 8, 16, 20 }.
         */
        if( !bucket_count )
        {
            bucket_count = sizeof( buckets ) / sizeof( buckets[ 0 ] );
            bucket_value = 2;

            for( i = 0; i < bucket_count; i++ )
            {
                if( ( bucket_value > 128 ) || ( bucket_value >= ( int ) MAXNAMELEN - 1 ) )
                {
                    break;
                }

                buckets[ i ] = bucket_value;
                bucket_value *= 2;
            }

            if( i < bucket_count )
            {
                buckets[ i ] = MAXNAMELEN - 1;
                i++;
            }

            bucket_count = i;
        }

        bucket = ( id ^ namerand ) % bucket_count;
        padmod = buckets[ bucket ] + 1 - len;

        if( padmod <= 0 )
        {
            return;
        }

        padlen = ( id ^ namerand ) % padmod;

        if( padlen )
        {
            memset( &buf[ len ], 'X', padlen );
            buf[ len + padlen ] = '\0';
        }
    }

    static int open_path( pathname_t * name,
                          int oflag )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = open( name->path, oflag );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = open_path( &newname, oflag );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static DIR * opendir_path( pathname_t * name )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        DIR * rval;

        rval = opendir( name->path );

        if( rval || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = opendir_path( &newname );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static int rename_path( pathname_t * name1,
                            pathname_t * name2 )
    {
        char buf1[ MAXNAMELEN ];
        char buf2[ MAXNAMELEN ];
        int down1;
        pathname_t newname1;
        pathname_t newname2;
        int rval;

        rval = rename( name1->path, name2->path );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name1, buf1, &newname1 );
        separate_pathname( name2, buf2, &newname2 );

        if( strcmp( buf1, buf2 ) == 0 )
        {
            if( chdir( buf1 ) == 0 )
            {
                rval = rename_path( &newname1, &newname2 );
                chdir( ".." );
            }
        }
        else
        {
            if( strcmp( buf1, ".." ) == 0 )
            {
                down1 = 0;
            }
            else if( strcmp( buf2, ".." ) == 0 )
            {
                down1 = 1;
            }
            else if( strlen( buf1 ) == 0 )
            {
                down1 = 0;
            }
            else if( strlen( buf2 ) == 0 )
            {
                down1 = 1;
            }
            else
            {
                down1 = MAX( newname1.len, 3 + name2->len ) <=
                        MAX( 3 + name1->len, newname2.len );
            }

            if( down1 )
            {
                free_pathname( &newname2 );
                append_pathname( &newname2, "../" );
                append_pathname( &newname2, name2->path );

                if( chdir( buf1 ) == 0 )
                {
                    rval = rename_path( &newname1, &newname2 );
                    chdir( ".." );
                }
            }
            else
            {
                free_pathname( &newname1 );
                append_pathname( &newname1, "../" );
                append_pathname( &newname1, name1->path );

                if( chdir( buf2 ) == 0 )
                {
                    rval = rename_path( &newname1, &newname2 );
                    chdir( ".." );
                }
            }
        }

        free_pathname( &newname1 );
        free_pathname( &newname2 );
        return rval;
    }

    static int rmdir_path( pathname_t * name )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = rmdir( name->path );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = rmdir_path( &newname );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static void separate_pathname( pathname_t * name,
                                   char * buf,
                                   pathname_t * newname )
    {
        char * slash;

        init_pathname( newname );
        slash = strchr( name->path, '/' );

        if( slash == NULL )
        {
            buf[ 0 ] = '\0';
            return;
        }

        *slash = '\0';
        strcpy( buf, name->path );
        *slash = '/';
        append_pathname( newname, slash + 1 );
    }

    static int stat64_path( pathname_t * name,
                            REDSTAT * sbuf )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = stat64( name->path, sbuf );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = stat64_path( &newname, sbuf );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static int truncate64_path( pathname_t * name,
                                off64_t length )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = truncate64( name->path, length );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = truncate64_path( &newname, length );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static int unlink_path( pathname_t * name )
    {
        char buf[ MAXNAMELEN ];
        pathname_t newname;
        int rval;

        rval = unlink( name->path );

        if( ( rval >= 0 ) || ( errno != RED_ENAMETOOLONG ) )
        {
            return rval;
        }

        separate_pathname( name, buf, &newname );

        if( chdir( buf ) == 0 )
        {
            rval = unlink_path( &newname );
            chdir( ".." );
        }

        free_pathname( &newname );
        return rval;
    }

    static void usage( const char * progname )
    {
        RedPrintf( "usage: %s VolumeID [Options]\n", progname );
        RedPrintf( "File system stress test.\n\n" );
        RedPrintf( "Where:\n" );
        RedPrintf( "  VolumeID\n" );
        RedPrintf( "      A volume number (e.g., 2) or a volume path prefix (e.g., VOL1: or /data)\n" );
        RedPrintf( "      of the volume to test.\n" );
        RedPrintf( "And 'Options' are any of the following:\n" );
        RedPrintf( "  --no-cleanup, -c\n" );
        RedPrintf( "      Specifies not to remove files (cleanup) after execution\n" );
        RedPrintf( "  --loops=count, -l count\n" );
        RedPrintf( "      Specifies the number of times the entire test should loop.  Use 0 for\n" );
        RedPrintf( "      infinite.  Default 1.\n" );
        RedPrintf( "  --nops=count, -n count\n" );
        RedPrintf( "      Specifies the number of operations to run (default 10000).\n" );
        RedPrintf( "  --namepad, -r\n" );
        RedPrintf( "      Specifies to use random name padding (resulting in longer names).\n" );
        RedPrintf( "  --seed=value, -s value\n" );
        RedPrintf( "      Specifies the seed for the random number generator (default timestamp).\n" );
        RedPrintf( "  --verbose, -v\n" );
        RedPrintf( "      Specifies verbose mode (without this, test is very quiet).\n" );
        RedPrintf( "  --dev=devname, -D devname\n" );
        RedPrintf( "      Specifies the device name.  This is typically only meaningful when\n" );
        RedPrintf( "      running the test on a host machine.  This can be \"ram\" to test on a RAM\n" );
        RedPrintf( "      disk, the path and name of a file disk (e.g., red.bin); or an OS-specific\n" );
        RedPrintf( "      reference to a device (on Windows, a drive letter like G: or a device name\n" );
        RedPrintf( "      like \\\\.\\PhysicalDrive7).\n" );
        RedPrintf( "  --help, -H\n" );
        RedPrintf( "      Prints this usage text and exits.\n\n" );
        RedPrintf( "Warning: This test will format the volume -- destroying all existing data.\n\n" );
    }

    static void creat_f( int opno,
                         long r )
    {
        int e;
        int e1;
        pathname_t f;
        int fd;
        fent_t * fep;
        int id;
        int parid;
        int type;
        int v;
        int v1;
        int esz = 0;

        if( !get_fname( FT_DIRm, r, NULL, NULL, &fep, &v1 ) )
        {
            parid = -1;
        }
        else
        {
            parid = fep->id;
        }

        init_pathname( &f );
        type = rtpct ? ( ( int ) ( random() % 100 ) > rtpct ? FT_REG : FT_RTF ) : FT_REG;
        e = generate_fname( fep, type, &f, &id, &v );
        v |= v1;

        if( !e )
        {
            if( v )
            {
                fent_to_name( &f, &flist[ FT_DIR ], fep );
                RedPrintf( "%d/%d: creat - no filename from %s\n",
                           procid, opno, f.path );
            }

            free_pathname( &f );
            return;
        }

        fd = creat_path( &f, 0666 );
        e = fd < 0 ? errno : 0;
        e1 = 0;
        check_cwd();
        esz = 0;

        if( fd >= 0 )
        {
            add_to_flist( type, id, parid );
            close( fd );
        }

        if( v )
        {
            RedPrintf( "%d/%d: creat %s x:%d %d %d\n", procid, opno, f.path,
                       esz, e, e1 );
        }

        free_pathname( &f );
    }

    static void fdatasync_f( int opno,
                             long r )
    {
        int e;
        pathname_t f;
        int fd;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_REGFILE, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: fdatasync - no filename\n",
                           procid, opno );
            }

            free_pathname( &f );
            return;
        }

        fd = open_path( &f, O_WRONLY );
        e = fd < 0 ? errno : 0;
        check_cwd();

        if( fd < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: fdatasync - open %s failed %d\n",
                           procid, opno, f.path, e );
            }

            free_pathname( &f );
            return;
        }

        e = fdatasync( fd ) < 0 ? errno : 0;

        if( v )
        {
            RedPrintf( "%d/%d: fdatasync %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
        close( fd );
    }

    static void fsync_f( int opno,
                         long r )
    {
        int e;
        pathname_t f;
        int fd;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_REGFILE, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: fsync - no filename\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        fd = open_path( &f, O_WRONLY );
        e = fd < 0 ? errno : 0;
        check_cwd();

        if( fd < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: fsync - open %s failed %d\n",
                           procid, opno, f.path, e );
            }

            free_pathname( &f );
            return;
        }

        e = fsync( fd ) < 0 ? errno : 0;

        if( v )
        {
            RedPrintf( "%d/%d: fsync %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
        close( fd );
    }

    static void getdents_f( int opno,
                            long r )
    {
        DIR * dir;
        pathname_t f;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_DIRm, r, &f, NULL, NULL, &v ) )
        {
            append_pathname( &f, "." );
        }

        dir = opendir_path( &f );
        check_cwd();

        if( dir == NULL )
        {
            if( v )
            {
                RedPrintf( "%d/%d: getdents - can't open %s\n",
                           procid, opno, f.path );
            }

            free_pathname( &f );
            return;
        }

        while( readdir64( dir ) != NULL )
        {
            continue;
        }

        if( v )
        {
            RedPrintf( "%d/%d: getdents %s 0\n", procid, opno, f.path );
        }

        free_pathname( &f );
        closedir( dir );
    }

    static void link_f( int opno,
                        long r )
    {
        int e;
        pathname_t f;
        fent_t * fep;
        flist_t * flp;
        int id;
        pathname_t l;
        int parid;
        int v;
        int v1;

        init_pathname( &f );

        if( !get_fname( FT_NOTDIR, r, &f, &flp, NULL, &v1 ) )
        {
            if( v1 )
            {
                RedPrintf( "%d/%d: link - no file\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        if( !get_fname( FT_DIRm, random(), NULL, NULL, &fep, &v ) )
        {
            parid = -1;
        }
        else
        {
            parid = fep->id;
        }

        v |= v1;
        init_pathname( &l );
        e = generate_fname( fep, ( int ) ( flp - flist ), &l, &id, &v1 );
        v |= v1;

        if( !e )
        {
            if( v )
            {
                fent_to_name( &l, &flist[ FT_DIR ], fep );
                RedPrintf( "%d/%d: link - no filename from %s\n",
                           procid, opno, l.path );
            }

            free_pathname( &l );
            free_pathname( &f );
            return;
        }

        e = link_path( &f, &l ) < 0 ? errno : 0;
        check_cwd();

        if( e == 0 )
        {
            add_to_flist( ( int ) ( flp - flist ), id, parid );
        }

        if( v )
        {
            RedPrintf( "%d/%d: link %s %s %d\n", procid, opno, f.path, l.path,
                       e );
        }

        free_pathname( &l );
        free_pathname( &f );
    }

    static void mkdir_f( int opno,
                         long r )
    {
        int e;
        pathname_t f;
        fent_t * fep;
        int id;
        int parid;
        int v;
        int v1;

        if( !get_fname( FT_DIRm, r, NULL, NULL, &fep, &v ) )
        {
            parid = -1;
        }
        else
        {
            parid = fep->id;
        }

        init_pathname( &f );
        e = generate_fname( fep, FT_DIR, &f, &id, &v1 );
        v |= v1;

        if( !e )
        {
            if( v )
            {
                fent_to_name( &f, &flist[ FT_DIR ], fep );
                RedPrintf( "%d/%d: mkdir - no filename from %s\n",
                           procid, opno, f.path );
            }

            free_pathname( &f );
            return;
        }

        e = mkdir_path( &f, 0777 ) < 0 ? errno : 0;
        check_cwd();

        if( e == 0 )
        {
            add_to_flist( FT_DIR, id, parid );
        }

        if( v )
        {
            RedPrintf( "%d/%d: mkdir %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
    }

    static void read_f( int opno,
                        long r )
    {
        char * buf;
        int e;
        pathname_t f;
        int fd;
        uint32_t len;
        __int64_t lr;
        off64_t off;
        REDSTAT stb;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_REGFILE, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: read - no filename\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        fd = open_path( &f, O_RDONLY );
        e = fd < 0 ? errno : 0;
        check_cwd();

        if( fd < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: read - open %s failed %d\n",
                           procid, opno, f.path, e );
            }

            free_pathname( &f );
            return;
        }

        if( fstat64( fd, &stb ) < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: read - fstat64 %s failed %d\n",
                           procid, opno, f.path, errno );
            }

            free_pathname( &f );
            close( fd );
            return;
        }

        if( stb.st_size == 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: read - %s zero size\n", procid, opno,
                           f.path );
            }

            free_pathname( &f );
            close( fd );
            return;
        }

        lr = ( ( __int64_t ) random() << 32 ) + random();
        off = ( off64_t ) ( lr % stb.st_size );
        lseek64( fd, off, SEEK_SET );
        len = ( random() % ( getpagesize() * 4 ) ) + 1;
        buf = malloc( len );
        e = read( fd, buf, len ) < 0 ? errno : 0;
        free( buf );

        if( v )
        {
            RedPrintf( "%d/%d: read %s [%lld,%ld] %d\n",
                       procid, opno, f.path, ( long long ) off, ( long int ) len, e );
        }

        free_pathname( &f );
        close( fd );
    }

    static void rename_f( int opno,
                          long r )
    {
        fent_t * dfep;
        int e;
        pathname_t f;
        fent_t * fep;
        flist_t * flp;
        int id;
        pathname_t newf;
        int oldid;
        int parid;
        int v;
        int v1;

        init_pathname( &f );

        if( !get_fname( FT_ANYm, r, &f, &flp, &fep, &v1 ) )
        {
            if( v1 )
            {
                RedPrintf( "%d/%d: rename - no filename\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        if( !get_fname( FT_DIRm, random(), NULL, NULL, &dfep, &v ) )
        {
            parid = -1;
        }
        else
        {
            parid = dfep->id;
        }

        v |= v1;
        init_pathname( &newf );
        e = generate_fname( dfep, ( int ) ( flp - flist ), &newf, &id, &v1 );
        v |= v1;

        if( !e )
        {
            if( v )
            {
                fent_to_name( &f, &flist[ FT_DIR ], dfep );
                RedPrintf( "%d/%d: rename - no filename from %s\n",
                           procid, opno, f.path );
            }

            free_pathname( &newf );
            free_pathname( &f );
            return;
        }

        e = rename_path( &f, &newf ) < 0 ? errno : 0;
        check_cwd();

        if( e == 0 )
        {
            if( flp - flist == FT_DIR )
            {
                oldid = fep->id;
                fix_parent( oldid, id );
            }

            del_from_flist( ( int ) ( flp - flist ), ( int ) ( fep - flp->fents ) );
            add_to_flist( ( int ) ( flp - flist ), id, parid );
        }

        if( v )
        {
            RedPrintf( "%d/%d: rename %s to %s %d\n", procid, opno, f.path,
                       newf.path, e );
        }

        free_pathname( &newf );
        free_pathname( &f );
    }

    static void rmdir_f( int opno,
                         long r )
    {
        int e;
        pathname_t f;
        fent_t * fep;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_DIRm, r, &f, NULL, &fep, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: rmdir - no directory\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        e = rmdir_path( &f ) < 0 ? errno : 0;
        check_cwd();

        if( e == 0 )
        {
            del_from_flist( FT_DIR, ( int ) ( fep - flist[ FT_DIR ].fents ) );
        }

        if( v )
        {
            RedPrintf( "%d/%d: rmdir %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
    }

    static void stat_f( int opno,
                        long r )
    {
        int e;
        pathname_t f;
        REDSTAT stb;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_ANYm, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: stat - no entries\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        e = lstat64_path( &f, &stb ) < 0 ? errno : 0;
        check_cwd();

        if( v )
        {
            RedPrintf( "%d/%d: stat %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
    }

    static void truncate_f( int opno,
                            long r )
    {
        int e;
        pathname_t f;
        __int64_t lr;
        off64_t off;
        REDSTAT stb;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_REGFILE, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: truncate - no filename\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        e = stat64_path( &f, &stb ) < 0 ? errno : 0;
        check_cwd();

        if( e > 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: truncate - stat64 %s failed %d\n",
                           procid, opno, f.path, e );
            }

            free_pathname( &f );
            return;
        }

        lr = ( ( __int64_t ) random() << 32 ) + random();
        off = lr % MIN( stb.st_size + ( 1024 * 1024 ), MAXFSIZE );
        off %= maxfsize;
        e = truncate64_path( &f, off ) < 0 ? errno : 0;
        check_cwd();

        if( v )
        {
            RedPrintf( "%d/%d: truncate %s %lld %d\n", procid, opno, f.path,
                       ( long long ) off, e );
        }

        free_pathname( &f );
    }

    static void unlink_f( int opno,
                          long r )
    {
        int e;
        pathname_t f;
        fent_t * fep;
        flist_t * flp;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_NOTDIR, r, &f, &flp, &fep, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: unlink - no file\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        e = unlink_path( &f ) < 0 ? errno : 0;
        check_cwd();

        if( e == 0 )
        {
            del_from_flist( ( int ) ( flp - flist ), ( int ) ( fep - flp->fents ) );
        }

        if( v )
        {
            RedPrintf( "%d/%d: unlink %s %d\n", procid, opno, f.path, e );
        }

        free_pathname( &f );
    }

    static void write_f( int opno,
                         long r )
    {
        char * buf;
        int e;
        pathname_t f;
        int fd;
        uint32_t len;
        __int64_t lr;
        off64_t off;
        REDSTAT stb;
        int v;

        init_pathname( &f );

        if( !get_fname( FT_REGm, r, &f, NULL, NULL, &v ) )
        {
            if( v )
            {
                RedPrintf( "%d/%d: write - no filename\n", procid, opno );
            }

            free_pathname( &f );
            return;
        }

        fd = open_path( &f, O_WRONLY );
        e = fd < 0 ? errno : 0;
        check_cwd();

        if( fd < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: write - open %s failed %d\n",
                           procid, opno, f.path, e );
            }

            free_pathname( &f );
            return;
        }

        if( fstat64( fd, &stb ) < 0 )
        {
            if( v )
            {
                RedPrintf( "%d/%d: write - fstat64 %s failed %d\n",
                           procid, opno, f.path, errno );
            }

            free_pathname( &f );
            close( fd );
            return;
        }

        lr = ( ( __int64_t ) random() << 32 ) + random();
        off = ( off64_t ) ( lr % MIN( stb.st_size + ( 1024 * 1024 ), MAXFSIZE ) );
        off %= maxfsize;
        lseek64( fd, off, SEEK_SET );
        len = ( random() % ( getpagesize() * 4 ) ) + 1;
        buf = malloc( len );
        memset( buf, nameseq & 0xff, len );
        e = write( fd, buf, len ) < 0 ? errno : 0;
        free( buf );

        if( v )
        {
            RedPrintf( "%d/%d: write %s [%lld,%ld] %d\n",
                       procid, opno, f.path, ( long long ) off, ( long int ) len, e );
        }

        free_pathname( &f );
        close( fd );
    }


    #if REDCONF_CHECKER == 1
        static void check_f( int opno,
                             long r )
        {
            int32_t ret;
            const char * pszVolume = gpRedVolConf->pszPathPrefix;

            ( void ) r;

            errno = 0;

            ret = red_transact( pszVolume );

            if( ret == 0 )
            {
                ret = red_umount( pszVolume );

                if( ret == 0 )
                {
                    int32_t ret2;

                    errno = -RedCoreVolCheck();

                    if( errno != 0 )
                    {
                        ret = -1;
                    }

                    ret2 = red_mount( pszVolume );

                    if( ret == 0 )
                    {
                        ret = ret2;
                    }

                    if( ret2 != 0 )
                    {
                        exit( 1 );
                    }
                }
            }

            if( verbose )
            {
                RedPrintf( "%d/%d: check %s %d\n", procid, opno, pszVolume, errno );
            }
        }
    #endif /* if REDCONF_CHECKER == 1 */


#endif /* FSSTRESS_SUPPORTED */
