Re: [LTP] [PATCH v2] statvfs01: Convert to new LTP API

Petr Vorel
2022-12-02T09:20:42Z

Hi all,

...
> >> To reply myself, both glibc and musl defines:
> >> statvfs->f_namemax = statfs->f_namelen;

> >> TL;DR: 6 * 255 = 1530 due names being in UTF-8:

> >> Therefore looking into kernel sources for statfs->f_namelen:

> >> include/linux/nls.h
> >> #define NLS_MAX_CHARSET_SIZE 6 /* for UTF-8 */

> >> === exfat ===
> >> exfat/exfat_raw.h
> >> #define EXFAT_MAX_FILE_LEN 255

> >> exfat/super.c
> >> static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
> >> {
> >> ...
> >> /* Unicode utf16 255 characters */
> >> buf->f_namelen = EXFAT_MAX_FILE_LEN * NLS_MAX_CHARSET_SIZE;

> >> === vfat ===
> >> include/uapi/linux/msdos_fs.h
> >> #define FAT_LFN_LEN 255 /* maximum long name length */

> >> fat/inode.c
> >> static int fat_statfs(struct dentry *dentry, struct kstatfs *buf)
> >> {
> >> ...
> >> buf->f_namelen =
> >> (sbi->options.isvfat ? FAT_LFN_LEN : 12) * NLS_MAX_CHARSET_SIZE;

> >> => i.e. for vfat without long filename support it'd be 72.

> >> How about
> >> 1) don't skip exfat and vfat but just skip creating file with valid name?
> >> or

> > Sure, I think this method is better.

> Is it supposed to return the length in bytes or unicode 'characters'? If
> it's the later then things get really complicated so I guess it's bytes.
Yes.

TL;DR: I'd test vfat and exfat for valid filename with (f_namelen / 6) + 1.

> However BTRFS also supports unicode (and bigger file names in theory)
> and just reports 255. If you look at the BTRFS code comments, it says
> that they limited it to 255 because other things might break.

> So will creating a file with > 255 chars ever work, even if we use
> UTF-16 symbols?

Wrote some testing code (see below), vfat and exfat behaves differently:

$ gcc -Wall statvfs-test-ms.c -o statvfs-test-ms && ./statvfs-test-ms /tmp/vfat/
=== filesystem: vfat ===

file: test_ϴ_12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
strlen: 256, mblen: 255
buf.f_namelen: 1530

file: test_ϴ_12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_
strlen: 257, mblen: 256
creat() failed: File name too long

file: ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ
strlen: 510, mblen: 255
buf.f_namelen: 1530

Also NTFS over fuse behaves the same (allows 256 length), but (probably due
fuse) f_namelen is only 255:

=== filesystem: fuse ===

file: test_ϴ_12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
strlen: 256, mblen: 255
buf.f_namelen: 255

file: test_ϴ_12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678_
strlen: 257, mblen: 256
creat() failed: File name too long

file: ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ
strlen: 510, mblen: 255
buf.f_namelen: 255

NOTE: they both allow strlen 256 as max character multibyte length is in that
case just 255 (not counting terminating null byte \0 ?).

When testing on normal linux filesystems (btrfs, ext4, xfs, tmpfs, ...) they all
allow only strlen 255 and obviously does not work with multibyte (output on shortened
filenames):

=== filesystem: btrfs ===

file: test_ϴ_1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
strlen: 255, mblen: 254
buf.f_namelen: 255

file: test_ϴ_1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567_
strlen: 256, mblen: 255
creat() failed: File name too long

file: ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ
strlen: 508, mblen: 254
creat() failed: File name too long

Kind regards,
Petr

> In the meantime could we just read the data into a guarded buffer and
> check it's not all zero's or all one's (for e.g.)?

> >> 2) Add #define NLS_MAX_CHARSET_SIZE 6 and for vfat and exfat calculate
> >> length as: buf.f_namemax / NLS_MAX_CHARSET_SIZE - 1 ?

> >> Kind regards,
> >> Petr

#include <fcntl.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/statfs.h>
#include <unistd.h>

/* test requires C.utf8 locale */
#define LOCALE "C.utf8"

/* here for vfat, exfat, which allow 256, Linux filesystems (btrfs, ext4, tmpfs xfs, ...) allow only 255 */
#define MAX_FILENAME_PATH "test_ϴ_" \
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \
"123456789012345678901234567890123456789012345678"

#define TOO_LONG_FILENAME MAX_FILENAME_PATH "_"

/* here for vfat, exfat, which allow 256, Linux filesystems (btrfs, ext4, tmpfs xfs, ...) allow only 255 */
#define MAX_FILENAME_PATH_UTF16 \
"ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ" \
"ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ" \
"ϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴϴ"

