/**
 * @file
 *
 * @ingroup libfs
 *
 * @brief Miscellaneous Routines Implementation for MSDOS FileSystem
 */

/*
 *  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.
 */

#define MSDOS_TRACE 1

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

#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <rtems/libio_.h>

#include "fat.h"
#include "fat_fat_operations.h"
#include "fat_file.h"

#include "msdos.h"


#include <stdio.h>

#define MSDOS_LFN_ENTRY_SIZE \
  (MSDOS_LFN_LEN_PER_ENTRY * MSDOS_NAME_LFN_BYTES_PER_CHAR)

#define MSDOS_LFN_ENTRY_SIZE_UTF8 \
  ((MSDOS_LFN_LEN_PER_ENTRY + 1 ) * MSDOS_NAME_LFN_BYTES_PER_CHAR \
    * MSDOS_NAME_MAX_UTF8_BYTES_PER_CHAR)

/*
 * External strings. Saves space this way.
 */
const char *const MSDOS_DOT_NAME    = ".          ";
const char *const MSDOS_DOTDOT_NAME = "..         ";

uint8_t
msdos_lfn_checksum(const void *entry)
{
    const uint8_t *name;
    uint8_t        cs;
    int            i;

    name = (const uint8_t *) MSDOS_DIR_NAME(entry);
    cs = 0;

    for (i = 0; i < MSDOS_SHORT_NAME_LEN; ++i) {
        cs = ((cs & 1) ? 0x80 : 0) + (cs >> 1) + name[i];
    }

    return cs;
}

/* msdos_is_valid_name_char --
 *     Routine to check the character in a file or directory name.
 *     The characters support in the short file name are letters,
 *     digits, or characters with code points values greater than
 *     127 (not sure what this last is) plus the following special
 *     characters "$%'-_@~`!(){}^#&". The must be uppercase.
 *
 *     The following 6 characters are allowed in a long names,
 *     " +,;=[]" including a space and lower case letters.
 *
 * PARAMETERS:
 *     ch        - character to check.
 *
 * RETURNS:
 *     MSDOS_NAME_INVALID - Not valid in a long or short name.
 *     MSDOS_NAME_SHORT   - Valid in a short name or long name.
 *     MSDOS_NAME_LONG    - Valid in a long name only.
 *
 */
static msdos_name_type_t
msdos_is_valid_name_char(const char ch)
{
    if (strchr(" +,;=[]", ch) != NULL)
        return MSDOS_NAME_LONG;

    if ((ch == '.') || isalnum((unsigned char)ch) ||
        (strchr("$%'-_@~`!(){}^#&", ch) != NULL) || (unsigned char) ch > 127)
        return MSDOS_NAME_SHORT;

    return MSDOS_NAME_INVALID;
}

/* msdos_short_hex_number --
 *     Routine to set the hex number in the SFN.
 *
 * PARAMETERS:
 *     name      - name to change
 *     num       - number to set
 *
 * RETURNS:
 *     nothing
 *
 */
static void
msdos_short_name_hex(char* sfn, uint32_t num)
{
    static const char* hex = "0123456789ABCDEF";
    char* c = MSDOS_DIR_NAME(sfn);
    int   i;
    for (i = 0; i < 2; i++, c++)
      if ((*c == ' ') || (*c == '.'))
        *c = '_';
    for (i = 0; i < 4; i++, c++)
      *c = hex[(num >> ((3 - i) * 4)) & 0xf];
    *c++ = '~';
    *c   = '1';
}

/* msdos_name_type --
 *     Routine the type of file name.
 *
 * PARAMETERS:
 *     name      - name to check
 *
 * RETURNS:
 *     true the name is long, else the name is short.
 *
 */
#define MSDOS_NAME_TYPE_PRINT 0
static msdos_name_type_t
msdos_name_type(const char *name, int name_len)
{
    bool lowercase = false;
    bool uppercase = false;
    int  dot_at = -1;
    int  count = 0;

    while (*name && (count < name_len))
    {
        bool is_dot = *name == '.';
        msdos_name_type_t type = msdos_is_valid_name_char(*name);

#if MSDOS_NAME_TYPE_PRINT
        printf ("MSDOS_NAME_TYPE: c:%02x type:%d\n", *name, type);
#endif

        if ((type == MSDOS_NAME_INVALID) || (type == MSDOS_NAME_LONG))
            return type;

        if (dot_at >= 0)
        {
            if (is_dot || ((count - dot_at) > 3))
            {
#if MSDOS_NAME_TYPE_PRINT
                printf ("MSDOS_NAME_TYPE: LONG[1]: is_dot:%d, at:%d cnt\n",
                        is_dot, dot_at, count);
#endif
                return MSDOS_NAME_LONG;
            }
        }
        else
        {
            if (count == 8 && !is_dot)
            {
#if MSDOS_NAME_TYPE_PRINT
                printf ("MSDOS_NAME_TYPE: LONG[2]: is_dot:%d, at:%d cnt\n",
                        is_dot, dot_at, count);
#endif
                return MSDOS_NAME_LONG;
            }
        }

        if (is_dot)
            dot_at = count;
        else if ((*name >= 'A') && (*name <= 'Z'))
            uppercase = true;
        else if ((*name >= 'a') && (*name <= 'z'))
            lowercase = true;

        count++;
        name++;
    }

    if (lowercase && uppercase)
    {
#if MSDOS_NAME_TYPE_PRINT
        printf ("MSDOS_NAME_TYPE: LONG[3]\n");
#endif
        return MSDOS_NAME_LONG;
    }

#if MSDOS_NAME_TYPE_PRINT
    printf ("MSDOS_NAME_TYPE: SHORT[1]\n");
#endif
    return MSDOS_NAME_SHORT;
}

/* msdos_long_to_short --
 *     Routine to creates a short name from a long. Start the end of the
 *
 * PARAMETERS:
 *     name      - name to check
 *
 * RETURNS:
 *     true the name is long, else the name is short.
 *
 */
#define MSDOS_L2S_PRINT 0
msdos_name_type_t
msdos_long_to_short(rtems_dosfs_convert_control     *converter,
                    const char                      *lfn,
                    int                              lfn_len,
                    char                            *sfn,
                    int                              sfn_len)
{
    msdos_name_type_t type;
    int               eno = 0;
    int               i;
    ssize_t           short_filename_length = sfn_len;
    void             *buffer = converter->buffer.data;
    size_t            codepage_name_len = converter->buffer.size;

    /*
     * Fill with spaces. This is how a short directory entry is padded.
     */
    memset (sfn, ' ', sfn_len);

    /*
     * Handle '.' and '..' specially.
     */
    if ((lfn[0] == '.') && (lfn_len == 1))
    {
        sfn[0] = '.';
#if MSDOS_L2S_PRINT
        printf ("MSDOS_L2S: SHORT[1]: lfn:'%s' SFN:'%s'\n", lfn, sfn);
#endif
        return MSDOS_NAME_SHORT;
    }

    if ((lfn[0] == '.') && (lfn[1] == '.') && (lfn_len == 2))
    {
        sfn[0] = sfn[1] = '.';
#if MSDOS_L2S_PRINT
        printf ("MSDOS_L2S: SHORT[2]: lfn:'%s' SFN:'%s'\n", lfn, sfn);
#endif
        return MSDOS_NAME_SHORT;
    }

    /*
     * Filenames with only blanks and dots are not allowed!
     */
    for (i = 0; i < lfn_len; i++)
        if ((lfn[i] != ' ') && (lfn[i] != '.'))
            break;

    if (i == lfn_len)
    {
#if MSDOS_L2S_PRINT
        printf ("MSDOS_L2S: INVALID[1]: lfn:'%s' SFN:'%s'\n", lfn, sfn);
#endif
        return MSDOS_NAME_INVALID;
    }

    /*
     * Is this a short name ?
     */

    eno = (*converter->handler->utf8_to_codepage) (
        converter,
        (const uint8_t*)&lfn[0],
        lfn_len,
        buffer,
        &codepage_name_len);
    if (eno == EINVAL)
    {
        eno = 0;
        type = MSDOS_NAME_LONG;
    }
    else
    {
        type = msdos_name_type (
            buffer,
            codepage_name_len);
    }

    if (type != MSDOS_NAME_INVALID)
    {
        short_filename_length = msdos_filename_utf8_to_short_name_for_save (
            converter,
            (const uint8_t*)lfn,
            lfn_len,
            sfn,
            short_filename_length);
        if (short_filename_length < 0 ) {
            type = MSDOS_NAME_INVALID;
        }
#if MSDOS_L2S_PRINT
        printf ("MSDOS_L2S: TYPE:%d lfn:'%s' SFN:'%s'\n", type, lfn, sfn);
#endif
    }
    else
    {
#if MSDOS_L2S_PRINT
        printf ("MSDOS_L2S: INVALID[2]: lfn:'%s' SFN:'%s'\n", lfn, sfn);
#endif
    }

    return type;
}

