COMP 3000 Fall 2017 Assignment 3 Solutions 1. [1] If you erase the first few blocks of a filesystem, it will become unmountable; however, fsck will generally be able to recover the filesystem. What extra information stored in the filesystem allows fsck to do the repair? A: The filesystem stores redundant copies of the superblock at standard positions throughout the filessystem so that if the primary one at the start of the filesystem gets corrupted or erased, the backups can be used to recover the filesystem. (0.5 points for discussing that the superblock was erased, 0.5 points for backup superblocks) 2. [1] Sometimes the kernel will refuse to unmount a filesystem (when the requesting user has sufficient privileges). What is one important reason why this happens? A: The primary reason this happens is because the filesystem is currently in use by another process (i.e., a process has a file open that resides on that filesystem). 3. [2] Why can't you use printf() in Linux kernel modules, but you can use similar C library functions such as snprintf()? More generally, why is only some of the standard C library available to kernel code? A: printf() doesn't make sense in kernel code because printf() outputs to standard out, and standard out doesn't exist for the kernel. In general only a subset of the standard C library is available in kernelspace because the kernel cannot depend upon any external libraries, it must be self-contained. Implementations of C library functions in the kernel thus are independent implementations, and only ones that make sense inside the kernel (e.g., ones that do not rely upon file descriptors or file streams) are available. (1 point for saying something about standard out and printf. Full credit for explaining how the kernel can't rely on the standard C library) 4. [2] What is a process's effective uid (euid)? Why is the euid not always equal to its uid? A: A process euid is used by the kernel to determine what privileges the process has (e.g., what files it can access, what processes it can send signals to). Normally a process's euid is equal to its uid; however, if there is an execve of a process that is setuid or setgit (such as sudo, passwd, or fusermount), then the process's euid or egid is set to the executable file's uid or gid. The uid and gid remains the same as it was prior to the execve. Also, a process with high privilegs (e.g., running as root) can drop privileges by changing its euid; however, normally when dropping privileges both the uid and euid are changed. (1 point for defining euid, 1 for explaining why euid and uid aren't always equal) 5. [2] When you load a kernel module, are you creating a new process? Why or why not? A: Loading a module does not create a new process on its own. A process must run code to load the module (e.g. insmod), but this process can terminate and the module will continue to be present in the kernel. Modules are code that is added to a running kernel, much like loading a library into a running process. All kernel code shares the same memory map, but a kernel module can create execution contexts. If it does so, those contexts are called kernel threads (as they are the same as threads, except they run in the kernel's address space). By default, though, the code in a module is only called in specific contexts. At minimum, this is when the module is loaded and when it is removed. (1 point for saying the process is not created, 1 for explanation) 6. [2] Is a running Linux system that is generating multiple "Oops" messages at risk of doing something bad, like corrupting data or crashing? Explain. A: Oops messages indicate the kernel is detecting bad memory accesses (i.e., pointers refering to inaccessible memory). As with any C program, a program that is accessing memory that it shouldn't is a very bad thing. Data structures and even code can be corrupted. In other words, don't trust a Linux system that is generating many Oops messages. (1 for saying the kernel is at risk, 1 for the explanation) 7. [2] Why does newgetpid.c use put_user() in newgetpid_read(), rather than just writing directly to buf? Explain briefly. A: buf is a pointer to memory in the process making the system call. The kernel should not directly manipulate such memory because it is untrusted and could be changed maliciously by the process (say, in another thread). Thus, access to userspace memory should be done through special access routines such as put_user(). (1 point for saying something about buf being a pointer to userspace, 1 for why this matters) 8. [2] How can the logical size of a filesystem be larger than the physical size? How can the logical size be smaller than the physical size? A: (This question was supposed to be about files, not filesystems. Full credit is given if you gave an answer about files.) The logical size can be larger than the physical size of a file can be larger than the physical size if the file is stored with "holes" - UNIX systems do not have to allocate blocks for portions of a file that contain all zeros. A filesystem stored in a file can thus take up less physical space (blocks) than its logical size. The logical size of a filesystem can also be larger than the physical size if the filesystem compresses or deduplicates files. The logical size of a file is in general smaller than the physical size of a file because a file's logical size is almost never an even multiple of the block size. The filesystem must thus "round up" and waste space in the last block. For example, if the block size is 4096 bytes and a file is 4097 blocks, the file must take up two blocks (8192 bytes), with the first block having the first 4095 bytes and the second block having only one byte of data. On disk (where all blocks are allocated), the logical size of a filesystem - the number of bytes it can store - will be less than the physical size of the filesystem because of overhead from storing the essential filesystem metadata, e.g. inodes and superblocks. Note that ext4 by default reserves some amount of space (generally 5%) to prevent fragmentation/give space for root. Thus the number of available blocks will be less than the total number of blocks minus the used blocks. (1 point for something reasonable about being larger, 1 for something about being smaller.) 9. [2] What type is "current" normally in Linux source code? What does "current" refer to? A: current is of type "struct task_struct". current refers to the process that made the system call that is being processed. Note that on a multicore system there can be multiple values of "current", one for each core that is running the kernel. Also, when the kernel is doing things not associated with a system call current is not defined/has no meaningful value. (1 point for the type, 1 for what current refers to) 10. [2] What line(s) in newgetpid.c sets the name of the device file (i.e. making it /dev/newgetpid rather than /dev/ones)? How did you verify that your answer was correct? A: Line 22, #define DEVICE_NAME "newgetpid" You can verify this by changing the value to another string, recompiling with make, loading the module with insmod, and seeing what device shows up in /dev. Note this value is used in lines 89, register_chrdev(), and 106, device_create(). The device_create() call is the one that actually sets the name in /dev, so line 106 is also a correct answer. (1 point for the line, 1 for the test) 11. [2] What changes need to be made to newgetpid.c to allow it to respond to write requests? Hint: How does newgetpid.c respond to read requests? A: Two changes need to be made. First, add a ".write" member to the newgetpid_fops struct that refers to a function, say "newgetpid_write". Then you need to create a newgetpid_write() function with the same declaration as newgetpid_read() except that the buf argument is constant. This new write function should report the number of bytes that has been successfully written; other than that, it can do almost anything. One simple thing for it to do is change the state of the module. (One point for the .write member, one for the function. You don't need to say anything about how the write function should behave.)