Linux: Using xfs project quotas to limit capacity within a subdirectory

XFS is a journaled filesystem that has excellent parallel performance, and is licensed under the GPL which means it has been included in many Linux distributions.

One of the features of XFS is the ability to enforce quotas based on user, group, and project.  In this article, I will show how to assign filesize quotas to subdirectories within an XFS filesystem using the project quota.

This can be beneficial for managing user directories, application data that partitions among folders , or throttling the size of Docker volume containers.

Goal

In this article, I will show you how you can take a 32Mb xfs filestore at “/data/volumes/xfs32m”, and configure quotas on these subdirectories:

  • /data/volumes/xfs32m/5m – quota of 5Mb
  • /data/volumes/xfs32m/10m – quota of 10Mb

Prerequisite

Before applying project quotas, first you need to have a mounted XFS filesystem.

Option 1: XFS filestore on host

If you already have an existing, mounted xfs filestore on your host system then you can simply use that.  Or if you want to shrink one of your non-boot device partitions, you can insert a new partition and format it using xfs.

If the xfs filesystem is your boot filesystem, append rootflags to GRUB_CMDLINE_LINUX_DEFAULT, update grub, and reboot:

rootflags=uquota,pquota

If the xfs filesystem is not your boot filesystem, then make sure the options column in the file “/etc/fstab” include “uquota,pquota” like below.

UUID=.... /home xfs defaults,uquota,pquota 0 0

If successful, after reboot the output of mount should show “prjquota” for your filesystem.

$ mount | grep quota

Options 2: XFS disk image on host

But if you want a quick test of xfs partitions without affecting your current system, probably the easiest path is to use a disk image and loopback device to mount a quick xfs filesystem.  This will allow you to play with these concepts, then tear it down in the end.

Follow the steps in my article, “Mounting a loopback xfs filesystem“, the section “Mounting an ext4 loopback device”.  In the end, you should have a mounted xfs filesystem at “/data/volumes/xfs32m”.

Make sure this directory is mounted and the “prjquota” option is specified.

$ mount | grep xfs32m

/data/volumes/xfs.32M on /data/volumes/xfs32m type xfs (rw,relatime,attr2,inode64,prjquota)

And that results are returned from the quota state, if this returns nothing, then the mount may not have been done with “-o prjquota”.

$ sudo xfs_quota -x -c state

User quota state on /data/volumes/xfs32m (/dev/loop1)
  Accounting: OFF
  Enforcement: OFF
  Inode: N/A
Group quota state on /data/volumes/xfs32m (/dev/loop1)
  Accounting: OFF
  Enforcement: OFF
  Inode: N/A
Project quota state on /data/volumes/xfs32m (/dev/loop1)
  Accounting: ON
  Enforcement: ON
  Inode: #100 (3 blocks, 3 extents)
Blocks grace time: [7 days]
Inodes grace time: [7 days]
Realtime Blocks grace time: [7 days]

Create Quotas

First, create the directories:

cd /data/volumes/xfs32m

# create subdirectories that will have quota applied
sudo mkdir -p /data/volumes/xfs32m/5m
sudo mkdir -p /data/volumes/xfs32m/10m

Then create the quota projects:

# initialize project, id=100
$ sudo xfs_quota -x -c 'project -s -p /data/volumes/xfs32m/5m 100' /data/volumes/xfs32m

Setting up project 100 (path /data/volumes/xfs32m/5m)...
Processed 1 (/etc/projects and cmdline) paths for project 100 with recursion depth infinite (-1).


# initialize project, id=200
$ sudo xfs_quota -x -c 'project -s -p /data/volumes/xfs32m/10m 200' /data/volumes/xfs32m

Setting up project 200 (path /data/volumes/xfs32m/10m)...
Processed 1 (/etc/projects and cmdline) paths for project 200 with recursion depth infinite (-1).

Notice that we don’t need to add entries to “/etc/projects” since we specify “-p” in the project initialization like above.  This ties the project id to the full path we are applying the quota unto.

Then set the quotas:

# set a 5M quota on project, id=100
sudo xfs_quota -x -c 'limit -p bsoft=5m bhard=5m 100' /data/volumes/xfs32m