/* msdos_find_name --
 *     Find the node which correspondes to the name, open fat-file which
 *     correspondes to the found node and close fat-file which correspondes
 *     to the node we searched in.
 *
 * PARAMETERS:
 *     parent_loc - parent node description
 *     name       - name to find
 *
 * RETURNS:
 *     RC_OK and updated 'parent_loc' on success, or -1 if error
 *     occured (errno set apropriately)
 *
 */
int
msdos_find_name(
    rtems_filesystem_location_info_t *parent_loc,
    const char                       *name,
    int                               name_len
    )
{
    int                rc = RC_OK;
    msdos_fs_info_t   *fs_info = parent_loc->mt_entry->fs_info;
    fat_file_fd_t     *fat_fd = NULL;
    msdos_name_type_t  name_type;
    fat_dir_pos_t      dir_pos;
    unsigned short     time_val = 0;
    unsigned short     date = 0;
    char               node_entry[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];

    memset(node_entry, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);

    name_type = msdos_long_to_short (
        fs_info->converter,
        name,
        name_len,
        MSDOS_DIR_NAME(node_entry),
        MSDOS_NAME_MAX);

    /*
     * find the node which corresponds to the name in the directory pointed by
     * 'parent_loc'
     */
    rc = msdos_get_name_node(parent_loc, false, name, name_len, name_type,
                             &dir_pos, node_entry);
    if (rc != RC_OK)
        return rc;

    if (((*MSDOS_DIR_ATTR(node_entry)) & MSDOS_ATTR_VOLUME_ID) ||
        ((*MSDOS_DIR_ATTR(node_entry) & MSDOS_ATTR_LFN_MASK) == MSDOS_ATTR_LFN))
        return MSDOS_NAME_NOT_FOUND_ERR;

    /* open fat-file corresponded to the found node */
    rc = fat_file_open(&fs_info->fat, &dir_pos, &fat_fd);
    if (rc != RC_OK)
        return rc;

    fat_fd->dir_pos = dir_pos;

    /*
     * I don't like this if, but: we should do it, or should write new file
     * size and first cluster num to the disk after each write operation
     * (even if one byte is written  - that is TOO slow) because
     * otherwise real values of these fields stored in fat-file descriptor
     * may be accidentally rewritten with wrong values stored on the disk
     */
    if (fat_fd->links_num == 1)
    {
        fat_fd->cln = MSDOS_EXTRACT_CLUSTER_NUM(node_entry);

        time_val = *MSDOS_DIR_WRITE_TIME(node_entry);
        date = *MSDOS_DIR_WRITE_DATE(node_entry);

        fat_fd->mtime = msdos_date_dos2unix(CF_LE_W(date), CF_LE_W(time_val));

        time_val = *MSDOS_DIR_CRT_TIME(node_entry);
        date = *MSDOS_DIR_CRT_DATE(node_entry);

        fat_fd->ctime = msdos_date_dos2unix(CF_LE_W(date), CF_LE_W(time_val));

        if ((*MSDOS_DIR_ATTR(node_entry)) & MSDOS_ATTR_DIRECTORY)
        {
            fat_fd->fat_file_type = FAT_DIRECTORY;
            fat_fd->size_limit = MSDOS_MAX_DIR_LENGTH;

            rc = fat_file_size(&fs_info->fat, fat_fd);
            if (rc != RC_OK)
            {
                fat_file_close(&fs_info->fat, fat_fd);
                return rc;
            }
        }
        else
        {
            fat_fd->fat_file_size = CF_LE_L(*MSDOS_DIR_FILE_SIZE(node_entry));
            fat_fd->fat_file_type = FAT_FILE;
            fat_fd->size_limit = MSDOS_MAX_FILE_SIZE;
        }

        /* these data is not actual for zero-length fat-file */
        fat_fd->map.file_cln = 0;
        fat_fd->map.disk_cln = fat_fd->cln;

        if ((fat_fd->fat_file_size != 0) &&
            (fat_fd->fat_file_size <= fs_info->fat.vol.bpc))
        {
            fat_fd->map.last_cln = fat_fd->cln;
        }
        else
        {
            fat_fd->map.last_cln = FAT_UNDEFINED_VALUE;
        }
    }

    /* close fat-file corresponded to the node we searched in */
    rc = fat_file_close(&fs_info->fat, parent_loc->node_access);
    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }

    /* update node_info_ptr field */
    parent_loc->node_access = fat_fd;

    return rc;
}

/* msdos_get_name_node --
 *     This routine is used in two ways: for a new node creation (a) or for
 *     search the node which correspondes to the name parameter (b).
 *     In case (a) 'name' should be set up to NULL and 'name_dir_entry' should
 *     point to initialized 32 bytes structure described a new node.
 *     In case (b) 'name' should contain a valid string.
 *
 *     (a): reading fat-file which correspondes to directory we are going to
 *          create node in. If free slot is found write contents of
 *          'name_dir_entry' into it. If reach end of fat-file and no free
 *          slot found, write 32 bytes to the end of fat-file.
 *
 *     (b): reading fat-file which correspondes to directory and trying to
 *          find slot with the name field == 'name' parameter
 *
 *
 * PARAMETERS:
 *     parent_loc     - node description to create node in or to find name in
 *     name           - NULL or name to find
 *     paux           - identify a node location on the disk -
 *                      cluster num and offset inside the cluster
 *     short_dir_entry - node to create/placeholder for found node (IN/OUT)
 *
 * RETURNS:
 *     RC_OK, filled aux_struct_ptr and name_dir_entry on success, or -1 if
 *     error occured (errno set apropriately)
 *
 */
int
msdos_get_name_node(
    const rtems_filesystem_location_info_t *parent_loc,
    bool                                    create_node,
    const char                             *name,
    int                                     name_len,
    msdos_name_type_t                       name_type,
    fat_dir_pos_t                          *dir_pos,
    char                                   *name_dir_entry
    )
{
    int              rc = RC_OK;
    fat_file_fd_t   *fat_fd = parent_loc->node_access;
    uint32_t         dotdot_cln = 0;

    /* find name in fat-file which corresponds to the directory */
    rc = msdos_find_name_in_fat_file(parent_loc->mt_entry, fat_fd,
                                     create_node, (const uint8_t*)name, name_len, name_type,
                                     dir_pos, name_dir_entry);
    if ((rc != RC_OK) && (rc != MSDOS_NAME_NOT_FOUND_ERR))
        return rc;

    if (!create_node)
    {
        /* if we search for valid name and name not found -> return */
        if (rc == MSDOS_NAME_NOT_FOUND_ERR)
            return rc;

        /*
         * if we have deal with ".." - it is a special case :(((
         *
         * Really, we should return cluster num and offset not of ".." slot, but
         * slot which correspondes to real directory name.
         */
        if (rc == RC_OK)
        {
            if (strncmp(name, "..", 2) == 0)
            {
                dotdot_cln = MSDOS_EXTRACT_CLUSTER_NUM((name_dir_entry));

                /* are we right under root dir ? */
                if (dotdot_cln == 0)
                {
                    /*
                     * we can relax about first_char field - it never should be
                     * used for root dir
                     */
                    fat_dir_pos_init(dir_pos);
                    dir_pos->sname.cln = FAT_ROOTDIR_CLUSTER_NUM;
                }
                else
                {
                    rc =
                        msdos_get_dotdot_dir_info_cluster_num_and_offset(parent_loc->mt_entry,
                                                                         dotdot_cln,
                                                                         dir_pos,
                                                                         name_dir_entry);
                    if (rc != RC_OK)
                        return rc;
                }
            }
        }
    }
    return rc;
}

