#exFAT
The concatenated file name has the same set of illegal characters as
other FAT-based file systems (see Table 31).
#FAT
...
Leading and trailing spaces in a long name are ignored.
Leading and embedded periods are allowed in a name and are stored in
the long name. Trailing periods are ignored.
Note: Leading and trailing space ' ' characters are currently retained
by Linux kernel exfat, in conflict with the above specification.
Some implementations, such as fuse-exfat, don't perform path trailer
removal. When mounting images which contain trailing-dot paths, these
paths are unreachable, e.g.:
+ mount.exfat-fuse /dev/zram0 /mnt/test/
FUSE exfat 1.3.0
+ cd /mnt/test/
+ touch fuse_created_dots... ' fuse_created_spaces '
+ ls -l
total 0
-rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
+ cd /
+ umount /mnt/test/
+ mount -t exfat /dev/zram0 /mnt/test
+ cd /mnt/test
+ ls -l
ls: cannot access 'fuse_created_dots...': No such file or directory
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-????????? ? ? ? ? ? fuse_created_dots...
+ touch kexfat_created_dots... ' kexfat_created_spaces '
+ ls -l
ls: cannot access 'fuse_created_dots...': No such file or directory
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
-????????? ? ? ? ? ? fuse_created_dots...
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
+ cd /
+ umount /mnt/test/
With this change, the "keep_last_dots" mount option can be used to access
paths with trailing periods and disallow creating files with names with
trailing periods. E.g. continuing from the previous example:
+ mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
+ cd /mnt/test
+ ls -l
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots
Link: https://bugzilla.suse.com/show_bug.cgi?id=1188964
Link: https://lore.kernel.org/linux-fsdevel/003b01d755e4$31fb0d80$95f12880$
@samsung.com/
Link: https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
Suggested-by: Takashi Iwai <[email protected]>
Signed-off-by: Vasant Karasulli <[email protected]>
Co-developed-by: David Disseldorp <[email protected]>
Signed-off-by: David Disseldorp <[email protected]>
---
fs/exfat/namei.c | 50 ++++++++++++++++++++++++++++++++++--------------
1 file changed, 36 insertions(+), 14 deletions(-)
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index af4eb39cc0c3..a4f8010fbd38 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -65,11 +65,14 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags)
return ret;
}
-/* returns the length of a struct qstr, ignoring trailing dots */
-static unsigned int exfat_striptail_len(unsigned int len, const char *name)
+/* returns the length of a struct qstr, ignoring trailing dots if necessary */
+static unsigned int exfat_striptail_len(unsigned int len, const char *name,
+ bool keep_last_dots)
{
- while (len && name[len - 1] == '.')
- len--;
+ if (!keep_last_dots) {
+ while (len && name[len - 1] == '.')
+ len--;
+ }
return len;
}
@@ -83,7 +86,8 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr)
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
const unsigned char *name = qstr->name;
- unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
+ unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
wchar_t c;
@@ -104,8 +108,10 @@ static int exfat_d_cmp(const struct dentry *dentry, unsigned int len,
{
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
- unsigned int alen = exfat_striptail_len(name->len, name->name);
- unsigned int blen = exfat_striptail_len(len, str);
+ unsigned int alen = exfat_striptail_len(name->len, name->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
+ unsigned int blen = exfat_striptail_len(len, str,
+ EXFAT_SB(sb)->options.keep_last_dots);
wchar_t c1, c2;
int charlen, i;
@@ -136,7 +142,8 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct super_block *sb = dentry->d_sb;
const unsigned char *name = qstr->name;
- unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
+ unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
unicode_t u;
@@ -161,8 +168,11 @@ static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len,
const char *str, const struct qstr *name)
{
struct super_block *sb = dentry->d_sb;
- unsigned int alen = exfat_striptail_len(name->len, name->name);
- unsigned int blen = exfat_striptail_len(len, str);
+ unsigned int alen = exfat_striptail_len(name->len, name->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
+ unsigned int blen = exfat_striptail_len(len, str,
+ EXFAT_SB(sb)->options.keep_last_dots);
+
unicode_t u_a, u_b;
int charlen, i;
@@ -416,13 +426,25 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
+ int pathlen = strlen(path);
- /* strip all trailing periods */
- namelen = exfat_striptail_len(strlen(path), path);
+ /*
+ * get the length of the pathname excluding
+ * trailing periods, if any.
+ */
+ namelen = exfat_striptail_len(pathlen, path, false);
+ if (EXFAT_SB(sb)->options.keep_last_dots) {
+ /*
+ * Do not allow the creation of files with names
+ * ending with period(s).
+ */
+ if (!lookup && (namelen < pathlen))
+ return -EINVAL;
+ namelen = pathlen;
+ }
if (!namelen)
return -ENOENT;
-
- if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
+ if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
return -ENAMETOOLONG;
/*
--
2.32.0
On Fr 11-03-22 18:03:57, Namjae Jeon wrote:
> 2022-03-11 5:06 GMT+09:00, David Disseldorp <[email protected]>:
> > Thanks for reworking these changes, Vasant.
> >
> > Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
> > (including a "exfat: " prefix), with the details moved into the commit
> > message body...
> >
> > On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:
> >
> >> #exFAT
> >> The concatenated file name has the same set of illegal characters as
> >> other FAT-based file systems (see Table 31).
> >>
> >> #FAT
> >> ...
> >> Leading and trailing spaces in a long name are ignored.
> >> Leading and embedded periods are allowed in a name and are stored in
> >> the long name. Trailing periods are ignored.
> >>
> >> Note: Leading and trailing space ' ' characters are currently retained
> >> by Linux kernel exfat, in conflict with the above specification.
> >
> > I think it makes sense to mention your findings from the Windows tests
> > here. E.g. "Windows 10 also retains leading and trailing space
> > characters".
> Windows 10 do also strip them. So you can make another patch to strip
> it as well as trailing periods.
Actually I found contradicting behavior between Window 10 File Explorer and
Commandline. Commandline seems to strip trailing spaces, but File Explorer
doesn't.
> >
> >> Some implementations, such as fuse-exfat, don't perform path trailer
> >> removal. When mounting images which contain trailing-dot paths, these
> >> paths are unreachable, e.g.:
> >>
> >> + mount.exfat-fuse /dev/zram0 /mnt/test/
> >> FUSE exfat 1.3.0
> >> + cd /mnt/test/
> >> + touch fuse_created_dots... ' fuse_created_spaces '
> >> + ls -l
> >> total 0
> >> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> >> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
> >> + cd /
> >> + umount /mnt/test/
> >> + mount -t exfat /dev/zram0 /mnt/test
> >> + cd /mnt/test
> >> + ls -l
> >> ls: cannot access 'fuse_created_dots...': No such file or directory
> >> total 0
> >> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> >> -????????? ? ? ? ? ? fuse_created_dots...
> >> + touch kexfat_created_dots... ' kexfat_created_spaces '
> >> + ls -l
> >> ls: cannot access 'fuse_created_dots...': No such file or directory
> >> total 0
> >> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> >> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
> >> -????????? ? ? ? ? ? fuse_created_dots...
> >> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
> >> + cd /
> >> + umount /mnt/test/
> >>
> >> With this change, the "keep_last_dots" mount option can be used to access
> >> paths with trailing periods and disallow creating files with names with
> >> trailing periods. E.g. continuing from the previous example:
> >>
> >> + mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
> >> + cd /mnt/test
> >> + ls -l
> >> total 0
> >> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
> >> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
> >> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
> >> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots
> >
> > It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.
> >
> > + echo > kexfat_created_dots_again...
> > sh: kexfat_created_dots_again...: Invalid argument
> >
> > @Namjae: not sure whether this is what you had in mind for preventing
> > creation of invalid paths. What's your preference?
> Look like what I wanted.
That's great. I will resend the patch after modifying the commit message
as David suggested.
>
> Thanks!
> >
> > Cheers, David
> >
Thanks,
Vasant Karasulli
Kernel generalist
http://www.suse.com<http://www.suse.com>
[https://www.suse.com/assets/img/social-platforms-suse-logo.png]<http://www.suse.com/>
SUSE - Open Source Solutions for Enterprise Servers & Cloud<http://www.suse.com/>
Modernize your infrastructure with SUSE Linux Enterprise servers, cloud technology for IaaS, and SUSE's software-defined storage.
http://www.suse.com
Thanks for reworking these changes, Vasant.
Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
(including a "exfat: " prefix), with the details moved into the commit
message body...
On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:
> #exFAT
> The concatenated file name has the same set of illegal characters as
> other FAT-based file systems (see Table 31).
>
> #FAT
> ...
> Leading and trailing spaces in a long name are ignored.
> Leading and embedded periods are allowed in a name and are stored in
> the long name. Trailing periods are ignored.
>
> Note: Leading and trailing space ' ' characters are currently retained
> by Linux kernel exfat, in conflict with the above specification.
I think it makes sense to mention your findings from the Windows tests
here. E.g. "Windows 10 also retains leading and trailing space
characters".
> Some implementations, such as fuse-exfat, don't perform path trailer
> removal. When mounting images which contain trailing-dot paths, these
> paths are unreachable, e.g.:
>
> + mount.exfat-fuse /dev/zram0 /mnt/test/
> FUSE exfat 1.3.0
> + cd /mnt/test/
> + touch fuse_created_dots... ' fuse_created_spaces '
> + ls -l
> total 0
> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
> + cd /
> + umount /mnt/test/
> + mount -t exfat /dev/zram0 /mnt/test
> + cd /mnt/test
> + ls -l
> ls: cannot access 'fuse_created_dots...': No such file or directory
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -????????? ? ? ? ? ? fuse_created_dots...
> + touch kexfat_created_dots... ' kexfat_created_spaces '
> + ls -l
> ls: cannot access 'fuse_created_dots...': No such file or directory
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
> -????????? ? ? ? ? ? fuse_created_dots...
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
> + cd /
> + umount /mnt/test/
>
> With this change, the "keep_last_dots" mount option can be used to access
> paths with trailing periods and disallow creating files with names with
> trailing periods. E.g. continuing from the previous example:
>
> + mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
> + cd /mnt/test
> + ls -l
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots
It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.
+ echo > kexfat_created_dots_again...
sh: kexfat_created_dots_again...: Invalid argument
@Namjae: not sure whether this is what you had in mind for preventing
creation of invalid paths. What's your preference?
Cheers, David
2022-03-11 5:06 GMT+09:00, David Disseldorp <[email protected]>:
> Thanks for reworking these changes, Vasant.
>
> Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
> (including a "exfat: " prefix), with the details moved into the commit
> message body...
>
> On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:
>
>> #exFAT
>> The concatenated file name has the same set of illegal characters as
>> other FAT-based file systems (see Table 31).
>>
>> #FAT
>> ...
>> Leading and trailing spaces in a long name are ignored.
>> Leading and embedded periods are allowed in a name and are stored in
>> the long name. Trailing periods are ignored.
>>
>> Note: Leading and trailing space ' ' characters are currently retained
>> by Linux kernel exfat, in conflict with the above specification.
>
> I think it makes sense to mention your findings from the Windows tests
> here. E.g. "Windows 10 also retains leading and trailing space
> characters".
Windows 10 do also strip them. So you can make another patch to strip
it as well as trailing periods.
>
>> Some implementations, such as fuse-exfat, don't perform path trailer
>> removal. When mounting images which contain trailing-dot paths, these
>> paths are unreachable, e.g.:
>>
>> + mount.exfat-fuse /dev/zram0 /mnt/test/
>> FUSE exfat 1.3.0
>> + cd /mnt/test/
>> + touch fuse_created_dots... ' fuse_created_spaces '
>> + ls -l
>> total 0
>> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
>> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
>> + cd /
>> + umount /mnt/test/
>> + mount -t exfat /dev/zram0 /mnt/test
>> + cd /mnt/test
>> + ls -l
>> ls: cannot access 'fuse_created_dots...': No such file or directory
>> total 0
>> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
>> -????????? ? ? ? ? ? fuse_created_dots...
>> + touch kexfat_created_dots... ' kexfat_created_spaces '
>> + ls -l
>> ls: cannot access 'fuse_created_dots...': No such file or directory
>> total 0
>> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
>> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
>> -????????? ? ? ? ? ? fuse_created_dots...
>> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
>> + cd /
>> + umount /mnt/test/
>>
>> With this change, the "keep_last_dots" mount option can be used to access
>> paths with trailing periods and disallow creating files with names with
>> trailing periods. E.g. continuing from the previous example:
>>
>> + mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
>> + cd /mnt/test
>> + ls -l
>> total 0
>> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
>> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
>> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
>> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots
>
> It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.
>
> + echo > kexfat_created_dots_again...
> sh: kexfat_created_dots_again...: Invalid argument
>
> @Namjae: not sure whether this is what you had in mind for preventing
> creation of invalid paths. What's your preference?
Look like what I wanted.
Thanks!
>
> Cheers, David
>
Hi, Vasant Karasulli.
> > > I think it makes sense to mention your findings from the Windows
> > > tests here. E.g. "Windows 10 also retains leading and trailing space
> > > characters".
> > Windows 10 do also strip them. So you can make another patch to strip
> > it as well as trailing periods.
> Actually I found contradicting behavior between Window 10 File Explorer and Commandline. Commandline seems to strip
> trailing spaces, but File Explorer doesn't.
The exfat specification specifies an invalid character set, but there are no restrictions on the use of leading or trailing white-space or dots.
Even if the filename has trailing-dot as shown below, it conforms to the exfat specification and can be created on Windows.
"a"
"a."
"a.."
These are treated as "a" in the current implementation of linix-exfat, so the intended file cannot be accessed.
The specified filename should not be modified to comply with the exfat specification.
Therefore, exfat_striptail_len() should not be used.
Note:
Windows explorer removes trailing white-space and dots, but not the behavior of the filesystem.
Also, you can create a trailing-dot filename by quoting it on the command line.
BR
T.Kohada
Hi T.Kohada,
On Mon, 14 Mar 2022 03:52:08 +0000, [email protected] wrote:
> Hi, Vasant Karasulli.
>
> > > > I think it makes sense to mention your findings from the Windows
> > > > tests here. E.g. "Windows 10 also retains leading and trailing space
> > > > characters".
> > > Windows 10 do also strip them. So you can make another patch to strip
> > > it as well as trailing periods.
> > Actually I found contradicting behavior between Window 10 File Explorer and Commandline. Commandline seems to strip
> > trailing spaces, but File Explorer doesn't.
>
> The exfat specification specifies an invalid character set, but there are no restrictions on the use of leading or trailing white-space or dots.
> Even if the filename has trailing-dot as shown below, it conforms to the exfat specification and can be created on Windows.
> "a"
> "a."
> "a.."
> These are treated as "a" in the current implementation of linix-exfat, so the intended file cannot be accessed.
> The specified filename should not be modified to comply with the exfat specification.
> Therefore, exfat_striptail_len() should not be used.
>
> Note:
> Windows explorer removes trailing white-space and dots, but not the behavior of the filesystem.
> Also, you can create a trailing-dot filename by quoting it on the command line.
Please explain how you came to that conclusion.
I did some further tests using the win32 CopyFile() API directly[1] on
Windows10 and observe that both trailing periods and trailing spaces are
trimmed for an exfat destination path.
Cheers, David
1: calling win32 CopyFile() from powershell
https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-1/
Hi, David.
Thank you for confirming the actual behavior.
> Please explain how you came to that conclusion.
> I did some further tests using the win32 CopyFile() API directly[1] on
> Windows10 and observe that both trailing periods and trailing spaces are
> trimmed for an exfat destination path.
I'm using the native api to investigate the behavior of the filesystem on windows.
This time, I verified it using NtCreateFile().
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntcreatefile
Cygwin and some tools can also create filenames with a trailing dot.
BR
T.Kohada