Linux permissions — practice with a group project folder, three users and a permission checker (and the teammate who couldn’t build)

First week with the shared folder for the IC2 project. I had set it up with all the care in the world: the ic2 group could get in, read the code and run the build script; everyone else, nothing. Ten minutes later, a message from Laura: *”It won’t let me build. I get something about ld: cannot open output file“. Me, absolutely sure: *”Impossible, the script has the group x”. I was right… and it didn’t help at all. The script ran perfectly; what it couldn’t do was create the compiled program, because that means writing in the folder, and I had taken the group’s w away from the folder. A lesson that repeats throughout the practice: every action needs one specific permission in one specific place, and it’s hardly ever the place you look first.

This practice puts everything from the Linux permissions theory to work with three medium-level programs (here, three terminal sessions and a script):

  1. Setting up the group folder with chgrp and chmod in octal.
  2. Testing it with three users (Laura, in the group; Pablo, from outside; and me, the owner) and fixing what fails with symbolic chmod.
  3. A permission checker in bash that warns about what “others” can touch, plus a umask for whatever gets created later.

The data look like real data on purpose: a private file that sneaks into the shared folder, a slip that leaves the folder open to everyone and a teammate who can’t do her part.

Before you start

It helps to be clear on:

  • How to read an ls -l line: type and three rwx triplets (owner, group, others).
  • The octal weights: r = 4, w = 2, x = 1.
  • Symbolic mode: for whom (u, g, o, a), which operation (+, -, =) and which permissions.
  • That on a folder, x means “get in” and w means “create and delete inside”.

If any of these feels shaky, go over the theory before carrying on.

To reproduce the whole practice you need several users. On your own Linux (or on the lab virtual machine) you can create them with sudo adduser laura, sudo adduser pablo and sudo groupadd ic2, and add the first two to the group with sudo usermod -aG ic2 sergio and sudo usermod -aG ic2 laura (replace sergio with your user). Then open a terminal as each of them with su - laura. If you can’t use sudo, follow the practice by reading the outputs: they are the real ones.

Program 1: setting up the group folder

We need to prepare ic2-lab/ for a C project: a src/ folder with the code, a docs/ folder with the submission and some private notes of mine, and a build.sh script. We want the ic2 group to be able to get in, read the code and the submission, and build, my notes to be mine only and nobody from outside to see anything. What makes it tricky is that we have to decide the permissions of seven different things, and folders and files aren’t treated the same way.

The idea

Order matters:

  1. Create the layout (mkdir -p, touch, the script with a heredoc).
  2. Hand everything to the group with a single chgrp -R: no need to go file by file.
  3. Set permissions by kind: folders, all 750 (me rwx; the group r-x to get in and list; others nothing); files the group reads, 640; my notes, 600; the script, 750 so the group can run it.
  4. Check with ls -lR, which also lists the contents of the subfolders.

We use octal because we want exact permissions, not to add or remove on top of whatever was there.

Before you code, think of each folder as a door: the group needs x on every folder along the way to reach a file. If ic2-lab/ doesn’t have the group’s x, it doesn’t matter what you set on docs/submission.txt.

The code

# 1. The project layout: code in src/, documents in docs/
mkdir -p ic2-lab/src ic2-lab/docs
cat > ic2-lab/src/main.c << 'EOF'
int main(void) { return 0; }
EOF
touch ic2-lab/docs/submission.txt ic2-lab/docs/private-notes.txt
cat > ic2-lab/build.sh << 'EOF'
#!/bin/bash
gcc -Wall -std=c11 src/main.c -o main && echo "OK"
EOF
# 2. The whole tree goes to the project group (-R = recursive)
chgrp -R ic2 ic2-lab
# 3. Folders: me everything, the group gets in and lists, others stay out
chmod 750 ic2-lab ic2-lab/src ic2-lab/docs
# 4. Files: the group reads the code and the submission; my notes, only me
chmod 640 ic2-lab/src/main.c ic2-lab/docs/submission.txt
chmod 600 ic2-lab/docs/private-notes.txt
chmod 750 ic2-lab/build.sh      # the group can run it too
# 5. Check: -R also lists what is inside each folder
ls -lR ic2-lab

