Filesystems

Class: CSCE-313


Notes:

Question 1

Read the section titled Pseudo Filesystems in Learning Modern Linux and answer the following questions.

1) Find the PID of your current shell

Run:

ps

You will see output similar to this:

  PID TTY          TIME CMD
 1234 pts/0    00:00:00 bash
 1250 pts/0    00:00:00 ps

Sometimes your shell might be sh, zsh, or bash, but on many Linux VMs it is usually bash.

How to identify the shell
The shell is the row whose CMD is your shell program, such as:

Do not choose the row for ps, because that is just the command you ran to inspect the processes.

2) Find how many file descriptors are in the shell’s file descriptor table

Once you know the shell PID, look in /proc/<PID>/fd.

Using the example PID 1234, run:

ls /proc/1234/fd

That lists the file descriptors currently open for that shell.

0  1  2  255

To count them, run:

ls /proc/1234/fd | wc -l

If the output is:

4

then the shell has 4 file descriptors open.

Why use /proc?
The /proc filesystem is a pseudo-filesystem. It does not store normal files on disk. Instead, it shows information about the kernel and running processes.

For a specific process:

Each entry in that directory corresponds to one open file descriptor.

Question 2

Use mount to find the device on which the root ext4 filesystem is laid out, and then use fdisk -l to determine the block size of the file system.

1. Find the device for the root ext4 filesystem

Run:

mount

You will see many mounted filesystems. Look for the line where the mount point is / (the root filesystem).

Example output:

/dev/sda1 on / type ext4 (rw,relatime,discard,errors=remount-ro)

From this line we learn:

So the root filesystem is stored on /dev/sda1.

2. Determine the block size with fdisk

Now inspect that device with:

sudo fdisk -l

Find the section for the same device you identified earlier.

Example snippet:

Disk /dev/sda: 16 GiB, 17179869184 bytes, 33554432 sectors
Disk model: PersistentDisk
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 6187B054-63B8-49DD-801F-4A35BBCD6B28

The key line is:

Sector size (logical/physical): 512 bytes / 4096 bytes

This means the filesystem block size is 512 bytes (logical) and 4096 bytes (physical).

Question 3

Find how long the Ubuntu OS on your GCP VM has been up since its last reboot by querying the /proc filesystem.

1. Check the uptime using /proc

Run the following command:

cat /proc/uptime

Output:

1589.23 3095.49

The file contains two numbers (in seconds):

  1. Total time the system has been running since the last boot
  2. Total time all CPUs have been idle

For the question, you only need the first number.

1589.23 seconds

Question 4

Print "Hello World" on your terminal by writing "Hello World" to the file that the terminal device represents.

Command to run

Run:

echo "Hello World" > /dev/tty

This will print:

Hello World

to your terminal.

Explanation

In Linux, devices are represented as files inside the /dev directory.

/dev/tty is a special device file that represents the current terminal session.

That means any data written to /dev/tty is displayed directly on the terminal.

Question 5

What is the significance of the x bit (execute) on a directory in Linux? Create a directory, say f, and create three files inside f: say these files are abc. Change the permission of f to 400 using chmod 400 f. Can you still do ls f? Can you do ls -l f? Explain what you see.

1. What the execute (x) bit means on a directory

For directories, the execute permission (x) allows users to enter and traverse the directory.

Specifically, the x bit allows you to:

Without the execute permission, even if you know the file names inside the directory, you cannot access them.

Directory permissions behave differently than file permissions:

Permission Meaning for directories
r Allows listing the directory contents
w Allows creating, deleting, or renaming files
x Allows entering/traversing the directory

2. Create the directory and files

Run the following commands:

mkdir f
touch f/a f/b f/c

This creates:

f/
 ├── a
 ├── b
 └── c

3. Change permissions

Now remove execute permission:

chmod 400 f

Check permissions:

ls -ld f

Example output:

dr-------- 2 user user 4096 Mar 8 12:00 f

Meaning:

4. Try listing the directory

Run:

ls f

Output:

ls: cannot access 'f/a': Permission denied
ls: cannot access 'f/b': Permission denied
ls: cannot access 'f/c': Permission denied
a  b  c

Why this happens?

5. Try long listing

Run:

ls -l f

Output:

ls: cannot access 'f/a': Permission denied
ls: cannot access 'f/b': Permission denied
ls: cannot access 'f/c': Permission denied
total 0
-????????? ? ? ? ?            ? a
-????????? ? ? ? ?            ? b
-????????? ? ? ? ?            ? c

Why?

6. Takeaway

The execute bit is required to access files inside a directory.

Even if the directory has read permission, you cannot access its contents without the execute permission.

The execute (x) bit on a directory allows a user to traverse the directory and access files within it. Without the execute permission, the system cannot access the files inside the directory even if read permission is present.

Question 6

Interpret the following mode bits in octal on a filesystem object. What type of object is it?

Convert to octal

In Linux, the mode bits of a filesystem object encode two things:

  1. The file type
  2. The permission bits

These are typically stored as a 6-digit octal number:

TTPPPP

Where:

Common file type values:

Octal Prefix File Type
010 Regular file
004 Directory
012 Symbolic link
006 Block device
002 Character device
001 FIFO (named pipe)
014 Socket

The last three digits represent permissions:

Digit Meaning
Owner permissions for owner
Group permissions for group
Others permissions for others

Permission values:

Value Meaning
4 read
2 write
1 execute

1. Mode: 0100664

File type

010 → regular file

Permissions

664

Breakdown:

User Permission
Owner rw- (6)
Group rw- (6)
Others r– (4)

Result

2. Mode: 010664

This is essentially the same value but written without the extra zero padding.

File type

010 → regular file

Permissions

664

Result

So 0100664 and 010664 represent the same thing, just formatted differently.

3. Mode: 0101664

File type

010 → regular file

Special bits

Permissions

664
User Permission
Owner rw-
Group rw-
Others r–
Result

Summary

Mode Object Type Permissions Notes
0100664 Regular file rw-rw-r– Standard format
010664 Regular file rw-rw-r– Same as above
0101664 Regular file rw-rw-r– Sticky bit set

In Unix-like systems, octal values representing file modes are often interpreted as part of the st_mode field, which combines the file type and the permission bits (read/write/execute). While both 0100664 and 010664 represent a regular file, the difference lies in how they are interpreted by certain tools (like git or ls internals) regarding their 16-bit or 32-bit representation.

Key Differences:

Octal Value Type Bits Permissions Description
0100664 100 (Reg) 664 (rw-rw-r--) Explicitly sets regular file, read/write to owner/group, read to others
010664 10 (Reg) 664 (rw-rw-r--) Often interpreted as regular file by specific tools (e.g., git)
Note: In octal notation, the leading 0 simply indicates that the number is octal, not part of the permission value.

Question 7

Do problem 1 under Weekly-HW → Filesystems on the class website.W

What are the possible contents of the file foo when the following code is executed? Read the manual page entry for the Unix command od and examine the contents of the file after executing the program using od -c -A d foo. What are the permission bits on foo if the file did not exist when the code is executed. What if the file already existed?

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
  int fd;
  char *fname = "foo";

  fd = open(fname, O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
  write(fd, "pqrs", 2);
  write(fd, "jklmn", 6);

  close(fd);
  return 0;
}

What the program does

fd = open(fname, O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU);
write(fd, "pqrs", 2);
write(fd, "jklmn", 6);

What od -c -A d foo would show

Shows the file byte-by-byte as characters, with decimal offsets.

Output:

$ od -c -A d foo
0000000   p   q   j   k   l   m   n  \0
0000008

Possible contents of foo

There is really one intended result here, assuming all system calls succeed:

pqjklmn\0

Permission bits if foo did not exist

When the file does not already exist, open(..., O_CREAT, S_IRWXU) creates it with mode:

S_IRWXU = 0700

That means:

So the created file would have permissions:

0700

or symbolically:

rwx------

Important detail: umask

In practice, the actual permissions are affected by the process’s umask.

So the real created mode is:

0700 & ~umask

What if foo already existed?

If foo already exists:

So the contents still become:

pqjklmn\0

But the permission bits do not change just because of open with O_CREAT.

The program opens foo for writing, creates it if needed, and truncates it if it already exists. The first call: write(fd, "pqrs", 2); writes only the first two characters, so it writes pq. The second call: write(fd, "jklmn", 6); writes six bytes: j, k, l, m, n, and the terminating null byte \0. Therefore the final contents of foo are: pqjklmn\0

Using od -c -A d foo:

0000000   p   q   j   k   l   m   n  \0
0000008

