summaryrefslogblamecommitdiffstats
path: root/cpukit/libfs/src/dosfs/msdos_dir.c
blob: be6b5a91ff1f7386aee2a474a1d0df71cdd13c18 (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                           
                                         






                   
                  

                   













                               
                                                                         
                                                      
   
   

                                                                   
 
                                  
                                             
                                                                 
                                                          



                                                                
                                                    
 



                                                   
                  




                                               
 







                                                                       
                                                                             

                        
   



                                             
                                                                 
                                                          



                                                                
                                                    
 



                                                        
                  





                                               
                                   
                                                                        
                                                                         
                                                              












                                                                  
                                                                        
   
              




















                                                       
                                                  





                                                                    
                  










                                         
                                                    







                                     

                                                                              




                                                                            
               


                                                                             







                                                       
                                                                         

                         
       
                                                              


                                             
                                                                 
                                                          

                                         
                                 
                               



                                    


                                                       




                                                                            

                                                



                                                                        
                                                                             
                                                                           


                                           
                                                                 
                                                                 
                                  



                                                                
                                                  
 
                     


                                                              
                                                                             
                                                                      

                                                                 
                                                                         



                                                       
                                                      



                                                                    
                                                      
 



                                                     

                                                    
                                                           
                              

             

                                                                             
                         
 


                                                                                   
                         

              

                                                                         
               

                                                                 
             


                        
 
















                                                                              
 






















                                                                             
 








                                                                               
 


                                                          
 



                                                             
 

















                                                
             
                
             
                                      
 








































































                                                                                  
 












                                                                            
             
 













                                               
                    
  






                                                                            
                                             






                                            
                                                                             

                         
   
               


                                                

                                             
                                                        




                                                                
                                                  
 
                                                                        
                              
                                                          




                                                                 
 














                                                                             

                                                                             
                                         











                                                                         
                                                          
                                                                 
 


                                                                
                                                  
 




                                                           
/*
 *  MSDOS directory handlers implementation
 *
 *  Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
 *  Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.com/license/LICENSE.
 *
 *  @(#) $Id$
 */
#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <rtems/libio_.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <dirent.h>

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

#include "msdos.h"

/* msdos_dir_open --
 *     Open fat-file which correspondes to the directory being opened and
 *     set offset field of file control block to zero.
 */
int
msdos_dir_open(rtems_libio_t *iop, const char *pathname, int oflag,
               mode_t mode)
{
    int                rc = RC_OK;
    rtems_status_code  sc = RTEMS_SUCCESSFUL;
    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;
    fat_file_fd_t     *fat_fd = iop->pathinfo.node_access;

    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
    if (sc != RTEMS_SUCCESSFUL)
        rtems_set_errno_and_return_minus_one( EIO );

    rc = fat_file_reopen(fat_fd);
    if (rc != RC_OK)
    {
        rtems_semaphore_release(fs_info->vol_sema);
        return rc;
    }

    iop->offset = 0;
    rtems_semaphore_release(fs_info->vol_sema);
    return RC_OK;
}

/* msdos_dir_close --
 *     Close  fat-file which correspondes to the directory being closed
 *
 * PARAMETERS:
 *     iop - file control block
 *
 * RETURNS:
 *     RC_OK, if directory closed successfully, or -1 if error occured (errno
 *     set apropriately.
 */
int
msdos_dir_close(rtems_libio_t *iop)
{
    int                rc = RC_OK;
    rtems_status_code  sc = RTEMS_SUCCESSFUL;
    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;
    fat_file_fd_t     *fat_fd = iop->pathinfo.node_access;

    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
    if (sc != RTEMS_SUCCESSFUL)
        rtems_set_errno_and_return_minus_one( EIO );

    rc = fat_file_close(iop->pathinfo.mt_entry, fat_fd);
    if (rc != RC_OK)
    {
        rtems_semaphore_release(fs_info->vol_sema);
        return rc;
    }

    rtems_semaphore_release(fs_info->vol_sema);
    return RC_OK;
}

/*  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
 */
static 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 += i + 1; /* extension + dot */
    src_tmp = src + MSDOS_SHORT_BASE_LEN;
    while (i-- > 0) {
      *dst++ = tolower((unsigned char)(*src_tmp++));
      len++;
    }
  }
  *dst = '\0'; /* terminate string */

  return len;
}