The meatiest part is the four chmods at the end. Look at them on their own:

# 3. Folders: me everything, the group gets in and lists, others stay out
chmod 750 ic2-lab ic2-lab/src ic2-lab/docs
# 4. Files: the group reads the code and the submission; my notes, only me
chmod 640 ic2-lab/src/main.c ic2-lab/docs/submission.txt
chmod 600 ic2-lab/docs/private-notes.txt
chmod 750 ic2-lab/build.sh      # the group can run it too

Each chmod accepts several files at once, so we group them by permissions: one line for all the 750 folders, another for the 640s… It’s shorter and, above all, easier to review: if tomorrow you decide the submission should be 660, you know which line to touch. The script gets its own line because, even though it shares the number with the folders, the reason is different (on a file, x means “run”, not “get in”).

Output

$ mkdir -p ic2-lab/src ic2-lab/docs
$ cat > ic2-lab/src/main.c << 'EOF'
> int main(void) { return 0; }
> EOF
$ touch ic2-lab/docs/submission.txt ic2-lab/docs/private-notes.txt
$ cat > ic2-lab/build.sh << 'EOF'
> #!/bin/bash
> gcc -Wall -std=c11 src/main.c -o main && echo "OK"
> EOF
$ chgrp -R ic2 ic2-lab
$ chmod 750 ic2-lab ic2-lab/src ic2-lab/docs
$ chmod 640 ic2-lab/src/main.c ic2-lab/docs/submission.txt
$ chmod 600 ic2-lab/docs/private-notes.txt
$ chmod 750 ic2-lab/build.sh
$ ls -lR ic2-lab
ic2-lab:
total 12
-rwxr-x--- 1 sergio ic2   63 Oct 25 16:49 build.sh
drwxr-x--- 2 sergio ic2 4096 Oct 25 16:49 docs
drwxr-x--- 2 sergio ic2 4096 Oct 25 16:49 src

ic2-lab/docs:
total 0
-rw------- 1 sergio ic2 0 Oct 25 16:49 private-notes.txt
-rw-r----- 1 sergio ic2 0 Oct 25 16:49 submission.txt

ic2-lab/src:
total 4
-rw-r----- 1 sergio ic2 29 Oct 25 16:49 main.c

Let’s check what matters by hand:

  • build.sh: -rwxr-x--- = 7 5 0. I read, change and run it; the group reads and runs it; others, nothing.
  • docs and src: drwxr-x---: the group can get in (x) and list (r), but can’t create anything inside (no w).
  • private-notes.txt: -rw------- = 6 0 0, just me.
  • submission.txt and main.c: -rw-r----- = 6 4 0: the group reads but doesn’t write.
  • The group column says ic2 on every line thanks to chgrp -R.

The root folder doesn’t show up in its own listing, but we set it to 750 with the same chmod. The drawing sums up the whole tree:

Permissions of the group folder Tree of ic2-lab with the permissions setup.sh leaves: folders 750, build.sh 750, submission.txt and main.c 640, private-notes.txt 600; everything owned by sergio, group ic2. The group folder after setup.sh owner: sergio · group: ic2 (sergio, laura) · others: pablo and everyone else u g o ic2-lab/ drwxr–x––– 750 the group enters and lists build.sh –rwxr–x––– 750 the group runs it docs/ drwxr–x––– 750 the group enters and lists private-notes.txt –rw––––––– 600 only Sergio submission.txt –rw–r––––– 640 the group reads it src/ drwxr–x––– 750 the group enters and lists main.c –rw–r––––– 640 the group reads it
Others (coral) get nothing anywhere in the tree; the group (blue) reads and enters, but still cannot write anywhere.

This is how the permissions of ic2-lab/ change throughout the program:

StepCommandPermissions of ic2-lab/Group
1mkdir -p …755 rwxr-xr-x (umask 022)sergio
2chgrp -R ic2 ic2-lab755 rwxr-xr-xic2
3chmod 750 ic2-lab …750 rwxr-x---ic2

Notice that, as it stands, the group can’t write anywhere. Is that what we want? Program 2 puts it to the test.

