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










                                                         



                   




































































                                                                           

                                                                  

































































































                                                                              
                                                                

































































































                                                                               
































































                                                                              





                                                              
                                                                               


                                                                    


































                                                                            





                                                              
                                                                             


                                                                  





























































                                                                              
/*
 *  fat_fat_operations.c
 *
 *  General operations on File Allocation Table
 *
 *  Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia
 *  Author: Eugeny S. Mints <Eugeny.Mints@oktet.ru>
 *
 *  @(#) $Id$
 */

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>

#include <rtems/libio_.h>

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

/* fat_scan_fat_for_free_clusters --
 *     Allocate chain of free clusters from Files Allocation Table
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     chain    - the number of the first allocated cluster (first cluster 
 *                in  the chain)
 *     count    - count of clusters to allocate (chain length)
 *
 * RETURNS:
 *     RC_OK on success, or error code if error occured (errno set 
 *     appropriately)
 *
 *     
 */
int 
fat_scan_fat_for_free_clusters(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    unsigned32                           *chain,
    unsigned32                            count,
    unsigned32                           *cls_added,
    unsigned32                           *last_cl 
    )
{
    int            rc = RC_OK;
    fat_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32     cl4find = 2;
    unsigned32     next_cln = 0;
    unsigned32     save_cln = 0; 
    unsigned32     data_cls_val = fs_info->vol.data_cls + 2;
    unsigned32     i = 2;
  
    *cls_added = 0;    

    if (count == 0)
        return rc;
        
    if ((fs_info->vol.type & FAT_FAT32) && 
        (fs_info->vol.next_cl != FAT_UNDEFINED_VALUE))  
        cl4find = fs_info->vol.next_cl;  

    /* 
     * fs_info->vol.data_cls is exactly the count of data clusters 
     * starting at cluster 2, so the maximum valid cluster number is 
     * (fs_info->vol.data_cls + 1)
     */
    while (i < data_cls_val)
    {
        rc = fat_get_fat_cluster(mt_entry, cl4find, &next_cln);
        if ( rc != RC_OK )
        {
            if (*cls_added != 0)
                fat_free_fat_clusters_chain(mt_entry, (*chain));
            return rc;
        }    

        /*if ((next_cln & fs_info->vol.mask) == FAT_GENFAT_FREE)*/
        if (next_cln == FAT_GENFAT_FREE)
        {
            /*
             * We are enforced to process allocation of the first free cluster
             * by separate 'if' statement because otherwise undo function 
             * wouldn't work properly
             */
            if (*cls_added == 0)
            {
                *chain = cl4find;
                rc = fat_set_fat_cluster(mt_entry, cl4find, FAT_GENFAT_EOC);
                if ( rc != RC_OK )
                {
                    /* 
                     * this is the first cluster we tried to allocate so no 
                     * cleanup activity needed 
                     */
                     return rc; 
                }
            }
            else
            {  
                /* set EOC value to new allocated cluster */
                rc = fat_set_fat_cluster(mt_entry, cl4find, FAT_GENFAT_EOC);
                if ( rc != RC_OK )
                {
                    /* cleanup activity */
                    fat_free_fat_clusters_chain(mt_entry, (*chain));
                    return rc; 
                }

                rc = fat_set_fat_cluster(mt_entry, save_cln, cl4find);
                if ( rc != RC_OK )
                {
                    /* cleanup activity */
                    fat_free_fat_clusters_chain(mt_entry, (*chain));
                    /* trying to save last allocated cluster for future use */
                    fat_set_fat_cluster(mt_entry, cl4find, FAT_GENFAT_FREE);
                    fat_buf_release(fs_info);
                    return rc; 
                }
            }  

            save_cln = cl4find;
            (*cls_added)++;

            /* have we satisfied request ? */
            if (*cls_added == count)
            {
                if (fs_info->vol.type & FAT_FAT32) 
                {
                    fs_info->vol.next_cl = save_cln;
                    if (fs_info->vol.free_cls != 0xFFFFFFFF)
                        fs_info->vol.free_cls -= (*cls_added);
                }        
                *last_cl = save_cln;    
                fat_buf_release(fs_info);
                return rc;  
            }  
        }
        i++;
        cl4find++;  
        if (cl4find >= data_cls_val)
            cl4find = 2;
    }

    if (fs_info->vol.type & FAT_FAT32)
    { 
        fs_info->vol.next_cl = save_cln;
        if (fs_info->vol.free_cls != 0xFFFFFFFF)
            fs_info->vol.free_cls -= (*cls_added);    
    }        
    *last_cl = save_cln;
    fat_buf_release(fs_info);
    return RC_OK;    
} 

/* fat_free_fat_clusters_chain --
 *     Free chain of clusters in Files Allocation Table.
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     chain    - number of the first cluster in  the chain 
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured (errno set appropriately)
 */
