The purpose of this (last!) programming assignment is more of an emphasis on C systems-level programming and implementing a Unix command/utility using some standard Unix system calls (Section 2) along with more Standard C Library routines.
You will build a version of the who utility we will call mywho that will implement a subset of the real who command along with some different options specific to our mywho command. Some options will be Extra Credit.
Besides lots of useful routines you have already learned to use, some of the new system calls and Standard C Library routines you will probably use include basename(), getopt(), stat(), rewind(), malloc()/calloc()/realloc(), free(), qsort(), time(), strftime(), localtime(), ttyname(), offsetof(), and possibly memcpy(), memset().
We will try not to do too much hand-holding on this PA. You have some experience now and you need to learn how to look up information yourself. You will not be given nearly as much help/hand-holding in some of the upper-division classes, so now is the time to start doing more on your own. Check out the man pages for each of the functions listed above and for the header file utmpx. Header files are located in /usr/include/ and /usr/include/sys/.
Grading Breakdown
README: 10 points
Compiling: 10 points
Style (all requirements met): 30 points
You will modify one of your Makefiles or one of the public template Makefiles from previous PAs.
Basic Strategy
The C modules you will write are:
The SPARC Assembly modules you will write are:
The header file:
/*
* mywho.h
*/
/* Macro Guard */
#ifndef _MYWHO_H
#define _MYWHO_H
#define B_FLAG 0x01 /* -B option: Boot Time */
#define H_FLAG 0x02 /* -H option: Display header */
/* C_FLAG not used in this version. */
#define C_FLAG 0x04 /* -c option: Info about current terminal user */
#define Q_FLAG 0x08 /* -q Quick mywho - only names and # of current users */
/* Ignore all other options */
#define R_FLAG 0x10 /* -r option: Reverse sort */
#define S_FLAG 0x20 /* -S option: Info about select user */
#define SORT_BY_IDLE_FLAG 0x40 /* -i option */
#define SORT_BY_USER_FLAG 0x80 /* -u option */
#define HELP_FLAG 0x100 /* -h option */
/* Extra Credit Options Here */
#define HEADER "NAME LINE IDLE"
#define LONG_HEADER "NAME LINE LOGIN IDLE PID"
/*
* Calculate ut_user and ut_line sizes from struct utmpx
*/
#include <utmpx.h> /* struct utmpx definition */
#include <stddef.h> /* offsetof() macro */
#define USER_SIZE ( offsetof( struct utmpx, ut_id ) - \
offsetof( struct utmpx, ut_user ) )
#define LINE_SIZE ( offsetof( struct utmpx, ut_pid ) - \
offsetof( struct utmpx, ut_line ) )
/*
* To pick up pid_t and time_t typedefs
*/
#include <sys/types.h>
struct utmpInfo {
char user[USER_SIZE];
char line[LINE_SIZE];
pid_t pid;
short type;
time_t time; /* login time. struct timeval ut_tv; */
/* time_t tv_sec member only from struct timeval */
char *host; /* dynamically allocate space for hostname */
time_t idle; /* calculated idle time (needed for -i option) */
};
/* Not used in this version.
* void currentTerminalInfo( struct utmpInfo *utmpTable, int entries );
*/
int buildUtmpInfoTable( const char *utmpFilename,
struct utmpInfo **utmpInfoPtr );
void displayUtmpInfo( struct utmpInfo * const table, const int entries,
const int displayMode );
void calcIdleTime( time_t idleTime, char *idleString, int size );
int idleTimeSortAscending( const void *p1, const void *p2 );
int idleTimeSortDescending( const void *p1, const void *p2 );
int nameSortAscending( const void *p1, const void *p2 );
int nameSortDescending( const void *p1, const void *p2 );
/* Other Extra Credit Function Prototypes Here */
#endif /* _MYWHO_H */
mywho.c
mywho.c will have the main(). Check for command-line options with getopt(). See the man page for getopt for more info on how to use it. The man page gives a pretty good example, but don't necessarily follow that example literally in your coding since we want to use bit masks instead of int flags.
Some of the options for mywho are the same as the real who command, some are quite different using the same option character with a different meaning, and some options are new just for this assignment. See the man page for who and try out the real who command.
% mywho -h
Usage: mywho [-aBbHhqrST[i|t|u]] utmpx_like_file
mywho -q [-n x] utmpx_like_file
default output: name, line, idle time, hostname
h this long help message
B print boot time only
H print header
q quick mywho info only
r reverse meaning of any sort option
ignored if no sort option specified
S user output all info on this username only
i output sorted by idle time (ascending)
u output sorted by username (ascending)
only one of t or u or i allowed (mutual exclusion)
Extra Credit Options:
t output sorted by login time (ascending) [Extra Credit]
only one of t or u or i allowed (mutual exclusion)
n x specify number of users per line for -q [Extra Credit]
b output uptime-like info [Extra Credit]
T add to default output [Extra Credit]
status of line (+ writable, - not writable, ? unknown),
login time, and pid
a all (BbHT options) [Extra Credit]
If the user specifies a bad option as in
% mywho -x
mywho: illegal option -- x
Usage: mywho [-aBbHhqrST[i|t|u]] utmpx_like_file
mywho -q [-n x] utmpx_like_file
default output: name, line, idle time, hostname
getopt() outputs the first line about the illegal option and we will be
printing out the other usage lines (to stderr). Again, see the man page for
getopt for more details. We will report multiple illegal options.
% mywho -gx mywho: illegal option -- g mywho: illegal option -- x ...
If you are not supporting a particular Extra Credit option, then output a not supported message with the option that you are not supporting. For example, if you are not supporting the extra credit option "-t", then you would display the string
Normally, we will build a simple in-memory database (array of struct utmpInfo) with buildUtmpInfoTable() that will be used to display info about the users currently logged in (displayUtmpInfo()). We will use the info from a utmpx-like file as specified on the command line after the options. See the man page for utmpx and /usr/include/utmpx.h. A sample utmpx file you can use for this command line argument (along with just using /var/adm/utmpx) is in ~/../public/utmpx.sample. This is a saved /var/adm/utmpx file from ieng9.ucsd.edu, so you will most likely need to run your mywho on ieng9.ucsd.edu to use this sample file.
When some options are used, other options will be ignored. For example, -q will ignore all other options (except the optional -n x) . Some options are mutually exclusive, like -i, -t, and -u. When in doubt, follow the behavior of pa4test.
Be sure to free all the dynamically-created memory (array of struct utmpInfo database and the hostnames array of chars) before terminating.
buildUtmpInfoTable.c
buildUtmpInfoTable() takes the name of a utmpx-like file and the address of the struct utmpInfo pointer back in main() that we will fill in based on our dynamic building of this utmpInfo table. This second arg is just like endptr in strtol() -- strtol() needs to set the pointer back in the calling function -- buildUtmpInfoTable() needs to set this pointer back in the calling function main() to the address in the Heap of our dynamically allocated array of struct utmpInfo database.
Try to open the utmpFilename and report if this failed (using snprintf() to create our error string that will be sent to perror()). Then check to make sure this is a umtpx-like file. For this assignment we can simple try to read the first record and see if the ut_line field is DOWN_MSG (as defined in /usr/include/utmp.h).
If everything looks fine, then start dynamically building our array of struct utmpInfo by copying the appropriate fields from each record we read to our utmpInfo record expanding the array as needed with realloc(). For this assignment, you can simple realloc() for each new struct. In practice, you will want to expand by more than one element at a time possibly doubling the size of the array each time you expand to reduce the overhead of the dynamic expansion (much like class Vector's implementation in Java). For the hostname, we will dynamically allocate just enough memory to hold the nul-terminated hostname string. We could have done this with the username and line strings also, but having them fixed-size in the record just as struct utmpx defines them may help with your inevitable debugging efforts. :-)
To set the idle time field, you will need to call stat() on the
terminal associated with this entry to get the last access time.
pa4time() must be used to get the current time. Idle time will be the
difference between now and last access. Copy pa4time.c from the public
directory into your pa4 directory. pa4time is used exactly the same way
time is used. See man -s2 time.
When all the records are read in and your array of struct utmpInfo database is built (and the idle time calculated and saved for each entry), set the utmpInfoTable pointer back in main() via the second param, and return the number of entries in the table. Be sure to close any files you opened.
displayUtmpInfo.c
displayUtmpInfo() will display the user info based on the displayMode bitmask set with our getopt() processing.
If the -q (quick mywho) option was specified, ignore all other options and output a simple list of all active users in the database (type == USER_PROCESS) 8 names to a row and the total number of users and return.
Output a header if -H was specified. If both -H and -T options were specified (and not -b option), output the long header.
If the -B (boot time) option was specified, output the boot time message in the line field and the time of the system boot in the time field formatted using strftime() and localtime() of the time field and return. This should be the second record in any utmpx-like compliant file. localtime() takes a time_t * and returns a struct tm *; strftime() takes a struct tm * and formats it into its first argument similar to snprintf(). Use the strftime() format string "%b %e %H:%M". See the man pages for strftime and localtime.
If the user specified the -S username option, output all info on this username only. You should sort the database of struct utmpInfo by username and then perform a binary search for the specified user. You must use bsearch() to perform the binary search. bsearch() is similar qsort(). Since there may be many entries for this user, be sure to examine records around the record found by bsearch(). Output an error message if no records for this user were found. You do not have to apply other sort options to the info on this user. Extra Credit: Apply other sort options to the info on this user.
Before we output the regular who info, see if we need to sort the array of stuct utmpInfo database by either username or idle time based on the -u or -i mutually-exclusive options. Default sort is in ascending order. If the -r option was specified, reverse the meaning of the sort (descending order). Use qsort() with the various {name|idleTime}Sort{Ascending|Descending}() functions. See the (3c) man page for qsort().
Now we are ready to cycle through the database of utmpInfo entries for the general display of user info for only those entries that are current users logged in (type == USER_PROCESS). Other options we are not implementing at this time would list info on other types of entries (see the various definitions of the ut_type field for struct utmpx).
The default (short) output includes the username, line, idle time, and remote hostname from which this user is connecting in parens. Local console X Windows login entries will probably have something like ":0" as the hostname string. Idle time will be calculated and stored in each record with calcIdleTime().
calcIdleTime.s
calcIdleTime() assembly routine forms the idle string to be displayed. It will take the saved idle time of the line calculated and saved in buildUtmpInfoTable(), a pointer to a char (address of the first char of an array of chars) in which to place the calculated idle time string, and the size of this idle time array.
If this idle time is less than 1 minute (60 seconds), set the idleString to " . ". If the idle time is greater than 1 day (24 hours), set the idleString to " old ". Otherwise set the idleString to be the number of hours:minute idle formated as HH:MM with no leading 0 for single-digit hours. You can use snprinf() with format specifier "%2d:%02d" or strftime() formating will be "%l:%M". See man page for strftime.
idleTimeSortAscending.c
idleTimeSortAscending() compares the idle fields of two struct utmpInfo records pointed to by the parameters and returns the value 0 if they are equal, a value < 0 if the time field pointed to by the first parameter is less than the time field pointed to by the second paramter, or a value > 0 if the time field pointed to by the first parameter is greater than the time field pointed to by the second parameter. See the man page for qsort().
idleTimeSortDescending.s
idleTimeSortDescending() must be written as a leaf subroutine in assembly. This function compares the idle fields of two struct utmpInfo records pointed to by the parameters and returns the value 0, a value < 0, or a value > 0 with a reverse logic for an ascending sort. Use a global variable (idleOffset) in mywho.c initialized to the offset of the idle field (using offsetof()) to communicate the required offset of the idle field to this leaf subroutine.
nameSortAscending.s
nameSortAscending() compares the name fields of two struct utmpInfo records pointed to by the parameters and returns the value 0 if they are equal, a value < 0 if the name field pointed to by the first parameter is less than the name field pointed to by the second paramter, or a value > 0 if the name field pointed to by the first parameter is greater than the name field pointed to by the second parameter. See the man page for qsort().
nameSortDescending.c
nameSortDescending() compares the name fields of two struct utmpInfo records pointed to by the parameters and returns the value 0, a value < 0, or a value > 0 with a reverse logic for an ascending sort. An Extra Credit option is to write this function as a leaf subroutine in assembly.
There is a pa4test executable in the public directory that includes the Extra Credit options. pa4time.c is also in the public directory for you to copy into your pa4 directory. To help examine utmpx files, you are given a program called utmpx_dump.c also in the public directory.
Follow the same coding style as expressed in previous programming assignments, including README, comments, style, and 0 points if what you turn in does not compile.
Start early! Due date is June 3 @ 11:59pm.
Extra Credit
Each Extra Credit option is worth 1% extra credit for a total of 5% extra credit. You may implement any number of these. For example, fully implement two of these extra credit options = 2% extra credit.
If the -T [Extra Credit] option was specified, then the long output includes username,
tty/line status, line, login time, idle time, pid, and hostname.
Login time will be formatted with strftime() and localtime() just as we did
with the Boot time.
tty/line status:
You will need to use the system call stat() on the line ("/dev/"+line) to determine the line's state (st_mode & S_IWGRP) and use the saved idle time to calculate the idle time string. See the (2) man page for stat.
[Extra Credit] Apply sort options (-i or -t) to the -S user option. Since we are displaying info about a single user, the -u sort option is meaningless. This option is not implemented yet in pa4test but is easy to check.
[Extra Credit] Use getloadavg() to add the system load averages over the last 1, 5, and 15 minutes to the -b option. See the man page for getloadavg. This option is not implemented yet in pa4test but is easy to check.
Must correctly pass at least 70% of test cases to be eligible for Extra Credit
% mywho -H NAME LINE IDLE cs131s pts/4 5:31 (fast36.ucsd.edu) r1thomas pts/5 0:02 (66-75-247-119.san.rr.com) jdiscar pts/6 1:00 (apm-2444-26.ucsd.edu) nvaidya pts/1 0:40 (taos.ucsd.edu) ehoang pts/7 1:36 (lt-e-hoang.gat.com) n11nguye pts/8 0:04 (cse-air-dhcp-214.ucsd.edu) ftrang pts/9 0:05 (dyn-135-181.ucsd.edu) bparent pts/10 0:45 (calvin.ucsd.edu) op pts/52 0:04 (acs-print.ucsd.edu) vihuynh pts/11 . (acc75208.ipt.aol.com) agt pts/13 . (twirlip.ucsd.edu) jdiscar pts/14 0:04 (apm-2444-26.ucsd.edu) jrapp pts/16 5:06 (chiasmata.ucsd.edu) nhnguyen pts/17 0:07 (mick.ucsd.edu) ebalter pts/18 0:02 (apm-2444-48.ucsd.edu) smahbuba pts/19 2:50 (128.54.48.89) joo pts/20 0:31 (user-10cmdmc.cable.mindspring.com) ... more output deleted for the sake of brevity. % mywho -b 3:07pm up 8 day(s), 5:21, 144 users % mywho -q cs131s r1thomas jdiscar nvaidya ehoang n11nguye ftrang bparent op vihuynh agt jdiscar jrapp nhnguyen ebalter smahbuba joo qkhuynh skanumur vihuynh cs80xba aselvara akhin jhriv cs30xal j6zhang cs30xcz slulovic kfcheng c2phan smahbuba qkhuynh dtjandra kfcheng tywu tywu jmccollo zchen archen archen jpachura xiliang kviswana dtjandra j39lee juzheng ajow sraychau qkhuynh archen kayamash agt gkoide cs30x mebarry vmenon ysitu n11nguye vihuynh vihuynh wgwong kfcheng jdiscar agt mkmcdona maryam dmartin bdai kfcheng nvaidya kfcheng ee283sad aselvara cs131w marisoyl maryam gbhatia jhriv jbryan jili jrapp vihuynh miyamagu imarsh yuc004 smahbuba imarsh mkmcdona op smahbuba jili yuc004 n11nguye cs131s ekwang jrapp ylichman k19lee qkhuynh jili xfchen enegado imarsh ksnisare jhwoo bdai tywu cs30x4 ssajama mcgo fcwang kayamash miyamagu akhin vihuynh jili n11nguye fkuan ytso aewalter glam hochoi c2phan d33nguye jokuo jkaser jkaser kshams aphsiao jmorante kfcheng jgullo jrapp aleontar cs131sae jili akhin ekwang ehlo miyamagu xfchen eseok yuc004 jrapp jrapp ee283sad # users=146Be sure to run pa4test for more example outputs.