As with previous assignments, we will be using GitHub to distribute skeleton code and collect submissions. Please refer to our Git Workflow guide for more details. Note that we will be using multiple tags for this assignment, for each deliverable part.

Code Style

There is a script in the skeleton code named It is a wrapper over linux/scripts/, which is a Perl script that comes with the linux kernel that checks if your code conforms to the kernel coding style.

Execute to see if your code conforms to the kernel style – it’ll let you know what changes you should make. You must make these changes before pushing a tag. Passing with no warnings and no errors is required for this assignment.

Part 0: Formatting and mounting disks

A loop device is a pseudo-device that makes a file accessible as a block device. Files of this kind are often used for CD ISO images. Mounting a file containing a file system via such a loop mount makes the files within that file system accessible.

Exploring ext2

  1. Create a loop device, build & mount an ext2 filesystem, and try creating directories and files. Below is a sample session you can follow that starts from my home directory. It goes without saying that you need to understand what’s going on at each step. Look at the man pages. Google stuff.

    $ sudo su
    # dd if=/dev/zero of=./ext2.img bs=1024 count=100
    100+0 records in
    100+0 records out
    102400 bytes (102 kB, 100 KiB) copied, 0.000600037 s, 171 MB/s
    # losetup --find --show ext2.img
    # mkfs -t ext2 /dev/loop0
    mke2fs 1.44.5 (15-Dec-2018)
    Creating filesystem with 100 1k blocks and 16 inodes
    Allocating group tables: done                            
    Writing inode tables: done                            
    Writing superblocks and filesystem accounting information: done
    # mkdir mnt
    # mount /dev/loop0 ./mnt
    # cd mnt
    # ls -al
    total 17
    drwxr-xr-x  3 root root  1024 Apr 21 02:22 .
    drwxr-xr-x 37 hans hans  4096 Apr 21 02:22 ..
    drwx------  2 root root 12288 Apr 21 02:22 lost+found
    # mkdir sub2
    # ls -al
    total 18
    drwxr-xr-x  4 root root  1024 Apr 21 02:23 .
    drwxr-xr-x 37 hans hans  4096 Apr 21 02:22 ..
    drwx------  2 root root 12288 Apr 21 02:22 lost+found
    drwxr-xr-x  2 root root  1024 Apr 21 02:23 sub2
    # cd sub2
    # ls -al
    total 2
    drwxr-xr-x 2 root root 1024 Apr 21 02:23 .
    drwxr-xr-x 4 root root 1024 Apr 21 02:23 ..
    # mkdir sub2.1
    # ls -al
    total 3
    drwxr-xr-x 3 root root 1024 Apr 21 02:24 .
    drwxr-xr-x 4 root root 1024 Apr 21 02:23 ..
    drwxr-xr-x 2 root root 1024 Apr 21 02:24 sub2.1
    # touch file2.1
    # ls -al
    total 3
    drwxr-xr-x 3 root root 1024 Apr 21 02:24 .
    drwxr-xr-x 4 root root 1024 Apr 21 02:23 ..
    -rw-r--r-- 1 root root    0 Apr 21 02:24 file2.1
    drwxr-xr-x 2 root root 1024 Apr 21 02:24 sub2.1
    # cd ../../
    # umount mnt/
    # losetup --find
    # losetup --detach /dev/loop0
    # losetup --find
    # ls -al mnt/
    total 8
    drwxr-xr-x  2 root root 4096 Apr 21 02:22 .
    drwxr-xr-x 37 hans hans 4096 Apr 21 02:22 ..
  2. In the sample session shown above, files and directories are created. Make sure you see the number of links each file or directory has, and make sure you understand why.

    Also try creating some hard links and symlinks. Make sure you understand how they affect the link counts.

Preparing your module build environment

In this assignment, we provide a compiled reference implementation of PantryFS for you to test your implementation against.

