/*
 * Copyright (c) 2012, 2013 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Dornierstr. 4
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif


#include "tmacros.h"

#include <errno.h>
#include <fcntl.h>
#include <dirent.h>

#include <bsp.h>
#include <rtems/io.h>
#include <rtems/libio.h>
#include <rtems/dosfs.h>
#include <rtems/ramdisk.h>
#include <rtems/libcsupport.h>
#include <rtems/userenv.h>
#include "image.h"
#include "image_bin_le_singlebyte.h"
#include "image_bin_le_multibyte.h"
#include "files.h"

#include <errno.h>

const char rtems_test_name[] = "FSDOSFSNAME 1";

#define PRINT_DISK_IMAGE 0

#define MOUNT_DIR "/mnt"
#define MOUNT_DIR_SIZE 4
#define START_DIR_SIZE 4
#define RAMDISK_PATH "/dev/rda"
#define BLOCK_NUM 47
#define BLOCK_SIZE 512
#define VOLUME_LABEL "MyDisk"

#define NUMBER_OF_DIRECTORIES 8
#define NUMBER_OF_FILES 13
#define NUMBER_OF_DIRECTORIES_INVALID 25
#define NUMBER_OF_DIRECTORIES_DUPLICATED 3
#define NUMBER_OF_MULTIBYTE_NAMES_DUPLICATED 2
#define NUMBER_OF_FILES_DUPLICATED 2
#define NUMBER_OF_NAMES_MULTIBYTE 10
#define MAX_NAME_LENGTH ( 255 + 1 )
#define MAX_NAME_LENGTH_INVALID ( 255 + 2 )
#define MAX_DUPLICATES_PER_NAME 3
static const char UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
#define UTF8_BOM_SIZE 3 /* Size of the UTF-8 byte-order-mark */

#define BLOCK_SIZE 512

static rtems_resource_snapshot            before_mount;

static const msdos_format_request_param_t rqdata = {
  .OEMName             = "RTEMS",
  .VolLabel            = VOLUME_LABEL,
  .sectors_per_cluster = 2,
  .fat_num             = 0,
  .files_per_root_dir  = 0,
  .media               = 0,
  .quick_format        = true,
  .skip_alignment      = 0,
  .info_level          = 0
};

static const char                         DIRECTORY_NAMES[NUMBER_OF_DIRECTORIES]
[MAX_NAME_LENGTH] = {
  "a dir",
  "Shortdir",
  "shrtdir",
  "shrt.dir",
  "long_conventional_dir",
  "long_conventional.dir",
  "LongConventionalDir",
  "This is a directory name with with 255 characters. The following numbers are aligned in that way, that the character 0 is the mentioned one. xxxxxx150xxxxxxx160xxxxxxx170xxxxxxx180xxxxxxx190xxxxxxx200xxxxxxx210xxxxxxx220xxxxxxx230xxxxxxx240xxxxxxx250xxxxx"
};

static const char DIRECTORY_NAMES_INVALID[
  NUMBER_OF_DIRECTORIES_INVALID][MAX_NAME_LENGTH_INVALID] = {
  "This is a directory name with with 256 characters. The following numbers are aligned in that way, that the character 0 is the mentioned one. xxxxxx150xxxxxxx160xxxxxxx170xxxxxxx180xxxxxxx190xxxxxxx200xxxxxxx210xxxxxxx220xxxxxxx230xxxxxxx240xxxxxxx250xxxxxx",
  ".",
  "..",
  "...",
  " ",
  "... ...",
  " ... ",
  "",
  "*",
  "/",
  ":",
  "<",
  ">",
  "?",
  "\\",
  "|",
  { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 17, 28, 29, 30, 31},
  {127},
  "э*_то длинное имя",
  "э:_то длинное имя",
  "э<_то длинное имя",
  "э>_то длинное имя",
  "э?_то длинное имя",
  "э|_то длинное имя"
};

static const char NAMES_MULTIBYTE[
  NUMBER_OF_NAMES_MULTIBYTE][MAX_NAME_LENGTH] = {
  "đây là một tên tập tin dài",
  "Bu uzun bir dosya adı",
  "هذا هو اسم ملف طويل",
  "αυτό είναι ένα μεγάλο όνομα αρχείου",
  "это длинное имя",
  "гэта доўгае імя",
  "това е дълго име на файла",
  "这是一个长文件名",
  "shrtname",
  "long_conventional_name"
};

static const char NAMES_MULTIBYTE_IN_CODEPAGE_FORMAT[
  NUMBER_OF_NAMES_MULTIBYTE][MAX_NAME_LENGTH] = {
  "_\2030005~1._t",
  "bu0008~1.bir",
  "__000b~1.__",
  "__000f~1.__",
  "__0012~1.___",
  "__0015~1.___",
  "__0018~1.___",
  "__001a~1",
  "shrtname",
  "long_conventional_name"
};

static const char FILE_NAMES[NUMBER_OF_FILES][
  MAX_NAME_LENGTH] = {
  "a file",
  "shrtfile",
  "ShrtFle",
  "The quick brown.fox",
  "long_conventional_file",
  "This is a filename with with 255 characters. The following numbers are aligned in that way, that the character 0 is the mentioned one. xx140xxxxxxx150xxxxxxx160xxxxxxx170xxxxxxx180xxxxxxx190xxxxxxx200xxxxxxx210xxxxxxx220xxxxxxx230xxxxxxx240xxxxxxx250xxxxx",
  "+",
  ",",
  "a.a",
  ";",
  "=",
  "[",
  "]"
};

