Skip to content

Commit 9ec784b

Browse files
Vasant Karasullinamjaejeon
authored andcommitted
exfat: allow access to paths with trailing dots
The Linux kernel exfat driver currently unconditionally strips trailing periods '.' from path components. This isdone intentionally, loosely following Windows behaviour and specifications which state: #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. On Windows 10, trailing and leading space ' ' characters are stripped from the filenames. 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/ This commit adds "keep_last_dots" mount option that controls whether or not trailing periods '.' are stripped from path components during file lookup or file creation. This 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 + echo > kexfat_created_dots_again... sh: kexfat_created_dots_again...: Invalid argument 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 <tiwai@suse.de> Signed-off-by: Vasant Karasulli <vkarasulli@suse.de> Co-developed-by: David Disseldorp <ddiss@suse.de> Signed-off-by: David Disseldorp <ddiss@suse.de> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
1 parent f443e37 commit 9ec784b

3 files changed

Lines changed: 45 additions & 15 deletions

File tree

fs/exfat/exfat_fs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ struct exfat_mount_options {
203203
/* on error: continue, panic, remount-ro */
204204
enum exfat_error_mode errors;
205205
unsigned utf8:1, /* Use of UTF-8 character set */
206-
discard:1; /* Issue discard requests on deletions */
206+
discard:1, /* Issue discard requests on deletions */
207+
keep_last_dots:1; /* Keep trailing periods in paths */
207208
int time_offset; /* Offset of timestamps from UTC (in minutes) */
208209
};
209210

fs/exfat/namei.c

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags)
6565
return ret;
6666
}
6767

68-
/* returns the length of a struct qstr, ignoring trailing dots */
69-
static unsigned int exfat_striptail_len(unsigned int len, const char *name)
68+
/* returns the length of a struct qstr, ignoring trailing dots if necessary */
69+
static unsigned int exfat_striptail_len(unsigned int len, const char *name,
70+
bool keep_last_dots)
7071
{
71-
while (len && name[len - 1] == '.')
72-
len--;
72+
if (!keep_last_dots) {
73+
while (len && name[len - 1] == '.')
74+
len--;
75+
}
7376
return len;
7477
}
7578

@@ -83,7 +86,8 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr)
8386
struct super_block *sb = dentry->d_sb;
8487
struct nls_table *t = EXFAT_SB(sb)->nls_io;
8588
const unsigned char *name = qstr->name;
86-
unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
89+
unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
90+
EXFAT_SB(sb)->options.keep_last_dots);
8791
unsigned long hash = init_name_hash(dentry);
8892
int i, charlen;
8993
wchar_t c;
@@ -104,8 +108,10 @@ static int exfat_d_cmp(const struct dentry *dentry, unsigned int len,
104108
{
105109
struct super_block *sb = dentry->d_sb;
106110
struct nls_table *t = EXFAT_SB(sb)->nls_io;
107-
unsigned int alen = exfat_striptail_len(name->len, name->name);
108-
unsigned int blen = exfat_striptail_len(len, str);
111+
unsigned int alen = exfat_striptail_len(name->len, name->name,
112+
EXFAT_SB(sb)->options.keep_last_dots);
113+
unsigned int blen = exfat_striptail_len(len, str,
114+
EXFAT_SB(sb)->options.keep_last_dots);
109115
wchar_t c1, c2;
110116
int charlen, i;
111117

@@ -136,7 +142,8 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr)
136142
{
137143
struct super_block *sb = dentry->d_sb;
138144
const unsigned char *name = qstr->name;
139-
unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
145+
unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
146+
EXFAT_SB(sb)->options.keep_last_dots);
140147
unsigned long hash = init_name_hash(dentry);
141148
int i, charlen;
142149
unicode_t u;
@@ -161,8 +168,11 @@ static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len,
161168
const char *str, const struct qstr *name)
162169
{
163170
struct super_block *sb = dentry->d_sb;
164-
unsigned int alen = exfat_striptail_len(name->len, name->name);
165-
unsigned int blen = exfat_striptail_len(len, str);
171+
unsigned int alen = exfat_striptail_len(name->len, name->name,
172+
EXFAT_SB(sb)->options.keep_last_dots);
173+
unsigned int blen = exfat_striptail_len(len, str,
174+
EXFAT_SB(sb)->options.keep_last_dots);
175+
166176
unicode_t u_a, u_b;
167177
int charlen, i;
168178

@@ -416,13 +426,25 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
416426
struct super_block *sb = inode->i_sb;
417427
struct exfat_sb_info *sbi = EXFAT_SB(sb);
418428
struct exfat_inode_info *ei = EXFAT_I(inode);
429+
int pathlen = strlen(path);
419430

420-
/* strip all trailing periods */
421-
namelen = exfat_striptail_len(strlen(path), path);
431+
/*
432+
* get the length of the pathname excluding
433+
* trailing periods, if any.
434+
*/
435+
namelen = exfat_striptail_len(pathlen, path, false);
436+
if (EXFAT_SB(sb)->options.keep_last_dots) {
437+
/*
438+
* Do not allow the creation of files with names
439+
* ending with period(s).
440+
*/
441+
if (!lookup && (namelen < pathlen))
442+
return -EINVAL;
443+
namelen = pathlen;
444+
}
422445
if (!namelen)
423446
return -ENOENT;
424-
425-
if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
447+
if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
426448
return -ENAMETOOLONG;
427449

428450
/*

fs/exfat/super.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root)
174174
seq_puts(m, ",errors=remount-ro");
175175
if (opts->discard)
176176
seq_puts(m, ",discard");
177+
if (opts->keep_last_dots)
178+
seq_puts(m, ",keep_last_dots");
177179
if (opts->time_offset)
178180
seq_printf(m, ",time_offset=%d", opts->time_offset);
179181
return 0;
@@ -217,6 +219,7 @@ enum {
217219
Opt_charset,
218220
Opt_errors,
219221
Opt_discard,
222+
Opt_keep_last_dots,
220223
Opt_time_offset,
221224

222225
/* Deprecated options */
@@ -243,6 +246,7 @@ static const struct fs_parameter_spec exfat_parameters[] = {
243246
fsparam_string("iocharset", Opt_charset),
244247
fsparam_enum("errors", Opt_errors, exfat_param_enums),
245248
fsparam_flag("discard", Opt_discard),
249+
fsparam_flag("keep_last_dots", Opt_keep_last_dots),
246250
fsparam_s32("time_offset", Opt_time_offset),
247251
__fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated,
248252
NULL),
@@ -297,6 +301,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param)
297301
case Opt_discard:
298302
opts->discard = 1;
299303
break;
304+
case Opt_keep_last_dots:
305+
opts->keep_last_dots = 1;
306+
break;
300307
case Opt_time_offset:
301308
/*
302309
* Make the limit 24 just in case someone invents something

0 commit comments

Comments
 (0)