We provide a reference implementation (ref/pantry.ko), compiled against the 4.19.50-cs4118 kernel. This version was the pristine kernel you built in HW4 part1 as your fallback. This version was built with full kernel configurations (without make localmodconfig). You should boot into this kernel for the rest of this assignment.

If you don’t have the 4.19.50-cs4118 pristine kernel, you should go back to the HW4 part1 instructions and build it again (making sure that the CONFIG_BLK_DEV_LOOP option is set to y). We’ve provided a config-cs4118 file that you can use as your .config when rebuilding your pristine kernel.

If your pristine kernel is not exactly named 4.19.50-cs4118, you will get a versioning error when you try to insert the reference implementation. We recommend going back and rebuilding your pristine kernel, but you may want to first experiment with passing the --force-vermagic flag. Note that this is potentially dangerous.

Exploring PantryFS

In this part, we will mount a loop device and format it as PantryFS. Then, we’ll use the reference implementation to interact with our newly formatted disk.

  1. Create a disk image and assign it to a loop device:

    $ dd bs=4096 count=200 if=/dev/zero of=~/pantry_disk.img
    # losetup --find --show ~/pantry_disk.img
    • This will create the file pantry_disk.img and bind it to an available loop device, probably /dev/loop0. Now, /dev/loop0 can be used as if it were a physical disk, and the data backing it will be stored in pantry_disk.img.
  2. Format the disk as PantryFS.

    # ./format_disk_as_pantryfs /dev/loop<N>
    • The skeleton code contains a formatting program called format_disk_as_pantryfs.c. Inspect it and then run it as shown above.
  3. Mount the disk at /mnt/pantry using the reference implementation:

    # mkdir /mnt/pantry
    # insmod pantry.ko
    # mount -t pantryfs /dev/loop<N> /mnt/pantry
  4. Explore the newly created filesystem. Edit hello.txt and create some new files.

    • You may encounter a warning while using vim. This is the expected behavior, since vim creates swapfiles that exceed the size limit of our filesystem.


Part 1: Creating additional files upon formatting

The formatting utility creates the new filesystem’s root directory and places hello.txt in that directory. In this part, we will create another directory and file.

PantryFS Specification

View slides


  1. Read the provided formatting utility, format_disk_as_pantryfs.c. Make sure you understand the on-disk format and what each line contributes toward creating the filesystem. Test your understanding by thinking about the following questions:

    • The program writes two inodes for the root directory and hello.txt, but does not zero out the rest of the inode block. Can you convince yourself that this is OK?

    • However, for filling out the root directory block, the program ensures that the unused portion of the block is zeroed out. Can you see why this is necessary?

  2. Extend the program to create a subdirectory called members. The directory should contain a single file, names.txt, that lists the names of your team members.

    • To make things easier, consider starting with an empty members directory. Once you have that working, create names.txt.

    • Be sure to set the directories’ link counts correctly.

  3. Create and format a new disk using your modified program. Use the reference implementation, pantry.ko, to verify that the new file and directory were created correctly. You can use the stat command to see the size, inode number, and other properties.



To submit this part, push the hw8p1handin tag with the following:

$ git tag -a -m "Completed hw8 part1." hw8p1handin
$ git push origin master
$ git push origin hw8p1handin

Part 2: The filesystem awakens

The rest of this assignment is structured to guide you while implementing your filesystem driver. Each part represents a milestone toward a working driver, forcing you to develop incrementally. It also allows the graders to award partial credit if you don’t get the whole thing working in the end.

In this part, we will begin by writing the code that mounts disks. Later, we’ll add the ability to read files, modify existing files, create new files, and delete them. There is an optional part that adds support for creating and removing directories.

Don’t worry about concurrency in your filesystem driver. That is, you may assume that each PantryFS disk will only have one thread of one program performing operations at any given time.

You are welcome to search the web for info. The Documentation/ directory in the Linux source tree contains a wealth of information as well. Here are some resources that might be useful:

  1. LKD chapter 13

  2. LKD chapter 14: pages 289 - 294

  3. Page cache overview slides

  4. Linux Kernel Internals chapter 3 (Virtual Filesystem)

  5. Documentation/filesystems/vfs.txt

  6. Linux VFS tutorial at

    • Note that the tutorial was written for an older version of the Linux kernel, so its code doesn’t build on our version. Nevertheless, the article provides a good high-level overview of how to interact with VFS.


  1. Read the skeleton code starting from its entry point, pantryfs_init(). This module is written so that it can be loaded simultaneously with the reference implementation. How is this accomplished?

  2. Unmount your PantryFS drive and try to mount it with the skeleton code instead. The following error message indicates that you are no longer mounting with the reference implementation:

    mount: /mnt/pantry: mount(2) system call failed: Function not implemented.

    This error is returned from pantryfs_fill_super() via pantryfs_mount().

  3. Implement pantryfs_fill_super() so that you can mount disks. Note that we’re only interested in making the mount and umount commands work cleanly in this part. We won’t be reading any files or directories at this time.

    • Use sb_set_blocksize() to ensure that the block layer reads blocks of the correct size.

    • Read the PantryFS superblock and inodes. Assign them to an instance of struct pantryfs_sb_buffer_heads. Store this struct in the s_fs_info member of the VFS superblock. This way, we can always find the PantryFS superblock and inodes by following the trail of pointers from the VFS superblock.

      The following diagram shows the relationship between these structs after this step.

      PantryFS fill_super

    • You will have to fill out some additional members of the VFS superblock structure, such as the magic number and pointer to the ops struct.

    • Use iget_locked() to create a new VFS inode for the root directory. Read the kernel source to learn what this function does for you and get some hints on how you’re supposed to use it. The only metadata you need to set is the mode. Make the root directory drwxrwxrwx for now.

    • After creating an inode for the root directory, you need to create a dentry associated with it. Make the VFS superblock point to the dentry.

    • Make sure to handle errors by returning an appropriate error code. For example, what if someone asks you to mount a filesystem that isn’t PantryFS?



To submit this part, push the hw8p2handin tag with the following:

$ git tag -a -m "Completed hw8 part2." hw8p2handin
$ git push origin master
$ git push origin hw8p2handin

Part 3: Listing the contents of the root directory


  1. In the previous part, we created a VFS inode without associating it with the corresponding PantryFS inode from disk. Update your code to associate the root VFS inode with the root PantryFS inode.

    • Use the i_private member of the VFS inode to store a pointer to the PantryFS inode. All of the PantryFS inodes live in the inode store that we read from disk in the previous section.

    • Note that you must set the i_sb and i_op members so that VFS can identify which filesystem the inode belongs to.

    • Consult the diagram in the PantryFS Specification section.

  2. Add support for listing the root directory.

    • You should be able to run ls and ls -a. Here’s sample session:

      # ls /mnt/pantry
      hello.txt  members
      # ls -a /mnt/pantry
      .  ..  hello.txt  members
      # ls /mnt/pantry/members
      ls: cannot access '/mnt/pantry/members': No such file or directory

      Note that we do not support listing the contents of a subdirectory yet.

    • The VFS framework will call the iterate member of the struct file_operations. Inside your iterate implementation, use dir_emit() to provide VFS with the contents of the requested directory. VFS will continue to call iterate until your implementation returns without calling dir_emit().

    • You can use the ctx->pos variable as a cursor to the directory entry that you are about to emit. (Note that the dir_emit_dots() function modifies ctx->pos.)

    • The following is an excerpt from the output of strace ls /usr/bin > /dev/null:

      getdents64(3, /* 1003 entries */, 32768) = 32744
      getdents64(3, /* 270 entries */, 32768) = 8888
      getdents64(3, /* 0 entries */, 32768)   = 0
      close(3)                                = 0

      The ls program first opens the /usr/bin directory file. Then, it calls getdents64() three times to retrieve the list of 1,273 files in /usr/bin. Finally, ls closes the directory file.

      Each call to getdents64() will result in one call to iterate_dir(), which in turn will call your iterate implementation. Consequently, your iterate implementation should call dir_emit() until the given buffer is full.

    • Running ls -l might print error messages because the ls program is unable to stat the files. This is the expected behavior for this part.



To submit this part, push the hw8p3handin tag with the following:

$ git tag -a -m "Completed hw8 part3." hw8p3handin
$ git push origin master
$ git push origin hw8p3handin

Part 4: Accessing subdirectories

In this part, we’ll implement the lookup function of inode_operations. The kernel calls this function repeatedly as it walks through a filepath such as /a/b/c/d/e/f.txt. For example, once it knows the inode of c, it will ask you for the inode associated with the name d in the directory c. Your job is to retrieve the inode for d from the filesystem.

To avoid repeated work when looking up similar paths, the kernel maintains a cache called the dentry cache. Learn how the dentry cache works by reading the materials given earlier.


  1. Add support for looking up filepaths.

    • You should be able to cd into directories and ls the contents of directories that aren’t the root.

    • As a side effect, the -l flag and stat command should work on both files and directories now.

    • Here’s a sample session:

      # ls /mnt/pantry/members
      # cd /mnt/pantry/members
      # stat names.txt
        File: names.txt
        Size: 0           Blocks: 0          IO Block: 4096   regular empty   file
      Device: 700h/1792d  Inode: 4           Links: 1
      Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
      Access: 2017-03-30 02:42:27.629345430 -0400
      Modify: 2017-03-30 02:42:27.629345430 -0400
      Change: 2017-03-30 02:42:27.629345430 -0400
       Birth: -
      # stat does_not_exist.txt
      stat: cannot stat 'does_not_exist.txt': No such file or directory
      # ls -l ..
      total 0
      ---------- 1 root root 0 Apr  3 23:31 hello.txt
      d--------- 1 root root 0 Dec 31  1969 members
    • VFS does most of the heavy lifting when looking up a filepath. It splits up the given path and looks up each part in the dentry cache. If a part isn’t in the dentry cache, it asks you to add it. Before you add things to the dentry cache, you’re responsible for determining whether the given parent directory contains an entry with the given name.

    • Don’t worry about returning metadata correctly at this time. That is, it’s fine to show incorrect permission bits, create/modify/access times, owner, group, etc.



To submit this part, push the hw8p4handin tag with the following:

$ git tag -a -m "Completed hw8 part4." hw8p4handin
$ git push origin master
$ git push origin hw8p4handin

Part 5: Reading the contents of regular files

In this part, we are going to implement reading the contents of files. Our implementation is made very simple by the fact that each file occupies exactly one block.


  1. Add support for reading the contents of files.

    # cat /mnt/pantry/hello.txt
    Hello world!
    # cat /mnt/pantry/members/names.txt
    Kevin Chen
    Mitchell Gouzenko
    John Hui
    # dd if=/mnt/pantry/hello.txt
    Hello world!
    0+1 records in
    0+1 records out
    13 bytes copied, 4.5167e-05 s, 266 kB/s
    # dd if=/mnt/pantry/hello.txt bs=1 skip=6
    7+0 records in
    7+0 records out
    7 bytes copied, 5.1431e-05 s, 117 kB/s
    • Which parameters are controlled by the user program? Make sure to check whether they’re reasonable.



To submit this part, push the hw8p5handin tag with the following:

$ git tag -a -m "Completed hw8 part5." hw8p5handin
$ git push origin master
$ git push origin hw8p5handin


  1. Fix all of the corners we cut in the previous section by making sure your code returns correct metadata for all files and directories. These include size, link count, timestamps, permissions, owner, and group.

    • Check out APUE chapter 4 (Files and Directories) if you’re not sure what these attributes mean.

    • Test by using ls -l and stat as before.

  2. At this point, you should stress test your PantryFS implementation. The rest of this assignment will be easier if you can depend on the reading functionality to report things correctly.

    Here are some ideas:

    • Try copying all the files out of your pantry using cp or rsync.

    • Extend the formatting program again to create additional files and a more complex directory structure. Be sure to include different file types. For example, add a small team photo to the members directory.

    • Overwrite the disk with random garbage from /dev/urandom (instead of /dev/zero). Format it. After formatting, the random data should not affect the normal operation of the filesystem.

    • Write a program that requests invalid offsets when reading files or iterating through directories.



To submit this part, push the hw8p6handin tag with the following:

$ git tag -a -m "Completed hw8 part6." hw8p6handin
$ git push origin master
$ git push origin hw8p6handin

Part 7: Overwriting existing files

So far, we’ve only been reading what’s already on the filesystem. In this part, we’ll begin implementing functions for modifying the filesystem contents.


  1. Add support for overwriting the contents of existing files.

    • If the existing file is smaller than the user buffer to be written, you don’t have to worry about changing the length of the file. Just write into the buffer head representing the data block until you reach the end of the user buffer or exceed the file’s size.

    • Here’s a sample session. The notrunc option is needed to prevent dd from passing the O_TRUNC flag to open(), which sets the length of the file to zero.

      # cd /mnt/pantry
      # echo -ne "4118" | dd of=hello.txt bs=1 seek=7 conv=notrunc
      # cat hello.txt
      Hello w4118!
      # echo "Greetings and salutations, w4118!" | dd of=hello.txt conv=notrunc
      # cat hello.txt
      Greetings and #
    • Writing to the buffer head only changes the contents in memory. It does not cause those changes to be written back to disk. Be sure to take the appropriate measures so that your modifications are written to disk.

  2. Properly update the VFS inode’s size if the length of the write exceeds the file’s length.

    • Here’s a sample session:

      # ls -l hello.txt
      -rw-rw-rw- 1 archie users 13 Apr 16 17:15 hello.txt
      # echo "Greetings and salutations, w4118!" > hello.txt
      # cat hello.txt
      Greetings and salutations, w4118!
      # ls -l hello.txt
      -rw-rw-rw- 1 archie users 34 Apr 16 17:32 hello.txt
      # echo "Hi w4118!" > hello.txt
      # cat hello.txt
      Hi w4118!
      # ls -l hello.txt
      -rw-rw-rw- 1 archie users 10 Apr 16 17:38 hello.txt

      You should also be able to edit files with the nano editor.

    • Ensure that changes to the VFS inode are written back to disk. You should do this by implementing pantryfs_write_inode(). Of course, VFS needs to be informed that the VFS inode is out of sync with the PantryFS inode.

    • Test this by unmounting and remounting.

  3. You may have noticed that although the nano editor works, vim complains about fsync() failing and refuses to save the file. Fix it.

    • You can use the Linux page cache function sync_dirty_buffer() to have a dirty buffer written to disk immediately.



To submit this part, push the hw8p7handin tag with the following:

$ git tag -a -m "Completed hw8 part7." hw8p7handin
$ git push origin master
$ git push origin hw8p7handin

Part 8: Creating new files


  1. Implement creating new files. That is, user programs should be able to call open() with a mode that includes O_CREAT.

    • What you need to do in this part is a combination of what you did in Part 1 (“Creating additional files upon formatting”) and Task 3 of Part 2 (“The filesystem awakens”).

    • Here’s a sample session:

      # cd /mnt/pantry/
      # ls
      hello.txt  members
      # touch world.txt
      # ls
      hello.txt  members  world.txt
      # stat world.txt
        File: world.txt
        Size: 0           Blocks: 0          IO Block: 4096   regular empty file
      Device: 700h/1792d  Inode: 5           Links: 1
      Access: (0644/-rw-r--r--)  Uid: ( 1000/  archie)   Gid: (  100/   users)
      Access: 2017-04-16 19:21:03.000000000 -0400
      Modify: 2017-04-16 19:21:03.000000000 -0400
      Change: 2017-04-16 19:21:03.000000000 -0400
       Birth: -
      # cat > members/favorite_foods.txt
      pad thai
      # cat members/favorite_foods.txt
      pad thai



To submit this part, push the hw8p8handin tag with the following:

$ git tag -a -m "Completed hw8 part8." hw8p8handin
$ git push origin master
$ git push origin hw8p8handin

Part 9: Deleting files

While testing the previous part, you probably created lots of files that are now cluttering your disk. Let’s implement a way to delete those files.


  1. Review how the VFS dentry and inode caches interact with each other using the resources given earlier in this assignment.

  2. Implement the unlink and evict_inode ops so that you can delete files.

    • You are not required to implement directory removal functionality.

    • Ensure that you are reclaiming data blocks and PantryFS inodes when appropriate. To test this, see if you can repeatedly create and remove files.

      for i in {1..10}; do touch {1..14}; rm {1..14}; done
    • In a Unix-like operating system, what is the correct behavior if one process unlinks a file while another process has the same file open? Here’s an experiment you can run on ext4 or the PantryFS reference implementation to find out:

      • Create a file named foo.

      • In terminal window A, run tail -f foo. This command will open foo, print out all the contents, and wait for more lines to be written.

      • In terminal B, run cat > foo. This reads from stdin and outputs the result to foo.

      • In terminal C, delete foo.

      • Back in terminal B, type some text and press return.

      • The text should appear in terminal A.

    • You can also use this C program, which tests the same functionality without you having to open three terminal windows.

      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      int wait_for_user()
          printf("Press any key to continue...");
          return getchar();
      int main(int argc, char **argv)
          int fd;
          int ret;
          char buf[4096];
          ssize_t len;
          if (argc != 2) {
              printf("usage: %s /path/to/file\n", argv[0]);
              return 1;
          fd = open(argv[1], 0);
          printf("=> Opened as fd %d\n", fd);
          ret = unlink(argv[1]);
          printf("=> Called unlink(%s) = %d\n", argv[1], ret);
          printf("=> Running ls to show that hello.txt is unlinked:\n");
          system("ls /mnt/pantry");
          len = read(fd, buf, sizeof(buf));
          if (len > 0) {
              printf("=> Read %d bytes:\n", len);
              fwrite(buf, 1, len, stdout);
          ret = close(fd);
          printf("=> close(fd) = %d\n", ret);
          return 0;



To submit this part, push the hw8p9handin tag with the following:

$ git tag -a -m "Completed hw8 part9." hw8p9handin
$ git push origin master
$ git push origin hw8p9handin

Part 10: Implementing odds and ends (optional)

This part is optional. However, if you successfully complete one or more tasks in this part, Jae will take it into consideration for boosting borderline grades at the end of the semester.


  1. Implement creating new directories. mkdir should work.

    • Be sure to update the appropriate link counts.
  2. Implement removing directories. rmdir should work on empty directories.

    • Be sure to update the appropriate link counts.
  3. Implement hard and soft links. ln should work on regular files, and ln -s should work on both regular files and directories.

    • Make sure that unlink works correctly on inodes with multiple hard links.

Submission (optional)

To submit this part, push the hw8p10handin tag with the following:

$ git tag -a -m "Completed hw8 part10." hw8p10handin
$ git push origin master
$ git push origin hw8p10handin


The PantryFS assignment and reference implementation are designed and implemented by the following TAs of COMS W4118 Operating Systems I, Spring 2017, Columbia University:

The PantryFS assignment and reference implementation was updated for 64-bit Linux version 4.9.81 by the following TAs of COMS W4118 Operating Systems, Spring 2018, Columbia University:

The PantryFS assignment and reference implementation was updated for 64-bit Linux version 4.19.50 by the following TAs of COMS W4118 Operating Systems, Spring 2020, Columbia University:

Last updated: 2020-11-22