/**
 * @file
 *
 * @ingroup libfs
 *
 * @brief RTEMS MSDOS Format Functionality
 *
 * This function formats a disk partition conforming to MS-DOS conventions
 */

/*
 *  Copyright (C) 2004 IMD
 *  Ingenieurbuero fuer Microcomputertechnik Th. Doerfler
 *  <Thomas.Doerfler@imd-systems.de>
 *
 *  All rights reserved.
 *
 *  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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <inttypes.h>

#include <rtems/libio_.h>
#include <rtems/dosfs.h>

#include "fat.h"
#include "fat_fat_operations.h"
#include "msdos.h"

#define ONE_GB (1024L * 1024L * 1024L)

typedef struct {
  uint32_t bytes_per_sector;
  uint32_t totl_sector_cnt;
  uint32_t rsvd_sector_cnt;

  uint32_t sectors_per_cluster;
  uint32_t sectors_per_fat;

  uint32_t fat_start_sec;
  uint32_t files_per_root_dir;
  uint32_t root_dir_sectors;
  uint32_t root_dir_start_sec;
  uint32_t root_dir_fmt_sec_cnt;
  uint32_t mbr_copy_sec; /* location of copy of mbr or 0 */
  uint32_t fsinfo_sec;   /* location of fsinfo sector or 0 */
  uint8_t  fat_num;
  uint8_t  media_code;
  uint8_t  fattype;
  char     OEMName[FAT_BR_OEMNAME_SIZE+1];
  char     VolLabel[FAT_BR_VOLLAB_SIZE+1];
  bool     VolLabel_present;
  uint32_t vol_id;
  bool     skip_alignment;
  char    *sec;
}  msdos_format_param_t;

/*
 * Align to cluster borders
 */
static uint32_t
 loc_align_object (const uint32_t sectors,
                   const uint32_t clustersize,
                   const bool     skip_alignment)
{
  if (! skip_alignment)
    return (sectors + clustersize - 1) & ~(clustersize - 1);
  else
    return sectors;
}

/*
 * Formatted output.
 */
static void
msdos_format_printf (const msdos_format_request_param_t *rqdata,
                     int                                 info_level,
                     const char                         *format, ...)
{
  va_list args;
  va_start (args, format);
  if (rqdata != NULL && rqdata->info_level >= info_level)
  {
    vfprintf (stdout, format, args);
    fflush (stdout);
  }
  va_end (args);
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_read_sec
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     function to read a sector                                             |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 int         fd,                       /* file descriptor index            */
 uint32_t    start_sector,             /* sector number to write to        */
 uint32_t    sector_size,              /* size of sector                   */
 char       *buffer                    /* buffer with read data into       */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  int ret_val = 0;

  if (0 > lseek(fd,((off_t)start_sector)*sector_size,SEEK_SET)) {
    ret_val = -1;
  }
  if (ret_val == 0) {
    if (0 > read(fd,buffer,sector_size)) {
      ret_val = -1;
    }
  }

  return ret_val;
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_write_sec
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     function to write to a sector                                         |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 int         fd,                       /* file descriptor index            */
 uint32_t    start_sector,             /* sector number to write to        */
 uint32_t    sector_size,              /* size of sector                   */
 const char *buffer                    /* buffer with write data           */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  int ret_val = 0;

  if (0 > lseek(fd,((off_t)start_sector)*sector_size,SEEK_SET)) {
    ret_val = -1;
  }
  if (ret_val == 0) {
    if (0 > write(fd,buffer,sector_size)) {
      ret_val = -1;
    }
  }

  return ret_val;
}

/*=========================================================================* \
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_fill_sectors
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     function to fill sectors with byte                                    |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 msdos_format_param_t *fmt_params,
 const msdos_format_request_param_t *rqdata,
 int         fd,                       /* file descriptor index            */
 uint32_t    start_sector,             /* sector number to fill to         */
 uint32_t    sector_cnt,               /* number of sectors to fill to     */
 const char  fill_byte                 /* byte to fill into sectors        */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  int ret_val = 0;
  uint32_t total_sectors = sector_cnt;
  int last_percent = -1;

  /*
   * fill buffer
   */
  if (ret_val == 0) {
    memset(fmt_params->sec,fill_byte,fmt_params->bytes_per_sector);
  }

  msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                       "Filling : ");
  /*
   * write to consecutive sectors
   */
  while ((ret_val == 0) &&
         (sector_cnt > 0)) {
    int percent = (sector_cnt * 100) / total_sectors;
    if (percent != last_percent) {
      if ((percent & 1) == 0)
        msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL, ".");
      last_percent = percent;
    }
    ret_val = msdos_format_write_sec(fd,start_sector,
                                     fmt_params->bytes_per_sector,
                                     fmt_params->sec);
    start_sector++;
    sector_cnt--;
  }

  msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL, "\n");

  if (ret_val)
    msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_INFO,
                         "filling error on sector: %d\n", start_sector);

  return ret_val;
}


