May 09, 2012

Session logging in Linux

Most of you must be familiar with the "who" command on linux, it lists the users logged in currently on the machine and shows a lot more information related to where they are logged in from etc. There are a lot more options the command supports including the usual "who am i" and "who mom likes" versions.
Basically what "who" does is parse a couple of files which are used for session logging, this post aims to explain these files and the structure of the records in these files. Typically on linux systems two files are used for session logging :

/var/run/utmp : Stores user session related information, a record is appended at each login and then wiped out on logout.
/var/log/wtmp : Stores a trail of the login/logout information and other such logging information for the system. The same record which is pushed to utmp is pushed here on login and then the same record with the "user" field zeroed in is pushed on logout.

The paths to the files can change and are available more generically as _PATH_UTMP and _PATH_WTMP defined in /usr/include/paths.h. Applications should use these definitions rather than hard coding the paths.

Linux for compatibility provides both the utmp and utmpx API for dealing with these log files, the utmpx API is an extension(and a parallel API) of the utmp API and was created in System V Release 4. Linux however does not create parallel utmpx and wtmpx files, all the information is available in the above mentioned two files only.

The records stored in each of the files is of the type "struct utmpx" defined in /usr/include/bits/utmpx.h as :

Note that to get some of the types for the fields in the structure it is important to declare _GNU_SOURCE in your program.

Each of the lines in "utmp" and "wtmp" files contains records of the above format. As I mentioned before Linux supports both the old utmp API and the utmpx API, if the utmp API is used then the records are read in as "struct utmp" which is defined in /usr/include/bits/utmp.h. The main difference between the two APIs is that the former has some re-entrant versions for functions which the latter(utmpx) does not. The various defined function calls are : 
One field which requires some attention above is the type field. Records in the files can be of various types,  : 

EMPTY - Invalid accounting information
RUN_LVL - Indicates a change in run-level during boot or shutdown (requires definition of _GNU_SOURCE)
BOOT_TIME - Contains the system boot time in ut_tv field. 
NEW_TIME - Contains the new time after a system clock change, recorded in the ut_tv field.
OLD_TIME - Contains the old time before a system clock change, similar to the NEW_TIME type record.

INIT_PROCESS - Signifies a process which has been spawned by the INIT process.
LOGIN_PROCESS - Record for a process like "login"
USER_PROCESS - Signifies a user session process started by the login program.
DEAD_PROCESS - Identifies a process that has exited (occurs on logout)

RUN_LVL and BOOT_TIME type records are written by "init" and these records are written to both the "utmp" file and the "wtmp" file.

Using the utmpx API you can read and write entries to these files directly. The good thing is that you need not directly open/close these files, calling the function "setutxent()" opens the file or rewinds the file pointer if already open. Similarly when we are done reading/writing, we can close the file using "endutxent()".
The functions available in the API to deal with the "utmp" file can be divided into three categories :

1. Getting entire records  using the getutxent() function which returns the entire record starting from the current file pointer location.
       struct utmpx * getutxent(void);
2. Searching for records based on given parameters, the parameters are passed in a struct utmpx pointer.
       struct utmpx * getutxid(const struct utmpx *ut);
       struct utmpx * getutxline(const struct utmpx *ut);
3. For putting a record to the file
       struct utmpx * pututxline(const struct utmpx *ut);

Functions of the type getutx* return a pointer to the utmpx record read or NULL if EOF is reached. The returned pointer points to a static area and can be cached on certain systems, that is if the search parameters match the result in the pointer returned by a previous call, then the same contents might be returned again. It is a good idea to zero the static area pointed to by this pointer. Also the pututxline() function may internally call the getutx* set of functions, but this will not affect the contents of the static area that the getutx* set of functions return a pointer to. The pututxline() functions returns a pointer to the record passed as the argument  on success and NULL on failure.
By default all the getutx* functions work on the "utmp" file, we can change this (for example to the "wtmp" file) using the following function :

int utmpxname (const char *file); 

Note that this function does not open the file and thus will not report any errors pertaining to invalid paths, such errors will show up only on the consequent calls to the other getutx* functions or the setutxent() function.
Also since updates to the "wtmp" files are always just simple appends (remember that records are never removed from this file), this can be achieved using the following wrapper call which opens the file, writes the record and then closes it :

void updwtmpx (char *wtmpx_file, struct utmpx *ut);

Some systems might instead provide the updwtmp(char *, struct utmp *) or the logwtmp (const char *, const char *, const char *) functions for updating the wtmp file.

There is another file on linux (and some unix implementations) which provides useful session information, this is "/var/log/lastlog" which contains logs of the logout time for users. The records in this file are of type "struct lastlog"  defined in /usr/include/bits/utmp.h (search for lastlog). This information is sometimes used to display the last time when you logged into a machine on login.

Here is some sample code which reads the "utmp" file and lists the user name on each record. The code does not use the API calls but reads the records directly from the file, (this is not the preferred way of doing it though, the code is just a proof-of-concept).

Apart from "who", there are other commands like "last" which display information extracted from the utmp/wtmp files.

All the above information (and more to come) is a result of the time I have spending with the "The Linux Programming Interface". Chapter 40 (Login Accounting) in the book provides quite a lot more details and sample code listings which use the utmpx API and do a lot more than the above sample code.