Questions you might ask

Why chgrp -R before the chmods? The order doesn’t actually change the result, but doing it first avoids a window in which the folder already has its group permissions open while still belonging to the sergio group. It’s a good habit: first decide who, then what.

Why are the folders 750 and not 740? Because a folder with r and no x lets you see the names but not get in or open anything (you saw it in the theory). We’d be giving the group a useless list of names.

Why not use chmod -R 750 ic2-lab and be done sooner? Because -R applies the same number to everything: main.c and submission.txt would end up with x (as if they were programs) and my private notes would be readable by the group. Octal with -R only makes sense when everything should end up the same.

What if Laura creates a file inside? It would be born with her primary group (laura), not ic2. We fix that with the setgid bit in level 3 of the exercises.

Does main.c need x to be compiled? No. gcc only needs to read the .c; the x is needed by the program it generates (and gcc creates that one with 755 automatically, because the linker does ask for it).

Setting the permissions on the file and forgetting the folders along the way. If docs/ didn’t have the group’s x, Laura couldn’t read submission.txt even if the file were 644.

Try it yourself

  • Change the program so my notes live outside the shared folder (for example, in ~/private/) and remove the chmod 600 line. Which folder needs protecting now?
  • Add a final-submissions/ folder the group can list but not change. What number do you give it? (Hint: the same as docs/.)

Program 2: testing it with three users

Setting things up is nice, but permissions aren’t checked by looking at them: they’re checked by using them. In this program, Laura (group ic2) and Pablo (from outside) try to do real things with the folder: list, read, build and write to the submission. Then I fix just what’s needed and Laura tries again. We start from the folder exactly as program 1 left it.

The idea

For each attempt we have to answer two questions: which class applies? (Laura is in the group, Pablo is “others”) and which permission is needed, and where? (on the file or on the folder). Then, when something fails, we fix it with symbolic chmod: we don’t want to recalculate the whole number, just add a group w in two places.

Before looking at the output, write down on paper what you think will happen with each command. It’s the best exercise in this topic.

The code

Each # @user line means that from there on the commands are typed by that person in their own terminal:

# @laura — Laura (group ic2) tests the folder from her terminal
ls ic2-lab/docs
cat ic2-lab/docs/private-notes.txt
cd ic2-lab
./build.sh
echo "Laura's part: exercise 2" >> docs/submission.txt
# @pablo — Pablo (another class, not in ic2) has a look around
ls ic2-lab
cat ic2-lab/docs/submission.txt
# @sergio — Sergio opens just enough: writing in the folder and in the submission
chmod g+w ic2-lab ic2-lab/docs/submission.txt
stat -c '%a %A %n' ic2-lab ic2-lab/docs/submission.txt
# @laura — Laura tries again
./build.sh
echo "Laura's part: exercise 2" >> docs/submission.txt
cat docs/submission.txt

Output

laura$ ls ic2-lab/docs
private-notes.txt
submission.txt
laura$ cat ic2-lab/docs/private-notes.txt
cat: ic2-lab/docs/private-notes.txt: Permission denied
laura$ cd ic2-lab
laura$ ./build.sh
/usr/bin/ld: cannot open output file main: Permission denied
collect2: error: ld returned 1 exit status
laura$ echo "Laura's part: exercise 2" >> docs/submission.txt
bash: docs/submission.txt: Permission denied
pablo$ ls ic2-lab
ls: cannot open directory 'ic2-lab': Permission denied
pablo$ cat ic2-lab/docs/submission.txt
cat: ic2-lab/docs/submission.txt: Permission denied
sergio$ chmod g+w ic2-lab ic2-lab/docs/submission.txt
sergio$ stat -c '%a %A %n' ic2-lab ic2-lab/docs/submission.txt
770 drwxrwx--- ic2-lab
660 -rw-rw---- ic2-lab/docs/submission.txt
laura$ ./build.sh
OK
laura$ echo "Laura's part: exercise 2" >> docs/submission.txt
laura$ cat docs/submission.txt
Laura's part: exercise 2

