/**
 * @file
 *
 * @brief Application Interface to FAT Filesystem
 *
 * @ingroup DOSFS
 */

/*
 *  Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
 *  Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
 *
 *  Modifications to support UTF-8 in the file system are
 *  Copyright (c) 2013 embedded brains GmbH.
 *
 *  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.
 */

#ifndef _RTEMS_DOSFS_H
#define _RTEMS_DOSFS_H

#include <rtems.h>
#include <rtems/libio.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct rtems_dosfs_convert_control rtems_dosfs_convert_control;

/**
 * @brief Converts from UTF-8 into a specific code page.
 *
 * @param[in,out] self The convert control.
 * @param[in] src A well-formed UTF-8 string to be converted.
 * @param[in] src_size The size of the string in bytes (inludes '\\0' if any).
 * @param[out] dst The address the converted string will get copied to.
 * @param[in,out] dst_size The size of the buffer in bytes respectively the
 * number of bytes written to the buffer.
 *
 * @retval 0 Successful operation.
 * @retval EINVAL Conversion was successful, but is not reversible.
 * @retval ENOMEM Conversion failed (possibly due to insufficient buffer size).
 */
typedef int (*rtems_dosfs_utf8_to_codepage)(
  rtems_dosfs_convert_control *self,
  const uint8_t               *src,
  size_t                       src_size,
  char                        *dst,
  size_t                      *dst_size
);

/**
 * @brief Converts from a specific code page into UTF-8
 *
 * @param[in,out] self The convert control.
 * @param[in] src A well-formed string in code page format.
 * @param[in] src_size The size of the string in bytes (inludes '\\0' if any).
 * @param[out] dst The address the converted string will get copied to.
 * @param[in,out] dst_size The size of the buffer in bytes respectively the
 * number of bytes written to the buffer.
 *
 * @retval 0 Successful operation.
 * @retval EINVAL Conversion was successful, but is not reversible.
 * @retval ENOMEM Conversion failed (possibly due to insufficient buffer size).
 */
typedef int (*rtems_dosfs_codepage_to_utf8)(
  rtems_dosfs_convert_control *self,
  const char                  *src,
  size_t                       src_size,
  uint8_t                     *dst,
  size_t                      *dst_size
);

/**
 * @brief Converts from UTF-8 to UTF-16
 *
 * @param[in,out] self The convert control.
 * @param[in] src A well-formed UTF-8 string to be converted.
 * @param[in] src_size The size of the string in bytes (inludes '\\0' if any).
 * @param[out] dst The address the converted string will get copied to
 * @param[in,out] dst_size The size of the buffer in bytes respectively the
 * number of bytes written to the buffer.
 *
 * @retval 0 Successful operation.
 * @retval EINVAL Conversion was successful, but is not reversible.
 * @retval ENOMEM Conversion failed (possibly due to insufficient buffer size).
 */
typedef int (*rtems_dosfs_utf8_to_utf16)(
  rtems_dosfs_convert_control *self,
  const uint8_t               *src,
  size_t                       src_size,
  uint16_t                    *dst,
  size_t                      *dst_size
);

/**
 * @brief Converts from UTF-16 to UTF-8.
 *
 * @param[in,out] self The convert control.
 * @param[in] src A well-formed UTF-16 string to be converted.
 * @param[in] src_size The size of the string in bytes (inludes '\\0' if any).
 * @param[out] dst The address the converted string will get copied to.
 * @param[in,out] dst_size The size of the buffer in bytes respectively the
 * number of bytes written to the buffer
 *
 * @retval 0 Successful operation.
 * @retval EINVAL Conversion was successful, but is not reversible.
 * @retval ENOMEM Conversion failed (possibly due to insufficient buffer size).
 */
typedef int (*rtems_dosfs_utf16_to_utf8)(
  rtems_dosfs_convert_control *self,
  const uint16_t              *src,
  size_t                       src_size,
  uint8_t                     *dst,
  size_t                      *dst_size
);

/**
 * @brief Converts from UTF-8 to Normalized Form Canonical Decomposition.
 *
 * Does canonical decomposition of the UTF-8 string and in addition
 * also converts upper case alphabetic characters to lower case characters
 *
 * @param[in,out] self The convert control.
 * @param[in] src A well-formed UTF-8 string to be normalized and fold.
 * @param[in] src_size The size of the string in bytes (inludes '\\0' if any).
 * @param[out] dst The address the normalized and fold string will get
 * copied to.
 * @param[in,out] dst_size The size of the buffer in bytes respectively the
 * number of bytes written to the buffer.
 *
 * @retval 0 Successful operation.
 * @retval EINVAL Conversion failed.
 * @retval ENOMEM Conversion failed (possibly due to insufficient buffer size).
 * @retval EOVERFLOW Conversion failed.
 * @retval ENOENT Conversion failed.
 */