# set a 10M quota on project, id=200 
sudo xfs_quota -x -c 'limit -p bsoft=10m bhard=10m 200' /data/volumes/xfs32m

Notice that we do not specify the path of the subdirectory getting the quota applied, just the mount path and the project id (which is how the subdirectory path is looked up).

If you get ‘Function not implemented’ error messages when setting these quotas, then make sure the mount command of the disk image was run with “-o pquota”.  Or if using an xfs partition from the host, then check the grub configuration (if boot partition) or “/etc/fstab” if non-boot partition as described in the Prerequisite section above.

Quota Report

Running the quota report should now show the project Ids and their 5M and 10M quotas.

$ sudo xfs_quota -x -c 'report -h' /data/volumes/xfs32m

Project quota on /data/volumes/xfs32m (/dev/loop1)
                        Blocks              
Project ID   Used   Soft   Hard Warn/Grace   
---------- --------------------------------- 
#0              0      0      0  00 [------]
#100            0     5M     5M  00 [------]
#200            0    10M    10M  00 [------]

Testing Quota

Attempting to create a file named “test.txt” in each folder, its size increasing 1M each iteration of the loop should show us the quota in action.

cd /data/volumes/xfs32m

for i in $(seq 1 7); do sudo dd if=/dev/zero of=5m/test.txt bs=1M count=$i 2>/dev/null; if [ $? -ne 0 ]; then echo "filesize of $i Mb failed, quota reached!"; else echo "filesize of $i Mb was ok"; fi; done

# cleanup
sudo rm 5m/test.txt

The output of the loop will show the file being created up to 5M, then failing on files any larger.

filesize of 1 Mb was ok
filesize of 2 Mb was ok
filesize of 3 Mb was ok
filesize of 4 Mb was ok
filesize of 5 Mb was ok
filesize of 6 Mb failed, quota reached!
filesize of 7 Mb failed, quota reached!

Attempting the same in the “10m” folder, will show filesizes up to 10Mb being created ok, but then failures after that.

for i in $(seq 1 12); do sudo dd if=/dev/zero of=10m/test.txt bs=1M count=$i 2>/dev/null; if [ $? -ne 0 ]; then echo "filesize of $i Mb failed, quota reached!"; else echo "filesize of $i Mb was ok"; fi; done

# cleanup
sudo rm 10m/test.txt

Teardown

If a drive image is mounted with the “-o loop” option, then umount knows how to remove it as a loopback device as well.

cd /data/volumes/
sudo umount /data/volumes/xfs32m

# check loopback devices, but should be already removed
sudo losetup -a

However, if you did not initially mount with “-o loop”, then you would have needed to assign the disk image to a a loopback device (/dev/loop<X>).  In this case, umount would not remove the loopback device and you would need to do it manually with “losetup -d”.

# check for unused loopback device
sudo losetup -a

# then assigned loopback device before mount
sudo losetup /dev/loopX xfs.32M

# after umount, you must manually remove
sudo losetup -d /dev/loopX

 

REFERENCES

redhat, xfs filesystem manual

thegeekdiary, virtual loopback block device

thegeekdiary, quotas on xfs

linuxtechi, good explanation of xfs quotas

held.org.il, examples of project based quotas, /etc/projects and /etc/projid files

scriptthe.net, hard quotas on directory

stackoverflow, setting GRUB_CMDLINE_LINUX_DEFAULT to support xfs quota

hosticron, grub settings for xfs quota support

xfs_quota man page, shows how to init configuration without config files

invent.life, create loopback device with ext4

man mount page

 

NOTES

If using xfs partition as boot partition on host, enable quotas in grub config.  Do NOT do this for non-boot partitions.

# add to GRUB_CMDLINE_LINUX_DEFAULT
rootflags=uquota,pquota

# ubuntu
sudo update-grub
# RHEL uses grub2-mkconfig
sudo reboot -h now

Adding mount to /etc/fstab, run “sudo blkid” first

UUID=7e17674f-745e-43b7-a834-877a3a007dbe /data/volumes/xfs32m xfs defaults,pquota 0 0