typedef struct {
  char name[MAX_NAME_LENGTH];
  unsigned int number_of_duplicates;
  char name_duplicates[MAX_DUPLICATES_PER_NAME][MAX_NAME_LENGTH];
} name_duplicates;

static const name_duplicates DIRECTORY_DUPLICATES[
  NUMBER_OF_DIRECTORIES_DUPLICATED] = {
  {
    "shrtdir",
    3,
    {
      "shrtdir",
      "SHRTDIR",
      "Shrtdir"
    }
  },
  {
    "Kurzdir",
    3,
    {
      "kurzdir",
      "KURZDIR",
      "Kurzdir"
    }
  },
  {
    "long_conventional_dir",
    3,
    {
      "long_conventional_dir",
      "LONG_CONVENTIONAL_DIR",
      "Long_conventional_dir"
    }
  }
};

static const name_duplicates MULTIBYTE_DUPLICATES[
  NUMBER_OF_MULTIBYTE_NAMES_DUPLICATED] = {
  {
    /* The angstroem encoded differently. These encodings might become short entries */
    {0xc3, 0x85}, /* '̊A' */
    2,
    {
      {0xc3, 0x85}, /* '̊A' */
      {0xe2, 0x84, 0xab} /* 'Å' */
    }
  },

  /* Again the angstroem encoded differently,
   * but this time with additional characters in order to enforce a long entry. */
  {
    {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', 0xc3,
     0x85},
    2,
    {
      {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', 0xc3,
       0x85},
      {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', 0xe2,
       0x84, 0xab}
    }
  }
};


static const name_duplicates FILES_DUPLICATES[NUMBER_OF_FILES_DUPLICATED] = {
  {
    "shrtfile",
    3,
    {
      "shrtfile",
      "SHRTFILE",
      "Shrtfile"
    }
  },
  {
    "long_conventional_file",
    3,
    {
      "long_conventional_file",
      "LONG_CONVENTIONAL_FILE",
      "Long_conventional_file"
    }
  }
};

static int path_is_directory( const char *path )
{
  struct stat s_buf;


  if ( stat( path, &s_buf ) )
    return 0;

  return S_ISDIR( s_buf.st_mode );
}

static void delete_folder_tree( const char *directory_name )
{
  DIR           *dp;
  struct dirent *ep;
  char           p_buf[1024] = {0};
  int            rc          = 0;


  dp = opendir( directory_name );
  rtems_test_assert( dp != NULL );

  while ( ( ep = readdir( dp ) ) != NULL && rc == 0 ) {
    if ( 0 != strcmp( ".", ep->d_name )
         && 0 != strcmp( "..", ep->d_name ) ) {
      snprintf( p_buf, sizeof( p_buf ), "%s/%s", directory_name, ep->d_name );

      if ( path_is_directory( p_buf ) ) {
        delete_folder_tree( p_buf );
        rc = rmdir( p_buf );
        rtems_test_assert( rc == 0 );
        rewinddir( dp );
      } else {
        rc = unlink( p_buf );
        rtems_test_assert( rc == 0 );
        rewinddir( dp );
      }
    }
  }

  rc = closedir( dp );
  rtems_test_assert( rc == 0 );
}

static void mount_device( const char *start_dir,
  const rtems_dosfs_mount_options    *mount_opts )
{
  int rc;

  rc = mount(
    RAMDISK_PATH,
    MOUNT_DIR,
    "dosfs",
    RTEMS_FILESYSTEM_READ_WRITE,
    mount_opts );
  rtems_test_assert( rc == 0 );

  rc = mkdir( start_dir, S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 || ( rc == -1 && errno == EEXIST ) );
}

static void mount_device_with_defaults( const char *start_dir )
{
  int rc;


  rc = msdos_format( RAMDISK_PATH, &rqdata );
  rtems_test_assert( rc == 0 );

  rtems_resource_snapshot_take( &before_mount );

  mount_device( start_dir, NULL );
}

static void mount_device_with_iconv(
  const char                *start_dir,
  rtems_dosfs_mount_options *mount_opts
)
{
  int                       rc;

  rc = msdos_format( RAMDISK_PATH, &rqdata );
  rtems_test_assert( rc == 0 );

  rtems_resource_snapshot_take( &before_mount );

  mount_opts->converter = rtems_dosfs_create_utf8_converter( "CP850" );
  rtems_test_assert( mount_opts->converter != NULL );

  mount_device( start_dir, mount_opts );
}

static void unmount_and_close_device( void )
{
  int                     rc;
  rtems_resource_snapshot now;
  bool                    are_resources_freed;


  delete_folder_tree( MOUNT_DIR );

  rc = unmount( MOUNT_DIR );
  rtems_test_assert( rc == 0 );

  are_resources_freed = rtems_resource_snapshot_check( &before_mount );

  if ( !are_resources_freed )
    rtems_resource_snapshot_take( &now );

  rtems_test_assert( are_resources_freed );
}

/*
 * Simply create a few directories. These tests should all succeed
 */
