Notes 4: Common Unix Data Files

Corresponds to Chapter 6 in Advanced Programming in the Unix Environment.

Password file

The system passwords and other information are stored in the /etc/passwd file (except in the case of shadow passwords, where the passwords are contained within another root-readable file). The file itself has one record per line, with color delimited fields. Fortunately, there are a bunch of library functions in <pwd.h> to read these records for you. Don't read them "by hand" or attempt to roll your own ones of these--you will only reduce portability.

struct passwd *getpwuid(uid_t uid);

For a given user ID (UID), return a struct passwd filled with all kinds of goodies:

    struct passwd {
        char    *pw_name;       /* user name */
        char    *pw_passwd;     /* user password */
        uid_t   pw_uid;         /* user id */
        gid_t   pw_gid;         /* group id */
        char    *pw_gecos;      /* real name */
        char    *pw_dir;        /* home directory */
        char    *pw_shell;      /* shell program */
    };

The pw_gecos field is a comment field which contains comma-delimited stuff. Here on Linux, my real name is "Beej!", my office is "Mars", followed by office phone and home phone:

pw_gecos = "Beej!,Mars,(415)845-2913,(916)331-8838,"

The finger and chfn programs know how to order these fields.

A UID is just an int; on my system I am UID 501, so I could call this function with:

    struct passwd *mine;
    mine = getpwuid(501);

This returns a pointer to a static structure so results of one call will be overwritten by the results of the next unless you copy the struct.

struct passwd *getpwnam(const char * name);

Getting a password entry by UID is kind of a pain, generally. It would be much nicer to get it by username:

    struct passwd *mine;
    mine = getpwnam("beej");

Just like getpwuid(), see?

struct passwd *getpwent(void);

Sometimes you want to scan through the password file an entry at a time looking for God-knows-what and don't know a specific UID or user name. Maybe you want to know everyone in a certain group or something like that.

This function will automatically open the password file the first time it is called and will return the first entry. Subsequent calls will return subsequent entries in the password file. Finally, it will return NULL when the end of the file is reached.

void setpwent(void);

Resets the file pointer used by getpwent() to the beginning of the file. It's a good idea to call this before using getpwent().

void endpwent(void);

Closes the password file when you're done with it and frees up the allocated file pointers. Call this when you're done.

The following example prints all the user names from the password file:

    #include <stdio.h>
    #include <stdlib.h>
    #include <pwd.h>
    #include <sys/types.h>

    main()
    {
        struct passwd *ent;

        setpwent();

        while((ent = getpwent()) != NULL)
            printf("%s\n", ent->pw_name);

        endpwent();
    }

Group file

The /etc/group lists groups and people who are in those groups. There is a set of functions defined in <grp.h> that parses this file.

struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);

Both of these functions work similarly to their getpwnam() and getpwuid() counterparts. They return, for a given GID or group name, a struct group filled with info:

    struct group {
        char    *gr_name;        /* group name */
        char    *gr_passwd;      /* group password */
        gid_t   gr_gid;          /* group id */
        char    **gr_mem;        /* group members */
    };

The gr_mem field contains the names of the people listed for that group in the group file. For people who choke on double indirection, here's an example that reads the info for my "doom" group:

    #include <stdio.h>
    #include <stdlib.h>
    #include <grp.h>
    #include <sys/types.h>

    main()
    {
        struct group *ent;

        if ((ent = getgrnam("doom")) == NULL) {
            printf("bad group\n");
            exit(1);
        }

        printf("gr_name = \"%s\"\n", ent->gr_name);
        printf("gr_passwd = \"%s\"\n", ent->gr_passwd);
        printf("gr_gid = %d\n", ent->gr_gid);
        while(*(ent->gr_mem) != NULL)
            printf("   \"%s\"\n", *((ent->gr_mem)++));
    }

The output is:

    gr_name = "doom"
    gr_passwd = ""
    gr_gid = 101
       "beej"
       "becca"
       "bapper"

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

These work just like the getpwent() function, except they operate on the group file. If you ever want to read through the group file an entry at a time, these functions are for you.

int getgroups(int size, gid_t list[]);

Returns a list of groups for the current UID in list. The argument size should be set to the size of the list array. The function returns the actual number of groups that were inserted into the array.

A broad example shows you which groups you're in (using getgrgid() to get the groups' names):

    #include <stdio.h>
    #include <stdlib.h>
    #include <grp.h>
    #include <sys/types.h>
    #include <errno.h>

    main()
    {
        struct group *ent;
        gid_t gids[50];
        int i,n;

        if ((n = getgroups(50, gids)) == -1) {
            perror("getgroups");
            exit(1);
        }

        printf("%d groups:\n", n);
        for(i = 0; i < n; i++) {
            if ((ent = getgrgid(gids[i])) == NULL) {
                fprintf(stderr, "no such group %d\n", gids[i]);
                exit(1);
            }
            printf("  %d %s\n", gids[i], ent->gr_name);
        }
    }

The output for me is the following:

    2 groups:
      100 users
      101 doom

Login Accounting

Remember the Unix "who" command? It dumps information similar to the following:

    beej     tty1     Aug 17 16:20
    beej     ttyp1    Aug 17 16:20 (:0.0)

That is, who you are, where you are logged in at (which terminal), when you logged in, and where you logged in from (if not local).

All of the above information (and more) is stored in a file called "/etc/utmp" and is accessable through a slew of widely implemented but not POSIX-compliant functions defined in <utmp.h>.

void utmpname(const char *file);
void setutent(void);
void endutent(void);

These are all setup functions for reading the utmp file, and perform similar functions as their counterparts for /etc/passwd and /etc/group. The new one is utmpname(), which allows you to set the name of the utmp file to open. (Defaults to /etc/utmp, generally.)