/*
 * msdos_get_dotdot_dir_info_cluster_num_and_offset
 *
 * Unfortunately, in general, we cann't work here in fat-file ideologic
 * (open fat_file "..", get ".." and ".", open "..", find an entry ...)
 * because if we open
 * fat-file ".." it may happend that we have two different fat-file
 * descriptors ( for real name of directory and ".." name ) for a single
 * file  ( cluster num of both pointers to the same cluster )
 * But...we do it because we protected by semaphore
 *
 */

/* msdos_get_dotdot_dir_info_cluster_num_and_offset --
 *     Get cluster num and offset not of ".." slot, but slot which correspondes
 *     to real directory name.
 *
 * PARAMETERS:
 *     mt_entry       - mount table entry
 *     cln            - data cluster num extracted drom ".." slot
 *     paux           - identify a node location on the disk -
 *                      number of cluster and offset inside the cluster
 *     dir_entry      - placeholder for found node
 *
 * RETURNS:
 *     RC_OK, filled 'paux' and 'dir_entry' on success, or -1 if error occured
 *     (errno set apropriately)
 *
 */
int
msdos_get_dotdot_dir_info_cluster_num_and_offset(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    uint32_t                              cln,
    fat_dir_pos_t                        *dir_pos,
    char                                 *dir_entry
    )
{
    int              rc = RC_OK;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    fat_file_fd_t   *fat_fd = NULL;
    char             dot_node[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
    char             dotdot_node[MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE];
    uint32_t         cl4find = 0;
    rtems_dosfs_convert_control *converter = fs_info->converter;

    /*
     * open fat-file corresponded to ".."
     */
    rc = fat_file_open(&fs_info->fat, dir_pos, &fat_fd);
    if (rc != RC_OK)
        return rc;

    fat_fd->cln = cln;
    fat_fd->fat_file_type = FAT_DIRECTORY;
    fat_fd->size_limit = MSDOS_MAX_DIR_LENGTH;

    fat_fd->map.file_cln = 0;
    fat_fd->map.disk_cln = fat_fd->cln;

    rc = fat_file_size(&fs_info->fat, fat_fd);
    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }

    /* find "." node in opened directory */
    memset(dot_node, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
    msdos_long_to_short(
        converter,
        ".", 1, dot_node, MSDOS_SHORT_NAME_LEN);
    rc = msdos_find_name_in_fat_file(mt_entry, fat_fd, false, (const uint8_t*)".", 1,
                                     MSDOS_NAME_SHORT, dir_pos, dot_node);

    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }

    /* find ".." node in opened directory */
    memset(dotdot_node, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
    msdos_long_to_short(
        converter,
        "..", 2, dotdot_node, MSDOS_SHORT_NAME_LEN);
    rc = msdos_find_name_in_fat_file(mt_entry, fat_fd, false, (const uint8_t*)"..", 2,
                                     MSDOS_NAME_SHORT, dir_pos,
                                     dotdot_node);

    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }

    cl4find = MSDOS_EXTRACT_CLUSTER_NUM(dot_node);

    /* close fat-file corresponded to ".." directory */
    rc = fat_file_close(&fs_info->fat, fat_fd);
    if ( rc != RC_OK )
        return rc;

    if ( (MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node)) == 0)
    {
        /*
         * we handle root dir for all FAT types in the same way with the
         * ordinary directories ( through fat_file_* calls )
         */
        fat_dir_pos_init(dir_pos);
        dir_pos->sname.cln = FAT_ROOTDIR_CLUSTER_NUM;
    }

    /* open fat-file corresponded to second ".." */
    rc = fat_file_open(&fs_info->fat, dir_pos, &fat_fd);
    if (rc != RC_OK)
        return rc;

    if ((MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node)) == 0)
        fat_fd->cln = fs_info->fat.vol.rdir_cl;
    else
        fat_fd->cln = MSDOS_EXTRACT_CLUSTER_NUM(dotdot_node);

    fat_fd->fat_file_type = FAT_DIRECTORY;
    fat_fd->size_limit = MSDOS_MAX_DIR_LENGTH;

    fat_fd->map.file_cln = 0;
    fat_fd->map.disk_cln = fat_fd->cln;

    rc = fat_file_size(&fs_info->fat, fat_fd);
    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }

    /* in this directory find slot with specified cluster num */
    rc = msdos_find_node_by_cluster_num_in_fat_file(mt_entry, fat_fd, cl4find,
                                                    dir_pos, dir_entry);
    if (rc != RC_OK)
    {
        fat_file_close(&fs_info->fat, fat_fd);
        return rc;
    }
    rc = fat_file_close(&fs_info->fat, fat_fd);
    return rc;
}


/* fat_file_write_time_and_date --
 *     Write last write date and time for a file to the disk (to corresponded
 *     32bytes node)
 *
 * PARAMETERS:
 *     fs_info  - fat fs info
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately).
 *
 */
int
fat_file_write_time_and_date(
    fat_fs_info_t                        *fs_info,
    fat_file_fd_t                        *fat_fd
    )
{
    int              rc = RC_OK;
    ssize_t          ret;
    uint16_t         time_val;
    uint16_t         date;
    uint32_t         sec = 0;
    uint32_t         byte = 0;

    /*
     * calculate input for fat_sector_write: convert (cluster num, offset) to
     * (sector num, new offset)
     */
    sec = fat_cluster_num_to_sector_num(fs_info, fat_fd->dir_pos.sname.cln);
    sec += (fat_fd->dir_pos.sname.ofs >> fs_info->vol.sec_log2);
    /* byte points to start of 32bytes structure */
    byte = fat_fd->dir_pos.sname.ofs & (fs_info->vol.bps - 1);

    msdos_date_unix2dos(fat_fd->mtime, &date, &time_val);

    time_val = CT_LE_W(time_val);
    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_WTIME_OFFSET,
                           2, (char *)(&time_val));
    if ( ret < 0 )
        rc = -1;

    date = CT_LE_W(date);
    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_WDATE_OFFSET,
                           2, (char *)(&date));
    if ( ret < 0 )
        rc = -1;

    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_ADATE_OFFSET,
                           2, (char *)(&date));
    if ( ret < 0 )
        rc = -1;

    msdos_date_unix2dos(fat_fd->ctime, &date, &time_val);

    time_val = CT_LE_W(time_val);
    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_CTIME_OFFSET,
                           2, (char *)(&time_val));
    if ( ret < 0 )
        rc = -1;

    date = CT_LE_W(date);
    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_CDATE_OFFSET,
                           2, (char *)(&date));
    if ( ret < 0 )
        rc = -1;

    return rc;
}

/* fat_set_first_cluster_num --
 *     Write number of first cluster of the file to the disk (to corresponded
 *     32bytes slot)
 *
 * PARAMETERS:
 *     fs_info  - fat fs info
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *
 */