static void test_creating_directories(
  const char        *start_dir,
  const char        *directories,
  const unsigned int number_of_directories )
{
  unsigned int   index;
  int            rc;
  char           dirname[MAX_NAME_LENGTH + strlen( start_dir ) + 1];
  DIR           *dirp;
  struct dirent *dp;


  for ( index = 0; index < number_of_directories; ++index ) {
    snprintf( dirname, sizeof( dirname ), "%s/%s", start_dir, directories
              + ( index * MAX_NAME_LENGTH ) );
    rc = mkdir( dirname, S_IRWXU | S_IRWXG | S_IRWXO );
    rtems_test_assert( rc == 0 );
  }

  dirp = opendir( start_dir );
  rtems_test_assert( NULL != dirp );

  index = 0;
  dp    = readdir( dirp );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( ".", dp->d_name ) );

  dp = readdir( dirp );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( "..", dp->d_name ) );

  dp = readdir( dirp );
  rtems_test_assert( dp != NULL );

  while ( dp != NULL ) {
    rtems_test_assert( 0
                       == strcmp( directories + ( index * MAX_NAME_LENGTH ),
                                  dp->d_name ) );
    ++index;
    dp = readdir( dirp );
  }

  rtems_test_assert( number_of_directories == index );

  rc = closedir( dirp );
  rtems_test_assert( rc == 0 );
}

/*
 * Try creating directories with invalid names.
 */
static void test_creating_invalid_directories( void )
{
  unsigned int index;
  int          rc;
  char         dirname[MAX_NAME_LENGTH_INVALID + MOUNT_DIR_SIZE + 1];


  for ( index = 0; index < NUMBER_OF_DIRECTORIES_INVALID; ++index ) {
    snprintf( dirname,
              sizeof( dirname ),
              "%s/%s",
              MOUNT_DIR,
              DIRECTORY_NAMES_INVALID[index] );
    rc = mkdir( dirname, S_IRWXU | S_IRWXG | S_IRWXO );
    rtems_test_assert( rc == -1 );
  }
}

/*
 * Try creating directories which do already exist
 * (although names may have different capitalization/encoding)
 */
static void test_creating_duplicate_directories(
  const char            *start_dir,
  const name_duplicates *duplicates,
  const unsigned int     number_of_duplicates )
{
  unsigned int index_dir;
  unsigned int index_duplicate;
  int          rc;
  char         dirname[MAX_NAME_LENGTH + MOUNT_DIR_SIZE + START_DIR_SIZE + 2];


  for ( index_dir = 0; index_dir < number_of_duplicates; ++index_dir ) {
    snprintf( dirname, sizeof( dirname ), "%s/%s", start_dir,
              duplicates[index_dir].name );
    rc = mkdir( dirname, S_IRWXU | S_IRWXG | S_IRWXO );
    rtems_test_assert( rc == 0 );

    for ( index_duplicate = 0;
          index_duplicate < duplicates[index_dir].number_of_duplicates;
          ++index_duplicate ) {
      snprintf( dirname, sizeof( dirname ), "%s/%s", start_dir,
                duplicates[index_dir].name_duplicates[index_duplicate] );
      errno = 0;
      rc = mkdir( dirname, S_IRWXU | S_IRWXG | S_IRWXO );
      rtems_test_assert( rc == -1 );
      rtems_test_assert( errno == EEXIST );
    }
  }
}

/*
 * Try creating and opening files with valid names
 */
static void test_handling_files(
  const char        *dirname,
  const char        *file_names,
  const unsigned int number_of_files )
{
  unsigned int index;
  int          rc;
  char         filename[MAX_NAME_LENGTH * 2 + MOUNT_DIR_SIZE + START_DIR_SIZE
                        + 4];
  int          fd;


  for ( index = 0; index < number_of_files; ++index ) {
    snprintf(
      filename,
      sizeof( filename ) - 1,
      "%s/%s",
      dirname,
      file_names + index * MAX_NAME_LENGTH );
    fd = open( filename,
               O_RDWR | O_CREAT,
               S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
    rtems_test_assert( fd >= 0 );

    rc = close( fd );
    rtems_test_assert( rc == 0 );

    /* See if the file still exists and can be found */
    fd = open( filename, O_RDWR );
    rtems_test_assert( fd >= 0 );

    rc = close( fd );
    rtems_test_assert( rc == 0 );
  }
}

/*
 * Try to find (and open) all of the file names from an
 * array in a given directory
 */
static void test_finding_files(
  const char        *dirname,
  const char        *file_names,
  const unsigned int number_of_files )
{
  int            rc;
  DIR           *dir_stream;
  struct dirent *dp;
  int            fd;
  unsigned int   index_file;
  char           filename[MAX_NAME_LENGTH * 2 + MOUNT_DIR_SIZE
                          + START_DIR_SIZE + 4];


  dir_stream = opendir( dirname );
  rtems_test_assert( dir_stream != NULL );

  dp = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( ".", dp->d_name ) );

  dp = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( "..", dp->d_name ) );

  dp         = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  index_file = 0;

  while ( dp != NULL ) {
    rtems_test_assert( 0 == strcmp(
                         file_names + index_file * MAX_NAME_LENGTH,
                         dp->d_name ) );

    snprintf(
      filename,
      sizeof( filename ) - 1,
      "%s/%s",
      dirname,
      file_names + index_file * MAX_NAME_LENGTH );

    /* See if the file still exists and can be found */
    fd = open( filename, O_RDWR );
    rtems_test_assert( fd >= 0 );

    rc = close( fd );
    rtems_test_assert( rc == 0 );

    ++index_file;
    dp = readdir( dir_stream );
  }

  rtems_test_assert( number_of_files == index_file );

  rc = closedir( dir_stream );
  rtems_test_assert( rc == 0 );
}

/*
 * Try opening files which do already exist (with different capitalization in their names)
 */
