What are "project IDs" in linux, as mentioned in the chattr manual?

I was reading through the manual for chattr on my Linux machine (kali4-amd64) when I saw this in the list of definitions of file attributes:

A directory with the 'P' attribute set will enforce a hierarchical
structure for project id's.  This means that files and directory created in
the directory will inherit the project id of the directory, rename
operations are constrained so when a file or directory is moved into
another directory, that the project id's much match.  In addition, a hard
link to file can only be created when the project id for the file and the
destination directory match.

I don't personally know what a "project id" is when it comes to Linux/UNIX, and a lot of googling net me no results either, so I hope somebody here can help me out.


What you're asking is a part of the concept of project quotas. Project quotas are a way to manage disk quota. A particular filesystem type may or may not support project id's. Let's concentrate on ext4 and start with man 8 tune2fs:

-O [^]feature[,...]
Set or clear the indicated filesystem features (options) in the filesystem. […]

[…]

project
Enable project ID tracking. This is used for project quota tracking.

quota Enable internal file system quota inodes.

[…]

-Q quota-options
Sets 'quota' feature on the superblock and works on the quota files for the given quota type. Quota options could be one or more of the following:

[^]usrquota
Sets/clears user quota inode in the superblock.

[^]grpquota
Sets/clears group quota inode in the superblock.

[^]prjquota Sets/clears project quota inode in the superblock.

You can enable these options on an existing filesystem:

tune2fs -O project,quota /your/device

(or supply them to mke2fs while creating a new filesystem). Then enable project quota (possibly with user quota and/or group quota if you wish):

tune2fs -Q prjquota /your/device

Mount it:

mount /your/device /the/mountpoint

Now you can manage quotas with tools like setquota and quota (note that old(-ish) versions of the tools may lack the -P option that handles project quotas). Traditionally you would limit the amount of disk space that user or group can use. With project quota you can do this for "projects", regardless of users and groups that participate.

It works like this. First place yourself in the mountpoint and create few directories:

cd /the/mountpoint
mkdir foo bar baz

Enable project hierarchy on them:

chattr +P foo bar baz

Assign them to two different projects:

chattr -p 123 foo       # 123 is an arbitrary number, project id
chattr -p 5 bar baz     # so is 5, the point is they are different

Create files within:

echo "lorem ipsum" > foo/file1
echo "lorem ipsum" > bar/file2
echo "lorem ipsum" > baz/file3

Now invoke:

lsattr -pR .

and you will see (among others) lines like this:

  123 --------------e---P ./foo/file1
    5 --------------e---P ./bar/file2
    5 --------------e---P ./baz/file3

which means file1 belongs to the project with id 123, file2 and file3 belong to the project with id 5. If you define quotas for these projects (i.e. limit the amount of disk space projects can use), each file will affect the quota consumption of its respective project.

Now what you cited makes lot of sense:

files and directory created in the directory will inherit the project id of the directory

In our example file1 inherited the project id from foo. If you create more files/directories in foo then they will inherit the id as well. This allows you (and fellow users) to work on the project in its designated directory, while files you create automatically count towards the respective quota.

a hard link to file can only be created when the project id for the file and the destination directory match.

ln ./baz/file3 ./foo/ will fail (try it) but ln ./baz/file3 ./bar/ will succeed. The OS won't let you easily "embed" a file that belongs to one project (and must stay like this because the source path is not unlinked) into a different project directory. Linking a file within its project is allowed.

when a file or directory is moved into another directory, the project id's must match

I think this is rather misleading. mv will do its job even if id's don't match. The point is if you invoke

mv baz/file3 foo/

the tool will first try to rename(2) the file to the new path, this will fail (like ln above does). Normally or within the same project it would succeed and the original name would disappear. Apparently this behavior is what "the project id's must match" is about.

But mv won't exit yet. It's like moving between filesystems: after mv fails to rename, it falls back to copy+delete mode. In effect it creates a copy (with a new inode number) in the target directory. In our case the copy inherits the project id from foo (like any new file in this directory would), so it affects the quota consumption of project 123. The original path is then unlinked. This may affect the quota consumption of project 5; or may not: hardlinks or open file descriptors will cause the original inode and data to survive.

Note it's somewhat surprising: a new file gets created, old hardlinks (if any) are not linked to the new file, file descriptors pointing to the old file have nothing to do with the new one; as if the moving operation was performed between filesystems.

There is a way to make mv rename instead of copy+delete. If you manually assign the original file to the target project

chattr -p 123 baz/file3

then mv baz/file3 foo/ will truly move it without copying, without breaking hardlinks (if any). But note that project number belongs to inode (not path, not name, not directory entry) so chattr -p affects all hardlinks.

So if you need to move a big file (i.e. data behind some inode, not just one of many hardlinks) to another project, changing the project and then moving will save you unnecessary copying.