/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_gen_volid
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     function to generate a pseudo-random volume id                        |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 uint32_t *volid_ptr                   /* volume ID return pointer         */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  int ret_val = 0;
  int rc;
  struct timeval time_value;

  rc = rtems_clock_get_tod_timeval(&time_value);
  if (rc == RTEMS_SUCCESSFUL) {
    *volid_ptr = time_value.tv_sec + time_value.tv_sec;
  }
  else {
    *volid_ptr = rand();
  }

  return ret_val;
}


/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_eval_sectors_per_cluster
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     function to check/adjust sectors_per_cluster to legal values          |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 int       fattype,                  /* type code of FAT (FAT_FAT12 ...) */
 uint32_t  bytes_per_sector,         /* byte count per sector (512)      */
 const uint32_t  total_sector_cnt,   /* total number of secters per volume */
 const uint32_t  rsvd_sector_cnt,    /* number of reserved sectors */
 const uint32_t  root_dir_sector_cnt,/* number of sectors for the root dir */
 uint8_t   fat_num,                  /* number of fat copies             */
 uint32_t  sectors_per_cluster,      /* sectors per cluster (requested)  */
 const bool skip_alignment,          /* true for no cluster alignment */
 uint32_t *sectors_per_cluster_adj,  /* ret: sec per cluster (granted)   */
 uint32_t *sectors_per_fat_ptr,      /* ret: sectors needed for one FAT  */
 uint32_t *data_cluster_cnt
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{

  bool     finished = false;
  uint32_t fatdata_cluster_cnt;
  uint32_t fat_capacity;
  uint32_t sectors_per_fat;
  uint32_t fatdata_sect_cnt;
  uint32_t fat_sectors_cnt;
  /*
   * ensure, that maximum cluster size (64KiB) is not exceeded
   */
  while (MS_BYTES_PER_CLUSTER_LIMIT / bytes_per_sector < sectors_per_cluster) {
    sectors_per_cluster /= 2;
  }

  do {
    /*
     * compute number of data clusters for current data:
     * - compute cluster count for data AND fat
     * - compute storage size for FAT
     * - subtract from total cluster count
     */
    fatdata_sect_cnt = total_sector_cnt
      - loc_align_object (rsvd_sector_cnt, sectors_per_cluster, skip_alignment);
    if (fattype == FAT_FAT12) {
      fatdata_sect_cnt    = fatdata_sect_cnt
        - loc_align_object (root_dir_sector_cnt, sectors_per_cluster, skip_alignment);
      fatdata_cluster_cnt = fatdata_sect_cnt/sectors_per_cluster;
      fat_capacity        = fatdata_cluster_cnt * 3 / 2;
    }
    else if (fattype == FAT_FAT16) {
      fatdata_sect_cnt    = fatdata_sect_cnt
        - loc_align_object (root_dir_sector_cnt, sectors_per_cluster, skip_alignment);
      fatdata_cluster_cnt = fatdata_sect_cnt/sectors_per_cluster;
      fat_capacity        = fatdata_cluster_cnt * 2;
    }
    else { /* FAT32 */
      fatdata_cluster_cnt = fatdata_sect_cnt/sectors_per_cluster;
      fat_capacity        = fatdata_cluster_cnt * 4;
    }

    sectors_per_fat = ((fat_capacity
                        + (bytes_per_sector - 1))
                       / bytes_per_sector);

    fat_sectors_cnt = loc_align_object (sectors_per_fat * fat_num,
                                        sectors_per_cluster,
                                        skip_alignment);

    *data_cluster_cnt = (fatdata_cluster_cnt -
                        ((fat_sectors_cnt
                          + (sectors_per_cluster - 1))
                         / sectors_per_cluster));
    /*
     * data cluster count too big? Then make clusters bigger
     */
    if (((fattype == FAT_FAT12) && (*data_cluster_cnt > FAT_FAT12_MAX_CLN)) ||
        ((fattype == FAT_FAT16) && (*data_cluster_cnt > FAT_FAT16_MAX_CLN))) {
      sectors_per_cluster *= 2;
    }
    else {
      finished = true;
    }
    /*
     * when maximum cluster size is exceeded, we have invalid data, abort...
     */
    if (fattype == FAT_FAT12) {
      if (MS_BYTES_PER_CLUSTER_LIMIT_FAT12 < (sectors_per_cluster * bytes_per_sector)) {
        finished = true;
      }
    } else if ((sectors_per_cluster * bytes_per_sector)
        > MS_BYTES_PER_CLUSTER_LIMIT) {
      finished = true;
    }
  } while (!finished);

  *sectors_per_cluster_adj = sectors_per_cluster;
  *sectors_per_fat_ptr     = fat_sectors_cnt / fat_num;

  return 0;
}

static uint8_t
msdos_get_fat_type( const uint32_t bytes_per_sector,
                    const uint32_t sectors_per_cluster,
                    const uint32_t number_of_clusters )
{
  uint32_t ms_sectors_per_cluster_limit_FAT12 =
    ( MS_BYTES_PER_CLUSTER_LIMIT_FAT12 +1 ) / bytes_per_sector;
  uint32_t ms_sectors_per_cluster_limit_FAT16 =
    ( 0x8000 +1 ) / bytes_per_sector;
  uint8_t fattype = FAT_FAT32;

  if (   number_of_clusters < FAT_FAT12_MAX_CLN
      && sectors_per_cluster <= ms_sectors_per_cluster_limit_FAT12 ) {
    fattype = FAT_FAT12;
  }
  else if (   number_of_clusters < FAT_FAT16_MAX_CLN
           && sectors_per_cluster <= ms_sectors_per_cluster_limit_FAT16 ) {
    fattype = FAT_FAT16;
  }

  return fattype;
}