int 
fat_free_fat_clusters_chain(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    unsigned32                            chain
    )
{
    int            rc = RC_OK, rc1 = RC_OK;
    fat_fs_info_t *fs_info = mt_entry->fs_info;
    unsigned32     cur_cln = chain; 
    unsigned32     next_cln = 0; 
    unsigned32     freed_cls_cnt = 0;
    
    while ((cur_cln & fs_info->vol.mask) < fs_info->vol.eoc_val)
    {
        rc = fat_get_fat_cluster(mt_entry, cur_cln, &next_cln);
        if ( rc != RC_OK )
        {
            if ((fs_info->vol.type & FAT_FAT32) && 
                (fs_info->vol.free_cls != FAT_UNDEFINED_VALUE))
                fs_info->vol.free_cls += freed_cls_cnt;
            fat_buf_release(fs_info);    
            return rc;
        }    

        rc = fat_set_fat_cluster(mt_entry, cur_cln, FAT_GENFAT_FREE);
        if ( rc != RC_OK )
            rc1 = rc;

        freed_cls_cnt++;
        cur_cln = next_cln;
    }

    if (fs_info->vol.type & FAT_FAT32)
    {
        fs_info->vol.next_cl = chain;
        if (fs_info->vol.free_cls != FAT_UNDEFINED_VALUE)
            fs_info->vol.free_cls += freed_cls_cnt;
    }    

    fat_buf_release(fs_info);
    if (rc1 != RC_OK)
        return rc1;

    return RC_OK; 
}

/* fat_get_fat_cluster --
 *     Fetches the contents of the cluster (link to next cluster in the chain) 
 *     from Files Allocation Table.
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     cln      - number of cluster to fetch the contents from
 *     ret_val  - contents of the cluster 'cln' (link to next cluster in 
 *                the chain) 
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *     and errno set appropriately
 */
int
fat_get_fat_cluster(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    unsigned32                            cln,
    unsigned32                           *ret_val
    )
{
    int                     rc = RC_OK;
    register fat_fs_info_t *fs_info = mt_entry->fs_info;
    bdbuf_buffer           *block0 = NULL;
    unsigned32              sec = 0;
    unsigned32              ofs = 0;

    /* sanity check */
    if ( (cln < 2) || (cln > (fs_info->vol.data_cls + 1)) )
        set_errno_and_return_minus_one(EIO);

    sec = (FAT_FAT_OFFSET(fs_info->vol.type, cln) >> fs_info->vol.sec_log2) + 
          fs_info->vol.afat_loc;
    ofs = FAT_FAT_OFFSET(fs_info->vol.type, cln) & (fs_info->vol.bps - 1);

    rc = fat_buf_access(fs_info, sec, FAT_OP_TYPE_READ, &block0);
    if (rc != RC_OK)
        return rc;

    switch ( fs_info->vol.type )
    {
        case FAT_FAT12:
            /* 
             * we are enforced in complex computations for FAT12 to escape CPU
             * align problems for some architectures
             */
            *ret_val = (*((unsigned8 *)(block0->buffer + ofs)));
            if ( ofs == (fs_info->vol.bps - 1) )
            {
                rc = fat_buf_access(fs_info, sec + 1, FAT_OP_TYPE_READ, 
                                    &block0);
                if (rc != RC_OK)
                    return rc;

                *ret_val |= (*((unsigned8 *)(block0->buffer)))<<8;
            }
            else
            {  
                *ret_val |= (*((unsigned8 *)(block0->buffer + ofs + 1)))<<8;
            }

            if ( FAT_CLUSTER_IS_ODD(cln) )
                *ret_val = (*ret_val) >> FAT12_SHIFT;
            else
                *ret_val = (*ret_val) & FAT_FAT12_MASK;
            break;

        case FAT_FAT16:
            *ret_val = *((unsigned16 *)(block0->buffer + ofs));
            *ret_val = CF_LE_W(*ret_val);
            break;

        case FAT_FAT32:
            *ret_val = *((unsigned32 *)(block0->buffer + ofs));
            *ret_val = CF_LE_L(*ret_val);
            break;

        default:
            set_errno_and_return_minus_one(EIO);
            break;
    }

    return RC_OK;
}

/* fat_set_fat_cluster --
 *     Set the contents of the cluster (link to next cluster in the chain)
 *     from Files Allocation Table.
 *
 * PARAMETERS:
 *     mt_entry - mount table entry
 *     cln      - number of cluster to set contents to
 *     in_val   - value to set 
 *
 * RETURNS:
 *     RC_OK on success, or -1 if error occured
 *     and errno set appropriately
 */
