Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ static void print_usage_and_exit(FILE *stream, const struct cpulimitcfg *cfg,
fprintf(
stream,
" COMMAND [ARG]... run the command and limit CPU usage (implies -z)\n");
exit(exit_code);
fflush(stream);
_exit(exit_code);
}

/**
Expand Down Expand Up @@ -206,10 +207,11 @@ static void validate_target_options(const struct cpulimitcfg *cfg) {
* @param cfg Pointer to configuration structure to be filled with parsed values
*
* This function processes all command-line options, validates the input,
* and exits the program (via exit()) if any errors are encountered or if
* and exits the program (via _exit()) if any errors are encountered or if
* help is requested. Upon successful return, cfg contains valid configuration.
*
* @note This function calls exit() and does not return on error or help request
* @note This function calls _exit() and does not return on error or help
* request
*/
void parse_arguments(int argc, char *const *argv, struct cpulimitcfg *cfg) {
int opt, n_cpu;
Expand Down
5 changes: 3 additions & 2 deletions src/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ struct cpulimitcfg {
* @param cfg Pointer to configuration structure to be filled with parsed values
*
* This function processes all command-line options, validates the input,
* and exits the program (via exit()) if any errors are encountered or if
* and exits the program (via _exit()) if any errors are encountered or if
* help is requested. Upon successful return, cfg contains valid configuration.
*
* @note This function calls exit() and does not return on error or help request
* @note This function calls _exit() and does not return on error or help
* request
*/
void parse_arguments(int argc, char *const *argv, struct cpulimitcfg *cfg);

Expand Down
2 changes: 0 additions & 2 deletions src/limit_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@
#include <unistd.h>

/* Very small value to prevent division by zero in calculations */
#ifndef EPSILON
#define EPSILON 1e-12
#endif

/*
* Base control time slot in microseconds.
Expand Down
25 changes: 23 additions & 2 deletions src/process_group.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ int init_process_group(struct process_group *pgroup, pid_t target_pid,

/* Record baseline timestamp for CPU usage calculation */
if (get_current_time(&pgroup->last_update) != 0) {
clear_list(pgroup->proclist);
free(pgroup->proclist);
pgroup->proclist = NULL;
process_table_destroy(pgroup->proctable);
free(pgroup->proctable);
pgroup->proctable = NULL;
exit(EXIT_FAILURE);
}
/* Perform initial scan to populate process list */
Expand All @@ -205,13 +211,17 @@ int init_process_group(struct process_group *pgroup, pid_t target_pid,
* 2. Destroys and frees the process hashtable
* 3. Sets both pointers to NULL for safety
*
* @note Safe to call with NULL pgroup (does nothing)
* @note Safe to call even if pgroup is partially initialized (NULLs are
* handled)
* @note Does not send any signals to processes; they continue running
* @note After return, pgroup fields should not be accessed without
* re-initialization
*/
int close_process_group(struct process_group *pgroup) {
if (pgroup == NULL) {
return 0;
}
if (pgroup->proclist != NULL) {
clear_list(pgroup->proclist);
free(pgroup->proclist);
Expand Down Expand Up @@ -243,7 +253,8 @@ static struct process *process_dup(const struct process *proc) {
fprintf(stderr, "Memory allocation failed for duplicated process\n");
exit(EXIT_FAILURE);
}
return (struct process *)memcpy(p, proc, sizeof(struct process));
memcpy(p, proc, sizeof(struct process));
return p;
}

/**
Expand Down Expand Up @@ -287,6 +298,7 @@ static struct process *process_dup(const struct process *proc) {
* - Handles backward time jumps (system clock adjustment)
* - New processes have cpu_usage=-1 until first valid measurement
*
* @note Does nothing if pgroup is NULL
* @note Should be called periodically (e.g., every 100ms) during CPU limiting
* @note Calls exit(EXIT_FAILURE) on critical errors (iterator init, time
* retrieval)
Expand All @@ -298,6 +310,10 @@ void update_process_group(struct process_group *pgroup) {
struct timespec now;
double dt;

if (pgroup == NULL) {
return;
}

/* Get current timestamp for delta calculation */
if (get_current_time(&now) != 0) {
exit(EXIT_FAILURE);
Expand Down Expand Up @@ -417,7 +433,8 @@ void update_process_group(struct process_group *pgroup) {
* @brief Calculate aggregate CPU usage across all processes in the group
* @param pgroup Pointer to the process_group structure to query
* @return Sum of CPU usage values for all processes with known usage, or
* -1.0 if no processes have valid CPU measurements yet
* -1.0 if no processes have valid CPU measurements yet, or
* -1.0 if pgroup is NULL
*
* CPU usage is expressed as a fraction of total system CPU capacity:
* - 0.0 = idle
Expand All @@ -430,11 +447,15 @@ void update_process_group(struct process_group *pgroup) {
* 3. Returns -1 if all processes have unknown usage (first update cycle)
*
* @note Returns -1 rather than 0 to distinguish "no usage" from "unknown"
* @note Returns -1 if pgroup is NULL
* @note Thread-safe if pgroup is not being modified concurrently
*/
double get_process_group_cpu_usage(const struct process_group *pgroup) {
const struct list_node *node;
double cpu_usage = -1;
if (pgroup == NULL) {
return -1;
}
for (node = first_node(pgroup->proclist); node != NULL; node = node->next) {
const struct process *p = (struct process *)node->data;
/* Skip processes without valid CPU measurements yet */
Expand Down
6 changes: 5 additions & 1 deletion src/process_group.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ int init_process_group(struct process_group *pgroup, pid_t target_pid,
* 2. Destroys and frees the process hashtable
* 3. Sets both pointers to NULL for safety
*
* @note Safe to call with NULL pgroup (does nothing)
* @note Safe to call even if pgroup is partially initialized (NULLs are
* handled)
* @note Does not send any signals to processes; they continue running
Expand All @@ -166,6 +167,7 @@ int close_process_group(struct process_group *pgroup);
* - Handles backward time jumps (system clock adjustment)
* - New processes have cpu_usage=-1 until first valid measurement
*
* @note Does nothing if pgroup is NULL
* @note Should be called periodically (e.g., every 100ms) during CPU limiting
* @note Calls exit(EXIT_FAILURE) on critical errors (iterator init, time
* retrieval)
Expand All @@ -176,7 +178,8 @@ void update_process_group(struct process_group *pgroup);
* @brief Calculate aggregate CPU usage across all processes in the group
* @param pgroup Pointer to the process_group structure to query
* @return Sum of CPU usage values for all processes with known usage, or
* -1.0 if no processes have valid CPU measurements yet
* -1.0 if no processes have valid CPU measurements yet, or
* -1.0 if pgroup is NULL
*
* CPU usage is expressed as a fraction of total system CPU capacity:
* - 0.0 = idle
Expand All @@ -189,6 +192,7 @@ void update_process_group(struct process_group *pgroup);
* 3. Returns -1 if all processes have unknown usage (first update cycle)
*
* @note Returns -1 rather than 0 to distinguish "no usage" from "unknown"
* @note Returns -1 if pgroup is NULL
* @note Thread-safe if pgroup is not being modified concurrently
*/
double get_process_group_cpu_usage(const struct process_group *pgroup);
Expand Down
6 changes: 6 additions & 0 deletions src/process_iterator_apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ static int read_process_info(pid_t pid, struct process *p, int read_cmd) {
* and processes not matching the PID filter criteria.
*/
int get_next_process(struct process_iterator *it, struct process *p) {
if (it == NULL || p == NULL) {
return -1;
}
if (it->i >= it->count) {
return -1;
}
Expand Down Expand Up @@ -363,6 +366,9 @@ int get_next_process(struct process_iterator *it, struct process *p) {
* After this call, the iterator must not be used until re-initialized.
*/
int close_process_iterator(struct process_iterator *it) {
if (it == NULL) {
return -1;
}
if (it->pidlist != NULL) {
free(it->pidlist);
it->pidlist = NULL;
Expand Down
35 changes: 28 additions & 7 deletions src/process_iterator_freebsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,26 @@ static int get_single_process(kvm_t *kd, pid_t pid, struct process *process,
* @brief Internal helper to get parent process ID without opening kvm
* @param kd Kernel virtual memory descriptor (must be already open)
* @param pid Process ID to query
* @return Parent process ID on success, -1 on error or if process not found
* @return Parent process ID on success, -1 on error, if process not found,
* if the process is a system process (e.g., PID 0 swapper), or if
* the process is a zombie
*
* Uses an existing kvm descriptor to query PPID. This avoids the overhead
* of repeatedly opening and closing kvm when checking multiple processes.
* System processes (P_SYSTEM flag) and zombie processes are treated as not
* found and return -1.
*/
static pid_t _getppid_of(kvm_t *kd, pid_t pid) {
int count;
struct kinfo_proc *kproc = kvm_getprocs(kd, KERN_PROC_PID, pid, &count);
return (count == 0 || kproc == NULL) ? (pid_t)(-1) : kproc->ki_ppid;
if (count == 0 || kproc == NULL) {
return (pid_t)(-1);
}
/* Skip system processes (e.g., PID 0 swapper) and zombie processes */
if ((kproc->ki_flag & P_SYSTEM) || (kproc->ki_stat == SZOMB)) {
return (pid_t)(-1);
}
return kproc->ki_ppid;
}

/**
Expand Down Expand Up @@ -292,7 +303,7 @@ int is_child_of(pid_t child_pid, pid_t parent_pid) {
if (kd == NULL) {
fprintf(stderr, "kvm_openfiles: %s\n", errbuf);
free(errbuf);
exit(EXIT_FAILURE);
return 0;
}
free(errbuf);
ret = _is_child_of(kd, child_pid, parent_pid);
Expand Down Expand Up @@ -376,15 +387,25 @@ int get_next_process(struct process_iterator *it, struct process *p) {
* After this call, the iterator must not be used until re-initialized.
*/
int close_process_iterator(struct process_iterator *it) {
int ret = 0;
if (it == NULL) {
return -1;
}
if (it->procs != NULL) {
free(it->procs);
it->procs = NULL;
}
if (kvm_close(it->kd) != 0) {
perror("kvm_close");
return -1;
if (it->kd != NULL) {
if (kvm_close(it->kd) != 0) {
perror("kvm_close");
ret = -1;
}
it->kd = NULL;
}
return 0;
it->i = 0;
it->count = 0;
it->filter = NULL;
return ret;
}

#endif
Expand Down
29 changes: 21 additions & 8 deletions src/process_iterator_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,25 @@ static int read_process_info(pid_t pid, struct process *p, int read_cmd) {
if (!read_cmd) {
return 0;
}
/* Read command path from /proc/[pid]/cmdline */
snprintf(exefile, sizeof(exefile), "/proc/%ld/cmdline", (long)p->pid);
if ((buffer = read_line_from_file(exefile)) == NULL) {
return -1;
/* Read command path directly from /proc/[pid]/cmdline */
{
FILE *fp;
size_t n;
snprintf(exefile, sizeof(exefile), "/proc/%ld/cmdline", (long)p->pid);
fp = fopen(exefile, "r");
if (fp == NULL) {
return -1;
}
/*
* Read raw bytes directly into p->command, bounded by buffer size.
* cmdline separates arguments with null bytes; the first null byte
* terminates the program path naturally when used as a C string.
*/
n = fread(p->command, 1, sizeof(p->command) - 1, fp);
fclose(fp);
p->command[n] = '\0';
return (n > 0) ? 0 : -1;
}
strncpy(p->command, buffer, sizeof(p->command) - 1);
p->command[sizeof(p->command) - 1] = '\0';
free(buffer);
return 0;
}

/**
Expand Down Expand Up @@ -329,6 +339,9 @@ int is_child_of(pid_t child_pid, pid_t parent_pid) {
int get_next_process(struct process_iterator *it, struct process *p) {
const struct dirent *dit = NULL;

if (it == NULL || p == NULL) {
return -1;
}
if (it->end_of_processes) {
return -1;
}
Expand Down
Loading
Loading