Re: FAT32 statfs(2) inode usage

From: Stefan Esser <se_at_FreeBSD.org>
Date: Mon, 06 Mar 2023 11:46:59 UTC
Am 06.03.23 um 06:41 schrieb Ben Woods:
> Hi Everyone,
> 
> I'm using Netdata to monitor my FreeBSD systems, and it is reporting critical alerts that my UEFI system partition has run out of available inodes. Investigating a bit further, I realised that the FreeBSD df(1) utility is reporting the same, meaning this is a FreeBSD bug rather than a Netdata bug.
> 
> $ df -i /boot/efi
> Filesystem  512-blocks Used  Avail Capacity iused ifree %iused  Mounted on
> /dev/nvd0p1     532352 3680 528672     1%     512     0  100%   /boot/efi

Hi Ben,

even if you write FAT32 in the subject, I'd want to ask whether this really
is a FAT32 file system or rather FAT12 or FAT16?

The reason I ask is that there is no limit on the number of files (reported
as inodes) in the root directory of a FAT32 file system, but such a limit
does exist in FAT12/FAT16 and the default value happens to be 512 in case
of FAT16.

For FAT16 the exhaustion of root directory entries could be seen as an
issue similar to no free inodes in a Unix file system, but it would only
prevent the creation of new files at the root directory level.

It is highly unlikely that you run into that limit unless you do actually
store lots of files in the root directory, and then hitting that limit might
look similar to inode exhaustion on a Unix file system (freespace, but no
new files can be created).

> It looks like this stems from the following section of the msdosfs(5) driver code sys/fs/msdosfs/msdosfs_vfsops.c:
> ----------------------------
> static int
> msdosfs_statfs(struct mount *mp, struct statfs *sbp)
> {
> 	struct msdosfsmount *pmp;
> 
> 	pmp = VFSTOMSDOSFS(mp);
> 	sbp->f_bsize = pmp->pm_bpcluster;
> 	sbp->f_iosize = pmp->pm_bpcluster;
> 	sbp->f_blocks = pmp->pm_maxcluster + 1;
> 	sbp->f_bfree = pmp->pm_freeclustercount;
> 	sbp->f_bavail = pmp->pm_freeclustercount;
> 	sbp->f_files = pmp->pm_RootDirEnts;	/* XXX */
> 	sbp->f_ffree = 0;	/* what to put in here? */
> 	return (0);
> }
> ----------------------------
> 
> I think it might be more correct for msdosfs_vfsops.c to set the f_files variable to 0. Thoughts on this change?

The value of pmp->pm_RootDirEnts is initialized to bytes 17 and 18 of the
boot sector, and that is specified to hold the number of root directory
entries for FAT12/FAT16, but the constant "0" for FAT32.

If this was a standard compliant FAT32 file, f_files should already be 0.

> This would then cause the following df(1) code to report iused% as "-" rather than "100%":
> ----------------------------
> inodes = sfsp->f_files;
> used = inodes - sfsp->f_ffree;
> ...
> if (inodes == 0)
> 	xo_emit(" {:inodes-used-percent/    -}{U:} ");
> else {
> 	xo_emit(" {:inodes-used-percent/%4.0f}{U:%%} ",
> 		(double)used / (double)inodes * 100.0);
> }
> ----------------------------
> 
> This would then give an df(1) output which is consistent with GNU/Linux - see below test results from Debian 11:
> # df -i /mnt/fat32
> Filesystem     Inodes IUsed IFree IUse% Mounted on
> /dev/vdb1           0     0     0     - /mnt/fat32

Please check the file system type (FAT16 vs. FAT32).

The newfs_msdos command accepts a -e option to select a non-standard number
of root directory entries (up to some 65000, not sure whether 65535 would be
accepted by all implementations).

The newfs_msdos command correctly sets the number of root directory entries
to 0 for FAT32 (see line 379 of mkfs_msdos.c).

Therefore, I do not think that there is a need to change anything in the
file system code or utilities.

If you tend to fill up your FAT12/FAT16 root directory, then a warning about
that directory being filled is quite useful, since if there already are more
than 100 files it is likely that you are adding more and more files at the
root directory level. When working with sub-directories, you'll probably
never come near to that limit.

Regards, STefan