]> git.sur5r.net Git - u-boot/blobdiff - fs/fat/fat_write.c
fs/fat/fatwrite: Local variable as buffer to store dir_slot entries
[u-boot] / fs / fat / fat_write.c
index 9f5e9118522e8dd371da428cf4fb1593a6af0af7..40a3860e47c9f58d63dc6b813b53fd772bd11c96 100644 (file)
@@ -13,6 +13,8 @@
 #include <asm/byteorder.h>
 #include <part.h>
 #include <linux/ctype.h>
+#include <div64.h>
+#include <linux/math64.h>
 #include "fat.c"
 
 static void uppercase(char *str, int len)
@@ -28,7 +30,9 @@ static void uppercase(char *str, int len)
 static int total_sector;
 static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
 {
-       if (!cur_dev || !cur_dev->block_write)
+       ulong ret;
+
+       if (!cur_dev)
                return -1;
 
        if (cur_part_info.start + block + nr_blocks >
@@ -37,8 +41,11 @@ static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
                return -1;
        }
 
-       return cur_dev->block_write(cur_dev->dev,
-                       cur_part_info.start + block, nr_blocks, buf);
+       ret = blk_dwrite(cur_dev, cur_part_info.start + block, nr_blocks, buf);
+       if (nr_blocks && ret == 0)
+               return -1;
+
+       return ret;
 }
 
 /*
@@ -97,13 +104,19 @@ static __u8 num_of_fats;
 /*
  * Write fat buffer into block device
  */
-static int flush_fat_buffer(fsdata *mydata)
+static int flush_dirty_fat_buffer(fsdata *mydata)
 {
        int getsize = FATBUFBLOCKS;
        __u32 fatlength = mydata->fatlength;
        __u8 *bufptr = mydata->fatbuf;
        __u32 startblock = mydata->fatbufnum * FATBUFBLOCKS;
 
+       debug("debug: evicting %d, dirty: %d\n", mydata->fatbufnum,
+             (int)mydata->fat_dirty);
+
+       if ((!mydata->fat_dirty) || (mydata->fatbufnum == -1))
+               return 0;
+
        startblock += mydata->fat_sect;
 
        if (getsize > fatlength)
@@ -123,6 +136,7 @@ static int flush_fat_buffer(fsdata *mydata)
                        return -1;
                }
        }
+       mydata->fat_dirty = 0;
 
        return 0;
 }
@@ -139,6 +153,11 @@ static __u32 get_fatent_value(fsdata *mydata, __u32 entry)
        __u32 ret = 0x00;
        __u16 val1, val2;
 
