Operating Systems 2020W: Assignment 2
Please submit the answers to the following questions via CULearn by 10:00 PM on February 25, 2020. There are 30 points in 17 questions.
Submit your answers as a plaintext file named "<username>-comp3000-assign2.txt" (where username is your MyCarletonOne username). The first four lines of this file should be "COMP 3000B 2020W Assignment 2", your name, your student number, and a blank line.
No other formats will be accepted. Submitting in another format will likely result in your assignment not being graded and you receiving no marks for this assignment. In particular do not submit an MS Word, OpenOffice, or PDF file as your answers document!
Don't forget to include what outside resources you used to complete each of your answers, including other students, man pages, and web resources. You do not need to list help from the instructor, TA, or information found in the textbook.
Background on node.js
In this assignment, we will be using node.js as a shell. While any program can potentially be a shell for a user account, for normal interactive users we want programs that provide some sort of read-eval-print loop: they should read the user's input from a terminal, execute the user's request, and then print the output to the terminal. Thus, any programming environment that allows interactive use can be used as a shell.
For this assignment, you will want to refer to the node.js documentation on the child_process and fs modules. Below are examples of shell commands and their node.js equivalents using child_process.execFileSync(). (Note that we use this one specifically as it is the closest equivalent in node to a fork and execve and does not invoke a shell indirectly.)
To install node.js on your vm, run:
sudo apt install nodejs
Here are some shell examples (in comments) followed by their node equivalents. You should try these examples at the node ">" prompt.
/* /bin/echo "Hello World!" */ child_process.execFileSync("/bin/echo", ["Hello World!"], {stdio: [0, 1, 2]}); /* echo "Hello World!" > hello.txt */ var f = fs.openSync("hello.txt", 'w+'); child_process.execFileSync("/bin/echo", ["Hello World!"], {stdio: [0, f, 2]}); fs.closeSync(f);
NOTE: While node.js is cross-platform, be sure to do the following on a system running Linux, preferably the a class VM on openstack. Answers from other platforms may not be accepted.
Questions
- [1] While password hashes do not store passwords, they need to be protected because they can be used to guess passwords. How are password hashes safer in /etc/shadow versus being in /etc/passwd? Specifically, who has read access to each file, and how can you verify this access?
- [1] When we say a program binary is setuid root, what does this say about its metadata (permissions, etc.)? Please be specific as to what it does and does not mean.
- [3] What is the difference between running a setuid root binary and a regular binary? Specifically:
- What system call(s) change their behaviour?
- What are the changes?
- How do those changes affect subsequent program behaviour?
- [2] Can regular processes (running as an unprivileged user) change their uid? What about their gid? For each, specify the system call that would be used and what under what conditions it would work.
- [2] Install node.js on your machine. How could you use child_process.execFileSync() to do the following shell commands in node? Note you may need additional node statements before or after this call.
- ls -l /home
- ls -l /home > /tmp/homefiles.txt
- sort unsorted.txt -o sorted.txt
- sort < unsorted.txt > sorted.txt
- [1] How can you specify the environment variables given to the program run through child_process.execFileSync() without changing the environment variables of node? Demonstrate using the env command.
- [1] How could you make node the default login shell for a user "testuser"?
- [2] When using public key cryptography for ssh authentication, where is a user's public key stored (in what file(s) and on what systems)? Where is a host's public key stored? Be sure to specify its original location and where any copies may be stored.
- [2] When ssh runs commands on remote systems, does ssh directly execve the executable, or does it first call a shell which then runs the specified program? How can you verify this behavior?
- [1] Why do you think that fs.lstatSync() takes a filename as input while fs.fstatSync() takes a file descriptor? Explain briefly.
- [2] What does fs.fstatSync() return when run on /dev/urandom on your system? How can you use this return value to determine whether it is a regular file or not?
- [2] Create a file A with the contents "Hello world!" on a line by itself. Then, answer the following:
- What node statement will create a symbolic link from A to B?
- What node statement will create a hard link from A to C?
- What are the similarities and differences in the output of fs.lstatSync() on A, B, and C? Note you may want to use other functions such as fs.isFile() to test the return value of fs.lstatSync(). Do all of your tests in node.
- [2] How can you find the inode number of a file in node? How can you change the inode number of a file in node?
- [3] Run the node commands below and answer the following questions.
var f = fs.openSync("/tmp/testFile.txt", 'w+'); fs.writeSync(f, "Hello World!\n", 20000); fs.closeSync(f);
- If you view /tmp/testFile.txt in less, what do you see? Why?
- What is the logical size of /tmp/testFile.txt? What is the physical size? Obtain both values just using node functions. Explain how each was determined.
- What system call(s) is the call to fs.writeSync() making? How do you know?
- [1] Where are the superblocks of the root filesystem of your VM? How did you find them?
- [1] When you use sshfs to mount the home directory of your access.scs.carleton.ca account on your VM, what are two characteristics of the files and directories within that indicate that they are from a remote system and are not local?
- [3] Create a filesystem, erase blocks, and repair it in a way that causes at least one file or directory to be put into the filesystem's lost+found directory. Give commands for creating the filesystem, populating it with files, erasing key blocks, and repairing it.