static void test_duplicated_files( const char *dirname,
  const name_duplicates                       *files_duplicated,
  const unsigned int                           number_of_files_duplicated )
{
  unsigned int index_file;
  unsigned int index_duplicate;
  int          rc;
  char         filename[MAX_NAME_LENGTH + strlen( dirname ) + 1];
  int          fd;


  for ( index_file = 0; index_file < number_of_files_duplicated;
        ++index_file ) {
    snprintf( filename,
              sizeof( filename ) - 1,
              "%s/%s",
              dirname,
              files_duplicated[index_file].name );
    fd = open( filename,
               O_RDWR | O_CREAT,
               S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
    rtems_test_assert( fd >= 0 );

    rc = close( fd );
    rtems_test_assert( rc == 0 );

    for ( index_duplicate = 0;
          index_duplicate < files_duplicated[index_file].number_of_duplicates;
          ++index_duplicate ) {
      snprintf( filename,
                sizeof( filename ) - 1,
                "%s/%s",
                dirname,
                files_duplicated[index_file].name_duplicates[index_duplicate] );
      fd = open( filename, O_RDWR );
      rtems_test_assert( fd >= 0 );

      rc = close( fd );
      rtems_test_assert( rc == 0 );

      errno = 0;
      fd = open( filename, O_RDWR | O_CREAT | O_EXCL );
      rtems_test_assert( fd == -1 );
      rtems_test_assert( errno == EEXIST );
    }

    rc = remove( filename );
    rtems_test_assert( rc == 0 );
  }
}

/*
 * Open and read existing valid directories
 */
static void test_handling_directories(
  const char        *start_dir,
  const char        *directory_names,
  const unsigned int number_of_directories,
  const char        *file_names,
  const unsigned int number_of_files )
{
  unsigned int   index_directory;
  unsigned int   index_file;
  int            rc;
  DIR           *dir_stream;
  char           dirname[MAX_NAME_LENGTH * 2];
  struct dirent *dp;


  for ( index_directory = 0;
        index_directory < number_of_directories;
        ++index_directory ) {
    snprintf(
      dirname,
      sizeof( dirname ) - 1,
      "%s/%s",
      start_dir,
      directory_names + index_directory * MAX_NAME_LENGTH );

    test_handling_files(
      dirname,
      file_names,
      number_of_files );

    dir_stream = opendir( dirname );
    rtems_test_assert( dir_stream != NULL );

    dp = readdir( dir_stream );
    rtems_test_assert( dp != NULL );
    rtems_test_assert( 0 == strcmp( ".", dp->d_name ) );

    dp = readdir( dir_stream );
    rtems_test_assert( dp != NULL );
    rtems_test_assert( 0 == strcmp( "..", dp->d_name ) );

    dp         = readdir( dir_stream );
    rtems_test_assert( dp != NULL );
    index_file = 0;

    while ( dp != NULL ) {
      rtems_test_assert( 0 == strcmp(
                           file_names + index_file * MAX_NAME_LENGTH,
                           dp->d_name ) );
      ++index_file;
      dp = readdir( dir_stream );
    }

    rtems_test_assert( number_of_files == index_file );

    rc = closedir( dir_stream );
    rtems_test_assert( rc == 0 );
  }
}

/*
 * Try to find all sub-directories from an array
 * in a given start directory.
 * In addition try to find and open files
 * in these sub-directories.
 */
static void test_finding_directories(
  const char        *start_dir,
  const char        *directory_names,
  const unsigned int number_of_directories,
  const char        *file_names,
  const unsigned int number_of_files )
{
  unsigned int   index_directory;
  int            rc;
  DIR           *dir_stream;
  struct dirent *dp;
  char           dirname[MAX_NAME_LENGTH * 2];


  dir_stream = opendir( start_dir );
  rtems_test_assert( dir_stream != NULL );

  dp = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( ".", dp->d_name ) );

  dp = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  rtems_test_assert( 0 == strcmp( "..", dp->d_name ) );

  dp              = readdir( dir_stream );
  rtems_test_assert( dp != NULL );
  index_directory = 0;

  while ( dp != NULL ) {
    rtems_test_assert( 0 == strcmp(
                         directory_names + index_directory * MAX_NAME_LENGTH,
                         dp->d_name ) );

    snprintf(
      dirname,
      sizeof( dirname ) - 1,
      "%s/%s",
      start_dir,
      directory_names + index_directory * MAX_NAME_LENGTH );

    test_finding_files(
      dirname,
      file_names,
      number_of_files );

    ++index_directory;
    dp = readdir( dir_stream );
  }

  rtems_test_assert( number_of_directories == index_directory );

  rc = closedir( dir_stream );
  rtems_test_assert( rc == 0 );
}

