Skip to content
/ reap Public

C library for Linux to make processing of procfs easier

License

Notifications You must be signed in to change notification settings

nickeldan/reap

Repository files navigation

"We will encourage you to develop the three great virtues of a programmer: laziness, impatience, and hubris.” - Larry Wall

The REAP library provides Ridiculously Easy Access to Procfs.

API

You can access all of REAP's functionality by

#include <reap/reap.h>

Process info

You can get basic information about a running process through the reapGetProcInfo function. Its signature is

int reapGetProcInfo(pid_t pid, reapProcInfo *info, char *exe_path, size_t path_size);

where reapProcInfo is defined as

typedef struct reapProcInfo {
    pid_t pid;  // Process ID.
    pid_t ppid; // Parent process ID.
    pid_t tid;  // Task ID.
    uid_t uid;  // User ID.
    uid_t euid; // Effective user ID.
    gid_t gid;  // Group ID.
    gid_t egid; // Effective group ID.
} reapProcInfo;

reapGetProcInfo returns REAP_RET_OK if successful and an error code otherwise (defined in reap/definitions.h). In addition to the error codes defined in that file, various REAP functions can return negative values. Such values will be equal to -1 * errno.

You can also pass a task ID (i.e., a value returned by the gettid system call) as pid.

If exe_path is not NULL and is of size path_size, then it will be populated with the path to the process' executable.

Error messages

When an error occurs, you can get a description of the error by

char *reapGetError(void);

The referenced buffer (which is unique to each thread) will be of size REAP_ERROR_BUFFER_SIZE (defined in reap/config.h).

Process iteration

A reapProcIterator can be used to iterate over all of the running processes (at least, those for which the user has permission to access).

First, the iterator has to be created:

int ret;
reapProcIterator *iterator;

ret = reapProcIteratorCreate(&iterator);
if ( ret != REAP_RET_OK ) {
    // handle the error
}

After that, we can repeatedly pull results from the iterator with

int reapProcIteratorNext(const reapProcIterator *iterator, reapProcInfo *info, char *exe_path, size_t path_size);

where the last three arguments are the same as for reapGetProcInfo.

This function returns REAP_RET_OK when yielding a result, REAP_RET_DONE when the iterator has been exhausted, and an error code otherwise.

The iterator must be destroyed when it is no longer needed:

reapProcIteratorDestroy(iterator);

File descriptor iteration

A reapFdIterator can be used to iterate over a process' open file descriptors.

First, the iterator has to be created:

int ret;
reapFdIterator *iterator;

ret = reapFdIteratorCreate(some_pid, &iterator);
if ( ret != REAP_RET_OK ) {
    // handle the error
}

After that, we can repeatedly acquire file descriptor information with

int reapFdIteratorNext(const reapFdIterator *iterator, reapFdResult *result, char *file, size_t file_size);

where reapFdResult is defined as

typedef struct reapFdResult {
    int fd;
    dev_t device;
    ino_t inode;
    mode_t mode;
} reapFdResult;

This function returns REAP_RET_OK when yielding a result, REAP_RET_DONE when the iterator has been exhausted, and an error code otherwise.

if file is not NULL and is of size file_size, then it will be populated with the name of the underlying file.

The iterator must be destroyed when it is no longer needed:

reapFdIteratorDestroy(iterator);

Memory map iteration

A reapMapIterator can be used to iterate over a process' mapped memory segments.

First, the iterator has to be created:

int ret;
reapMapIterator *iterator;

ret = reapMapIteratorCreate(some_pid, &iterator);
if ( ret != REAP_RET_OK ) {
    // handle the error
}

After that, we can repeatedly acquire mapped memory information with

int reapMapIteratorNext(const reapMapIterator *iterator, reapMapResult *result, char *name, size_t name_size);

where reapMapResult is defined as

typedef struct reapMapResult {
    unsigned long long start; // The start address of the memory segment.
    unsigned long long end; // The end address of the memory segment.
    unsigned long long offset; // The offset of the memory segment within the referent file (if any).
    int permissions; // The permissions of the memory segment.
    dev_t device; // The device number of the referent file (if any).
    ino_t inode; // The inode number of the referent file (if any).
} reapMapResult;