+       if (CHECK_CLUST(entry, mydata->fatsize)) {
+               printf("Error: Invalid FAT entry: 0x%08x\n", entry);
+               return ret;
+       }
+
        switch (mydata->fatsize) {
        case 32:
                bufnum = entry / FAT32BUFSIZE;
@@ -171,14 +190,11 @@ static __u32 get_fatent_value(fsdata *mydata, __u32 entry)
                if (getsize > fatlength)
                        getsize = fatlength;
 
-               fatlength *= mydata->sect_size; /* We want it in bytes now */
                startblock += mydata->fat_sect; /* Offset from start of disk */
 
                /* Write back the fatbuf to the disk */
-               if (mydata->fatbufnum != -1) {
-                       if (flush_fat_buffer(mydata) < 0)
-                               return -1;
-               }
+               if (flush_dirty_fat_buffer(mydata) < 0)
+                       return -1;
 
                if (disk_read(startblock, getsize, bufptr) < 0) {
                        debug("Error reading FAT blocks\n");
@@ -311,13 +327,12 @@ static void flush_dir_table(fsdata *mydata, dir_entry **dentptr);
 static void
 fill_dir_slot(fsdata *mydata, dir_entry **dentptr, const char *l_name)
 {
-       dir_slot *slotptr = (dir_slot *)get_contents_vfatname_block;
+       __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)];
+       dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer;
        __u8 counter = 0, checksum;
        int idx = 0, ret;
-       char s_name[16];
 
-       /* Get short file name and checksum value */
-       strncpy(s_name, (*dentptr)->name, 16);
+       /* Get short file name checksum value */
        checksum = mkcksum((*dentptr)->name, (*dentptr)->ext);
 
        do {
@@ -485,10 +500,8 @@ static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
                if (getsize > fatlength)
                        getsize = fatlength;
 
-               if (mydata->fatbufnum != -1) {
-                       if (flush_fat_buffer(mydata) < 0)
-                               return -1;
-               }
+               if (flush_dirty_fat_buffer(mydata) < 0)
+                       return -1;
 
                if (disk_read(startblock, getsize, bufptr) < 0) {
                        debug("Error reading FAT blocks\n");
@@ -497,6 +510,9 @@ static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
                mydata->fatbufnum = bufnum;
        }
 
+       /* Mark as dirty */
+       mydata->fat_dirty = 1;
+
        /* Set the actual entry */
        switch (mydata->fatsize) {
        case 32:
@@ -513,7 +529,8 @@ static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value)
 }
 
 /*
- * Determine the entry value at index 'entry' in a FAT (16/32) table
+ * Determine the next free cluster after 'entry' in a FAT (16/32) table
+ * and link it to 'entry'. EOC marker is not set on returned entry.
  */
 static __u32 determine_fatent(fsdata *mydata, __u32 entry)
 {
@@ -522,6 +539,7 @@ static __u32 determine_fatent(fsdata *mydata, __u32 entry)
        while (1) {
                next_fat = get_fatent_value(mydata, next_entry);
                if (next_fat == 0) {
+                       /* found free entry, link to entry */
                        set_fatent_value(mydata, entry, next_entry);
                        break;
                }
@@ -541,8 +559,9 @@ static int
 set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
             unsigned long size)
 {
-       int idx = 0;
+       __u32 idx = 0;
        __u32 startsect;
+       int ret;
 
        if (clustnum > 0)
                startsect = mydata->data_begin +
@@ -552,26 +571,45 @@ set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
 
        debug("clustnum: %d, startsect: %d\n", clustnum, startsect);
 
-       if ((size / mydata->sect_size) > 0) {
-               if (disk_write(startsect, size / mydata->sect_size, buffer) < 0) {
-                       debug("Error writing data\n");
+       if ((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1)) {
+               ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
+
+               printf("FAT: Misaligned buffer address (%p)\n", buffer);
+
+               while (size >= mydata->sect_size) {
+                       memcpy(tmpbuf, buffer, mydata->sect_size);
+                       ret = disk_write(startsect++, 1, tmpbuf);
+                       if (ret != 1) {
+                               debug("Error writing data (got %d)\n", ret);
+                               return -1;
+                       }
+
+                       buffer += mydata->sect_size;
+                       size -= mydata->sect_size;
+               }
+       } else if (size >= mydata->sect_size) {
+               idx = size / mydata->sect_size;
+               ret = disk_write(startsect, idx, buffer);
+               if (ret != idx) {
+                       debug("Error writing data (got %d)\n", ret);
                        return -1;
                }
-       }
 
-       if (size % mydata->sect_size) {
-               __u8 tmpbuf[mydata->sect_size];
+               startsect += idx;
+               idx *= mydata->sect_size;
+               buffer += idx;
+               size -= idx;
+       }
 
-               idx = size / mydata->sect_size;
-               buffer += idx * mydata->sect_size;
-               memcpy(tmpbuf, buffer, size % mydata->sect_size);
+       if (size) {
+               ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size);
 
-               if (disk_write(startsect + idx, 1, tmpbuf) < 0) {
-                       debug("Error writing data\n");
+               memcpy(tmpbuf, buffer, size);
+               ret = disk_write(startsect, 1, tmpbuf);
+               if (ret != 1) {
+                       debug("Error writing data (got %d)\n", ret);
                        return -1;
                }
-
-               return 0;
        }
 
        return 0;
@@ -616,7 +654,7 @@ static void flush_dir_table(fsdata *mydata, dir_entry **dentptr)
 
        dir_curclust = dir_newclust;
 
-       if (flush_fat_buffer(mydata) < 0)
+       if (flush_dirty_fat_buffer(mydata) < 0)
                return;
 
        memset(get_dentfromdir_block, 0x00,
@@ -646,7 +684,7 @@ static int clear_fatent(fsdata *mydata, __u32 entry)
        }
 
        /* Flush fat buffer */
-       if (flush_fat_buffer(mydata) < 0)
+       if (flush_dirty_fat_buffer(mydata) < 0)
                return -1;
 
        return 0;
@@ -655,24 +693,34 @@ static int clear_fatent(fsdata *mydata, __u32 entry)
 /*
  * Write at most 'maxsize' bytes from 'buffer' into
  * the file associated with 'dentptr'
- * Return the number of bytes read or -1 on fatal errors.
+ * Update the number of bytes written in *gotsize and return 0
+ * or return -1 on fatal errors.
  */
 static int
 set_contents(fsdata *mydata, dir_entry *dentptr, __u8 *buffer,
-             unsigned long maxsize)
+             loff_t maxsize, loff_t *gotsize)
 {
-       unsigned long filesize = FAT2CPU32(dentptr->size), gotsize = 0;
+       loff_t filesize = FAT2CPU32(dentptr->size);
        unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
        __u32 curclust = START(dentptr);
        __u32 endclust = 0, newclust = 0;
-       unsigned long actsize;
+       loff_t actsize;
 
-       debug("Filesize: %ld bytes\n", filesize);
+       *gotsize = 0;
+       debug("Filesize: %llu bytes\n", filesize);
 
        if (maxsize > 0 && filesize > maxsize)
                filesize = maxsize;
 
-       debug("%ld bytes\n", filesize);
+       debug("%llu bytes\n", filesize);
+
+       if (!curclust) {
+               if (filesize) {
+                       debug("error: nonempty clusterless file!\n");
+                       return -1;
+               }
+               return 0;
+       }
 
        actsize = bytesperclust;
        endclust = curclust;
@@ -685,32 +733,21 @@ set_contents(fsdata *mydata, dir_entry *dentptr, __u8 *buffer,
                                goto getit;
 
                        if (CHECK_CLUST(newclust, mydata->fatsize)) {
-                               debug("curclust: 0x%x\n", newclust);
+                               debug("newclust: 0x%x\n", newclust);
                                debug("Invalid FAT entry\n");
-                               return gotsize;
+                               return 0;
                        }
                        endclust = newclust;
                        actsize += bytesperclust;
                }
-               /* actsize >= file size */
-               actsize -= bytesperclust;
-               /* set remaining clusters */
-               if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
-                       debug("error: writing cluster\n");
-                       return -1;
-               }
 
                /* set remaining bytes */
-               gotsize += (int)actsize;
-               filesize -= actsize;
-               buffer += actsize;
                actsize = filesize;
-
-               if (set_cluster(mydata, endclust, buffer, (int)actsize) != 0) {
+               if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
                        debug("error: writing cluster\n");
                        return -1;
                }
-               gotsize += actsize;
+               *gotsize += actsize;
 
                /* Mark end of file in FAT */
                if (mydata->fatsize == 16)
@@ -719,20 +756,20 @@ set_contents(fsdata *mydata, dir_entry *dentptr, __u8 *buffer,
                        newclust = 0xfffffff;
                set_fatent_value(mydata, endclust, newclust);
 
-               return gotsize;
+               return 0;
 getit:
                if (set_cluster(mydata, curclust, buffer, (int)actsize) != 0) {
                        debug("error: writing cluster\n");
                        return -1;
                }
-               gotsize += (int)actsize;
+               *gotsize += actsize;
                filesize -= actsize;
                buffer += actsize;
 
-               if (CHECK_CLUST(curclust, mydata->fatsize)) {
-                       debug("curclust: 0x%x\n", curclust);
+               if (CHECK_CLUST(newclust, mydata->fatsize)) {
+                       debug("newclust: 0x%x\n", newclust);
                        debug("Invalid FAT entry\n");
-                       return gotsize;
+                       return 0;
                }
                actsize = bytesperclust;
                curclust = endclust = newclust;
@@ -740,15 +777,24 @@ getit:
 }
 
 /*
- * Fill dir_entry
+ * Set start cluster in directory entry
  */
-static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
-       const char *filename, __u32 start_cluster, __u32 size, __u8 attr)
+static void set_start_cluster(const fsdata *mydata, dir_entry *dentptr,
+                               __u32 start_cluster)
 {
        if (mydata->fatsize == 32)
                dentptr->starthi =
                        cpu_to_le16((start_cluster & 0xffff0000) >> 16);
        dentptr->start = cpu_to_le16(start_cluster & 0xffff);
+}
+
+/*
+ * Fill dir_entry
+ */
+static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
+       const char *filename, __u32 start_cluster, __u32 size, __u8 attr)
+{
+       set_start_cluster(mydata, dentptr, start_cluster);
        dentptr->size = cpu_to_le32(size);
 
        dentptr->attr = attr;
@@ -761,9 +807,9 @@ static void fill_dentry(fsdata *mydata, dir_entry *dentptr,
  * exceed the size of the block device
  * Return -1 when overflow occurs, otherwise return 0
  */
-static int check_overflow(fsdata *mydata, __u32 clustnum, unsigned long size)
+static int check_overflow(fsdata *mydata, __u32 clustnum, loff_t size)
 {
-       __u32 startsect, sect_num;
+       __u32 startsect, sect_num, offset;
 
        if (clustnum > 0) {
                startsect = mydata->data_begin +
@@ -772,13 +818,13 @@ static int check_overflow(fsdata *mydata, __u32 clustnum, unsigned long size)
                startsect = mydata->rootdir_sect;
        }
 
-       sect_num = size / mydata->sect_size;
-       if (size % mydata->sect_size)
+       sect_num = div_u64_rem(size, mydata->sect_size, &offset);
+
+       if (offset != 0)
                sect_num++;
 
        if (startsect + sect_num > cur_part_info.start + total_sector)
                return -1;
-
        return 0;
 }
 
@@ -881,8 +927,30 @@ static dir_entry *find_directory_entry(fsdata *mydata, int startsect,
                        return dentptr;
                }
 
+               /*
+                * In FAT16/12, the root dir is locate before data area, shows
+                * in following:
+                * -------------------------------------------------------------
+                * | Boot | FAT1 & 2 | Root dir | Data (start from cluster #2) |
+                * -------------------------------------------------------------
+                *
+                * As a result if curclust is in Root dir, it is a negative
+                * number or 0, 1.
+                *
+                */
+               if (mydata->fatsize != 32 && (int)curclust <= 1) {
+                       /* Current clust is in root dir, set to next clust */
+                       curclust++;
+                       if ((int)curclust <= 1)
+                               continue;       /* continue to find */
+
+                       /* Reach the end of root dir */
+                       empty_dentptr = dentptr;
+                       return NULL;
+               }
+
                curclust = get_fatent_value(mydata, dir_curclust);
-               if ((curclust >= 0xffffff8) || (curclust >= 0xfff8)) {
+               if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
                        empty_dentptr = dentptr;
                        return NULL;
                }
@@ -896,8 +964,8 @@ static dir_entry *find_directory_entry(fsdata *mydata, int startsect,
        return NULL;
 }
 
-static int do_fat_write(const char *filename, void *buffer,
-       unsigned long size)
+static int do_fat_write(const char *filename, void *buffer, loff_t size,
+                       loff_t *actwrite)
 {
        dir_entry *dentptr, *retdent;
        __u32 startsect;
@@ -909,8 +977,8 @@ static int do_fat_write(const char *filename, void *buffer,
        int cursect;
        int ret = -1, name_len;
        char l_filename[VFAT_MAXLEN_BYTES];
-       int write_size = size;
 
+       *actwrite = size;
        dir_curclust = 0;
 
        if (read_bootsectandvi(&bs, &volinfo, &mydata->fatsize)) {
@@ -920,7 +988,7 @@ static int do_fat_write(const char *filename, void *buffer,
 
        total_sector = bs.total_sect;
        if (total_sector == 0)
-               total_sector = cur_part_info.size;
+               total_sector = (int)cur_part_info.size; /* cast of lbaint_t */
 
        if (mydata->fatsize == 32)
                mydata->fatlength = bs.fat32_length;
@@ -952,7 +1020,8 @@ static int do_fat_write(const char *filename, void *buffer,
        }
 
        mydata->fatbufnum = -1;
-       mydata->fatbuf = malloc(FATBUFSIZE);
+       mydata->fat_dirty = 0;
+       mydata->fatbuf = memalign(ARCH_DMA_MINALIGN, FATBUFSIZE);
        if (mydata->fatbuf == NULL) {
                debug("Error: allocating memory\n");
                return -1;
@@ -981,99 +1050,102 @@ static int do_fat_write(const char *filename, void *buffer,
        if (retdent) {
                /* Update file size and start_cluster in a directory entry */
                retdent->size = cpu_to_le32(size);
-               start_cluster = FAT2CPU16(retdent->start);
-               if (mydata->fatsize == 32)
-                       start_cluster |=
-                               (FAT2CPU16(retdent->starthi) << 16);
-
-               ret = check_overflow(mydata, start_cluster, size);
-               if (ret) {
-                       printf("Error: %ld overflow\n", size);
-                       goto exit;
-               }
+               start_cluster = START(retdent);
+
+               if (start_cluster) {
+                       if (size) {
+                               ret = check_overflow(mydata, start_cluster,
+                                                       size);
+                               if (ret) {
+                                       printf("Error: %llu overflow\n", size);
+                                       goto exit;
+                               }
+                       }
 
-               ret = clear_fatent(mydata, start_cluster);
-               if (ret) {
-                       printf("Error: clearing FAT entries\n");
-                       goto exit;
-               }
+                       ret = clear_fatent(mydata, start_cluster);
+                       if (ret) {
+                               printf("Error: clearing FAT entries\n");
+                               goto exit;
+                       }
 
-               ret = set_contents(mydata, retdent, buffer, size);
-               if (ret < 0) {
-                       printf("Error: writing contents\n");
-                       goto exit;
-               }
-               write_size = ret;
-               debug("attempt to write 0x%x bytes\n", write_size);
-
-               /* Flush fat buffer */
-               ret = flush_fat_buffer(mydata);
-               if (ret) {
-                       printf("Error: flush fat buffer\n");
-                       goto exit;
-               }
+                       if (!size)
+                               set_start_cluster(mydata, retdent, 0);
+               } else if (size) {
+                       ret = start_cluster = find_empty_cluster(mydata);
+                       if (ret < 0) {
+                               printf("Error: finding empty cluster\n");
+                               goto exit;
+                       }
+
+                       ret = check_overflow(mydata, start_cluster, size);
+                       if (ret) {
+                               printf("Error: %llu overflow\n", size);
+                               goto exit;
+                       }
 
-               /* Write directory table to device */
-               ret = set_cluster(mydata, dir_curclust,
-                           get_dentfromdir_block,
-                           mydata->clust_size * mydata->sect_size);
-               if (ret) {
-                       printf("Error: writing directory entry\n");
-                       goto exit;
+                       set_start_cluster(mydata, retdent, start_cluster);
                }
        } else {
                /* Set short name to set alias checksum field in dir_slot */
                set_name(empty_dentptr, filename);
                fill_dir_slot(mydata, &empty_dentptr, filename);
 
-               ret = start_cluster = find_empty_cluster(mydata);
-               if (ret < 0) {
-                       printf("Error: finding empty cluster\n");
-                       goto exit;
-               }
+               if (size) {
+                       ret = start_cluster = find_empty_cluster(mydata);
+                       if (ret < 0) {
+                               printf("Error: finding empty cluster\n");
+                               goto exit;
+                       }
 
-               ret = check_overflow(mydata, start_cluster, size);
-               if (ret) {
-                       printf("Error: %ld overflow\n", size);
-                       goto exit;
+                       ret = check_overflow(mydata, start_cluster, size);
+                       if (ret) {
+                               printf("Error: %llu overflow\n", size);
+                               goto exit;
+                       }
+               } else {
+                       start_cluster = 0;
                }
 
                /* Set attribute as archieve for regular file */
                fill_dentry(mydata, empty_dentptr, filename,
                        start_cluster, size, 0x20);
 
-               ret = set_contents(mydata, empty_dentptr, buffer, size);
-               if (ret < 0) {
-                       printf("Error: writing contents\n");
-                       goto exit;
-               }
-               write_size = ret;
-               debug("attempt to write 0x%x bytes\n", write_size);
-
-               /* Flush fat buffer */
-               ret = flush_fat_buffer(mydata);
-               if (ret) {
-                       printf("Error: flush fat buffer\n");
-                       goto exit;
-               }
+               retdent = empty_dentptr;
+       }
 
-               /* Write directory table to device */
-               ret = set_cluster(mydata, dir_curclust,
-                           get_dentfromdir_block,
-                           mydata->clust_size * mydata->sect_size);
-               if (ret) {
-                       printf("Error: writing directory entry\n");
-                       goto exit;
-               }
+       ret = set_contents(mydata, retdent, buffer, size, actwrite);
+       if (ret < 0) {
+               printf("Error: writing contents\n");
+               goto exit;
+       }
+       debug("attempt to write 0x%llx bytes\n", *actwrite);
+
+       /* Flush fat buffer */
+       ret = flush_dirty_fat_buffer(mydata);
+       if (ret) {
+               printf("Error: flush fat buffer\n");
+               goto exit;
        }
 
+       /* Write directory table to device */
+       ret = set_cluster(mydata, dir_curclust, get_dentfromdir_block,
+                       mydata->clust_size * mydata->sect_size);
+       if (ret)
+               printf("Error: writing directory entry\n");
+
 exit:
        free(mydata->fatbuf);
-       return ret < 0 ? ret : write_size;
+       return ret;
 }
 
-int file_fat_write(const char *filename, void *buffer, unsigned long maxsize)
+int file_fat_write(const char *filename, void *buffer, loff_t offset,
+                  loff_t maxsize, loff_t *actwrite)
 {
+       if (offset != 0) {
+               printf("Error: non zero offset is currently not supported.\n");
+               return -1;
+       }
+
        printf("writing %s\n", filename);
-       return do_fat_write(filename, buffer, maxsize);
+       return do_fat_write(filename, buffer, maxsize, actwrite);
 }