/*  msdos_dir_read --
 *      This routine will read the next directory entry based on the directory
 *      offset. The offset should be equal to -n- time the size of an
 *      individual dirent structure. If n is not an integer multiple of the
 *      sizeof a dirent structure, an integer division will be performed to
 *      determine directory entry that will be returned in the buffer. Count
 *      should reflect -m- times the sizeof dirent bytes to be placed in the
 *      buffer.
 *      If there are not -m- dirent elements from the current directory
 *      position to the end of the exisiting file, the remaining entries will
 *      be placed in the buffer and the returned value will be equal to
 *      -m actual- times the size of a directory entry.
 *
 * PARAMETERS:
 *     iop    - file control block
 *     buffer - buffer provided by user
 *     count  - count of bytes to read
 *
 * RETURNS:
 *     the number of bytes read on success, or -1 if error occured (errno
 *     set apropriately).
 */
ssize_t
msdos_dir_read(rtems_libio_t *iop, void *buffer, size_t count)
{
    int                rc = RC_OK;
    rtems_status_code  sc = RTEMS_SUCCESSFUL;
    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;
    fat_file_fd_t     *fat_fd = iop->pathinfo.node_access;
    fat_file_fd_t     *tmp_fat_fd = NULL;
    struct dirent      tmp_dirent;
    uint32_t           start = 0;
    ssize_t            ret = 0;
    uint32_t           cmpltd = 0;
    uint32_t           j = 0, i = 0;
    uint32_t           bts2rd = 0;
    uint32_t           cur_cln = 0;
    uint32_t           lfn_start = FAT_FILE_SHORT_NAME;
    uint8_t            lfn_checksum = 0;
    int                lfn_entries = 0;

    /*
     * cast start and count - protect against using sizes that are not exact
     * multiples of the -dirent- size. These could result in unexpected
     * results
     */
    start = iop->offset / sizeof(struct dirent);
    count = (count / sizeof(struct dirent)) * sizeof(struct dirent);

    /*
     * optimization: we know that root directory for FAT12/16 volumes is
     * sequential set of sectors and any cluster is sequential set of sectors
     * too, so read such set of sectors is quick operation for low-level IO
     * layer.
     */
    bts2rd = (FAT_FD_OF_ROOT_DIR(fat_fd) &&
             (fs_info->fat.vol.type & (FAT_FAT12 | FAT_FAT16))) ?
             fat_fd->fat_file_size                              :
             fs_info->fat.vol.bpc;

    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
    if (sc != RTEMS_SUCCESSFUL)
        rtems_set_errno_and_return_minus_one(EIO);

    while (count > 0)
    {
        /*
         * fat-file is already opened by open call, so read it
         * Always read directory fat-file from the beggining because of MSDOS
         * directories feature :( - we should count elements currently
         * present in the directory because there may be holes :)
         */
        ret = fat_file_read(iop->pathinfo.mt_entry, fat_fd, (j * bts2rd),
                            bts2rd, fs_info->cl_buf);
        if (ret < MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE)
        {
            rtems_semaphore_release(fs_info->vol_sema);
            rtems_set_errno_and_return_minus_one(EIO);
        }

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

            /*
             * Is this directory from here on empty ?
             */
            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) ==
                MSDOS_THIS_DIR_ENTRY_AND_REST_EMPTY)
            {
                rtems_semaphore_release(fs_info->vol_sema);
                return cmpltd;
            }

            /* Is the directory entry empty */
            if ((*MSDOS_DIR_ENTRY_TYPE(entry)) == MSDOS_THIS_DIR_ENTRY_EMPTY)
                continue;

            /* Is the directory entry empty a volume label */
            if (((*MSDOS_DIR_ATTR(entry)) & MSDOS_ATTR_VOLUME_ID) &&
                ((*MSDOS_DIR_ATTR(entry) & MSDOS_ATTR_LFN_MASK) != MSDOS_ATTR_LFN))
                continue;

            /*
             * 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)
            {
                int   o;
                char* p;
                int   q;

                /*
                 * Is this is the first entry of a LFN ?
                 */
                if (lfn_start == FAT_FILE_SHORT_NAME)
                {
                    /*
                     * The first entry must have the last long entry flag set.
                     */
                    if ((*MSDOS_DIR_ENTRY_TYPE(entry) &
                         MSDOS_LAST_LONG_ENTRY) == 0)
                        continue;

                    /*
                     * Remember the start location of the long file name.
                     */
                    lfn_start =
                      ((j * bts2rd) + i) / MSDOS_DIRECTORY_ENTRY_STRUCT_SIZE;

                    /*
                     * Get the number of entries so we can count down and
                     * also the checksum of the short entry.
                     */
                    lfn_entries = (*MSDOS_DIR_ENTRY_TYPE(entry) &
                                   MSDOS_LAST_LONG_ENTRY_MASK);
                    lfn_checksum = *MSDOS_DIR_LFN_CHECKSUM(entry);
                    memset (tmp_dirent.d_name, 0, sizeof(tmp_dirent.d_name));
                }

                /*
                 * 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_entries != (*MSDOS_DIR_ENTRY_TYPE(entry) &
                                     MSDOS_LAST_LONG_ENTRY_MASK)) ||
                    (lfn_checksum != *MSDOS_DIR_LFN_CHECKSUM(entry)))
                {
                    lfn_start = FAT_FILE_SHORT_NAME;
                    continue;
                }

                /*
                 * Extract the file name into the directory entry. The data is
                 * stored in UNICODE characters (16bit). No translation is
                 * currently supported.
                 *
                 * The DOS maximum length is 255 characters without the
                 * trailing nul character. We need to range check the length to
                 * fit in the directory entry name field.
                 */

                lfn_entries--;
                p = entry + 1;
                o = lfn_entries * MSDOS_LFN_LEN_PER_ENTRY;

                for (q = 0; q < MSDOS_LFN_LEN_PER_ENTRY; q++)
                {
                    if (o >= (sizeof(tmp_dirent.d_name) - 1))
                        break;

                    tmp_dirent.d_name[o++] = *p;

                    if (*p == '\0')
                        break;

                    switch (q)
                    {
                        case 4:
                            p += 5;
                            break;
                        case 10:
                            p += 4;
                            break;
                        default:
                            p += 2;
                            break;
                    }
                }
            }
            else
            {
                fat_dir_pos_t dir_pos;

                /*
                 * Skip active entries until get the entry to start from.
                 */
                if (start)
                {
                    lfn_start = FAT_FILE_SHORT_NAME;
                    start--;
                    continue;
                }

                /*
                 * Move the entry to the return buffer
                 *
                 * unfortunately there is no method to extract ino except to
                 * open fat-file descriptor :( ... so, open it
                 */

                /* get number of cluster we are working with */
                rc = fat_file_ioctl(iop->pathinfo.mt_entry, fat_fd, F_CLU_NUM,
                                    j * bts2rd, &cur_cln);
                if (rc != RC_OK)
                {
                    rtems_semaphore_release(fs_info->vol_sema);
                    return rc;
                }

                fat_dir_pos_init(&dir_pos);
                dir_pos.sname.cln = cur_cln;
                dir_pos.sname.ofs = i;
                rc = fat_file_open(iop->pathinfo.mt_entry, &dir_pos, &tmp_fat_fd);
                if (rc != RC_OK)
                {
                    rtems_semaphore_release(fs_info->vol_sema);
                    return rc;
                }

                /* fill in dirent structure */
                /* XXX: from what and in what d_off should be computed ?! */
                tmp_dirent.d_off = start + cmpltd;
                tmp_dirent.d_reclen = sizeof(struct dirent);
                tmp_dirent.d_ino = tmp_fat_fd->ino;

                /*
                 * If a long file name check if the correct number of
                 * entries have been found and if the checksum is correct.
                 * If not return the short file name.
                 */
                if (lfn_start != FAT_FILE_SHORT_NAME)
                {
                    uint8_t  cs = 0;
                    uint8_t* p = (uint8_t*) entry;
                    int      i;

                    for (i = 0; i < 11; i++, p++)
                        cs = ((cs & 1) ? 0x80 : 0) + (cs >> 1) + *p;

                    if (lfn_entries || (lfn_checksum != cs))
                        lfn_start = FAT_FILE_SHORT_NAME;
                }

                if (lfn_start == FAT_FILE_SHORT_NAME)
                {
                    /*
                     * convert dir entry from fixed 8+3 format (without dot)
                     * to 0..8 + 1dot + 0..3 format
                     */
                    tmp_dirent.d_namlen = msdos_format_dirent_with_dot(
                        tmp_dirent.d_name, entry); /* src text */
                }
                else
                {
                    tmp_dirent.d_namlen = strlen(tmp_dirent.d_name);
                }

                memcpy(buffer + cmpltd, &tmp_dirent, sizeof(struct dirent));

                iop->offset = iop->offset + sizeof(struct dirent);
                cmpltd += (sizeof(struct dirent));
                count -= (sizeof(struct dirent));

                /* inode number extracted, close fat-file */
                rc = fat_file_close(iop->pathinfo.mt_entry, tmp_fat_fd);
                if (rc != RC_OK)
                {
                    rtems_semaphore_release(fs_info->vol_sema);
                    return rc;
                }
            }

            if (count <= 0)
                break;
        }
        j++;
    }

    rtems_semaphore_release(fs_info->vol_sema);
    return cmpltd;
}

