Owners, Groups and umask
Problem statement
Work out what permissions a new file and a new folder get, using umask, and set up a team folder where every new file belongs to the team's group. This explains why a file your app writes is unreadable to another service, and why a teammate cannot edit a file you just created.
The script works in an empty temporary folder. Do these steps in order:
- Under each of the umask values
022,027and077, create one file and one folder, and print their modes. - Show the math: what a new file's starting mode
666becomes under umask022and under umask033. - Make a folder called
sharedwith mode2775, and print its mode.
Expected output:
== umask 022 ==file: 644folder: 755== umask 027 ==file: 640folder: 750== umask 077 ==file: 600folder: 700== the math ==666 with umask 022 -> 644666 with umask 033 -> 644== shared team folder ==2775 drwxrwsr-x sharedHints
666 and new folders at 777. The umask lists the bits to take away from that start.Approach
Optimal: umask and setgid
Covers: id, groups, chown user:group, chgrp, umask, chmod 2775, the setgid bit, subshells ( ).
Every file has one owner and one group. ls -l shows them in the third and fourth columns, for example asha devs. The owner bits apply to the owner, the group bits apply to everyone in that group, and the others bits apply to the rest. You can see who you are and which groups you are in with id:
$ iduid=1001(asha) gid=1001(asha) groups=1001(asha),27(sudo),998(docker)Changing owner and group.
| Command | Does | Who can run it |
|---|---|---|
chown ravi file |
make ravi the owner |
only root |
chown ravi:devs file |
change owner and group | only root |
chgrp devs file |
change only the group | the owner, if they are in devs |
chown -R app:app /srv/app |
change a whole tree | only root |
Only root can give a file away. Otherwise a user could hide a file under someone else's name, or dodge a disk quota.
Where new permissions come from: the umask. When a program creates a file, it asks for 666 (read and write for everyone). Folders ask for 777. Then the kernel removes the bits listed in your umask. The umask is a mask of bits to take away, written like a mode:
| umask | New file | New folder | Used for |
|---|---|---|---|
022 |
644 |
755 |
the usual default: others can read |
002 |
664 |
775 |
team setups: the group can write too |
027 |
640 |
750 |
servers: others get nothing |
077 |
600 |
700 |
private: only you |
New files never get execute, because the starting point 666 has no execute bits at all. That is why you always need chmod +x on a new script.
Removing bits is not subtracting. With umask 033, a file is not 666 - 033 = 633. The umask says "remove write and execute from group and others". The group has rw- and loses write, so it has r--. It never had execute, so there is nothing to lose. The result is 644. The code shows this with bash math: $(( 0666 & ~0033 )). Here ~ flips the mask and & keeps only the bits that are in both, which is exactly "remove these bits".
A team folder with setgid. Say asha and ravi are both in the group devs, and they share /srv/reports. When asha creates a file, it gets her own group by default, so ravi may not be able to edit it. Setting the setgid bit on the folder fixes that: every new file inside takes the folder's group instead.
report.txt"]):::blue --> R["group is devs"]:::green --> E["ravi can edit it"]:::green end subgraph NO["Folder without setgid"] direction TB A2(["asha creates
notes.txt"]):::blue --> R2["group is asha"]:::red --> E2["ravi may not
edit it"]:::red end YES ~~~ NO classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px style YES fill:transparent,stroke:#059669,stroke-width:2px style NO fill:transparent,stroke:#dc2626,stroke-width:2px
chmod 2775 shared sets it. The extra 2 in front is the setgid bit, and in ls -l it shows as an s where the group's x would be: drwxrwsr-x. Combine it with umask 002 so the group also gets write on new files.
Walking through the code. The # Setup: line only creates an empty temporary folder, so skip past it.
- The loop runs each test inside
( ), a subshell. A subshell is a copy of the shell, soumaskchanges only last until the), and the next test starts clean.touchandmkdircreate the file and folder, andstat -c '%a'prints the mode as a number. printf '%o'prints a number in octal (base 8), the way modes are written. The leading0in0666tells bash that the number is octal too.chmod 2775 sharedsets setgid plus775, andstatshows thes.
Edge cases. umask with no value prints the current one, like 0022. A umask set in a terminal ends when that terminal closes. To make it last, it must be set in a login file like ~/.profile, or in the service's settings. stat -c is GNU (Linux); macOS uses stat -f '%Lp'.
# Setup: work in a fresh temporary folder
cd "$(mktemp -d)"
# 1. Make one file and one folder under three different umasks.
# The ( ) runs each block in a subshell, so the umask change ends with it.
for mask in 022 027 077; do
(
umask "$mask"
touch "file-$mask"
mkdir "dir-$mask"
echo "== umask $mask =="
echo "file: $(stat -c '%a' "file-$mask")"
echo "folder: $(stat -c '%a' "dir-$mask")"
)
done
# 2. The math: umask REMOVES bits, it does not subtract
echo "== the math =="
for mask in 022 033; do
printf '666 with umask %s -> %o\n' "$mask" $(( 0666 & ~0$mask ))
done
# 3. A team folder: setgid (the 2 in front) makes new files inherit the folder's group
mkdir shared
chmod 2775 shared
echo "== shared team folder =="
stat -c '%a %A %n' sharedInterview follow-ups
How do you set up a folder where a whole team can create and edit files?
Make a group and put the team in it, for example
groupadd devsandusermod -aG devs asha. Give the folder that group and the setgid bit:chgrp devs /srv/reportsandchmod 2775 /srv/reports. Now every new file inside gets thedevsgroup. Then make sure new files are group-writable, either by setting umask002for the team or by using a default ACL withsetfacl -d -m g:devs:rwX /srv/reports. ACLs (access control lists) let you add rules for extra users or groups on top of the normal bits.
Frequently asked questions
Only root can change a file's owner. If normal users could, you could create a huge file and give it to someone else to fill their disk quota, or plant a file that looks like another user wrote it. You can change the group, but only to a group you are in, with chgrp. On a server, ownership changes are done with sudo chown, usually once during setup, for example sudo chown -R app:app /srv/app.
For your login shell, the umask usually comes from /etc/login.defs, /etc/profile or your own ~/.profile and ~/.bashrc. Services do not read those files. A systemd service gets its umask from the UMask= line in its unit file, and the default there is 0022. That is why a file written by a service can have different permissions from a file you create by hand. If an app writes files another service must read, set the service's umask on purpose instead of fixing files later with chmod.
Your group list is read when you log in. Adding you to a group with usermod -aG devs asha changes the account, but your open sessions still have the old list. Run id and you will not see the new group yet. Log out and back in, or start a new shell with newgrp devs, and it shows up. Services need a restart for the same reason.