The permissions are some bitwise-OR combination of PROT_READ, PROT_WRITE, and PROT_EXEC from sys/mman.h.

This function returns REAP_RET_OK when yielding a result, REAP_RET_DONE when the iterator has been exhausted, and an error code otherwise.

If name is not NULL and is of size name_size, then it will be populated with the name of the memory segment.

The iterator must be destroyed when it is no longer needed:

reapMapIteratorDestroy(iterator);

Thread iteration

A reapThreadIterator can be used to iterate over a process' threads.

First, the iterator has to be created:

int ret;
reapThreadIterator *iterator;

ret = reapThreadIteratorCreate(some_pid, &iterator);
if ( ret != REAP_RET_OK ) {
    // handle the error
}

After initialization, we can repeatedly acquire thread IDs with

int reapThreadIteratorNext(const reapThreadIterator *iterator, pid_t *thread);

This function returns REAP_RET_OK when yielding a result, REAP_RET_DONE when the iterator has been exhausted, and an error code otherwise.

The iterator must be destroyed when it is no longer needed:

reapThreadIteratorDestroy(iterator);

Socket iteration

A reapNetIterator can be used to iterate over the open sockets in the network namespace.

First, the iterator has to be created:

int ret;
reapNetIterator *iterator;

ret = reapNetIteratorCreate(0, &iterator);
if ( ret != REAP_RET_OK ) {
    // handle the error
}

The first parameter to the function holds zero or more flags combined with bitwise-OR. The available flags are

  • REAP_NET_FLAG_UDP: Find UDP sockets.
  • REAP_NET_FLAG_IPV6: Find IPv6 sockets.
  • REAP_NET_FLAG_DOMAIN: Find Unix domain sockets. If this flag is specified, then all other flags are ignored.

By default, the iterator will find TCP sockets over IPv4.

After initialization, we can repeatedly acquire socket information with

int reapNetIteratorNext(const reapNetIterator *iterator, reapNetResult *result);

where reapNetResult is defined as

typedef struct reapNetResult {
    union {
        struct { // For IP sockets.
            reapNetPeer local;
            reapNetPeer remote;
        };
        struct { // For Unix domain sockets.
            char path[108]; // Actually, the size is the same as that of the sun_path field of struct sockaddr_un.
            int socket_type; // E.g., SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET.
            unsigned int connected : 1;
        };
    };
    ino_t inode; // The inode number of the socket.
    unsigned int flags; // The flags that were passed to reapNetIteratorCreate.
} reapNetResult;

where reapNetPeer is defined as

typedef struct reapNetPeer {
    uint16_t port;
    uint8_t address[16];
} reapNetPeer;

If representing a Unix domain socket which is connected to an abstract socket address (i.e., where the first character of the path is a null byte), then all of the null bytes in the path will be replaced by '@'s.

This function returns REAP_RET_OK when yielding a result, REAP_RET_DONE when the iterator has been exhausted, and an error code otherwise.

The iterator must be destroyed when it is no longer needed:

reapNetIteratorDestroy(iterator);

Building REAP

Shared and static libraries are built using make. Adding debug=yes to the make invocation will disable optimization and build the libraries with debugging symbols.

You can also include REAP in a larger project by including make.mk. Before doing so, however, the REAP_DIR variable must be set to the location of the REAP directory. You can also tell make where to place the shared and static libraries by defining the REAP_LIB_DIR variable (defaults to $(REAP_DIR)). Similarly, you can define the REAP_OBJ_DIR variable which tells make where to place the object files (defaults to $(REAP_DIR)/src).

make.mk adds a target to the CLEAN_TARGETS variable. This is so that implementing

clean: $(CLEAN_TARGETS)
    ...

in your project's Makefile will cause REAP to be cleaned up as well.

The CLEAN_TARGETS variable should be added to .PHONY if you're using GNU make.

make.mk defines the variables REAP_SHARED_LIBRARY and REAP_STATIC_LIBRARY which contain the paths of the specified libraries.

Testing

Testing can be performed through the Scrutiny framework. After installing at least version 0.5.0 of the framework, you can run REAP's tests by

make tests