int
fat_file_write_first_cluster_num(
    fat_fs_info_t                        *fs_info,
    fat_file_fd_t                        *fat_fd
    )
{
    ssize_t          ret1 = 0, ret2 = 0;
    uint32_t         new_cln = fat_fd->cln;
    uint16_t         le_cl_low = 0;
    uint16_t         le_cl_hi = 0;
    uint32_t         sec = 0;
    uint32_t         byte = 0;

    /*
     * calculate input for fat_sector_write: convert (cluster num, offset) to
     * (sector num, new offset)
     */
    sec = fat_cluster_num_to_sector_num(fs_info, fat_fd->dir_pos.sname.cln);
    sec += (fat_fd->dir_pos.sname.ofs >> fs_info->vol.sec_log2);
    /* byte from points to start of 32bytes structure */
    byte = fat_fd->dir_pos.sname.ofs & (fs_info->vol.bps - 1);

    le_cl_low = CT_LE_W((uint16_t  )(new_cln & 0x0000FFFF));
    ret1 = fat_sector_write(fs_info, sec,
                            byte + MSDOS_FIRST_CLUSTER_LOW_OFFSET, 2,
                            (char *)(&le_cl_low));
    le_cl_hi = CT_LE_W((uint16_t  )((new_cln & 0xFFFF0000) >> 16));
    ret2 = fat_sector_write(fs_info, sec,
                            byte + MSDOS_FIRST_CLUSTER_HI_OFFSET, 2,
                            (char *)(&le_cl_hi));
    if ( (ret1 < 0) || (ret2 < 0) )
        return -1;

    return RC_OK;
}


/* fat_set_file size --
 *     Write file size of the file to the disk (to corresponded 32bytes slot)
 *
 * PARAMETERS:
 *     fs_info  - fat fs info
 *     fat_fd   - fat-file descriptor
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately).
 *
 */
int
fat_file_write_file_size(
    fat_fs_info_t                        *fs_info,
    fat_file_fd_t                        *fat_fd
    )
{
    ssize_t          ret = 0;
    uint32_t         le_new_length = 0;
    uint32_t         sec = 0;
    uint32_t         byte = 0;

    sec = fat_cluster_num_to_sector_num(fs_info, fat_fd->dir_pos.sname.cln);
    sec += (fat_fd->dir_pos.sname.ofs >> fs_info->vol.sec_log2);
    byte = (fat_fd->dir_pos.sname.ofs & (fs_info->vol.bps - 1));

    if (fat_fd->fat_file_type == FAT_DIRECTORY) {
      le_new_length = CT_LE_L(0);
    } else {
      le_new_length = CT_LE_L(fat_fd->fat_file_size);
    }

    ret = fat_sector_write(fs_info, sec, byte + MSDOS_FILE_SIZE_OFFSET, 4,
                           (char *)(&le_new_length));
    if ( ret < 0 )
        return -1;

    return RC_OK;
}

/*
 * We should not check whether this routine is called for root dir - it
 * never can happend
 */

/* msdos_set_first_char4file_name --
 *     Write first character of the name of the file to the disk (to
 *     corresponded 32bytes slot)
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     cl       - number of cluster
 *     ofs      - offset inside cluster
 *     fchar    - character to set up
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately)
 *
 */