#if (PRINT_DISK_IMAGE != 0)
static void print_image(
  const char* include_guard,
  const char* image_name
)
{
  #define                   BYTES_PER_ROW     8
  int                       rc;
  int                       fd;
  ssize_t                   bytes_read;
  uint8_t                   buf[BLOCK_SIZE];
  unsigned int              index_block;
  unsigned int              index_row;
  unsigned int              index_column;
  unsigned int              index_buf;
#ifdef SHOW_LINE_NUMBERS
  size_t                    index_row_start = 1;
#endif /* SWOW_LINE_NUMBERS */
  size_t                    bytes_written   = 0;
  const size_t              DISK_SIZE       = BLOCK_SIZE * BLOCK_NUM;
  const unsigned int        ROWS_PER_BLOCK  = BLOCK_SIZE / BYTES_PER_ROW;

  printf ("/*\n"
          " *  Declarations for C structure representing a disk image\n"
          " *\n"
          " *  WARNING: Automatically generated by init.c -- do not edit!\n"
          " */\n"
          "#ifndef %s\n"
          "#define %s\n"
          "\n"
          "#include <sys/types.h>\n"
          "\n"
          "#ifdef __cplusplus\n"
          "extern \"C\" {\n"
          "#endif /* __cplusplus */\n"
          "\n"
          "static unsigned char %s[] = {\n",
          include_guard,
          include_guard,
          image_name);
  fd = open( RAMDISK_PATH, O_RDWR );
  rtems_test_assert( fd >= 0 );

  for ( index_block = 0; index_block < BLOCK_NUM; ++index_block )
  {
    bytes_read = read( fd, &buf[0], BLOCK_SIZE );
    rtems_test_assert( bytes_read = BLOCK_SIZE );

    index_buf = 0;

    for ( index_row = 0; index_row < ROWS_PER_BLOCK; ++index_row )
    {
      printf( "  " );
      for ( index_column = 0;
            index_column < BYTES_PER_ROW;
            ++index_column ) {
        printf("0x%02x", buf[index_buf]);
        ++bytes_written;
        ++index_buf;
        if ( bytes_written < DISK_SIZE ) {
          printf (", ");
        } else {
          printf ("  ");
        }
      }
#ifdef SHOW_LINE_NUMBERS
      printf( "/* %6u - %6u */", index_row_start, bytes_written );
      index_row_start = bytes_written + 1;
#endif /* SWOW_LINE_NUMBERS */
      printf( "\n" );
    }
  }

  rc = close( fd );
  rtems_test_assert( rc == 0 );

  printf ("};\n"
          "#ifdef __cplusplus\n"
          "}\n"
          "#endif /* __cplusplus */\n"
          "\n"
          "#endif /* %s */\n",
          include_guard);
}
#else /* PRINT_DISK_IMAGE */
static void print_image(
  const char* include_guard,
  const char* image_name
)
{
  /* Nothing to be done */
}
#endif /* PRINT_DISK_IMAGE */


static void compare_files(
  const char *file0,
  const char *file1
)
{
  struct stat  stat_buf[2];
  int          fd[2];
  unsigned int index;
  uint8_t      buf[2];
  ssize_t      bytes_read;
  int          rc;

  rc = stat( file0 , &stat_buf[0] );
  rtems_test_assert( rc == 0 );
  rc = stat( file1 , &stat_buf[1] );
  rtems_test_assert( rc == 0 );

  rtems_test_assert( stat_buf[0].st_size == stat_buf[1].st_size );

  fd[0] = open( file0, O_RDONLY );
  rtems_test_assert( fd[0] > 0 );
  fd[1] = open( file1, O_RDONLY );
  rtems_test_assert( fd[1] > 0 );

  for ( index = 0; index < stat_buf[0].st_size; ++index ) {
    bytes_read = read( fd[0], &buf[0], 1 );
    rtems_test_assert( bytes_read == 1 );
    bytes_read = read( fd[1], &buf[1], 1 );
    rtems_test_assert( bytes_read == 1 );
    rtems_test_assert( buf[0] == buf[1] );
  }
  rc = close( fd[0] );
  rtems_test_assert( rc == 0 );
  rc = close( fd[1] );
  rtems_test_assert( rc == 0 );
}

static void compare_directories(
  const char *dir0,
  const char *dir1)
{
  int                       rc;
  DIR                      *dir_stream[2];
  struct dirent            *dp;
  struct stat               stat_buf[2];
  char                     *path[2];
  const unsigned int        PATH_LENGTH = 1024;

  path[0] = calloc( PATH_LENGTH, sizeof(char) );
  rtems_test_assert( path[0] != NULL );
  path[1] = calloc( PATH_LENGTH, sizeof(char) );
  rtems_test_assert( path[1] != NULL );

  dir_stream[0] = opendir( dir0 );
  rtems_test_assert( dir_stream[0] != NULL );

  dir_stream[1] = opendir( dir1 );
  rtems_test_assert( dir_stream[1] != NULL );

  dp = readdir( dir_stream[0] );
  rtems_test_assert( dp != NULL );

  while ( dp != NULL ) {
    rc = snprintf(path[0], PATH_LENGTH, "%s/%s", dir0, dp->d_name);
    rtems_test_assert( rc < PATH_LENGTH );
    rtems_test_assert( rc >= 0 );
    rc = snprintf(path[1], PATH_LENGTH, "%s/%s", dir1, dp->d_name);
    rtems_test_assert( rc < PATH_LENGTH );
    rtems_test_assert( rc >= 0 );

    rc = stat( path[0] , &stat_buf[0] );
    rtems_test_assert( rc == 0 );

    if (   ( strcmp( dp->d_name, "."  ) != 0)
        && ( strcmp( dp->d_name, ".." ) != 0) ) {
      if ( S_ISDIR( stat_buf[0].st_mode ) ) {
        compare_directories( path[0], path[1] );
      } else {
        compare_files( path[0], path[1] );
      }
    }

    dp = readdir( dir_stream[0] );

  }
  rc = closedir( dir_stream[0] );
  rtems_test_assert( rc == 0 );

  rc = closedir( dir_stream[1] );
  rtems_test_assert( rc == 0 );

  free (path[0]);
  free (path[1]);
}

