fs: fat: create correct short names
The current function set_name() used to create short names has the
following deficiencies resolved by this patch:
* Long names (e.g. FOO.TXT) are stored even if a short name is enough.
* Short names with spaces are created, e.g. "A ~1.TXT".
* Short names with illegal characters are created, e.g. "FOO++BAR".
* Debug output does not not consider that the short file name has no
concluding '\0'.
The solution for the following bug is split of into a separate patch:
* Short file names must be unique.
This patch only provides the loop over possible short file names.
Fixes: c30a15e590
("FAT: Add FAT write feature")
Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
This commit is contained in:
parent
d236e825a2
commit
28cef9ca2e
@ -8,25 +8,140 @@
|
||||
#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)
|
||||
/* Characters that may only be used in long file names */
|
||||
static const char LONG_ONLY_CHARS[] = "+,;=[]";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @dirent: directory entry
|
||||
* @filename: long file name
|
||||
* Return: number of directory entries needed, negative on error
|
||||
*/
|
||||
static int set_name(dir_entry *dirent, const char *filename)
|
||||
{
|
||||
char *period;
|
||||
char *pos;
|
||||
int period_location;
|
||||
char buf[13];
|
||||
int i;
|
||||
|
||||
if (!filename)
|
||||
return -EIO;
|
||||
|
||||
/* Initialize buffers */
|
||||
memset(dirent->name, ' ', sizeof(dirent->name));
|
||||
memset(dirent->ext, ' ', sizeof(dirent->ext));
|
||||
|
||||
/* 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))
|
||||
return 1;
|
||||
|
||||
/* 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("short name: %s\n", buf);
|
||||
/* TODO: Check that the short name does not exist yet. */
|
||||
|
||||
/* Each long name directory entry takes 13 characters. */
|
||||
return (strlen(filename) + 25) / 13;
|
||||
}
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int total_sector;
|
||||
@ -50,67 +165,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
|
||||
*/
|
||||
@ -1181,13 +1235,15 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
|
||||
|
||||
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 */
|
||||
ret = set_name(itr->dent, filename);
|
||||
if (ret < 0)
|
||||
goto exit;
|
||||
if (ret > 1) {
|
||||
/* Set long name entries */
|
||||
ret = fill_dir_slot(itr, filename);
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Set short name entry */
|
||||
@ -1441,9 +1497,16 @@ int fat_mkdir(const char *new_dirname)
|
||||
|
||||
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);
|
||||
/* Check if long name is needed */
|
||||
ret = set_name(itr->dent, dirname);
|
||||
if (ret < 0)
|
||||
goto exit;
|
||||
if (ret > 1) {
|
||||
/* Set long name entries */
|
||||
ret = fill_dir_slot(itr, dirname);
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Set attribute as archive for regular file */
|
||||
fill_dentry(itr->fsdata, itr->dent, dirname, 0, 0,
|
||||
|
@ -168,7 +168,7 @@ config REGEX
|
||||
choice
|
||||
prompt "Pseudo-random library support type"
|
||||
depends on NET_RANDOM_ETHADDR || RANDOM_UUID || CMD_UUID || \
|
||||
RNG_SANDBOX || UT_LIB && AES
|
||||
RNG_SANDBOX || UT_LIB && AES || FAT_WRITE
|
||||
default LIB_RAND
|
||||
help
|
||||
Select the library to provide pseudo-random number generator
|
||||
|
Loading…
Reference in New Issue
Block a user