typedef int (*rtems_dosfs_utf8_normalize_and_fold)(
  rtems_dosfs_convert_control *self,
  const uint8_t               *src,
  size_t                       src_size,
  uint8_t                     *dst,
  size_t                      *dst_size
);

/**
 * @brief Destroys a convert control structure.
 *
 * @param[in,out] self The convert control for destruction.
 */
typedef void (*rtems_dosfs_convert_destroy)(
  rtems_dosfs_convert_control *self
);

/**
 * @brief FAT filesystem convert handler.
 */
typedef struct {
  rtems_dosfs_utf8_to_codepage        utf8_to_codepage;
  rtems_dosfs_codepage_to_utf8        codepage_to_utf8;
  rtems_dosfs_utf8_to_utf16           utf8_to_utf16;
  rtems_dosfs_utf16_to_utf8           utf16_to_utf8;
  rtems_dosfs_utf8_normalize_and_fold utf8_normalize_and_fold;
  rtems_dosfs_convert_destroy         destroy;
} rtems_dosfs_convert_handler;

typedef struct {
  void   *data;
  size_t  size;
} rtems_dosfs_buffer;

/**
 * @brief FAT filesystem convert control.
 *
 * Short file names are stored in the code page format.  Long file names are
 * stored as little-endian UTF-16.  The convert control determines the format
 * conversions to and from the POSIX file name strings.
 */
struct rtems_dosfs_convert_control {
  const rtems_dosfs_convert_handler *handler;
  rtems_dosfs_buffer                 buffer;
};

/**
 * @defgroup DOSFS FAT Filesystem Support
 *
 * @ingroup FileSystemTypesAndMount
 *
 * @brief FAT file system configuration support, format and mount options.
 *
 * A block device can be formatted with a FAT file system with the
 * msdos_format() function.
 *
 * The FAT file system mount operation can be controlled with FAT file system
 * specific mount options, see @ref rtems_dosfs_mount_options.
 *
 * @{
 */

/**
 * @brief Semaphore count per FAT filesystem instance.
 *
 * This can be used for system configuration via <rtems/confdefs.h>.
 */
#define RTEMS_DOSFS_SEMAPHORES_PER_INSTANCE 1

/**
 * @brief FAT filesystem mount options.
 */
typedef struct {
  /**
   * @brief Converter implementation for new file system instance.
   *
   * Note: If you pass a converter to mount, you have to destroy it yourself if
   * mount failed. In a good case it is destroyed at unmount.
   *
   * Before converters have been added to the RTEMS implementation of the FAT
   * file system, the implementation was:
   * - Short names were saved in code page format (as is still the case).
   * - Long names were not saved in UTF-16 format as mandated by the FAT file
   *   system specification.  Instead the character in the local encoding was
   *   stored to the low byte directly and the high byte was set to zero.
   *
   * There are a few compatibility issues due to a non-standard conform
   * implementation of the FAT file system before the UTF-8 support was added.
   * These following issues affect the default converter and the UTF-8
   * converter:
   * - Before UTF-8 support was added, it was possible to create files with the
   *   the same short name in single case and mixed case in a directory.  It
   *   was for example possible to have files "ABC" and "aBc" in a single
   *   directory.  Now this bug is fixed.
   * - Before UTF-8 support was added, it was possible to create files with a
   *   name length of slightly more than 255 characters.  Now the
   *   implementation adheres exactly to the 255 character limit.
   * - Long file names saved before UTF-8 support was added could contain
   *   non-ASCII characters in the low byte which was saved for a long name
   *   character.  With the default converter this means such files can be read
   *   only by their short file name.  With the UTF-8 converter file names will
   *   be read correctly as long as the characters written with the old
   *   implementation were Latin-1 characters.
   *
   * The following sample code demonstrates how to mount a file
   * system with UTF-8 support:
   * @code
   * #include <errno.h>
   * #include <assert.h>
   * #include <rtems/dosfs.h>
   * #include <rtems/libio.h>
   *
   * static int mount_with_utf8(
   *   const char *device_file,
   *   const char *mount_point
   * )
   * {
   *   rtems_dosfs_convert_control *convert_ctrl;
   *   int                          rv;
   *
   *   convert_ctrl = rtems_dosfs_create_utf8_converter( "CP850" );
   *
   *   if ( convert_ctrl != NULL ) {
   *     rtems_dosfs_mount_options mount_opts;
   *
   *     memset( &mount_opts, 0, sizeof( mount_opts ) );
   *     mount_opts.converter = convert_ctrl;
   *
   *     rv = mount_and_make_target_path(
   *       device_file,
   *       mount_point,
   *       RTEMS_FILESYSTEM_TYPE_DOSFS,
   *       RTEMS_FILESYSTEM_READ_WRITE,
   *       &mount_opts
   *     );
   *
   *     if (rv != 0) {
   *       (*mount_opts.converter->handler->destroy)(mount_opts.converter);
   *     }
   *   } else {
   *     rv = -1;
   *     errno = ENOMEM;
   *   }
   *
   *   return rv;
   * }
   * @endcode
   *
   * In case you do not want UTF-8 support, you can simply pass a NULL pointer
   * to mount_and_make_target_path() respectively to mount() instead of the
   * mount_opts address.
   *
   * @see rtems_dosfs_create_default_converter() and
   * rtems_dosfs_create_utf8_converter().
   */
  rtems_dosfs_convert_control *converter;
} rtems_dosfs_mount_options;