static void compare_image(
  const char                      *mount_dir,
  const char                      *dev,
  const rtems_dosfs_mount_options *mount_opts )
{
  #define MOUNT_DIR2 "/mnt2"
  int                       rc;

  rc = mount_and_make_target_path(
    dev,
    MOUNT_DIR2,
    RTEMS_FILESYSTEM_TYPE_DOSFS,
    RTEMS_FILESYSTEM_READ_WRITE,
    mount_opts );
  rtems_test_assert( rc == 0 );

  compare_directories(mount_dir, MOUNT_DIR2);

  rc = unmount( MOUNT_DIR2 );
  rtems_test_assert( rc == 0 );

  rc = rmdir( MOUNT_DIR2 );
  rtems_test_assert( rc == 0 );

}
/*
 * Test the compatibility with a genuine MS Windows FAT file system.
 */
static void test_compatibility( void )
{
  int                       rc;
  char                      diskpath[] = "/dev/rdd";
  rtems_dosfs_mount_options mount_opts;
  FILE                     *fp;
  int                       buffer;
  unsigned int              index_file = 0;
  unsigned int              index_char;
  unsigned int              offset;
  char                      content_buf[MAX_NAME_LENGTH + strlen( MOUNT_DIR )
                                        + 1];
  char                      file_path[MAX_NAME_LENGTH + strlen( MOUNT_DIR )
                                      + 1];
  DIR                      *dir_stream;
  struct dirent            *dp;


  mount_opts.converter = rtems_dosfs_create_utf8_converter( "CP850" );
  rtems_test_assert( mount_opts.converter != NULL );

  rc = mount_and_make_target_path(
    diskpath,
    MOUNT_DIR,
    RTEMS_FILESYSTEM_TYPE_DOSFS,
    RTEMS_FILESYSTEM_READ_WRITE,
    &mount_opts );
  rtems_test_assert( rc == 0 );

  dir_stream = opendir( MOUNT_DIR );
  rtems_test_assert( dir_stream != NULL );

  dp = readdir( dir_stream );
  rtems_test_assert( dp != NULL );

  while ( dp != NULL ) {
    index_char = 0;

    size_t len = strlen( filenames[index_file] );

    if ( filenames[index_file][len - 1] == '.' )
      rtems_test_assert( ( len - 1 ) == dp->d_namlen );
    else
      rtems_test_assert( len == dp->d_namlen );

    rtems_test_assert( 0
                       == memcmp( &filenames[index_file][0], &dp->d_name[0],
                                  dp->d_namlen ) );

    snprintf( file_path, sizeof( file_path ), "%s/%s", MOUNT_DIR,
              filenames[index_file] );
    fp = fopen( file_path, "r" );
    rtems_test_assert( fp != NULL );

    /* These files should contain their own file names. */
    while ( ( buffer = fgetc( fp ) ) != EOF ) {
      content_buf[index_char] = buffer;
      ++index_char;
    }

    if ( 0 == strncmp( content_buf, UTF8_BOM, UTF8_BOM_SIZE ) )
      offset = UTF8_BOM_SIZE;
    else
      offset = 0;

    rtems_test_assert( 0
                       == memcmp( filenames[index_file],
                                  &content_buf[offset],
                                  index_char - offset ) );

    rc = fclose( fp );
    rtems_test_assert( rc == 0 );

    ++index_file;
    dp = readdir( dir_stream );
  }

  rtems_test_assert( index_file == FILES_FILENAMES_NUMBER_OF );

  rc = closedir( dir_stream );
  rtems_test_assert( rc == 0 );

  rc = unmount( MOUNT_DIR );
  rtems_test_assert( rc == 0 );
}

static void test_end_of_string_matches( void )
{
  int rc;

  rc = mkdir( MOUNT_DIR "/lib.beam", S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 );

  errno = 0;
  rc = unlink( MOUNT_DIR "/proc_lib.beam" );
  rtems_test_assert( rc == -1 );
  rtems_test_assert( errno == ENOENT );

  rc = unlink( MOUNT_DIR "/lib.beam" );
  rtems_test_assert( rc == 0 );
}