int
msdos_set_first_char4file_name(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_dir_pos_t                        *dir_pos,
    unsigned char                         fchar
    )
{
    ssize_t          ret;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    uint32_t         dir_block_size;
    fat_pos_t        start = dir_pos->lname;
    fat_pos_t        end = dir_pos->sname;

    if ((end.cln == fs_info->fat.vol.rdir_cl) &&
        (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
      dir_block_size = fs_info->fat.vol.rdir_size;
    else
      dir_block_size = fs_info->fat.vol.bpc;

    if (dir_pos->lname.cln == FAT_FILE_SHORT_NAME)
      start = dir_pos->sname;

    /*
     * We handle the changes directly due the way the short file
     * name code was written rather than use the fat_file_write
     * interface.
     */
    while (true)
    {
      uint32_t sec = (fat_cluster_num_to_sector_num(&fs_info->fat, start.cln) +
                      (start.ofs >> fs_info->fat.vol.sec_log2));
      uint32_t byte = (start.ofs & (fs_info->fat.vol.bps - 1));

      ret = fat_sector_write(&fs_info->fat, sec, byte + MSDOS_FILE_NAME_OFFSET,
                             1, &fchar);
      if (ret < 0)
        return -1;

      if ((start.cln == end.cln) && (start.ofs == end.ofs))
        break;

      start.ofs += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;
      if (start.ofs >= dir_block_size)
      {
        int rc;
        if ((end.cln == fs_info->fat.vol.rdir_cl) &&
            (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
          break;
        rc = fat_get_fat_cluster(&fs_info->fat, start.cln, &start.cln);
        if ( rc != RC_OK )
          return rc;
        start.ofs = 0;
      }
    }

    return  RC_OK;
}

/* msdos_dir_is_empty --
 *     Check whether directory which correspondes to the fat-file descriptor is
 *     empty.
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     fat_fd   - fat-file descriptor
 *     ret_val  - placeholder for result
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *
 */
int
msdos_dir_is_empty(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd,
    bool                                 *ret_val
    )
{
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    uint32_t         j = 0, i = 0;

    /* dir is not empty */
    *ret_val = false;

    while ((ret = fat_file_read(&fs_info->fat, fat_fd, j * fs_info->fat.vol.bps,
                                  fs_info->fat.vol.bps,
                                  fs_info->cl_buf)) != FAT_EOF)
    {
        if (ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
            return -1;

        assert(ret == fs_info->fat.vol.bps);

        /* have to look at the DIR_NAME as "raw" 8-bit data */
        for (i = 0;
             i < fs_info->fat.vol.bps;
             i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            char* entry = (char*) fs_info->cl_buf + i;

            /*
             * If the entry is empty, a long file name entry, or '.' or '..'
             * then consider it as empty.
             *
             * Just ignore long file name entries. They must have a short entry to
             * be valid.
             */
            if (((*MSDOS_DIR_ENTRY_TYPE(entry)) ==
                 MSDOS_THIS_DIR_ENTRY_EMPTY) ||
                ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_LFN_MASK) ==
                 MSDOS_ATTR_LFN) ||
                (strncmp(MSDOS_DIR_NAME((entry)), MSDOS_DOT_NAME,
                         MSDOS_SHORT_NAME_LEN) == 0) ||
                (strncmp(MSDOS_DIR_NAME((entry)),
                         MSDOS_DOTDOT_NAME,
                         MSDOS_SHORT_NAME_LEN) == 0))
                continue;

            /*
             * Nothing more to look at.
             */
            if ((*MSDOS_DIR_NAME(entry)) ==
                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
            {
                *ret_val = true;
                return RC_OK;
            }

            /*
             * Short file name entries mean not empty.
             */
            return RC_OK;
        }
        j++;
    }
    *ret_val = true;
    return RC_OK;
}

#define MSDOS_FIND_PRINT 0
static int
msdos_on_entry_found (
    msdos_fs_info_t                      *fs_info,
    fat_file_fd_t                        *fat_fd,
    const uint32_t                        bts2rd,
    char                                 *name_dir_entry,
    char                                 *entry,
    fat_dir_pos_t                        *dir_pos,
    uint32_t                              dir_offset,
    const uint32_t                        dir_entry,
    const fat_pos_t                      *lfn_start
)
{
    int rc = RC_OK;
#if MSDOS_FIND_PRINT
    printf ("MSFS:[9.3] SNF found\n");
#endif
    /*
     * We get the entry we looked for - fill the position
     * structure and the 32 bytes of the short entry
     */
    rc = fat_file_ioctl(&fs_info->fat,
                        fat_fd,
                        F_CLU_NUM,
                        dir_offset * bts2rd,
                        &dir_pos->sname.cln);
    if (rc == RC_OK) {
        dir_pos->sname.ofs = dir_entry;

        if (lfn_start->cln != FAT_FILE_SHORT_NAME)
        {
            rc = fat_file_ioctl (&fs_info->fat,
                                 fat_fd,
                                 F_CLU_NUM,
                                 lfn_start->cln * bts2rd,
                                 &lfn_start->cln);
        }
        if ( rc == RC_OK ) {
            dir_pos->lname.cln = lfn_start->cln;
            dir_pos->lname.ofs = lfn_start->ofs;

            memcpy(name_dir_entry, entry,
                   MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
        }
    }

    return rc;
}

ssize_t
msdos_get_utf16_string_from_long_entry (
    const char                 *entry,
    uint16_t                   *entry_string_buf,
    const size_t                buf_size,
    bool                        is_first_entry
)
{
    ssize_t chars_in_entry;

    if (buf_size >= MSDOS_LFN_ENTRY_SIZE) {
        memcpy (&entry_string_buf[0],  &entry[1],  10 );
        memcpy (&entry_string_buf[5],  &entry[14], 12 );
        memcpy (&entry_string_buf[11], &entry[28],  4 );

        if (is_first_entry) {
            for (chars_in_entry = 0;
                 (   chars_in_entry < MSDOS_LFN_LEN_PER_ENTRY
                  && entry_string_buf[chars_in_entry] != 0x0000);
                  ++chars_in_entry) {
                ;
            }
        }
        else
            chars_in_entry = MSDOS_LFN_LEN_PER_ENTRY;

        return chars_in_entry * MSDOS_NAME_LFN_BYTES_PER_CHAR;
    }
    else
        return ENOMEM;
}

/*  msdos_format_dirent_with_dot --
 *      This routine convert a (short) MSDOS filename as present on disk
 *      (fixed 8+3 characters, filled with blanks, without separator dot)
 *      to a "normal" format, with between 0 and 8 name chars,
 *      a separating dot and up to 3 extension characters
 *   Rules to work:
 *      - copy any (0-8) "name" part characters that are non-blank
 *      - if an extension exists, append a dot
 *      - copy any (0-3) non-blank extension characters
 *      - append a '\0' (dont count it for the rturn code
 *
 * PARAMETERS:
 *     dst: pointer to destination char array (must be big enough)
 *     src: pointer to source characters
 *
 *
 * RETURNS:
 *     the number of bytes (without trailing '\0'(written to destination
 */
ssize_t
msdos_format_dirent_with_dot(char *dst,const char *src)
{
  ssize_t len;
  int i;
  const char *src_tmp;

  /*
   * find last non-blank character of base name
   */
  for (i = MSDOS_SHORT_BASE_LEN, src_tmp = src + MSDOS_SHORT_BASE_LEN - 1;
       i > 0 && *src_tmp == ' ';
       --i,--src_tmp)
    {};
  /*
   * copy base name to destination
   */
  src_tmp = src;
  len = i;
  while (i-- > 0) {
    *dst++ = tolower((unsigned char)(*src_tmp++));
  }
  /*
   * find last non-blank character of extension
   */
  for (i = MSDOS_SHORT_EXT_LEN,
        src_tmp = src + MSDOS_SHORT_BASE_LEN+MSDOS_SHORT_EXT_LEN-1;
       i > 0 && *src_tmp == ' ';
       --i, --src_tmp)
    {};
  /*
   * extension is not empty
   */
  if (i > 0) {
    *dst++ = '.'; /* append dot */
    ++len;        /* dot */
    src_tmp = src + MSDOS_SHORT_BASE_LEN;
    while (i-- > 0) {
      *dst++ = tolower((unsigned char)(*src_tmp++));
      ++len;
    }
  }
  *dst = '\0'; /* terminate string */

  return len;
}

static ssize_t
msdos_long_entry_to_utf8_name (
    rtems_dosfs_convert_control *converter,
    const char                  *entry,
    const bool                   is_first_entry,
    uint8_t                     *entry_utf8_buf,
    const size_t                 buf_size)
{
    ssize_t      retval         = 0;
    int          eno            = 0;
    size_t       bytes_in_utf8  = buf_size;
    size_t       bytes_in_buf;
    uint16_t     entry_string[MSDOS_LFN_LEN_PER_ENTRY];

    retval = msdos_get_utf16_string_from_long_entry (
        entry,
        entry_string,
        sizeof (entry_string),
        is_first_entry
    );

    if (retval >= 0) {
        bytes_in_buf = retval;
        eno = (*converter->handler->utf16_to_utf8) (
            converter,
            &entry_string[0],
            bytes_in_buf,
            &entry_utf8_buf[0],
            &bytes_in_utf8);
        if ( eno == 0 ) {
            retval = bytes_in_utf8;
        }
    }

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

    return retval;
}

static ssize_t msdos_short_entry_to_utf8_name (
  rtems_dosfs_convert_control     *converter,
  const char                      *entry,
  uint8_t                         *buf,
  const size_t                     buf_size)
{
  char         char_buf[MSDOS_NAME_MAX_WITH_DOT];
  int          eno             = 0;
  size_t       bytes_converted = buf_size;
  ssize_t      bytes_written   = msdos_format_dirent_with_dot(char_buf, entry);

  if (bytes_written > 0) {
    if (char_buf[0] == 0x05)
      char_buf[0] = 0xE5;

    eno = (*converter->handler->codepage_to_utf8) (
        converter,
        char_buf,
        bytes_written,
        buf,
        &bytes_converted);
    if (eno == 0)
        bytes_written = bytes_converted;
  } else {
      eno = EINVAL;
  }

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

  return bytes_written;
}

static ssize_t
msdos_compare_entry_against_filename (
    rtems_dosfs_convert_control *converter,
    const uint8_t               *entry,
    const size_t                 entry_size,
    const uint8_t               *filename,
    const size_t                 name_len_remaining,
    bool                        *is_matching)
{
  ssize_t      size_remaining = name_len_remaining;
  int          eno            = 0;
  uint8_t      entry_normalized[MSDOS_LFN_ENTRY_SIZE_UTF8];
  size_t       bytes_in_entry_normalized = sizeof ( entry_normalized );

  eno = (*converter->handler->utf8_normalize_and_fold) (
      converter,
      &entry[0],
      entry_size,
      &entry_normalized[0],
      &bytes_in_entry_normalized);
  if (eno == 0) {
#if MSDOS_FIND_PRINT > 1
        printf ( "MSFS:[6] entry_normalized:%s"
                 "name:%s\n",
                 entry,
                 filename );
#endif
        if (bytes_in_entry_normalized <= size_remaining) {
            size_remaining = size_remaining - bytes_in_entry_normalized;
            if (0 == memcmp ( &entry_normalized[0],
                              &filename[size_remaining],
                              bytes_in_entry_normalized)) {
                *is_matching = true;
            } else {
                *is_matching   = false;
                size_remaining = name_len_remaining;
            }

        }
        else {
          *is_matching = false;
        }
    }

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

    return size_remaining;
}

static void
msdos_prepare_for_next_entry(
    fat_pos_t *lfn_start,
    bool      *entry_matched,
    ssize_t   *name_len_remaining,
    size_t     name_len_for_compare)
{
    lfn_start->cln = FAT_FILE_SHORT_NAME;
    *entry_matched = false;
    *name_len_remaining = name_len_for_compare;
}

static int
msdos_find_file_in_directory (
    const uint8_t                        *filename_converted,
    const size_t                          name_len_for_compare,
    const size_t                          name_len_for_save,
    const msdos_name_type_t               name_type,
    msdos_fs_info_t                      *fs_info,
    fat_file_fd_t                        *fat_fd,
    const uint32_t                        bts2rd,
    const bool                            create_node,
    const unsigned int                    lfn_entries,
    char                                 *name_dir_entry,
    fat_dir_pos_t                        *dir_pos,
    uint32_t                             *empty_file_offset,
    uint32_t                             *empty_entry_count)
{
    int               rc                = RC_OK;
    ssize_t           bytes_read;
    uint32_t          dir_entry;
    fat_pos_t         lfn_start;
    uint8_t           lfn_checksum      = 0;
    bool              entry_matched;
    bool              empty_space_found = false;
    uint32_t          entries_per_block = bts2rd / MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;
    int               lfn_entry         = 0;
    uint8_t           entry_utf8_normalized[MSDOS_LFN_ENTRY_SIZE_UTF8];
    size_t            bytes_in_entry;
    bool              filename_matched  = false;
    ssize_t           name_len_remaining;
    rtems_dosfs_convert_control *converter = fs_info->converter;
    uint32_t          dir_offset = 0;

    /*
     * Scan the directory seeing if the file is present. While
     * doing this see if a suitable location can be found to
     * create the entry if the name is not found.
     */

    msdos_prepare_for_next_entry(&lfn_start, &entry_matched,
                                 &name_len_remaining,
                                 name_len_for_compare);

    while (   (bytes_read = fat_file_read (&fs_info->fat, fat_fd, (dir_offset * bts2rd),
                                             bts2rd, fs_info->cl_buf)) != FAT_EOF
           && rc == RC_OK)
    {
        bool remainder_empty = false;
#if MSDOS_FIND_PRINT
        printf ("MSFS:[2] dir_offset:%li\n", dir_offset);
#endif

        if (bytes_read < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
            rtems_set_errno_and_return_minus_one(EIO);

        assert(bytes_read == bts2rd);

        /* have to look at the DIR_NAME as "raw" 8-bit data */
        for (dir_entry = 0;
             dir_entry < bts2rd && rc == RC_OK && (! filename_matched);
             dir_entry += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            char* entry = (char*) fs_info->cl_buf + dir_entry;

            /*
             * See if the entry is empty or the remainder of the directory is
             * empty ? Localize to make the code read better.
             */
            bool entry_empty = (*MSDOS_DIR_ENTRY_TYPE(entry) ==
                                MSDOS_THIS_DIR_ENTRY_EMPTY);
            remainder_empty = (*MSDOS_DIR_ENTRY_TYPE(entry) ==
                               MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY);
#if MSDOS_FIND_PRINT
            printf ("MSFS:[3] re:%i ee:%i do:%li de:%li(%ld)\n",
                    remainder_empty, entry_empty, dir_offset,
                    dir_entry, (dir_entry / MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE));
#endif
            /*
             * Remember where the we are, ie the start, so we can come back
             * to here and write the long file name if this is the start of
             * a series of empty entries. If empty_entry_count is 0 then
             * we are currently not inside an empty series of entries. It
             * is a count of empty entries.
             */
            if (*empty_entry_count == 0)
            {
                *empty_file_offset = dir_offset * bts2rd + dir_entry;
            }

            if (remainder_empty)
            {
#if MSDOS_FIND_PRINT
                printf ("MSFS:[3.1] cn:%i esf:%i\n", create_node, empty_space_found);
#endif
                /*
                 * If just looking and there is no more entries in the
                 * directory - return name-not-found
                 */
                if (!create_node)
                    rc = MSDOS_NAME_NOT_FOUND_ERR;

                /*
                 * Lets go and write the directory entries. If we have not found
                 * any available space add the remaining number of entries to any that
                 * we may have already found that are just before this entry. If more
                 * are needed FAT_EOF is returned by the read and we extend the file.
                 */
                if (   !empty_space_found
                    && rc == RC_OK )
                {
                    *empty_entry_count +=
                    entries_per_block - (dir_entry / MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
                    empty_space_found = true;
#if MSDOS_FIND_PRINT
                    printf ( "MSFS:[3.2] esf:%i esc%"PRIu32"\n", empty_space_found, *empty_entry_count );
#endif
                }
                break;
            }
            else if (entry_empty)
            {
                if (create_node)
                {
                    /*
                     * Remainder is not empty so is this entry empty ?
                     */
                    (*empty_entry_count)++;

                    if (*empty_entry_count == (lfn_entries + 1))
                        empty_space_found = true;
                }
#if MSDOS_FIND_PRINT
                printf ("MSFS:[4.1] esc:%li esf:%i\n",
                        *empty_entry_count, empty_space_found);
#endif
                msdos_prepare_for_next_entry(&lfn_start, &entry_matched,
                                             &name_len_remaining,
                                             name_len_for_compare);
            }
            else
            {
                /*
                 * A valid entry so handle it.
                 *
                 * If empty space has not been found we need to start the
                 * count again.
                 */
                if (create_node && !empty_space_found)
                {
                    *empty_file_offset = 0;
                    *empty_entry_count = 0;
                }

                /*
                 * Check the attribute to see if the entry is for a long
                 * file name.
                 */
                if ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_LFN_MASK) ==
                    MSDOS_ATTR_LFN)
                {
                    bool is_first_lfn_entry =
                        (lfn_start.cln == FAT_FILE_SHORT_NAME);

/*                    int   o;*/
#if MSDOS_FIND_PRINT
                    printf ("MSFS:[4.2] lfn:%c entry:%i checksum:%i\n",
                            lfn_start.cln == FAT_FILE_SHORT_NAME ? 'f' : 't',
                            *MSDOS_DIR_ENTRY_TYPE(entry) & MSDOS_LAST_LONG_ENTRY_MASK,
                            *MSDOS_DIR_LFN_CHECKSUM(entry));
#endif
                    /*
                     * If we are not already processing a LFN see if this is
                     * the first entry of a LFN ?
                     */
                    if (is_first_lfn_entry)
                    {
                        entry_matched = false;

                        /*
                         * The first entry must have the last long entry
                         * flag set.
                         */
                        if ((*MSDOS_DIR_ENTRY_TYPE(entry) &
                             MSDOS_LAST_LONG_ENTRY) == 0)
                            continue;

                        lfn_start.cln = dir_offset;
                        lfn_start.ofs = dir_entry;
                        lfn_entry = (*MSDOS_DIR_ENTRY_TYPE(entry)
                            & MSDOS_LAST_LONG_ENTRY_MASK);
                        lfn_checksum = *MSDOS_DIR_LFN_CHECKSUM(entry);

#if MSDOS_FIND_PRINT
                        printf ("MSFS:[4.3] lfn_checksum:%i\n",
                                lfn_checksum);
#endif
                    }

                    /*
                     * If the entry number or the check sum do not match
                     * forget this series of long directory entries. These
                     * could be orphaned entries depending on the history
                     * of the disk.
                     */
                    if ((lfn_entry != (*MSDOS_DIR_ENTRY_TYPE(entry) &
                                       MSDOS_LAST_LONG_ENTRY_MASK)) ||
                        (lfn_checksum != *MSDOS_DIR_LFN_CHECKSUM(entry)))
                    {
#if MSDOS_FIND_PRINT
                        printf ("MSFS:[4.4] no match\n");
#endif
                        msdos_prepare_for_next_entry(&lfn_start,
                                                     &entry_matched,
                                                     &name_len_remaining,
                                                     name_len_for_compare);
                        continue;
                    }
#if MSDOS_FIND_PRINT
                    printf ("MSFS:[5] lfne:%i\n", lfn_entry);
#endif
                    lfn_entry--;

                    bytes_in_entry = msdos_long_entry_to_utf8_name (
                        converter,
                        entry,
                        is_first_lfn_entry,
                        &entry_utf8_normalized[0],
                        sizeof (entry_utf8_normalized));
                    if (bytes_in_entry > 0) {
                        name_len_remaining = msdos_compare_entry_against_filename (
                            converter,
                            &entry_utf8_normalized[0],
                            bytes_in_entry,
                            &filename_converted[0],
                            name_len_remaining,
                            &entry_matched);

                        if (name_len_remaining < 0 || !entry_matched) {
                            msdos_prepare_for_next_entry(&lfn_start,
                                                         &entry_matched,
                                                         &name_len_remaining,
                                                         name_len_for_compare);
                        }
                    } else {
                        msdos_prepare_for_next_entry(&lfn_start,
                                                     &entry_matched,
                                                     &name_len_remaining,
                                                     name_len_for_compare);
                    }
                }
                else
                {
#if MSDOS_FIND_PRINT
                    printf ("MSFS:[9.1] SFN entry, entry_matched:%i\n", entry_matched);
#endif
                    /*
                     * SFN entry found.
                     *
                     * If a LFN has been found and it matched check the
                     * entries have all been found and the checksum is
                     * correct. If this is the case return the short file
                     * name entry.
                     */
                    if (entry_matched)
                    {
                        if (lfn_entry ||
                            name_len_remaining > 0 ||
                            lfn_checksum != msdos_lfn_checksum(entry)) {
                            msdos_prepare_for_next_entry(&lfn_start,
                                                         &entry_matched,
                                                         &name_len_remaining,
                                                         name_len_for_compare);
                        } else if (name_len_remaining == 0) {
                            filename_matched = true;
                            rc = msdos_on_entry_found (
                                fs_info,
                                fat_fd,
                                bts2rd,
                                name_dir_entry,
                                entry,
                                dir_pos,
                                dir_offset,
                                dir_entry,
                                &lfn_start
                            );
                        }

#if MSDOS_FIND_PRINT
                        printf ("MSFS:[9.2] checksum, entry_matched:%i, lfn_entry:%i, lfn_checksum:%02x/%02x\n",
                                entry_matched, lfn_entry, lfn_checksum, msdos_lfn_checksum(entry));
#endif
                    } else if ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_VOLUME_ID)
                               == 0) {
                        bytes_in_entry = MSDOS_SHORT_NAME_LEN + 1;
                        bytes_in_entry = msdos_short_entry_to_utf8_name (
                            converter,
                            MSDOS_DIR_NAME (entry),
                            &entry_utf8_normalized[0],
                            bytes_in_entry);
                        if (bytes_in_entry > 0) {
                            name_len_remaining = msdos_compare_entry_against_filename (
                                converter,
                                &entry_utf8_normalized[0],
                                bytes_in_entry,
                                &filename_converted[0],
                                name_len_for_compare,
                                &entry_matched);
                            if (entry_matched && name_len_remaining == 0) {
                                filename_matched = true;
                                rc = msdos_on_entry_found (
                                    fs_info,
                                    fat_fd,
                                    bts2rd,
                                    name_dir_entry,
                                    entry,
                                    dir_pos,
                                    dir_offset,
                                    dir_entry,
                                    &lfn_start
                                );
                            }
                            if (rc == RC_OK && !filename_matched) {
                                msdos_prepare_for_next_entry(&lfn_start,
                                                             &entry_matched,
                                                             &name_len_remaining,
                                                             name_len_for_compare);
                            }
                        } else {
                          msdos_prepare_for_next_entry(&lfn_start,
                                                       &entry_matched,
                                                       &name_len_remaining,
                                                       name_len_for_compare);
                        }
                    }
                }
            }
        }

        if (filename_matched || remainder_empty)
            break;

        dir_offset++;
    }
    if ( ! filename_matched ) {
        /*
         * If we are not to create the entry return a not found error.
         */
        if (!create_node)
            rc = MSDOS_NAME_NOT_FOUND_ERR;

#if MSDOS_FIND_PRINT
        printf ( "MSFS:[8.1] WRITE do:%"PRIu32" esc:%"PRIu32" efo:%"PRIu32"\n",
                 dir_offset, *empty_entry_count, *empty_file_offset );
#endif
    }

    return rc;
}

static int
msdos_get_pos(
    msdos_fs_info_t *fs_info,
    fat_file_fd_t   *fat_fd,
    uint32_t         bts2rd,
    uint32_t         file_offset,
    fat_pos_t       *pos
)
{
    pos->ofs = file_offset & (bts2rd - 1);
    return fat_file_ioctl(&fs_info->fat, fat_fd, F_CLU_NUM,
                          file_offset, &pos->cln);
}

static int
msdos_add_file (
    const char                           *name_converted,
    const msdos_name_type_t               name_type,
    msdos_fs_info_t                      *fs_info,
    fat_file_fd_t                        *fat_fd,
    const uint32_t                        bts2rd,
    const unsigned int                    lfn_entries,
    const char                           *name_dir_entry,
    fat_dir_pos_t                        *dir_pos,
    uint32_t                              empty_file_offset,
    const uint32_t                        empty_entry_count
)
{
    int              ret;
    ssize_t          bytes_written;
    uint8_t          lfn_checksum;
    int              lfn_entry;
    uint8_t         *entry;
    uint32_t         short_file_offset;
    uint32_t         length;

    /*
     * If there is not enough space available then extend the file.
     */
    if (empty_entry_count < lfn_entries + 1)
    {
        uint32_t unused;

        empty_file_offset = fat_fd->fat_file_size -
            empty_entry_count * MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;

        ret = fat_file_extend(&fs_info->fat,
                              fat_fd,
                              true,
                              fat_fd->fat_file_size + fs_info->fat.vol.bpc,
                              &unused);
        if (ret != RC_OK)
            return ret;
    }

    if (name_type == MSDOS_NAME_LONG)
    {
        uint32_t slot;

        /*
         * If a long file name calculate the checksum of the short file name
         * data to place in each long file name entry. First set the short
         * file name to the slot of the SFN entry. This will mean no clashes
         * in this directory.
         */
        slot = (empty_file_offset /
            MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE) + lfn_entries + 1;
        msdos_short_name_hex(MSDOS_DIR_NAME(name_dir_entry), slot);

        lfn_checksum = msdos_lfn_checksum(name_dir_entry);

        short_file_offset = empty_file_offset + lfn_entries
            * MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;

        /* Get position of first long file name entry */
        ret = msdos_get_pos(fs_info, fat_fd, bts2rd, empty_file_offset,
                            &dir_pos->lname);
        if (ret != RC_OK)
            return ret;
    } else {
        lfn_checksum = 0;
        short_file_offset = empty_file_offset;
        dir_pos->lname.cln = FAT_FILE_SHORT_NAME;
        dir_pos->lname.ofs = FAT_FILE_SHORT_NAME;
    }

    /* Get position of short file name entry */
    ret = msdos_get_pos(fs_info, fat_fd, bts2rd, short_file_offset,
                        &dir_pos->sname);

    /*
     * Handle the entry writes.
     */
    entry = fs_info->cl_buf;

#if MSDOS_FIND_PRINT
    printf ("MSFS:[9] read_cluster:%d efo:%ld ese:%ld\n",
            read_cluster, empty_file_offset, empty_space_entry);
#endif

    /* Long file name entries */
    for (lfn_entry = 0; lfn_entry < lfn_entries; ++lfn_entry) {
        uint8_t       *p;
        const uint8_t *n;
        int            i;
        uint8_t        fill = 0;

        /*
         * Clear the entry before loading the data.
         */
        memset (entry, 0, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);

        *MSDOS_DIR_LFN_CHECKSUM(entry) = lfn_checksum;

        p = entry + 1;
        n = (const uint8_t *) name_converted +
            (lfn_entries - lfn_entry - 1) * MSDOS_LFN_ENTRY_SIZE;

#if MSDOS_FIND_PRINT
        printf ("MSFS:[11] ");
#endif
        for (i = 0; i < MSDOS_LFN_LEN_PER_ENTRY; ++i)
        {
            if (*n != 0 || *(n + 1) != 0)
            {
                *p = *n;
                *(p + 1) = *(n + 1);
                n += MSDOS_NAME_LFN_BYTES_PER_CHAR;
            }
            else
            {
                p [0] = fill;
                p [1] = fill;
                fill = 0xff;
            }
#if MSDOS_FIND_PRINT
            printf ( "'%c''%c'", *p, *(p+1) );
#endif

            switch (i)
            {
                case 4:
                    p += 5;
                    break;
                case 10:
                    p += 4;
                    break;
                default:
                    p += 2;
                    break;
            }
        }
#if MSDOS_FIND_PRINT
        printf ( "\n" );
#endif
        *MSDOS_DIR_ENTRY_TYPE(entry) = lfn_entries - lfn_entry;
        if (lfn_entry == 0)
            *MSDOS_DIR_ENTRY_TYPE(entry) |= MSDOS_LAST_LONG_ENTRY;
        *MSDOS_DIR_ATTR(entry) |= MSDOS_ATTR_LFN;

        entry += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;
    }

    /* Short file name entry */
    memcpy(entry, name_dir_entry, MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);

    length = (lfn_entries + 1) * MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;
    bytes_written = fat_file_write(&fs_info->fat, fat_fd,
                                   empty_file_offset,
                                   length, fs_info->cl_buf);
    if (bytes_written == (ssize_t) length)
        return 0;
    else if (bytes_written == -1)
        return -1;
    else
        rtems_set_errno_and_return_minus_one(EIO);
}

int
msdos_find_name_in_fat_file (
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd,
    bool                                  create_node,
    const uint8_t                        *name_utf8,
    int                                   name_utf8_len,
    msdos_name_type_t                     name_type,
    fat_dir_pos_t                        *dir_pos,
    char                                 *name_dir_entry)
{
    int                                retval                     = 0;
    msdos_fs_info_t                   *fs_info                    = mt_entry->fs_info;
    ssize_t                            name_len_for_save;
    ssize_t                            name_len_for_compare;
    uint32_t                           bts2rd                     = 0;
    uint32_t                           empty_file_offset          = 0;
    uint32_t                           empty_entry_count          = 0;
    unsigned int                       lfn_entries;
    rtems_dosfs_convert_control       *converter = fs_info->converter;
    void                              *buffer = converter->buffer.data;
    size_t                             buffer_size = converter->buffer.size;

    assert(name_utf8_len > 0);

    fat_dir_pos_init(dir_pos);



    if (FAT_FD_OF_ROOT_DIR(fat_fd) &&
        (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
        bts2rd = fat_fd->fat_file_size;
    else
        bts2rd = fs_info->fat.vol.bpc;

    switch ( name_type ) {
        case MSDOS_NAME_SHORT:
            name_len_for_compare = msdos_filename_utf8_to_short_name_for_compare (
                converter,
                name_utf8,
                name_utf8_len,
                buffer,
                MSDOS_SHORT_NAME_LEN);
            if (name_len_for_compare > 0) {
                lfn_entries = 0;
            }
            else
                retval = -1;
        break;
        case MSDOS_NAME_LONG:
            name_len_for_save = msdos_filename_utf8_to_long_name_for_save (
                converter,
                name_utf8,
                name_utf8_len,
                buffer,
                buffer_size);
            if (name_len_for_save > 0) {
                lfn_entries = (name_len_for_save + MSDOS_LFN_ENTRY_SIZE - 1)
                    / MSDOS_LFN_ENTRY_SIZE;
                name_len_for_compare = msdos_filename_utf8_to_long_name_for_compare (
                  converter,
                  name_utf8,
                  name_utf8_len,
                  buffer,
                  buffer_size);
                if (0 >= name_len_for_compare) {
                    retval = -1;
                }
            }
            else
                retval = -1;
        break;
        default:
            errno = EINVAL;
            retval = -1;
        break;
    }
    if (retval == RC_OK) {
      /* See if the file/directory does already exist */
      retval = msdos_find_file_in_directory (
          buffer,
          name_len_for_compare,
          name_len_for_save,
          name_type,
          fs_info,
          fat_fd,
          bts2rd,
          create_node,
          lfn_entries,
          name_dir_entry,
          dir_pos,
          &empty_file_offset,
          &empty_entry_count);
    }
    /* Create a non-existing file/directory if requested */
    if (   retval == RC_OK
        && create_node) {
        switch (name_type) {
          case MSDOS_NAME_SHORT:
              name_len_for_save = msdos_filename_utf8_to_short_name_for_save (
                  converter,
                  name_utf8,
                  name_utf8_len,
                  buffer,
                  MSDOS_SHORT_NAME_LEN);
              if (name_len_for_save > 0 ) {
                  lfn_entries = 0;
              }
              else
                  retval = -1;
          break;
          case MSDOS_NAME_LONG:
              name_len_for_save = msdos_filename_utf8_to_long_name_for_save (
                  converter,
                  name_utf8,
                  name_utf8_len,
                  buffer,
                  buffer_size);
              if (name_len_for_save > 0) {
                  lfn_entries = (name_len_for_save + MSDOS_LFN_ENTRY_SIZE - 1)
                    / MSDOS_LFN_ENTRY_SIZE;
              }
              else
                  retval = -1;
          break;
          default:
              errno = EINVAL;
              retval = -1;
          break;
        }

        if (retval == RC_OK)
            retval = msdos_add_file (
                buffer,
                name_type,
                fs_info,
                fat_fd,
                bts2rd,
                lfn_entries,
                name_dir_entry,
                dir_pos,
                empty_file_offset,
                empty_entry_count
            );
    }

    return retval;
}


/* msdos_find_node_by_cluster_num_in_fat_file --
 *     Find node with specified number of cluster in fat-file.
 *
 * Note, not updated in the LFN change because it is only used
 *       for . and .. entries and these are always short.
 *
 * PARAMETERS:
 *     mt_entry  - mount table entry
 *     fat_fd    - fat-file descriptor
 *     cl4find   - number of cluster to find
 *     paux      - identify a node location on the disk -
 *                 cluster num and offset inside the cluster
 *     dir_entry - placeholder for found node
 *
 * RETURNS:
 *     RC_OK on success, or error code if error occured
 *
 */
int msdos_find_node_by_cluster_num_in_fat_file(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    fat_file_fd_t                        *fat_fd,
    uint32_t                              cl4find,
    fat_dir_pos_t                        *dir_pos,
    char                                 *dir_entry
    )
{
    int              rc = RC_OK;
    ssize_t          ret = 0;
    msdos_fs_info_t *fs_info = mt_entry->fs_info;
    uint32_t         bts2rd = 0;
    uint32_t         i = 0, j = 0;

    if (FAT_FD_OF_ROOT_DIR(fat_fd) &&
       (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16)))
        bts2rd = fat_fd->fat_file_size;
    else
        bts2rd = fs_info->fat.vol.bpc;

    while ((ret = fat_file_read(&fs_info->fat, fat_fd, j * bts2rd, bts2rd,
                                  fs_info->cl_buf)) != FAT_EOF)
    {
        if ( ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE )
            rtems_set_errno_and_return_minus_one( EIO );

        assert(ret == bts2rd);

        for (i = 0; i < bts2rd; i += MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            char* entry = (char*) fs_info->cl_buf + i;

            /* if this and all rest entries are empty - return not-found */
            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) ==
                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
                return MSDOS_NAME_NOT_FOUND_ERR;

            /* if this entry is empty - skip it */
            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) ==
                MSDOS_THIS_DIR_ENTRY_EMPTY)
                continue;

            /* if get a non-empty entry - compare clusters num */
            if (MSDOS_EXTRACT_CLUSTER_NUM(entry) == cl4find)
            {
                /* on success fill aux structure and copy all 32 bytes */
                rc = fat_file_ioctl(&fs_info->fat, fat_fd, F_CLU_NUM, j * bts2rd,
                                    &dir_pos->sname.cln);
                if (rc != RC_OK)
                    return rc;

                dir_pos->sname.ofs = i;
                dir_pos->lname.cln = FAT_FILE_SHORT_NAME;
                dir_pos->lname.ofs = FAT_FILE_SHORT_NAME;

                memcpy(dir_entry, entry,
                       MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE);
                return RC_OK;
            }
        }
        j++;
    }
    return MSDOS_NAME_NOT_FOUND_ERR;
}

int
msdos_sync(rtems_libio_t *iop)
{
    int                rc = RC_OK;
    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;

    msdos_fs_lock(fs_info);

    rc = fat_sync(&fs_info->fat);

    msdos_fs_unlock(fs_info);
    return rc;
}
