File System Level-3 in Unix/Linux

File system level-3 supports mount, umount of file systems and file protection.

Mount Operation

The command

mount filesys mount_point

mounts a file system to a mount_point directory. It allows the file system to include other file systems as parts of the existing file system. The data structures used in mount are the MOUNT table and the in-memory minode of the mount_point directory. The algorithm of mount is

1. Algorithm of mount

/********* Algorithm of mount **********/

  1. If no parameter, display current mounted file systems;
  2. Check whether filesys is already mounted:

The MOUNT table entries contain mounted file system (device) names and their mounting points. Reject if the device is already mounted.

If not, allocate a free MOUNT table entry.

  1. Open the filesys virtual disk (under Linux) for RW; use (Linux) file descriptor as new dev. Read filesys superblock to verify it is an EXT2 FS.
  2. Find the ino, and then the minode of mount_point:

ino = getino(pathname);    // get ino:

mip = iget(dev, ino);      // load its inode into memory;

  1. Check mount_point is a DIR and not busy, e.g. not someone’s CWD.
  2. Record new dev and filesys name in the MOUNT table entry, store its ninodes, nblocks, bmap, imap and inodes start block, etc. for quick access.
  3. Mark mount_point minode as mounted on (mounted flag = 1) and let it point at the MOUNT table entry, which points back to the mount_point minode.

Umount Operation:

The operation umount filesys

un-mounts a mounted file system. It detaches a mounted file system from the mounting point, where filesys may be a virtual diak name or a mounting point directory name. The algorithm of umount is

2. Algorithm of umount

/******************* Algorithm of umount ***********************/

  1. Search the MOUNT table to check filesys is indeed mounted.
  2. Check whether any file is active in the mounted filesys; If so, reject;
  3. Find the mount_point in-memory inode, which should be in memory while it’s mounted on. Reset the minode’s mounted flag to 0; then iput() the minode.

To check whether a mounted file system contains any files that are actively in use, search the in-memory minodes for any entry whose dev matches the dev number of the mounted file system.

3. Cross Mounting Points

While it is easy to implement mount and umount, there are implications. With mount, we must modify the getino(pathname) function to support crossing mount points. Assume that a file system, newfs, has been mounted on the directory /a/b/c/. When traversing a pathname, mount point crossing may occur in both directions.

  • Downward traversal: When traversing the pathname /a/b/c/x, once we reach the minode of /a/b/c, we should see that the minode has been mounted on (mounted flag=1). Instead of searching for x in the INODE of /a/b/c, we must
    • Follow the minode’s mntPtr pointer to locate the mount table entry.
    • From the mount table’s dev number, get its root (ino=2) INODE into memory.
    • Then continue search for x under the root INODE of the mounted device.
  • Upward traversal: Assume that we are at the directory /a/b/c/x/ and traversing upward, e.g. cd ../ ../, which will cross the mount point /a/b/c. When we reach the root INODE of the mounted file system, we should see that it is a root directory (ino=2) but its dev number differs from that of the real root, so it is not the real root yet. Using its dev number, we can locate its mount table entry, which points to the mounted minode of /a/b/c/. Then, we switch to the minode of /a/b/c/ and continue the upward traversal. Thus, crossing mount point is like a monkey or squirrel hoping from one tree to another and then back.

Since crossing mounting points changes the device number, a single global device number becomes inadequate.We must modify the getino() function as

int getino(char *pathname, int *dev)

and modify calls to getino() as

int dev;                    // local in function that calls getino()

if (pathnme[0]==’/’)

dev = root->dev;          // absolute pathname

else

dev = running->cwd->dev; // relative pathname

int ino = getino(pathname, &dev); // pass &dev as an extra parameter

In the modified getino() function, change *dev whenever the pathname crosses a mounting point. Thus, the modified getino() function essentially returns (dev, ino) of a pathname with dev being the final device number.

4. File Protection

In Unix/Linux, file protection is by permission bits in the file’s INODE. Each file’s INODE has an i_mode field, in which the low 9 bits are permissions. The 9 permission bits are

The first 3 bits apply to the owner of the file, the second 3 bits apply to users in the same group as the owner, and the last 3 bits apply to all others. For directories, the x bit indicates whether a process is allowed to go into the directory. Each process has a uid and a gid. When a process tries to access a file, the file system checks the process uid and gid against the file’s permission bits to determine whether it is allowed to access the file with the intended mode of operation. If the process does not have the right permission, the access will be denied. For the sake of simplicity, we may ignore process gid and use only the process uid to check access permission.

5. Real and Effective uid

In Unix/Linux, each process has a real uid and an effective uid. The file system checks the access rights of a process by its effective uid. Under normal conditions, the effective uid and real uid of a process are identical. When a process executes a setuid program, which has the setuid bit in the file’s i_mode field turned on, the process’ effective uid becomes the uid of the program. While executing a setuid program, the process effectively becomes the owner of the program file. For example, when a process executes the mail program, which is a setuid program owned by the superuser, it can write to the mail file of another user. When a process finishes executing a setuid program, it reverts back to the real uid. For simplicity, we shall ignore effective uid also.

6. File Locking

File locking is a mechanism which allows a process to set locks on a file, or parts of a file to prevent race conditions when updating files. File locks can be either shared, which allows concurrent reads, or exclusive, which enforces exclusive write. File locks can also be mandatory or advisory. For example, Linux supports both shared and exclusive files locks but file locking is only advisory. In Linux, file locks can be set by the fcntl() system call and manipulated by the flock() system call. For simplicity, we shall assume only a very simple kind of file locking. When a process tries to open a file, the intended mode of operation is checked for compatibility. The only compatible modes are READs. If a file is already opened for updating mode, i.e. WR|RW|APPEND, it cannot be opened again. However, this does not prevent related processes, e.g. a parent process and a child process, from modifying the same file that’s opened by the parent, but this is also true in Unix/Linux. In that case, the file system can only guarantee each write operation is atomic but not the writing order of the processes, which depends on process scheduling.

7. Programming Project #3: Implementation of Complete File System

Programming Project #3 is to complete the file system implementation by including functions in the Level-3 of the file system. Sample solution of the file system is available for download at the book’ s website. Source code of the complete file system is available for instructors upon request.

Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.

Leave a Reply

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