diff mbox series

[06/18] fs: fat: create correct short names

Message ID 20201129020216.4865-7-xypron.glpk@gmx.de
State Accepted, archived
Commit 28cef9ca2e86d90b6f1266a7eefa8557ae0ba144
Delegated to: Tom Rini
Headers show
Series fs: fat: fix long name support | expand

Commit Message

Heinrich Schuchardt Nov. 29, 2020, 2:02 a.m. UTC
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: c30a15e590c ("FAT: Add FAT write feature")
Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
---
 fs/fat/fat_write.c | 215 +++++++++++++++++++++++++++++----------------
 lib/Kconfig        |   2 +-
 2 files changed, 140 insertions(+), 77 deletions(-)

--
2.29.2
diff mbox series

Patch

diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index 4da342f9c9..058b566629 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -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 < 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;

-	for (i = 0; i < len; i++) {
-		*str = toupper(*str);
-		str++;
+	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,
diff --git a/lib/Kconfig b/lib/Kconfig
index 7673d2e4e0..06eb8d07dc 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -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