int
fat_set_fat_cluster(
    rtems_filesystem_mount_table_entry_t *mt_entry,
    unsigned32                            cln,
    unsigned32                            in_val
    )
{
    int                rc = RC_OK;
    fat_fs_info_t     *fs_info = mt_entry->fs_info;
    unsigned32         sec = 0;
    unsigned32         ofs = 0;
    unsigned16         fat16_clv = 0;
    unsigned32         fat32_clv = 0;
    bdbuf_buffer      *block0 = NULL;  

    /* sanity check */
    if ( (cln < 2) || (cln > (fs_info->vol.data_cls + 1)) )
        set_errno_and_return_minus_one(EIO);

    sec = (FAT_FAT_OFFSET(fs_info->vol.type, cln) >> fs_info->vol.sec_log2) + 
          fs_info->vol.afat_loc;
    ofs = FAT_FAT_OFFSET(fs_info->vol.type, cln) & (fs_info->vol.bps - 1);
  
    rc = fat_buf_access(fs_info, sec, FAT_OP_TYPE_READ, &block0);
    if (rc != RC_OK)
        return rc;

    switch ( fs_info->vol.type )
    {
        case FAT_FAT12:
            if ( FAT_CLUSTER_IS_ODD(cln) )
            {
#if 0
	      /* 
	       * do not perform endian conversion explicitely,
	       * because following code will enforce little 
	       * endian format implicitly!
	       */		 
                fat16_clv = CT_LE_W((((unsigned16)in_val) << FAT_FAT12_SHIFT));
#else
                fat16_clv = ((unsigned16)in_val) << FAT_FAT12_SHIFT;
#endif
                *((unsigned8 *)(block0->buffer + ofs)) = 
                        (*((unsigned8 *)(block0->buffer + ofs))) & 0x0F;

                *((unsigned8 *)(block0->buffer + ofs)) = 
                        (*((unsigned8 *)(block0->buffer + ofs))) | 
                        (unsigned8)(fat16_clv & 0x00FF);

                fat_buf_mark_modified(fs_info);
                
                if ( ofs == (fs_info->vol.bps - 1) )
                {
                    rc = fat_buf_access(fs_info, sec + 1, FAT_OP_TYPE_READ, 
                                        &block0);
                    if (rc != RC_OK)
                        return rc;

                     *((unsigned8 *)(block0->buffer)) &= 0x00;
  
                     *((unsigned8 *)(block0->buffer)) = 
                            (*((unsigned8 *)(block0->buffer))) | 
                            (unsigned8)((fat16_clv & 0xFF00)>>8);
                     
                     fat_buf_mark_modified(fs_info);
                }
                else
                {
                    *((unsigned8 *)(block0->buffer + ofs + 1)) &= 0x00;
              
                    *((unsigned8 *)(block0->buffer + ofs + 1)) = 
                            (*((unsigned8 *)(block0->buffer + ofs + 1))) | 
                            (unsigned8)((fat16_clv & 0xFF00)>>8);
                }  
            }
            else
            {
#if 0
	      /* 
	       * do not perform endian conversion explicitely,
	       * because following code will enforce little 
	       * endian format implicitly!
	       */		 
                fat16_clv = CT_LE_W((((unsigned16)in_val) & FAT_FAT12_MASK));
#else
                fat16_clv = ((unsigned16)in_val) & FAT_FAT12_MASK;
#endif
                *((unsigned8 *)(block0->buffer + ofs)) &= 0x00;

                *((unsigned8 *)(block0->buffer + ofs)) = 
                        (*((unsigned8 *)(block0->buffer + ofs))) | 
                        (unsigned8)(fat16_clv & 0x00FF);
                        
                fat_buf_mark_modified(fs_info);         
 
                if ( ofs == (fs_info->vol.bps - 1) )
                {
                    rc = fat_buf_access(fs_info, sec + 1, FAT_OP_TYPE_READ, 
                                        &block0);
                    if (rc != RC_OK)
                        return rc;

                    *((unsigned8 *)(block0->buffer)) = 
                            (*((unsigned8 *)(block0->buffer))) & 0xF0;
  
                    *((unsigned8 *)(block0->buffer)) = 
                            (*((unsigned8 *)(block0->buffer))) | 
                            (unsigned8)((fat16_clv & 0xFF00)>>8);
                            
                    fat_buf_mark_modified(fs_info);        
                }
                else
                {
                    *((unsigned8 *)(block0->buffer + ofs + 1)) =   
                      (*((unsigned8 *)(block0->buffer + ofs + 1))) & 0xF0;

                    *((unsigned8 *)(block0->buffer + ofs+1)) = 
                           (*((unsigned8 *)(block0->buffer + ofs+1))) | 
                           (unsigned8)((fat16_clv & 0xFF00)>>8);
                }  
            }
            break;

        case FAT_FAT16:
            *((unsigned16 *)(block0->buffer + ofs)) = 
                    (unsigned16)(CT_LE_W(in_val));
            fat_buf_mark_modified(fs_info);        
            break;

        case FAT_FAT32:
            fat32_clv = CT_LE_L((in_val & FAT_FAT32_MASK));

            *((unsigned32 *)(block0->buffer + ofs)) = 
            (*((unsigned32 *)(block0->buffer + ofs))) & (CT_LE_L(0xF0000000));

            *((unsigned32 *)(block0->buffer + ofs)) = 
                   fat32_clv | (*((unsigned32 *)(block0->buffer + ofs)));
            
            fat_buf_mark_modified(fs_info);
            break;

        default:
            set_errno_and_return_minus_one(EIO);
            break;

    }

    return RC_OK;
}