static void test_end_of_string_matches_2( void )
{
  int rc;
  int fd;

  fd = open( MOUNT_DIR "/ets.beam", O_RDWR | O_CREAT,
             S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
  rtems_test_assert( fd >= 0 );
  rc = close( fd );
  rtems_test_assert( rc == 0 );

  fd = open( MOUNT_DIR "/sets.beam", O_RDWR | O_CREAT,
             S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
  rtems_test_assert( fd >= 0 );
  rc = close( fd );
  rtems_test_assert( rc == 0 );

  rc = unlink( MOUNT_DIR "/sets.beam" );
  rtems_test_assert( rc == 0 );

  rc = unlink( MOUNT_DIR "/ets.beam" );
  rtems_test_assert( rc == 0 );
}

static void test_full_8_3_name( void )
{
  int rc;

  rc = mkdir( MOUNT_DIR "/txtvsbin.txt", S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 );

  rc = unlink( MOUNT_DIR "/txtvsbin.txt" );
  rtems_test_assert( rc == 0 );
}

static void test_dir_with_same_name_as_volume_label( void )
{
  int  rc;
  DIR *dirp;

  rc = mkdir( MOUNT_DIR "/" VOLUME_LABEL, S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 );

  dirp = opendir( MOUNT_DIR "/" VOLUME_LABEL );
  rtems_test_assert( NULL != dirp );

  rc = closedir( dirp );
  rtems_test_assert( rc == 0 );

  rc = unlink( MOUNT_DIR "/" VOLUME_LABEL );
  rtems_test_assert( rc == 0 );
}

static void test_file_with_same_name_as_volume_label( void )
{
  int rc;
  int fd;

  fd = open( MOUNT_DIR "/" VOLUME_LABEL, O_RDWR | O_CREAT,
             S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
  rtems_test_assert( fd >= 0 );

  rc = close( fd );
  rtems_test_assert( rc == 0 );

  fd = open( MOUNT_DIR "/" VOLUME_LABEL, O_RDWR );
  rtems_test_assert( fd >= 0 );

  rc = close( fd );
  rtems_test_assert( rc == 0 );

  rc = unlink( MOUNT_DIR "/" VOLUME_LABEL );
  rtems_test_assert( rc == 0 );
}

static void test_getcwd( void )
{
  const char *dir_path = MOUNT_DIR "/somedir";
  char cwd_buf[128];
  char *cwd;
  int rc;
  rtems_status_code sc;

  sc = rtems_libio_set_private_env();
  rtems_test_assert( sc == RTEMS_SUCCESSFUL );

  cwd = getcwd( cwd_buf, sizeof( cwd_buf ) );
  rtems_test_assert( cwd != NULL );
  rtems_test_assert( strcmp( cwd, "/" ) == 0 );

  rc = mkdir( dir_path, S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 );

  rc = chdir( dir_path );
  rtems_test_assert( rc == 0 );

  cwd = getcwd( cwd_buf, sizeof( cwd_buf ) );
  rtems_test_assert( cwd != NULL );
  rtems_test_assert( strcmp( cwd, dir_path ) == 0 );

  rc = chdir( "/" );
  rtems_test_assert( rc == 0 );

  rc = unlink( dir_path );
  rtems_test_assert( rc == 0 );

  cwd = getcwd( cwd_buf, sizeof( cwd_buf ) );
  rtems_test_assert( cwd != NULL );
  rtems_test_assert( strcmp( cwd, "/" ) == 0 );

  rtems_libio_use_global_env();
}

static void test_special_cases( void )
{
  test_end_of_string_matches();
  test_end_of_string_matches_2();
  test_full_8_3_name();
  test_file_with_same_name_as_volume_label();
  test_dir_with_same_name_as_volume_label();
  test_getcwd();
}

/*
 * Main test method
 */
static void test( void )
{
  int  rc;
  char start_dir[MOUNT_DIR_SIZE + START_DIR_SIZE + 2];
  rtems_dosfs_mount_options mount_opts[2];

  rc = mkdir( MOUNT_DIR, S_IRWXU | S_IRWXG | S_IRWXO );
  rtems_test_assert( rc == 0 );

  snprintf( start_dir, sizeof( start_dir ), "%s/%s", MOUNT_DIR, "strt" );

  /*
   * Tests with code page 850 compatible directory and file names
   * and the code page 850 backwards compatible default mode mode of the
   * FAT file system
   */
  mount_device_with_defaults( start_dir );

  test_creating_duplicate_directories(
    &start_dir[0],
    &DIRECTORY_DUPLICATES[0],
    NUMBER_OF_DIRECTORIES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_defaults( start_dir );

  test_duplicated_files(
    MOUNT_DIR,
    FILES_DUPLICATES,
    NUMBER_OF_FILES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_defaults( start_dir );

  test_creating_invalid_directories();

  test_creating_directories(
    &start_dir[0],
    &DIRECTORY_NAMES[0][0],
    NUMBER_OF_DIRECTORIES );

  test_handling_directories(
    &start_dir[0],
    &DIRECTORY_NAMES[0][0],
    NUMBER_OF_DIRECTORIES,
    &FILE_NAMES[0][0],
    NUMBER_OF_FILES );

  compare_image(
    MOUNT_DIR,
    "/dev/rdb",
    NULL);

  test_special_cases();

  rc = unmount( MOUNT_DIR );
  rtems_test_assert( rc == 0 );

  /*
   * Again tests with code page 850 compatible directory and file names
   * but with multibyte string compatible conversion methods which use
   * iconv and utf8proc
   */
  mount_opts[0].converter = rtems_dosfs_create_utf8_converter( "CP850" );
  rtems_test_assert( mount_opts[0].converter != NULL );

  rc                     = mount(
    RAMDISK_PATH,
    MOUNT_DIR,
    "dosfs",
    RTEMS_FILESYSTEM_READ_WRITE,
    &mount_opts );
  rtems_test_assert( rc == 0 );

  test_finding_directories(
    &start_dir[0],
    &DIRECTORY_NAMES[0][0],
    NUMBER_OF_DIRECTORIES,
    &FILE_NAMES[0][0],
    NUMBER_OF_FILES );
  unmount_and_close_device();

  mount_device_with_iconv( start_dir, &mount_opts[0] );
  test_creating_invalid_directories();

  test_creating_duplicate_directories(
    &start_dir[0],
    &DIRECTORY_DUPLICATES[0],
    NUMBER_OF_DIRECTORIES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_iconv( start_dir, &mount_opts[0] );

  test_duplicated_files(
    MOUNT_DIR,
    FILES_DUPLICATES,
    NUMBER_OF_FILES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_iconv( start_dir, &mount_opts[0] );

  test_creating_directories(
    &start_dir[0],
    &DIRECTORY_NAMES[0][0],
    NUMBER_OF_DIRECTORIES );

  test_handling_directories(
    &start_dir[0],
    &DIRECTORY_NAMES[0][0],
    NUMBER_OF_DIRECTORIES,
    &FILE_NAMES[0][0],
    NUMBER_OF_FILES );

  mount_opts[1].converter = rtems_dosfs_create_utf8_converter( "CP850" );
  rtems_test_assert( mount_opts[1].converter != NULL );

  compare_image(
    MOUNT_DIR,
    "/dev/rdb",
    &mount_opts[1]);

  test_special_cases();

  rc = unmount( MOUNT_DIR );
  rtems_test_assert( rc == 0 );

  print_image(
      "IMAGE_BIN_LE_SINGLEBYTE_H_",
      "IMAGE_BIN_LE_SINGLEBYTE");

  rc = mount(
    RAMDISK_PATH,
    MOUNT_DIR,
    "dosfs",
    RTEMS_FILESYSTEM_READ_WRITE,
    NULL );
  rtems_test_assert( rc == 0 );

  unmount_and_close_device();


  /*
   * Tests with multibyte directory and file names and
   * with multibyte string compatible conversion methods which use
   * iconv and utf8proc
   */
  mount_device_with_iconv( start_dir, &mount_opts[0] );

  test_creating_duplicate_directories(
    &start_dir[0],
    &MULTIBYTE_DUPLICATES[0],
    NUMBER_OF_MULTIBYTE_NAMES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_iconv( start_dir, &mount_opts[0] );

  test_duplicated_files(
    MOUNT_DIR,
    &MULTIBYTE_DUPLICATES[0],
    NUMBER_OF_MULTIBYTE_NAMES_DUPLICATED );

  unmount_and_close_device();

  mount_device_with_iconv( start_dir, &mount_opts[0] );

  test_creating_directories(
    &start_dir[0],
    &NAMES_MULTIBYTE[0][0],
    NUMBER_OF_NAMES_MULTIBYTE );

  test_handling_directories(
    &start_dir[0],
    &NAMES_MULTIBYTE[0][0],
    NUMBER_OF_NAMES_MULTIBYTE,
    &NAMES_MULTIBYTE[0][0],
    NUMBER_OF_NAMES_MULTIBYTE );

  mount_opts[1].converter = rtems_dosfs_create_utf8_converter( "CP850" );
  rtems_test_assert( mount_opts[1].converter != NULL );

  compare_image(
    MOUNT_DIR,
    "/dev/rdc",
    &mount_opts[1]);

  test_special_cases();

  rc = unmount( MOUNT_DIR );
  rtems_test_assert( rc == 0 );

  print_image(
    "IMAGE_BIN_LE_MULTIBYTE_H_",
    "IMAGE_BIN_LE_MULTIBYTE");

  rc = mount(
    RAMDISK_PATH,
    MOUNT_DIR,
    "dosfs",
    RTEMS_FILESYSTEM_READ_WRITE,
    NULL );
  rtems_test_assert( rc == 0 );

  test_finding_directories(
    &start_dir[0],
    &NAMES_MULTIBYTE_IN_CODEPAGE_FORMAT[0][0],
    NUMBER_OF_NAMES_MULTIBYTE,
    &NAMES_MULTIBYTE_IN_CODEPAGE_FORMAT[0][0],
    NUMBER_OF_NAMES_MULTIBYTE );

  unmount_and_close_device();

  test_compatibility();
}

static void Init( rtems_task_argument arg )
{
  TEST_BEGIN();

  test();

  TEST_END();
  rtems_test_exit( 0 );
}

rtems_ramdisk_config rtems_ramdisk_configuration [] = {
  { .block_size = BLOCK_SIZE, .block_num = BLOCK_NUM },
  { .block_size = BLOCK_SIZE, .block_num = BLOCK_NUM, .location = &IMAGE_BIN_LE_SINGLEBYTE[0] },
  { .block_size = BLOCK_SIZE, .block_num = BLOCK_NUM, .location = &IMAGE_BIN_LE_MULTIBYTE[0] },
  { .block_size = BLOCK_SIZE, .block_num = sizeof( image_bin ) / BLOCK_SIZE, .location = image_bin }
};

size_t rtems_ramdisk_configuration_size = RTEMS_ARRAY_SIZE(rtems_ramdisk_configuration);

#define CONFIGURE_INIT_TASK_STACK_SIZE ( 1024 * 64 )
#define CONFIGURE_APPLICATION_DOES_NOT_NEED_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER
#define CONFIGURE_MAXIMUM_SEMAPHORES (2 * RTEMS_DOSFS_SEMAPHORES_PER_INSTANCE)
#define CONFIGURE_APPLICATION_EXTRA_DRIVERS RAMDISK_DRIVER_TABLE_ENTRY

#define CONFIGURE_APPLICATION_NEEDS_LIBBLOCK

#define CONFIGURE_FILESYSTEM_DOSFS

/* 2 RAM disk device files + 2 mount_dir + stdin + stdout + stderr +
 * 2 for open directories/files  + 4 * 2 for recursive tree compares*/
#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS ( 7 + 2 + ( 4 * 2 ) )

#define CONFIGURE_MAXIMUM_TASKS 1

#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION

#define CONFIGURE_RTEMS_INIT_TASKS_TABLE

#define CONFIGURE_INIT_TASK_ATTRIBUTES RTEMS_FLOATING_POINT

#define CONFIGURE_INIT

#include <rtems/confdefs.h>
