DEV Community

Cover image for Two Hundred MiB Fit Inside a Hundred MiB Limit
Mustafa ERBAY
Mustafa ERBAY

Posted on Originally published at mustafaerbay.com.tr

Two Hundred MiB Fit Inside a Hundred MiB Limit

The promise of project quota sounds too good: you get to tell a single directory "you are 100 GiB at most". No separate partition to create, no LVM volume, no bind mount acrobatics. Keep /var/log from ballooning, keep a customer upload directory from finishing off the disk — all through the filesystem's own accounting.

I sat down and set it up today. By the book: an ext4 with the quota and project features, a project ID on the directory, a 100 MiB hard limit via setquota. I asked quotaon -p -P and it said "on". Then 200 MiB went into that directory under an unprivileged user and nothing complained.

200+0 records out
209715200 bytes (210 MB, 200 MiB) copied, 0.184693 s, 1.1 GB/s
Enter fullscreen mode Exit fullscreen mode

The quota was on. The limit was in place. The file was twice its size.

This article chases that contradiction. Five separate traps came out of it, all reproduced on loop images and each traced down to a single line of kernel source. One changes the identity of your mv command, one forces df to give two different answers for the same device, and two come from ext4 and XFS routing the same feature through different places. The first is the most dangerous, because it sits exactly where you declare the setup finished — and even /proc/mounts can lie to you there.

I have written about the same disk twice before. df said zero, root kept writing was about ext4's 5% reserve, the share the filesystem keeps for itself; how much space is on my disk was about where five different answers for one volume come from. The mechanism here is separate from both: per-directory quota, an independent accounting layer with its own on-disk structures and its own enforcement switch.

The lab: two loop images, one kernel

The environment is Ubuntu 26.04 LTS, kernel 7.0.0-34-generic, mke2fs 1.47.2, xfs_quota 6.18.0, quota tools 4.09. No measurement touched a real volume; everything happened inside 512 MiB loop images.

One note, because you should not look for these exact strings on your own machine: on Ubuntu 26.04 /usr/bin/dd is not GNU coreutils but uutils coreutils 0.8.0, a symlink to /usr/lib/cargo/bin/coreutils/dd. The error texts differ as a result. When GNU dd hits the quota wall it prints dd: error writing '/mnt/q/p1/x': Disk quota exceeded; uutils says dd: IO error: Disk quota exceeded. Same errno, different sentence — and that difference is itself a good argument for why alerting rules should key on the error number rather than the message, something that comes back later in this article.

The first surprise arrived on the second line of the setup. I created a plain ext4 and tried to give a directory a project ID:

mkfs.ext4 -q -F noproj.img
mount -o loop noproj.img /mnt/noproj
mkdir /mnt/noproj/p1
chattr -p 42 /mnt/noproj/p1   # assign the ID
chattr +P /mnt/noproj/p1      # let children inherit it
Enter fullscreen mode Exit fullscreen mode

The first one was rejected as expected:

chattr: Operation not supported while setting project on /mnt/noproj/p1
Enter fullscreen mode Exit fullscreen mode

The second one went through. Exit code zero, no warning at all:

    0 --------------e----P-- /mnt/noproj/p1
Enter fullscreen mode Exit fullscreen mode

The P flag sits right there, persistent, even though the filesystem never had the project feature. The reason is in fs/ext4/ioctl.c: ext4_fileattr_set routes only the project ID through the feature check in ext4_ioctl_setproject, while the PROJINHERIT flag passes along with ordinary flags. So you can end up with a directory that says "inheritance is on" and a filesystem with nothing to inherit. If you write a setup script with set -e and trust the chattr +P line to pass, that script is lying to you. The right check is not in the flag but in the feature list:

mkfs.ext4 -q -F -O quota,project proj.img
dumpe2fs -h proj.img | grep -E "^(Filesystem features|Inode size)"
Enter fullscreen mode Exit fullscreen mode
Filesystem features:      has_journal ext_attr resize_inode dir_index orphan_file
filetype extent 64bit flex_bg metadata_csum_seed sparse_super large_file huge_file
dir_nlink extra_isize quota metadata_csum project
Inode size:               256
Enter fullscreen mode Exit fullscreen mode

The project ID lives as a field inside the inode, which is why this does not work with the old 128-byte inodes. For anyone adding it to an existing filesystem later, that is a harder wall than it looks:

$ tune2fs -O quota,project small.img
Cannot enable project feature; inode size too small.
Enter fullscreen mode Exit fullscreen mode

Since inode size cannot be changed in place, the only way out there is recreating the filesystem. 256 bytes has been the Ubuntu /etc/mke2fs.conf default for years, so new installs are fine; the problem is volumes built years ago.

Trap one: accounting and enforcement are not the same switch

Back to the beginning of the story. The features are on, the directory has an ID, the limit is set:

mount -o loop proj.img /mnt/q
chattr -p 42 +P /mnt/q/p1
setquota -P 42 0 102400 0 0 /mnt/q   # hard limit: 102400 KiB = 100 MiB
quotaon -p -P /mnt/q
Enter fullscreen mode Exit fullscreen mode
project quota on /mnt/q (/dev/loop0) is on
Enter fullscreen mode Exit fullscreen mode

That sentence misled me. repquota -P worked too, counted usage correctly, showed the limit correctly. Yet an unprivileged write landed all 200 MiB on disk. When I remounted the image with one extra option, the same limit, the same user and the same command answered differently:

dd: IO error: Disk quota exceeded
Enter fullscreen mode Exit fullscreen mode

The difference is one word: prjquota. The mechanism sits in ext4_enable_quotas in fs/ext4/super.c, and it is not hidden at all:

    bool quota_mopt[EXT4_MAXQUOTAS] = {
        test_opt(sb, USRQUOTA),
        test_opt(sb, GRPQUOTA),
        test_opt(sb, PRJQUOTA),
    };
    ...
            err = ext4_quota_enable(sb, type, QFMT_VFS_V1,
                DQUOT_USAGE_ENABLED |
                (quota_mopt[type] ? DQUOT_LIMITS_ENABLED : 0));
Enter fullscreen mode Exit fullscreen mode

Usage accounting (DQUOT_USAGE_ENABLED) is turned on unconditionally; enforcing the limits (DQUOT_LIMITS_ENABLED) only if the corresponding mount option was given. quotaon -p reports the first one. The tool is not lying, really, it just does not answer the question we thought we were asking.

Note that all three types get identical treatment in that array. The kernel's ext4 documentation says of the quota options that they "are ignored by the filesystem. They are used only by quota tools to recognize volumes where quota should be turned on" — a sentence written for the older mode with separate quota files. On a filesystem with the quota feature enabled (that is, whatever the mkfs line above produces), usrquota and grpquota behave exactly like prjquota: they are what turns enforcement on. The doc is stale for all three, and especially misleading for project quota, since that option does not appear in the list at all.

Now the bad news. "Fine, on a live system I will fix fstab and remount," I thought. So I did:

mount -o remount,prjquota /mnt/q
Enter fullscreen mode Exit fullscreen mode
$ grep " /mnt/q " /proc/mounts
/dev/loop0 /mnt/q ext4 rw,relatime,prjquota 0 0
Enter fullscreen mode Exit fullscreen mode

Exit code zero. /proc/mounts now says prjquota. Every indicator green. Then I ran the same test again:

209715200 bytes (210 MB, 200 MiB) copied, 0.129205 s, 1.6 GB/s
Enter fullscreen mode Exit fullscreen mode

204800 KiB. The remount recorded the option string and never turned enforcement on. That is the sneakiest finding in this article: the place you check to confirm your work confirms it for you.

The reverse direction is not quiet. Trying to turn it off while mounted with prjquota, mount exited 32 and the kernel log said:

EXT4-fs: Cannot change quota options when quota turned on
Enter fullscreen mode Exit fullscreen mode

The same string sits in fs/ext4/super.c under the err_quota_change label. So turning quota options on is silently ineffective and turning them off is openly refused. The only way to actually change either is umount + mount. For a root filesystem that means adding rootflags=prjquota to the kernel command line and rebooting — and the /var/log example from the opening sentence sits on the root filesystem on most servers.

XFS is more readable here. The two states get reported separately:

xfs_quota -x -c 'state -p' /mnt/xq
Enter fullscreen mode Exit fullscreen mode
Project quota state on /mnt/xq (/dev/loop1)
  Accounting: ON
  Enforcement: ON
Enter fullscreen mode Exit fullscreen mode

Leave out the mount option and /proc/mounts says noquota outright — unlike ext4, what you see there is real. But none of that comes from the documentation: the kernel's XFS page lumps pquota/prjquota/pqnoenforce into one line saying "project disk quota accounting enabled and limits (optionally) enforced", and draws no distinction between them. The difference shows up in the tooling, not in the docs.

