2
\$\begingroup\$

The following logic provides iteration over /proc/<PID>/maps for a Linux process. What I'm primarily (though not solely) interested in for this review is whether or not I've made any invalid assumptions about the layout of the maps file.

iterate_map.h

#ifndef REAP_ITERATE_MAP_H
#define REAP_ITERATE_MAP_H

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

#define REAP_PATH_SIZE 256

enum reapRetValue {
    REAP_RET_OK,             /**< The function was successful.*/
    REAP_RET_DONE,           /**< The iteration is finished.*/
    REAP_RET_BAD_USAGE,      /**< A funciton was called with invalid arguments.*/
    REAP_RET_OUT_OF_MEMORY,  /**< A memory allocation failed.*/
    REAP_RET_NO_PERMISSION,  /**< No permission to access a requested resource.*/
    REAP_RET_NOT_FOUND,      /**< A requested resource was not found.*/
    REAP_RET_TOO_MANY_LINKS, /**< Too many symbolic links encountered.*/
    REAP_RET_FILE_READ,      /**< Failed to read from a file.*/
    REAP_RET_OTHER,          /**< Catch-all error.*/
};

/**
 * @brief Iterates over all line in a /proc/<PID>/maps file.
 *
 * @note User code should not access the iterator's fields.
 */
typedef struct reapMapIterator {
    FILE *file;
} reapMapIterator;

/**
 * @brief Result generated by a reapMapIterator.
 */
typedef struct reapMapResult {
    unsigned long start;             /**< The start of the memory section.*/
    unsigned long end;               /**< The end of the memory section.*/
    unsigned int offset;             /**< The offset of the memory section in the referent file.*/
    int permissions;                 /**< The permissions of the memory section.*/
    dev_t device;                    /**< The device number of the referent file.*/
    ino_t inode;                     /**< The inode of the referent file.*/
    char name[REAP_PATH_SIZE];       /**< The name of the referent file.*/
} reapMapResult;

/**
 * @brief Initializes an iterator.
 *
 * @param pid       The PID.
 * @param iterator  A pointer to the iterator.
 *
 * @return          REAP_RET_OK if successful and an error code otherwise.
 */
int
reapMapIteratorInit(pid_t pid, reapMapIterator *iterator);

/**
 * @brief Closes an iterator.
 *
 * @param iterator A pointer to the iterator.
 */
void
reapMapIteratorClose(reapMapIterator *iterator);

/**
 * @brief Gets the next result.
 *
 * @param iterator      A pointer to the iterator.
 * @param[out] result   A pointer to the result to be populated.
 *
 * @return              REAP_RET_OK if a result was found, REAP_RET_DONE if the iteration is finished, and an
 * error code otherwise.
 */
int
reapMapIteratorNext(const reapMapIterator *iterator, reapMapResult *result);

#endif  // REAP_ITERATE_MAP_H

iterate_map.c

#include <errno.h>
#include <string.h>
#include <sys/sysmacros.h>
#include <sys/types.h>

#include <reap/iterate_map.h>

static int
translateErrno(void)
{
    switch (errno) {
    case EINVAL: return REAP_RET_BAD_USAGE;
    case EACCES: return REAP_RET_NO_PERMISSION;
    case ELOOP: return REAP_RET_TOO_MANY_LINKS;
    case ENOENT:
    case ENOTDIR:
    case EIO: return REAP_RET_FILE_READ;
    case ENOMEM: return REAP_RET_OUT_OF_MEMORY;
    default: return REAP_RET_OTHER;
    }
}

int
reapMapIteratorInit(pid_t pid, reapMapIterator *iterator)
{
    char buffer[100];

    if (pid <= 0 || !iterator) {
        return REAP_RET_BAD_USAGE;
    }

    snprintf(buffer, sizeof(buffer), "/proc/%li/maps", (long)pid);
    iterator->file = fopen(buffer, "r");
    if (!iterator->file) {
        return translateErrno();
    }

    return REAP_RET_OK;
}

void
reapMapIteratorClose(reapMapIterator *iterator)
{
    if (iterator && iterator->file) {
        fclose(iterator->file);
        iterator->file = NULL;
    }
}

int
reapMapIteratorNext(const reapMapIterator *iterator, reapMapResult *result)
{
    int num_matches;
    unsigned int major, minor;
    unsigned long inode;
    char r, w, x;
    char line[256];

    if (!iterator || !iterator->file || !result) {
        return REAP_RET_BAD_USAGE;
    }

    if (!fgets(line, sizeof(line), iterator->file)) {
        if (ferror(iterator->file)) {
            return REAP_RET_FILE_READ;
        }
        else {
            return REAP_RET_DONE;
        }
    }
    num_matches = sscanf(line, "%lx-%lx %c%c%c%*c %x %x:%x %lu %s", &result->start, &result->end, &r, &w, &x,
                         &result->offset, &major, &minor, &inode, result->name);
    if (num_matches < 9) {
        return REAP_RET_OTHER;
    }

    if (num_matches == 9) {
        result->name[0] = '\0';
    }

    result->permissions = 0;
    if (r == 'r') {
        result->permissions |= PROT_READ;
    }
    if (w == 'w') {
        result->permissions |= PROT_WRITE;
    }
    if (x == 'x') {
        result->permissions |= PROT_EXEC;
    }

    result->device = makedev(major, minor);
    result->inode = inode;

    return REAP_RET_OK;
}
\$\endgroup\$
4
  • \$\begingroup\$ Why 256 in #define REAP_PATH_SIZE 256? 256 deserves some explanation. \$\endgroup\$ Aug 25, 2022 at 7:45
  • \$\begingroup\$ Are you asking why it's a power of 2 or why I'm not using the already-defined PATH_SIZE? \$\endgroup\$ Aug 25, 2022 at 13:20
  • \$\begingroup\$ Daniel Walker, OK, why power of 2, why not PATH_SIZE, why undocumented? \$\endgroup\$ Aug 25, 2022 at 14:45
  • \$\begingroup\$ I didn't want to make the structure take up too much space. Consider if I wanted to form an array or linked list of all of a process' map entries. That's a lot of memory to allocate when, in all likelihood, none of the paths will be anywhere near PATH_SIZE in length. \$\endgroup\$ Aug 27, 2022 at 2:19

1 Answer 1

2
\$\begingroup\$

If user code is not supposed to access the innards of a reapMapIterator, it's best to make it an incomplete type in the header, and define it only in the implementation. A small change is needed to the interface, so that reapMapIteratorInit() returns an allocated reapMapIterator and reapMapIteratorClose() also frees the memory.

I don't like the translation of errno values to reapRetValue - the former are much more usable, given that strerror() already exists to provide translated user messages.

\$\endgroup\$
1
  • \$\begingroup\$ Perhaps I could return -1*errno instead so that the caller knows when an errno value is being returned. \$\endgroup\$ Aug 22, 2022 at 13:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.