From c5924118c03dc8db30d633de98afe6ad6eb7f277 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 24 Nov 2020 21:04:07 +0100 Subject: fs: fat: correct first cluster for '..' The FAT specification [1] requires that for a '..' directory entry pointing to the root directory the fields DIR_FstClusHi and DIR_FstClusLo are 0. [1] Microsoft FAT Specification, Microsoft Corporation, August 30 2005 Fixes: 31a18d570d96 ("fs: fat: support mkdir") Signed-off-by: Heinrich Schuchardt Acked-by: Marek Szyprowski --- fs/fat/fat_write.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 7afc8388b2..4da342f9c9 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1468,7 +1468,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); -- cgit v1.2.3 From d236e825a214a5684598fa7a7b919af332c43390 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 09:19:52 +0100 Subject: fs: fat: export fat_next_cluster() Rename function next_cluster() to fat_next_cluster() and export it. When creating a new directory entries we should reuse deleted entries. This requires re-scanning the directory. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat.c | 106 ++++++++++++++++++++++++++++++++++++++++------------------ include/fat.h | 7 +++- 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/fs/fat/fat.c b/fs/fat/fat.c index fb6ba89466..674236d68a 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -621,7 +621,7 @@ static int get_fs_info(fsdata *mydata) /* * The root directory is not cluster-aligned and may be on a * "negative" cluster, this will be handled specially in - * next_cluster(). + * fat_next_cluster(). */ mydata->root_cluster = 0; } @@ -647,44 +647,76 @@ static int get_fs_info(fsdata *mydata) return 0; } - -/* - * Directory iterator, to simplify filesystem traversal +/** + * struct fat_itr - directory iterator, to simplify filesystem traversal * * Implements an iterator pattern to traverse directory tables, * transparently handling directory tables split across multiple * clusters, and the difference between FAT12/FAT16 root directory * (contiguous) and subdirectories + FAT32 root (chained). * - * Rough usage: + * Rough usage * - * for (fat_itr_root(&itr, fsdata); fat_itr_next(&itr); ) { - * // to traverse down to a subdirectory pointed to by - * // current iterator position: - * fat_itr_child(&itr, &itr); - * } + * .. code-block:: c * - * For more complete example, see fat_itr_resolve() + * for (fat_itr_root(&itr, fsdata); fat_itr_next(&itr); ) { + * // to traverse down to a subdirectory pointed to by + * // current iterator position: + * fat_itr_child(&itr, &itr); + * } + * + * For a more complete example, see fat_itr_resolve(). */ - -typedef struct { - fsdata *fsdata; /* filesystem parameters */ - unsigned start_clust; /* first cluster */ - unsigned clust; /* current cluster */ - unsigned next_clust; /* next cluster if remaining == 0 */ - int last_cluster; /* set once we've read last cluster */ - int is_root; /* is iterator at root directory */ - int remaining; /* remaining dent's in current cluster */ - - /* current iterator position values: */ - dir_entry *dent; /* current directory entry */ - char l_name[VFAT_MAXLEN_BYTES]; /* long (vfat) name */ - char s_name[14]; /* short 8.3 name */ - char *name; /* l_name if there is one, else s_name */ - - /* storage for current cluster in memory: */ - u8 block[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN); -} fat_itr; +struct fat_itr { + /** + * @fsdata: filesystem parameters + */ + fsdata *fsdata; + /** + * @start_clust: first cluster + */ + unsigned int start_clust; + /** + * @clust: current cluster + */ + unsigned int clust; + /** + * @next_clust: next cluster if remaining == 0 + */ + unsigned int next_clust; + /** + * @last_cluster: set if last cluster of directory reached + */ + int last_cluster; + /** + * @is_root: is iterator at root directory + */ + int is_root; + /** + * @remaining: remaining directory entries in current cluster + */ + int remaining; + /** + * @dent: current directory entry + */ + dir_entry *dent; + /** + * @l_name: long name of current directory entry + */ + char l_name[VFAT_MAXLEN_BYTES]; + /** + * @s_name: short 8.3 name of current directory entry + */ + char s_name[14]; + /** + * @name: l_name if there is one, else s_name + */ + char *name; + /** + * @block: buffer for current cluster + */ + u8 block[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN); +}; static int fat_itr_isdir(fat_itr *itr); @@ -753,7 +785,17 @@ static void fat_itr_child(fat_itr *itr, fat_itr *parent) itr->last_cluster = 0; } -static void *next_cluster(fat_itr *itr, unsigned *nbytes) +/** + * fat_next_cluster() - load next FAT cluster + * + * The function is used when iterating through directories. It loads the + * next cluster with directory entries + * + * @itr: directory iterator + * @nbytes: number of bytes read, 0 on error + * Return: first directory entry, NULL on error + */ +void *fat_next_cluster(fat_itr *itr, unsigned int *nbytes) { fsdata *mydata = itr->fsdata; /* for silly macros */ int ret; @@ -825,7 +867,7 @@ static dir_entry *next_dent(fat_itr *itr) { if (itr->remaining == 0) { unsigned nbytes; - struct dir_entry *dent = next_cluster(itr, &nbytes); + struct dir_entry *dent = fat_next_cluster(itr, &nbytes); /* have we reached the last cluster? */ if (!dent) { diff --git a/include/fat.h b/include/fat.h index 02742f92a5..3c29a4484d 100644 --- a/include/fat.h +++ b/include/fat.h @@ -9,8 +9,9 @@ #ifndef _FAT_H_ #define _FAT_H_ -#include #include +#include +#include struct disk_partition; @@ -179,6 +180,9 @@ typedef struct { int fats; /* Number of FATs */ } fsdata; +struct fat_itr; +typedef struct fat_itr fat_itr; + static inline u32 clust_to_sect(fsdata *fsdata, u32 clust) { return fsdata->data_begin + clust * fsdata->clust_size; @@ -208,4 +212,5 @@ void fat_closedir(struct fs_dir_stream *dirs); int fat_unlink(const char *filename); int fat_mkdir(const char *dirname); void fat_close(void); +void *fat_next_cluster(fat_itr *itr, unsigned int *nbytes); #endif /* _FAT_H_ */ -- cgit v1.2.3 From 28cef9ca2e86d90b6f1266a7eefa8557ae0ba144 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 20 Nov 2020 12:55:22 +0100 Subject: 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: c30a15e590c ("FAT: Add FAT write feature") Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 215 ++++++++++++++++++++++++++++++++++------------------- lib/Kconfig | 2 +- 2 files changed, 140 insertions(+), 77 deletions(-) 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 #include #include +#include #include #include #include -#include #include +#include +#include #include #include -#include #include #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 -- cgit v1.2.3 From a343249bef2faaf256fee2bf921d95cf0f44f367 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 21 Nov 2020 08:32:50 +0100 Subject: fs: fat: pass shortname to fill_dir_slot Currently we pass the short name via the directory iterator. Pass it explicitly as a parameter. This removes the requirement to set the short name in the iterator before writing the long name. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 058b566629..7e8886791c 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -278,12 +278,16 @@ name11_12: 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; @@ -291,7 +295,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)); @@ -317,7 +321,7 @@ fill_dir_slot(fat_itr *itr, const char *l_name) if (!fat_itr_next(itr) && !itr->dent) if ((itr->is_root && itr->fsdata->fatsize != 32) || new_dir_table(itr)) - return -1; + return -EIO; } return 0; @@ -1241,7 +1245,7 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, goto exit; if (ret > 1) { /* Set long name entries */ - ret = fill_dir_slot(itr, filename); + ret = fill_dir_slot(itr, filename, itr->dent->name); if (ret) goto exit; } @@ -1503,7 +1507,7 @@ int fat_mkdir(const char *new_dirname) goto exit; if (ret > 1) { /* Set long name entries */ - ret = fill_dir_slot(itr, dirname); + ret = fill_dir_slot(itr, dirname, itr->dent->name); if (ret) goto exit; } -- cgit v1.2.3 From 57b745e2387a7dafd2d29004351cdd3ffffcc5b9 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 19:19:39 +0100 Subject: fs: fat: call set_name() only once In set_name() we select the short name. Once this is correctly implemented this will be a performance intensive operation because we need to check that the name does not exist yet. So set_name should only be called once. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 87 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 7e8886791c..716b6a6627 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -23,6 +23,9 @@ /* 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 * @@ -68,24 +71,28 @@ static int str2fat(char *dest, char *src, int length) * * If a long name is needed, a short name is constructed. * - * @dirent: directory entry * @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(dir_entry *dirent, const char *filename) +static int set_name(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 buffers */ - memset(dirent->name, ' ', sizeof(dirent->name)); - memset(dirent->ext, ' ', sizeof(dirent->ext)); + /* Initialize buffer */ + memset(&dirent, ' ', sizeof(dirent)); /* Convert filename to upper case short name */ period = strrchr(filename, '.'); @@ -95,20 +102,22 @@ static int set_name(dir_entry *dirent, const char *filename) period = 0; } if (period) - str2fat(dirent->ext, period + 1, sizeof(dirent->ext)); - period_location = str2fat(dirent->name, pos, sizeof(dirent->name)); + 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 = '_'; + if (*dirent.name == ' ') + *dirent.name = '_'; /* 0xe5 signals a deleted directory entry. Replace it by 0x05. */ - if (*dirent->name == 0xe5) - *dirent->name = 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; + 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) { @@ -128,20 +137,24 @@ static int set_name(dir_entry *dirent, const char *filename) 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 != ' ') + memcpy(dirent.name + suffix_start, buf, suffix_len); + if (*dirent.ext != ' ') sprintf(buf, "%.*s.%.3s", suffix_start + suffix_len, - dirent->name, dirent->ext); + dirent.name, dirent.ext); else sprintf(buf, "%.*s", suffix_start + suffix_len, - dirent->name); + 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; + ret = (strlen(filename) + 25) / 13; + goto out; } return -EIO; +out: + memcpy(shortname, dirent.name, SHORT_NAME_SIZE); + return ret; } static int total_sector; @@ -1019,18 +1032,27 @@ 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); } /* @@ -1215,6 +1237,7 @@ 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]; if (itr->is_root) { /* root dir cannot have "." or ".." */ @@ -1237,21 +1260,19 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, goto exit; } - memset(itr->dent, 0, sizeof(*itr->dent)); - /* Check if long name is needed */ - ret = set_name(itr->dent, filename); + ret = set_name(filename, shortname); if (ret < 0) goto exit; if (ret > 1) { /* Set long name entries */ - ret = fill_dir_slot(itr, filename, itr->dent->name); + 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; @@ -1484,6 +1505,8 @@ int fat_mkdir(const char *new_dirname) ret = -EEXIST; goto exit; } else { + char shortname[SHORT_NAME_SIZE]; + if (itr->is_root) { /* root dir cannot have "." or ".." */ if (!strcmp(l_dirname, ".") || @@ -1499,21 +1522,19 @@ int fat_mkdir(const char *new_dirname) goto exit; } - memset(itr->dent, 0, sizeof(*itr->dent)); - /* Check if long name is needed */ - ret = set_name(itr->dent, dirname); + ret = set_name(dirname, shortname); if (ret < 0) goto exit; if (ret > 1) { /* Set long name entries */ - ret = fill_dir_slot(itr, dirname, itr->dent->name); + 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; -- cgit v1.2.3 From 3a331aee561f4551e2e131114c416a70e91ae05f Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Wed, 25 Nov 2020 16:33:55 +0100 Subject: fs: fat: generate unique short names File names must be unique within their directory. So before assigning a short name we must check that it is unique. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 716b6a6627..59cffef34e 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -20,6 +20,8 @@ #include #include "fat.c" +static dir_entry *find_directory_entry(fat_itr *itr, char *filename); + /* Characters that may only be used in long file names */ static const char LONG_ONLY_CHARS[] = "+,;=[]"; @@ -63,6 +65,27 @@ static int str2fat(char *dest, char *src, int length) 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 * @@ -71,11 +94,12 @@ static int str2fat(char *dest, char *src, int length) * * 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(const char *filename, char *shortname) +static int set_name(fat_itr *itr, const char *filename, char *shortname) { char *period; char *pos; @@ -144,9 +168,16 @@ static int set_name(const char *filename, char *shortname) 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. */ + 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; @@ -1261,7 +1292,7 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, } /* Check if long name is needed */ - ret = set_name(filename, shortname); + ret = set_name(itr, filename, shortname); if (ret < 0) goto exit; if (ret > 1) { @@ -1523,7 +1554,7 @@ int fat_mkdir(const char *new_dirname) } /* Check if long name is needed */ - ret = set_name(dirname, shortname); + ret = set_name(itr, dirname, shortname); if (ret < 0) goto exit; if (ret > 1) { -- cgit v1.2.3 From 27ed690382fd6f6e5796ec9c08693a94cfdc0f14 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 19:24:46 +0100 Subject: fs: fat: dentry iterator for fill_dir_slot() For reusing deleted directory entries we have to adjust the function called to step to the next directory entry. This patch alone is not enough to actually reuse deleted directory entries as the fill_dir_slot() is still called with first never used directory entry. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 59cffef34e..0746d73f8d 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -362,7 +362,7 @@ fill_dir_slot(fat_itr *itr, const char *l_name, const char *shortname) flush_dir(itr); /* allocate a cluster for more entries */ - if (!fat_itr_next(itr) && !itr->dent) + if (!next_dent(itr) && !itr->dent) if ((itr->is_root && itr->fsdata->fatsize != 32) || new_dir_table(itr)) return -EIO; -- cgit v1.2.3 From 7557c8485517aa9d2602abb0010c10ab64feee72 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 16:04:47 +0100 Subject: fs: fat: set start cluster for root directory When iterating over a child directory we set itr->start_clust. Do the same when over the root directory. When looking for deleted directory entries or existing short names we will have to iterate over directories a second and third time. With this patch we do not need any special logic for the root directory. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/fat/fat.c b/fs/fat/fat.c index 674236d68a..5a418cfbb7 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -734,7 +734,7 @@ static int fat_itr_root(fat_itr *itr, fsdata *fsdata) return -ENXIO; itr->fsdata = fsdata; - itr->start_clust = 0; + itr->start_clust = fsdata->root_cluster; itr->clust = fsdata->root_cluster; itr->next_clust = fsdata->root_cluster; itr->dent = NULL; @@ -778,6 +778,7 @@ static void fat_itr_child(fat_itr *itr, fat_itr *parent) } else { itr->clust = parent->fsdata->root_cluster; itr->next_clust = parent->fsdata->root_cluster; + itr->start_clust = parent->fsdata->root_cluster; itr->is_root = 1; } itr->dent = NULL; @@ -1067,6 +1068,7 @@ static int fat_itr_resolve(fat_itr *itr, const char *path, unsigned type) /* point back to itself */ itr->clust = itr->fsdata->root_cluster; itr->next_clust = itr->fsdata->root_cluster; + itr->start_clust = itr->fsdata->root_cluster; itr->dent = NULL; itr->remaining = 0; itr->last_cluster = 0; -- cgit v1.2.3 From 569b0e1938a5c78b6f4152704ef8c8a4e04fcb09 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Thu, 26 Nov 2020 19:06:55 +0100 Subject: fs: fat: flush new directory cluster When handling long file names directory entries may be split over multiple clusters. We must make sure that new clusters are zero filled on disk. When allocating a new cluster for a directory flush it. The flushing should be executed before updating the FAT. This way if flushing fails, we still have a valid directory structure. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 0746d73f8d..941f8789ab 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -738,17 +738,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) @@ -756,13 +771,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; -- cgit v1.2.3 From 32a5f887c48e54d0842eef6af63534fd1f6001d7 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 09:58:44 +0100 Subject: fs: fat: fat_find_empty_dentries() Provide a function to find a series of empty directory entries. The current directory is scanned for deleted entries. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 941f8789ab..d560b94b60 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -21,6 +21,7 @@ #include "fat.c" 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[] = "+,;=[]"; @@ -250,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 __maybe_unused 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', */ @@ -319,7 +380,6 @@ name11_12: return 1; } -static int new_dir_table(fat_itr *itr); static int flush_dir(fat_itr *itr); /** -- cgit v1.2.3 From 3049a5106c95781663202c626d2e0f56fa2c6646 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 22 Nov 2020 11:54:22 +0100 Subject: fs: fat: reuse deleted directory entries When creating new directory entries try to reuse entries marked as deleted. In fill_dir_slot() do not allocate new clusters as this has already been done in fat_find_empty_dentries(). Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index d560b94b60..a029ef8ed6 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -258,7 +258,7 @@ static int flush_dirty_fat_buffer(fsdata *mydata) * @count: number of directory entries to find * Return: 0 on success or negative error number */ -static int __maybe_unused fat_find_empty_dentries(fat_itr *itr, int count) +static int fat_find_empty_dentries(fat_itr *itr, int count) { unsigned int cluster; dir_entry *dent; @@ -421,11 +421,9 @@ fill_dir_slot(fat_itr *itr, const char *l_name, const char *shortname) if (itr->remaining == 0) flush_dir(itr); - /* allocate a cluster for more entries */ - if (!next_dent(itr) && !itr->dent) - if ((itr->is_root && itr->fsdata->fatsize != 32) || - new_dir_table(itr)) - return -EIO; + next_dent(itr); + if (!itr->dent) + return -EIO; } return 0; @@ -1339,6 +1337,7 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, } else { /* Create a new file */ char shortname[SHORT_NAME_SIZE]; + int ndent; if (itr->is_root) { /* root dir cannot have "." or ".." */ @@ -1362,10 +1361,15 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer, } /* Check if long name is needed */ - ret = set_name(itr, filename, shortname); - if (ret < 0) + 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 (ret > 1) { + if (ndent > 1) { /* Set long name entries */ ret = fill_dir_slot(itr, filename, shortname); if (ret) @@ -1607,6 +1611,7 @@ int fat_mkdir(const char *new_dirname) goto exit; } else { char shortname[SHORT_NAME_SIZE]; + int ndent; if (itr->is_root) { /* root dir cannot have "." or ".." */ @@ -1624,10 +1629,15 @@ int fat_mkdir(const char *new_dirname) } /* Check if long name is needed */ - ret = set_name(itr, dirname, shortname); - if (ret < 0) + ndent = set_name(itr, dirname, shortname); + if (ndent < 0) { + ret = ndent; + goto exit; + } + ret = fat_find_empty_dentries(itr, ndent); + if (ret) goto exit; - if (ret > 1) { + if (ndent > 1) { /* Set long name entries */ ret = fill_dir_slot(itr, dirname, shortname); if (ret) -- cgit v1.2.3 From 1e51c8d64a945946af72099dd469a3320ce02e7c Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Thu, 26 Nov 2020 16:10:01 +0100 Subject: fs: fat: search file should not allocate cluster Searching for a file is not a write operation. So it should not lead to the allocation of a new cluster to the directory. If we reuse deleted entries, we might not even use the new cluster and due to not flushing it the directory could be corrupted. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index a029ef8ed6..56ea28553b 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1154,10 +1154,12 @@ static void fill_dentry(fsdata *mydata, dir_entry *dentptr, 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) { @@ -1180,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; } @@ -1348,12 +1343,6 @@ 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; @@ -1622,12 +1611,6 @@ int fat_mkdir(const char *new_dirname) } } - if (!itr->dent) { - printf("Error: allocating new dir entry\n"); - ret = -EIO; - goto exit; - } - /* Check if long name is needed */ ndent = set_name(itr, dirname, shortname); if (ndent < 0) { -- cgit v1.2.3 From 4a593dd0c5a1e1af40f47758f2aa1a4074a4ccdc Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Thu, 19 Nov 2020 06:47:35 +0100 Subject: fs: fat: use constant DELETED_FLAG When deleting a directory entry 0xe5 is written to name[0]. We have a constant for this value and should use it consistently. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index 56ea28553b..c403d7d5c6 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1464,7 +1464,7 @@ static int delete_dentry(fat_itr *itr) * - find and mark the "new" first invalid entry as name[0]=0x00 */ memset(dentptr, 0, sizeof(*dentptr)); - dentptr->name[0] = 0xe5; + dentptr->name[0] = DELETED_FLAG; if (flush_dir(itr)) { printf("error: writing directory entry\n"); -- cgit v1.2.3 From 89735b44c4cee895b416f005468370d2943fa8d8 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Thu, 19 Nov 2020 07:44:08 +0100 Subject: fs: fat: first dentry of long name in FAT iterator A long name is split over multiple directory entries. When deleting a file with a long name we need the first directory entry to be able to delete the whole chain. Add the necessary fields to the FAT iterator: * cluster of first directory entry * address of first directory entry * remaining entries in cluster Signed-off-by: Heinrich Schuchardt --- fs/fat/fat.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/fat/fat.c b/fs/fat/fat.c index 5a418cfbb7..47344bb57e 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -700,6 +700,18 @@ struct fat_itr { * @dent: current directory entry */ dir_entry *dent; + /** + * @dent_rem: remaining entries after long name start + */ + int dent_rem; + /** + * @dent_clust: cluster of long name start + */ + unsigned int dent_clust; + /** + * @dent_start: first directory entry for long name + */ + dir_entry *dent_start; /** * @l_name: long name of current directory entry */ @@ -966,9 +978,13 @@ static int fat_itr_next(fat_itr *itr) while (1) { dent = next_dent(itr); - if (!dent) + if (!dent) { + itr->dent_start = NULL; return 0; - + } + itr->dent_rem = itr->remaining; + itr->dent_start = itr->dent; + itr->dent_clust = itr->clust; if (dent->name[0] == DELETED_FLAG) continue; -- cgit v1.2.3 From 3d20d212cf410102ce67b62774c831bd73617424 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Thu, 19 Nov 2020 07:31:18 +0100 Subject: fs: fat: deletion of long file names Long file names are stored in multiple directory entries. When deleting a file we must delete all of them. Signed-off-by: Heinrich Schuchardt --- fs/fat/fat_write.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index c403d7d5c6..20a54a2418 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1445,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] = DELETED_FLAG; + 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; @@ -1535,7 +1599,7 @@ int fat_unlink(const char *filename) } } - ret = delete_dentry(itr); + ret = delete_dentry_long(itr); exit: free(fsdata.fatbuf); -- cgit v1.2.3 From 470fa190f35006e2641d775d4befbd74108837bc Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Wed, 9 Dec 2020 07:38:46 +0100 Subject: efi_loader: don't set EFI_RT_SUPPORTED_UPDATE_CAPSULE The EFI_RT_PROPERTIES_TABLE configuration table indicates which runtime services are available at runtime. Even if CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y, we neither support UpdateCapsule() nor QueryCapsuleCapabilities() at runtime. Thus we should not set the corresponding flags EFI_RT_SUPPORTED_UPDATE_CAPSULE and EFI_RT_SUPPORTED_QUERY_CAPSULE_CAPABILITIES in RuntimeServicesSupported. Fixes: 2bc27ca8a04a ("efi_loader: define UpdateCapsule api") Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_runtime.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c index 0b171c1ff7..93c9478b22 100644 --- a/lib/efi_loader/efi_runtime.c +++ b/lib/efi_loader/efi_runtime.c @@ -133,10 +133,6 @@ efi_status_t efi_init_runtime_supported(void) #ifdef CONFIG_EFI_HAVE_RUNTIME_RESET rt_table->runtime_services_supported |= EFI_RT_SUPPORTED_RESET_SYSTEM; #endif - if (IS_ENABLED(CONFIG_EFI_RUNTIME_UPDATE_CAPSULE)) - rt_table->runtime_services_supported |= - (EFI_RT_SUPPORTED_UPDATE_CAPSULE | - EFI_RT_SUPPORTED_QUERY_CAPSULE_CAPABILITIES); ret = efi_install_configuration_table(&efi_rt_properties_table_guid, rt_table); -- cgit v1.2.3 From 861072b2a88497366a5b33017c4496406f0084b2 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Mon, 7 Dec 2020 18:20:57 +0100 Subject: efi_loader: remove EFI_HII_CONFIG_ROUTING_PROTOCOL Our implementation of the EFI_HII_CONFIG_ROUTING_PROTOCOL is a mere stub, where all services return an error code. The protocol is neither needed for the EFI shell nor for the UEFI SCT. To reduce the code size remove it from the U-Boot binary. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/Makefile | 2 +- lib/efi_loader/efi_hii_config.c | 10 +++++++--- lib/efi_loader/efi_root_node.c | 3 --- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 0afcaf4813..462d4d9ac4 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -30,7 +30,7 @@ obj-y += efi_device_path.o obj-$(CONFIG_EFI_DEVICE_PATH_TO_TEXT) += efi_device_path_to_text.o obj-y += efi_device_path_utilities.o obj-y += efi_file.o -obj-$(CONFIG_EFI_LOADER_HII) += efi_hii.o efi_hii_config.o +obj-$(CONFIG_EFI_LOADER_HII) += efi_hii.o obj-y += efi_image_loader.o obj-y += efi_memory.o obj-y += efi_root_node.o diff --git a/lib/efi_loader/efi_hii_config.c b/lib/efi_loader/efi_hii_config.c index 26ea4b9bc0..237e8acf84 100644 --- a/lib/efi_loader/efi_hii_config.c +++ b/lib/efi_loader/efi_hii_config.c @@ -1,9 +1,13 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * EFI Human Interface Infrastructure ... Configuration + * EFI Human Interface Infrastructure ... Configuration * - * Copyright (c) 2017 Leif Lindholm - * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + * Copyright (c) 2017 Leif Lindholm + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + * + * As this is still a non-working stub and the protocol is neither required + * by the EFI shell nor by the UEFI SCT this module has been removed from + * the Makefile. */ #include diff --git a/lib/efi_loader/efi_root_node.c b/lib/efi_loader/efi_root_node.c index f68b0fdc61..b17db312f7 100644 --- a/lib/efi_loader/efi_root_node.c +++ b/lib/efi_loader/efi_root_node.c @@ -77,9 +77,6 @@ efi_status_t efi_root_node_register(void) /* HII database protocol */ &efi_guid_hii_database_protocol, (void *)&efi_hii_database, - /* HII configuration routing protocol */ - &efi_guid_hii_config_routing_protocol, - (void *)&efi_hii_config_routing, #endif NULL)); efi_root->type = EFI_OBJECT_TYPE_U_BOOT_FIRMWARE; -- cgit v1.2.3 From 0e9d2d7bc25bd3114e8f6ff0d18cdf9ee675909c Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 4 Dec 2020 03:02:03 +0100 Subject: efi_loader: resequence functions in efi_boottime.c For implementing support for the EFI_LOAD_FILE_PROTOCOL in the LoadImage() service we will have to call the LocateDevicePath() service. To avoid a forward declaration resequence the functions. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_boottime.c | 164 +++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 246b59d3b3..4f7479d4df 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1769,6 +1769,88 @@ failure: return ret; } +/** + * efi_locate_device_path() - Get the device path and handle of an device + * implementing a protocol + * @protocol: GUID of the protocol + * @device_path: device path + * @device: handle of the device + * + * This function implements the LocateDevicePath service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_locate_device_path( + const efi_guid_t *protocol, + struct efi_device_path **device_path, + efi_handle_t *device) +{ + struct efi_device_path *dp; + size_t i; + struct efi_handler *handler; + efi_handle_t *handles; + size_t len, len_dp; + size_t len_best = 0; + efi_uintn_t no_handles; + u8 *remainder; + efi_status_t ret; + + EFI_ENTRY("%pUl, %p, %p", protocol, device_path, device); + + if (!protocol || !device_path || !*device_path) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Find end of device path */ + len = efi_dp_instance_size(*device_path); + + /* Get all handles implementing the protocol */ + ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, protocol, NULL, + &no_handles, &handles)); + if (ret != EFI_SUCCESS) + goto out; + + for (i = 0; i < no_handles; ++i) { + /* Find the device path protocol */ + ret = efi_search_protocol(handles[i], &efi_guid_device_path, + &handler); + if (ret != EFI_SUCCESS) + continue; + dp = (struct efi_device_path *)handler->protocol_interface; + len_dp = efi_dp_instance_size(dp); + /* + * This handle can only be a better fit + * if its device path length is longer than the best fit and + * if its device path length is shorter of equal the searched + * device path. + */ + if (len_dp <= len_best || len_dp > len) + continue; + /* Check if dp is a subpath of device_path */ + if (memcmp(*device_path, dp, len_dp)) + continue; + if (!device) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *device = handles[i]; + len_best = len_dp; + } + if (len_best) { + remainder = (u8 *)*device_path + len_best; + *device_path = (struct efi_device_path *)remainder; + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return EFI_EXIT(ret); +} + /** * efi_load_image_from_path() - load an image using a file path * @@ -2403,88 +2485,6 @@ found: return EFI_EXIT(EFI_SUCCESS); } -/** - * efi_locate_device_path() - Get the device path and handle of an device - * implementing a protocol - * @protocol: GUID of the protocol - * @device_path: device path - * @device: handle of the device - * - * This function implements the LocateDevicePath service. - * - * See the Unified Extensible Firmware Interface (UEFI) specification for - * details. - * - * Return: status code - */ -static efi_status_t EFIAPI efi_locate_device_path( - const efi_guid_t *protocol, - struct efi_device_path **device_path, - efi_handle_t *device) -{ - struct efi_device_path *dp; - size_t i; - struct efi_handler *handler; - efi_handle_t *handles; - size_t len, len_dp; - size_t len_best = 0; - efi_uintn_t no_handles; - u8 *remainder; - efi_status_t ret; - - EFI_ENTRY("%pUl, %p, %p", protocol, device_path, device); - - if (!protocol || !device_path || !*device_path) { - ret = EFI_INVALID_PARAMETER; - goto out; - } - - /* Find end of device path */ - len = efi_dp_instance_size(*device_path); - - /* Get all handles implementing the protocol */ - ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, protocol, NULL, - &no_handles, &handles)); - if (ret != EFI_SUCCESS) - goto out; - - for (i = 0; i < no_handles; ++i) { - /* Find the device path protocol */ - ret = efi_search_protocol(handles[i], &efi_guid_device_path, - &handler); - if (ret != EFI_SUCCESS) - continue; - dp = (struct efi_device_path *)handler->protocol_interface; - len_dp = efi_dp_instance_size(dp); - /* - * This handle can only be a better fit - * if its device path length is longer than the best fit and - * if its device path length is shorter of equal the searched - * device path. - */ - if (len_dp <= len_best || len_dp > len) - continue; - /* Check if dp is a subpath of device_path */ - if (memcmp(*device_path, dp, len_dp)) - continue; - if (!device) { - ret = EFI_INVALID_PARAMETER; - goto out; - } - *device = handles[i]; - len_best = len_dp; - } - if (len_best) { - remainder = (u8 *)*device_path + len_best; - *device_path = (struct efi_device_path *)remainder; - ret = EFI_SUCCESS; - } else { - ret = EFI_NOT_FOUND; - } -out: - return EFI_EXIT(ret); -} - /** * efi_install_multiple_protocol_interfaces() - Install multiple protocol * interfaces -- cgit v1.2.3 From b6f11098c9a619f480582b26edd26c5b195c69f4 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 4 Dec 2020 03:33:41 +0100 Subject: efi_loader: move EFI_LOAD_FILE2_PROTOCOL_GUID The EFI_LOAD_FILE_PROTOCOL_GUID and EFI_LOAD_FILE2_PROTOCOL_GUID are needed to complement the implementation of the LoadFile() boot service. Remove a duplicate declaration of a variable for the EFI_LOAD_FILE2_PROTOCOL_GUID. Move the remaining declaration to efi_boottime.c. Add a variable for the EFI_LOAD_FILE_PROTOCOL_GUID. Signed-off-by: Heinrich Schuchardt --- include/efi_loader.h | 3 +++ lib/efi_loader/efi_boottime.c | 3 +++ lib/efi_loader/efi_load_initrd.c | 3 --- lib/efi_selftest/efi_selftest_load_initrd.c | 7 ++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/efi_loader.h b/include/efi_loader.h index 76cd2b36f2..4c6eb8616d 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -195,6 +195,9 @@ extern const efi_guid_t efi_file_system_info_guid; extern const efi_guid_t efi_guid_device_path_utilities_protocol; /* GUID of the deprecated Unicode collation protocol */ extern const efi_guid_t efi_guid_unicode_collation_protocol; +/* GUIDs of the Load File and Load File2 protocol */ +extern const efi_guid_t efi_guid_load_file_protocol; +extern const efi_guid_t efi_guid_load_file2_protocol; /* GUID of the Unicode collation protocol */ extern const efi_guid_t efi_guid_unicode_collation_protocol2; extern const efi_guid_t efi_guid_hii_config_routing_protocol; diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 4f7479d4df..afe8adb91e 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -81,6 +81,9 @@ const efi_guid_t efi_guid_event_group_ready_to_boot = /* event group ResetSystem() invoked (before ExitBootServices) */ const efi_guid_t efi_guid_event_group_reset_system = EFI_EVENT_GROUP_RESET_SYSTEM; +/* GUIDs of the Load File and Load File2 protocols */ +const efi_guid_t efi_guid_load_file_protocol = EFI_LOAD_FILE_PROTOCOL_GUID; +const efi_guid_t efi_guid_load_file2_protocol = EFI_LOAD_FILE2_PROTOCOL_GUID; static efi_status_t EFIAPI efi_disconnect_controller( efi_handle_t controller_handle, diff --git a/lib/efi_loader/efi_load_initrd.c b/lib/efi_loader/efi_load_initrd.c index d517d686c3..4bf3b5ef68 100644 --- a/lib/efi_loader/efi_load_initrd.c +++ b/lib/efi_loader/efi_load_initrd.c @@ -12,9 +12,6 @@ #include #include -static const efi_guid_t efi_guid_load_file2_protocol = - EFI_LOAD_FILE2_PROTOCOL_GUID; - static efi_status_t EFIAPI efi_load_file2_initrd(struct efi_load_file_protocol *this, struct efi_device_path *file_path, bool boot_policy, diff --git a/lib/efi_selftest/efi_selftest_load_initrd.c b/lib/efi_selftest/efi_selftest_load_initrd.c index fe060a6644..f591dcd211 100644 --- a/lib/efi_selftest/efi_selftest_load_initrd.c +++ b/lib/efi_selftest/efi_selftest_load_initrd.c @@ -86,7 +86,6 @@ static int setup(const efi_handle_t handle, static int execute(void) { - efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID; struct efi_load_file_protocol *lf2; struct efi_device_path *dp2, *dp2_invalid; efi_status_t status; @@ -99,13 +98,15 @@ static int execute(void) memset(buffer, 0, sizeof(buffer)); dp2 = (struct efi_device_path *)&dp; - status = boottime->locate_device_path(&lf2_proto_guid, &dp2, &handle); + status = boottime->locate_device_path(&efi_guid_load_file2_protocol, + &dp2, &handle); if (status != EFI_SUCCESS) { efi_st_error("Unable to locate device path\n"); return EFI_ST_FAILURE; } - status = boottime->handle_protocol(handle, &lf2_proto_guid, + status = boottime->handle_protocol(handle, + &efi_guid_load_file2_protocol, (void **)&lf2); if (status != EFI_SUCCESS) { efi_st_error("Unable to locate protocol\n"); -- cgit v1.2.3 From c06c55b1f5deb994ef7544f99419b4ab68f82e4f Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 4 Dec 2020 09:27:41 +0100 Subject: efi_loader: pass boot_policy to efi_load_image_from_path Implementing support for loading images via the EFI_LOAD_FILE_PROTOCOL requires the boot policy as input for efi_load_image_from_path(). Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_boottime.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index afe8adb91e..f18e384c39 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1860,13 +1860,15 @@ out: * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the * callers obligation to update the memory type as needed. * - * @file_path: the path of the image to load - * @buffer: buffer containing the loaded image - * @size: size of the loaded image - * Return: status code + * @boot_policy: true for request originating from the boot manager + * @file_path: the path of the image to load + * @buffer: buffer containing the loaded image + * @size: size of the loaded image + * Return: status code */ static -efi_status_t efi_load_image_from_path(struct efi_device_path *file_path, +efi_status_t efi_load_image_from_path(bool boot_policy, + struct efi_device_path *file_path, void **buffer, efi_uintn_t *size) { struct efi_file_info *info = NULL; @@ -1968,8 +1970,8 @@ efi_status_t EFIAPI efi_load_image(bool boot_policy, } if (!source_buffer) { - ret = efi_load_image_from_path(file_path, &dest_buffer, - &source_size); + ret = efi_load_image_from_path(boot_policy, file_path, + &dest_buffer, &source_size); if (ret != EFI_SUCCESS) goto error; } else { -- cgit v1.2.3 From 0e074d12393ba14536f8a103b28c75f74b7c5896 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 6 Dec 2020 10:47:57 +0100 Subject: efi_loader: carve out efi_load_image_from_file() efi_load_image_from_file() should read via either of: * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL * EFI_LOAD_FILE_PROTOCOL * EFI_LOAD_FILE2_PROTOCOL To make the code readable carve out a function to load the image via the file system protocol. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_boottime.c | 45 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index f18e384c39..1983ca3f6b 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1855,32 +1855,26 @@ out: } /** - * efi_load_image_from_path() - load an image using a file path + * efi_load_image_from_file() - load an image from file system * * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the * callers obligation to update the memory type as needed. * - * @boot_policy: true for request originating from the boot manager * @file_path: the path of the image to load * @buffer: buffer containing the loaded image * @size: size of the loaded image * Return: status code */ static -efi_status_t efi_load_image_from_path(bool boot_policy, - struct efi_device_path *file_path, +efi_status_t efi_load_image_from_file(struct efi_device_path *file_path, void **buffer, efi_uintn_t *size) { struct efi_file_info *info = NULL; struct efi_file_handle *f; - static efi_status_t ret; + efi_status_t ret; u64 addr; efi_uintn_t bs; - /* In case of failure nothing is returned */ - *buffer = NULL; - *size = 0; - /* Open file */ f = efi_file_from_path(file_path); if (!f) @@ -1928,6 +1922,39 @@ error: return ret; } +/** + * efi_load_image_from_path() - load an image using a file path + * + * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the + * callers obligation to update the memory type as needed. + * + * @boot_policy: true for request originating from the boot manager + * @file_path: the path of the image to load + * @buffer: buffer containing the loaded image + * @size: size of the loaded image + * Return: status code + */ +static +efi_status_t efi_load_image_from_path(bool boot_policy, + struct efi_device_path *file_path, + void **buffer, efi_uintn_t *size) +{ + efi_handle_t device; + efi_status_t ret; + struct efi_device_path *dp; + + /* In case of failure nothing is returned */ + *buffer = NULL; + *size = 0; + + dp = file_path; + ret = EFI_CALL(efi_locate_device_path( + &efi_simple_file_system_protocol_guid, &dp, &device)); + if (ret == EFI_SUCCESS) + return efi_load_image_from_file(file_path, buffer, size); + return EFI_NOT_FOUND; +} + /** * efi_load_image() - load an EFI image into memory * @boot_policy: true for request originating from the boot manager -- cgit v1.2.3 From 3da0b28582004981d6ca6866130d2835c3cbf0d0 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sun, 6 Dec 2020 13:00:15 +0100 Subject: efi_loader: support EFI_LOAD_FILE_PROTOCOL Support loading images via the EFI_LOAD_FILE_PROTOCOL and EFI_LOAD_FILE2_PROTOCOL. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_boottime.c | 49 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 1983ca3f6b..9bd63b6e52 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1942,6 +1942,10 @@ efi_status_t efi_load_image_from_path(bool boot_policy, efi_handle_t device; efi_status_t ret; struct efi_device_path *dp; + struct efi_load_file_protocol *load_file_protocol = NULL; + efi_uintn_t buffer_size; + uint64_t addr, pages; + const efi_guid_t *guid; /* In case of failure nothing is returned */ *buffer = NULL; @@ -1952,7 +1956,50 @@ efi_status_t efi_load_image_from_path(bool boot_policy, &efi_simple_file_system_protocol_guid, &dp, &device)); if (ret == EFI_SUCCESS) return efi_load_image_from_file(file_path, buffer, size); - return EFI_NOT_FOUND; + + ret = EFI_CALL(efi_locate_device_path( + &efi_guid_load_file_protocol, &dp, &device)); + if (ret == EFI_SUCCESS) { + guid = &efi_guid_load_file_protocol; + } else if (!boot_policy) { + guid = &efi_guid_load_file2_protocol; + ret = EFI_CALL(efi_locate_device_path(guid, &dp, &device)); + } + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + ret = EFI_CALL(efi_handle_protocol(device, guid, + (void **)&load_file_protocol)); + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + buffer_size = 0; + ret = load_file_protocol->load_file(load_file_protocol, dp, + boot_policy, &buffer_size, + NULL); + if (ret != EFI_BUFFER_TOO_SMALL) + goto out; + pages = efi_size_in_pages(buffer_size); + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, EFI_BOOT_SERVICES_DATA, + pages, &addr); + if (ret != EFI_SUCCESS) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = EFI_CALL(load_file_protocol->load_file( + load_file_protocol, dp, boot_policy, + &buffer_size, (void *)(uintptr_t)addr)); + if (ret != EFI_SUCCESS) + efi_free_pages(addr, pages); +out: + if (load_file_protocol) + EFI_CALL(efi_close_protocol(device, + &efi_guid_load_file2_protocol, + efi_root, NULL)); + if (ret == EFI_SUCCESS) { + *buffer = (void *)(uintptr_t)addr; + *size = buffer_size; + } + + return ret; } /** -- cgit v1.2.3 From 6d78ca8360190a339eaf469d957eb0f89d840bc2 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 8 Dec 2020 07:12:10 +0100 Subject: efi_selftest: clean up Makefile Bring all obj-y entries together. Sort *.o targets. Signed-off-by: Heinrich Schuchardt --- lib/efi_selftest/Makefile | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index 58fb43fcdf..706df0b27b 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -25,9 +25,11 @@ efi_selftest_crc32.o \ efi_selftest_devicepath_util.o \ efi_selftest_events.o \ efi_selftest_event_groups.o \ +efi_selftest_exception.o \ efi_selftest_exitbootservices.o \ efi_selftest_gop.o \ efi_selftest_loaded_image.o \ +efi_selftest_loadimage.o \ efi_selftest_manageprotocols.o \ efi_selftest_mem.o \ efi_selftest_memory.o \ @@ -35,6 +37,8 @@ efi_selftest_open_protocol.o \ efi_selftest_register_notify.o \ efi_selftest_reset.o \ efi_selftest_set_virtual_address_map.o \ +efi_selftest_startimage_exit.o \ +efi_selftest_startimage_return.o \ efi_selftest_textinput.o \ efi_selftest_textinputex.o \ efi_selftest_textoutput.o \ @@ -65,12 +69,6 @@ ifeq ($(CONFIG_BLK)$(CONFIG_DOS_PARTITION),yy) obj-y += efi_selftest_block_device.o endif -obj-y += \ -efi_selftest_exception.o \ -efi_selftest_loadimage.o \ -efi_selftest_startimage_exit.o \ -efi_selftest_startimage_return.o - targets += \ efi_miniapp_file_image_exception.h \ efi_miniapp_file_image_exit.h \ @@ -94,10 +92,10 @@ $(obj)/efi_miniapp_file_image_return.h: $(obj)/efi_selftest_miniapp_return.efi $(obj)/../../tools/file2include $(obj)/efi_selftest_miniapp_return.efi > \ $(obj)/efi_miniapp_file_image_return.h -$(obj)/efi_selftest_loadimage.o: $(obj)/efi_miniapp_file_image_exit.h - $(obj)/efi_selftest_exception.o: $(obj)/efi_miniapp_file_image_exception.h +$(obj)/efi_selftest_loadimage.o: $(obj)/efi_miniapp_file_image_exit.h + $(obj)/efi_selftest_startimage_exit.o: $(obj)/efi_miniapp_file_image_exit.h $(obj)/efi_selftest_startimage_return.o: $(obj)/efi_miniapp_file_image_return.h -- cgit v1.2.3 From 7f8e656338c442c300034bd5119a2b798a1b098c Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 8 Dec 2020 15:55:01 +0100 Subject: efi_selftest: test EFI_LOAD_FILE_PROTOCOL A unit test is supplied to test the support for the EFI_LOAD_FILE_PROTOCOL and the EFI_LOAD_FILE2_PROTOCOL by the LoadImage() boot service. Signed-off-by: Heinrich Schuchardt --- lib/efi_selftest/Makefile | 3 + lib/efi_selftest/efi_selftest_load_file.c | 475 ++++++++++++++++++++++++++++++ 2 files changed, 478 insertions(+) create mode 100644 lib/efi_selftest/efi_selftest_load_file.c diff --git a/lib/efi_selftest/Makefile b/lib/efi_selftest/Makefile index 706df0b27b..426552bfa0 100644 --- a/lib/efi_selftest/Makefile +++ b/lib/efi_selftest/Makefile @@ -28,6 +28,7 @@ efi_selftest_event_groups.o \ efi_selftest_exception.o \ efi_selftest_exitbootservices.o \ efi_selftest_gop.o \ +efi_selftest_load_file.o \ efi_selftest_loaded_image.o \ efi_selftest_loadimage.o \ efi_selftest_manageprotocols.o \ @@ -94,6 +95,8 @@ $(obj)/efi_miniapp_file_image_return.h: $(obj)/efi_selftest_miniapp_return.efi $(obj)/efi_selftest_exception.o: $(obj)/efi_miniapp_file_image_exception.h +$(obj)/efi_selftest_load_file.o: $(obj)/efi_miniapp_file_image_exit.h + $(obj)/efi_selftest_loadimage.o: $(obj)/efi_miniapp_file_image_exit.h $(obj)/efi_selftest_startimage_exit.o: $(obj)/efi_miniapp_file_image_exit.h diff --git a/lib/efi_selftest/efi_selftest_load_file.c b/lib/efi_selftest/efi_selftest_load_file.c new file mode 100644 index 0000000000..4473e7c36e --- /dev/null +++ b/lib/efi_selftest/efi_selftest_load_file.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * efi_selftest_load_file + * + * Copyright (c) 2020 Heinrich Schuchardt + * + * This test checks the handling of the LOAD_FILE and the LOAD_FILE2 protocol + * by the LoadImage() service. + */ + +#include +/* Include containing the miniapp.efi application */ +#include "efi_miniapp_file_image_exit.h" + +/* Block size of compressed disk image */ +#define COMPRESSED_DISK_IMAGE_BLOCK_SIZE 8 + +/* Binary logarithm of the block size */ +#define LB_BLOCK_SIZE 9 + +#define GUID_VENDOR \ + EFI_GUID(0xdbca4c98, 0x6cb0, 0x694d, \ + 0x08, 0x72, 0x81, 0x9c, 0x65, 0xfc, 0xbb, 0xd1) + +#define GUID_VENDOR2 \ + EFI_GUID(0xdbca4c98, 0x6cb0, 0x694d, \ + 0x08, 0x72, 0x81, 0x9c, 0x65, 0xfc, 0xbb, 0xd2) + +#define FILE_NAME_SIZE 16 + +static const efi_guid_t efi_st_guid_load_file_protocol = + EFI_LOAD_FILE_PROTOCOL_GUID; +static const efi_guid_t efi_st_guid_load_file2_protocol = + EFI_LOAD_FILE2_PROTOCOL_GUID; +static const efi_guid_t efi_st_guid_device_path = + EFI_DEVICE_PATH_PROTOCOL_GUID; + +static efi_handle_t image_handle; +static struct efi_boot_services *boottime; +static efi_handle_t handle_lf; +static efi_handle_t handle_lf2; + +/* One 8 byte block of the compressed disk image */ +struct line { + size_t addr; + char *line; +}; + +/* Compressed file image */ +struct compressed_file_image { + size_t length; + struct line lines[]; +}; + +static struct compressed_file_image img = EFI_ST_DISK_IMG; + +static int load_file_call_count; +static int load_file2_call_count; + +/* Decompressed file image */ +static u8 *image; + +static struct { + struct efi_device_path_vendor v; + struct efi_device_path d; +} dp_lf_prot = { + { + { + DEVICE_PATH_TYPE_HARDWARE_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR, + sizeof(struct efi_device_path_vendor), + }, + GUID_VENDOR, + }, + { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(struct efi_device_path), + }, +}; + +static struct { + struct efi_device_path_vendor v; + struct efi_device_path_file_path f; + u16 file_name[FILE_NAME_SIZE]; + struct efi_device_path e; +} dp_lf_file = { + { + { + DEVICE_PATH_TYPE_HARDWARE_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR, + sizeof(struct efi_device_path_vendor), + }, + GUID_VENDOR, + }, + { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_FILE_PATH, + sizeof(struct efi_device_path_file_path) + + FILE_NAME_SIZE * sizeof(u16), + } + }, + L"\\lf.efi", + { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(struct efi_device_path), + }, +}; + +struct efi_device_path *dp_lf_file_remainder = &dp_lf_file.f.dp; + +static struct { + struct efi_device_path_vendor v; + struct efi_device_path d; +} dp_lf2_prot = { + { + { + DEVICE_PATH_TYPE_HARDWARE_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR, + sizeof(struct efi_device_path_vendor), + }, + GUID_VENDOR2, + }, + { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(struct efi_device_path), + }, +}; + +static struct { + struct efi_device_path_vendor v; + struct efi_device_path_file_path f; + u16 file_name[FILE_NAME_SIZE]; + struct efi_device_path e; +} dp_lf2_file = { + { + { + DEVICE_PATH_TYPE_HARDWARE_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR, + sizeof(struct efi_device_path_vendor), + }, + GUID_VENDOR2, + }, + { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_FILE_PATH, + sizeof(struct efi_device_path_file_path) + + FILE_NAME_SIZE * sizeof(u16), + } + }, + L"\\lf2.efi", + { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(struct efi_device_path), + }, +}; + +struct efi_device_path *dp_lf2_file_remainder = &dp_lf2_file.f.dp; + +/* + * Decompress the disk image. + * + * @image decompressed disk image + * @return status code + */ +static efi_status_t decompress(u8 **image) +{ + u8 *buf; + size_t i; + size_t addr; + size_t len; + efi_status_t ret; + + ret = boottime->allocate_pool(EFI_LOADER_DATA, img.length, + (void **)&buf); + if (ret != EFI_SUCCESS) { + efi_st_error("Out of memory\n"); + return ret; + } + boottime->set_mem(buf, img.length, 0); + + for (i = 0; ; ++i) { + if (!img.lines[i].line) + break; + addr = img.lines[i].addr; + len = COMPRESSED_DISK_IMAGE_BLOCK_SIZE; + if (addr + len > img.length) + len = img.length - addr; + boottime->copy_mem(buf + addr, img.lines[i].line, len); + } + *image = buf; + return ret; +} + +/* + * load_file() - LoadFile() service of a EFI_LOAD_FILE_PROTOCOL + * + * @this: instance of EFI_LOAD_FILE_PROTOCOL + * @file_path: remaining device path + * @boot_policy: true if called by boot manager + * @buffer_size: (required) buffer size + * @buffer: buffer to which the file is to be loaded + */ +efi_status_t EFIAPI load_file(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, + bool boot_policy, + efi_uintn_t *buffer_size, + void *buffer) +{ + ++load_file_call_count; + if (memcmp(file_path, dp_lf_file_remainder, + sizeof(struct efi_device_path_file_path) + + FILE_NAME_SIZE * sizeof(u16) + + sizeof(struct efi_device_path))) { + efi_st_error("Wrong remaining device path\n"); + return EFI_NOT_FOUND; + } + if (this->load_file != load_file) { + efi_st_error("wrong this\n"); + return EFI_INVALID_PARAMETER; + } + if (*buffer_size < img.length) { + *buffer_size = img.length; + return EFI_BUFFER_TOO_SMALL; + } + memcpy(buffer, image, img.length); + *buffer_size = img.length; + return EFI_SUCCESS; +} + + +/* + * load_file2() - LoadFile() service of a EFI_LOAD_FILE2_PROTOCOL + * + * @this: instance of EFI_LOAD_FILE2_PROTOCOL + * @file_path: remaining device path + * @boot_policy: true if called by boot manager + * @buffer_size: (required) buffer size + * @buffer: buffer to which the file is to be loaded + */ +efi_status_t EFIAPI load_file2(struct efi_load_file_protocol *this, + struct efi_device_path *file_path, + bool boot_policy, + efi_uintn_t *buffer_size, + void *buffer) +{ + ++load_file2_call_count; + if (memcmp(file_path, dp_lf2_file_remainder, + sizeof(struct efi_device_path_file_path) + + FILE_NAME_SIZE * sizeof(u16) + + sizeof(struct efi_device_path))) { + efi_st_error("Wrong remaining device path\n"); + return EFI_NOT_FOUND; + } + if (this->load_file != load_file2) { + efi_st_error("wrong this\n"); + return EFI_INVALID_PARAMETER; + } + if (boot_policy) { + efi_st_error("LOAD_FILE2 called with boot_policy = true"); + return EFI_INVALID_PARAMETER; + } + if (*buffer_size < img.length) { + *buffer_size = img.length; + return EFI_BUFFER_TOO_SMALL; + } + memcpy(buffer, image, img.length); + *buffer_size = img.length; + return EFI_SUCCESS; +} + +static struct efi_load_file_protocol lf_prot = {load_file}; +static struct efi_load_file_protocol lf2_prot = {load_file2}; + +/* + * Setup unit test. + * + * Install an EFI_LOAD_FILE_PROTOCOL and an EFI_LOAD_FILE2_PROTOCOL. + * + * @handle: handle of the loaded image + * @systable: system table + * @return: EFI_ST_SUCCESS for success + */ +static int efi_st_load_file_setup(const efi_handle_t handle, + const struct efi_system_table *systable) +{ + efi_status_t ret; + + image_handle = handle; + boottime = systable->boottime; + + /* Load the application image into memory */ + decompress(&image); + + ret = boottime->install_multiple_protocol_interfaces( + &handle_lf, + &efi_st_guid_device_path, + &dp_lf_prot, + &efi_st_guid_load_file_protocol, + &lf_prot, + NULL); + if (ret != EFI_SUCCESS) { + efi_st_error("InstallMultipleProtocolInterfaces failed\n"); + return EFI_ST_FAILURE; + } + ret = boottime->install_multiple_protocol_interfaces( + &handle_lf2, + &efi_st_guid_device_path, + &dp_lf2_prot, + &efi_st_guid_load_file2_protocol, + &lf2_prot, + NULL); + if (ret != EFI_SUCCESS) { + efi_st_error("InstallMultipleProtocolInterfaces failed\n"); + return EFI_ST_FAILURE; + } + + return EFI_ST_SUCCESS; +} + +/* + * Tear down unit test. + * + * @return: EFI_ST_SUCCESS for success + */ +static int efi_st_load_file_teardown(void) +{ + efi_status_t ret = EFI_ST_SUCCESS; + + if (handle_lf) { + ret = boottime->uninstall_multiple_protocol_interfaces( + handle_lf, + &efi_st_guid_device_path, + &dp_lf_prot, + &efi_st_guid_load_file_protocol, + &lf_prot, + NULL); + if (ret != EFI_SUCCESS) { + efi_st_error( + "UninstallMultipleProtocolInterfaces failed\n"); + return EFI_ST_FAILURE; + } + } + if (handle_lf2) { + ret = boottime->uninstall_multiple_protocol_interfaces( + handle_lf2, + &efi_st_guid_device_path, + &dp_lf2_prot, + &efi_st_guid_load_file2_protocol, + &lf2_prot, + NULL); + if (ret != EFI_SUCCESS) { + efi_st_error( + "UninstallMultipleProtocolInterfaces failed\n"); + return EFI_ST_FAILURE; + } + } + + if (image) { + ret = boottime->free_pool(image); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to free image\n"); + return EFI_ST_FAILURE; + } + } + return ret; +} + +/* + * Execute unit test. + * + * Try loading an image via the EFI_LOAD_FILE_PROTOCOL and the + * EFI_LOAD_FILE2_PROTOCOL. Finally execute the image. + * + * @return: EFI_ST_SUCCESS for success + */ +static int efi_st_load_file_execute(void) +{ + efi_status_t ret; + efi_handle_t handle; + efi_uintn_t exit_data_size = 0; + u16 *exit_data = NULL; + u16 expected_text[] = EFI_ST_SUCCESS_STR; + + load_file_call_count = 0; + load_file2_call_count = 0; + handle = NULL; + ret = boottime->load_image(true, image_handle, &dp_lf_file.v.dp, NULL, + 0, &handle); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to load image\n"); + return EFI_ST_FAILURE; + } + if (load_file2_call_count || !load_file_call_count) { + efi_st_error("Wrong image loaded\n"); + return EFI_ST_FAILURE; + } + ret = boottime->unload_image(handle); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to unload image\n"); + return EFI_ST_FAILURE; + } + + load_file_call_count = 0; + load_file2_call_count = 0; + handle = NULL; + ret = boottime->load_image(false, image_handle, &dp_lf_file.v.dp, NULL, + 0, &handle); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to load image\n"); + return EFI_ST_FAILURE; + } + if (load_file2_call_count || !load_file_call_count) { + efi_st_error("Wrong image loaded\n"); + return EFI_ST_FAILURE; + } + ret = boottime->unload_image(handle); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to unload image\n"); + return EFI_ST_FAILURE; + } + + ret = boottime->load_image(true, image_handle, &dp_lf2_file.v.dp, NULL, + 0, &handle); + if (ret != EFI_NOT_FOUND) { + efi_st_error( + "Boot manager should not use LOAD_FILE2_PROTOCOL\n"); + return EFI_ST_FAILURE; + } + + load_file_call_count = 0; + load_file2_call_count = 0; + handle = NULL; + ret = boottime->load_image(false, image_handle, &dp_lf2_file.v.dp, NULL, + 0, &handle); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to load image\n"); + return EFI_ST_FAILURE; + } + if (!load_file2_call_count || load_file_call_count) { + efi_st_error("Wrong image loaded\n"); + return EFI_ST_FAILURE; + } + + ret = boottime->start_image(handle, &exit_data_size, &exit_data); + if (ret != EFI_UNSUPPORTED) { + efi_st_error("Wrong return value from application\n"); + return EFI_ST_FAILURE; + } + if (!exit_data || exit_data_size != sizeof(expected_text) || + memcmp(exit_data, expected_text, sizeof(expected_text))) { + efi_st_error("Incorrect exit data\n"); + return EFI_ST_FAILURE; + } + ret = boottime->free_pool(exit_data); + if (ret != EFI_SUCCESS) { + efi_st_error("Failed to free exit data\n"); + return EFI_ST_FAILURE; + } + + return EFI_ST_SUCCESS; +} + +EFI_UNIT_TEST(load_file_protocol) = { + .name = "load file protocol", + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, + .setup = efi_st_load_file_setup, + .execute = efi_st_load_file_execute, + .teardown = efi_st_load_file_teardown, +}; -- cgit v1.2.3 From f9ad240e65ac2f45d7b0acd770b5d79e50c3d92f Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 10 Jan 2020 12:33:59 +0100 Subject: efi_loader: make efi_protocol_open() non-static Provide efi_protocol_open() as library function. Signed-off-by: Heinrich Schuchardt --- include/efi_loader.h | 5 +++++ lib/efi_loader/efi_boottime.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/efi_loader.h b/include/efi_loader.h index 4c6eb8616d..365f3d01dc 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -500,6 +500,11 @@ efi_status_t efi_search_protocol(const efi_handle_t handle, efi_status_t efi_add_protocol(const efi_handle_t handle, const efi_guid_t *protocol, void *protocol_interface); +/* Open protocol */ +efi_status_t efi_protocol_open(struct efi_handler *handler, + void **protocol_interface, void *agent_handle, + void *controller_handle, uint32_t attributes); + /* Delete protocol from a handle */ efi_status_t efi_remove_protocol(const efi_handle_t handle, const efi_guid_t *protocol, diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 9bd63b6e52..03053e8660 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -2779,7 +2779,7 @@ static void EFIAPI efi_set_mem(void *buffer, size_t size, uint8_t value) * * Return: status code */ -static efi_status_t efi_protocol_open( +efi_status_t efi_protocol_open( struct efi_handler *handler, void **protocol_interface, void *agent_handle, void *controller_handle, uint32_t attributes) -- cgit v1.2.3 From 264485131c59c1c8fa17fe742bbca65cef868d94 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 10 Jan 2020 12:36:01 +0100 Subject: efi_loader: link partition to block device We provide a UEFI driver for block devices. When ConnectController() is called for a handle with the EFI_BLOCK_IO_PROTOCOL this driver creates the partitions. When DisconnectController() is called the handles for the partitions have to be deleted. This requires that the child controllers (partitions) open the EFI_BLOCK_IO_PROTOCOL of the controller (block IO device) with attribute EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_disk.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index 7bd1ccec45..496ef29dd8 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -376,6 +376,23 @@ static efi_status_t efi_disk_add_dev( /* Fill in object data */ if (part) { struct efi_device_path *node = efi_dp_part_node(desc, part); + struct efi_handler *handler; + void *protocol_interface; + + /* Parent must expose EFI_BLOCK_IO_PROTOCOL */ + ret = efi_search_protocol(parent, &efi_block_io_guid, &handler); + if (ret != EFI_SUCCESS) + goto error; + + /* + * Link the partition (child controller) to the block device + * (controller). + */ + ret = efi_protocol_open(handler, &protocol_interface, NULL, + &diskobj->header, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER); + if (ret != EFI_SUCCESS) + goto error; diskobj->dp = efi_dp_append_node(dp_parent, node); efi_free_pool(node); @@ -453,6 +470,9 @@ static efi_status_t efi_disk_add_dev( } } return EFI_SUCCESS; +error: + efi_delete_handle(&diskobj->header); + return ret; } /** -- cgit v1.2.3