/* msdos_dir_write --
 *     no write for directory
 */

/* msdos_dir_stat --
 *
 * This routine will obtain the following information concerning the current
 * directory:
 *     st_dev      device id
 *     st_ino      node serial number :)
 *     st_mode     mode extracted from the node
 *     st_size     total size in bytes
 *     st_blksize  blocksize for filesystem I/O
 *     st_blocks   number of blocks allocated
 *     stat_mtime  time of last modification
 *
 * PARAMETERS:
 *     loc - this directory
 *     buf - stat buffer provided by user
 *
 * RETURNS:
 *     RC_OK and filled stat buffer on success, or -1 if error occured (errno
 *     set apropriately).
 */
int
msdos_dir_stat(
    const rtems_filesystem_location_info_t *loc,
    struct stat *buf
)
{
    rtems_status_code  sc = RTEMS_SUCCESSFUL;
    msdos_fs_info_t   *fs_info = loc->mt_entry->fs_info;
    fat_file_fd_t     *fat_fd = loc->node_access;

    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
    if (sc != RTEMS_SUCCESSFUL)
        rtems_set_errno_and_return_minus_one(EIO);

    buf->st_dev = rtems_disk_get_device_identifier(fs_info->fat.vol.dd);
    buf->st_ino = fat_fd->ino;
    buf->st_mode  = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
    buf->st_rdev = 0ll;
    buf->st_size = fat_fd->fat_file_size;
    buf->st_blocks = fat_fd->fat_file_size >> FAT_SECTOR512_BITS;
    buf->st_blksize = fs_info->fat.vol.bps;
    buf->st_mtime = fat_fd->mtime;

    rtems_semaphore_release(fs_info->vol_sema);
    return RC_OK;
}