/* man 2 statfs or kernel-source/include/linux/magic.h */
#define TST_BTRFS_MAGIC 0x9123683E
#define TST_NFS_MAGIC 0x6969
#define TST_RAMFS_MAGIC 0x858458f6
#define TST_TMPFS_MAGIC 0x01021994
#define TST_V9FS_MAGIC 0x01021997
#define TST_XFS_MAGIC 0x58465342
#define TST_EXT2_OLD_MAGIC 0xEF51
/* ext2, ext3, ext4 have the same magic number */
#define TST_EXT234_MAGIC 0xEF53
#define TST_MINIX_MAGIC 0x137F
#define TST_MINIX_MAGIC2 0x138F
#define TST_MINIX2_MAGIC 0x2468
#define TST_MINIX2_MAGIC2 0x2478
#define TST_MINIX3_MAGIC 0x4D5A
#define TST_UDF_MAGIC 0x15013346
#define TST_SYSV2_MAGIC 0x012FF7B6
#define TST_SYSV4_MAGIC 0x012FF7B5
#define TST_UFS_MAGIC 0x00011954
#define TST_UFS2_MAGIC 0x19540119
#define TST_F2FS_MAGIC 0xF2F52010
#define TST_NILFS_MAGIC 0x3434
#define TST_EXOFS_MAGIC 0x5DF5
#define TST_OVERLAYFS_MAGIC 0x794c7630
#define TST_FUSE_MAGIC 0x65735546
#define TST_VFAT_MAGIC 0x4d44 /* AKA MSDOS */
#define TST_EXFAT_MAGIC 0x2011BAB0UL

long tst_fs_type(const char *path)
{
struct statfs sbuf;

if (statfs(path, &sbuf)) {
perror("statfs");
exit(EXIT_FAILURE);
return 0;
}

return sbuf.f_type;
}

const char *tst_fs_type_name(long f_type)
{
switch (f_type) {
case TST_TMPFS_MAGIC:
return "tmpfs";
case TST_NFS_MAGIC:
return "nfs";
case TST_V9FS_MAGIC:
return "9p";
case TST_RAMFS_MAGIC:
return "ramfs";
case TST_BTRFS_MAGIC:
return "btrfs";
case TST_XFS_MAGIC:
return "xfs";
case TST_EXT2_OLD_MAGIC:
return "ext2";
case TST_EXT234_MAGIC:
return "ext2/ext3/ext4";
case TST_MINIX_MAGIC:
case TST_MINIX_MAGIC2:
case TST_MINIX2_MAGIC:
case TST_MINIX2_MAGIC2:
case TST_MINIX3_MAGIC:
return "minix";
case TST_UDF_MAGIC:
return "udf";
case TST_SYSV2_MAGIC:
case TST_SYSV4_MAGIC:
return "sysv";
case TST_UFS_MAGIC:
case TST_UFS2_MAGIC:
return "ufs";
case TST_F2FS_MAGIC:
return "f2fs";
case TST_NILFS_MAGIC:
return "nilfs";
case TST_EXOFS_MAGIC:
return "exofs";
case TST_OVERLAYFS_MAGIC:
return "overlayfs";
case TST_FUSE_MAGIC:
return "fuse";
case TST_VFAT_MAGIC:
return "vfat";
case TST_EXFAT_MAGIC:
return "exfat";
default:
return "unknown";
}
}

int creat_file_print_len(char *str)
{
int fd;
struct statfs buf;

printf("\nfile: %s\nstrlen: %zu, mblen: %zu\n", str,
strlen(str), mbstowcs(NULL, str, 0));

if ((fd = creat(str, 0666)) < 0) {
perror("creat() failed");
return 1;
}

close(fd);

if (statfs(str, &buf) < 0) {
perror("statfs() failed");
return 1;
}

printf("buf.f_namelen: %zu\n", buf.f_namelen);

return 0;
}

int main(int argc, char *argv[]) {

int ret = 0;

if (argc < 2) {
fprintf(stderr, "Usage: %s DIR\n", argv[0]);
exit(EXIT_FAILURE);
}

if (chdir(argv[1]) > 0) {
perror("chdir() failed");
exit(EXIT_FAILURE);
}
printf("=== filesystem: %s ===\n", tst_fs_type_name(tst_fs_type(".")));

if (setlocale(LC_ALL, LOCALE) == NULL) {
perror("setlocale");
exit(EXIT_FAILURE);
}

ret |= creat_file_print_len(MAX_FILENAME_PATH);
ret |= creat_file_print_len(TOO_LONG_FILENAME);
ret |= creat_file_print_len(MAX_FILENAME_PATH_UTF16);

return ret;
}