Let’s go attempt by attempt:

  • laura$ ls ic2-lab/docs works: Laura is in the group, and both ic2-lab/ and docs/ have r-x for the group. She sees both names.
  • cat … private-notes.txt: Permission denied. The group triplet is --- (600). Just what we wanted.
  • ./build.sh: the script does start (it has r-x for the group), but it fails inside: ld: cannot open output file main: Permission denied. The linker wants to create main in ic2-lab/, and creating a file means writing in the folder, for which the group has no w. This is the story from the beginning.
  • echo … >> docs/submission.txt: Permission denied, because the group only has r-- on submission.txt.
  • pablo$ ls ic2-lab: cannot open directory. Pablo is “others”, and others have --- on the folder.
  • pablo$ cat ic2-lab/docs/submission.txt: it fails too, and it doesn’t even get to look at the permissions of submission.txt: without x on ic2-lab/ he can’t get through the first door.

Then, a single command from me: chmod g+w ic2-lab ic2-lab/docs/submission.txt. The stat confirms 770 on the folder (7 7 0: 5 + 2 = 7 for the group) and 660 on the submission (4 + 2 = 6). Laura tries again and now she gets OK when building, her line is added to the submission and cat shows it.

The drawing puts every attempt together with the permission that decides it:

What each user can do Real results of p2: Laura lists docs but cannot read the private notes, build or write the submission; Pablo cannot get in. After chmod g+w, Laura builds and writes. Laura and Pablo test the folder Who Command What decides it Laura ls docs   group r-x on docs/ (750) Laura cat private-notes.txt   group — (600) Laura ./build.sh   group has no w on ic2-lab/: ld cannot create main Laura >> docs/submission.txt   group r– on submission.txt (640) Pablo ls ic2-lab   others — on the folder (750) Pablo cat docs/submission.txt   others have no x: he never gets there sergio$ chmod g+w ic2-lab docs/submission.txt Laura ./build.sh   folder 770: now she can create main Laura >> docs/submission.txt   submission.txt 660
Every failure has one specific culprit in a triplet; the violet band is Sergio’s only change.

This is the trace of Laura’s attempts, with the class and the triplet Linux looks at:

AttemptClassWhere it looksTripletNeedsResult
ls docsgroupdocs/r-xr and xYes
cat private-notes.txtgroupthe file---rNo
./build.sh (create main)groupic2-lab/r-xw and xNo
>> docs/submission.txtgroupthe filer--wNo
./build.sh after g+wgroupic2-lab/rwxw and xYes
>> docs/submission.txt after g+wgroupthe filerw-wYes

Questions you might ask

Why does the script run if it fails afterwards? Because they are two different permissions in two different places. Running build.sh checks the file’s x; creating main checks the folder’s w. The first Permission denied you see isn’t always about the first step.

Why g+w and not chmod 770? For the folder it would be the same, but submission.txt goes in the same command, and it needs 660. With symbolic mode, a single command adds the group’s w to both things while respecting what they already had.

Now that the group writes in ic2-lab/, can Laura delete my build.sh? Yes. Even though she can’t change it (the group has r-x on the script), deleting means removing a name from the folder, and she now has w on that folder. That’s the price of opening it up, and the sticky bit fixes it (level 3 of the exercises). My notes are still safe because they’re in docs/, which doesn’t have the group’s w.

Why can’t Pablo even see the names? Because ls needs the folder’s r to read its list, and others have ---.

Fixing a Permission denied by giving permissions to the file named in the message without thinking about which action failed. Laura’s error mentioned main, a file that didn’t even exist: the missing permission was the folder’s.

Try it yourself

  • After the fix, have Laura try rm docs/private-notes.txt. What do you think happens? Does she need w on docs/?
  • Keep the submission at 660 but remove the group’s w from docs/. Can Laura still append lines with >>? And create docs/laura-part.txt?

Program 3: a permission checker and the group’s umask

With several users touching the folder, sooner or later someone leaves something open. This program has two parts: a script, check.sh, that walks through the folder and warns about everything “others” can touch, and a session that causes two slips, detects them, fixes them and sets a umask so new things are born right.

The idea