Question 8

Do Problem 4 under Weekly-HW -> Filesystems on the class website

What permission bits does a file have if it's readable and executable by everyone except for the owner of the file?

Calculating permissions

We need permissions such that:

Recall the permission values:

Permission Value
read (r) 4
write (w) 2
execute (x) 1
Owner permissions

Owner should have no read and no execute, and no write mentioned either. So owner permissions are:

---

Octal value:

0
Group permissions

Group must have:

r-x

That is

4 + 1 = 5
Other permissions

Others must also have:

r-x

Octal value:

4 + 1 = 5

Final octal permission

Octal value:

055

Symbolic form:

---r-xr-x

Question 9

The setuid and setgid bits. The setuid (set user ID) and setgid (set group ID) bits are special permission bits in Linux that affect how processes execute:

These bits appear in the execute position: s for setuid/setgid. The uppercase letter S indicates the corresponding execute bit is not set. In other words, $$s=x+\text { setuid }$$
while $$S=\bar{x}+\text { setuid }$$
What is the octal representation of the file permissions -rwsr-xr-x?

Options:

Overall explanation:

1. Identify the file type

The first character:

-

2. Break the permissions into groups

rws r-x r-x
Section Meaning
rws owner permissions
r-x group permissions
r-x others permissions

3. Convert the normal permissions to octal

Using:

Permission Value
r 4
w 2
x 1

Owner: rws

s means:

Note:

Normal permissions:

rwx = 4 + 2 + 1 = 7

Group: r-x

4 + 1 = 5

Others: r-x

4 + 1 = 5

So the base permissions are:

755

4. Add the special bit

s in the owner execute position means the setuid bit is set.

Setuid value:

4000

So we add it:

4000 + 0755 = 4755

Answer: 4755

Question 10

Consider the following program.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void) {
  if(open("/foo/bar", O_RDONLY) < 0) exit(1);
}

The program exits with a value of 1 if it cannot open the file **/foo/bar**. How can we change this program to print a more specific message that indicates the reason for failure to open the file? Hint: use **perror** (man 3 perror).

Consider the following program.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void) {
  if(open("/foo/bar", O_RDONLY) < 0) exit(1);
}

The program exits with a value of 1 if it cannot open the file /foo/bar. How can we change this program to print a more specific message that indicates the reason for failure to open the file? Hint: use perror (man 3 perror).

Understanding current program

The program currently exits silently if open() fails:

if(open("/foo/bar", O_RDONLY) < 0) exit(1);

However, when a system call like open() fails, it sets a global variable called errno that contains the reason for the failure (for example: file not found, permission denied, etc.).

The function perror() prints a human-readable message describing the error stored in errno.

Modified program using perror

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void) {
    if (open("/foo/bar", O_RDONLY) < 0) {
        perror("open");
        exit(1);
    }
}

How it works

  1. open("/foo/bar", O_RDONLY) tries to open the file in read-only mode.
  2. If the call fails, it returns -1.
  3. The kernel sets errno to indicate the reason for failure.

perror("open") prints the message:

open: <error description>

Example outputs might be:

open: No such file or directory

or

open: Permission denied

The program can be modified to print a more specific error message by using the perror() function. When a system call such as open() fails, it sets the global variable errno to indicate the reason for the failure. The perror() function reads errno and prints a descriptive error message. By calling perror("open") when open() returns a negative value, the program prints the reason why the file /foo/bar could not be opened.

Question 11

Which bit pattern represents a directory with permissions drwxr-xr-x? Note that the leading 0 in the choices indicates that the # is specified in octal. You can use an online octal to binary converter such as the one at www.rapidtables.com to simplify your life.

Options:

Overall explanation:

1. Identify the file type

The first character:

d

In Linux mode bits, the directory type is represented by:

040000

2. Convert the permission bits

Permissions:

rwx r-x r-x

Convert each group to octal:

Permission Calculation Octal
rwx 4+2+1 7
r-x 4+1 5
r-x 4+1 5

So the permission part is:

0755

3. Combine file type and permissions

Combined mode:

040755

With the leading zero indicating octal formatting:

0040755

Question 12

Consider this code that creates a file:

int fd = open("newfile.txt", O_CREAT | O_WRONLY, 0644);

