[PATCH 0/2] fs: fat: Implement FAT file rename

Hello This patch series is a first implementation for file renaming on a FAT filesystems. This implementation does a file rename only (no moving between directories) as needed by bootloaders like systemd-boot [1].
The renaming process is tried to be made as fail-safe as possible, by 1. Writing the new, empty file 2. Copying the address of the start cluster into new directory entry 3. Deleting old file This can decay in the worst case to a hard link
Additionally, make use of the FAT file rename in /lib/efi_loader
[1] https://www.freedesktop.org/software/systemd/man/latest/systemd-boot.html#Bo...
Burak Gerz (2): efi_loader: enable support for file renaming fs: fat: Implement FAT file rename
fs/fat/fat_write.c | 99 +++++++++++++++++++++++++++++++++++++++ fs/fs.c | 30 ++++++++++++ include/fat.h | 1 + include/fs.h | 2 + lib/efi_loader/efi_file.c | 9 ++-- 5 files changed, 137 insertions(+), 4 deletions(-)

EFI applications can issue a file rename to U-boot - add support for this
Signed-off-by: Burak Gerz burak@gerz.io ---
lib/efi_loader/efi_file.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c index 95b3c890ee..b4d1a9dad5 100644 --- a/lib/efi_loader/efi_file.c +++ b/lib/efi_loader/efi_file.c @@ -975,14 +975,15 @@ static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, } pos = new_file_name; utf16_utf8_strcpy(&pos, info->file_name); + if (strcmp(new_file_name, filename)) { - /* TODO: we do not support renaming */ - EFI_PRINT("Renaming not supported\n"); + if (set_blk_dev(fh) == 0 && fs_rename(fh->path, new_file_name) == 0) + ret = EFI_SUCCESS; + else + ret = EFI_DEVICE_ERROR; free(new_file_name); - ret = EFI_ACCESS_DENIED; goto out; } - free(new_file_name); /* Check for truncation */ if (!fh->isdir) { ret = efi_get_file_size(fh, &file_size);

On 12.12.24 23:06, Burak Gerz wrote:
EFI applications can issue a file rename to U-boot - add support for this
Thank you for the patch series. This is a valuable addition.
We want to be able to bisect our code git repository. This requires that the code builds after each individual patch.
So this patch must be last.
Signed-off-by: Burak Gerz burak@gerz.io
lib/efi_loader/efi_file.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c index 95b3c890ee..b4d1a9dad5 100644 --- a/lib/efi_loader/efi_file.c +++ b/lib/efi_loader/efi_file.c @@ -975,14 +975,15 @@ static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, } pos = new_file_name; utf16_utf8_strcpy(&pos, info->file_name);
- if (strcmp(new_file_name, filename)) {
/* TODO: we do not support renaming */
EFI_PRINT("Renaming not supported\n");
if (set_blk_dev(fh) == 0 && fs_rename(fh->path, new_file_name) == 0)
We tend to avoid "== 0" in the U-Boot code.
if (!set_blk_dev(fh) && !fs_rename(fh->path, new_file_name))
ret = EFI_SUCCESS;
else
ret = EFI_DEVICE_ERROR;
What is wrong about EFI_ACCESS_DENIED?
Best regards
Heinrich
free(new_file_name);
}ret = EFI_ACCESS_DENIED; goto out;
/* Check for truncation */ if (!fh->isdir) { ret = efi_get_file_size(fh, &file_size);free(new_file_name);

Implement a simple FAT file rename.
Signed-off-by: Burak Gerz burak@gerz.io
---
fs/fat/fat_write.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fs.c | 30 ++++++++++++++ include/fat.h | 1 + include/fs.h | 2 + 4 files changed, 132 insertions(+)
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index ea877ee917..acd0e22652 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1813,3 +1813,102 @@ exit: free(dotdent); return ret; } + +int file_fat_rename(const char *path, const char *new_filename) +{ + char *dirname, *basename; + char path_copy[VFAT_MAXLEN_BYTES]; + char new_path[VFAT_MAXLEN_BYTES]; + dir_entry *dentry_src, *dentry_dst; + fsdata src_datablock = { .fatbuf = NULL }; + fsdata dst_datablock = { .fatbuf = NULL }; + fat_itr *src_itr = NULL; + fat_itr *dst_itr = NULL; + loff_t actwrite; + int ret; + void *buffer = &actwrite; + + if (strlen(new_filename) > VFAT_MAXLEN_BYTES) + return -EINVAL; + strcpy(path_copy, path); + split_filename(path_copy, &dirname, &basename); + strcpy(new_path, dirname); + strcat(new_path, "/"); + strcat(new_path, new_filename); + src_itr = malloc_cache_aligned(sizeof(*src_itr)); + if (!src_itr) + goto exit; + dst_itr = malloc_cache_aligned(sizeof(*dst_itr)); + if (!dst_itr) + goto exit; + ret = fat_itr_root(src_itr, &src_datablock); + if (ret) + goto exit; + ret = fat_itr_root(dst_itr, &dst_datablock); + if (ret) + goto exit; + ret = fat_itr_resolve(src_itr, dirname, TYPE_DIR); + if (ret) + goto exit; + dentry_src = find_directory_entry(src_itr, (char *)basename); + if (!dentry_src) + goto exit; + + dst_datablock.fatbuf = NULL; + fat_itr_root(dst_itr, &dst_datablock); + ret = fat_itr_resolve(dst_itr, dirname, TYPE_DIR); + if (ret) + goto exit; + dentry_dst = find_directory_entry(dst_itr, (char *)new_filename); + if (dentry_dst) + goto exit; + ret = file_fat_write(new_path, buffer, 0, 0, &actwrite); + if (ret) + goto exit; + dst_datablock.fatbuf = NULL; + fat_itr_root(dst_itr, &dst_datablock); + ret = fat_itr_resolve(dst_itr, dirname, TYPE_DIR); + if (ret) + goto exit; + dentry_dst = find_directory_entry(dst_itr, (char *)new_filename); + if (!dentry_dst) + goto exit; + dentry_dst->starthi = dentry_src->starthi; + dentry_dst->start = dentry_src->start; + dentry_dst->size = dentry_src->size; + ret = flush_dir(dst_itr); + if (ret) + return -1; + src_datablock.fatbuf = NULL; + ret = fat_itr_root(src_itr, &src_datablock); + if (ret) + goto exit; + ret = fat_itr_resolve(src_itr, dirname, TYPE_DIR); + if (ret) + goto exit; + dentry_src = find_directory_entry(src_itr, (char *)basename); + if (!dentry_src) + goto exit; + dir_entry *dent = src_itr->dent; + + if (src_itr->clust != src_itr->dent_clust) { + ret = fat_move_to_cluster(src_itr, src_itr->dent_clust); + if (ret) + goto exit; + } + src_itr->dent = src_itr->dent_start; + src_itr->remaining = src_itr->dent_rem; + dent = src_itr->dent_start; + if ((dent->attr & ATTR_VFAT) == ATTR_VFAT && + (dent->nameext.name[0] & LAST_LONG_ENTRY_MASK)) { + ret = delete_long_name(src_itr); + if (ret) + goto exit; + } + delete_single_dentry(src_itr); + ret = flush_dir(src_itr); +exit: + free(src_itr); + free(dst_itr); + return ret; +} diff --git a/fs/fs.c b/fs/fs.c index 21a23efd93..3024bd2cec 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -118,6 +118,11 @@ static inline int fs_ln_unsupported(const char *filename, const char *target) return -1; }
+static inline int fs_rename_unsupported(const char *filename, const char *target) +{ + return -1; +} + static inline void fs_close_unsupported(void) { } @@ -183,6 +188,7 @@ struct fstype_info { int (*unlink)(const char *filename); int (*mkdir)(const char *dirname); int (*ln)(const char *filename, const char *target); + int (*rename)(const char *old_filename_path, const char *new_filename); };
static struct fstype_info fstypes[] = { @@ -201,6 +207,7 @@ static struct fstype_info fstypes[] = { .write = file_fat_write, .unlink = fat_unlink, .mkdir = fat_mkdir, + .rename = file_fat_rename, #else .write = fs_write_unsupported, .unlink = fs_unlink_unsupported, @@ -222,6 +229,7 @@ static struct fstype_info fstypes[] = { .probe = ext4fs_probe, .close = ext4fs_close, .ls = fs_ls_generic, + .rename = fs_rename_unsupported, .exists = ext4fs_exists, .size = ext4fs_size, .read = ext4_read_file, @@ -247,6 +255,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = true, .probe = sandbox_fs_set_blk_dev, .close = sandbox_fs_close, + .rename = fs_rename_unsupported, .ls = sandbox_fs_ls, .exists = sandbox_fs_exists, .size = sandbox_fs_size, @@ -266,6 +275,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = true, .probe = smh_fs_set_blk_dev, .close = fs_close_unsupported, + .rename = fs_rename_unsupported, .ls = fs_ls_unsupported, .exists = fs_exists_unsupported, .size = smh_fs_size, @@ -284,6 +294,7 @@ static struct fstype_info fstypes[] = { .fstype = FS_TYPE_UBIFS, .name = "ubifs", .null_dev_desc_ok = true, + .rename = fs_rename_unsupported, .probe = ubifs_set_blk_dev, .close = ubifs_close, .ls = ubifs_ls, @@ -305,6 +316,7 @@ static struct fstype_info fstypes[] = { .fstype = FS_TYPE_BTRFS, .name = "btrfs", .null_dev_desc_ok = false, + .rename = fs_rename_unsupported, .probe = btrfs_probe, .close = btrfs_close, .ls = btrfs_ls, @@ -327,6 +339,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = false, .probe = sqfs_probe, .opendir = sqfs_opendir, + .rename = fs_rename_unsupported, .readdir = sqfs_readdir, .ls = fs_ls_generic, .read = sqfs_read, @@ -348,6 +361,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = false, .probe = erofs_probe, .opendir = erofs_opendir, + .rename = fs_rename_unsupported, .readdir = erofs_readdir, .ls = fs_ls_generic, .read = erofs_read, @@ -369,6 +383,7 @@ static struct fstype_info fstypes[] = { .probe = fs_probe_unsupported, .close = fs_close_unsupported, .ls = fs_ls_unsupported, + .rename = fs_rename_unsupported, .exists = fs_exists_unsupported, .size = fs_size_unsupported, .read = fs_read_unsupported, @@ -697,6 +712,21 @@ int fs_mkdir(const char *dirname) return ret; }
+int fs_rename(const char *path, const char *new_filename) +{ + struct fstype_info *info = fs_get_info(fs_type); + int ret; + + ret = info->rename(path, new_filename); + if (ret < 0) { + log_err("** Unable to rename from %s to %s **\n", path, new_filename); + ret = -1; + } + fs_close(); + + return ret; +} + int fs_ln(const char *fname, const char *target) { struct fstype_info *info = fs_get_info(fs_type); diff --git a/include/fat.h b/include/fat.h index 3dce99a23c..1155e59c68 100644 --- a/include/fat.h +++ b/include/fat.h @@ -208,6 +208,7 @@ void fat_closedir(struct fs_dir_stream *dirs); int fat_unlink(const char *filename); int fat_mkdir(const char *dirname); void fat_close(void); +int file_fat_rename(const char *filename, const char *buffer); void *fat_next_cluster(fat_itr *itr, unsigned int *nbytes);
/** diff --git a/include/fs.h b/include/fs.h index 2474880385..c8d0eb41c9 100644 --- a/include/fs.h +++ b/include/fs.h @@ -165,6 +165,8 @@ int fs_read(const char *filename, ulong addr, loff_t offset, loff_t len, int fs_write(const char *filename, ulong addr, loff_t offset, loff_t len, loff_t *actwrite);
+int fs_rename(const char *path, const char *filename); + /* * Directory entry types, matches the subset of DT_x in posix readdir() * which apply to u-boot.

On 12.12.24 23:06, Burak Gerz wrote:
Implement a simple FAT file rename.
Signed-off-by: Burak Gerz burak@gerz.io
Please, separate the change into two patches.
1) define fs_rename() 2) implement ffat_rename()
fs/fat/fat_write.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fs.c | 30 ++++++++++++++ include/fat.h | 1 + include/fs.h | 2 + 4 files changed, 132 insertions(+)
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index ea877ee917..acd0e22652 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -1813,3 +1813,102 @@ exit: free(dotdent); return ret; }
+int file_fat_rename(const char *path, const char *new_filename)
%s/file_fat_rename/fat_rename/
+{
- char *dirname, *basename;
- char path_copy[VFAT_MAXLEN_BYTES];
- char new_path[VFAT_MAXLEN_BYTES];
- dir_entry *dentry_src, *dentry_dst;
- fsdata src_datablock = { .fatbuf = NULL };
- fsdata dst_datablock = { .fatbuf = NULL };
- fat_itr *src_itr = NULL;
- fat_itr *dst_itr = NULL;
- loff_t actwrite;
- int ret;
- void *buffer = &actwrite;
- if (strlen(new_filename) > VFAT_MAXLEN_BYTES)
return -EINVAL;
- strcpy(path_copy, path);
- split_filename(path_copy, &dirname, &basename);
- strcpy(new_path, dirname);
- strcat(new_path, "/");
- strcat(new_path, new_filename);
- src_itr = malloc_cache_aligned(sizeof(*src_itr));
if (!src_itr)
goto exit;
- dst_itr = malloc_cache_aligned(sizeof(*dst_itr));
if (!dst_itr)
goto exit;
- ret = fat_itr_root(src_itr, &src_datablock);
- if (ret)
goto exit;
- ret = fat_itr_root(dst_itr, &dst_datablock);
- if (ret)
goto exit;
- ret = fat_itr_resolve(src_itr, dirname, TYPE_DIR);
- if (ret)
goto exit;
- dentry_src = find_directory_entry(src_itr, (char *)basename);
- if (!dentry_src)
goto exit;
- dst_datablock.fatbuf = NULL;
- fat_itr_root(dst_itr, &dst_datablock);
- ret = fat_itr_resolve(dst_itr, dirname, TYPE_DIR);
- if (ret)
goto exit;
- dentry_dst = find_directory_entry(dst_itr, (char *)new_filename);
- if (dentry_dst)
goto exit;
This duplicates code from fat_mkdir() and file_fat_write_at(). Please, carve out a common functions.
- ret = file_fat_write(new_path, buffer, 0, 0, &actwrite);
Before any update you must check that new_path does not exist.
- if (ret)
goto exit;
- dst_datablock.fatbuf = NULL;
- fat_itr_root(dst_itr, &dst_datablock);
- ret = fat_itr_resolve(dst_itr, dirname, TYPE_DIR);
- if (ret)
goto exit;
- dentry_dst = find_directory_entry(dst_itr, (char *)new_filename);
- if (!dentry_dst)
goto exit;
- dentry_dst->starthi = dentry_src->starthi;
- dentry_dst->start = dentry_src->start;
- dentry_dst->size = dentry_src->size;
- ret = flush_dir(dst_itr);
- if (ret)
return -1;
- src_datablock.fatbuf = NULL;
- ret = fat_itr_root(src_itr, &src_datablock);
- if (ret)
goto exit;
- ret = fat_itr_resolve(src_itr, dirname, TYPE_DIR);
- if (ret)
goto exit;
- dentry_src = find_directory_entry(src_itr, (char *)basename);
- if (!dentry_src)
goto exit;
- dir_entry *dent = src_itr->dent;
- if (src_itr->clust != src_itr->dent_clust) {
ret = fat_move_to_cluster(src_itr, src_itr->dent_clust);
if (ret)
goto exit;
- }
- src_itr->dent = src_itr->dent_start;
- src_itr->remaining = src_itr->dent_rem;
- dent = src_itr->dent_start;
- if ((dent->attr & ATTR_VFAT) == ATTR_VFAT &&
(dent->nameext.name[0] & LAST_LONG_ENTRY_MASK)) {
ret = delete_long_name(src_itr);
if (ret)
goto exit;
- }
- delete_single_dentry(src_itr);
You are duplicating code from delete_dentry_long(). This can be avoided by moving the freeing of cluster blocks out of delete_dentry_long() and calling this function here.
- ret = flush_dir(src_itr);
+exit:
- free(src_itr);
- free(dst_itr);
- return ret;
+} diff --git a/fs/fs.c b/fs/fs.c index 21a23efd93..3024bd2cec 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -118,6 +118,11 @@ static inline int fs_ln_unsupported(const char *filename, const char *target) return -1; }
+static inline int fs_rename_unsupported(const char *filename, const char *target) +{
- return -1;
+}
- static inline void fs_close_unsupported(void) { }
@@ -183,6 +188,7 @@ struct fstype_info { int (*unlink)(const char *filename); int (*mkdir)(const char *dirname); int (*ln)(const char *filename, const char *target);
int (*rename)(const char *old_filename_path, const char *new_filename); };
static struct fstype_info fstypes[] = {
@@ -201,6 +207,7 @@ static struct fstype_info fstypes[] = { .write = file_fat_write, .unlink = fat_unlink, .mkdir = fat_mkdir,
#else .write = fs_write_unsupported, .unlink = fs_unlink_unsupported,.rename = file_fat_rename,
@@ -222,6 +229,7 @@ static struct fstype_info fstypes[] = { .probe = ext4fs_probe, .close = ext4fs_close, .ls = fs_ls_generic,
.exists = ext4fs_exists, .size = ext4fs_size, .read = ext4_read_file,.rename = fs_rename_unsupported,
@@ -247,6 +255,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = true, .probe = sandbox_fs_set_blk_dev, .close = sandbox_fs_close,
.ls = sandbox_fs_ls, .exists = sandbox_fs_exists, .size = sandbox_fs_size,.rename = fs_rename_unsupported,
@@ -266,6 +275,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = true, .probe = smh_fs_set_blk_dev, .close = fs_close_unsupported,
.ls = fs_ls_unsupported, .exists = fs_exists_unsupported, .size = smh_fs_size,.rename = fs_rename_unsupported,
@@ -284,6 +294,7 @@ static struct fstype_info fstypes[] = { .fstype = FS_TYPE_UBIFS, .name = "ubifs", .null_dev_desc_ok = true,
.probe = ubifs_set_blk_dev, .close = ubifs_close, .ls = ubifs_ls,.rename = fs_rename_unsupported,
@@ -305,6 +316,7 @@ static struct fstype_info fstypes[] = { .fstype = FS_TYPE_BTRFS, .name = "btrfs", .null_dev_desc_ok = false,
.probe = btrfs_probe, .close = btrfs_close, .ls = btrfs_ls,.rename = fs_rename_unsupported,
@@ -327,6 +339,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = false, .probe = sqfs_probe, .opendir = sqfs_opendir,
.readdir = sqfs_readdir, .ls = fs_ls_generic, .read = sqfs_read,.rename = fs_rename_unsupported,
@@ -348,6 +361,7 @@ static struct fstype_info fstypes[] = { .null_dev_desc_ok = false, .probe = erofs_probe, .opendir = erofs_opendir,
.readdir = erofs_readdir, .ls = fs_ls_generic, .read = erofs_read,.rename = fs_rename_unsupported,
@@ -369,6 +383,7 @@ static struct fstype_info fstypes[] = { .probe = fs_probe_unsupported, .close = fs_close_unsupported, .ls = fs_ls_unsupported,
.exists = fs_exists_unsupported, .size = fs_size_unsupported, .read = fs_read_unsupported,.rename = fs_rename_unsupported,
@@ -697,6 +712,21 @@ int fs_mkdir(const char *dirname) return ret; }
+int fs_rename(const char *path, const char *new_filename) +{
- struct fstype_info *info = fs_get_info(fs_type);
- int ret;
- ret = info->rename(path, new_filename);
Please, check if the name is equal before calling rename().
Best regards
Heinrich
- if (ret < 0) {
log_err("** Unable to rename from %s to %s **\n", path, new_filename);
ret = -1;
- }
- fs_close();
- return ret;
+}
- int fs_ln(const char *fname, const char *target) { struct fstype_info *info = fs_get_info(fs_type);
diff --git a/include/fat.h b/include/fat.h index 3dce99a23c..1155e59c68 100644 --- a/include/fat.h +++ b/include/fat.h @@ -208,6 +208,7 @@ void fat_closedir(struct fs_dir_stream *dirs); int fat_unlink(const char *filename); int fat_mkdir(const char *dirname); void fat_close(void); +int file_fat_rename(const char *filename, const char *buffer); void *fat_next_cluster(fat_itr *itr, unsigned int *nbytes);
/** diff --git a/include/fs.h b/include/fs.h index 2474880385..c8d0eb41c9 100644 --- a/include/fs.h +++ b/include/fs.h @@ -165,6 +165,8 @@ int fs_read(const char *filename, ulong addr, loff_t offset, loff_t len, int fs_write(const char *filename, ulong addr, loff_t offset, loff_t len, loff_t *actwrite);
+int fs_rename(const char *path, const char *filename);
- /*
- Directory entry types, matches the subset of DT_x in posix readdir()
- which apply to u-boot.

On Thu, Dec 12, 2024 at 11:06:27PM +0100, Burak Gerz wrote:
Hello This patch series is a first implementation for file renaming on a FAT filesystems. This implementation does a file rename only (no moving between directories) as needed by bootloaders like systemd-boot [1].
The renaming process is tried to be made as fail-safe as possible, by
- Writing the new, empty file
- Copying the address of the start cluster into new directory entry
- Deleting old file
This can decay in the worst case to a hard link
Additionally, make use of the FAT file rename in /lib/efi_loader
[1] https://www.freedesktop.org/software/systemd/man/latest/systemd-boot.html#Bo...
This is useful, thanks! Can you please extend the existing tests in test/py/tests/test_fs/ to cover this as well?
participants (3)
-
Burak Gerz
-
Heinrich Schuchardt
-
Tom Rini