To know whether “others” can do anything with a file, it’s enough to look at the last octal digit of its permissions: if it’s 0, others can’t do anything; otherwise, there’s a warning. stat -c '%a' gives us the number, and bash can keep just its last character. We walk through everything with find, which lists a folder and everything inside it.

Before you code, think about order: find doesn’t promise any. If the report has to come out the same every time (so you can compare it from one day to the next), you have to sort.

The code

First, the checker script:

#!/bin/bash
# Checks a folder and warns about everything "others" can touch.
folder=${1:-.}                         # the folder passed as an argument (or the current one)
problems=0
# sort: find guarantees no order; sorted, the report always comes out the same
for f in $(find "$folder" | sort); do
    mode=$(stat -c '%a' "$f")          # permissions in octal, for example 640
    others=${mode: -1}                 # the LAST digit is the others one (also works with 4 digits)
    if [ "$others" -ne 0 ]; then       # 0 = others can do nothing; any other value is a warning
        echo "WARNING $mode $f"
        problems=$((problems + 1))
    fi
done
echo "Problems: $problems"

The key line is the one that keeps the others digit:

mode=$(stat -c '%a' "$f")         # permissions in octal, for example 640
others=${mode: -1}                # the LAST digit is the others one (also works with 4 digits)

${mode: -1} is bash syntax for “the last character of the variable” (the space before -1 is required; without it, bash reads it as something else). It works both with 640 and with a four-digit 2770, because the others digit is always the last one. And [ "$others" -ne 0 ] compares numbers (-ne = not equal).

And the session that uses it (it starts from the program 1 folder):

chmod u+x check.sh                      # the script arrives without x: I give it to its owner
chmod 644 ic2-lab/docs/private-notes.txt   # a slip: someone left it readable by everyone
chmod o+rx ic2-lab                      # and another: the folder open to anyone
./check.sh ic2-lab
chmod -R o-rwx ic2-lab                  # -R: removes everything from others in the whole tree
chmod 600 ic2-lab/docs/private-notes.txt
./check.sh ic2-lab
umask 027                               # for whatever we create from now on
touch ic2-lab/docs/minutes.txt
mkdir ic2-lab/tests
stat -c '%a %A %n' ic2-lab/docs/minutes.txt ic2-lab/tests
./check.sh ic2-lab

Output

$ chmod u+x check.sh
$ chmod 644 ic2-lab/docs/private-notes.txt
$ chmod o+rx ic2-lab
$ ./check.sh ic2-lab
WARNING 755 ic2-lab
WARNING 644 ic2-lab/docs/private-notes.txt
Problems: 2
$ chmod -R o-rwx ic2-lab
$ chmod 600 ic2-lab/docs/private-notes.txt
$ ./check.sh ic2-lab
Problems: 0
$ umask 027
$ touch ic2-lab/docs/minutes.txt
$ mkdir ic2-lab/tests
$ stat -c '%a %A %n' ic2-lab/docs/minutes.txt ic2-lab/tests
640 -rw-r----- ic2-lab/docs/minutes.txt
750 drwxr-x--- ic2-lab/tests
$ ./check.sh ic2-lab
Problems: 0

The first check finds exactly the two slips, and this is the trace of its loop:

RoundfmodeothersWarning?problems
1ic2-lab7555yes1
2ic2-lab/build.sh7500no1
3ic2-lab/docs7500no1
4ic2-lab/docs/private-notes.txt6444yes2
5ic2-lab/docs/submission.txt6400no2
6ic2-lab/src7500no2
7ic2-lab/src/main.c6400no2

Seven entries checked, two warnings: 755 → others 5 (r-x) and 644 → others 4 (r--).

The fix uses two commands because there are two different problems. chmod -R o-rwx removes everything from others across the whole tree without touching the owner’s or the group’s permissions (with octal and -R it would have been impossible, because each thing has a different number). But that leaves the notes at 640, readable by the group, so I put them back to 600 separately. The second check gives Problems: 0.

Finally, umask 027: new files come out 640 (666 without ----w-rwx) and folders 750 (777 without the same). Checked: minutes.txt is 640 and tests/ is 750, and the third check is still at zero. The folder no longer depends on someone remembering to remove permissions by hand.

Questions you might ask

