diff options
Diffstat (limited to 'fs/fat/fat_write.c')
-rw-r--r-- | fs/fat/fat_write.c | 530 |
1 files changed, 390 insertions, 140 deletions
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 7afc8388b2..20a54a2418 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -8,25 +8,185 @@ #include <common.h> #include <command.h> #include <config.h> +#include <div64.h> #include <fat.h> #include <log.h> #include <malloc.h> -#include <asm/byteorder.h> #include <part.h> +#include <rand.h> +#include <asm/byteorder.h> #include <asm/cache.h> #include <linux/ctype.h> -#include <div64.h> #include <linux/math64.h> #include "fat.c" -static void uppercase(char *str, int len) +static dir_entry *find_directory_entry(fat_itr *itr, char *filename); +static int new_dir_table(fat_itr *itr); + +/* Characters that may only be used in long file names */ +static const char LONG_ONLY_CHARS[] = "+,;=[]"; + +/* Combined size of the name and ext fields in the directory entry */ +#define SHORT_NAME_SIZE 11 + +/** + * str2fat() - convert string to valid FAT name characters + * + * Stop when reaching end of @src or a period. + * Ignore spaces. + * Replace characters that may only be used in long names by underscores. + * Convert lower case characters to upper case. + * + * To avoid assumptions about the code page we do not use characters + * above 0x7f for the short name. + * + * @dest: destination buffer + * @src: source buffer + * @length: size of destination buffer + * Return: number of bytes in destination buffer + */ +static int str2fat(char *dest, char *src, int length) { int i; - for (i = 0; i < len; i++) { - *str = toupper(*str); - str++; + for (i = 0; i < length; ++src) { + char c = *src; + + if (!c || c == '.') + break; + if (c == ' ') + continue; + if (strchr(LONG_ONLY_CHARS, c) || c > 0x7f) + c = '_'; + else if (c >= 'a' && c <= 'z') + c &= 0xdf; + dest[i] = c; + ++i; + } + return i; +} + +/** + * fat_move_to_cluster() - position to first directory entry in cluster + * + * @itr: directory iterator + * @cluster cluster + * Return: 0 for success, -EIO on error + */ +static int fat_move_to_cluster(fat_itr *itr, unsigned int cluster) +{ + unsigned int nbytes; + + /* position to the start of the directory */ + itr->next_clust = cluster; + itr->last_cluster = 0; + if (!fat_next_cluster(itr, &nbytes)) + return -EIO; + itr->dent = (dir_entry *)itr->block; + itr->remaining = nbytes / sizeof(dir_entry) - 1; + return 0; +} + +/** + * set_name() - set short name in directory entry + * + * The function determines if the @filename is a valid short name. + * In this case no long name is needed. + * + * If a long name is needed, a short name is constructed. + * + * @itr: directory iterator + * @filename: long file name + * @shortname: buffer of 11 bytes to receive chosen short name and extension + * Return: number of directory entries needed, negative on error + */ +static int set_name(fat_itr *itr, const char *filename, char *shortname) +{ + char *period; + char *pos; + int period_location; + char buf[13]; + int i; + int ret; + struct { + char name[8]; + char ext[3]; + } dirent; + + if (!filename) + return -EIO; + + /* Initialize buffer */ + memset(&dirent, ' ', sizeof(dirent)); + + /* Convert filename to upper case short name */ + period = strrchr(filename, '.'); + pos = (char *)filename; + if (*pos == '.') { + pos = period + 1; + period = 0; + } + if (period) + str2fat(dirent.ext, period + 1, sizeof(dirent.ext)); + period_location = str2fat(dirent.name, pos, sizeof(dirent.name)); + if (period_location < 0) + return period_location; + if (*dirent.name == ' ') + *dirent.name = '_'; + /* 0xe5 signals a deleted directory entry. Replace it by 0x05. */ + if (*dirent.name == 0xe5) + *dirent.name = 0x05; + + /* If filename and short name are the same, quit. */ + sprintf(buf, "%.*s.%.3s", period_location, dirent.name, dirent.ext); + if (!strcmp(buf, filename)) { + ret = 1; + goto out; + } + + /* Construct an indexed short name */ + for (i = 1; i < 0x200000; ++i) { + int suffix_len; + int suffix_start; + int j; + + /* To speed up the search use random numbers */ + if (i < 10) { + j = i; + } else { + j = 30 - fls(i); + j = 10 + (rand() >> j); + } + sprintf(buf, "~%d", j); + suffix_len = strlen(buf); + suffix_start = 8 - suffix_len; + if (suffix_start > period_location) + suffix_start = period_location; + memcpy(dirent.name + suffix_start, buf, suffix_len); + if (*dirent.ext != ' ') + sprintf(buf, "%.*s.%.3s", suffix_start + suffix_len, + dirent.name, dirent.ext); + else + sprintf(buf, "%.*s", suffix_start + suffix_len, + dirent.name); + debug("generated short name: %s\n", buf); + + /* Check that the short name does not exist yet. */ + ret = fat_move_to_cluster(itr, itr->start_clust); + if (ret) + return ret; + if (find_directory_entry(itr, buf)) + continue; + + debug("chosen short name: %s\n", buf); + /* Each long name directory entry takes 13 characters. */ + ret = (strlen(filename) + 25) / 13; + goto out; } + return -EIO; +out: + memcpy(shortname, dirent.name, SHORT_NAME_SIZE); + return ret; } static int total_sector; @@ -50,67 +210,6 @@ static int disk_write(__u32 block, __u32 nr_blocks, void *buf) return ret; } -/** - * set_name() - set short name in directory entry - * - * @dirent: directory entry - * @filename: long file name - */ -static void set_name(dir_entry *dirent, const char *filename) -{ - char s_name[VFAT_MAXLEN_BYTES]; - char *period; - int period_location, len, i, ext_num; - - if (filename == NULL) - return; - - len = strlen(filename); - if (len == 0) - return; - - strncpy(s_name, filename, VFAT_MAXLEN_BYTES - 1); - s_name[VFAT_MAXLEN_BYTES - 1] = '\0'; - uppercase(s_name, len); - - period = strchr(s_name, '.'); - if (period == NULL) { - period_location = len; - ext_num = 0; - } else { - period_location = period - s_name; - ext_num = len - period_location - 1; - } - - /* Pad spaces when the length of file name is shorter than eight */ - if (period_location < 8) { - memcpy(dirent->name, s_name, period_location); - for (i = period_location; i < 8; i++) - dirent->name[i] = ' '; - } else if (period_location == 8) { - memcpy(dirent->name, s_name, period_location); - } else { - memcpy(dirent->name, s_name, 6); - /* - * TODO: Translating two long names with the same first six - * characters to the same short name is utterly wrong. - * Short names must be unique. - */ - dirent->name[6] = '~'; - dirent->name[7] = '1'; - } - - if (ext_num < 3) { - memcpy(dirent->ext, s_name + period_location + 1, ext_num); - for (i = ext_num; i < 3; i++) - dirent->ext[i] = ' '; - } else - memcpy(dirent->ext, s_name + period_location + 1, 3); - - debug("name : %s\n", dirent->name); - debug("ext : %s\n", dirent->ext); -} - /* * Write fat buffer into block device */ @@ -152,6 +251,66 @@ static int flush_dirty_fat_buffer(fsdata *mydata) return 0; } +/** + * fat_find_empty_dentries() - find a sequence of available directory entries + * + * @itr: directory iterator + * @count: number of directory entries to find + * Return: 0 on success or negative error number + */ +static int fat_find_empty_dentries(fat_itr *itr, int count) +{ + unsigned int cluster; + dir_entry *dent; + int remaining; + unsigned int n = 0; + int ret; + + ret = fat_move_to_cluster(itr, itr->start_clust); + if (ret) + return ret; + + for (;;) { + if (!itr->dent) { + log_debug("Not enough directory entries available\n"); + return -ENOSPC; + } + switch (itr->dent->name[0]) { + case 0x00: + case DELETED_FLAG: + if (!n) { + /* Remember first deleted directory entry */ + cluster = itr->clust; + dent = itr->dent; + remaining = itr->remaining; + } + ++n; + if (n == count) + goto out; + break; + default: + n = 0; + break; + } + + next_dent(itr); + if (!itr->dent && + (!itr->is_root || itr->fsdata->fatsize == 32) && + new_dir_table(itr)) + return -ENOSPC; + } +out: + /* Position back to first directory entry */ + if (itr->clust != cluster) { + ret = fat_move_to_cluster(itr, cluster); + if (ret) + return ret; + } + itr->dent = dent; + itr->remaining = remaining; + return 0; +} + /* * Set the file name information from 'name' into 'slotptr', */ @@ -221,15 +380,18 @@ name11_12: return 1; } -static int new_dir_table(fat_itr *itr); static int flush_dir(fat_itr *itr); -/* - * Fill dir_slot entries with appropriate name, id, and attr - * 'itr' will point to a next entry +/** + * fill_dir_slot() - fill directory entries for long name + * + * @itr: directory iterator + * @l_name: long name + * @shortname: short name + * Return: 0 for success, -errno otherwise */ static int -fill_dir_slot(fat_itr *itr, const char *l_name) +fill_dir_slot(fat_itr *itr, const char *l_name, const char *shortname) { __u8 temp_dir_slot_buffer[MAX_LFN_SLOT * sizeof(dir_slot)]; dir_slot *slotptr = (dir_slot *)temp_dir_slot_buffer; @@ -237,7 +399,7 @@ fill_dir_slot(fat_itr *itr, const char *l_name) int idx = 0, ret; /* Get short file name checksum value */ - checksum = mkcksum(itr->dent->name, itr->dent->ext); + checksum = mkcksum(shortname, shortname + 8); do { memset(slotptr, 0x00, sizeof(dir_slot)); @@ -259,11 +421,9 @@ fill_dir_slot(fat_itr *itr, const char *l_name) if (itr->remaining == 0) flush_dir(itr); - /* allocate a cluster for more entries */ - if (!fat_itr_next(itr) && !itr->dent) - if ((itr->is_root && itr->fsdata->fatsize != 32) || - new_dir_table(itr)) - return -1; + next_dent(itr); + if (!itr->dent) + return -EIO; } return 0; @@ -636,17 +796,32 @@ static int find_empty_cluster(fsdata *mydata) return entry; } -/* - * Allocate a cluster for additional directory entries +/** + * new_dir_table() - allocate a cluster for additional directory entries + * + * @itr: directory iterator + * Return: 0 on success, -EIO otherwise */ static int new_dir_table(fat_itr *itr) { fsdata *mydata = itr->fsdata; int dir_newclust = 0; + int dir_oldclust = itr->clust; unsigned int bytesperclust = mydata->clust_size * mydata->sect_size; dir_newclust = find_empty_cluster(mydata); - set_fatent_value(mydata, itr->clust, dir_newclust); + + /* + * Flush before updating FAT to ensure valid directory structure + * in case of failure. + */ + itr->clust = dir_newclust; + itr->next_clust = dir_newclust; + memset(itr->block, 0x00, bytesperclust); + if (flush_dir(itr)) + return -EIO; + + set_fatent_value(mydata, dir_oldclust, dir_newclust); if (mydata->fatsize == 32) set_fatent_value(mydata, dir_newclust, 0xffffff8); else if (mydata->fatsize == 16) @@ -654,13 +829,8 @@ static int new_dir_table(fat_itr *itr) else if (mydata->fatsize == 12) set_fatent_value(mydata, dir_newclust, 0xff8); - itr->clust = dir_newclust; - itr->next_clust = dir_newclust; - if (flush_dirty_fat_buffer(mydata) < 0) - return -1; - - memset(itr->block, 0x00, bytesperclust); + return -EIO; itr->dent = (dir_entry *)itr->block; itr->last_cluster = 1; @@ -961,24 +1131,35 @@ getit: return 0; } -/* - * Fill dir_entry +/** + * fill_dentry() - fill directory entry with shortname + * + * @mydata: private filesystem parameters + * @dentptr: directory entry + * @shortname: chosen short name + * @start_cluster: first cluster of file + * @size: file size + * @attr: file attributes */ static void fill_dentry(fsdata *mydata, dir_entry *dentptr, - const char *filename, __u32 start_cluster, __u32 size, __u8 attr) + const char *shortname, __u32 start_cluster, __u32 size, __u8 attr) { + memset(dentptr, 0, sizeof(*dentptr)); + set_start_cluster(mydata, dentptr, start_cluster); dentptr->size = cpu_to_le32(size); dentptr->attr = attr; - set_name(dentptr, filename); + memcpy(dentptr->name, shortname, SHORT_NAME_SIZE); } -/* - * Find a directory entry based on filename or start cluster number - * If the directory entry is not found, - * the new position for writing a directory entry will be returned +/** + * find_directory_entry() - find a directory entry by filename + * + * @itr: directory iterator + * @filename: name of file to find + * Return: directory entry or NULL */ static dir_entry *find_directory_entry(fat_itr *itr, char *filename) { @@ -1001,13 +1182,6 @@ static dir_entry *find_directory_entry(fat_itr *itr, char *filename) return itr->dent; } - /* allocate a cluster for more entries */ - if (!itr->dent && - (!itr->is_root || itr->fsdata->fatsize == 32) && - new_dir_table(itr)) - /* indicate that allocating dent failed */ - itr->dent = NULL; - return NULL; } @@ -1157,6 +1331,8 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, retdent->size = cpu_to_le32(pos + size); } else { /* Create a new file */ + char shortname[SHORT_NAME_SIZE]; + int ndent; if (itr->is_root) { /* root dir cannot have "." or ".." */ @@ -1167,31 +1343,30 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, } } - if (!itr->dent) { - printf("Error: allocating new dir entry\n"); - ret = -EIO; - goto exit; - } - if (pos) { /* No hole allowed */ ret = -EINVAL; goto exit; } - memset(itr->dent, 0, sizeof(*itr->dent)); - - /* Calculate checksum for short name */ - set_name(itr->dent, filename); - - /* Set long name entries */ - if (fill_dir_slot(itr, filename)) { - ret = -EIO; + /* Check if long name is needed */ + ndent = set_name(itr, filename, shortname); + if (ndent < 0) { + ret = ndent; goto exit; } + ret = fat_find_empty_dentries(itr, ndent); + if (ret) + goto exit; + if (ndent > 1) { + /* Set long name entries */ + ret = fill_dir_slot(itr, filename, shortname); + if (ret) + goto exit; + } /* Set short name entry */ - fill_dentry(itr->fsdata, itr->dent, filename, 0, size, + fill_dentry(itr->fsdata, itr->dent, shortname, 0, size, ATTR_ARCH); retdent = itr->dent; @@ -1270,27 +1445,91 @@ exit: return count; } -static int delete_dentry(fat_itr *itr) +/** + * delete_single_dentry() - delete a single directory entry + * + * @itr: directory iterator + * Return: 0 for success + */ +static int delete_single_dentry(fat_itr *itr) +{ + struct dir_entry *dent = itr->dent; + + memset(dent, 0, sizeof(*dent)); + dent->name[0] = DELETED_FLAG; + + if (!itr->remaining) { + if (flush_dir(itr)) { + printf("error: writing directory entry\n"); + return -EIO; + } + } + return 0; +} + +/** + * delete_long_name() - delete long name directory entries + * + * @itr: directory iterator + * Return: 0 for success + */ +static int delete_long_name(fat_itr *itr) +{ + struct dir_entry *dent = itr->dent; + int seqn = itr->dent->name[0] & ~LAST_LONG_ENTRY_MASK; + + while (seqn--) { + int ret; + + ret = delete_single_dentry(itr); + if (ret) + return ret; + dent = next_dent(itr); + if (!dent) + return -EIO; + } + return 0; +} + +/** + * delete_dentry_long() - remove directory entry + * + * @itr: directory iterator + * Return: 0 for success + */ +static int delete_dentry_long(fat_itr *itr) { fsdata *mydata = itr->fsdata; - dir_entry *dentptr = itr->dent; + dir_entry *dent = itr->dent; /* free cluster blocks */ - clear_fatent(mydata, START(dentptr)); + clear_fatent(mydata, START(dent)); if (flush_dirty_fat_buffer(mydata) < 0) { printf("Error: flush fat buffer\n"); return -EIO; } + /* Position to first directory entry for long name */ + if (itr->clust != itr->dent_clust) { + int ret; - /* - * update a directory entry - * TODO: - * - long file name support - * - find and mark the "new" first invalid entry as name[0]=0x00 - */ - memset(dentptr, 0, sizeof(*dentptr)); - dentptr->name[0] = 0xe5; + ret = fat_move_to_cluster(itr, itr->dent_clust); + if (ret) + return ret; + } + itr->dent = itr->dent_start; + itr->remaining = itr->dent_rem; + dent = itr->dent_start; + /* Delete long name */ + if ((dent->attr & ATTR_VFAT) == ATTR_VFAT && + (dent->name[0] & LAST_LONG_ENTRY_MASK)) { + int ret; + ret = delete_long_name(itr); + if (ret) + return ret; + } + /* Delete short name */ + delete_single_dentry(itr); if (flush_dir(itr)) { printf("error: writing directory entry\n"); return -EIO; @@ -1360,7 +1599,7 @@ int fat_unlink(const char *filename) } } - ret = delete_dentry(itr); + ret = delete_dentry_long(itr); exit: free(fsdata.fatbuf); @@ -1424,6 +1663,9 @@ int fat_mkdir(const char *new_dirname) ret = -EEXIST; goto exit; } else { + char shortname[SHORT_NAME_SIZE]; + int ndent; + if (itr->is_root) { /* root dir cannot have "." or ".." */ if (!strcmp(l_dirname, ".") || @@ -1433,20 +1675,24 @@ int fat_mkdir(const char *new_dirname) } } - if (!itr->dent) { - printf("Error: allocating new dir entry\n"); - ret = -EIO; + /* Check if long name is needed */ + ndent = set_name(itr, dirname, shortname); + if (ndent < 0) { + ret = ndent; goto exit; } - - memset(itr->dent, 0, sizeof(*itr->dent)); - - /* Set short name to set alias checksum field in dir_slot */ - set_name(itr->dent, dirname); - fill_dir_slot(itr, dirname); + ret = fat_find_empty_dentries(itr, ndent); + if (ret) + goto exit; + if (ndent > 1) { + /* Set long name entries */ + ret = fill_dir_slot(itr, dirname, shortname); + if (ret) + goto exit; + } /* Set attribute as archive for regular file */ - fill_dentry(itr->fsdata, itr->dent, dirname, 0, 0, + fill_dentry(itr->fsdata, itr->dent, shortname, 0, 0, ATTR_DIR | ATTR_ARCH); retdent = itr->dent; @@ -1468,7 +1714,11 @@ int fat_mkdir(const char *new_dirname) memcpy(dotdent[1].name, ".. ", 8); memcpy(dotdent[1].ext, " ", 3); dotdent[1].attr = ATTR_DIR | ATTR_ARCH; - set_start_cluster(mydata, &dotdent[1], itr->start_clust); + + if (itr->is_root) + set_start_cluster(mydata, &dotdent[1], 0); + else + set_start_cluster(mydata, &dotdent[1], itr->start_clust); ret = set_contents(mydata, retdent, 0, (__u8 *)dotdent, bytesperclust, &actwrite); |