/**
 * @brief Allocates and initializes a default converter.
 *
 * This default converter will accept only POSIX file names with pure ASCII
 * characters. This largely corresponds to the file name handling before the
 * optional UTF-8 support was added to the RTEMS implementation of the FAT file
 * system.  This handling is mostly backwards compatible to the previous RTEMS
 * implementation of the FAT file system.
 *
 * For backwards compatibility and the previous RTEMS implementation of the FAT
 * file system please see also @ref rtems_dosfs_mount_options and mount().
 *
 * @retval NULL Something failed.
 * @retval other Pointer to initialized converter.
 */
rtems_dosfs_convert_control *rtems_dosfs_create_default_converter(void);

/**
 * @brief Allocates and initializes a UTF-8 converter.
 *
 * This converter will assume that all file names passed to POSIX file handling
 * methods are UTF-8 strings and will convert them to the selected code page
 * for short file names and to UTF-16 for long file names.  This conversion
 * will be done during reading and writing.  These conversions correspond to
 * the specification of the FAT file system.  This handling is mostly backwards
 * compatible to the previous RTEMS implementation of the FAT file system.
 *
 * For backwards compatibility and the previous RTEMS implementation of the FAT
 * file system please see also @ref rtems_dosfs_mount_options and mount().
 *
 * One possible issue with this converter is: When reading file names which
 * have been created with other implementations of the FAT file system, it can
 * happen that during the conversion to UTF-8 a long file name becomes longer
 * and exceeds the 255 bytes limit.  In such a case only the short file name
 * will get read.
 *
 * @param[in] codepage The iconv() identification string for the used code
 * page.
 *
 * @retval NULL Something failed.
 * @retval other Pointer to initialized converter.
 */
rtems_dosfs_convert_control *rtems_dosfs_create_utf8_converter(
  const char *codepage
);

#define MSDOS_FMT_INFO_LEVEL_NONE   (0)
#define MSDOS_FMT_INFO_LEVEL_INFO   (1)
#define MSDOS_FMT_INFO_LEVEL_DETAIL (2)
#define MSDOS_FMT_INFO_LEVEL_DEBUG  (3)

/**
 * @brief FAT file system format request parameters.
 */
typedef struct {
  /**
   * @brief OEM name string or NULL.
   */
  const char *OEMName;

  /**
   * @brief Volume label string or NULL.
   */
  const char *VolLabel;

  /**
   * @brief Sectors per cluster hint.
   *
   * The format procedure may choose another value.  Use 0 as default value.
   */
  uint32_t sectors_per_cluster;

  /**
   * @brief Number of FATs hint.
   *
   * Use 0 as default value.
   */
  uint32_t fat_num;

  /**
   * @brief Minimum files in root directory for FAT12 and FAT16.
   *
   * The format procedure may choose a greater value.  Use 0 as default value.
   */
  uint32_t files_per_root_dir;

  /**
   * @brief Media code.
   *
   * Use 0 as default value.  The default media code is 0xf8.
   */
  uint8_t media;

  /**
   * @brief Quick format.
   *
   * If set to true, then do not clear data sectors to zero.
   */
  bool quick_format;

  /**
   * @brief Do not align FAT, data cluster, and root directory to a cluster
   * boundary.
   */
  bool skip_alignment;

  /**
   * @brief Synchronize device after write operations.
   */
  bool sync_device;

  /**
   * @brief The amount of info to output.
   */
  int info_level;
} msdos_format_request_param_t;

/**
 * @brief Formats a block device with a FAT file system.
 *
 * @param[in] devname The block device path.
 * @param[in] rqdata The FAT file system format request data.  Use NULL for
 * default parameters.
 *
 * @retval 0 Successful operation.
 * @retval -1 An error occurred.  The @c errno indicates the error.
 */
int msdos_format (
  const char *devname,
  const msdos_format_request_param_t *rqdata
);

/** @} */

int rtems_dosfs_initialize(rtems_filesystem_mount_table_entry_t *mt_entry,
                           const void                           *data);

#ifdef __cplusplus
}
#endif

#endif