Call utmpname() first (if you want to), then setutent() to rewind the file to the beginning. When you're through reading the file (below), call endutent() to clean up.

struct utmp *getutent(void);

This function reads the next entry in the utmp file into a static structure and returns a pointer to it. (It returns NULL on EOF.) The structure (on Linux) is as follows (you should check your local man pages for the proper struct for your platform, or look it up in /usr/include/utmp.h):

    struct utmp
    {
        short   ut_type;                /* type of login */
        pid_t   ut_pid;                 /* pid of login-process */
        char    ut_line[UT_LINESIZE];   /* devicename of tty -"/dev/" */
        char    ut_id[4];               /* inittab id */
        time_t  ut_time;                /* login time */
        char    ut_user[UT_NAMESIZE];   /* username, not null-term */
        char    ut_host[UT_HOSTSIZE];   /* hostname for remote login... */
        long    ut_addr;                /* IP addr of remote host */
    };

Some of the entries might not be null-terminated (like ut_user here), so be careful.

One of the more interesting fields is the ut_type field--it contains the type of the utmp entry we're looking at. You might be most interested in the case where ut_type is equal to USER_PROCESS, since this is what "who" looks for; check the man page for a more complete list of values.

struct utmp *getutid(struct utmp *ut);

When you're looking for a specific ut_type, use this function. By loading this field with a value in your struct, then passing it to getutid(), the function will return the first information in the utmp file that matches your requested ut_type.

struct utmp *getutline(struct utmp *ut);

This variation searches through the utmp file for ut_type==USER_PROCESS or ut_type==LOGIN_PROCESS an returns entries where the ut_line matches the ut->ut_line passed into the function.

System ID

There is a popular Unix command "uname" that tells you the name of your machine, OS version, etc., comme ça:

    $ uname -a
    Linux frog 2.0.29 #2 Wed Mar 12 11:31:31 PST 1997 i586

As you might have guessed, there is a Unix system call by the same name.

int uname(struct utsname *buf);

The data is returned in buf, which is of type:

    struct utsname {
        char sysname[9];  /* OS name */
        char nodename[9]; /* system name */
        char release[9];  /* OS release */
        char version[9];  /* OS release version */
        char machine[9];  /* machine type */
    };

(All these arrays are declared as 65-length under Linux--your mileage may vary.)

I can mimic the output of "uname -a" using the following code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/utsname.h>

    main()
    {
        struct utsname buf;

        if (uname(&buf) == -1) {
            perror("uname");
            exit(1);
        }

        printf("%s %s %s ", buf.sysname, buf.nodename, buf.release);
        printf("%s %s\n", buf.version, buf.machine);
    }

Time Routines

There are two types of time variables: 1) time_t, which records the number of seconds since Epoch (it's usually a long), and 2) struct tm, which contains minutes, hours, seconds, day of week, etc.:

    struct tm
    {
        int     tm_sec;         /* seconds */
        int     tm_min;         /* minutes */
        int     tm_hour;        /* hours */
        int     tm_mday;        /* day of the month */
        int     tm_mon;         /* month */
        int     tm_year;        /* year */
        int     tm_wday;        /* day of the week */
        int     tm_yday;        /* day in the year */
        int     tm_isdst;       /* daylight saving time */
    };

Confusion reigns when people see that some functions use different time types than others. To make matters even more interesting, there are multiple functions to convert from one type to another.

time_t time(time_t *t);

This function returns the time since Epoch. If t is not NULL, it also assigns it to the time_t to which t points.

char *ctime(const time_t *timep);

Given a time_t*, returns a pointer to a static string containing a human-readable time. You might want to use strftime() for more control over what happens.

struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);

Both of these functions convert a time_t into a struct tm which can be used in other functions. gmtime() converts the time_t into Universal Time (UTC or GMT). The second, localtime(), takes the timezone into account (and daylight savings time, if applicable) and converts the time_t into a struct tm. You'll probably be using localtime() more often.

To change the timezone, use the tzset() function.

time_t mktime(struct tm *timeptr);

This function goes the other way: from struct tm back to time_t. If you have the individual elements of the time, load them into the struct tm and pass them to this function. If you don't know some of the elements, assign -1 to the fields. (Of course, there are a minimum number of fields that need to be loaded for mktime() to figure it all out.)

char *asctime(const struct tm *timeptr);

This function is just like ctime(), except that it takes a struct tm instead of a time_t.

size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

This is the end-all function for printing times. You pass it a string that will hold the final result, the maximum length of that string, a format specifier that will tell it how to print the time, and a struct tm that has the current time loaded.

Check the man page for the individual format specifiers, but here's a sample:

    time_t t1;
    char str[100];

    time(&t1);
    strftime(str, 100, "%A, %B %d", localtime(&t1));
    printf("%s\n", str);

The above example will print something like:

    Monday, August 18

As you can imagine, there are format specifiers for just about every date-related thing you can think of.

int stime(time_t *t);

If you're root, you can set the system time with this call.

int gettimeofday(struct timeval *tv, struct timezone *tz);

You probably won't ever use these, but here goes.

Ok, so I lied: there's another time structure. Welcome to struct timeval and struct timezone:

    struct timeval {
        long tv_sec;   /* seconds */
        long tv_usec;  /* microseconds */
    };

    struct timezone {
        int  tz_minuteswest; /* minutes west of Greenwich */
        int  tz_dsttime;     /* type of dst correction */
    };

These will be loaded properly once the call to gettimeofday() returns. Note that this is a BSD function and might not be supported under all flavors of Unix. Linux implements it, of course.

Also, if you're root, you can call the very similar settimeofday() to set the time.