Programming Assignment Four: PA4

Due Wednesday night, June 3 @ 11:59pm

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/.

man -l name

will help you determine which section of the man pages to specify. For example,

man -l getopt
getopt (1) -M /usr/share/man
getopt (3c) -M /usr/share/man

lists all the man pages found matching getopt within the man search path. We want the section 3c version (not the section 1 version - commands). So we would specify

man -s3c getopt

Grading Breakdown

README: 10 points
Compiling: 10 points
Style (all requirements met): 30 points

See Requirements for Style and Code Style in PA1 write-up
Correctness: 50 points
-10 points (minus 10 points) for each unimplemented module or
module written in the wrong language (C vs. Assembly and vice versa)
Extra Credit: more than 5 points
Must correctly pass at least 70% of test cases to be eligible for Extra Credit.
This Extra Credit is implementing additional options to the mywho command
(1 point for each Extra Credit option unless otherwise noted).


NOTE: If what you turn in does not compile, you will receive 0 points for this assignment. Your turned in files must compile with your Makefile in order for your programming assignment to be graded.

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:

mywho.c with main() defined in here
buildUtmpInfoTable.c
displayUtmpInfo.c
nameSortDescending.c
idleTimeSortAscending.c

The SPARC Assembly modules you will write are:

calcIdleTime.s
nameSortAscending.s
idleTimeSortDescending.s (must be written as a leaf subroutine)

The header file:

Here are some suggestions for your mywho.h
/*
 * 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

"Extra Credit Option -t Not Supported"
and exit.

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.

t output sorted by login time (ascending) [Extra Credit]
only one of t or u or i allow
The -t option will indicate the output should be displayed sorted by login time (ascending order by default). You will need to add a timeSortAscending.c and timeSortDescending.s (leaf subroutine). The -r option will reverse this sort.
n x specify number of users per line for -q [Extra Credit]
This option will be ignored unless used with the -q option. The number specified must be at least 1. You should use your strToLong() you wrote in PA1 to convert the arg to an integer.
b output uptime-like info [Extra Credit]
Output current time, length of time the system has been up, and number of users. Number of users should match that of mywho -q.
a all (BbHT options) [Extra Credit]
Output uptime-like info, header, time of system boot, long output. This may be combined with some other options; however, -q and -m options short circuit further display. When in doubt on a particular behavior, check the pa4test-EC output.

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:

'+' if line is group writeable
'-' if line is not group writeable
'?' if we cannot stat the line to determine state
idle time:
as calculated by calcIdleTime()
pid:
process id of this user's login shell

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=146
Be sure to run pa4test for more example outputs.