static int
msdos_set_sectors_per_cluster_from_request(
  const msdos_format_request_param_t *rqdata,
  msdos_format_param_t               *fmt_params )
{
  int      ret_val = -1;
  uint32_t onebit;

  if ( rqdata != NULL && rqdata->sectors_per_cluster > 0 ) {
    fmt_params->sectors_per_cluster = rqdata->sectors_per_cluster;
  }
  /*
   * check sectors per cluster.
   * must be power of 2
   * must be smaller than or equal to 128
   * sectors_per_cluster*bytes_per_sector must not be bigger than 64K
   */
  for ( onebit = 128; onebit >= 1; onebit = onebit >> 1 ) {
    if ( fmt_params->sectors_per_cluster >= onebit ) {
      fmt_params->sectors_per_cluster = onebit;
      if (   fmt_params->sectors_per_cluster
          <= MS_BYTES_PER_CLUSTER_LIMIT / fmt_params->bytes_per_sector ) {
        /* value is small enough so this value is ok */
        onebit = 1;
        ret_val = 0;
      }
    }
  }

  if (ret_val != 0) {
    errno = EINVAL;
  }

  return ret_val;
}

static void
msdos_set_default_sectors_per_cluster_for_fattype(
  msdos_format_param_t *fmt_params,
  const uint64_t        total_size )
{
  if (   fmt_params->fattype == FAT_FAT12
      || fmt_params->fattype == FAT_FAT16 ) {
    /* start trying with small clusters */
    fmt_params->sectors_per_cluster = 2;
  }
  else {
    uint32_t gigs = ( total_size + ONE_GB ) / ONE_GB;
    int b;
    /* scale with the size of disk... */
    for ( b = 31; b > 0; b-- ) {
      if ( (gigs & ( 1 << b) ) != 0 )
        break;
    }
    fmt_params->sectors_per_cluster = 1 << b;
  }
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_determine_fmt_params
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     determine parameters for formatting                                   |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 int fd,                                       /* disk file descriptor */
 const msdos_format_request_param_t *rqdata,   /* requested fmt parameters */
 msdos_format_param_t               *fmt_params/* computed fmt parameters        */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  int ret_val = 0;
  uint32_t sectors_per_cluster_adj = 0;
  uint64_t total_size = 0;
  uint32_t data_clusters_cnt;
  uint8_t  iteration_cnt = 0;
  uint8_t  fat_type = UINT8_MAX;

  memset(fmt_params,0,sizeof(*fmt_params));

  /*
   * this one is fixed in this implementation.
   * At least one thing we don't have to magically guess...
   */
  if (ret_val == 0) {
    ret_val = rtems_disk_fd_get_media_block_size(fd, &fmt_params->bytes_per_sector);
  }
  if (ret_val == 0) {
    ret_val = rtems_disk_fd_get_block_count(fd, &fmt_params->totl_sector_cnt);
  }
  if (ret_val == 0) {
    total_size = (uint64_t)fmt_params->bytes_per_sector * fmt_params->totl_sector_cnt;
    msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                         "bytes per sector: %" PRIu32 "\ntotal sectors: %" PRIu32 "\ntotal size: %" PRIu64 "\n",
                         fmt_params->bytes_per_sector, fmt_params->totl_sector_cnt, total_size);
  }

  /*
   * determine number of FATs
   */
  if (ret_val == 0) {
    if ((rqdata == NULL) || (rqdata->fat_num == 0)) {
      fmt_params->fat_num = 2;
    }
    else if (rqdata->fat_num <= 6) {
      fmt_params->fat_num = rqdata->fat_num;
    }
    else {
      errno = EINVAL;
      ret_val = -1;
    }
  }

  if (ret_val == 0)
    msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                         "number of fats: %d\n", fmt_params->fat_num);

  /*
   * Now we get sort of a loop when determining things:
   * The FAT type (FAT12/16/32) is determined ONLY from the
   * data cluster count:
   * Disks with data cluster count <  4085 are FAT12.
   * Disks with data cluster count < 65525 are FAT16.
   * The rest is FAT32 (no FAT128 available yet :-)
   *
   * The number of data clusters is the
   * total capacity
   * minus reserved sectors
   * minus root directory ares
   * minus storage needed for the FAT (and its copy/copies).
   *
   * The last item once again depends on the FAT type and the cluster count.
   *
   * So here is what we do in this formatter:
   * - If a FAT type is requested from the caller, we try to modify
   * the cluster size, until the data cluster count is in range
   * - If no FAT type is given, we estimate a useful FAT type from
   * the disk capacity and then adapt the cluster size
   */

  /*
   * determine characteristic values:
   * - number of sectors
   * - number of reserved sectors
   * - number of used sectors
   * - sectors per cluster
   */
  /*
   * determine FAT type and sectors per cluster
   * depends on
   */
  if (ret_val == 0) {
    fmt_params->sectors_per_cluster = 1;
    /*
     * limiting values for disk size, fat type, sectors per cluster
     * NOTE: maximum sect_per_clust is arbitrarily chosen with values that
     * are a compromise concerning capacity and efficency
     */
    uint32_t fat12_sect_per_clust = 8;
    uint32_t fat16_sect_per_clust = 32;

    if (rqdata != NULL && rqdata->sectors_per_cluster != 0) {
      fat12_sect_per_clust = rqdata->sectors_per_cluster;
      fat16_sect_per_clust = rqdata->sectors_per_cluster;
    }

    if (fmt_params->totl_sector_cnt < FAT_FAT12_MAX_CLN * fat12_sect_per_clust) {
      fmt_params->fattype = FAT_FAT12;
      /* start trying with small clusters */
      fmt_params->sectors_per_cluster = 2;
    }
    else if (fmt_params->totl_sector_cnt < FAT_FAT16_MAX_CLN * fat16_sect_per_clust) {
      fmt_params->fattype = FAT_FAT16;
      /* start trying with small clusters */
      fmt_params->sectors_per_cluster = 2;
    }
    else {
      uint32_t gigs = (total_size + ONE_GB) / ONE_GB;
      int b;
      fmt_params->fattype = FAT_FAT32;
      /* scale with the size of disk... */
      for (b = 31; b > 0; b--)
        if ((gigs & (1 << b)) != 0)
          break;
      fmt_params->sectors_per_cluster = 1 << b;
    }

    ret_val = msdos_set_sectors_per_cluster_from_request( rqdata, fmt_params );

    /* For now we will estimate the number of data clusters to the total number
     *  of clusters */
    if (ret_val == 0) {
      data_clusters_cnt =
        fmt_params->totl_sector_cnt / fmt_params->sectors_per_cluster;
    }

    while(   ret_val == 0
          && fmt_params->fattype != fat_type
          && fmt_params->totl_sector_cnt > 0 ) {
      /*
       * Skip aligning structures or d align them
       */
      if (ret_val == 0 && rqdata != NULL)
        fmt_params->skip_alignment = rqdata->skip_alignment;

      if (ret_val == 0) {
        msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                             "sectors per cluster: %d\n", fmt_params->sectors_per_cluster);

        if (fmt_params->fattype == FAT_FAT32) {
          /* recommended: for FAT32, always set reserved sector count to 32 */
          fmt_params->rsvd_sector_cnt = 32;
          /* for FAT32, always set files per root directory 0 */
          fmt_params->files_per_root_dir = 0;
          /* location of copy of MBR */
          fmt_params->mbr_copy_sec = 6;
          /* location of fsinfo sector */
          fmt_params->fsinfo_sec = 1;

        }
        else {
          /* recommended: for FAT12/FAT16, always set reserved sector count to 1 */
          fmt_params->rsvd_sector_cnt = 1;
          /* recommended: for FAT16, set files per root directory to 512 */
          /* for FAT12/FAT16, set files per root directory */
          /* must fill up an even count of sectors         */
          if ((rqdata != NULL) &&
              (rqdata->files_per_root_dir > 0)) {
            fmt_params->files_per_root_dir = rqdata->files_per_root_dir;
          }
          else {
            if (fmt_params->fattype == FAT_FAT16) {
              fmt_params->files_per_root_dir = 512;
            }
            else {
              fmt_params->files_per_root_dir = 64;
            }
          }
          fmt_params->files_per_root_dir = (fmt_params->files_per_root_dir +
                                           (2*fmt_params->bytes_per_sector/
                                           FAT_DIRENTRY_SIZE-1));
          fmt_params->files_per_root_dir -= (fmt_params->files_per_root_dir %
                                            (2*fmt_params->bytes_per_sector
                                            /FAT_DIRENTRY_SIZE));

        }
        fmt_params->root_dir_sectors =
          (((fmt_params->files_per_root_dir * FAT_DIRENTRY_SIZE)
            + fmt_params->bytes_per_sector - 1)
           / fmt_params->bytes_per_sector);
      }
      if (ret_val == 0) {
        /*
         * check values to get legal arrangement of FAT type and cluster count
         */

        ret_val = msdos_format_eval_sectors_per_cluster(fmt_params->fattype,
                     fmt_params->bytes_per_sector,
                     fmt_params->totl_sector_cnt,
                     fmt_params->rsvd_sector_cnt,
                     fmt_params->root_dir_sectors,
                     fmt_params->fat_num,
                     fmt_params->sectors_per_cluster,
                     fmt_params->skip_alignment,
                     &sectors_per_cluster_adj,
                     &fmt_params->sectors_per_fat,
                     &data_clusters_cnt);
        fmt_params->sectors_per_cluster = sectors_per_cluster_adj;
        fat_type = fmt_params->fattype;

        /* Correct the FAT type according to the new data cluster count */
        if ( ret_val == 0 ) {
          fmt_params->fattype = msdos_get_fat_type(
            fmt_params->bytes_per_sector,
            fmt_params->sectors_per_cluster,
            data_clusters_cnt );
          /* Correct sectors per cluster to the fat type specific default value */
          if (fat_type != fmt_params->fattype) {
            msdos_set_default_sectors_per_cluster_for_fattype( fmt_params,
                                                               total_size );
            ret_val = msdos_set_sectors_per_cluster_from_request( rqdata,
                                                                  fmt_params );
          }
        }
        if (fat_type != fmt_params->fattype && 1 < iteration_cnt) {
          --fmt_params->totl_sector_cnt;
        }

      }

      ++iteration_cnt;
    }
  }
  if ( fmt_params->totl_sector_cnt == 0 )
  {
    errno = EINVAL;
    ret_val = -1;
  }

  if (0 == ret_val)
  {
    if (FAT_FAT32 != fmt_params->fattype)
    {
      fmt_params->files_per_root_dir = loc_align_object (fmt_params->root_dir_sectors,
                                                         fmt_params->sectors_per_cluster,
                                                         fmt_params->skip_alignment)
                                       * (fmt_params->bytes_per_sector / FAT_DIRENTRY_SIZE);
    }

    fmt_params->rsvd_sector_cnt = loc_align_object (fmt_params->rsvd_sector_cnt,
                                                    fmt_params->sectors_per_cluster,
                                                    fmt_params->skip_alignment);
  }

  /*
   * determine media code
   */
  if (ret_val == 0) {
    if ((rqdata != NULL) && (rqdata->media != 0)) {
      const char valid_media_codes[] =
        {0xF0,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF};
      if (NULL==memchr(valid_media_codes,
                       rqdata->media,
                       sizeof(valid_media_codes))) {
        ret_val = -1;
        errno = EINVAL;
      }
      else {
        fmt_params->media_code = rqdata->media;
      }
    }
    else {
      fmt_params->media_code = FAT_BR_MEDIA_FIXED;
    }
  }
  /*
   * determine location and size of root directory
   * for formatting
   */
  if (fmt_params->root_dir_sectors > 0) {
    fmt_params->root_dir_start_sec =
      fmt_params->rsvd_sector_cnt
      + (fmt_params-> fat_num*fmt_params->sectors_per_fat);
    fmt_params->root_dir_fmt_sec_cnt = fmt_params->root_dir_sectors;
  }
  else {
    /*
     * for FAT32: root directory is in cluster 2
     */
    fmt_params->root_dir_start_sec =
      fmt_params->rsvd_sector_cnt
      + (fmt_params-> fat_num*fmt_params->sectors_per_fat);
    fmt_params->root_dir_fmt_sec_cnt = fmt_params->sectors_per_cluster;
  }
  /*
   * determine usable OEMName
   */
  if (ret_val == 0) {
      const char *from;
      char        *to = fmt_params->OEMName;
      int          cnt;
      from = "RTEMS"; /* default: make "from" point to OS Name */
    if ((rqdata != NULL) && (rqdata->OEMName != NULL)) {
      from = rqdata->OEMName;
    }
    for (cnt = 0; cnt < (sizeof(fmt_params->OEMName)-1); cnt++) {
      if (isprint((unsigned char)*from)) {
        *to++ = *from++;
      }
      else {
        /*
         * non-printable character in given name, so keep stuck
         * at that character and replace all following characters
         * with a ' '
         */
        *to++=' ';
      }
      *to = '\0';
    }
  }

  /*
   * determine usable Volume Label
   */
  if (ret_val == 0) {
      const char *from;
      char        *to = fmt_params->VolLabel;
      int          cnt;
      from = ""; /* default: make "from" point to empty string */
    if ((rqdata != NULL) &&
        (rqdata->VolLabel != NULL)) {
      from = rqdata->VolLabel;
      fmt_params->VolLabel_present = true;
    }
    for (cnt = 0; cnt < (sizeof(fmt_params->VolLabel)-1); cnt++) {
      if (isprint((unsigned char)*from)) {
        *to++ = *from++;
      }
      else {
        /*
         * non-printable character in given name, so keep stuck
         * at that character and replace all following characters
         * with a ' '
         */
        *to++=' ';
      }
      *to = '\0';
    }
  }

  /*
   * determine usable Volume ID
   */
  if (ret_val == 0) {
    msdos_format_gen_volid(&(fmt_params->vol_id));
  }
  /*
   * Phuuu.... That's it.
   */
  return ret_val;
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_gen_mbr
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     create master boot record content from parameter set                  |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 char mbr[],                           /* sector buffer                    */
 const msdos_format_param_t *fmt_params/* computed fmt parameters          */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  uint32_t  total_sectors_num16 = 0;
  uint32_t  total_sectors_num32 = 0;

  /* store total sector count in either 16 or 32 bit field in mbr */
  if (fmt_params->totl_sector_cnt < 0x10000) {
    total_sectors_num16 = fmt_params->totl_sector_cnt;
  }
  else {
    total_sectors_num32 = fmt_params->totl_sector_cnt;
  }
  /*
   * finally we are there: let's fill in the values into the MBR
   * but first clear the MRB leaving the partition table.
   */