Trap two: who stops root

Once the limit started working, I repeated the test as root. Same directory, same 100 MiB hard limit:

209715200 bytes (210 MB, 200 MiB) copied, 0.142471 s, 1.5 GB/s
Enter fullscreen mode Exit fullscreen mode

204800 KiB. All of it. In the same second, in the same directory, the unprivileged user was hitting the wall at 101376 KiB.

This time the source did not take long to find, because the function name does not hide its intent. From fs/quota/dquot.c:

static int ignore_hardlimit(struct dquot *dquot)
{
    struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type];

    return capable_noaudit(CAP_SYS_RESOURCE) &&
           (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD ||
        !(info->dqi_flags & DQF_ROOT_SQUASH));
}
Enter fullscreen mode Exit fullscreen mode

The hard limit check hangs off it: if (dquot->dq_dqb.dqb_bhardlimit && tspace > dquot->dq_dqb.dqb_bhardlimit && !ignore_hardlimit(dquot)) → -EDQUOT. The root_squash clause on the second line looks like an escape hatch at first, but it is unusable here: ext4_enable_quotas above pins the format to QFMT_VFS_V1, not QFMT_VFS_OLD. So the first half of that condition is always true and only the capability check is left.

To confirm the hypothesis I stayed root but dropped only that capability:

capsh --drop=cap_sys_resource -- -c "dd if=/dev/zero of=/mnt/q/p1/nocap bs=1M count=200"
Enter fullscreen mode Exit fullscreen mode
dd: IO error: Disk quota exceeded
Enter fullscreen mode Exit fullscreen mode

It is not about uid 0, it is about the capability. That distinction pays off directly in the container world, because Docker leaves this capability out of its default set:

$ docker run --rm alpine:3 sh -c "grep CapEff /proc/self/status"
CapEff: 00000000a80425fb

$ docker run --rm --privileged alpine:3 sh -c "grep CapEff /proc/self/status"
CapEff: 000001ffffffffff
Enter fullscreen mode Exit fullscreen mode

CAP_SYS_RESOURCE is bit 24, mask 0x01000000. That bit is off in the default value and on in the privileged one. Root inside a container does hit the project quota; the moment you pass --privileged, the protection is gone. For cleanup or backup scripts running as root under cron on the host, there never was any protection.

I ran the same test on XFS and the result flipped:

dd: IO error: No space left on device
Enter fullscreen mode Exit fullscreen mode

