* [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
@ 2025-08-17 0:30 Ethan Ferguson
2025-08-17 0:30 ` [PATCH v2 1/1] " Ethan Ferguson
2025-08-17 12:30 ` [PATCH v2 0/1] " Namjae Jeon
0 siblings, 2 replies; 11+ messages in thread
From: Ethan Ferguson @ 2025-08-17 0:30 UTC (permalink / raw)
To: linkinjeon, sj1557.seo
Cc: yuezhang.mo, linux-fsdevel, linux-kernel, Ethan Ferguson
Add support for reading / writing to the exfat volume label from the
FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls.
Implemented in similar ways to other fs drivers, namely btrfs and ext4,
where the ioctls are performed on file inodes.
v2:
Fix endianness conversion as reported by kernel test robot
v1:
Link: https://lore.kernel.org/all/20250815171056.103751-1-ethan.ferguson@zetier.com/
Ethan Ferguson (1):
exfat: Add support for FS_IOC_{GET,SET}FSLABEL
exfat: Fix endian conversion
fs/exfat/exfat_fs.h | 2 +
fs/exfat/exfat_raw.h | 6 +++
fs/exfat/file.c | 56 +++++++++++++++++++++++++
fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 163 insertions(+)
--
2.50.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 0:30 [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL Ethan Ferguson
@ 2025-08-17 0:30 ` Ethan Ferguson
2025-08-19 1:45 ` Namjae Jeon
2025-08-19 5:32 ` [PATCH v2 1/1] " Yuezhang.Mo
2025-08-17 12:30 ` [PATCH v2 0/1] " Namjae Jeon
1 sibling, 2 replies; 11+ messages in thread
From: Ethan Ferguson @ 2025-08-17 0:30 UTC (permalink / raw)
To: linkinjeon, sj1557.seo
Cc: yuezhang.mo, linux-fsdevel, linux-kernel, Ethan Ferguson
Add support for reading / writing to the exfat volume label from the
FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls
Signed-off-by: Ethan Ferguson <ethan.ferguson@zetier.com>
---
fs/exfat/exfat_fs.h | 2 +
fs/exfat/exfat_raw.h | 6 +++
fs/exfat/file.c | 56 +++++++++++++++++++++++++
fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 163 insertions(+)
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index f8ead4d47ef0..a764e6362172 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -267,6 +267,7 @@ struct exfat_sb_info {
struct buffer_head **vol_amap; /* allocation bitmap */
unsigned short *vol_utbl; /* upcase table */
+ unsigned short volume_label[EXFAT_VOLUME_LABEL_LEN]; /* volume name */
unsigned int clu_srch_ptr; /* cluster search pointer */
unsigned int used_clusters; /* number of used clusters */
@@ -431,6 +432,7 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
/* super.c */
int exfat_set_volume_dirty(struct super_block *sb);
int exfat_clear_volume_dirty(struct super_block *sb);
+int exfat_write_volume_label(struct super_block *sb);
/* fatent.c */
#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
index 971a1ccd0e89..af04cef81c0c 100644
--- a/fs/exfat/exfat_raw.h
+++ b/fs/exfat/exfat_raw.h
@@ -80,6 +80,7 @@
#define BOOTSEC_OLDBPB_LEN 53
#define EXFAT_FILE_NAME_LEN 15
+#define EXFAT_VOLUME_LABEL_LEN 11
#define EXFAT_MIN_SECT_SIZE_BITS 9
#define EXFAT_MAX_SECT_SIZE_BITS 12
@@ -159,6 +160,11 @@ struct exfat_dentry {
__le32 start_clu;
__le64 size;
} __packed upcase; /* up-case table directory entry */
+ struct {
+ __u8 char_count;
+ __le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
+ __u8 reserved[8];
+ } __packed volume_label;
struct {
__u8 flags;
__u8 vendor_guid[16];
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 538d2b6ac2ec..c57d266aae3d 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -12,6 +12,7 @@
#include <linux/security.h>
#include <linux/msdos_fs.h>
#include <linux/writeback.h>
+#include "../nls/nls_ucs2_utils.h"
#include "exfat_raw.h"
#include "exfat_fs.h"
@@ -486,6 +487,57 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
return exfat_force_shutdown(sb, flags);
}
+static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
+{
+ int ret;
+ char utf8[FSLABEL_MAX] = {0};
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ size_t len = UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
+
+ mutex_lock(&sbi->s_lock);
+ ret = utf16s_to_utf8s(sbi->volume_label, len,
+ UTF16_HOST_ENDIAN, utf8, FSLABEL_MAX);
+ mutex_unlock(&sbi->s_lock);
+
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user((char __user *)arg, utf8, FSLABEL_MAX))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int exfat_ioctl_set_volume_label(struct super_block *sb, unsigned long arg)
+{
+ int ret = 0;
+ char utf8[FSLABEL_MAX];
+ size_t len;
+ unsigned short utf16[EXFAT_VOLUME_LABEL_LEN] = {0};
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(utf8, (char __user *)arg, FSLABEL_MAX))
+ return -EFAULT;
+
+ len = strnlen(utf8, FSLABEL_MAX);
+ if (len > EXFAT_VOLUME_LABEL_LEN)
+ exfat_info(sb, "Volume label length too long, truncating");
+
+ mutex_lock(&sbi->s_lock);
+ ret = utf8s_to_utf16s(utf8, len, UTF16_HOST_ENDIAN, utf16, EXFAT_VOLUME_LABEL_LEN);
+ mutex_unlock(&sbi->s_lock);
+
+ if (ret < 0)
+ return ret;
+
+ memcpy(sbi->volume_label, utf16, sizeof(sbi->volume_label));
+
+ return exfat_write_volume_label(sb);
+}
+
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
@@ -500,6 +552,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return exfat_ioctl_shutdown(inode->i_sb, arg);
case FITRIM:
return exfat_ioctl_fitrim(inode, arg);
+ case FS_IOC_GETFSLABEL:
+ return exfat_ioctl_get_volume_label(inode->i_sb, arg);
+ case FS_IOC_SETFSLABEL:
+ return exfat_ioctl_set_volume_label(inode->i_sb, arg);
default:
return -ENOTTY;
}
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 8926e63f5bb7..96cd4bb7cb19 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -18,6 +18,7 @@
#include <linux/nls.h>
#include <linux/buffer_head.h>
#include <linux/magic.h>
+#include "../nls/nls_ucs2_utils.h"
#include "exfat_raw.h"
#include "exfat_fs.h"
@@ -573,6 +574,98 @@ static int exfat_verify_boot_region(struct super_block *sb)
return 0;
}
+static int exfat_get_volume_label_ptrs(struct super_block *sb,
+ struct buffer_head **out_bh,
+ struct exfat_dentry **out_dentry)
+{
+ int i;
+ unsigned int type;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct exfat_chain clu;
+ struct exfat_dentry *ep;
+ struct buffer_head *bh;
+
+ clu.dir = sbi->root_dir;
+ clu.flags = ALLOC_FAT_CHAIN;
+
+ while (clu.dir != EXFAT_EOF_CLUSTER) {
+ for (i = 0; i < sbi->dentries_per_clu; i++) {
+ ep = exfat_get_dentry(sb, &clu, i, &bh);
+
+ if (!ep)
+ return -EIO;
+
+ type = exfat_get_entry_type(ep);
+ if (type == TYPE_UNUSED) {
+ brelse(bh);
+ return -EIO;
+ }
+
+ if (type == TYPE_VOLUME) {
+ *out_bh = bh;
+ *out_dentry = ep;
+ return 0;
+ }
+
+ brelse(bh);
+ }
+
+ if (exfat_get_next_cluster(sb, &(clu.dir)))
+ return -EIO;
+ }
+
+ return -EIO;
+}
+
+static int exfat_read_volume_label(struct super_block *sb)
+{
+ int ret, i;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct buffer_head *bh;
+ struct exfat_dentry *ep;
+
+ ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
+ if (ret < 0)
+ goto cleanup;
+
+ for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
+ sbi->volume_label[i] = le16_to_cpu(ep->dentry.volume_label.volume_label[i]);
+
+cleanup:
+ if (bh)
+ brelse(bh);
+
+ return ret;
+}
+
+int exfat_write_volume_label(struct super_block *sb)
+{
+ int ret, i;
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct buffer_head *bh;
+ struct exfat_dentry *ep;
+
+ ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
+ if (ret < 0)
+ goto cleanup;
+
+ mutex_lock(&sbi->s_lock);
+ for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
+ ep->dentry.volume_label.volume_label[i] = cpu_to_le16(sbi->volume_label[i]);
+
+ ep->dentry.volume_label.char_count =
+ UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
+ mutex_unlock(&sbi->s_lock);
+
+cleanup:
+ if (bh) {
+ exfat_update_bh(bh, true);
+ brelse(bh);
+ }
+
+ return ret;
+}
+
/* mount the file system volume */
static int __exfat_fill_super(struct super_block *sb,
struct exfat_chain *root_clu)
@@ -616,6 +709,12 @@ static int __exfat_fill_super(struct super_block *sb,
goto free_bh;
}
+ ret = exfat_read_volume_label(sb);
+ if (ret) {
+ exfat_err(sb, "failed to read volume label");
+ goto free_bh;
+ }
+
ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
if (ret) {
exfat_err(sb, "failed to scan clusters");
--
2.50.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 0:30 [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL Ethan Ferguson
2025-08-17 0:30 ` [PATCH v2 1/1] " Ethan Ferguson
@ 2025-08-17 12:30 ` Namjae Jeon
2025-08-17 14:32 ` [PATCH v2 1/1] " Ethan Ferguson
1 sibling, 1 reply; 11+ messages in thread
From: Namjae Jeon @ 2025-08-17 12:30 UTC (permalink / raw)
To: Ethan Ferguson; +Cc: sj1557.seo, yuezhang.mo, linux-fsdevel, linux-kernel
On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson
<ethan.ferguson@zetier.com> wrote:
>
> Add support for reading / writing to the exfat volume label from the
> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls.
>
> Implemented in similar ways to other fs drivers, namely btrfs and ext4,
> where the ioctls are performed on file inodes.
We can load and store a volume label using tune.exfat in exfatprogs.
Is there any usage that requires this, even though there are utils
that can do it?
Thanks.
>
> v2:
> Fix endianness conversion as reported by kernel test robot
> v1:
> Link: https://lore.kernel.org/all/20250815171056.103751-1-ethan.ferguson@zetier.com/
>
> Ethan Ferguson (1):
> exfat: Add support for FS_IOC_{GET,SET}FSLABEL
> exfat: Fix endian conversion
>
> fs/exfat/exfat_fs.h | 2 +
> fs/exfat/exfat_raw.h | 6 +++
> fs/exfat/file.c | 56 +++++++++++++++++++++++++
> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 163 insertions(+)
>
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 12:30 ` [PATCH v2 0/1] " Namjae Jeon
@ 2025-08-17 14:32 ` Ethan Ferguson
2025-08-18 5:52 ` Christoph Hellwig
0 siblings, 1 reply; 11+ messages in thread
From: Ethan Ferguson @ 2025-08-17 14:32 UTC (permalink / raw)
To: linkinjeon
Cc: ethan.ferguson, linux-fsdevel, linux-kernel, sj1557.seo,
yuezhang.mo
On 8/17/25 08:30, Namjae Jeon wrote:
> On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson
> <ethan.ferguson@zetier.com> wrote:
>>
>> Add support for reading / writing to the exfat volume label from the
>> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls.
>>
>> Implemented in similar ways to other fs drivers, namely btrfs and ext4,
>> where the ioctls are performed on file inodes.
> We can load and store a volume label using tune.exfat in exfatprogs.
> Is there any usage that requires this, even though there are utils
> that can do it?
>
> Thanks.
Both e2fsprogs and btrfs-progs now use the FS_IOC_{GET,SET}FSLABEL
ioctls to change the label on a mounted filesystem.
As for me, personally, I ran into this while developing on an
embedded device that does not have, and cannot have, exfatprogs.
Having this be a kernel feature would (I believe) bring the exfat driver
more in line with other fs drivers in the mainline tree.
Thank you for your consideration!
>>
>> v2:
>> Fix endianness conversion as reported by kernel test robot
>> v1:
>> Link: https://lore.kernel.org/all/20250815171056.103751-1-ethan.ferguson@zetier.com/
>>
>> Ethan Ferguson (1):
>> exfat: Add support for FS_IOC_{GET,SET}FSLABEL
>> exfat: Fix endian conversion
>>
>> fs/exfat/exfat_fs.h | 2 +
>> fs/exfat/exfat_raw.h | 6 +++
>> fs/exfat/file.c | 56 +++++++++++++++++++++++++
>> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 163 insertions(+)
>>
>> --
>> 2.50.1
>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 14:32 ` [PATCH v2 1/1] " Ethan Ferguson
@ 2025-08-18 5:52 ` Christoph Hellwig
2025-08-18 17:49 ` [PATCH v2 0/1] " Ethan Ferguson
0 siblings, 1 reply; 11+ messages in thread
From: Christoph Hellwig @ 2025-08-18 5:52 UTC (permalink / raw)
To: Ethan Ferguson
Cc: linkinjeon, linux-fsdevel, linux-kernel, sj1557.seo, yuezhang.mo
On Sun, Aug 17, 2025 at 10:32:00AM -0400, Ethan Ferguson wrote:
> Both e2fsprogs and btrfs-progs now use the FS_IOC_{GET,SET}FSLABEL
> ioctls to change the label on a mounted filesystem.
Additionally userspace writes to blocks on mounted file systems are
dangerous. They can easily corrupt data when racing with updates
performed by the kernel, and are impossible when the the
CONFIG_BLK_DEV_WRITE_MOUNTED config option is disabled.
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-18 5:52 ` Christoph Hellwig
@ 2025-08-18 17:49 ` Ethan Ferguson
2025-08-19 8:15 ` Christoph Hellwig
0 siblings, 1 reply; 11+ messages in thread
From: Ethan Ferguson @ 2025-08-18 17:49 UTC (permalink / raw)
To: hch
Cc: ethan.ferguson, linkinjeon, linux-fsdevel, linux-kernel,
sj1557.seo, yuezhang.mo
On 8/18/25 01:52, Christoph Hellwig wrote:
> On Sun, Aug 17, 2025 at 10:32:00AM -0400, Ethan Ferguson wrote:
>> Both e2fsprogs and btrfs-progs now use the FS_IOC_{GET,SET}FSLABEL
>> ioctls to change the label on a mounted filesystem.
>
> Additionally userspace writes to blocks on mounted file systems are
> dangerous. They can easily corrupt data when racing with updates
> performed by the kernel, and are impossible when the the
> CONFIG_BLK_DEV_WRITE_MOUNTED config option is disabled.
That's fair. I took a look at how btrfs guards against this, it seems
as if they use mnt_want_write_file to guard against bad writes, and
only write to the in-memory superblock, and commit the transaction
afterwards. However, this (during my testing with
CONFIG_BLK_DEV_WRITE_MOUNTED both on and off) still results in an
immediate disk flush.
My changes from this thread also seem to work with
CONFIG_BLK_DEV_WRITE_MOUNTED both disabled and enabled.
Maybe an alternative would be to only write to sbi->volume_label (with
mutex guarding), and only flush to disk on exfat_put_super? And to use
mnt_want_write_file as well.
Unfortunately, given that I'm pretty new to kernel development, I
wouldn't know that the best way to approach this. Any thoughts, or
pointers in the right direction, would be appreciated.
Thank you for the feedback!
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 0:30 ` [PATCH v2 1/1] " Ethan Ferguson
@ 2025-08-19 1:45 ` Namjae Jeon
2025-08-19 13:22 ` [PATCH] " Ethan Ferguson
2025-08-19 5:32 ` [PATCH v2 1/1] " Yuezhang.Mo
1 sibling, 1 reply; 11+ messages in thread
From: Namjae Jeon @ 2025-08-19 1:45 UTC (permalink / raw)
To: Ethan Ferguson; +Cc: sj1557.seo, yuezhang.mo, linux-fsdevel, linux-kernel
On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson
<ethan.ferguson@zetier.com> wrote:
>
> Add support for reading / writing to the exfat volume label from the
> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls
>
> Signed-off-by: Ethan Ferguson <ethan.ferguson@zetier.com>
>
> ---
> fs/exfat/exfat_fs.h | 2 +
> fs/exfat/exfat_raw.h | 6 +++
> fs/exfat/file.c | 56 +++++++++++++++++++++++++
> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 163 insertions(+)
>
> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
> index f8ead4d47ef0..a764e6362172 100644
> --- a/fs/exfat/exfat_fs.h
> +++ b/fs/exfat/exfat_fs.h
> @@ -267,6 +267,7 @@ struct exfat_sb_info {
> struct buffer_head **vol_amap; /* allocation bitmap */
>
> unsigned short *vol_utbl; /* upcase table */
> + unsigned short volume_label[EXFAT_VOLUME_LABEL_LEN]; /* volume name */
There's no reason to have this in sbi. I think it's better to read the
volume name in ioctl fslabel and return it.
>
> unsigned int clu_srch_ptr; /* cluster search pointer */
> unsigned int used_clusters; /* number of used clusters */
> @@ -431,6 +432,7 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
> /* super.c */
> int exfat_set_volume_dirty(struct super_block *sb);
> int exfat_clear_volume_dirty(struct super_block *sb);
> +int exfat_write_volume_label(struct super_block *sb);
>
> /* fatent.c */
> #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
> diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
> index 971a1ccd0e89..af04cef81c0c 100644
> --- a/fs/exfat/exfat_raw.h
> +++ b/fs/exfat/exfat_raw.h
> @@ -80,6 +80,7 @@
> #define BOOTSEC_OLDBPB_LEN 53
>
> #define EXFAT_FILE_NAME_LEN 15
> +#define EXFAT_VOLUME_LABEL_LEN 11
>
> #define EXFAT_MIN_SECT_SIZE_BITS 9
> #define EXFAT_MAX_SECT_SIZE_BITS 12
> @@ -159,6 +160,11 @@ struct exfat_dentry {
> __le32 start_clu;
> __le64 size;
> } __packed upcase; /* up-case table directory entry */
> + struct {
> + __u8 char_count;
> + __le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
> + __u8 reserved[8];
> + } __packed volume_label;
> struct {
> __u8 flags;
> __u8 vendor_guid[16];
> diff --git a/fs/exfat/file.c b/fs/exfat/file.c
> index 538d2b6ac2ec..c57d266aae3d 100644
> --- a/fs/exfat/file.c
> +++ b/fs/exfat/file.c
> @@ -12,6 +12,7 @@
> #include <linux/security.h>
> #include <linux/msdos_fs.h>
> #include <linux/writeback.h>
> +#include "../nls/nls_ucs2_utils.h"
>
> #include "exfat_raw.h"
> #include "exfat_fs.h"
> @@ -486,6 +487,57 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
> return exfat_force_shutdown(sb, flags);
> }
>
> +static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
> +{
> + int ret;
> + char utf8[FSLABEL_MAX] = {0};
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + size_t len = UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> +
> + mutex_lock(&sbi->s_lock);
> + ret = utf16s_to_utf8s(sbi->volume_label, len,
> + UTF16_HOST_ENDIAN, utf8, FSLABEL_MAX);
> + mutex_unlock(&sbi->s_lock);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (copy_to_user((char __user *)arg, utf8, FSLABEL_MAX))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int exfat_ioctl_set_volume_label(struct super_block *sb, unsigned long arg)
> +{
> + int ret = 0;
> + char utf8[FSLABEL_MAX];
> + size_t len;
> + unsigned short utf16[EXFAT_VOLUME_LABEL_LEN] = {0};
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> +
> + if (!capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + if (copy_from_user(utf8, (char __user *)arg, FSLABEL_MAX))
> + return -EFAULT;
> +
> + len = strnlen(utf8, FSLABEL_MAX);
> + if (len > EXFAT_VOLUME_LABEL_LEN)
Is FSLABEL_MAX in bytes or the number of characters ?
> + exfat_info(sb, "Volume label length too long, truncating");
> +
> + mutex_lock(&sbi->s_lock);
> + ret = utf8s_to_utf16s(utf8, len, UTF16_HOST_ENDIAN, utf16, EXFAT_VOLUME_LABEL_LEN);
> + mutex_unlock(&sbi->s_lock);
> +
> + if (ret < 0)
> + return ret;
> +
> + memcpy(sbi->volume_label, utf16, sizeof(sbi->volume_label));
> +
> + return exfat_write_volume_label(sb);
> +}
> +
> long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> {
> struct inode *inode = file_inode(filp);
> @@ -500,6 +552,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> return exfat_ioctl_shutdown(inode->i_sb, arg);
> case FITRIM:
> return exfat_ioctl_fitrim(inode, arg);
> + case FS_IOC_GETFSLABEL:
> + return exfat_ioctl_get_volume_label(inode->i_sb, arg);
> + case FS_IOC_SETFSLABEL:
> + return exfat_ioctl_set_volume_label(inode->i_sb, arg);
> default:
> return -ENOTTY;
> }
> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
> index 8926e63f5bb7..96cd4bb7cb19 100644
> --- a/fs/exfat/super.c
> +++ b/fs/exfat/super.c
> @@ -18,6 +18,7 @@
> #include <linux/nls.h>
> #include <linux/buffer_head.h>
> #include <linux/magic.h>
> +#include "../nls/nls_ucs2_utils.h"
>
> #include "exfat_raw.h"
> #include "exfat_fs.h"
> @@ -573,6 +574,98 @@ static int exfat_verify_boot_region(struct super_block *sb)
> return 0;
> }
>
> +static int exfat_get_volume_label_ptrs(struct super_block *sb,
> + struct buffer_head **out_bh,
> + struct exfat_dentry **out_dentry)
> +{
> + int i;
> + unsigned int type;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct exfat_chain clu;
> + struct exfat_dentry *ep;
> + struct buffer_head *bh;
> +
> + clu.dir = sbi->root_dir;
> + clu.flags = ALLOC_FAT_CHAIN;
> +
> + while (clu.dir != EXFAT_EOF_CLUSTER) {
> + for (i = 0; i < sbi->dentries_per_clu; i++) {
> + ep = exfat_get_dentry(sb, &clu, i, &bh);
> +
> + if (!ep)
> + return -EIO;
> +
> + type = exfat_get_entry_type(ep);
> + if (type == TYPE_UNUSED) {
> + brelse(bh);
> + return -EIO;
> + }
> +
> + if (type == TYPE_VOLUME) {
> + *out_bh = bh;
> + *out_dentry = ep;
> + return 0;
> + }
> +
> + brelse(bh);
> + }
> +
> + if (exfat_get_next_cluster(sb, &(clu.dir)))
> + return -EIO;
> + }
> +
> + return -EIO;
> +}
> +
> +static int exfat_read_volume_label(struct super_block *sb)
> +{
> + int ret, i;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct buffer_head *bh;
> + struct exfat_dentry *ep;
> +
> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> + if (ret < 0)
> + goto cleanup;
> +
> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> + sbi->volume_label[i] = le16_to_cpu(ep->dentry.volume_label.volume_label[i]);
> +
> +cleanup:
> + if (bh)
> + brelse(bh);
> +
> + return ret;
> +}
> +
> +int exfat_write_volume_label(struct super_block *sb)
> +{
> + int ret, i;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct buffer_head *bh;
> + struct exfat_dentry *ep;
> +
> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> + if (ret < 0)
> + goto cleanup;
> +
> + mutex_lock(&sbi->s_lock);
> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> + ep->dentry.volume_label.volume_label[i] = cpu_to_le16(sbi->volume_label[i]);
> +
> + ep->dentry.volume_label.char_count =
> + UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> + mutex_unlock(&sbi->s_lock);
> +
> +cleanup:
> + if (bh) {
> + exfat_update_bh(bh, true);
> + brelse(bh);
> + }
> +
> + return ret;
> +}
> +
> /* mount the file system volume */
> static int __exfat_fill_super(struct super_block *sb,
> struct exfat_chain *root_clu)
> @@ -616,6 +709,12 @@ static int __exfat_fill_super(struct super_block *sb,
> goto free_bh;
> }
>
> + ret = exfat_read_volume_label(sb);
It will affect mount time if volume label entry is located at the end.
So, we can read it in ioctl fslabel as I said above.
> + if (ret) {
> + exfat_err(sb, "failed to read volume label");
> + goto free_bh;
> + }
> +
> ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
> if (ret) {
> exfat_err(sb, "failed to scan clusters");
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-17 0:30 ` [PATCH v2 1/1] " Ethan Ferguson
2025-08-19 1:45 ` Namjae Jeon
@ 2025-08-19 5:32 ` Yuezhang.Mo
1 sibling, 0 replies; 11+ messages in thread
From: Yuezhang.Mo @ 2025-08-19 5:32 UTC (permalink / raw)
To: Ethan Ferguson, linkinjeon@kernel.org, sj1557.seo@samsung.com
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson <ethan.ferguson@zetier.com> wrote:
> Add support for reading / writing to the exfat volume label from the
> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls
>
> Signed-off-by: Ethan Ferguson <ethan.ferguson@zetier.com>
>
> ---
> fs/exfat/exfat_fs.h | 2 +
> fs/exfat/exfat_raw.h | 6 +++
> fs/exfat/file.c | 56 +++++++++++++++++++++++++
> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 163 insertions(+)
>
> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
> index f8ead4d47ef0..a764e6362172 100644
> --- a/fs/exfat/exfat_fs.h
> +++ b/fs/exfat/exfat_fs.h
> @@ -267,6 +267,7 @@ struct exfat_sb_info {
> struct buffer_head **vol_amap; /* allocation bitmap */
>
> unsigned short *vol_utbl; /* upcase table */
> + unsigned short volume_label[EXFAT_VOLUME_LABEL_LEN]; /* volume name */
>
> unsigned int clu_srch_ptr; /* cluster search pointer */
> unsigned int used_clusters; /* number of used clusters */
> @@ -431,6 +432,7 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
> /* super.c */
> int exfat_set_volume_dirty(struct super_block *sb);
> int exfat_clear_volume_dirty(struct super_block *sb);
> +int exfat_write_volume_label(struct super_block *sb);
>
> /* fatent.c */
> #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
> diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
> index 971a1ccd0e89..af04cef81c0c 100644
> --- a/fs/exfat/exfat_raw.h
> +++ b/fs/exfat/exfat_raw.h
> @@ -80,6 +80,7 @@
> #define BOOTSEC_OLDBPB_LEN 53
>
> #define EXFAT_FILE_NAME_LEN 15
> +#define EXFAT_VOLUME_LABEL_LEN 11
>
> #define EXFAT_MIN_SECT_SIZE_BITS 9
> #define EXFAT_MAX_SECT_SIZE_BITS 12
> @@ -159,6 +160,11 @@ struct exfat_dentry {
> __le32 start_clu;
> __le64 size;
> } __packed upcase; /* up-case table directory entry */
> + struct {
> + __u8 char_count;
> + __le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
> + __u8 reserved[8];
> + } __packed volume_label;
> struct {
> __u8 flags;
> __u8 vendor_guid[16];
> diff --git a/fs/exfat/file.c b/fs/exfat/file.c
> index 538d2b6ac2ec..c57d266aae3d 100644
> --- a/fs/exfat/file.c
> +++ b/fs/exfat/file.c
> @@ -12,6 +12,7 @@
> #include <linux/security.h>
> #include <linux/msdos_fs.h>
> #include <linux/writeback.h>
> +#include "../nls/nls_ucs2_utils.h"
>
> #include "exfat_raw.h"
> #include "exfat_fs.h"
> @@ -486,6 +487,57 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
> return exfat_force_shutdown(sb, flags);
> }
>
> +static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
> +{
> + int ret;
> + char utf8[FSLABEL_MAX] = {0};
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + size_t len = UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> +
> + mutex_lock(&sbi->s_lock);
> + ret = utf16s_to_utf8s(sbi->volume_label, len,
> + UTF16_HOST_ENDIAN, utf8, FSLABEL_MAX);
The iocharset specified by the mount option should be used instead of always using utf8.
> + mutex_unlock(&sbi->s_lock);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (copy_to_user((char __user *)arg, utf8, FSLABEL_MAX))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int exfat_ioctl_set_volume_label(struct super_block *sb, unsigned long arg)
> +{
> + int ret = 0;
> + char utf8[FSLABEL_MAX];
> + size_t len;
> + unsigned short utf16[EXFAT_VOLUME_LABEL_LEN] = {0};
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> +
> + if (!capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + if (copy_from_user(utf8, (char __user *)arg, FSLABEL_MAX))
> + return -EFAULT;
> +
> + len = strnlen(utf8, FSLABEL_MAX);
> + if (len > EXFAT_VOLUME_LABEL_LEN)
> + exfat_info(sb, "Volume label length too long, truncating");
> +
> + mutex_lock(&sbi->s_lock);
> + ret = utf8s_to_utf16s(utf8, len, UTF16_HOST_ENDIAN, utf16, EXFAT_VOLUME_LABEL_LEN);
If the new volume label contains invalid characters, an error should be returned.
Invalid characters are defined at
https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#table-35-invalid-filename-characters
> + mutex_unlock(&sbi->s_lock);
> +
> + if (ret < 0)
> + return ret;
> +
> + memcpy(sbi->volume_label, utf16, sizeof(sbi->volume_label));
> +
> + return exfat_write_volume_label(sb);
> +}
> +
> long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> {
> struct inode *inode = file_inode(filp);
> @@ -500,6 +552,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> return exfat_ioctl_shutdown(inode->i_sb, arg);
> case FITRIM:
> return exfat_ioctl_fitrim(inode, arg);
> + case FS_IOC_GETFSLABEL:
> + return exfat_ioctl_get_volume_label(inode->i_sb, arg);
> + case FS_IOC_SETFSLABEL:
> + return exfat_ioctl_set_volume_label(inode->i_sb, arg);
> default:
> return -ENOTTY;
> }
> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
> index 8926e63f5bb7..96cd4bb7cb19 100644
> --- a/fs/exfat/super.c
> +++ b/fs/exfat/super.c
> @@ -18,6 +18,7 @@
> #include <linux/nls.h>
> #include <linux/buffer_head.h>
> #include <linux/magic.h>
> +#include "../nls/nls_ucs2_utils.h"
>
> #include "exfat_raw.h"
> #include "exfat_fs.h"
> @@ -573,6 +574,98 @@ static int exfat_verify_boot_region(struct super_block *sb)
> return 0;
> }
>
> +static int exfat_get_volume_label_ptrs(struct super_block *sb,
> + struct buffer_head **out_bh,
> + struct exfat_dentry **out_dentry)
> +{
> + int i;
> + unsigned int type;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct exfat_chain clu;
> + struct exfat_dentry *ep;
> + struct buffer_head *bh;
> +
> + clu.dir = sbi->root_dir;
> + clu.flags = ALLOC_FAT_CHAIN;
> +
> + while (clu.dir != EXFAT_EOF_CLUSTER) {
> + for (i = 0; i < sbi->dentries_per_clu; i++) {
> + ep = exfat_get_dentry(sb, &clu, i, &bh);
> +
> + if (!ep)
> + return -EIO;
> +
> + type = exfat_get_entry_type(ep);
> + if (type == TYPE_UNUSED) {
> + brelse(bh);
> + return -EIO;
> + }
> +
When formatting a partition, exfatprogs's mkfs will create a label directory
entry regardless of whether the user specifies a label.
However, on Windows or when using other mkfs, no label directory entry
may be created. In this case, if we want to set a volume label, should we
create it with an empty directory entry (TYPE_DELETED or TYPE_UNUSED)?
Should we even allocate a new cluster if there's no empty directory entry?
> + if (type == TYPE_VOLUME) {
> + *out_bh = bh;
> + *out_dentry = ep;
> + return 0;
> + }
> +
> + brelse(bh);
> + }
> +
> + if (exfat_get_next_cluster(sb, &(clu.dir)))
> + return -EIO;
> + }
> +
> + return -EIO;
> +}
> +
> +static int exfat_read_volume_label(struct super_block *sb)
> +{
> + int ret, i;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct buffer_head *bh;
> + struct exfat_dentry *ep;
> +
> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> + if (ret < 0)
> + goto cleanup;
> +
> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> + sbi->volume_label[i] = le16_to_cpu(ep->dentry.volume_label.volume_label[i]);
> +
> +cleanup:
> + if (bh)
> + brelse(bh);
> +
> + return ret;
> +}
> +
> +int exfat_write_volume_label(struct super_block *sb)
> +{
> + int ret, i;
> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> + struct buffer_head *bh;
> + struct exfat_dentry *ep;
> +
> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> + if (ret < 0)
> + goto cleanup;
> +
> + mutex_lock(&sbi->s_lock);
> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> + ep->dentry.volume_label.volume_label[i] = cpu_to_le16(sbi->volume_label[i]);
> +
> + ep->dentry.volume_label.char_count =
> + UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> + mutex_unlock(&sbi->s_lock);
> +
> +cleanup:
> + if (bh) {
> + exfat_update_bh(bh, true);
> + brelse(bh);
> + }
> +
> + return ret;
> +}
> +
> /* mount the file system volume */
> static int __exfat_fill_super(struct super_block *sb,
> struct exfat_chain *root_clu)
> @@ -616,6 +709,12 @@ static int __exfat_fill_super(struct super_block *sb,
> goto free_bh;
> }
>
> + ret = exfat_read_volume_label(sb);
> + if (ret) {
> + exfat_err(sb, "failed to read volume label");
> + goto free_bh;
> + }
> +
> ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
> if (ret) {
> exfat_err(sb, "failed to scan clusters");
> --
> 2.50.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-18 17:49 ` [PATCH v2 0/1] " Ethan Ferguson
@ 2025-08-19 8:15 ` Christoph Hellwig
0 siblings, 0 replies; 11+ messages in thread
From: Christoph Hellwig @ 2025-08-19 8:15 UTC (permalink / raw)
To: Ethan Ferguson
Cc: hch, linkinjeon, linux-fsdevel, linux-kernel, sj1557.seo,
yuezhang.mo
On Mon, Aug 18, 2025 at 01:49:11PM -0400, Ethan Ferguson wrote:
> That's fair. I took a look at how btrfs guards against this, it seems
> as if they use mnt_want_write_file to guard against bad writes, and
> only write to the in-memory superblock, and commit the transaction
> afterwards. However, this (during my testing with
> CONFIG_BLK_DEV_WRITE_MOUNTED both on and off) still results in an
> immediate disk flush.
>
> My changes from this thread also seem to work with
> CONFIG_BLK_DEV_WRITE_MOUNTED both disabled and enabled.
What I meant to say is that we actually need your change to work with
CONFIG_BLK_DEV_WRITE_MOUNTED, as the current way in tunefs is broken,
even if that's something a few Linux file systems have historically
done.
> Maybe an alternative would be to only write to sbi->volume_label (with
> mutex guarding), and only flush to disk on exfat_put_super? And to use
> mnt_want_write_file as well.
I think your patch is fine as-is. I've just been trying to give you
additional ammunition.
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-19 1:45 ` Namjae Jeon
@ 2025-08-19 13:22 ` Ethan Ferguson
2025-08-19 14:51 ` Namjae Jeon
0 siblings, 1 reply; 11+ messages in thread
From: Ethan Ferguson @ 2025-08-19 13:22 UTC (permalink / raw)
To: linkinjeon
Cc: ethan.ferguson, linux-fsdevel, linux-kernel, sj1557.seo,
yuezhang.mo
On 8/18/25 21:45, Namjae Jeon wrote:
> On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson
> <ethan.ferguson@zetier.com> wrote:
>>
>> Add support for reading / writing to the exfat volume label from the
>> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls
>>
>> Signed-off-by: Ethan Ferguson <ethan.ferguson@zetier.com>
>>
>> ---
>> fs/exfat/exfat_fs.h | 2 +
>> fs/exfat/exfat_raw.h | 6 +++
>> fs/exfat/file.c | 56 +++++++++++++++++++++++++
>> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 163 insertions(+)
>>
>> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
>> index f8ead4d47ef0..a764e6362172 100644
>> --- a/fs/exfat/exfat_fs.h
>> +++ b/fs/exfat/exfat_fs.h
>> @@ -267,6 +267,7 @@ struct exfat_sb_info {
>> struct buffer_head **vol_amap; /* allocation bitmap */
>>
>> unsigned short *vol_utbl; /* upcase table */
>> + unsigned short volume_label[EXFAT_VOLUME_LABEL_LEN]; /* volume name */
> There's no reason to have this in sbi. I think it's better to read the
> volume name in ioctl fslabel and return it.
>
That's fair. I wrote it this way because the volume label is stored in
the sbi in btrfs, but there it's (as far as I understand) part of the
fs header on disk, and not (as is the case in exfat) a directory entry
that could be arbitrarily far from the start of the disk. Maybe we could
cache it in the sbi after the first read? I'm open to either.
>>
>> unsigned int clu_srch_ptr; /* cluster search pointer */
>> unsigned int used_clusters; /* number of used clusters */
>> @@ -431,6 +432,7 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
>> /* super.c */
>> int exfat_set_volume_dirty(struct super_block *sb);
>> int exfat_clear_volume_dirty(struct super_block *sb);
>> +int exfat_write_volume_label(struct super_block *sb);
>>
>> /* fatent.c */
>> #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
>> diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
>> index 971a1ccd0e89..af04cef81c0c 100644
>> --- a/fs/exfat/exfat_raw.h
>> +++ b/fs/exfat/exfat_raw.h
>> @@ -80,6 +80,7 @@
>> #define BOOTSEC_OLDBPB_LEN 53
>>
>> #define EXFAT_FILE_NAME_LEN 15
>> +#define EXFAT_VOLUME_LABEL_LEN 11
>>
>> #define EXFAT_MIN_SECT_SIZE_BITS 9
>> #define EXFAT_MAX_SECT_SIZE_BITS 12
>> @@ -159,6 +160,11 @@ struct exfat_dentry {
>> __le32 start_clu;
>> __le64 size;
>> } __packed upcase; /* up-case table directory entry */
>> + struct {
>> + __u8 char_count;
>> + __le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
>> + __u8 reserved[8];
>> + } __packed volume_label;
>> struct {
>> __u8 flags;
>> __u8 vendor_guid[16];
>> diff --git a/fs/exfat/file.c b/fs/exfat/file.c
>> index 538d2b6ac2ec..c57d266aae3d 100644
>> --- a/fs/exfat/file.c
>> +++ b/fs/exfat/file.c
>> @@ -12,6 +12,7 @@
>> #include <linux/security.h>
>> #include <linux/msdos_fs.h>
>> #include <linux/writeback.h>
>> +#include "../nls/nls_ucs2_utils.h"
>>
>> #include "exfat_raw.h"
>> #include "exfat_fs.h"
>> @@ -486,6 +487,57 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
>> return exfat_force_shutdown(sb, flags);
>> }
>>
>> +static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
>> +{
>> + int ret;
>> + char utf8[FSLABEL_MAX] = {0};
>> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
>> + size_t len = UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
>> +
>> + mutex_lock(&sbi->s_lock);
>> + ret = utf16s_to_utf8s(sbi->volume_label, len,
>> + UTF16_HOST_ENDIAN, utf8, FSLABEL_MAX);
>> + mutex_unlock(&sbi->s_lock);
>> +
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (copy_to_user((char __user *)arg, utf8, FSLABEL_MAX))
>> + return -EFAULT;
>> +
>> + return 0;
>> +}
>> +
>> +static int exfat_ioctl_set_volume_label(struct super_block *sb, unsigned long arg)
>> +{
>> + int ret = 0;
>> + char utf8[FSLABEL_MAX];
>> + size_t len;
>> + unsigned short utf16[EXFAT_VOLUME_LABEL_LEN] = {0};
>> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
>> +
>> + if (!capable(CAP_SYS_ADMIN))
>> + return -EPERM;
>> +
>> + if (copy_from_user(utf8, (char __user *)arg, FSLABEL_MAX))
>> + return -EFAULT;
>> +
>> + len = strnlen(utf8, FSLABEL_MAX);
>> + if (len > EXFAT_VOLUME_LABEL_LEN)
> Is FSLABEL_MAX in bytes or the number of characters ?
>
the definition mentions chars, and everywhere else it's used it's in
terms of chars, so I'd say it's in terms of bytes. The
FS_IOC_{GET,SET}FSLABEL ioctls are in terms of char[FSLABEL_MAX], so
I think it's reasonable to use it as a number of bytes.
>> + exfat_info(sb, "Volume label length too long, truncating");
>> +
>> + mutex_lock(&sbi->s_lock);
>> + ret = utf8s_to_utf16s(utf8, len, UTF16_HOST_ENDIAN, utf16, EXFAT_VOLUME_LABEL_LEN);
>> + mutex_unlock(&sbi->s_lock);
>> +
>> + if (ret < 0)
>> + return ret;
>> +
>> + memcpy(sbi->volume_label, utf16, sizeof(sbi->volume_label));
>> +
>> + return exfat_write_volume_label(sb);
>> +}
>> +
>> long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
>> {
>> struct inode *inode = file_inode(filp);
>> @@ -500,6 +552,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
>> return exfat_ioctl_shutdown(inode->i_sb, arg);
>> case FITRIM:
>> return exfat_ioctl_fitrim(inode, arg);
>> + case FS_IOC_GETFSLABEL:
>> + return exfat_ioctl_get_volume_label(inode->i_sb, arg);
>> + case FS_IOC_SETFSLABEL:
>> + return exfat_ioctl_set_volume_label(inode->i_sb, arg);
>> default:
>> return -ENOTTY;
>> }
>> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
>> index 8926e63f5bb7..96cd4bb7cb19 100644
>> --- a/fs/exfat/super.c
>> +++ b/fs/exfat/super.c
>> @@ -18,6 +18,7 @@
>> #include <linux/nls.h>
>> #include <linux/buffer_head.h>
>> #include <linux/magic.h>
>> +#include "../nls/nls_ucs2_utils.h"
>>
>> #include "exfat_raw.h"
>> #include "exfat_fs.h"
>> @@ -573,6 +574,98 @@ static int exfat_verify_boot_region(struct super_block *sb)
>> return 0;
>> }
>>
>> +static int exfat_get_volume_label_ptrs(struct super_block *sb,
>> + struct buffer_head **out_bh,
>> + struct exfat_dentry **out_dentry)
>> +{
>> + int i;
>> + unsigned int type;
>> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
>> + struct exfat_chain clu;
>> + struct exfat_dentry *ep;
>> + struct buffer_head *bh;
>> +
>> + clu.dir = sbi->root_dir;
>> + clu.flags = ALLOC_FAT_CHAIN;
>> +
>> + while (clu.dir != EXFAT_EOF_CLUSTER) {
>> + for (i = 0; i < sbi->dentries_per_clu; i++) {
>> + ep = exfat_get_dentry(sb, &clu, i, &bh);
>> +
>> + if (!ep)
>> + return -EIO;
>> +
>> + type = exfat_get_entry_type(ep);
>> + if (type == TYPE_UNUSED) {
>> + brelse(bh);
>> + return -EIO;
>> + }
>> +
>> + if (type == TYPE_VOLUME) {
>> + *out_bh = bh;
>> + *out_dentry = ep;
>> + return 0;
>> + }
>> +
>> + brelse(bh);
>> + }
>> +
>> + if (exfat_get_next_cluster(sb, &(clu.dir)))
>> + return -EIO;
>> + }
>> +
>> + return -EIO;
>> +}
>> +
>> +static int exfat_read_volume_label(struct super_block *sb)
>> +{
>> + int ret, i;
>> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
>> + struct buffer_head *bh;
>> + struct exfat_dentry *ep;
>> +
>> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
>> + if (ret < 0)
>> + goto cleanup;
>> +
>> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
>> + sbi->volume_label[i] = le16_to_cpu(ep->dentry.volume_label.volume_label[i]);
>> +
>> +cleanup:
>> + if (bh)
>> + brelse(bh);
>> +
>> + return ret;
>> +}
>> +
>> +int exfat_write_volume_label(struct super_block *sb)
>> +{
>> + int ret, i;
>> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
>> + struct buffer_head *bh;
>> + struct exfat_dentry *ep;
>> +
>> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
>> + if (ret < 0)
>> + goto cleanup;
>> +
>> + mutex_lock(&sbi->s_lock);
>> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
>> + ep->dentry.volume_label.volume_label[i] = cpu_to_le16(sbi->volume_label[i]);
>> +
>> + ep->dentry.volume_label.char_count =
>> + UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
>> + mutex_unlock(&sbi->s_lock);
>> +
>> +cleanup:
>> + if (bh) {
>> + exfat_update_bh(bh, true);
>> + brelse(bh);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> /* mount the file system volume */
>> static int __exfat_fill_super(struct super_block *sb,
>> struct exfat_chain *root_clu)
>> @@ -616,6 +709,12 @@ static int __exfat_fill_super(struct super_block *sb,
>> goto free_bh;
>> }
>>
>> + ret = exfat_read_volume_label(sb);
> It will affect mount time if volume label entry is located at the end.
> So, we can read it in ioctl fslabel as I said above.
Sounds good. I'll incorporate your changes, and those of
yuezhang.mo@sony.com, and submit version 3 of the patch soon.
>> + if (ret) {
>> + exfat_err(sb, "failed to read volume label");
>> + goto free_bh;
>> + }
>> +
>> ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
>> if (ret) {
>> exfat_err(sb, "failed to scan clusters");
>> --
>> 2.50.1
>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] exfat: Add support for FS_IOC_{GET,SET}FSLABEL
2025-08-19 13:22 ` [PATCH] " Ethan Ferguson
@ 2025-08-19 14:51 ` Namjae Jeon
0 siblings, 0 replies; 11+ messages in thread
From: Namjae Jeon @ 2025-08-19 14:51 UTC (permalink / raw)
To: Ethan Ferguson; +Cc: linux-fsdevel, linux-kernel, sj1557.seo, yuezhang.mo
On Tue, Aug 19, 2025 at 10:22 PM Ethan Ferguson
<ethan.ferguson@zetier.com> wrote:
>
> On 8/18/25 21:45, Namjae Jeon wrote:
> > On Sun, Aug 17, 2025 at 9:31 AM Ethan Ferguson
> > <ethan.ferguson@zetier.com> wrote:
> >>
> >> Add support for reading / writing to the exfat volume label from the
> >> FS_IOC_GETFSLABEL and FS_IOC_SETFSLABEL ioctls
> >>
> >> Signed-off-by: Ethan Ferguson <ethan.ferguson@zetier.com>
> >>
> >> ---
> >> fs/exfat/exfat_fs.h | 2 +
> >> fs/exfat/exfat_raw.h | 6 +++
> >> fs/exfat/file.c | 56 +++++++++++++++++++++++++
> >> fs/exfat/super.c | 99 ++++++++++++++++++++++++++++++++++++++++++++
> >> 4 files changed, 163 insertions(+)
> >>
> >> diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
> >> index f8ead4d47ef0..a764e6362172 100644
> >> --- a/fs/exfat/exfat_fs.h
> >> +++ b/fs/exfat/exfat_fs.h
> >> @@ -267,6 +267,7 @@ struct exfat_sb_info {
> >> struct buffer_head **vol_amap; /* allocation bitmap */
> >>
> >> unsigned short *vol_utbl; /* upcase table */
> >> + unsigned short volume_label[EXFAT_VOLUME_LABEL_LEN]; /* volume name */
> > There's no reason to have this in sbi. I think it's better to read the
> > volume name in ioctl fslabel and return it.
> >
> That's fair. I wrote it this way because the volume label is stored in
> the sbi in btrfs, but there it's (as far as I understand) part of the
> fs header on disk, and not (as is the case in exfat) a directory entry
> that could be arbitrarily far from the start of the disk. Maybe we could
> cache it in the sbi after the first read? I'm open to either.
I agree to cache it in sbi after the first read.
>
> >>
> >> unsigned int clu_srch_ptr; /* cluster search pointer */
> >> unsigned int used_clusters; /* number of used clusters */
> >> @@ -431,6 +432,7 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
> >> /* super.c */
> >> int exfat_set_volume_dirty(struct super_block *sb);
> >> int exfat_clear_volume_dirty(struct super_block *sb);
> >> +int exfat_write_volume_label(struct super_block *sb);
> >>
> >> /* fatent.c */
> >> #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu)
> >> diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
> >> index 971a1ccd0e89..af04cef81c0c 100644
> >> --- a/fs/exfat/exfat_raw.h
> >> +++ b/fs/exfat/exfat_raw.h
> >> @@ -80,6 +80,7 @@
> >> #define BOOTSEC_OLDBPB_LEN 53
> >>
> >> #define EXFAT_FILE_NAME_LEN 15
> >> +#define EXFAT_VOLUME_LABEL_LEN 11
> >>
> >> #define EXFAT_MIN_SECT_SIZE_BITS 9
> >> #define EXFAT_MAX_SECT_SIZE_BITS 12
> >> @@ -159,6 +160,11 @@ struct exfat_dentry {
> >> __le32 start_clu;
> >> __le64 size;
> >> } __packed upcase; /* up-case table directory entry */
> >> + struct {
> >> + __u8 char_count;
> >> + __le16 volume_label[EXFAT_VOLUME_LABEL_LEN];
> >> + __u8 reserved[8];
> >> + } __packed volume_label;
> >> struct {
> >> __u8 flags;
> >> __u8 vendor_guid[16];
> >> diff --git a/fs/exfat/file.c b/fs/exfat/file.c
> >> index 538d2b6ac2ec..c57d266aae3d 100644
> >> --- a/fs/exfat/file.c
> >> +++ b/fs/exfat/file.c
> >> @@ -12,6 +12,7 @@
> >> #include <linux/security.h>
> >> #include <linux/msdos_fs.h>
> >> #include <linux/writeback.h>
> >> +#include "../nls/nls_ucs2_utils.h"
> >>
> >> #include "exfat_raw.h"
> >> #include "exfat_fs.h"
> >> @@ -486,6 +487,57 @@ static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
> >> return exfat_force_shutdown(sb, flags);
> >> }
> >>
> >> +static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long arg)
> >> +{
> >> + int ret;
> >> + char utf8[FSLABEL_MAX] = {0};
> >> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> >> + size_t len = UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> >> +
> >> + mutex_lock(&sbi->s_lock);
> >> + ret = utf16s_to_utf8s(sbi->volume_label, len,
> >> + UTF16_HOST_ENDIAN, utf8, FSLABEL_MAX);
> >> + mutex_unlock(&sbi->s_lock);
> >> +
> >> + if (ret < 0)
> >> + return ret;
> >> +
> >> + if (copy_to_user((char __user *)arg, utf8, FSLABEL_MAX))
> >> + return -EFAULT;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int exfat_ioctl_set_volume_label(struct super_block *sb, unsigned long arg)
> >> +{
> >> + int ret = 0;
> >> + char utf8[FSLABEL_MAX];
> >> + size_t len;
> >> + unsigned short utf16[EXFAT_VOLUME_LABEL_LEN] = {0};
> >> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> >> +
> >> + if (!capable(CAP_SYS_ADMIN))
> >> + return -EPERM;
> >> +
> >> + if (copy_from_user(utf8, (char __user *)arg, FSLABEL_MAX))
> >> + return -EFAULT;
> >> +
> >> + len = strnlen(utf8, FSLABEL_MAX);
> >> + if (len > EXFAT_VOLUME_LABEL_LEN)
> > Is FSLABEL_MAX in bytes or the number of characters ?
> >
> the definition mentions chars, and everywhere else it's used it's in
> terms of chars, so I'd say it's in terms of bytes. The
> FS_IOC_{GET,SET}FSLABEL ioctls are in terms of char[FSLABEL_MAX], so
> I think it's reasonable to use it as a number of bytes.
Okay.
>
> >> + exfat_info(sb, "Volume label length too long, truncating");
> >> +
> >> + mutex_lock(&sbi->s_lock);
> >> + ret = utf8s_to_utf16s(utf8, len, UTF16_HOST_ENDIAN, utf16, EXFAT_VOLUME_LABEL_LEN);
> >> + mutex_unlock(&sbi->s_lock);
> >> +
> >> + if (ret < 0)
> >> + return ret;
> >> +
> >> + memcpy(sbi->volume_label, utf16, sizeof(sbi->volume_label));
> >> +
> >> + return exfat_write_volume_label(sb);
> >> +}
> >> +
> >> long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> >> {
> >> struct inode *inode = file_inode(filp);
> >> @@ -500,6 +552,10 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> >> return exfat_ioctl_shutdown(inode->i_sb, arg);
> >> case FITRIM:
> >> return exfat_ioctl_fitrim(inode, arg);
> >> + case FS_IOC_GETFSLABEL:
> >> + return exfat_ioctl_get_volume_label(inode->i_sb, arg);
> >> + case FS_IOC_SETFSLABEL:
> >> + return exfat_ioctl_set_volume_label(inode->i_sb, arg);
> >> default:
> >> return -ENOTTY;
> >> }
> >> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
> >> index 8926e63f5bb7..96cd4bb7cb19 100644
> >> --- a/fs/exfat/super.c
> >> +++ b/fs/exfat/super.c
> >> @@ -18,6 +18,7 @@
> >> #include <linux/nls.h>
> >> #include <linux/buffer_head.h>
> >> #include <linux/magic.h>
> >> +#include "../nls/nls_ucs2_utils.h"
> >>
> >> #include "exfat_raw.h"
> >> #include "exfat_fs.h"
> >> @@ -573,6 +574,98 @@ static int exfat_verify_boot_region(struct super_block *sb)
> >> return 0;
> >> }
> >>
> >> +static int exfat_get_volume_label_ptrs(struct super_block *sb,
> >> + struct buffer_head **out_bh,
> >> + struct exfat_dentry **out_dentry)
> >> +{
> >> + int i;
> >> + unsigned int type;
> >> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> >> + struct exfat_chain clu;
> >> + struct exfat_dentry *ep;
> >> + struct buffer_head *bh;
> >> +
> >> + clu.dir = sbi->root_dir;
> >> + clu.flags = ALLOC_FAT_CHAIN;
> >> +
> >> + while (clu.dir != EXFAT_EOF_CLUSTER) {
> >> + for (i = 0; i < sbi->dentries_per_clu; i++) {
> >> + ep = exfat_get_dentry(sb, &clu, i, &bh);
> >> +
> >> + if (!ep)
> >> + return -EIO;
> >> +
> >> + type = exfat_get_entry_type(ep);
> >> + if (type == TYPE_UNUSED) {
> >> + brelse(bh);
> >> + return -EIO;
> >> + }
> >> +
> >> + if (type == TYPE_VOLUME) {
> >> + *out_bh = bh;
> >> + *out_dentry = ep;
> >> + return 0;
> >> + }
> >> +
> >> + brelse(bh);
> >> + }
> >> +
> >> + if (exfat_get_next_cluster(sb, &(clu.dir)))
> >> + return -EIO;
> >> + }
> >> +
> >> + return -EIO;
> >> +}
> >> +
> >> +static int exfat_read_volume_label(struct super_block *sb)
> >> +{
> >> + int ret, i;
> >> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> >> + struct buffer_head *bh;
> >> + struct exfat_dentry *ep;
> >> +
> >> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> >> + if (ret < 0)
> >> + goto cleanup;
> >> +
> >> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> >> + sbi->volume_label[i] = le16_to_cpu(ep->dentry.volume_label.volume_label[i]);
> >> +
> >> +cleanup:
> >> + if (bh)
> >> + brelse(bh);
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +int exfat_write_volume_label(struct super_block *sb)
> >> +{
> >> + int ret, i;
> >> + struct exfat_sb_info *sbi = EXFAT_SB(sb);
> >> + struct buffer_head *bh;
> >> + struct exfat_dentry *ep;
> >> +
> >> + ret = exfat_get_volume_label_ptrs(sb, &bh, &ep);
> >> + if (ret < 0)
> >> + goto cleanup;
> >> +
> >> + mutex_lock(&sbi->s_lock);
> >> + for (i = 0; i < EXFAT_VOLUME_LABEL_LEN; i++)
> >> + ep->dentry.volume_label.volume_label[i] = cpu_to_le16(sbi->volume_label[i]);
> >> +
> >> + ep->dentry.volume_label.char_count =
> >> + UniStrnlen(sbi->volume_label, EXFAT_VOLUME_LABEL_LEN);
> >> + mutex_unlock(&sbi->s_lock);
> >> +
> >> +cleanup:
> >> + if (bh) {
> >> + exfat_update_bh(bh, true);
> >> + brelse(bh);
> >> + }
> >> +
> >> + return ret;
> >> +}
> >> +
> >> /* mount the file system volume */
> >> static int __exfat_fill_super(struct super_block *sb,
> >> struct exfat_chain *root_clu)
> >> @@ -616,6 +709,12 @@ static int __exfat_fill_super(struct super_block *sb,
> >> goto free_bh;
> >> }
> >>
> >> + ret = exfat_read_volume_label(sb);
> > It will affect mount time if volume label entry is located at the end.
> > So, we can read it in ioctl fslabel as I said above.
> Sounds good. I'll incorporate your changes, and those of
> yuezhang.mo@sony.com, and submit version 3 of the patch soon.
As Yuezhang pointed out, You need to consider the case where there
is no volume label entry. Some mkfs implementations, though not
Windows, don't create a volume entry. So, if FS_IOC_SETFSLABEL
doesn't find a volume label entry, it should either look for an empty entry,
or, if that's not available, allocate a cluster to get a entry.
Thanks for your work!
>
> >> + if (ret) {
> >> + exfat_err(sb, "failed to read volume label");
> >> + goto free_bh;
> >> + }
> >> +
> >> ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
> >> if (ret) {
> >> exfat_err(sb, "failed to scan clusters");
> >> --
> >> 2.50.1
> >>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-08-19 14:51 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-17 0:30 [PATCH v2 0/1] exfat: Add support for FS_IOC_{GET,SET}FSLABEL Ethan Ferguson
2025-08-17 0:30 ` [PATCH v2 1/1] " Ethan Ferguson
2025-08-19 1:45 ` Namjae Jeon
2025-08-19 13:22 ` [PATCH] " Ethan Ferguson
2025-08-19 14:51 ` Namjae Jeon
2025-08-19 5:32 ` [PATCH v2 1/1] " Yuezhang.Mo
2025-08-17 12:30 ` [PATCH v2 0/1] " Namjae Jeon
2025-08-17 14:32 ` [PATCH v2 1/1] " Ethan Ferguson
2025-08-18 5:52 ` Christoph Hellwig
2025-08-18 17:49 ` [PATCH v2 0/1] " Ethan Ferguson
2025-08-19 8:15 ` Christoph Hellwig
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).