#define RTEMS_IDE_PARTITION_TABLE_OFFSET                  0x1be
#define RTEMS_IDE_PARTITION_TABLE_SIZE                    (4 * 16)
  memset(mbr,0,RTEMS_IDE_PARTITION_TABLE_OFFSET);
  memset(mbr + RTEMS_IDE_PARTITION_TABLE_OFFSET + RTEMS_IDE_PARTITION_TABLE_SIZE,
         0,
         FAT_TOTAL_MBR_SIZE - (RTEMS_IDE_PARTITION_TABLE_OFFSET + RTEMS_IDE_PARTITION_TABLE_SIZE));
  /*
   * FIXME: fill jmpBoot and Boot code...
   * with 0xEB,....
   */
  /*
   * fill OEMName
   */
  memcpy(FAT_GET_ADDR_BR_OEMNAME(mbr),
         fmt_params->OEMName,
         FAT_BR_OEMNAME_SIZE);
  FAT_SET_BR_BYTES_PER_SECTOR(mbr    , fmt_params->bytes_per_sector);
  FAT_SET_BR_SECTORS_PER_CLUSTER(mbr , fmt_params->sectors_per_cluster);
  FAT_SET_BR_RESERVED_SECTORS_NUM(mbr, fmt_params->rsvd_sector_cnt);

  /* number of FATs on medium */
  FAT_SET_BR_FAT_NUM(mbr             , 2); /* standard/recommended value */
  FAT_SET_BR_FILES_PER_ROOT_DIR(mbr  , fmt_params->files_per_root_dir);
  FAT_SET_BR_TOTAL_SECTORS_NUM16(mbr , total_sectors_num16);
  FAT_SET_BR_MEDIA(mbr               , fmt_params->media_code);

  FAT_SET_BR_SECTORS_PER_TRACK(mbr   , 255); /* only needed for INT13... */
  FAT_SET_BR_NUMBER_OF_HEADS(mbr     , 6);   /* only needed for INT13... */
  FAT_SET_BR_HIDDEN_SECTORS(mbr      , 1);   /* only needed for INT13... */

  FAT_SET_BR_TOTAL_SECTORS_NUM32(mbr , total_sectors_num32);
  if (fmt_params->fattype != FAT_FAT32) {
    FAT_SET_BR_SECTORS_PER_FAT(mbr   ,fmt_params->sectors_per_fat);
    FAT_SET_BR_DRVNUM(mbr            , 0); /* only needed for INT13... */
    FAT_SET_BR_RSVD1(mbr             , 0); /* fill with zero */
    FAT_SET_BR_BOOTSIG(mbr           , FAT_BR_BOOTSIG_VAL);
    FAT_SET_BR_VOLID(mbr             , fmt_params->vol_id); /* volume id */
  memcpy(FAT_GET_ADDR_BR_VOLLAB(mbr),
         fmt_params->VolLabel,
         FAT_BR_VOLLAB_SIZE);
    memcpy(FAT_GET_ADDR_BR_FILSYSTYPE(mbr),
           (fmt_params->fattype == FAT_FAT12)
           ? "FAT12   "
           : "FAT16   ",
           FAT_BR_FILSYSTYPE_SIZE);
  }
  else {
    FAT_SET_BR_SECTORS_PER_FAT32(mbr   ,fmt_params->sectors_per_fat);
    FAT_SET_BR_EXT_FLAGS(mbr           , 0);
    FAT_SET_BR_FSVER(mbr               , 0); /* FAT32 Version:0.0 */
    FAT_SET_BR_FAT32_ROOT_CLUSTER(mbr  , 2); /* put root dir to cluster 2 */
    FAT_SET_BR_FAT32_FS_INFO_SECTOR(mbr, 1); /* Put fsinfo  to rsrvd sec 1*/
    FAT_SET_BR_FAT32_BK_BOOT_SECTOR(mbr, fmt_params->mbr_copy_sec ); /* Put MBR copy to rsrvd sec */
    memset(FAT_GET_ADDR_BR_FAT32_RESERVED(mbr),0,FAT_BR_FAT32_RESERVED_SIZE);

    FAT_SET_BR_FAT32_DRVNUM(mbr      , 0); /* only needed for INT13... */
    FAT_SET_BR_FAT32_RSVD1(mbr       , 0); /* fill with zero */
    FAT_SET_BR_FAT32_BOOTSIG(mbr     ,FAT_BR_FAT32_BOOTSIG_VAL);
    FAT_SET_BR_FAT32_VOLID(mbr       , 0); /* not set */
    memset(FAT_GET_ADDR_BR_FAT32_VOLLAB(mbr)   ,0,FAT_BR_VOLLAB_SIZE);
    memcpy(FAT_GET_ADDR_BR_FAT32_FILSYSTYPE(mbr),
           "FAT32   ",
           FAT_BR_FILSYSTYPE_SIZE);
  }
  /*
   * add boot record signature
   */
  FAT_SET_BR_SIGNATURE(mbr,      FAT_BR_SIGNATURE_VAL);

  /*
   * add jump to boot loader at start of sector
   */
  FAT_SET_VAL8(mbr,0,0xeb);
  FAT_SET_VAL8(mbr,1,0x3c);
  FAT_SET_VAL8(mbr,2,0x90);
  /*
   * FIXME: a nice little PC boot loader would be nice here.
   * but where can I get one for free?
   */
  /*
   * Phuuu.... That's it.
   */
  return 0;
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
static int msdos_format_gen_fsinfo
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     create FAT32 fsinfo sector                                            |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 char fsinfo[]                         /* sector buffer                    */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  /*
   * clear fsinfo sector data
   */
  memset(fsinfo,0,FAT_TOTAL_FSINFO_SIZE);
  /*
   * write LEADSIG, STRUCTSIG, TRAILSIG
   */
  FAT_SET_FSINFO_LEAD_SIGNATURE (fsinfo,FAT_FSINFO_LEAD_SIGNATURE_VALUE );
  FAT_SET_FSINFO_STRUC_SIGNATURE(fsinfo,FAT_FSINFO_STRUC_SIGNATURE_VALUE);
  FAT_SET_FSINFO_TRAIL_SIGNATURE(fsinfo,FAT_FSINFO_TRAIL_SIGNATURE_VALUE);
  /*
   * write "empty" values for free cluster count and next cluster number
   */
  FAT_SET_FSINFO_FREE_CLUSTER_COUNT(fsinfo+FAT_FSI_INFO, 0xffffffff);
  FAT_SET_FSINFO_NEXT_FREE_CLUSTER (fsinfo+FAT_FSI_INFO, 0xffffffff);
  return 0;
}