/* msdos_dir_truncate --
 *     No truncate for directory.
 *
 * PARAMETERS:
 *
 * RETURNS:
 *
 */

/* msdos_dir_sync --
 *     The following routine does a syncronization on a MSDOS directory node.
 *     DIR_WrtTime, DIR_WrtDate and DIR_fileSize fields of 32 Bytes Directory
 *     Entry Structure should not be updated for directories, so only call
 *     to corresponding fat-file routine.
 *
 * PARAMETERS:
 *     iop - file control block
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set apropriately).
 */
int
msdos_dir_sync(rtems_libio_t *iop)
{
    int                rc = RC_OK;
    rtems_status_code  sc = RTEMS_SUCCESSFUL;
    fat_file_fd_t     *fat_fd = iop->pathinfo.node_access;
    msdos_fs_info_t   *fs_info = iop->pathinfo.mt_entry->fs_info;

    sc = rtems_semaphore_obtain(fs_info->vol_sema, RTEMS_WAIT,
                                MSDOS_VOLUME_SEMAPHORE_TIMEOUT);
    if (sc != RTEMS_SUCCESSFUL)
        rtems_set_errno_and_return_minus_one(EIO);

    rc = fat_file_datasync(iop->pathinfo.mt_entry, fat_fd);

    rtems_semaphore_release(fs_info->vol_sema);
    return rc;
}