If the process's umask is set to 0022, what will be the actual permissions of the created file? Explain your reasoning. See APUE Section 4.8 for an explanation of umask. Specifically, for the open system call, the actual permission bits used to create the new file are $$\text { mode } \wedge \overline{u m a s k}$$
Notes:

1. Given values

Requested mode in the program:

0644

This corresponds to:

rw-r--r--
Owner Group Others
rw- r– r–

Process umask:

0022

This means:

----w--w-

2. Compute the complement of the umask

~0022 = 0755

In permission form:

rwxr-xr-x

3. Apply the formula

Compute in octal:

 0644
&0755
-----
 0644

4. Final result

The resulting permissions remain:

0644

Symbolically

rw-r--r--
Why the permissions do not change

The umask only removes permissions that were requested. Since the requested mode 0644 already does not include write permission for group and others, the umask 0022 has no additional effect.

The file will be created with permissions 0644 (rw-r--r--). The final mode is computed as:

mode & ~umask
0644 & ~0022
0644 & 0755 = 0644

The bitwise AND operation (&) compares each bit of two numbers and returns a 1 only if both bits are 1. To see why 0644 & 0755=0644, we must convert the octal digits into their 3-bit binary equivalents.

Convert Octal to Binary

In octal, each digit represents 3 bits (4, 2, and 1):

Perform the Bitwise AND

Now, we compare the bits vertically. A 1 is produced only if there is a 1 in both rows:

Position 0644 (Binary) 0755 (Binary) Result (Binary)
Owner 1 1 0 & 1 1 1 = 1 1 0
Group 1 0 0 & 1 0 1 = 1 0 0
Other 1 0 0 & 1 0 1 = 1 0 0

Full Binary Result: 110 100 100

Convert Back to Octal

Finally, convert the binary result groups back into octal digits:

Final Octal: 0644

Question 13

A file has permissions -rwsr-sr-x with octal 6755. What happens when a regular user executes this file? You can read more about setuid and setguid in APUE Section 4.4.

Options:

Overall explanation:

Reasoning

This specific octal mode, 6755, is a combination of standard permissions and two special "set" bits. Here is how it breaks down:

1. The Special Bits (6xxx)

The leading 6 in the octal representation is the sum of two special bits:

2. The Execution Behavior

When a regular user executes this file, the operating system changes the Effective User ID (EUID) and Effective Group ID (EGID) for that specific process:

Comparison of Options

Scenario Mode Bits Behavior
Normal Execution 0755 Runs as the user who executed it.
SetUID Only 4755 Runs as the file owner.
SetGID Only 2755 Runs as the file's group.
SetUID + SetGID 6755 Runs as both the file owner and the file group.

Question 14

What are all the groups that you belong to on your GCP VM? Run the id command. How many groups are present on your VM? All the groups are defined in the /etc/group file. Read more about group id's in APUE Section 6.5.

Using the id command

macc@instance-csce-313-1:~/Sandbox/filesystems$ id
uid=1002(macc) gid=1003(macc) groups=1003(macc),4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),119(netdev),120(lxd),1000(ubuntu),1001(google-sudoers)

Count total groups on your VM:

macc@instance-csce-313-1:~/Sandbox/filesystems$ cat /etc/group | wc -l
64

Question 15

What does the library function makecontext (man 3 makecontext) do? Summarize in a couple of sentences. Don't copy the description from the manual page.

Answer:
makecontext is used to manually set up a user-level execution context (a thread-like state) within a specific memory region. It modifies a context structure—previously initialized by getcontext—so that when it is later activated by setcontext or swapcontext, the program begins executing a specified function with a custom-allocated stack.

Essentially, it "primes" a context to behave like a new entry point, allowing developers to implement cooperative multitasking or "coroutines" by jumping between different execution flows within a single process.

Key Requirements for makecontext

To use it effectively, you must manually handle the following before the call:

makecontext sets up a user-level execution context so that when the context is activated, it starts running a specific function. It's typically used with getcontext and setcontext/swapcontext to implement user-level threads or coroutines by letting you switch execution between different functions with their own stacks.

Question 16

Given the following code snippet, what will be printed? Read the man page for the system call stat in section 2 of the man pages. You can find more about S_ISREG in APUE Section 4.3.

struct stat sb;
stat("/usr/bin/sudo", &sb);

if (S_ISREG(sb.st_mode)) {
    printf("Regular file\n");
}
if (sb.st_mode & S_ISUID) {
    printf("Setuid bit is set\n");
}

