Example 12.1

Write a program that lists the process ID and command name for all processes being run by the user named in the program’s command-line argument. (You may find the userIdFromName() function from Example 8-1, in Example program, useful.) This can be done by inspecting the Name: and Uid: lines of all of the /proc/PID/status files on the system. Walking through all of the /proc/PID directories on the system requires the use of readdir(3), which is described in Section 18.8. Make sure your program correctly handles the possibility that a /proc/PID directory disappears between the time that the program determines that the directory exists and the time that it tries to open the corresponding /proc/PID/status file.

Usage:


chapter12questionOne username

Source Code

Disclaimer: I don't write C code professionally, I'm simply using Michael Kerrick's book on the Linux Programming interface to learn more about the Linux Operating System. Use this code without warranty or as a reference as you work through the problems in the book yourself.


/**
 * Michael Trottier
 *
 * Chapter 12 Question 1 Linux Programming Interface
 *
 * Write a program that lists the process ID and command name for all processes being run by the user named in the
 * program’s command-line argument. (You may find the userIdFromName() function from Example 8-1, in Example program,
 * useful.) This can be done by inspecting the Name: and Uid: lines of all of the /proc/PID/status files on the system.
 * Walking through all of the /proc/PID directories on the system requires the use of readdir(3), which is described
 * in Section 18.8. Make sure your program correctly handles the possibility that a /proc/PID directory disappears
 * between the time that the program determines that the directory exists and the time that it tries to open the
 * corresponding /proc/PID/status file.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <ctype.h>
#include <dirent.h>
#include <stdbool.h>

#define BUF_SIZE 8096

/**
 * This method was taken from 8-1 per the recommendation in the question text above. It simply gets the users
 * ID from the /etc/passwd file.
 *
 * @return UID corresponding to 'name', or -1 on error
 */
uid_t userIdFromName(const char *name)
{
    struct passwd *pwd;
    uid_t u;
    char *endptr;

    if (name == NULL || *name == '\0')  /* On NULL or empty string */
        return -1;                      /* return an error */

    /*
    * The strtol() function converts the initial part of the string in nptr
    * to a long integer value according to the given base, which must be
    * between 2 and 36 inclusive, or be the special value 0.
    *
    * The string may begin with an arbitrary amount of white space (as
    * determined by isspace(3)) followed by a single optional '+' or '-'
    * sign.  If base is zero or 16, the string may then include a "0x" or
    * "0X" prefix, and the number will be read in base 16; otherwise, a
    * zero base is taken as 10 (decimal) unless the next character is '0',
    * in which case it is taken as 8 (octal).
    *
    * The remainder of the string is converted to a long int value in the
    * obvious manner, stopping at the first character which is not a valid
    * digit in the given base.  (In bases above 10, the letter 'A' in
    * either uppercase or lowercase represents 10, 'B' represents 11, and
    * so forth, with 'Z' representing 35.)
    *
    * If endptr is not NULL, strtol() stores the address of the first
    * invalid character in *endptr.  If there were no digits at all,
    * strtol() stores the original value of nptr in *endptr (and returns
    * 0).  In particular, if *nptr is not '\0' but **endptr is '\0' on
    * return, the entire string is valid.
    *
    * The strtoll() function works just like the strtol() function but
    * returns a long long integer value.
    */
    u = strtol(name, &endptr, 10);      /* As a convenience to caller */
    if (*endptr == '\0')                /* allow a numeric string */
        return u;

    pwd = getpwnam(name);
    if (pwd == NULL)
        return -1;

    return pwd->pw_uid;
}

/**
 * The purpose of the function is to get the processes associated with the user name provided by the command line
 * arguments.  We will be iterating through all the /proc/PID/status files. Comparing the UID to the UID in the file
 * and printing out the PID name.
 *
 * @return int 0 for success any other integer for failure
 */