/*=========================================================================*\
| Function:                                                                 |
\*-------------------------------------------------------------------------*/
int msdos_format
(
/*-------------------------------------------------------------------------*\
| Purpose:                                                                  |
|     format device with msdos filesystem                                   |
+---------------------------------------------------------------------------+
| Input Parameters:                                                         |
\*-------------------------------------------------------------------------*/
 const char *devname,                  /* device name                      */
 const msdos_format_request_param_t *rqdata  /* requested fmt parameters   */
                                             /* set to NULL for automatic  */
                                             /* determination              */
 )
/*-------------------------------------------------------------------------*\
| Return Value:                                                             |
|    0, if success, -1 and errno if failed                                  |
\*=========================================================================*/
{
  struct stat          stat_buf;
  int                  ret_val   = 0;
  int                  fd        = -1;
  int                  i;
  msdos_format_param_t fmt_params;

  memset(&fmt_params, 0, sizeof(fmt_params));

  /*
   * open device for writing
   */
  msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL, "open device\n");
  fd = open(devname, O_RDWR);
  if (fd == -1) {
    ret_val= -1;
  }

  /*
   * sanity check on device
   */
  msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                       "stat check: %s\n", devname);
  if (ret_val == 0) {
    ret_val = fstat(fd, &stat_buf);
  }

  msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_INFO,
                       "formating: %s\n", devname);
  /* rtems feature: no block devices, all are character devices */
  if ((ret_val == 0) && (!S_ISBLK(stat_buf.st_mode))) {
    errno = ENOTTY;
    ret_val = -1;
  }

  /*
   * compute formatting parameters
   */
  if (ret_val == 0) {
    ret_val = msdos_format_determine_fmt_params(fd,rqdata,&fmt_params);
  }

  if (ret_val == 0) {
    fmt_params.sec = malloc(fmt_params.bytes_per_sector);
    if (fmt_params.sec == NULL) {
      ret_val = -1;
    }
  }

  /*
   * if requested, write whole disk/partition with 0xe5
   */
  if ((ret_val == 0) &&
      (rqdata != NULL) &&
      !(rqdata->quick_format)) {
    ret_val = msdos_format_fill_sectors
      (&fmt_params,
       rqdata,
       fd,
       0,                            /* start sector */
       fmt_params.totl_sector_cnt,   /* sector count */
       0xe5);
  }

  /*
   * create master boot record
   */
  if (ret_val == 0) {
    /*
     * Read the current MBR to obtain the partition table.
     */
    msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                         "read MRB sector\n");
    ret_val = msdos_format_read_sec(fd,
                                    0,
                                    fmt_params.bytes_per_sector,
                                    fmt_params.sec);
    if (ret_val == 0) {
      msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                           "generate MRB sector\n");
      ret_val = msdos_format_gen_mbr(fmt_params.sec,&fmt_params);
    }

    /*
     * write master boot record to disk
     * also write copy of MBR to disk
     */
    if (ret_val == 0) {
      msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                           "write MRB sector\n");
      ret_val = msdos_format_write_sec(fd,
                                       0,
                                       fmt_params.bytes_per_sector,
                                       fmt_params.sec);
    }
    if ((ret_val == 0) &&
        (fmt_params.mbr_copy_sec != 0)) {
      /*
       * write copy of MBR
       */
      msdos_format_printf (rqdata, MSDOS_FMT_INFO_LEVEL_DETAIL,
                           "write back up MRB sector\n");
      ret_val = msdos_format_write_sec(fd,
                                       fmt_params.mbr_copy_sec ,
                                       fmt_params.bytes_per_sector,
                                       fmt_params.sec);
    }
  }
  /*
   * for FAT32: initialize info sector on disk
   */
  if ((ret_val == 0) &&
      (fmt_params.fsinfo_sec != 0)) {
      ret_val = msdos_format_gen_fsinfo(fmt_params.sec);
  }
  /*
   * write fsinfo sector
   */
  if ((ret_val == 0) &&
      (fmt_params.fsinfo_sec != 0)) {
    ret_val = msdos_format_write_sec(fd,
                                     fmt_params.fsinfo_sec,
                                     fmt_params.bytes_per_sector,
                                     fmt_params.sec);
  }
  /*
   * write FAT as all empty
   * -> write all FAT sectors as zero
   */
  if (ret_val == 0) {
    ret_val = msdos_format_fill_sectors
      (&fmt_params,
       rqdata,
       fd,
       fmt_params.rsvd_sector_cnt,                   /* start sector */
       fmt_params.fat_num*fmt_params.sectors_per_fat,/* sector count */
       0x00);
  }
  /*
   * clear/init root directory
   * -> write all directory sectors as 0x00
   */
  if (ret_val == 0) {
    ret_val = msdos_format_fill_sectors
      (&fmt_params,
       rqdata,
       fd,
       fmt_params.root_dir_start_sec,        /* start sector */
       fmt_params.root_dir_fmt_sec_cnt,      /* sector count */
       0x00);
  }
  /*
   * write volume label to first entry of directory
   */
  if ((ret_val == 0) && fmt_params.VolLabel_present) {
    memset(fmt_params.sec,0,fmt_params.bytes_per_sector);
    memcpy(MSDOS_DIR_NAME(fmt_params.sec),fmt_params.VolLabel,MSDOS_SHORT_NAME_LEN);
    *MSDOS_DIR_ATTR(fmt_params.sec) = MSDOS_ATTR_VOLUME_ID;
    ret_val = msdos_format_write_sec
      (fd,
       fmt_params.root_dir_start_sec,
       fmt_params.bytes_per_sector,
       fmt_params.sec);
  }
  /*
   * write FAT entry 0 as (0xffffff00|Media_type)EOC,
   * write FAT entry 1 as EOC
   * allocate directory in a FAT32 FS
   */
  if (ret_val == 0) {
    uint32_t start_sector;

    /*
     * empty sector: all clusters are free/do not link further on
     */
    memset(fmt_params.sec,0,fmt_params.bytes_per_sector);

    switch(fmt_params.fattype) {
    case FAT_FAT12:
      /* LSBits of FAT entry 0: media_type */
      FAT_SET_VAL8(fmt_params.sec,0,(fmt_params.media_code));
      /* MSBits of FAT entry 0:0xf, LSBits of FAT entry 1: LSB of EOC */
      FAT_SET_VAL8(fmt_params.sec,1,(0x0f | (FAT_FAT12_EOC << 4)));
      /* MSBits of FAT entry 1: MSBits of EOC */
      FAT_SET_VAL8(fmt_params.sec,2,(FAT_FAT12_EOC >> 4));
      break;

    case FAT_FAT16:
      /* FAT entry 0: 0xff00|media_type */
      FAT_SET_VAL8(fmt_params.sec,0,fmt_params.media_code);
      FAT_SET_VAL8(fmt_params.sec,1,0xff);
      /* FAT entry 1: EOC */
      FAT_SET_VAL16(fmt_params.sec,2,FAT_FAT16_EOC);
      break;

    case FAT_FAT32:
      /* FAT entry 0: 0xffffff00|media_type */
      FAT_SET_VAL32(fmt_params.sec,0,0xffffff00|fmt_params.media_code);
      /* FAT entry 1: Not dirty, no IO error, EOC */
      FAT_SET_VAL32(fmt_params.sec,4,0xc0000000|FAT_FAT32_EOC);
      break;

    default:
      ret_val = -1;
      errno = EINVAL;
    }
    if (fmt_params.fattype == FAT_FAT32) {
      /*
       * only first valid cluster (cluster number 2) belongs
       * to root directory, and is end of chain
       * mark this in every copy of the FAT
       */
      FAT_SET_VAL32(fmt_params.sec,8,FAT_FAT32_EOC);
    }

    start_sector = loc_align_object (fmt_params.rsvd_sector_cnt,
                                     fmt_params.sectors_per_cluster,
                                     fmt_params.skip_alignment);
    for (i = 0;
         (i < fmt_params.fat_num) && (ret_val == 0);
         i++) {
      ret_val = msdos_format_write_sec
        (fd,
         start_sector
         + (i * fmt_params.sectors_per_fat),
         fmt_params.bytes_per_sector,
         fmt_params.sec);
    }
  }

  if (ret_val == 0 && rqdata != NULL && rqdata->sync_device) {
    ret_val = rtems_disk_fd_sync(fd);
  }

  /*
   * cleanup:
   * sync and unlock disk
   * free any data structures (not needed now)
   */
  if (fd != -1) {
    close(fd);
  }

  free(fmt_params.sec);

  return ret_val;
}