1. S_ISREG(sb.st_mode)

This macro checks whether the file is a regular file.

So it prints:

Regular file

2. sb.st_mode & S_ISUID

S_ISUID checks whether the setuid bit is set.

So this condition is also true, and it prints:

Setuid bit is set

Final output:

Regular file
Setuid bit is set

Question 17

Analyze this code that modifies the permissions:

struct stat sb;
stat("myfile", &sb);
chmod("myfile", sb.st_mode | S_IXUSR | S_IXGRP);

What will happen to the file's permissions? Read the manual page for the system call chmod in section 2.

1. stat("myfile", &sb);

This retrieves the file's metadata and stores it in sb.

The current permission bits are stored in:

sb.st_mode

2. chmod("myfile", sb.st_mode | S_IXUSR | S_IXGRP);

The new mode is computed using the bitwise OR (|) operator.

Using | means those bits are added (turned on) while keeping all existing permission bits unchanged.

Result

Example:
If the file originally had:

rw-r--r--   (0644)

after the code runs it becomes:

rwxr-xr--   (0754)

The program keeps the current permissions of myfile but ensures that execute permission is set for the owner and the group.

Question 18

A directory has permissions drwxrwsr-x (octal 2775). When a user creates a file in this directory, what group will own the new file? See Resources → Filesystems → u+/g+/t for directories on the class website.

Options:

Overall explanation:

Question 19

Which of the following file mode bits has the value 0001 in octal?

Options:

Overall explanation:

the octal value:

0001

means:

The value 1 corresponds to the execute bit.

Since it appears in the others position, it represents: execute permission for others.

Apparently they were putting padding and not specifying file type so it was instead referring to the owner execute permission?

Question 20

Consider a file with permissions -rwsr-Sr-x (note the capital ‘S’). What does the capital ‘S’ indicate, and what security implications might this have?

What the capital S means

A capital S means the setgid bit is set but the execute bit for the group is not set.

Normally:

So in r-S:

Security implication

Because the group execute bit is not set, the file cannot be executed by the group, so the setgid bit will not actually take effect during execution.

This usually indicates a misconfigured permission, where the special bit is set but the file cannot be executed by that group. It doesn’t grant extra privileges but may signal incorrect permission settings that should be fixed.

Question 21

Examine the following code snippet, and read the manual page for the stat system call.

struct stat sb;
  
stat("/home/user/myfile", &sb);
if (sb.st_mode & S_IRUSR) {
    printf("Owner has read permission\n");
}

What does the S_IRUSR constant represent, and what operation is being performed with the & operator?

The S_IRUSR Constant

The S_IRUSR constant and the & operator work together to "query" the file's metadata to see if a specific permission is active.

The S_IRUSR constant represents the Read permission bit for the Owner of the file.

In the <sys/stat.h> header file, this is defined as the octal value 0400. When converted to binary, it is a "mask" where only a single bit is turned on (the bit corresponding to the owner's read access).

The Bitwise & (AND) Operator

The & operator performs a Bitwise AND operation. In this context, it is being used as a Bitmasking tool.

When you perform sb.st_mode & S_IRUSR, the CPU compares the two values bit-by-bit:

Visualization of the Logic

Imagine myfile has permissions 0644 (rw-r--r--):

Variable Octal Binary Bits
sb.st_mode 0644 1 1 0 1 0 0 1 0 0
S_IRUSR 0400 1 0 0 0 0 0 0 0 0
Result of & 0400 1 0 0 0 0 0 0 0 0

Because the result is non-zero, the if statement evaluates to true, and the message is printed.

Why not use '=='?

You cannot easily use sb.st_mode == S_IRUSR because st_mode contains all the permissions (and the file type). Using '==' would only be true if the file had only owner-read permissions and absolutely nothing else. The & operator allows you to isolate and check one specific bit regardless of what the other bits are doing.

The S_IRUSR constant is a bitmask (with an octal value of 0400) that specifically represents the Read permission for the file's Owner.

The & (Bitwise AND) operator is used to "filter" the file's mode bits. It checks if that specific "Read" bit is flipped "on" in the file's actual permissions.

If the bit is there, the result is a non-zero value (which C treats as true), and the printf runs. If the bit is missing, the result is 0 (which is false), and nothing happens. It’s basically a digital "yes/no" check for that one specific permission.