Why $(find … | sort) and not find … -exec? Because of the order: find returns entries in the order they’re stored on disk, which can change. With sort the report always comes out the same. (There’s a catch: names with spaces would be split; there aren’t any in this folder.)

Why does the script start without x? Because I copied it (or downloaded it) and it’s born with 644, like any new file. That’s why the session’s first command is chmod u+x check.sh: the same problem as the hook in the theory.

Why is the umask 027 and not 007? 027 also removes the group’s w from whatever is created: Laura will be able to read the new minutes, but not change them unless I decide so. With 007 they’d come out 660 and 770.

Does the umask fix files that already existed? No. It only acts at creation time. That’s why you need both: chmod -R for what was already there and the umask for what’s coming.

chmod -R 640 ic2-lab to “lock down” the folder. It removes the x from every subfolder (nobody can get in anymore, not even the group) and from the script (it no longer runs). To remove permissions from one class across a whole tree, use symbolic mode: chmod -R o-rwx.

find ic2-lab -perm /o=rwx does the same as the loop in a single line: it lists everything that has any permission for others. The script is longer, but it teaches you how to read a mode and lets you customise the warning.

Try it yourself

  • Change the script so it also warns if a .txt file has the x set (hint: the owner digit is ${mode: -3:1}).
  • Make the script, besides warning, count how many entries it has checked and print it at the end.

Common mistakes in this practice

  • Only looking at the file: many Permission denied errors come from the folder (creating or deleting inside, or going through it without x).
  • chmod -R with a number: it applies the same thing to folders and files; folders lose their x or files get an extra one.
  • Forgetting the chgrp: with the sergio group, group permissions are useless to Laura.
  • Testing as root or with sudo: it skips permissions and makes you believe everything works.
  • Relying on the umask for what already exists: it only acts at creation time.
  • Giving the group w on the folder and forgetting that lets them delete other people’s files: you need the sticky bit.

Try it in your terminal

There’s no Python Tutor for this topic, so you do the step by step. This walkthrough only needs your own user, and it checks something that surprises almost everyone: when you’re the owner, Linux only looks at the owner triplet, even if the group one would let you. Type the commands after each $ in a test folder:

$ touch test.txt
$ chmod 070 test.txt
$ id -gn
sergio
$ cat test.txt
cat: test.txt: Permission denied
$ chmod 670 test.txt
$ cat test.txt
$ stat -c '%a %A %n' test.txt
670 -rw-rwx--- test.txt

What to look at:

  1. chmod 070 leaves the file with everything for the group and nothing for the owner or others.
  2. id -gn confirms your primary group is the same as the file’s (the one touch gave it).
  3. cat test.txt fails even though you’re in the group with rwx: since you’re the owner, Linux only looks at the first triplet, and it’s ---. It doesn’t “add up” the group permissions.
  4. With 670 the owner gets rw- back and cat works. The final stat shows -rw-rwx---.

Summary

  • A project group’s folders go to 750 (or 770 if the group must create things inside); files the group reads, to 640; private stuff, to 600.
  • chgrp -R group folder is the first step to sharing: without it, group permissions help nobody.
  • For every Permission denied, ask yourself which action fails and where the permission that decides it lives: on the file or on the folder.
  • Octal is for setting exact permissions; symbolic, for adding or removing without touching the rest (especially with -R).
  • A checker as simple as looking at the last octal digit catches what others can touch.
  • The umask (027 for a group) makes new things be born right; chmod fixes what already exists.

Checklist: have you practised it?

  • Have you run the three programs and checked the outputs?
  • Could you explain why Laura could run build.sh but couldn’t build?
  • Do you know which number to give a folder so the group can get in but not create anything?
  • Did you predict on paper the result of each attempt in program 2 before looking?
  • Have you tried the “Try it yourself” ideas without looking at the theory?
  • Do you know why chmod -R o-rwx is safe and chmod -R 640 isn’t?

If you answered yes to almost everything, you’re ready for the Linux permissions exercises, where I no longer hand you the commands. See you there!

Next step: exercisesLinux permissions — exercises with chmod, umask and a shared submissions folder (and the chmod -R that locked me out of my own folder)

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *