The Who Command
When working with operating systems, you will often interact with system resources using system calls. Learning how to interact with system resources is the purpose of the study of system programming. We will take a look at common command line utilities in Linux, learn how they work, and learn how to implement our own versions of them. In doing this, we will gain a good understanding of how operating systems handle resources, and how a program can interact with them.
The command we will look at in this article is the who command. The purpose of the who command is to show you who is currently using a system you are logged into. If I run the who command on my current Linux installation, I get the following results.
The information displayed by the who command is the username of the person who is logged in, the type of terminal the user is using, the date that they logged in, and the location that they logged in from. The first question we need to ask when trying to implement this command is, how does the current command do it? Where does this information come from, and how do we access it?
To figure out how a command actually works, there are four main techniques that are valuable:
- Read the manual
- Search the manual
- Read the header files
- Read the "see also" links
The manual for system commands is accessible through the command line, using the man command. By typing man who, we will be able to see the manual entry for the who command. Doing so will show us a great deal of information:
- The name of the command
- A synopsis of how the command is used. This will include mandatory and optional arguments. Optional arguments are displayed with square brackets.
- A description of what the command does, as well as the arguments available and what they provide.
- The author of the program
- Instructions for reporting bugs
- Copyright information
- See also links, which link to helpful information and further documentation
If we follow the "see also" links to the gnu documentation page for who, we will see a more detailed description of the program, which will reveal more details about the implementation of the program. Of interest is the files noted that are accessed by the program. The program uses a file called utmp, which is located typically in /var/run/utmp or /etc/utmp. This tells us that in order to get login information, we need to be able to view the utmp file.
If you cd to the /var/run directory and attempt to open or cat the utmp file, you will see that its format is not easily readable. This tells us that there is likely more to the story than just opening and reading this file. One idea could be to try searching utmp in the manual to see if there is any documentation on how it is accessed. If we use the command man -k utmp we will see all of the manual entries that contain the keyword utmp. Looking at these manual pages, we can see a common theme is they utilize a file called utmp.h, which is a structure header file that can be used to access the utmp resource. If we open this header file, we are able to see the structure definition. This reveals to us the information we would need to write an equivalent to the man command.
To start, we are going to declare variables to hold our utmp struct, the file identifier of the utmp file, and the size of the records in the utmp file. Using the UTMP_FILE property defined in the utmp struct, we open the utmp file in read mode, and set the file descriptor equal to the result. If we fail to open the file, we throw an error, and exit the program. Otherwise, we loop through the file reading each record, displaying the information in the record. Once this is done, we close the utmpfd file and exit.
The show_info function is where the actual printing of the utmp file takes place. Inside of this function, we print four properties of the utmp struct. These properties are the ut_name, which is the name of the user, the ut_line, which is the terminal of the user, and ut_time, which is the time the user logged in, and the ut_host, which is the host the user logged in from.
When we run this program all together, we get an output that looks similar to who, but with a few issues. Here is a comparison of my current outputs.
You will notice that the time doesn't display correctly at all, and there are a number of additional entries that are not a part of the typical output. To fix these issues, we will need to make a few adjustments to our current code.
To solve the problem of the extra records, we can turn to the utmp header to see if any properties are useful. For instance, there exists a property, ut_type, which can tell us if the record corresponds to a user. If we check this before printing, we will only get records that correspond with users, matching the existing who command.
This solves the issue with the record count, however the time still looks to be an issue. To solve this, we need to investigate into how the login time is stored in the struct. The struct uses a special format for its time called time_t. We need to convert this into a more readable format, which can be done using the ctime function. The ctime function takes a pointer to a time_t variable, and converts it into a readable string. Using this we can once again modify our logic to get a better output.
We setup a character array, and use it to store the result of the ctime function. We then take formattedTime + 4, as we want to exclude the first 4 characters of the time value. This is because the first four characters have the day of the week, which doesn't exist in the original who command. With these changes, we now have a program that matches the who command.
You could further adjust the formatting to be an exact match, however the main point here is to understand how we can interact with a system resource like utmp.