在XFS文件系统上实现针对目录的配额限制

在Linux系统上支持对用户以及对用户组设置磁盘配额的文件系统很多,常见的Ext4文件系统对配额的支持就很好,但是如果要针对某个目录进行配额限制的话,就比较难办了。
至少在Ext4文件系统上并没有什么好的办法,有些比较hack的办法,比如使用 fuse 挂载一个目录,并在这个文件系统里实现目录级别的配额,虽然可以实现,但是问题就是 fuse 的性能要差很多。
然后调查了一下XFS,XFS文件系统支持 Project Quota 功能,通过该特性,可以支持目录级别的配额限制。

要使用Project Quota 功能,需要在挂载时指定-o prjquota参数,并且这个参数还不能和usrquotagrpquota一起使用,也就是说,如果开启Project Quota就无法使用针对用户和用户组的配额限制。

[root@test ~]# mount -o prjquota /dev/sdb1 /tmp/data0

这里把/dev/sdb1挂载到/tmp/data0。挂载之后,就可以使用xfs_quota 工具对该分区的quota进行操作了。

[root@test ~]# xfs_quota -x -c report /tmp/data0
Project quota on /tmp/data0 (/dev/sdb1)
                               Blocks
Project ID       Used       Soft       Hard    Warn/Grace
---------- --------------------------------------------------
#0                  4          0          0     00 [--------]

使用-x开启专家模式,-c report执行report命令,后面跟上挂载点,这样可以列出所有的Project ID以及对应的配额使用情况。

如果要限制某个目录的配额,首先需要保证该目录存在,然后调用project命令创建一个project,再调用limit命令设置配额。

[root@test ~]# mkdir /tmp/data0/100MB   # 创建对应文件夹
[root@test ~]# xfs_quota -x -c 'project -s -p /tmp/data0/100MB 1'   # 为文件夹分配ProjectID 1
Setting up project 1 (path /tmp/data0/100MB)...
Processed 1 (/etc/projects and cmdline) paths for project 1 with recursion depth infinite (-1).
[root@test ~]# xfs_quota -x -c 'limit -p bhard=100m 1' /tmp/data0/  # 限制Project 1的配额为100MB
[root@test ~]# xfs_quota -x -c report /tmp/data0
Project quota on /tmp/data0 (/dev/sdb1)
                               Blocks
Project ID       Used       Soft       Hard    Warn/Grace
---------- --------------------------------------------------
#0                  4          0          0     00 [--------]
#1                  0          0     102400     00 [--------]

[root@test ~]# dd if=/dev/zero of=/tmp/data0/100MB/test bs=1M count=101
dd: error writing '/tmp/data0/100MB/test': No space left on device  # 配额生效
101+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 1.01324 s, 103 MB/s

[root@test ~]# xfs_quota -x -c report /tmp/data0                       
Project quota on /tmp/data0 (/dev/sdb1)
                               Blocks                     
Project ID       Used       Soft       Hard    Warn/Grace     
---------- -------------------------------------------------- 
#0                  4          0          0     00 [--------]
#1             102400          0     102400     00 [--------]

[root@test ~]# xfs_quota -c 'quota -p 1' /tmp/data0     #查询某个project的配额使用情况
Disk quotas for Project #1 (1)
Filesystem              Blocks      Quota      Limit  Warn/Time      Mounted on
/dev/loop0              102400          0     102400   00 [--------] /tmp/data0

看到已经设置好对应的配额。并能正常工作。其中需要注意的。使用 -c quota 查询某个project的配额使用情况时,只有当使用的空间超过0才会被正常显示,否则是无法通过该方式查询的。

上面的测试是在本地的机器上做测试的,所有的功能都能正常工作,但是在线上机器上做测试的时候,发现可以正常设置quota,并且quota也能正常生效,但是在调用 report 命令的时候,线上机器永远只会显示Project ID为0的
配额情况,而不会显示其他Project的配额,需要说明的是本地的系统是ArchLinx,使用4.9版本内核,线上系统使用CentOS 7,使用3.10的内核。因为内核版本不一样,不排除是内核版本导致的问题,但是也不能确定就是内核问题。

于是决定看一下xfs_quota这个工具的代码,看一下 report 这个命令是如何实现的。xfs_quota这个工具属于xfsprogs这个包,所以在https://www.kernel.org/pub/linux/utils/fs/xfs/xfsprogs/xfsprogs-4.9.0.tar.xz下载了4.9版本的源代码。
发现在 quota/report.c 文件中,report_mount函数有这样一段代码:

...
	if (flags & GETNEXTQUOTA_FLAG)
		cmd = XFS_GETNEXTQUOTA;
	else
		cmd = XFS_GETQUOTA;

	/* Fall back silently if XFS_GETNEXTQUOTA fails, warn on XFS_GETQUOTA*/
	if (xfsquotactl(cmd, dev, type, id, (void *)&d) < 0) {
		if (errno != ENOENT && errno != ENOSYS && errno != ESRCH &&
		    cmd == XFS_GETQUOTA)
			perror("XFS_GETQUOTA");
		return 0;
	}
...

看到注释,尝试使用XFS_GETNEXTQUOTA操作失败后,自动fall back到XFS_GETQUOTA,会不会是这个问题?尝试一下,如果XFS_GETNEXTQUOTA失败,直接打印错误信息。
将代码修改成:

if (xfsquotactl(cmd, dev, type, id, (void *)&d) < 0) {
       printf("not support XFS_GETNEXTQUOTA\n");
	if (errno != ENOENT && errno != ENOSYS && errno != ESRCH &&
	    cmd == XFS_GETQUOTA)
		perror("XFS_GETQUOTA");
	return 0;
}

编译后运行一下,果然在线上机器上打印出not support XFS_GETNEXTQUOTA,说明CentOS 7的3.10内核不支持XFS_GETNEXTQUOTA这个操作。

然后就去翻了一下内核代码,发现了这个提交 wire up Q_XGETNEXTQUOTA / get_nextdqblk,这个操作应该是在Linux 4.6版本引入的,所以CentOS 7的3.10内核就不支持该操作了。
不过没有这个特性,也不会影响实际的Quota功能,所以线上可以正常使用。