int getUserProcessInformation(char *userName) {
    int errnoSaved;

    uid_t userUID = userIdFromName(userName);
    errnoSaved = errno;

    if (0 > userUID) {
        fprintf(stderr, "%s", strerror(errnoSaved));
        fprintf(stderr, "Couldn't find the username specified %s\n", userName);
    }

    fprintf(stdout, "Got UID for User: %s with UID: %d\n", userName, userUID);

    DIR *procDirectory;
    struct dirent *procDirectoryEntry;

    procDirectory = opendir("/proc/");
    errnoSaved = errno;

    if (0 > procDirectory) {
        fprintf(stderr, "%s", strerror(errnoSaved));
        fprintf(stderr, "Couldn't open the proc filesystem.\n");
    }

    int counter = 0;
    char processNames[2048][100] = { 0 };

    /*
     * Read through every directoryEntry is the directory specified.  In this case we are specifying the directory
     * to be the proc filesystem and we will be reading every /proc/PID/status file.
     */
    while ((procDirectoryEntry = readdir(procDirectory)) != NULL) {

        char pidStatusFileName[1024] = "/proc/";
        strcat(pidStatusFileName, procDirectoryEntry->d_name);
        strcat(pidStatusFileName, "/status");

        FILE *pidStatusStream;
        pidStatusStream = fopen(pidStatusFileName, "r");
        errnoSaved = errno;

        // Some of the file directories in /proc/ aren't associated with PIDs and don't have a status file
        if (pidStatusStream == NULL) {
            fprintf(stderr, "Having issues opening: %s\n", pidStatusFileName);
            fprintf(stderr, "%s\n", strerror(errnoSaved));
            continue;
        }

        size_t length = 0;
        char userUidString[64];
        sprintf(userUidString, "%d", userUID);
        char *lineRead = NULL;
        char processName[1024];
        char processId[256];
        bool userProcess = false;

        /**
         * Iterate through every line in the /proc/PID/status file. It looks similar to the output below,
         * there is more information after UID but this was snipped as it wasn't needed for this problem.
         *
         *  Name:	bash
         *  Umask:	0022
         *  State:	S (sleeping)
         *  Tgid:	58472
         *  Ngid:	0
         *  Pid:	58472
         *  PPid:	58451
         *  TracerPid:	0
         *  Uid:	3179777	3179777	3179777	3179777
         */
        while (getline(&lineRead, &length, pidStatusStream) != -1) {
            /*
             * The strcmp() function compares the two strings s1 and s2. It returns an integer less than, equal to,
             * or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2.
             * The strncmp() function is similar, except it only compares the first (at most) n bytes of s1 and s2.
             *
             * Ensure the line starts with "Uid:"
             */
            if (0 == strncmp("Name:", lineRead, strlen("Name:"))) {
                strcpy(processName, lineRead);
            }

            if (0 == strncmp("Pid:", lineRead, strlen("Pid:"))) {
                strcpy(processId, lineRead);
            }

            // Ensure the line starts with "Uid:"
            if (0 == strncmp("Uid:", lineRead, strlen("Uid:"))) {

                /*
                 * The strstr() function finds the first occurrence of the substring needle in the string haystack.
                 * The terminating null bytes (aq\0aq) are not compared.
                 * The first argument is the haystack the second argument is the needle.
                 */
                if (strstr(lineRead, userUidString) != NULL) {
                    // Uncomment for extra debug information.
                    // fprintf(stdout, "Compared UID with UID in File, there was match!\n");
                    // fprintf(stdout, "%s UID: %d\n", userName, userUID);
                    // fprintf(stdout, "%s", lineRead);
                    userProcess = true;
                    break;
                }
            }
        }

        if (userProcess) {
            // Uncomment for extra debug information.
            // fprintf(stdout, "Process %s", processName);
            strcpy(processNames[counter], processName);
            strcat(processNames[counter], processId);
            counter++;
        }

        fclose(pidStatusStream);
    }

    fprintf(stdout, "Printing Summary of Processes owned by %s with UID: %d\n", userName, userUID);

    counter = 0;
    while (counter < 2048 && strlen(processNames[counter]) != 0) {
        fprintf(stdout, "%s", processNames[counter]);
        counter++;
    }

    return closedir(procDirectory);
}

int main(int argc, char *argv[]) {
    // Take as input a user name or don't rum
    if (argc <= 1) {
        fprintf(stderr, "usage: %s userName\n",argv[0]);
        return 1;
    }

    char *userName = argv[1];

    return getUserProcessInformation(userName);
}