Root stopped at 102400 KiB. XFS runs project quota through its own transaction reservation rather than the generic dquot layer; xfs_trans_dqresv in fs/xfs/xfs_trans_dquot.c only looks at this:

    if ((flags & XFS_QMOPT_FORCE_RES) == 0 && dqp->q_id &&
        xfs_dquot_is_enforced(dqp)) {
Enter fullscreen mode Exit fullscreen mode

There is not a single capable() call in the whole file. XFS_QMOPT_FORCE_RES is a flag used by the kernel's own internal paths, not by arbitrary processes. The result: the same hard limit grazes past root on ext4 and does not on XFS.

Do not read this as a security feature. Project quota is disk accounting, not access control. But while you relax because "I have a quota", you need to know which filesystem you are standing on.

Trap three: the same error under two different names

Put the outputs side by side:

ext4 XFS
Unprivileged user Disk quota exceeded (EDQUOT) No space left on device (ENOSPC)
Written up to the limit 102396 KiB 102400 KiB
root (full capabilities) 204800 KiB — limit exceeded 102400 KiB — limit held

The same feature, the same limit, a different errno. The mechanism is on the exit path of xfs_trans_dqresv:

error_return:
    mutex_unlock(&dqp->q_qlock);
    if (xfs_dquot_type(dqp) == XFS_DQTYPE_PROJ)
        return -ENOSPC;
    return -EDQUOT;
Enter fullscreen mode Exit fullscreen mode

The comment blocks above the function attribute this behaviour to a flag named XFS_QMOPT_ENOSPC — "returns ENOSPC not EDQUOT. Used by pquota." But that flag no longer exists: in fs/xfs/libxfs/xfs_quota_defs.h the XFS_QMOPT_* definitions cover bits 0–4, 7–14 and 31, and ENOSPC is not among them. The comment fell behind the code. The behaviour stands, the name explaining it was deleted — not a bad reminder to separate comments from code when reading source.

The logic makes sense: a project quota belongs to a directory, not a user, so from the user's point of view what happened is not "I am over my quota" but "there is no room here". You pay for it on the monitoring side. If your application treats ENOSPC and EDQUOT differently — and well-written services do — then the same code takes two branches on two filesystems. An alert rule grepping logs for Disk quota exceeded goes quiet the day you move to XFS; and as the dd example at the top showed, the text for one errno varies even by coreutils implementation. Key the rule on the error number, not the message.

The small difference in the numbers matters too: ext4 stopped at 102396 KiB while writing 4 KiB blocks, because the data block of the project's own directory was already holding that 4 KiB. On XFS an empty project directory counts as zero blocks, so it reached a full 102400 KiB. Running the same test with 1 MiB blocks, ext4 stopped at 101376 KiB (99 MiB) — dd fails on the whole block that would cross the limit, so the bigger the block, the earlier the wall appears. Note the block size when you measure a quota threshold.

Trap four: mv quietly starts copying

This was the finding that surprised me most in the lab. I created a 10 MiB file outside the project directory and moved it in:

dd if=/dev/zero of=/mnt/q/serbest/a bs=1M count=10
lsattr -p /mnt/q/serbest/a      #     0 ...
mv /mnt/q/serbest/a /mnt/q/p1/a
lsattr -p /mnt/q/p1/a           #    42 ...
Enter fullscreen mode Exit fullscreen mode

The file's project ID went from 0 to 42 and the quota started counting it. "The kernel reassigned the moved file," I thought. I should not have — rename(2) within the same filesystem does not change identity. I looked with strace:

renameat2(AT_FDCWD, "/mnt/q/serbest/c", AT_FDCWD, "/mnt/q/p1/c",
          RENAME_NOREPLACE) = -1 EXDEV (Invalid cross-device link)
openat(AT_FDCWD, "/mnt/q/p1/c", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4
unlinkat(AT_FDCWD, "/mnt/q/serbest/c", 0) = 0
Enter fullscreen mode Exit fullscreen mode

mv did not move anything. The rename call was rejected with EXDEV, and mv took that to mean "separate filesystems" and fell back to copy-and-delete. New inode, new project ID, no warning, exit code zero. The same boundary applies to ln, except there is no disguise there:

ln: failed to create hard link '/mnt/q/serbest/b' => '/mnt/q/p1/b-link': Invalid cross-device link
Enter fullscreen mode Exit fullscreen mode

I found the source in three places in fs/ext4/namei.c: ext4_link, ext4_rename and ext4_cross_rename, each returning -EXDEV after a projid_eq comparison. The first two look only at the target directory's PROJINHERIT flag:

    if ((ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) &&
        (!projid_eq(EXT4_I(dir)->i_projid,
            EXT4_I(old_dentry->d_inode)->i_projid)))
        return -EXDEV;
Enter fullscreen mode Exit fullscreen mode

The third one, the RENAME_EXCHANGE path, tests both directories — the boundary is symmetric there. But an everyday mv goes through the first two, and there the boundary is one-way. Going into the quota'd directory you get EXDEV; coming out, if the destination is an ordinary directory, it is a real rename and the file carries its project ID along:

mv /mnt/q/p1/y /mnt/q/serbest/y
lsattr -p /mnt/q/serbest/y      #    42 ...
Enter fullscreen mode Exit fullscreen mode

The file now lives outside the quota'd directory and still eats that directory's quota. Search for it with du and you will not find it, because it is somewhere else; repquota keeps counting it. This is the sneakiest way for a quota to look full "for no reason".

Two consequences hurt separately in production. First: moving a 200 GiB directory into a quota'd location with mv is not a metadata operation taking seconds, it is a full-size copy; the disk writes twice, the duration goes from minutes to hours, and if it hits the quota halfway you are left with half a file. Second: to take data out of the quota, mv is not enough — you have to clear the ID yourself with chattr -p 0.

Diagram

Trap five: df gives two answers for the same device

Project quota changes statfs(2) as well. I set the soft limit to 20 MiB and the hard limit to 100 MiB, then ran df in two places — same loop device, same mount point:

$ df -h --output=size,used,avail,target /mnt/q/p1
  20M  4.0K   20M /mnt/q

$ df -h --output=size,avail,target /mnt/q
 488M  452M /mnt/q
Enter fullscreen mode Exit fullscreen mode

Looking from inside the directory you see a 20 MiB filesystem; looking from the mount point, a 488 MiB one. The Mounted on column says /mnt/q in both, so the output itself gives you no way to tell which is which. stat -f compares them in one line: 5120 blocks in the project directory, 124725 at the mount point, both 4096 bytes.

The behaviour is useful. Applications that treat a directory like a volume — services that say "stop writing above 90% full", monitoring agents reading df output — pick up the project quota for free. But there are two details, and neither can be guessed without reading the source.

The first is which limit gets reported. From ext4_statfs_project:

    limit = min_not_zero(dquot->dq_dqb.dqb_bsoftlimit,
                 dquot->dq_dqb.dqb_bhardlimit);
Enter fullscreen mode Exit fullscreen mode

The smaller of the two, ignoring zeros. With a 20 MiB soft and a 100 MiB hard limit, df tells you 20 MiB; when I zeroed the soft limit and left only the hard one, the same directory became 100 MiB. XFS produces the same answer by a different route — fs/xfs/xfs_qm_bhv.c has limit = blkres->softlimit ? blkres->softlimit : blkres->hardlimit. While the soft limit is below the hard one the two behave identically; set a soft limit above the hard one and they diverge. Either way, if you set a soft limit thinking of it as "a gentle warning threshold", you have shrunk the size df sees without realising it.

The second is quieter. ext4_statfs only takes this path if the dentry carries PROJINHERIT. So the df trick works only on directories that hold the inheritance flag; point df at a subdirectory without it, or at a file path, and you silently get the whole filesystem back. Which path your monitoring agent probes determines the answer you get.

The small ones hurt too

Soft limits and the grace period work with classic quota logic. I set the soft limit to 20 MiB and grace to 1 second; writing 30 MiB over the soft limit went through without complaint, because while the window is open you can write up to the hard limit. I waited three seconds and tried another 5 MiB, and the window had closed — zero bytes written. The moment grace expires, the soft limit behaves like a hard one, which is exactly where the "it could write in the evening and cannot in the morning, with nothing changed in the limits" scenario comes from.

The inode limit surprised me as well. I set the hard inode limit to 5 and tried creating files; it stopped after the fourth:

touch: cannot touch '/mnt/q/p1/f5': Disk quota exceeded
Enter fullscreen mode Exit fullscreen mode

I said five and got four. The project's own directory holds an inode too, so the number always comes out one short, and with subdirectories the gap grows.

Retrofitting an existing directory

Everything so far happened with an empty directory. The real job is usually putting a full /var/log under quota, and there is one more step there. First the directory gets an ID and the inheritance flag:

echo "7002:/mnt/q/eski" > /etc/projects
echo "eskilog:7002"     > /etc/projid
chattr -p 7002 +P /mnt/q/eski
setquota -P eskilog 0 51200 0 0 /mnt/q
Enter fullscreen mode Exit fullscreen mode

Naming works: once /etc/projects and /etc/projid are in place, setquota -P eskilog and repquota -P use the name instead of a bare number. If you would rather not operate on magic integers in production, those two files are the first thing to create.

But the quota showed 4 KiB — only the directory itself. The 15 MiB of old files inside it still did not belong to the project:

    0 --------------e------- /mnt/q/eski/a.log
    0 --------------e------- /mnt/q/eski/alt/b.log
Enter fullscreen mode Exit fullscreen mode

Inheritance does not work backwards. Picking up the existing content needs a recursive assignment:

chattr -R -p 7002 /mnt/q/eski
Enter fullscreen mode Exit fullscreen mode

That fixes the files' IDs and the quota climbs to 15368 KiB. There is one more trap here, though: -R -p does not put the inheritance flag on sub*directories*. A new file I created in alt/ after the assignment was born with project 0 again. Trying chattr -R -p 7002 +P wrote the flag onto directories but produced an Operation not supported while setting flags error for every regular file, because +P is a directory-only flag. The clean way is to separate the two:

chattr -R -p 7003 /mnt/q/retro
find /mnt/q/retro -type d -exec chattr +P {} +
Enter fullscreen mode Exit fullscreen mode

Both exit zero, P appears on every directory, and files created afterwards are born with the right ID.

Then there is restoring from backup, which quietly costs you the quota. tar does not carry the project ID:

project ID of the restored file:
    0 --------------e------- /mnt/q/geri/varlog/eski.log
Enter fullscreen mode Exit fullscreen mode

The restored 15 MiB was written to project 0 and the quota'd project's counter stayed where it was. If your disaster recovery plan has no step that reapplies project IDs, after a restore your quota is technically in place and counting nothing.

For monitoring there is machine-readable output; since node_exporter does not export project quota, you have to collect this yourself:

$ repquota -P -O csv /mnt/q
Project,BlockStatus,FileStatus,BlockUsed,BlockSoftLimit,BlockHardLimit,BlockGrace,FileUsed,...
varlog,ok,ok,15372,0,51200,,5,0,0,
#42,hard,ok,204804,0,102400,,2,0,0,
Enter fullscreen mode Exit fullscreen mode

The BlockStatus column says outright that a project is over its hard limit — far more durable to alert on than message text. The XFS equivalent is xfs_quota -x -c 'report -p -N -b'.

The container side: what the docs say and what the machine does

Most people first hear about project quota in the container world, because it is how you put a limit on a container's writable layer. The Docker documentation states the condition clearly: --storage-opt size= exists only on the btrfs, overlay2, windowsfilter and zfs drivers, and "for the overlay2 storage driver, the size option is only available if the backing filesystem is xfs and mounted with the pquota mount option".

I tried it on a machine whose root filesystem is ext4, Docker 29.1.3:

docker run --rm --storage-opt size=50m alpine:3 sh -c "df -h / | tail -1"
Enter fullscreen mode Exit fullscreen mode
overlay                  95.8G     53.5G     42.3G  56% /
Enter fullscreen mode Exit fullscreen mode

No error, no warning, exit code zero. I asked for 50 MiB and the container saw 95.8 GiB.

Two different things should not be conflated here. The documented condition was written for the old overlay2 graph driver; this machine, as docker info says, uses the containerd snapshotter (Storage Driver: overlayfs, driver-type: io.containerd.snapshotter.v1). By Docker's own documentation, the containerd image store is the default on fresh installs from 29.0 onwards. So most people now run on the code path that swallows --storage-opt size silently, not the one it was documented against.

Verifying it is simple and filesystem-independent: after setting the limit, run df -h / inside the container and look for the size you expect. If you cannot see it, there is no limit — and that is worse than never having configured a quota, because it looks configured.

The order of operations

After all these measurements, my own checklist ended up like this:

  1. Decide early when you pick the filesystem. If a per-directory limit needs to cover root as well, use XFS. On ext4 the hard limit does not stop processes carrying CAP_SYS_RESOURCE, and the root_squash escape is unusable because of QFMT_VFS_V1.
  2. Verify the feature, do not trust the flag. Look for quota and project in dumpe2fs -h | grep "Filesystem features". chattr +P succeeding proves nothing. On an old volume with 128-byte inodes tune2fs will turn you away, and the only remedy is recreating the filesystem.
  3. Budget a maintenance window for prjquota. Writing it into fstab is not enough: a remount is silently ineffective and turning it off is refused. It really takes umount + mount; on a root filesystem that means rootflags=prjquota and a reboot.
  4. Accept enforcement only by measuring it. Try writing over the limit as an unprivileged user and see the error. The setup is finished the moment you see that error — not when quotaon -p says "on".
  5. Name your projects. Without /etc/projects and /etc/projid you live with bare numbers in production; both files are read by setquota -P and xfs_quota alike.
  6. Retrofit an existing tree in two steps. First chattr -R -p <id>, then find <dir> -type d -exec chattr +P {} +. Doing it in one step errors on regular files; skipping it leaves subdirectories without inheritance.
  7. Key monitoring on the errno and the status, not the text. ext4 returns EDQUOT, XFS returns ENOSPC, and the text for one errno varies by coreutils implementation. The BlockStatus field in repquota -P -O csv is the durable signal.
  8. Add project IDs to the backup plan. tar does not carry them; unless they are reapplied after a restore, the quota appears to be counting and counts nothing.

This morning I looked at project quota as "a directory limit without opening a separate partition". By the evening I see it as something else: an accounting layer bolted onto the filesystem afterwards, routed through two different places in two implementations, whose tools still answer the old questions. It works. But the only thing that proves a limit really exists is the error you get when you try to exceed it — not /proc/mounts, not quotaon, not even repquota.

The test costs one loop image and two minutes. That is all today amounted to.

Official Sources

Top comments (0)