diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c68c757b --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +SortIncludes: false +BasedOnStyle: Microsoft +ColumnLimit: 0 +IndentWidth: 4 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..5bb71470 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,81 @@ +name: CI + +on: + push: + branches: [ master ] + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build on ${{ matrix.os }} + permissions: + contents: read + strategy: + matrix: + os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, macos-15] + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@main + with: + ref: master + fetch-depth: 0 + + - name: Compile and Test + run: | + if [[ "${{ matrix.os }}" == ubuntu-* ]]; then + export LDFLAGS="$LDFLAGS -static" + fi + make + sudo ./tests/process_iterator_test + random_file="$(mktemp $(printf 'X%.0s' $(seq 1 255)))" + cp ./tests/process_iterator_test ./tests/${random_file} + echo "./tests/${random_file}" + sudo ./tests/${random_file} + + - name: Upload Artifacts + uses: actions/upload-artifact@main + with: + name: cpulimit-${{ matrix.os }} + path: src/cpulimit + + build-FreeBSD: + name: Build on FreeBSD-${{ matrix.osver }} + permissions: + contents: read + strategy: + matrix: + osver: ['13.4', '14.1', '15.0'] + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@main + + - name: Build on FreeBSD + uses: vmactions/freebsd-vm@v1 + with: + release: ${{ matrix.osver }} + usesh: true + prepare: | + pkg install -y lang/gcc gmake sudo + run: | + gmake + sudo ./tests/process_iterator_test + random_file="$(mktemp $(printf 'X%.0s' $(seq 1 255)))" + cp ./tests/process_iterator_test ./tests/${random_file} + echo "./tests/${random_file}" + sudo ./tests/${random_file} + + - name: Upload Artifacts + uses: actions/upload-artifact@main + with: + name: cpulimit-FreeBSD-${{ matrix.osver }} + path: src/cpulimit diff --git a/Makefile b/Makefile index 7a467380..bc552e74 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ default: all .DEFAULT: - cd src && $(MAKE) $@ - cd tests && $(MAKE) $@ + $(MAKE) -C src $@ + $(MAKE) -C tests $@ diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..465c469b --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,3 @@ +*.o +*~ +cpulimit diff --git a/src/Makefile b/src/Makefile index 86fbfbd3..371f7944 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,28 +1,49 @@ -CC?=gcc -CFLAGS?=-Wall -g -D_GNU_SOURCE -TARGETS=cpulimit -LIBS=list.o process_iterator.o process_group.o - -UNAME := $(shell uname) - +# Compiler configuration +CC ?= gcc + +# Compiler flags +override CFLAGS := $(filter-out -std=% -ansi -W%, $(CFLAGS)) \ + -std=c89 \ + -Wall \ + -Wextra \ + -pedantic \ + -Wold-style-definition \ + -Wmissing-prototypes \ + -Wstrict-prototypes \ + -Werror + +# Target binary +TARGET := cpulimit + +# Detect operating system +UNAME ?= $(shell uname) + +# Platform-specific linker flags ifeq ($(UNAME), FreeBSD) -LIBS+=-lkvm + override LDFLAGS += -lkvm endif -all:: $(TARGETS) $(LIBS) +ifeq ($(UNAME), Darwin) + override LDFLAGS += -lproc +endif -cpulimit: cpulimit.c $(LIBS) - $(CC) -o cpulimit cpulimit.c $(LIBS) $(CFLAGS) +# Check for librt availability +override LDFLAGS += $(shell \ + echo "int main(void){ return 0; }" | \ + $(CC) -x c -o /dev/null -lrt - 2>/dev/null && \ + echo -lrt \ +) -process_iterator.o: process_iterator.c process_iterator.h - $(CC) -c process_iterator.c $(CFLAGS) +# Phony targets +.PHONY: all clean -list.o: list.c list.h - $(CC) -c list.c $(CFLAGS) +# Default target +all: $(TARGET) -process_group.o: process_group.c process_group.h process_iterator.o list.o - $(CC) -c process_group.c $(CFLAGS) +# Build target +$(TARGET): $(wildcard *.c *.h) + $(CC) $(CFLAGS) $(filter-out process_iterator_%.c %.h, $^) $(LDFLAGS) -o $@ +# Clean target clean: - rm -f *~ *.o $(TARGETS) - + rm -f *~ $(TARGET) diff --git a/src/cpulimit.c b/src/cpulimit.c index 50eabeac..22f63137 100644 --- a/src/cpulimit.c +++ b/src/cpulimit.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,504 +27,596 @@ * */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include #include -#include #include #include -#include -#include -#include #include -#include -#include -#include -#include +#include #include #include - -#ifdef __APPLE__ || __FREEBSD__ -#include -#endif +#include +#include #include "process_group.h" #include "list.h" +#include "util.h" -#ifdef HAVE_SYS_SYSINFO_H -#include -#endif - -#ifdef __APPLE__ -#include "memrchr.c" +#ifndef EPSILON +/* Define a very small value to avoid division by zero */ +#define EPSILON 1e-12 #endif -//some useful macro -#ifndef MIN -#define MIN(a,b) (((a)<(b))?(a):(b)) -#endif -#ifndef MAX -#define MAX(a,b) (((a)>(b))?(a):(b)) -#endif - -//control time slot in microseconds -//each slot is splitted in a working slice and a sleeping slice -//TODO: make it adaptive, based on the actual system load +/* Control time slot in microseconds */ +/* Each slot is split into a working slice and a sleeping slice */ #define TIME_SLOT 100000 -#define MAX_PRIORITY -10 - /* GLOBAL VARIABLES */ -//the "family" +/* Define a global process group (family of processes) */ struct process_group pgroup; -//pid of cpulimit + +/* PID of cpulimit */ pid_t cpulimit_pid; -//name of this program (maybe cpulimit...) -char *program_name; -//number of cpu +/* Name of this program */ +const char *program_name; + +/* Number of CPUs available in the system */ int NCPU; /* CONFIGURATION VARIABLES */ -//verbose mode +/* Verbose mode flag */ int verbose = 0; -//lazy mode (exits if there is no process) + +/* Lazy mode flag (exit if no process is found) */ int lazy = 0; -//SIGINT and SIGTERM signal handler -static void quit(int sig) -{ - //let all the processes continue if stopped - struct list_node *node = NULL; - if (pgroup.proclist != NULL) - { - for (node = pgroup.proclist->first; node != NULL; node = node->next) { - struct process *p = (struct process*)(node->data); - kill(p->pid, SIGCONT); - } - close_process_group(&pgroup); - } - //fix ^C little problem - printf("\r"); - fflush(stdout); - exit(0); -} +/* Quit flag for handling SIGINT and SIGTERM signals */ +volatile sig_atomic_t quit_flag = 0; -//return t1-t2 in microseconds (no overflow checks, so better watch out!) -static inline unsigned long timediff(const struct timeval *t1,const struct timeval *t2) +/** + * Signal handler for SIGINT and SIGTERM signals. + * Sets the quit_flag to 1 when a termination signal is received. + * + * @param sig Signal number (SIGINT or SIGTERM). + */ +static void sig_handler(int sig) { - return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_usec - t2->tv_usec); + /* Handle SIGINT and SIGTERM signals by setting quit_flag to 1 */ + switch (sig) + { + case SIGINT: + case SIGTERM: + quit_flag = 1; + break; + default: + break; + } } -static void print_usage(FILE *stream, int exit_code) +/** + * Prints the usage information for the program and exit. + * + * @param stream The file stream to write the usage information to (e.g., stdout). + * @param exit_code The exit code to return after printing usage. + */ +static void print_usage_and_exit(FILE *stream, int exit_code) { - fprintf(stream, "Usage: %s [OPTIONS...] TARGET\n", program_name); - fprintf(stream, " OPTIONS\n"); - fprintf(stream, " -l, --limit=N percentage of cpu allowed from 0 to %d (required)\n", 100*NCPU); - fprintf(stream, " -v, --verbose show control statistics\n"); - fprintf(stream, " -z, --lazy exit if there is no target process, or if it dies\n"); - fprintf(stream, " -i, --include-children limit also the children processes\n"); - fprintf(stream, " -h, --help display this help and exit\n"); - fprintf(stream, " TARGET must be exactly one of these:\n"); - fprintf(stream, " -p, --pid=N pid of the process (implies -z)\n"); - fprintf(stream, " -e, --exe=FILE name of the executable program file or path name\n"); - fprintf(stream, " COMMAND [ARGS] run this command and limit it (implies -z)\n"); - fprintf(stream, "\nReport bugs to .\n"); - exit(exit_code); + /* Print the usage message along with available options */ + fprintf(stream, "Usage: %s [OPTIONS...] TARGET\n", program_name); + fprintf(stream, " OPTIONS\n"); + fprintf(stream, " -l, --limit=N percentage of cpu allowed from 0 to %d (required)\n", 100 * NCPU); + fprintf(stream, " -v, --verbose show control statistics\n"); + fprintf(stream, " -z, --lazy exit if there is no target process, or if it dies\n"); + fprintf(stream, " -i, --include-children limit also the children processes\n"); + fprintf(stream, " -h, --help display this help and exit\n"); + fprintf(stream, " TARGET must be exactly one of these:\n"); + fprintf(stream, " -p, --pid=N pid of the process (implies -z)\n"); + fprintf(stream, " -e, --exe=FILE name of the executable program file or path name\n"); + fprintf(stream, " COMMAND [ARGS] run this command and limit it (implies -z)\n"); + fprintf(stream, "\nReport bugs to .\n"); + exit(exit_code); } -static void increase_priority() { - //find the best available nice value - int old_priority = getpriority(PRIO_PROCESS, 0); - int priority = old_priority; - while (setpriority(PRIO_PROCESS, 0, priority-1) == 0 && priority>MAX_PRIORITY) { - priority--; - } - if (priority != old_priority) { - if (verbose) printf("Priority changed to %d\n", priority); - } - else { - if (verbose) printf("Warning: Cannot change priority. Run as root or renice for best results.\n"); - } -} - -/* Get the number of CPUs */ -static int get_ncpu() { - int ncpu; -#ifdef _SC_NPROCESSORS_ONLN - ncpu = sysconf(_SC_NPROCESSORS_ONLN); -#elif defined __APPLE__ - int mib[2] = {CTL_HW, HW_NCPU}; - size_t len = sizeof(ncpu); - sysctl(mib, 2, &ncpu, &len, NULL, 0); -#elif defined _GNU_SOURCE - ncpu = get_nprocs(); -#else - ncpu = -1; -#endif - return ncpu; +/** + * Dynamically calculates the time slot based on system load. + * This allows the program to adapt to varying system conditions. + * + * @return The calculated dynamic time slot in microseconds. + */ +static double get_dynamic_time_slot(void) +{ + static double time_slot = TIME_SLOT; + static const double MIN_TIME_SLOT = TIME_SLOT, /* Minimum allowed time slot */ + MAX_TIME_SLOT = TIME_SLOT * 5; /* Maximum allowed time slot */ + double load, new_time_slot; + + /* Get the system load average */ + if (getloadavg(&load, 1) != 1) + { + return time_slot; + } + + /* Adjust the time slot based on system load and number of CPUs */ + new_time_slot = time_slot * load / NCPU / 0.3; + new_time_slot = MAX(new_time_slot, MIN_TIME_SLOT); + new_time_slot = MIN(new_time_slot, MAX_TIME_SLOT); + + /* Smoothly adjust the time slot using a moving average */ + time_slot = time_slot * 0.95 + new_time_slot * 0.05; + + return time_slot; } -int get_pid_max() +/** + * Controls the CPU usage of a process (and optionally its children). + * Limits the amount of time the process can run based on a given percentage. + * + * @param pid Process ID of the target process. + * @param limit The CPU usage limit as a percentage (0.0 to 1.0). + * @param include_children Whether to include child processes. + */ +static void limit_process(pid_t pid, double limit, int include_children) { -#ifdef __linux__ - //read /proc/sys/kernel/pid_max - static char buffer[1024]; - FILE *fd = fopen("/proc/sys/kernel/pid_max", "r"); - if (fd==NULL) return -1; - if (fgets(buffer, sizeof(buffer), fd)==NULL) { - fclose(fd); - return -1; - } - fclose(fd); - return atoi(buffer); -#elif defined __FreeBSD__ - return 99998; -#elif defined __APPLE__ - return 99998; -#endif + /* Slice of time in which the process is allowed to work */ + struct timespec twork; + /* Slice of time in which the process is stopped */ + struct timespec tsleep; + /* Generic list item for iterating over processes */ + struct list_node *node; + /* Counter to help with printing status */ + int c = 0; + + /* The ratio of the time the process is allowed to work (range 0 to 1) */ + double workingrate = -1; + + /* Increase priority of the current process to reduce overhead */ + increase_priority(); + + /* Initialize the process group (including children if needed) */ + init_process_group(&pgroup, pid, include_children); + + if (verbose) + printf("Members in the process group owned by %ld: %d\n", + (long)pgroup.target_pid, pgroup.proclist->count); + + /* Main loop to control the process until quit_flag is set */ + while (!quit_flag) + { + /* CPU usage of the controlled processes */ + /* 1 means that the processes are using 100% cpu */ + double pcpu = -1; + double twork_total_nsec, tsleep_total_nsec; + double time_slot; + + /* Update the process group, including checking for dead processes */ + update_process_group(&pgroup); + + /* Exit if no more processes are running */ + if (pgroup.proclist->count == 0) + { + if (verbose) + printf("No more processes.\n"); + break; + } + + /* Estimate CPU usage of all processes in the group */ + for (node = pgroup.proclist->first; node != NULL; node = node->next) + { + const struct process *proc = (const struct process *)(node->data); + if (proc->cpu_usage < 0) + { + continue; + } + if (pcpu < 0) + pcpu = 0; + pcpu += proc->cpu_usage; + } + + /* Adjust the work and sleep time slices based on CPU usage */ + if (pcpu < 0) + { + /* Initialize workingrate if it's the first cycle */ + pcpu = limit; + workingrate = limit; + } + else + { + /* Adjust workingrate based on CPU usage and limit */ + workingrate = workingrate * limit / MAX(pcpu, EPSILON); + } + + /* Clamp workingrate to the valid range (0, 1) */ + workingrate = MIN(workingrate, 1 - EPSILON); + workingrate = MAX(workingrate, EPSILON); + + /* Get the dynamic time slot */ + time_slot = get_dynamic_time_slot(); + + /* Calculate work and sleep times in nanoseconds */ + twork_total_nsec = time_slot * 1000 * workingrate; + nsec2timespec(twork_total_nsec, &twork); + + tsleep_total_nsec = time_slot * 1000 - twork_total_nsec; + nsec2timespec(tsleep_total_nsec, &tsleep); + + if (verbose) + { + /* Print CPU usage statistics every 10 cycles */ + if (c % 200 == 0) + printf("\n%9s%16s%16s%14s\n", + "%CPU", "work quantum", "sleep quantum", "active rate"); + + if (c % 10 == 0 && c > 0) + printf("%8.2f%%%13.0f us%13.0f us%13.2f%%\n", + pcpu * 100, twork_total_nsec / 1000, + tsleep_total_nsec / 1000, workingrate * 100); + } + + /* Resume processes in the group */ + node = pgroup.proclist->first; + while (node != NULL) + { + struct list_node *next_node = node->next; + const struct process *proc = (const struct process *)(node->data); + if (kill(proc->pid, SIGCONT) != 0) + { + /* If the process is dead, remove it from the group */ + if (verbose) + { + char errbuf[100]; + sprintf(errbuf, "kill failed to send SIGCONT to process %ld", + (long)proc->pid); + perror(errbuf); + } + delete_node(pgroup.proclist, node); + remove_process(&pgroup, proc->pid); + } + node = next_node; + } + + /* Allow processes to run during the work slice */ + sleep_timespec(&twork); + + if (tsleep.tv_nsec > 0 || tsleep.tv_sec > 0) + { + /* Stop processes during the sleep slice if needed */ + node = pgroup.proclist->first; + while (node != NULL) + { + struct list_node *next_node = node->next; + const struct process *proc = (const struct process *)(node->data); + if (kill(proc->pid, SIGSTOP) != 0) + { + /* If the process is dead, remove it from the group */ + if (verbose) + { + char errbuf[100]; + sprintf(errbuf, "kill failed to send SIGSTOP to process %ld", + (long)proc->pid); + perror(errbuf); + } + delete_node(pgroup.proclist, node); + remove_process(&pgroup, proc->pid); + } + node = next_node; + } + /* Allow the processes to sleep during the sleep slice */ + sleep_timespec(&tsleep); + } + c = (c + 1) % 200; + } + + /* If the quit_flag is set, resume all processes before exiting */ + if (quit_flag) + { + for (node = pgroup.proclist->first; node != NULL; node = node->next) + { + const struct process *p = (const struct process *)(node->data); + kill(p->pid, SIGCONT); + } + } + + /* Clean up the process group */ + close_process_group(&pgroup); } -void limit_process(pid_t pid, double limit, int include_children) +/** + * Handles the cleanup when a termination signal is received. + * Clears the current line on the console if the quit flag is set. + */ +static void quit_handler(void) { - //slice of the slot in which the process is allowed to run - struct timespec twork; - //slice of the slot in which the process is stopped - struct timespec tsleep; - //when the last twork has started - struct timeval startwork; - //when the last twork has finished - struct timeval endwork; - //initialization - memset(&twork, 0, sizeof(struct timespec)); - memset(&tsleep, 0, sizeof(struct timespec)); - memset(&startwork, 0, sizeof(struct timeval)); - memset(&endwork, 0, sizeof(struct timeval)); - //last working time in microseconds - unsigned long workingtime = 0; - //generic list item - struct list_node *node; - //counter - int c = 0; - - //get a better priority - increase_priority(); - - //build the family - init_process_group(&pgroup, pid, include_children); - - if (verbose) printf("Members in the process group owned by %d: %d\n", pgroup.target_pid, pgroup.proclist->count); - - //rate at which we are keeping active the processes (range 0-1) - //1 means that the process are using all the twork slice - double workingrate = -1; - while(1) { - update_process_group(&pgroup); - - if (pgroup.proclist->count==0) { - if (verbose) printf("No more processes.\n"); - break; - } - - //total cpu actual usage (range 0-1) - //1 means that the processes are using 100% cpu - double pcpu = -1; - - //estimate how much the controlled processes are using the cpu in the working interval - for (node = pgroup.proclist->first; node != NULL; node = node->next) { - struct process *proc = (struct process*)(node->data); - if (proc->cpu_usage < 0) { - continue; - } - if (pcpu < 0) pcpu = 0; - pcpu += proc->cpu_usage; - } - - //adjust work and sleep time slices - if (pcpu < 0) { - //it's the 1st cycle, initialize workingrate - pcpu = limit; - workingrate = limit; - twork.tv_nsec = TIME_SLOT * limit * 1000; - } - else { - //adjust workingrate - workingrate = MIN(workingrate / pcpu * limit, 1); - twork.tv_nsec = TIME_SLOT * 1000 * workingrate; - } - tsleep.tv_nsec = TIME_SLOT * 1000 - twork.tv_nsec; - - if (verbose) { - if (c%200==0) - printf("\n%%CPU\twork quantum\tsleep quantum\tactive rate\n"); - if (c%10==0 && c>0) - printf("%0.2lf%%\t%6ld us\t%6ld us\t%0.2lf%%\n", pcpu*100, twork.tv_nsec/1000, tsleep.tv_nsec/1000, workingrate*100); - } - - //resume processes - node = pgroup.proclist->first; - while (node != NULL) - { - struct list_node *next_node = node->next; - struct process *proc = (struct process*)(node->data); - if (kill(proc->pid,SIGCONT) != 0) { - //process is dead, remove it from family - if (verbose) fprintf(stderr, "SIGCONT failed. Process %d dead!\n", proc->pid); - //remove process from group - delete_node(pgroup.proclist, node); - remove_process(&pgroup, proc->pid); - } - node = next_node; - } - - //now processes are free to run (same working slice for all) - gettimeofday(&startwork, NULL); - nanosleep(&twork, NULL); - gettimeofday(&endwork, NULL); - workingtime = timediff(&endwork, &startwork); - - long delay = workingtime - twork.tv_nsec/1000; - if (c>0 && delay>10000) { - //delay is too much! signal to user? - //fprintf(stderr, "%d %ld us\n", c, delay); - } - - if (tsleep.tv_nsec>0) { - //stop processes only if tsleep>0 - node = pgroup.proclist->first; - while (node != NULL) - { - struct list_node *next_node = node->next; - struct process *proc = (struct process*)(node->data); - if (kill(proc->pid,SIGSTOP)!=0) { - //process is dead, remove it from family - if (verbose) fprintf(stderr, "SIGSTOP failed. Process %d dead!\n", proc->pid); - //remove process from group - delete_node(pgroup.proclist, node); - remove_process(&pgroup, proc->pid); - } - node = next_node; - } - //now the processes are sleeping - nanosleep(&tsleep,NULL); - } - c++; - } - close_process_group(&pgroup); + /* If quit_flag is set, clear the current line on console (fix for ^C issue) */ + if (quit_flag) + { + printf("\r"); + } } -int main(int argc, char **argv) { - //argument variables - const char *exe = NULL; - int perclimit = 0; - int exe_ok = 0; - int pid_ok = 0; - int limit_ok = 0; - pid_t pid = 0; - int include_children = 0; - - //get program name - char *p = (char*)memrchr(argv[0], (unsigned int)'/', strlen(argv[0])); - program_name = p==NULL ? argv[0] : (p+1); - //get current pid - cpulimit_pid = getpid(); - //get cpu count - NCPU = get_ncpu(); - - //parse arguments - int next_option; +int main(int argc, char *argv[]) +{ + /* Variables to store user-provided arguments */ + char *exe = NULL; + double perclimit = 0.0; + int exe_ok = 0; + int pid_ok = 0; + int limit_ok = 0; + pid_t pid = 0; + int include_children = 0; + int command_mode; + + /* For parsing command-line options */ + int next_option; int option_index = 0; - //A string listing valid short options letters - const char* short_options = "+p:e:l:vzih"; - //An array describing valid long options - const struct option long_options[] = { - { "pid", required_argument, NULL, 'p' }, - { "exe", required_argument, NULL, 'e' }, - { "limit", required_argument, NULL, 'l' }, - { "verbose", no_argument, NULL, 'v' }, - { "lazy", no_argument, NULL, 'z' }, - { "include-children", no_argument, NULL, 'i' }, - { "help", no_argument, NULL, 'h' }, - { 0, 0, 0, 0 } - }; - - do { - next_option = getopt_long(argc, argv, short_options,long_options, &option_index); - switch(next_option) { - case 'p': - pid = atoi(optarg); - pid_ok = 1; - break; - case 'e': - exe = optarg; - exe_ok = 1; - break; - case 'l': - perclimit = atoi(optarg); - limit_ok = 1; - break; - case 'v': - verbose = 1; - break; - case 'z': - lazy = 1; - break; - case 'i': - include_children = 1; - break; - case 'h': - print_usage(stdout, 1); - break; - case '?': - print_usage(stderr, 1); - break; - case -1: - break; - default: - abort(); - } - } while(next_option != -1); - - if (pid_ok && (pid <= 1 || pid >= get_pid_max())) { - fprintf(stderr,"Error: Invalid value for argument PID\n"); - print_usage(stderr, 1); - exit(1); - } - if (pid != 0) { - lazy = 1; - } - - if (!limit_ok) { - fprintf(stderr,"Error: You must specify a cpu limit percentage\n"); - print_usage(stderr, 1); - exit(1); - } - double limit = perclimit / 100.0; - if (limit<0 || limit >NCPU) { - fprintf(stderr,"Error: limit must be in the range 0-%d00\n", NCPU); - print_usage(stderr, 1); - exit(1); - } - - int command_mode = optind < argc; - if (exe_ok + pid_ok + command_mode == 0) { - fprintf(stderr,"Error: You must specify one target process, either by name, pid, or command line\n"); - print_usage(stderr, 1); - exit(1); - } - - if (exe_ok + pid_ok + command_mode > 1) { - fprintf(stderr,"Error: You must specify exactly one target process, either by name, pid, or command line\n"); - print_usage(stderr, 1); - exit(1); - } - - //all arguments are ok! - signal(SIGINT, quit); - signal(SIGTERM, quit); - - //print the number of available cpu - if (verbose) printf("%d cpu detected\n", NCPU); - - if (command_mode) { - int i; - //executable file - const char *cmd = argv[optind]; - //command line arguments - char **cmd_args = (char**)malloc((argc-optind + 1) * sizeof(char*)); - if (cmd_args==NULL) exit(2); - for (i=0; i 0) { - //parent - int status_process; - int status_limiter; - waitpid(child, &status_process, 0); - waitpid(limiter, &status_limiter, 0); - if (WIFEXITED(status_process)) { - if (verbose) printf("Process %d terminated with exit status %d\n", child, (int)WEXITSTATUS(status_process)); - exit(WEXITSTATUS(status_process)); - } - printf("Process %d terminated abnormally\n", child); - exit(status_process); - } - else { - //limiter code - if (verbose) printf("Limiting process %d\n",child); - limit_process(child, limit, include_children); - exit(0); - } - } - } - - while(1) { - //look for the target process..or wait for it - pid_t ret = 0; - if (pid_ok) { - //search by pid - ret = find_process_by_pid(pid); - if (ret == 0) { - printf("No process found\n"); - } - else if (ret < 0) { - printf("Process found but you aren't allowed to control it\n"); - } - } - else { - //search by file or path name - ret = find_process_by_name(exe); - if (ret == 0) { - printf("No process found\n"); - } - else if (ret < 0) { - printf("Process found but you aren't allowed to control it\n"); - } - else { - pid = ret; - } - } - if (ret > 0) { - if (ret == cpulimit_pid) { - printf("Target process %d is cpulimit itself! Aborting because it makes no sense\n", ret); - exit(1); - } - printf("Process %d found\n", pid); - //control - limit_process(pid, limit, include_children); - } - if (lazy) break; - sleep(2); - }; - - exit(0); + + /* Define valid short and long command-line options */ + const char *short_options = "+p:e:l:vzih"; + /* An array describing valid long options */ + const struct option long_options[] = { + {"pid", required_argument, NULL, 'p'}, + {"exe", required_argument, NULL, 'e'}, + {"limit", required_argument, NULL, 'l'}, + {"verbose", no_argument, NULL, 'v'}, + {"lazy", no_argument, NULL, 'z'}, + {"include-children", no_argument, NULL, 'i'}, + {"help", no_argument, NULL, 'h'}, + {0, 0, 0, 0}}; + + double limit; + char *endptr; + + /* Set waiting time between process searches */ + struct timespec wait_time = {2, 0}; + + /* Signal action struct for handling interrupts */ + struct sigaction sa; + + /* Register the quit handler to run at program exit */ + if (atexit(quit_handler) != 0) + { + fprintf(stderr, "Failed to register quit_handler\n"); + exit(EXIT_FAILURE); + } + + /* Extract the program name and store it in program_base_name */ + program_name = basename(argv[0]); + + /* Get the current process ID */ + cpulimit_pid = getpid(); + + /* Get the number of CPUs available */ + NCPU = get_ncpu(); + + /* Parse the command-line options */ + do + { + next_option = getopt_long(argc, argv, short_options, long_options, &option_index); + if (strchr("pel", next_option) != NULL && optarg[0] == '-') + { + fprintf(stderr, "%s: option '%c' requires an argument.\n", + argv[0], next_option); + print_usage_and_exit(stderr, EXIT_FAILURE); + } + + switch (next_option) + { + case 'p': + /* Store the PID provided by the user */ + pid = (pid_t)strtol(optarg, &endptr, 10); + pid_ok = endptr != optarg && *endptr == '\0'; + break; + case 'e': + /* Store the executable name provided by the user */ + exe = optarg; + exe_ok = 1; + break; + case 'l': + /* Store the CPU limit percentage provided by the user */ + perclimit = strtod(optarg, &endptr); + limit_ok = endptr != optarg && *endptr == '\0'; + break; + case 'v': + /* Enable verbose mode */ + verbose = 1; + break; + case 'z': + /* Enable lazy mode */ + lazy = 1; + break; + case 'i': + /* Include child processes in the limit */ + include_children = 1; + break; + case 'h': + /* Print usage information and exit */ + print_usage_and_exit(stdout, EXIT_SUCCESS); + break; + case '?': + /* Print usage information on invalid option */ + print_usage_and_exit(stderr, EXIT_FAILURE); + break; + case -1: + /* No more options to process */ + break; + default: + abort(); + } + } while (next_option != -1); + + /* Validate provided PID */ + if (pid_ok && (pid <= 1 || pid >= get_pid_max())) + { + fprintf(stderr, "Error: Invalid value for argument PID\n"); + print_usage_and_exit(stderr, EXIT_FAILURE); + } + if (pid != 0) + { + /* Implicitly enable lazy mode if a PID is provided */ + lazy = 1; + } + + /* Ensure that a CPU limit was specified */ + if (!limit_ok) + { + fprintf(stderr, "Error: You must specify a cpu limit percentage\n"); + print_usage_and_exit(stderr, EXIT_FAILURE); + } + + /* Calculate the CPU limit as a fraction */ + limit = perclimit / 100.0; + if (limit < 0 || limit > NCPU) + { + fprintf(stderr, "Error: limit must be in the range 0-%d00\n", NCPU); + print_usage_and_exit(stderr, EXIT_FAILURE); + } + + /* Determine if a command was provided */ + command_mode = optind < argc; + + /* Ensure exactly one target process (pid, executable, or command) is specified */ + if (exe_ok + pid_ok + command_mode != 1) + { + fprintf(stderr, "Error: You must specify exactly one target process by name, pid, or command line\n"); + print_usage_and_exit(stderr, EXIT_FAILURE); + } + + /* Set up signal handlers for SIGINT and SIGTERM */ + sa.sa_handler = &sig_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* Print number of CPUs if in verbose mode */ + if (verbose) + printf("%d cpu detected\n", NCPU); + + /* Handle command mode (run a command and limit its CPU usage) */ + if (command_mode) + { + pid_t child; + char *const *cmd_args = argv + optind; + + /* If verbose, print the command being executed */ + if (verbose) + { + int i; + printf("Running command: '%s", cmd_args[0]); + for (i = 1; i < argc - optind; i++) + { + printf(" %s", cmd_args[i]); + } + printf("'\n"); + } + + /* Fork a child process to run the command */ + child = fork(); + if (child < 0) + { + exit(EXIT_FAILURE); + } + else if (child == 0) + { + /* Execute the command in the child process */ + int ret = execvp(cmd_args[0], cmd_args); + perror("Error"); /* Display error if execvp fails */ + exit(ret); + } + else + { + /* Parent process forks another limiter process to control CPU usage */ + pid_t limiter; + limiter = fork(); + if (limiter < 0) + { + exit(EXIT_FAILURE); + } + else if (limiter > 0) + { + /* Wait for both child and limiter processes to complete */ + int status_process; + int status_limiter; + waitpid(child, &status_process, 0); + waitpid(limiter, &status_limiter, 0); + if (WIFEXITED(status_process)) + { + if (verbose) + printf("Process %ld terminated with exit status %d\n", + (long)child, (int)WEXITSTATUS(status_process)); + exit(WEXITSTATUS(status_process)); + } + printf("Process %ld terminated abnormally\n", (long)child); + exit(status_process); + } + else + { + /* Limiter process controls the CPU usage of the child process */ + if (verbose) + printf("Limiting process %ld\n", (long)child); + limit_process(child, limit, include_children); + exit(EXIT_SUCCESS); + } + } + } + + /* Monitor and limit the target process specified by PID or executable name */ + while (!quit_flag) + { + pid_t ret = 0; + if (pid_ok) + { + /* Search for the process by PID */ + ret = find_process_by_pid(pid); + if (ret <= 0) + { + printf("No process found or you aren't allowed to control it\n"); + } + } + else + { + /* Search for the process by executable name */ + ret = find_process_by_name(exe); + if (ret == 0) + { + printf("No process found\n"); + } + else if (ret < 0) + { + printf("Process found but you aren't allowed to control it\n"); + } + else + { + pid = ret; + } + } + + /* If a process is found, start limiting its CPU usage */ + if (ret > 0) + { + if (ret == cpulimit_pid) + { + printf("Target process %ld is cpulimit itself! Aborting because it makes no sense\n", + (long)ret); + exit(EXIT_FAILURE); + } + printf("Process %ld found\n", (long)pid); + limit_process(pid, limit, include_children); + } + + /* Break the loop if lazy mode is enabled or quit flag is set */ + if (lazy || quit_flag) + break; + + /* Wait for 2 seconds before the next process search */ + sleep_timespec(&wait_time); + } + + return 0; } diff --git a/src/list.c b/src/list.c index 2a78358a..a7f47308 100644 --- a/src/list.c +++ b/src/list.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,130 +19,169 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include #include - +#include #include "list.h" -#define EMPTYLIST NULL - -void init_list(struct list *l,int keysize) { - l->first=l->last=NULL; - l->keysize=keysize; - l->count=0; -} - -struct list_node *add_elem(struct list *l,void *elem) { - struct list_node *newnode=(struct list_node*)malloc(sizeof(struct list_node)); - newnode->data=elem; - newnode->previous=l->last; - newnode->next=NULL; - if (l->count==0) { - l->first=l->last=newnode; - } - else { - l->last->next=newnode; - l->last=newnode; - } - l->count++; - return newnode; -} - -void delete_node(struct list *l,struct list_node *node) { - if (l->count==1) { - l->first=l->last=NULL; - } - else if (node==l->first) { - node->next->previous=NULL; - l->first=node->next; - } - else if (node==l->last) { - node->previous->next=NULL; - l->last=node->previous; - } - else { - node->previous->next=node->next; - node->next->previous=node->previous; - } - l->count--; - free(node); -} - -void destroy_node(struct list *l,struct list_node *node) { - free(node->data); - node->data=NULL; - delete_node(l,node); -} - -int is_empty_list(struct list *l) { - return (l->count==0?TRUE:FALSE); -} - -int get_list_count(struct list *l) { - return l->count; -} - -void *first_elem(struct list *l) { - return l->first->data; -} - -struct list_node *first_node(struct list *l) { - return l->first; -} - -void *last_elem(struct list *l) { - return l->last->data; -} - -struct list_node *last_node(struct list *l) { - return l->last; -} - -struct list_node *xlocate_node(struct list *l,void *elem,int offset,int length) { - struct list_node *tmp; - tmp=l->first; - while(tmp!=NULL) { - if(!memcmp((char*)tmp->data+offset,elem,length==0?l->keysize:length)) return (tmp); - tmp=tmp->next; - } - return EMPTYLIST; -} - -struct list_node *locate_node(struct list *l,void *elem) { - return(xlocate_node(l,elem,0,0)); -} - -void *xlocate_elem(struct list *l,void *elem,int offset,int length) { - struct list_node *node=xlocate_node(l,elem,offset,length); - return(node==NULL?NULL:node->data); -} - -void *locate_elem(struct list *l,void *elem) { - return(xlocate_elem(l,elem,0,0)); -} - -void clear_list(struct list *l) { - while(l->first!=EMPTYLIST) { - struct list_node *tmp; - tmp=l->first; - l->first=l->first->next; - free(tmp); - tmp=NULL; - } - l->last=EMPTYLIST; - l->count=0; -} - -void destroy_list(struct list *l) { - while(l->first!=EMPTYLIST) { - struct list_node *tmp; - tmp=l->first; - l->first=l->first->next; - free(tmp->data); - tmp->data=NULL; - free(tmp); - tmp=NULL; - } - l->last=EMPTYLIST; - l->count=0; +#define safe_free(p) \ + do \ + { \ + if ((p) != NULL) \ + { \ + free((p)); \ + (p) = NULL; \ + } \ + } while (0) + +void init_list(struct list *l, int keysize) +{ + l->first = l->last = NULL; + l->keysize = keysize; + l->count = 0; +} + +struct list_node *add_elem(struct list *l, void *elem) +{ + struct list_node *newnode = (struct list_node *)malloc(sizeof(struct list_node)); + if (newnode == NULL) + { + fprintf(stderr, "Memory allocation failed for the new list node\n"); + exit(EXIT_FAILURE); + } + newnode->data = elem; + newnode->previous = l->last; + newnode->next = NULL; + if (l->count == 0) + { + l->first = l->last = newnode; + } + else + { + l->last->next = newnode; + l->last = newnode; + } + l->count++; + return newnode; +} + +void delete_node(struct list *l, struct list_node *node) +{ + if (l->count == 1) + { + l->first = l->last = NULL; + } + else if (node == l->first) + { + node->next->previous = NULL; + l->first = node->next; + } + else if (node == l->last) + { + node->previous->next = NULL; + l->last = node->previous; + } + else + { + node->previous->next = node->next; + node->next->previous = node->previous; + } + l->count--; + safe_free(node); +} + +void destroy_node(struct list *l, struct list_node *node) +{ + safe_free(node->data); + delete_node(l, node); +} + +int is_empty_list(const struct list *l) +{ + return l->count == 0; +} + +int get_list_count(const struct list *l) +{ + return l->count; +} + +void *first_elem(struct list *l) +{ + return l->first->data; +} + +struct list_node *first_node(const struct list *l) +{ + return l->first; +} + +void *last_elem(struct list *l) +{ + return l->last->data; +} + +struct list_node *last_node(const struct list *l) +{ + return l->last; +} + +struct list_node *xlocate_node(struct list *l, const void *elem, int offset, int length) +{ + struct list_node *tmp; + tmp = l->first; + while (tmp != NULL) + { + if (!memcmp((char *)tmp->data + offset, elem, (size_t)(length == 0 ? l->keysize : length))) + return (tmp); + tmp = tmp->next; + } + return NULL; +} + +struct list_node *locate_node(struct list *l, const void *elem) +{ + return (xlocate_node(l, elem, 0, 0)); +} + +void *xlocate_elem(struct list *l, const void *elem, int offset, int length) +{ + struct list_node *node = xlocate_node(l, elem, offset, length); + return (node == NULL ? NULL : node->data); +} + +void *locate_elem(struct list *l, const void *elem) +{ + return (xlocate_elem(l, elem, 0, 0)); +} + +void clear_list(struct list *l) +{ + while (l->first != NULL) + { + struct list_node *tmp; + tmp = l->first; + l->first = l->first->next; + safe_free(tmp); + } + l->last = NULL; + l->count = 0; +} + +void destroy_list(struct list *l) +{ + while (l->first != NULL) + { + struct list_node *tmp; + tmp = l->first; + l->first = l->first->next; + safe_free(tmp->data); + safe_free(tmp); + } + l->last = NULL; + l->count = 0; } diff --git a/src/list.h b/src/list.h index 0b43a2b3..85d58355 100644 --- a/src/list.h +++ b/src/list.h @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,119 +19,184 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __LIST__ +#ifndef __LIST_H +#define __LIST_H -#define __LIST__ - -#ifndef TRUE - #define TRUE 1 - #define FALSE 0 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE #endif -struct list_node { - //pointer to the content of the node +/** + * Structure representing a node in a doubly linked list. + */ +struct list_node +{ + /* Pointer to the content of the node */ void *data; - //pointer to previous node + + /* Pointer to the previous node in the list */ struct list_node *previous; - //pointer to next node + + /* Pointer to the next node in the list */ struct list_node *next; }; -struct list { - //first node +/** + * Structure representing a doubly linked list. + */ +struct list +{ + /* Pointer to the first node in the list */ struct list_node *first; - //last node + + /* Pointer to the last node in the list */ struct list_node *last; - //size of the search key in bytes + + /* Size of the search key in bytes */ int keysize; - //element count + + /* Count of elements in the list */ int count; }; -/* - * Initialize a list, with a specified key size +/** + * Initializes a list with a specified key size. + * + * @param l Pointer to the list to initialize. + * @param keysize Size of the key used for comparisons. */ -void init_list(struct list *l,int keysize); +void init_list(struct list *l, int keysize); -/* - * Add a new element at the end of the list - * return the pointer to the new node +/** + * Adds a new element to the end of the list. + * + * @param l Pointer to the list to which the element will be added. + * @param elem Pointer to the element to add. + * @return Pointer to the newly created node containing the element. */ -struct list_node *add_elem(struct list *l,void *elem); +struct list_node *add_elem(struct list *l, void *elem); -/* - * Delete a node +/** + * Deletes a specified node from the list. + * + * @param l Pointer to the list from which to delete the node. + * @param node Pointer to the node to delete. */ -void delete_node(struct list *l,struct list_node *node); +void delete_node(struct list *l, struct list_node *node); -/* - * Delete a node from the list, even the content pointed by it - * Use only when the content is a dynamically allocated pointer +/** + * Deletes a node from the list and frees its content. + * + * This function should only be used when the content is dynamically allocated. + * + * @param l Pointer to the list from which to delete the node. + * @param node Pointer to the node to delete. */ -void destroy_node(struct list *l,struct list_node *node); +void destroy_node(struct list *l, struct list_node *node); -/* - * Check whether a list is empty or not +/** + * Checks whether the list is empty. + * + * @param l Pointer to the list to check. + * @return Non-zero if the list is empty; zero otherwise. */ -int is_empty_list(struct list *l); +int is_empty_list(const struct list *l); -/* - * Return the element count of the list +/** + * Returns the count of elements in the list. + * + * @param l Pointer to the list. + * @return Number of elements in the list. */ -int get_list_count(struct list *l); +int get_list_count(const struct list *l); -/* - * Return the first element (content of the node) from the list +/** + * Returns the content of the first element in the list. + * + * @param l Pointer to the list. + * @return Pointer to the content of the first node, or NULL if the list is empty. */ void *first_elem(struct list *l); -/* - * Return the first node from the list +/** + * Returns the first node in the list. + * + * @param l Pointer to the list. + * @return Pointer to the first node, or NULL if the list is empty. */ -struct list_node *first_node(struct list *l); +struct list_node *first_node(const struct list *l); -/* - * Return the last element (content of the node) from the list +/** + * Returns the content of the last element in the list. + * + * @param l Pointer to the list. + * @return Pointer to the content of the last node, or NULL if the list is empty. */ void *last_elem(struct list *l); -/* - * Return the last node from the list +/** + * Returns the last node in the list. + * + * @param l Pointer to the list. + * @return Pointer to the last node, or NULL if the list is empty. */ -struct list_node *last_node(struct list *l); +struct list_node *last_node(const struct list *l); -/* - * Search an element of the list by content - * the comparison is done from the specified offset and for a specified length - * if offset=0, the comparison starts from the address pointed by data - * if length=0, default keysize is used for length - * if the element is found, return the node address - * else return NULL +/** + * Searches for an element in the list by its content. + * + * Comparison is performed from the specified offset and for a specified length. + * If offset=0, comparison starts from the address pointed to by data. + * If length=0, the default keysize is used. + * + * @param l Pointer to the list to search. + * @param elem Pointer to the element to locate. + * @param offset Offset from which to start the comparison. + * @param length Length of the comparison. + * @return Pointer to the node if found; NULL if not found. */ -struct list_node *xlocate_node(struct list *l,void *elem,int offset,int length); +struct list_node *xlocate_node(struct list *l, const void *elem, int offset, int length); -/* - * The same of xlocate_node(), but return the content of the node +/** + * Similar to xlocate_node(), but returns the content of the node. + * + * @param l Pointer to the list to search. + * @param elem Pointer to the element to locate. + * @param offset Offset from which to start the comparison. + * @param length Length of the comparison. + * @return Pointer to the content of the node if found; NULL if not found. */ -void *xlocate_elem(struct list *l,void *elem,int offset,int length); +void *xlocate_elem(struct list *l, const void *elem, int offset, int length); -/* - * The same of calling xlocate_node() with offset=0 and length=0 +/** + * Locates a node in the list using default parameters (offset=0, length=0). + * + * @param l Pointer to the list to search. + * @param elem Pointer to the element to locate. + * @return Pointer to the node if found; NULL if not found. */ -struct list_node *locate_node(struct list *l,void *elem); +struct list_node *locate_node(struct list *l, const void *elem); -/* - * The same of locate_node, but return the content of the node +/** + * Similar to locate_node(), but returns the content of the node. + * + * @param l Pointer to the list to search. + * @param elem Pointer to the element to locate. + * @return Pointer to the content of the node if found; NULL if not found. */ -void *locate_elem(struct list *l,void *elem); +void *locate_elem(struct list *l, const void *elem); -/* - * Delete all the elements in the list +/** + * Deletes all elements in the list. + * + * @param l Pointer to the list to clear. */ void clear_list(struct list *l); -/* - * Delete every element in the list, and free the memory pointed by all the node data +/** + * Deletes every element in the list and frees the memory of all node data. + * + * @param l Pointer to the list to destroy. */ void destroy_list(struct list *l); diff --git a/src/memrchr.c b/src/memrchr.c deleted file mode 100644 index 1f378702..00000000 --- a/src/memrchr.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2007 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -/* - * Reverse memchr() - * Find the last occurrence of 'c' in the buffer 's' of size 'n'. - */ -void * -memrchr(s, c, n) - const void *s; - int c; - size_t n; -{ - if (n != 0) { - const unsigned char *cp; - cp = (unsigned char *)s + n; - do { - if (*(--cp) == (unsigned char)c) - return((void *)cp); - } while (--n != 0); - } - return((void *)0); -} diff --git a/src/process_group.c b/src/process_group.c index 06d73a6f..7b569b06 100644 --- a/src/process_group.c +++ b/src/process_group.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,187 +19,205 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include #include #include -#include -#include #include - -#include +#include +#include +#include #include "process_iterator.h" #include "process_group.h" #include "list.h" +#include "process_table.h" +#include "util.h" -// look for a process by pid -// search_pid : pid of the wanted process -// return: pid of the found process, if successful -// negative pid, if the process does not exist or if the signal fails -int find_process_by_pid(pid_t pid) +/* look for a process by pid +search_pid : pid of the wanted process +return: pid of the found process, if successful + negative pid, if the process does not exist or if the signal fails */ +pid_t find_process_by_pid(pid_t pid) { - return (kill(pid,0)==0) ? pid : -pid; + return (kill(pid, 0) == 0) ? pid : -pid; } -// look for a process with a given name -// process: the name of the wanted process. it can be an absolute path name to the executable file -// or just the file name -// return: pid of the found process, if it is found -// 0, if it's not found -// negative pid, if it is found but it's not possible to control it -int find_process_by_name(const char *process_name) +/* look for a process with a given name +process: the name of the wanted process. it can be an absolute path name to the executable file + or just the file name +return: pid of the found process, if it is found + 0, if it's not found + negative pid, if it is found but it's not possible to control it */ +pid_t find_process_by_name(char *process_name) { - //pid of the target process - pid_t pid = -1; - - //process iterator - struct process_iterator it; - struct process proc; - struct process_filter filter; - filter.pid = 0; - filter.include_children = 0; - init_process_iterator(&it, &filter); - while (get_next_process(&it, &proc) != -1) - { - //process found - if (strncmp(basename(proc.command), process_name, strlen(process_name))==0 && kill(pid,SIGCONT)==0) { - //process is ok! - pid = proc.pid; - break; - } - } - if (close_process_iterator(&it) != 0) exit(1); - if (pid >= 0) { - //ok, the process was found - return pid; - } - else { - //process not found - return 0; - } + /* pid of the target process */ + pid_t pid = -1; + + /* process iterator */ + struct process_iterator it; + struct process *proc; + struct process_filter filter; + const char *process_basename = basename(process_name); + proc = (struct process *)malloc(sizeof(struct process)); + if (proc == NULL) + { + fprintf(stderr, "Memory allocation failed for the process\n"); + exit(EXIT_FAILURE); + } + + filter.pid = 0; + filter.include_children = 0; + init_process_iterator(&it, &filter); + while (get_next_process(&it, proc) != -1) + { + /* process found */ + const char *cmd_basename = basename(proc->command); + if (strncmp(cmd_basename, process_basename, sizeof(proc->command)) == 0) + { + if (pid < 0 || is_child_of(pid, proc->pid)) + { + pid = proc->pid; + } + } + } + free(proc); + if (close_process_iterator(&it) != 0) + exit(EXIT_FAILURE); + + return (pid > 0) ? find_process_by_pid(pid) : 0; } -int init_process_group(struct process_group *pgroup, int target_pid, int include_children) +int init_process_group(struct process_group *pgroup, pid_t target_pid, int include_children) { - //hashtable initialization - memset(&pgroup->proctable, 0, sizeof(pgroup->proctable)); - pgroup->target_pid = target_pid; - pgroup->include_children = include_children; - pgroup->proclist = (struct list*)malloc(sizeof(struct list)); - init_list(pgroup->proclist, 4); - memset(&pgroup->last_update, 0, sizeof(pgroup->last_update)); - update_process_group(pgroup); - return 0; + /* hashtable initialization */ + pgroup->proctable = (struct process_table *)malloc(sizeof(struct process_table)); + if (pgroup->proctable == NULL) + { + fprintf(stderr, "Memory allocation failed for the process table\n"); + exit(EXIT_FAILURE); + } + process_table_init(pgroup->proctable, 2048); + pgroup->target_pid = target_pid; + pgroup->include_children = include_children; + pgroup->proclist = (struct list *)malloc(sizeof(struct list)); + if (pgroup->proclist == NULL) + { + fprintf(stderr, "Memory allocation failed for the process list\n"); + exit(EXIT_FAILURE); + } + init_list(pgroup->proclist, sizeof(pid_t)); + if (get_time(&pgroup->last_update)) + { + exit(EXIT_FAILURE); + } + update_process_group(pgroup); + return 0; } int close_process_group(struct process_group *pgroup) { - int i; - int size = sizeof(pgroup->proctable) / sizeof(struct process*); - for (i=0; iproctable[i] != NULL) { - //free() history for each process - destroy_list(pgroup->proctable[i]); - free(pgroup->proctable[i]); - pgroup->proctable[i] = NULL; - } - } - clear_list(pgroup->proclist); - free(pgroup->proclist); - pgroup->proclist = NULL; - return 0; -} + if (pgroup->proclist != NULL) + { + clear_list(pgroup->proclist); + free(pgroup->proclist); + pgroup->proclist = NULL; + } -void remove_terminated_processes(struct process_group *pgroup) -{ - //TODO + if (pgroup->proctable != NULL) + { + process_table_destroy(pgroup->proctable); + free(pgroup->proctable); + pgroup->proctable = NULL; + } + + return 0; } -//return t1-t2 in microseconds (no overflow checks, so better watch out!) -static inline unsigned long timediff(const struct timeval *t1,const struct timeval *t2) +static struct process *process_dup(const struct process *proc) { - return (t1->tv_sec - t2->tv_sec) * 1000000 + (t1->tv_usec - t2->tv_usec); + struct process *p = (struct process *)malloc(sizeof(struct process)); + if (p == NULL) + { + fprintf(stderr, "Memory allocation failed for duplicated process\n"); + exit(EXIT_FAILURE); + } + return (struct process *)memcpy(p, proc, sizeof(struct process)); } -//parameter in range 0-1 -#define ALFA 0.08 +/* parameter in range 0-1 */ +#define ALPHA 0.08 #define MIN_DT 20 void update_process_group(struct process_group *pgroup) { - struct process_iterator it; - struct process tmp_process; - struct process_filter filter; - struct timeval now; - gettimeofday(&now, NULL); - //time elapsed from previous sample (in ms) - long dt = timediff(&now, &pgroup->last_update) / 1000; - filter.pid = pgroup->target_pid; - filter.include_children = pgroup->include_children; - init_process_iterator(&it, &filter); - clear_list(pgroup->proclist); - init_list(pgroup->proclist, 4); - - while (get_next_process(&it, &tmp_process) != -1) - { -// struct timeval t; -// gettimeofday(&t, NULL); -// printf("T=%ld.%ld PID=%d PPID=%d START=%d CPUTIME=%d\n", t.tv_sec, t.tv_usec, tmp_process.pid, tmp_process.ppid, tmp_process.starttime, tmp_process.cputime); - int hashkey = pid_hashfn(tmp_process.pid); - if (pgroup->proctable[hashkey] == NULL) - { - //empty bucket - pgroup->proctable[hashkey] = malloc(sizeof(struct list)); - struct process *new_process = malloc(sizeof(struct process)); - tmp_process.cpu_usage = -1; - memcpy(new_process, &tmp_process, sizeof(struct process)); - init_list(pgroup->proctable[hashkey], 4); - add_elem(pgroup->proctable[hashkey], new_process); - add_elem(pgroup->proclist, new_process); - } - else - { - //existing bucket - struct process *p = (struct process*)locate_elem(pgroup->proctable[hashkey], &tmp_process); - if (p == NULL) - { - //process is new. add it - struct process *new_process = malloc(sizeof(struct process)); - tmp_process.cpu_usage = -1; - memcpy(new_process, &tmp_process, sizeof(struct process)); - add_elem(pgroup->proctable[hashkey], new_process); - add_elem(pgroup->proclist, new_process); - } - else - { - assert(tmp_process.pid == p->pid); - assert(tmp_process.starttime == p->starttime); - add_elem(pgroup->proclist, p); - if (dt < MIN_DT) continue; - //process exists. update CPU usage - double sample = 1.0 * (tmp_process.cputime - p->cputime) / dt; - if (p->cpu_usage == -1) { - //initialization - p->cpu_usage = sample; - } - else { - //usage adjustment - p->cpu_usage = (1.0-ALFA) * p->cpu_usage + ALFA * sample; - } - p->cputime = tmp_process.cputime; - } - } - } - close_process_iterator(&it); - if (dt < MIN_DT) return; - pgroup->last_update = now; + struct process_iterator it; + struct process *tmp_process, *p; + struct process_filter filter; + struct timespec now; + double dt; + if (get_time(&now)) + { + exit(EXIT_FAILURE); + } + tmp_process = (struct process *)malloc(sizeof(struct process)); + if (tmp_process == NULL) + { + fprintf(stderr, "Memory allocation failed for tmp_process\n"); + exit(EXIT_FAILURE); + } + /* time elapsed from previous sample (in ms) */ + dt = timediff_in_ms(&now, &pgroup->last_update); + filter.pid = pgroup->target_pid; + filter.include_children = pgroup->include_children; + init_process_iterator(&it, &filter); + clear_list(pgroup->proclist); + init_list(pgroup->proclist, sizeof(pid_t)); + + while (get_next_process(&it, tmp_process) != -1) + { + p = process_table_find(pgroup->proctable, tmp_process); + if (p == NULL) + { + /* process is new. add it */ + tmp_process->cpu_usage = -1; + p = process_dup(tmp_process); + process_table_add(pgroup->proctable, p); + add_elem(pgroup->proclist, p); + } + else + { + double sample; + add_elem(pgroup->proclist, p); + if (dt < MIN_DT) + continue; + /* process exists. update CPU usage */ + sample = (tmp_process->cputime - p->cputime) / dt; + sample = MIN(sample, 1.0); + if (p->cpu_usage < 0) + { + /* initialization */ + p->cpu_usage = sample; + } + else + { + /* usage adjustment */ + p->cpu_usage = (1.0 - ALPHA) * p->cpu_usage + ALPHA * sample; + } + p->cputime = tmp_process->cputime; + } + } + free(tmp_process); + close_process_iterator(&it); + if (dt < MIN_DT) + return; + pgroup->last_update = now; } -int remove_process(struct process_group *pgroup, int pid) +int remove_process(struct process_group *pgroup, pid_t pid) { - int hashkey = pid_hashfn(pid); - if (pgroup->proctable[hashkey] == NULL) return 1; //nothing to delete - struct list_node *node = (struct list_node*)locate_node(pgroup->proctable[hashkey], &pid); - if (node == NULL) return 2; - delete_node(pgroup->proctable[hashkey], node); - return 0; + return process_table_del_pid(pgroup->proctable, pid); } diff --git a/src/process_group.h b/src/process_group.h index 5a5b5815..05822e1b 100644 --- a/src/process_group.h +++ b/src/process_group.h @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,36 +20,90 @@ */ #ifndef __PROCESS_GROUP_H - #define __PROCESS_GROUP_H -#include "process_iterator.h" +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif -#include "list.h" +#include -#define PIDHASH_SZ 1024 -#define pid_hashfn(x) ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1)) +#include "process_iterator.h" +#include "list.h" +/** + * Structure representing a group of processes for tracking. + */ struct process_group { - //hashtable with all the processes (array of struct list of struct process) - struct list *proctable[PIDHASH_SZ]; - struct list *proclist; - pid_t target_pid; - int include_children; - struct timeval last_update; + /* Pointer to the process table for storing process information */ + struct process_table *proctable; + + /* Pointer to the list of processes in this group */ + struct list *proclist; + + /* PID of the target process to monitor */ + pid_t target_pid; + + /* Flag indicating whether to include child processes (1 for yes, 0 for no) */ + int include_children; + + /* Timestamp of the last update for this process group */ + struct timespec last_update; }; -int init_process_group(struct process_group *pgroup, int target_pid, int include_children); +/** + * Initialize a process group for tracking processes. + * + * @param pgroup Pointer to the process group structure to initialize. + * @param target_pid PID of the target process to track. + * @param include_children Flag indicating whether to include child processes. + * @return 0 on success, exits with -1 on memory allocation failure. + */ +int init_process_group(struct process_group *pgroup, pid_t target_pid, int include_children); +/** + * Update the process group with the latest process information. + * + * @param pgroup Pointer to the process group to update. + */ void update_process_group(struct process_group *pgroup); +/** + * Close a process group and free associated resources. + * + * @param pgroup Pointer to the process group to close. + * @return 0 on success. + */ int close_process_group(struct process_group *pgroup); -int find_process_by_pid(pid_t pid); +/** + * Look for a process by its PID. + * + * @param pid The PID of the desired process. + * @return PID of the found process if successful, + * Negative PID if the process does not exist or if the signal fails. + */ +pid_t find_process_by_pid(pid_t pid); -int find_process_by_name(const char *process_name); +/** + * Look for a process with a given name. + * + * @param process_name The name of the desired process. It can be an absolute path + * to the executable file or just the file name. + * @return PID of the found process if it is found, + * 0 if the process is not found, + * Negative PID if the process is found but cannot be controlled. + */ +pid_t find_process_by_name(char *process_name); -int remove_process(struct process_group *pgroup, int pid); +/** + * Remove a process from the process group by its PID. + * + * @param pgroup Pointer to the process group from which to remove the process. + * @param pid The PID of the process to remove. + * @return 0 if removal is successful, or 1 if process is not found. + */ +int remove_process(struct process_group *pgroup, pid_t pid); #endif diff --git a/src/process_iterator.c b/src/process_iterator.c index 8b4019d2..73147540 100644 --- a/src/process_iterator.c +++ b/src/process_iterator.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,31 +19,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include -#ifndef __APPLE__ -#include -#endif -#include -#include "process_iterator.h" +/* See this link to port to other systems: http://www.steve.org.uk/Reference/Unix/faq_8.html#SEC85 */ -//See this link to port to other systems: http://www.steve.org.uk/Reference/Unix/faq_8.html#SEC85 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif -#ifdef __linux__ +#if defined(__linux__) #include "process_iterator_linux.c" -#elif defined __FreeBSD__ +#elif defined(__FreeBSD__) #include "process_iterator_freebsd.c" -#elif defined __APPLE__ +#elif defined(__APPLE__) #include "process_iterator_apple.c" #else -#error Platform not supported +#error "Platform not supported" #endif diff --git a/src/process_iterator.h b/src/process_iterator.h index 70520b68..cee8dfe3 100644 --- a/src/process_iterator.h +++ b/src/process_iterator.h @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,78 +20,132 @@ */ #ifndef __PROCESS_ITERATOR_H - #define __PROCESS_ITERATOR_H -#include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include #include +#ifdef __linux__ #include - -//USER_HZ detection, from openssl code -#ifndef HZ -# if defined(_SC_CLK_TCK) \ - && (!defined(OPENSSL_SYS_VMS) || __CTRL_VER >= 70000000) -# define HZ ((double)sysconf(_SC_CLK_TCK)) -# else -# ifndef CLK_TCK -# ifndef _BSD_CLK_TCK_ /* FreeBSD hack */ -# define HZ 100.0 -# else /* _BSD_CLK_TCK_ */ -# define HZ ((double)_BSD_CLK_TCK_) -# endif -# else /* CLK_TCK */ -# define HZ ((double)CLK_TCK) -# endif -# endif #endif - #ifdef __FreeBSD__ #include #endif -// process descriptor -struct process { - //pid of the process - pid_t pid; - //ppid of the process - pid_t ppid; - //start time (unix timestamp) - int starttime; - //cputime used by the process (in milliseconds) - int cputime; - //actual cpu usage estimation (value in range 0-1) - double cpu_usage; - //absolute path of the executable file - char command[PATH_MAX+1]; +/** + * Structure representing a process descriptor. + */ +struct process +{ + /* Process ID of the process */ + pid_t pid; + + /* Parent Process ID of the process */ + pid_t ppid; + + /* CPU time used by the process (in milliseconds) */ + double cputime; + + /* Actual CPU usage estimation (value in range 0-1) */ + double cpu_usage; + + /* Absolute path of the executable file */ + char command[PATH_MAX]; }; -struct process_filter { - int pid; - int include_children; - char program_name[PATH_MAX+1]; +/** + * Structure representing a filter for processes. + */ +struct process_filter +{ + /* Process ID to filter */ + pid_t pid; + + /* Flag indicating whether to include child processes (1 for yes, 0 for no) */ + int include_children; }; -struct process_iterator { -#ifdef __linux__ - DIR *dip; - int boot_time; -#elif defined __FreeBSD__ - kvm_t *kd; - struct kinfo_proc *procs; - int count; - int i; -#elif defined __APPLE__ - int i; - int count; - int *pidlist; +/** + * Structure representing an iterator for processes. + * This structure provides a way to iterate over processes + * in different operating systems with their specific implementations. + */ +struct process_iterator +{ +#if defined(__linux__) + /* Directory stream for accessing the /proc filesystem on Linux */ + DIR *dip; +#elif defined(__FreeBSD__) + /* Kernel virtual memory descriptor for accessing process information on FreeBSD */ + kvm_t *kd; + + /* Array of process information structures */ + struct kinfo_proc *procs; + + /* Total number of processes retrieved */ + int count; + + /* Current index in the process array */ + int i; +#elif defined(__APPLE__) + /* Current index in the process list */ + int i; + + /* Total number of processes retrieved */ + int count; + + /* List of process IDs */ + pid_t *pidlist; #endif - struct process_filter *filter; + + /* Pointer to a process filter to apply during iteration */ + struct process_filter *filter; }; -int init_process_iterator(struct process_iterator *i, struct process_filter *filter); +/** + * Initializes a process iterator. + * + * @param it Pointer to the process_iterator structure. + * @param filter Pointer to the process_filter structure. + * @return 0 on success, -1 on failure. + */ +int init_process_iterator(struct process_iterator *it, struct process_filter *filter); + +/** + * Retrieves the next process information in the process iterator. + * + * @param it Pointer to the process_iterator structure. + * @param p Pointer to the process structure to store process information. + * @return 0 on success, -1 if no more processes are available. + */ +int get_next_process(struct process_iterator *it, struct process *p); + +/** + * Closes the process iterator. + * + * @param it Pointer to the process_iterator structure. + * @return 0 on success, -1 on failure. + */ +int close_process_iterator(struct process_iterator *it); -int get_next_process(struct process_iterator *i, struct process *p); +/** + * Determines if a process is a child of another process. + * + * @param child_pid The child process ID. + * @param parent_pid The parent process ID. + * @return 1 if the child process is a child of the parent process, 0 otherwise. + */ +int is_child_of(pid_t child_pid, pid_t parent_pid); -int close_process_iterator(struct process_iterator *i); +/** + * Retrieves the parent process ID (PPID) of a given PID. + * + * @param pid The given PID. + * @return The parent process ID. + */ +pid_t getppid_of(pid_t pid); #endif diff --git a/src/process_iterator_apple.c b/src/process_iterator_apple.c index b878ed8c..1f3f013e 100644 --- a/src/process_iterator_apple.c +++ b/src/process_iterator_apple.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,132 +17,200 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * + * * Author: Simon Sigurdhsson * */ +#ifdef __APPLE__ + +#ifndef __PROCESS_ITERATOR_APPLE_C +#define __PROCESS_ITERATOR_APPLE_C + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include -#include #include +#include +#include +#include +#include +#include +#include "process_iterator.h" + +int init_process_iterator(struct process_iterator *it, struct process_filter *filter) +{ + size_t len; + struct kinfo_proc *procs; + int i; + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + + it->i = 0; + it->pidlist = NULL; + it->filter = filter; + + /* Get the size of all process information */ + if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) + { + perror("Failed to get process information buffer size"); + exit(EXIT_FAILURE); /* Exit on error */ + } -int unique_nonzero_ints(int* arr_in, int len_in, int* arr_out) { - int* source = arr_in; - if (arr_out == NULL) return -1; - if (arr_in == arr_out) { - source = malloc(sizeof(int)*len_in); - memcpy(source, arr_in, sizeof(int)*len_in); - memset(arr_out, -1, sizeof(int)*len_in); - } - int len_out = 0; - int i, j; - for (i=0; icount = (int)(len / sizeof(struct kinfo_proc)); + it->pidlist = (pid_t *)malloc((size_t)it->count * sizeof(pid_t)); /* Allocate PID array */ + if (it->pidlist == NULL) + { + free(procs); + fprintf(stderr, "Memory allocation failed for PID list\n"); + exit(EXIT_FAILURE); /* Exit on error */ + } + + /* Fill the PID array */ + for (i = 0; i < it->count; i++) + { + it->pidlist[i] = procs[i].kp_proc.p_pid; + } + + free(procs); + return 0; /* Success */ +} + +static int pti2proc(struct proc_taskallinfo *ti, struct process *process) +{ + process->pid = (pid_t)ti->pbsd.pbi_pid; + process->ppid = (pid_t)ti->pbsd.pbi_ppid; + process->cputime = ti->ptinfo.pti_total_user / 1e6 + ti->ptinfo.pti_total_system / 1e6; + if (proc_pidpath((int)ti->pbsd.pbi_pid, process->command, sizeof(process->command)) <= 0) + return -1; + return 0; } -int init_process_iterator(struct process_iterator *it, struct process_filter *filter) { - it->i = 0; - /* Find out how much to allocate for it->pidlist */ - if ((it->count = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0)) <= 0) { - fprintf(stderr, "proc_listpids: %s\n", strerror(errno)); - return -1; - } - /* Allocate and populate it->pidlist */ - if ((it->pidlist = (int *)malloc((it->count)*sizeof(int))) == NULL) { - fprintf(stderr, "malloc: %s\n", strerror(errno)); - } - if ((it->count = proc_listpids(PROC_ALL_PIDS, 0, it->pidlist, it->count)) <= 0) { - fprintf(stderr, "proc_listpids: %s\n", strerror(errno)); - return -1; - } - it->count = unique_nonzero_ints(it->pidlist, it->count, it->pidlist); - it->filter = filter; - return 0; +static int get_process_pti(pid_t pid, struct proc_taskallinfo *ti) +{ + int bytes = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, ti, sizeof(*ti)); + if (bytes <= 0) + { + if (errno != EPERM && errno != ESRCH) + { + perror("proc_pidinfo"); + } + return -1; + } + else if (bytes < (int)sizeof(*ti)) + { + fprintf(stderr, "proc_pidinfo: too few bytes; expected %lu, got %d\n", + (unsigned long)sizeof(*ti), bytes); + return -1; + } + return 0; } -static int pti2proc(struct proc_taskallinfo *ti, struct process *process) { - int bytes; - process->pid = ti->pbsd.pbi_pid; - process->ppid = ti->pbsd.pbi_ppid; - process->starttime = ti->pbsd.pbi_start_tvsec; - process->cputime = (ti->ptinfo.pti_total_user + ti->ptinfo.pti_total_system) / 1000000; - bytes = strlen(ti->pbsd.pbi_comm); - memcpy(process->command, ti->pbsd.pbi_comm, (bytes < PATH_MAX ? bytes : PATH_MAX) + 1); - return 0; +pid_t getppid_of(pid_t pid) +{ + struct proc_taskallinfo ti; + if (get_process_pti(pid, &ti) == 0) + { + return (pid_t)ti.pbsd.pbi_ppid; + } + return (pid_t)(-1); } -static int get_process_pti(pid_t pid, struct proc_taskallinfo *ti) { - int bytes; - bytes = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, ti, sizeof(*ti)); - if (bytes <= 0) { - if (!(errno & (EPERM | ESRCH))) { - fprintf(stderr, "proc_pidinfo: %s\n", strerror(errno)); - } - return -1; - } else if (bytes < sizeof(ti)) { - fprintf(stderr, "proc_pidinfo: too few bytes; expected %ld, got %d\n", sizeof(ti), bytes); - return -1; - } - return 0; +int is_child_of(pid_t child_pid, pid_t parent_pid) +{ + if (child_pid <= 1 || parent_pid <= 0 || child_pid == parent_pid) + return 0; + if (parent_pid == 1) + return 1; + while (child_pid > 1 && child_pid != parent_pid) + { + child_pid = getppid_of(child_pid); + } + return child_pid == parent_pid; } -int get_next_process(struct process_iterator *it, struct process *p) { - if (it->i == it->count) return -1; - if (it->filter->pid != 0 && !it->filter->include_children) { - struct proc_taskallinfo ti; - if (get_process_pti(it->filter->pid, &ti) != 0) { - it->i = it->count = 0; - return -1; - } - it->i = it->count = 1; - return pti2proc(&ti, p); - } - while (it->i < it->count) { - struct proc_taskallinfo ti; - if (get_process_pti(it->pidlist[it->i], &ti) != 0) { - it->i++; - continue; - } - if (ti.pbsd.pbi_flags & PROC_FLAG_SYSTEM) { - it->i++; - continue; - } - if (it->filter->pid != 0 && it->filter->include_children) { - pti2proc(&ti, p); - it->i++; - if (p->pid != it->pidlist[it->i - 1]) // I don't know why this can happen - continue; - if (p->pid != it->filter->pid && p->ppid != it->filter->pid) - continue; - return 0; - } - else if (it->filter->pid == 0) - { - pti2proc(&ti, p); - it->i++; - return 0; - } - } - return -1; +int get_next_process(struct process_iterator *it, struct process *p) +{ + if (it->i >= it->count) + return -1; + if (it->filter->pid != 0 && !it->filter->include_children) + { + struct proc_taskallinfo ti; + if (get_process_pti(it->filter->pid, &ti) == 0 && pti2proc(&ti, p) == 0) + { + it->i = it->count = 1; + return 0; + } + it->i = it->count = 0; + return -1; + } + while (it->i < it->count) + { + struct proc_taskallinfo ti; + if (get_process_pti(it->pidlist[it->i], &ti) != 0) + { + it->i++; + continue; + } + if (ti.pbsd.pbi_flags & PROC_FLAG_SYSTEM) + { + it->i++; + continue; + } + if (it->filter->pid != 0 && it->filter->include_children) + { + it->i++; + if (pti2proc(&ti, p) != 0) + continue; + if (p->pid != it->filter->pid && !is_child_of(p->pid, it->filter->pid)) + continue; + return 0; + } + else if (it->filter->pid == 0) + { + it->i++; + if (pti2proc(&ti, p) != 0) + continue; + return 0; + } + } + return -1; } -int close_process_iterator(struct process_iterator *it) { - free(it->pidlist); - it->pidlist = NULL; - it->filter = NULL; - it->count = 0; - it->i = 0; - return 0; +int close_process_iterator(struct process_iterator *it) +{ + free(it->pidlist); + it->pidlist = NULL; + it->filter = NULL; + it->count = 0; + it->i = 0; + return 0; } + +#endif +#endif diff --git a/src/process_iterator_freebsd.c b/src/process_iterator_freebsd.c index a6381123..a89b67d1 100644 --- a/src/process_iterator_freebsd.c +++ b/src/process_iterator_freebsd.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,101 +19,213 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include +#ifdef __FreeBSD__ + +#ifndef __PROCESS_ITERATOR_FREEBSD_C +#define __PROCESS_ITERATOR_FREEBSD_C + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#if defined(__STRICT_ANSI__) || !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +#define inline +#endif + #include +#include +#include +#include +#include #include +#include +#include +#include + +#include "process_iterator.h" + +int init_process_iterator(struct process_iterator *it, struct process_filter *filter) +{ + const struct kinfo_proc *procs; + char *errbuf = (char *)malloc(sizeof(char) * _POSIX2_LINE_MAX); + if (errbuf == NULL) + { + fprintf(stderr, "Memory allocation failed for the error buffer\n"); + exit(EXIT_FAILURE); + } + it->i = 0; + it->procs = NULL; + /* Open the kvm interface, get a descriptor */ + if ((it->kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf)) == NULL) + { + fprintf(stderr, "kvm_openfiles: %s\n", errbuf); + free(errbuf); + return -1; + } + free(errbuf); + /* Get the list of processes. */ + if ((procs = kvm_getprocs(it->kd, KERN_PROC_PROC, 0, &it->count)) == NULL) + { + kvm_close(it->kd); + return -1; + } + it->procs = (struct kinfo_proc *)malloc(sizeof(struct kinfo_proc) * (size_t)it->count); + if (it->procs == NULL) + { + fprintf(stderr, "Memory allocation failed for the process list\n"); + exit(EXIT_FAILURE); + } -int init_process_iterator(struct process_iterator *it, struct process_filter *filter) { - char errbuf[_POSIX2_LINE_MAX]; - it->i = 0; - /* Open the kvm interface, get a descriptor */ - if ((it->kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf)) == NULL) { - fprintf(stderr, "kvm_open: %s\n", errbuf); - return -1; - } - /* Get the list of processes. */ - if ((it->procs = kvm_getprocs(it->kd, KERN_PROC_PROC, 0, &it->count)) == NULL) { - kvm_close(it->kd); -// fprintf(stderr, "kvm_getprocs: %s\n", kvm_geterr(it->kd)); - return -1; - } - it->filter = filter; - return 0; + memcpy(it->procs, procs, sizeof(struct kinfo_proc) * (size_t)it->count); + it->filter = filter; + return 0; } static int kproc2proc(kvm_t *kd, struct kinfo_proc *kproc, struct process *proc) { - proc->pid = kproc->ki_pid; - proc->ppid = kproc->ki_ppid; - proc->cputime = kproc->ki_runtime / 1000; - proc->starttime = kproc->ki_start.tv_sec; - char **args = kvm_getargv(kd, kproc, sizeof(proc->command)); - if (args == NULL) return -1; - memcpy(proc->command, args[0], strlen(args[0]) + 1); - return 0; + char **args; + size_t len_max; + proc->pid = kproc->ki_pid; + proc->ppid = kproc->ki_ppid; + proc->cputime = (double)kproc->ki_runtime / 1000.0; + len_max = sizeof(proc->command) - 1; + if ((args = kvm_getargv(kd, kproc, (int)len_max)) == NULL) + return -1; + strncpy(proc->command, args[0], len_max); + proc->command[len_max] = '\0'; + return 0; } static int get_single_process(kvm_t *kd, pid_t pid, struct process *process) { - int count; - struct kinfo_proc *kproc = kvm_getprocs(kd, KERN_PROC_PID, pid, &count); - if (count == 0 || kproc == NULL) - { -// fprintf(stderr, "kvm_getprocs: %s\n", kvm_geterr(kd)); - return -1; - } - kproc2proc(kd, kproc, process); - return 0; + int count; + struct kinfo_proc *kproc = kvm_getprocs(kd, KERN_PROC_PID, pid, &count); + if (count == 0 || kproc == NULL || kproc2proc(kd, kproc, process) != 0) + return -1; + return 0; } -int get_next_process(struct process_iterator *it, struct process *p) { - if (it->i == it->count) - { - return -1; - } - if (it->filter->pid != 0 && !it->filter->include_children) - { - if (get_single_process(it->kd, it->filter->pid, p) != 0) - { - it->i = it->count = 0; - return -1; - } - it->i = it->count = 1; - return 0; - } - while (it->i < it->count) - { - struct kinfo_proc *kproc = &(it->procs[it->i]); - if (kproc->ki_flag & P_SYSTEM) - { - // skip system processes - it->i++; - continue; - } - if (it->filter->pid != 0 && it->filter->include_children) - { - kproc2proc(it->kd, kproc, p); - it->i++; - if (p->pid != it->filter->pid && p->ppid != it->filter->pid) - continue; - return 0; - } - else if (it->filter->pid == 0) - { - kproc2proc(it->kd, kproc, p); - it->i++; - return 0; - } - } - 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; } -int close_process_iterator(struct process_iterator *it) { - if (kvm_close(it->kd) == -1) { - fprintf(stderr, "kvm_getprocs: %s\n", kvm_geterr(it->kd)); - return -1; - } - return 0; +pid_t getppid_of(pid_t pid) +{ + pid_t ppid; + kvm_t *kd; + char *errbuf = (char *)malloc(sizeof(char) * _POSIX2_LINE_MAX); + if (errbuf == NULL) + { + fprintf(stderr, "Memory allocation failed for the error buffer\n"); + exit(EXIT_FAILURE); + } + kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf); + if (kd == NULL) + { + fprintf(stderr, "kvm_openfiles: %s\n", errbuf); + free(errbuf); + return (pid_t)(-1); + } + free(errbuf); + ppid = _getppid_of(kd, pid); + kvm_close(kd); + return ppid; +} + +static int _is_child_of(kvm_t *kd, pid_t child_pid, pid_t parent_pid) +{ + if (child_pid <= 0 || parent_pid <= 0 || child_pid == parent_pid) + return 0; + while (child_pid > 1 && child_pid != parent_pid) + { + child_pid = _getppid_of(kd, child_pid); + } + return child_pid == parent_pid; +} + +int is_child_of(pid_t child_pid, pid_t parent_pid) +{ + int ret; + kvm_t *kd; + char *errbuf = (char *)malloc(sizeof(char) * _POSIX2_LINE_MAX); + if (errbuf == NULL) + { + fprintf(stderr, "Memory allocation failed for the error buffer\n"); + exit(EXIT_FAILURE); + } + kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf); + if (kd == NULL) + { + fprintf(stderr, "kvm_openfiles: %s\n", errbuf); + free(errbuf); + return (pid_t)(-1); + } + free(errbuf); + ret = _is_child_of(kd, child_pid, parent_pid); + kvm_close(kd); + return ret; +} + +int get_next_process(struct process_iterator *it, struct process *p) +{ + if (it->i >= it->count) + { + return -1; + } + if (it->filter->pid != 0 && !it->filter->include_children) + { + if (get_single_process(it->kd, it->filter->pid, p) != 0) + { + it->i = it->count = 0; + return -1; + } + it->i = it->count = 1; + return 0; + } + while (it->i < it->count) + { + struct kinfo_proc *kproc = it->procs + it->i; + if (kproc->ki_flag & P_SYSTEM) + { + it->i++; + /* skip system processes */ + continue; + } + if (it->filter->pid != 0 && it->filter->include_children) + { + it->i++; + if (kproc2proc(it->kd, kproc, p) != 0) + continue; + if (p->pid != it->filter->pid && + !_is_child_of(it->kd, p->pid, it->filter->pid)) + continue; + return 0; + } + else if (it->filter->pid == 0) + { + it->i++; + if (kproc2proc(it->kd, kproc, p) != 0) + continue; + return 0; + } + } + return -1; +} + +int close_process_iterator(struct process_iterator *it) +{ + free(it->procs); + it->procs = NULL; + if (kvm_close(it->kd) == -1) + { + perror("kvm_close"); + return -1; + } + return 0; } +#endif +#endif diff --git a/src/process_iterator_linux.c b/src/process_iterator_linux.c index c8cdd07a..a002d036 100644 --- a/src/process_iterator_linux.c +++ b/src/process_iterator_linux.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,163 +19,226 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#ifdef __linux__ + +#ifndef __PROCESS_ITERATOR_LINUX_C +#define __PROCESS_ITERATOR_LINUX_C + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include #include +#include +#include -static int get_boot_time() -{ - int uptime = 0; - FILE *fp = fopen ("/proc/uptime", "r"); - if (fp != NULL) - { - char buf[BUFSIZ]; - char *b = fgets(buf, BUFSIZ, fp); - if (b == buf) - { - char *end_ptr; - double upsecs = strtod(buf, &end_ptr); - uptime = (int)upsecs; - } - fclose (fp); - } - time_t now = time(NULL); - return now - uptime; -} +#include "process_iterator.h" -static int check_proc() +static int check_proc(void) { - struct statfs mnt; - if (statfs("/proc", &mnt) < 0) - return 0; - if (mnt.f_type!=0x9fa0) - return 0; - return 1; + struct stat statbuf; + return stat("/proc", &statbuf) == 0 && S_ISDIR(statbuf.st_mode); } int init_process_iterator(struct process_iterator *it, struct process_filter *filter) { - if (!check_proc()) { - fprintf(stderr, "procfs is not mounted!\nAborting\n"); - exit(-2); - } - //open a directory stream to /proc directory - if ((it->dip = opendir("/proc")) == NULL) - { - perror("opendir"); - return -1; - } - it->filter = filter; - it->boot_time = get_boot_time(); - return 0; + if (!check_proc()) + { + fprintf(stderr, "procfs is not mounted!\nAborting\n"); + exit(EXIT_FAILURE); + } + /* open a directory stream to /proc directory */ + if ((it->dip = opendir("/proc")) == NULL) + { + perror("opendir"); + return -1; + } + it->filter = filter; + return 0; } static int read_process_info(pid_t pid, struct process *p) { - static char buffer[1024]; - static char statfile[32]; - static char exefile[1024]; - p->pid = pid; - //read stat file - sprintf(statfile, "/proc/%d/stat", p->pid); - FILE *fd = fopen(statfile, "r"); - if (fd==NULL) return -1; - if (fgets(buffer, sizeof(buffer), fd)==NULL) { - fclose(fd); - return -1; - } - fclose(fd); - char *token = strtok(buffer, " "); - int i; - for (i=0; i<3; i++) token = strtok(NULL, " "); - p->ppid = atoi(token); - for (i=0; i<10; i++) - token = strtok(NULL, " "); - p->cputime = atoi(token) * 1000 / HZ; - token = strtok(NULL, " "); - p->cputime += atoi(token) * 1000 / HZ; - for (i=0; i<7; i++) - token = strtok(NULL, " "); - p->starttime = atoi(token) / sysconf(_SC_CLK_TCK); - //read command line - sprintf(exefile,"/proc/%d/cmdline", p->pid); - fd = fopen(exefile, "r"); - if (fgets(buffer, sizeof(buffer), fd)==NULL) { - fclose(fd); - return -1; - } - fclose(fd); - strcpy(p->command, buffer); - return 0; + char statfile[32], exefile[32], state; + double usertime, systime; + long ppid; + FILE *fd; + static long sc_clk_tck = -1; + + p->pid = pid; + + /* read command line */ + sprintf(exefile, "/proc/%ld/cmdline", (long)p->pid); + if ((fd = fopen(exefile, "r")) == NULL) + { + return -1; + } + if (fgets(p->command, sizeof(p->command), fd) == NULL) + { + fclose(fd); + return -1; + } + fclose(fd); + + /* read stat file */ + sprintf(statfile, "/proc/%ld/stat", (long)p->pid); + if ((fd = fopen(statfile, "r")) == NULL) + { + return -1; + } + if (fscanf(fd, "%*d (%*[^)]) %c %ld %*d %*d %*d %*d %*d %*d %*d %*d %*d %lf %lf", + &state, &ppid, &usertime, &systime) != 4 || + strchr("ZXx", state) != NULL) + { + fclose(fd); + return -1; + } + fclose(fd); + p->ppid = (pid_t)ppid; + if (sc_clk_tck < 0) + { + sc_clk_tck = sysconf(_SC_CLK_TCK); + } + p->cputime = (usertime + systime) * 1000.0 / sc_clk_tck; + + return 0; +} + +pid_t getppid_of(pid_t pid) +{ + char statfile[32]; + FILE *fd; + long ppid; + if (pid <= 0) + return (pid_t)(-1); + sprintf(statfile, "/proc/%ld/stat", (long)pid); + if ((fd = fopen(statfile, "r")) == NULL) + return (pid_t)(-1); + if (fscanf(fd, "%*d (%*[^)]) %*c %ld", &ppid) != 1) + ppid = -1; + fclose(fd); + return (pid_t)ppid; } -static pid_t getppid_of(pid_t pid) +static int get_start_time(pid_t pid, struct timespec *start_time) { - char statfile[20]; - char buffer[1024]; - sprintf(statfile, "/proc/%d/stat", pid); - FILE *fd = fopen(statfile, "r"); - if (fd==NULL) return -1; - if (fgets(buffer, sizeof(buffer), fd)==NULL) { - fclose(fd); - return -1; - } - fclose(fd); - char *token = strtok(buffer, " "); - int i; - for (i=0; i<3; i++) token = strtok(NULL, " "); - return atoi(token); + struct stat procfs_stat; + char procfs_path[32]; + int ret; + if (start_time == NULL) + return -1; + sprintf(procfs_path, "/proc/%ld", (long)pid); + if ((ret = stat(procfs_path, &procfs_stat)) == 0) + *start_time = procfs_stat.st_ctim; + return ret; } -static int is_child_of(pid_t child_pid, pid_t parent_pid) +static int earlier_than(const struct timespec *t1, const struct timespec *t2) { - int ppid = child_pid; - while(ppid > 1 && ppid != parent_pid) { - ppid = getppid_of(ppid); - } - return ppid == parent_pid; + return t1->tv_sec < t2->tv_sec || + (t1->tv_sec == t2->tv_sec && t1->tv_nsec < t2->tv_nsec); +} + +int is_child_of(pid_t child_pid, pid_t parent_pid) +{ + int ret_child, ret_parent; + struct timespec child_start_time, parent_start_time; + if (child_pid <= 1 || parent_pid <= 0 || child_pid == parent_pid) + return 0; + if (parent_pid == 1) + return 1; + ret_parent = get_start_time(parent_pid, &parent_start_time); + while (child_pid > 1) + { + if (ret_parent == 0) + { + ret_child = get_start_time(child_pid, &child_start_time); + if (ret_child == 0 && earlier_than(&child_start_time, &parent_start_time)) + return 0; + } + child_pid = getppid_of(child_pid); + if (child_pid == parent_pid) + return 1; + } + return 0; +} + +static int is_numeric(const char *str) +{ + if (str == NULL || *str == '\0') + return 0; + for (; *str != '\0'; str++) + { + if (!isdigit(*str)) + return 0; + } + return 1; } int get_next_process(struct process_iterator *it, struct process *p) { - if (it->dip == NULL) - { - //end of processes - return -1; - } - if (it->filter->pid != 0 && !it->filter->include_children) - { - int ret = read_process_info(it->filter->pid, p); - //p->starttime += it->boot_time; - closedir(it->dip); - it->dip = NULL; - if (ret != 0) return -1; - return 0; - } - struct dirent *dit = NULL; - //read in from /proc and seek for process dirs - while ((dit = readdir(it->dip)) != NULL) { - if(strtok(dit->d_name, "0123456789") != NULL) - continue; - p->pid = atoi(dit->d_name); - if (it->filter->pid != 0 && it->filter->pid != p->pid && !is_child_of(p->pid, it->filter->pid)) continue; - read_process_info(p->pid, p); - //p->starttime += it->boot_time; - break; - } - if (dit == NULL) - { - //end of processes - closedir(it->dip); - it->dip = NULL; - return -1; - } - return 0; + const struct dirent *dit = NULL; + + if (it->dip == NULL) + { + /* end of processes */ + return -1; + } + if (it->filter->pid != 0 && !it->filter->include_children) + { + int ret = read_process_info(it->filter->pid, p); + closedir(it->dip); + it->dip = NULL; + return ret == 0 ? 0 : -1; + } + + /* read in from /proc and seek for process dirs */ + while ((dit = readdir(it->dip)) != NULL) + { +#ifdef _DIRENT_HAVE_D_TYPE + if (dit->d_type != DT_DIR) + continue; +#endif + if (!is_numeric(dit->d_name) || + (p->pid = (pid_t)atol(dit->d_name)) <= 0) + continue; + if (it->filter->pid != 0 && + it->filter->pid != p->pid && + !is_child_of(p->pid, it->filter->pid)) + continue; + if (read_process_info(p->pid, p) != 0) + continue; + return 0; + } + /* end of processes */ + closedir(it->dip); + it->dip = NULL; + return -1; } -int close_process_iterator(struct process_iterator *it) { - if (it->dip != NULL && closedir(it->dip) == -1) { - perror("closedir"); - return 1; - } - it->dip = NULL; - return 0; +int close_process_iterator(struct process_iterator *it) +{ + int ret = 0; + if (it == NULL) + return -1; /* Invalid argument */ + + if (it->dip != NULL) + { + if ((ret = closedir(it->dip)) != 0) + { + perror("closedir"); + } + it->dip = NULL; + } + + return ret == 0 ? 0 : -1; } + +#endif +#endif diff --git a/src/process_table.c b/src/process_table.c new file mode 100644 index 00000000..d220a654 --- /dev/null +++ b/src/process_table.c @@ -0,0 +1,114 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include "process_table.h" + +void process_table_init(struct process_table *pt, int hashsize) +{ + pt->hashsize = hashsize; + pt->table = (struct list **)calloc((size_t)pt->hashsize, sizeof(struct list *)); + if (pt->table == NULL) + { + fprintf(stderr, "Memory allocation failed for the process table\n"); + exit(EXIT_FAILURE); + } +} + +static int proc_hash(const struct process_table *pt, const struct process *p) +{ + return p->pid % pt->hashsize; +} + +struct process *process_table_find(const struct process_table *pt, const struct process *p) +{ + int idx = proc_hash(pt, p); + if (pt->table[idx] == NULL) + { + return NULL; + } + return (struct process *)locate_elem(pt->table[idx], p); +} + +struct process *process_table_find_pid(const struct process_table *pt, pid_t pid) +{ + struct process *p, *res; + p = (struct process *)malloc(sizeof(struct process)); + if (p == NULL) + { + fprintf(stderr, "Memory allocation failed for the process\n"); + exit(EXIT_FAILURE); + } + p->pid = pid; + res = process_table_find(pt, p); + free(p); + return res; +} + +void process_table_add(struct process_table *pt, struct process *p) +{ + int idx = proc_hash(pt, p); + if (pt->table[idx] == NULL) + { + pt->table[idx] = (struct list *)malloc(sizeof(struct list)); + if (pt->table[idx] == NULL) + { + fprintf(stderr, "Memory allocation failed for the process list\n"); + exit(EXIT_FAILURE); + } + init_list(pt->table[idx], sizeof(pid_t)); + } + add_elem(pt->table[idx], p); +} + +int process_table_del(struct process_table *pt, const struct process *p) +{ + struct list_node *node; + int idx = proc_hash(pt, p); + if (pt->table[idx] == NULL) + { + return 1; /* nothing to delete */ + } + node = (struct list_node *)locate_node(pt->table[idx], p); + if (node == NULL) + { + return 1; /* nothing to delete */ + } + delete_node(pt->table[idx], node); + return 0; +} + +int process_table_del_pid(struct process_table *pt, pid_t pid) +{ + struct process *p; + int ret; + p = (struct process *)malloc(sizeof(struct process)); + if (p == NULL) + { + fprintf(stderr, "Memory allocation failed for the process\n"); + exit(EXIT_FAILURE); + } + p->pid = pid; + ret = process_table_del(pt, p); + free(p); + return ret; +} + +void process_table_destroy(struct process_table *pt) +{ + int i; + for (i = 0; i < pt->hashsize; i++) + { + if (pt->table[i] != NULL) + { + destroy_list(pt->table[i]); + free(pt->table[i]); + } + } + free(pt->table); + pt->table = NULL; +} diff --git a/src/process_table.h b/src/process_table.h new file mode 100644 index 00000000..0dcee66f --- /dev/null +++ b/src/process_table.h @@ -0,0 +1,83 @@ +#ifndef __PROCESS_TABLE_H +#define __PROCESS_TABLE_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include "process_iterator.h" +#include "list.h" + +/** + * Structure representing a process table. + */ +struct process_table +{ + /* Array of pointers to linked lists for storing processes */ + struct list **table; + + /* Size of the hash table for the process table */ + int hashsize; +}; + +/** + * Initializes the process table with the given hash size. + * + * @param pt The process table to initialize + * @param hashsize The size of the hash table + */ +void process_table_init(struct process_table *pt, int hashsize); + +/** + * Finds a process in the process table based on the given process. + * + * @param pt The process table to search in + * @param p The process to find + * @return A pointer to the found process or NULL if not found + */ +struct process *process_table_find(const struct process_table *pt, const struct process *p); + +/** + * Finds a process in the process table based on the given PID. + * + * @param pt The process table to search in + * @param pid The PID of the process to find + * @return A pointer to the found process or NULL if not found + */ +struct process *process_table_find_pid(const struct process_table *pt, pid_t pid); + +/** + * Adds a process to the process table. + * + * @param pt The process table to add the process to + * @param p The process to add + */ +void process_table_add(struct process_table *pt, struct process *p); + +/** + * Deletes a process from the process table. + * + * @param pt The process table to delete the process from + * @param p The process to delete + * @return 0 if deletion is successful, 1 if process not found + */ +int process_table_del(struct process_table *pt, const struct process *p); + +/** + * Deletes a process from the process table based on the given PID. + * + * @param pt The process table to delete the process from + * @param pid The PID of the process to delete + * @return 0 if deletion is successful, 1 if process not found + */ +int process_table_del_pid(struct process_table *pt, pid_t pid); + +/** + * Destroys the process table and frees up the memory. + * + * @param pt The process table to destroy + */ +void process_table_destroy(struct process_table *pt); + +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 00000000..2749c538 --- /dev/null +++ b/src/util.c @@ -0,0 +1,98 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#endif +#include "util.h" + +#ifdef __IMPL_BASENAME +const char *__basename(const char *path) +{ + const char *p = strrchr(path, '/'); + return p != NULL ? p + 1 : path; +} +#endif + +#ifdef __IMPL_GET_TIME +int __get_time(struct timespec *ts) +{ + struct timeval tv; + if (gettimeofday(&tv, NULL)) + { + return -1; + } + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000L; + return 0; +} +#endif + +void increase_priority(void) +{ + static const int MAX_PRIORITY = -20; + int old_priority, priority; + old_priority = getpriority(PRIO_PROCESS, 0); + for (priority = MAX_PRIORITY; priority < old_priority; priority++) + { + if (setpriority(PRIO_PROCESS, 0, priority) == 0 && + getpriority(PRIO_PROCESS, 0) == priority) + break; + } +} + +/* Get the number of CPUs */ +int get_ncpu(void) +{ + int ncpu = -1; +#if defined(_SC_NPROCESSORS_ONLN) + ncpu = (int)sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(__APPLE__) + int mib[2] = {CTL_HW, HW_AVAILCPU}; + size_t len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); +#elif defined(__FreeBSD__) + int mib[2]{CTL_HW, HW_NCPU}; + size_t len = sizeof(ncpu); + sysctl(mib, 2, &ncpu, &len, NULL, 0); +#elif defined(_GNU_SOURCE) + ncpu = get_nprocs(); +#else +#error "Platform not supported" +#endif + return ncpu; +} + +pid_t get_pid_max(void) +{ +#if defined(__linux__) + long pid_max = -1; + FILE *fd; + if ((fd = fopen("/proc/sys/kernel/pid_max", "r")) != NULL) + { + if (fscanf(fd, "%ld", &pid_max) != 1) + { + perror("fscanf"); + pid_max = -1; + } + fclose(fd); + } + return (pid_t)pid_max; +#elif defined(__APPLE__) || defined(__FreeBSD__) + int max_proc; + size_t size = sizeof(max_proc); + if (sysctlbyname("kern.maxproc", &max_proc, &size, NULL, 0) == -1) + { + perror("sysctl"); + return (pid_t)-1; + } + return (pid_t)max_proc; +#else +#error "Platform not supported" +#endif +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..486f3f65 --- /dev/null +++ b/src/util.h @@ -0,0 +1,178 @@ +#ifndef __UTIL_H +#define __UTIL_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + +/* Useful macros */ + +#if defined(__GNUC__) && !defined(__UNIQUE_ID) +/** + * Helper macros to concatenate tokens + */ +#define ___PASTE(a, b) a##b +#define __PASTE(a, b) ___PASTE(a, b) + +/** + * Generates a unique ID based on a prefix + */ +#define __UNIQUE_ID(prefix) \ + __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__) +#endif + +#ifndef MIN +#ifdef __GNUC__ +/** + * Helper for finding the minimum of two values, utilizing type safety + */ +#define __min(t1, t2, min1, min2, x, y) \ + (__extension__({ \ + t1 min1 = (x); \ + t2 min2 = (y); \ + (void)(&min1 == &min2); \ + min1 < min2 ? min1 : min2; \ + })) +/** + * Macro to find the minimum of two values + */ +#define MIN(x, y) \ + __min(__typeof__(x), __typeof__(y), \ + __UNIQUE_ID(min1_), __UNIQUE_ID(min2_), \ + x, y) +#else +/** + * Simple macro to find the minimum of two values + */ +#define MIN(a, b) \ + (((a) < (b)) ? (a) : (b)) +#endif +#endif + +#ifndef MAX +#ifdef __GNUC__ +/** + * Helper for finding the maximum of two values, utilizing type safety + */ +#define __max(t1, t2, max1, max2, x, y) \ + (__extension__({ \ + t1 max1 = (x); \ + t2 max2 = (y); \ + (void)(&max1 == &max2); \ + max1 > max2 ? max1 : max2; \ + })) +/** + * Macro to find the maximum of two values + */ +#define MAX(x, y) \ + __max(__typeof__(x), __typeof__(y), \ + __UNIQUE_ID(max1_), __UNIQUE_ID(max2_), \ + x, y) +#else +/** + * Simple macro to find the maximum of two values + */ +#define MAX(a, b) \ + (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#ifndef basename +#ifdef __GNUC__ +/** + * Helper macro to get the basename of a path + */ +#define __basename(full_path, last_slash, path) \ + (__extension__({ \ + const char *full_path, *last_slash; \ + full_path = (path); \ + last_slash = strrchr(full_path, '/'); \ + (last_slash != NULL) ? (last_slash + 1) : full_path; \ + })) +#define basename(path) \ + __basename(__UNIQUE_ID(full_path_), __UNIQUE_ID(last_slash_), path) +#else +/** + * Get the basename of a path + */ +const char *__basename(const char *path); +#define basename(path) __basename(path) +#define __IMPL_BASENAME +#endif +#endif + +/** + * Converts nanoseconds to a timespec structure + */ +#ifndef nsec2timespec +#define nsec2timespec(nsec, t) \ + do \ + { \ + (t)->tv_sec = (time_t)((nsec) / 1e9); \ + (t)->tv_nsec = (long)((nsec) - (double)(t)->tv_sec * 1e9); \ + } while (0) +#endif + +/** + * Sleep for a specified timespec duration + */ +#ifndef sleep_timespec +#if defined(__linux__) && _POSIX_C_SOURCE >= 200112L && defined(CLOCK_TAI) +#define sleep_timespec(t) clock_nanosleep(CLOCK_TAI, 0, (t), NULL) +#else +#define sleep_timespec(t) nanosleep((t), NULL) +#endif +#endif + +/** + * Retrieves the current time into a timespec structure + */ +#ifndef get_time +#if _POSIX_TIMERS > 0 +#if defined(CLOCK_TAI) +#define get_time(ts) clock_gettime(CLOCK_TAI, (ts)) +#elif defined(CLOCK_MONOTONIC) +#define get_time(ts) clock_gettime(CLOCK_MONOTONIC, (ts)) +#endif +#endif +#endif + +/** + * Fallback function for getting time if not defined + */ +#ifndef get_time +int __get_time(struct timespec *ts); +#define get_time(ts) __get_time(ts) +#define __IMPL_GET_TIME +#endif + +/** + * Returns the difference between two timespecs in milliseconds + */ +#ifndef timediff_in_ms +#define timediff_in_ms(t1, t2) \ + ((double)((t1)->tv_sec - (t2)->tv_sec) * 1e3 + (double)((t1)->tv_nsec - (t2)->tv_nsec) / 1e6) +#endif + +/** + * Increases the priority of the current process + */ +void increase_priority(void); + +/** + * Retrieves the number of available CPUs + */ +int get_ncpu(void); + +/** + * Retrieves the maximum process ID + */ +pid_t get_pid_max(void); + +#endif diff --git a/.gitignore b/tests/.gitignore similarity index 64% rename from .gitignore rename to tests/.gitignore index 70e48752..032dbfeb 100644 --- a/.gitignore +++ b/tests/.gitignore @@ -1,5 +1,5 @@ *.o *~ -src/cpulimit busy +multi_process_busy process_iterator_test diff --git a/tests/Makefile b/tests/Makefile index 764db878..25bc54a3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,23 +1,57 @@ -CC?=gcc -CFLAGS?=-Wall -g -TARGETS=busy process_iterator_test -SRC=../src -SYSLIBS?=-lpthread -LIBS=$(SRC)/list.o $(SRC)/process_iterator.o $(SRC)/process_group.o -UNAME := $(shell uname) +# Compiler configuration +CC ?= gcc +# Compiler flags +override CFLAGS := $(filter-out -std=% -ansi -W%, $(CFLAGS)) \ + -std=c89 \ + -Wall \ + -Wextra \ + -pedantic \ + -Wold-style-definition \ + -Wmissing-prototypes \ + -Wstrict-prototypes \ + -Werror + +# Target binaries and source directory +TARGETS := $(patsubst %.c, %, $(wildcard *.c)) +SRC := ../src + +# Detect operating system +UNAME ?= $(shell uname) + +# Platform-specific linker flags ifeq ($(UNAME), FreeBSD) -LIBS+=-lkvm + override LDFLAGS += -lkvm endif -all:: $(TARGETS) +ifeq ($(UNAME), Darwin) + override LDFLAGS += -lproc +endif -busy: busy.c - $(CC) -o busy busy.c $(SYSLIBS) $(CFLAGS) +# Check for librt availability +override LDFLAGS += $(shell \ + echo "int main(void){ return 0; }" | \ + $(CC) -x c -o /dev/null -lrt - 2>/dev/null && \ + echo -lrt \ +) -process_iterator_test: process_iterator_test.c $(LIBS) - $(CC) -I$(SRC) -o process_iterator_test process_iterator_test.c $(LIBS) $(SYSLIBS) $(CFLAGS) +# Phony targets +.PHONY: all clean -clean: - rm -f *~ *.o $(TARGETS) +# Default target +all: $(TARGETS) +# Target rules +busy: busy.c $(wildcard $(SRC)/util.*) + $(CC) $(CFLAGS) $(filter-out %.h, $^) -lpthread $(LDFLAGS) -o $@ + +multi_process_busy: multi_process_busy.c + $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ + +process_iterator_test: process_iterator_test.c \ + $(filter-out $(SRC)/cpulimit.c, $(wildcard $(SRC)/*.c $(SRC)/*.h)) + $(CC) $(CFLAGS) $(filter-out $(SRC)/process_iterator_%.c %.h, $^) $(LDFLAGS) -o $@ + +# Clean target +clean: + rm -f *~ $(TARGETS) diff --git a/tests/busy.c b/tests/busy.c index b3afb7cd..5aa3d29f 100644 --- a/tests/busy.c +++ b/tests/busy.c @@ -1,29 +1,38 @@ #include #include #include -#include +#include "../src/util.h" -void *loop() +#ifndef __GNUC__ +#define __attribute__(attr) +#endif + +static void *loop(void *param __attribute__((unused))) { - while(1); + volatile int unused_value = 0; + while (1) + (void)unused_value; + return NULL; } -int main(int argc, char **argv) { +int main(int argc, char *argv[]) +{ - int i = 0; - int num_threads = 1; - if (argc == 2) num_threads = atoi(argv[1]); - for (i=0; i + +int main(void) +{ + fork(); + fork(); + while (1) + ; + return 0; +} diff --git a/tests/process_iterator_test.c b/tests/process_iterator_test.c index 16151967..df8cfbf8 100644 --- a/tests/process_iterator_test.c +++ b/tests/process_iterator_test.c @@ -2,7 +2,7 @@ * * cpulimit - a CPU limiter for Linux * - * Copyright (C) 2005-2012, by: Angelo Marletta + * Copyright (C) 2005-2012, by: Angelo Marletta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,224 +19,315 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include -#include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#undef NDEBUG #include -#include #include +#include +#include #include +#include +#include +#include + +#include "../src/process_iterator.h" +#include "../src/process_group.h" +#include "../src/util.h" -#ifdef __APPLE__ || __FREEBSD__ -#include +#ifndef __GNUC__ +#define __attribute__(attr) #endif -#include -#include +static void ignore_signal(int sig __attribute__((unused))) +{ +} -volatile sig_atomic_t child; +static void test_single_process(void) +{ + struct process_iterator it; + struct process *process; + struct process_filter filter; + int count; + process = (struct process *)malloc(sizeof(struct process)); + assert(process != NULL); + /* don't iterate children */ + filter.pid = getpid(); + filter.include_children = 0; + count = 0; + init_process_iterator(&it, &filter); + while (get_next_process(&it, process) == 0) + { + assert(process->pid == getpid()); + assert(process->ppid == getppid()); + assert(process->cputime <= 100); + count++; + } + assert(count == 1); + close_process_iterator(&it); + /* iterate children */ + filter.pid = getpid(); + filter.include_children = 0; + count = 0; + init_process_iterator(&it, &filter); + while (get_next_process(&it, process) == 0) + { + assert(process->pid == getpid()); + assert(process->ppid == getppid()); + assert(process->cputime <= 100); + count++; + } + assert(count == 1); + free(process); + close_process_iterator(&it); +} -void kill_child(int sig) +static void test_multiple_process(void) { - kill(child, SIGINT); + struct process_iterator it; + struct process *process; + struct process_filter filter; + int count = 0; + pid_t child = fork(); + if (child == 0) + { + /* child is supposed to be killed by the parent :/ */ + while (1) + sleep(5); + } + process = (struct process *)malloc(sizeof(struct process)); + assert(process != NULL); + filter.pid = getpid(); + filter.include_children = 1; + init_process_iterator(&it, &filter); + while (get_next_process(&it, process) == 0) + { + if (process->pid == getpid()) + assert(process->ppid == getppid()); + else if (process->pid == child) + assert(process->ppid == getpid()); + else + assert(0); + assert(process->cputime <= 100); + count++; + } + assert(count == 2); + free(process); + close_process_iterator(&it); + kill(child, SIGKILL); } -void test_single_process() +static void test_all_processes(void) { - struct process_iterator it; - struct process process; - struct process_filter filter; - int count; - //don't iterate children - filter.pid = getpid(); - filter.include_children = 0; - count = 0; -// time_t now = time(NULL); - init_process_iterator(&it, &filter); - while (get_next_process(&it, &process) == 0) - { - assert(process.pid == getpid()); - assert(process.ppid == getppid()); - assert(process.cputime < 100); -// assert(process.starttime == now || process.starttime == now - 1); - count++; - } - assert(count == 1); - close_process_iterator(&it); - //iterate children - filter.pid = getpid(); - filter.include_children = 0; - count = 0; -// now = time(NULL); - init_process_iterator(&it, &filter); - while (get_next_process(&it, &process) == 0) - { - assert(process.pid == getpid()); - assert(process.ppid == getppid()); - assert(process.cputime < 100); -// assert(process.starttime == now || process.starttime == now - 1); - count++; - } - assert(count == 1); - close_process_iterator(&it); + struct process_iterator it; + struct process *process; + struct process_filter filter; + int count = 0; + filter.pid = 0; + filter.include_children = 0; + process = (struct process *)malloc(sizeof(struct process)); + assert(process != NULL); + init_process_iterator(&it, &filter); + + while (get_next_process(&it, process) == 0) + { + if (process->pid == getpid()) + { + assert(process->ppid == getppid()); + assert(process->cputime <= 100); + } + count++; + } + assert(count >= 10); + free(process); + close_process_iterator(&it); } -void test_multiple_process() +static void test_process_group_all(void) { - struct process_iterator it; - struct process process; - struct process_filter filter; - pid_t child = fork(); - if (child == 0) - { - //child is supposed to be killed by the parent :/ - sleep(1); - exit(1); - } - filter.pid = getpid(); - filter.include_children = 1; - init_process_iterator(&it, &filter); - int count = 0; -// time_t now = time(NULL); - while (get_next_process(&it, &process) == 0) - { - if (process.pid == getpid()) assert(process.ppid == getppid()); - else if (process.pid == child) assert(process.ppid == getpid()); - else assert(0); - assert(process.cputime < 100); -// assert(process.starttime == now || process.starttime == now - 1); - count++; - } - assert(count == 2); - close_process_iterator(&it); - kill(child, SIGINT); + struct process_group pgroup; + struct list_node *node = NULL; + int count = 0; + assert(init_process_group(&pgroup, 0, 0) == 0); + update_process_group(&pgroup); + for (node = pgroup.proclist->first; node != NULL; node = node->next) + { + count++; + } + assert(count > 10); + update_process_group(&pgroup); + assert(close_process_group(&pgroup) == 0); } -void test_all_processes() +static void test_process_group_single(int include_children) { - struct process_iterator it; - struct process process; - struct process_filter filter; - filter.pid = 0; - filter.include_children = 0; - init_process_iterator(&it, &filter); - int count = 0; -// time_t now = time(NULL); - while (get_next_process(&it, &process) == 0) - { - if (process.pid == getpid()) - { - assert(process.ppid == getppid()); - assert(process.cputime < 100); -// assert(process.starttime == now || process.starttime == now - 1); - } - count++; - } - assert(count >= 10); - close_process_iterator(&it); + struct process_group pgroup; + int i; + pid_t child = fork(); + if (child == 0) + { + /* child is supposed to be killed by the parent :/ */ + volatile int unused_value = 0; + increase_priority(); + while (1) + (void)unused_value; + } + assert(init_process_group(&pgroup, child, include_children) == 0); + for (i = 0; i < 100; i++) + { + struct list_node *node = NULL; + int count = 0; + struct timespec interval; + update_process_group(&pgroup); + for (node = pgroup.proclist->first; node != NULL; node = node->next) + { + const struct process *p = (const struct process *)(node->data); + assert(p->pid == child); + assert(p->ppid == getpid()); + /* p->cpu_usage should be -1 or [0, 1] */ + assert((p->cpu_usage >= (-1.00001) && p->cpu_usage <= (-0.99999)) || + (p->cpu_usage >= 0 && p->cpu_usage <= 1.0)); + count++; + } + assert(count == 1); + interval.tv_sec = 0; + interval.tv_nsec = 200000000; + sleep_timespec(&interval); + } + assert(close_process_group(&pgroup) == 0); + kill(child, SIGKILL); } -void test_process_group_all() +char *command = NULL; + +static void test_process_name(void) { - struct process_group pgroup; - assert(init_process_group(&pgroup, 0, 0) == 0); - update_process_group(&pgroup); - struct list_node *node = NULL; - int count = 0; - for (node=pgroup.proclist->first; node!= NULL; node=node->next) { - count++; - } - assert(count > 10); - update_process_group(&pgroup); - assert(close_process_group(&pgroup) == 0); + struct process_iterator it; + struct process *process; + struct process_filter filter; + const char *proc_name1, *proc_name2; + process = (struct process *)malloc(sizeof(struct process)); + assert(process != NULL); + filter.pid = getpid(); + filter.include_children = 0; + init_process_iterator(&it, &filter); + assert(get_next_process(&it, process) == 0); + assert(process->pid == getpid()); + assert(process->ppid == getppid()); + proc_name1 = basename(command); + proc_name2 = basename(process->command); + assert(strncmp(proc_name1, proc_name2, sizeof(process->command)) == 0); + assert(get_next_process(&it, process) != 0); + free(process); + close_process_iterator(&it); } -void test_process_group_single(int include_children) +static void test_process_group_wrong_pid(void) { - struct process_group pgroup; - child = fork(); - if (child == 0) - { - //child is supposed to be killed by the parent :/ - while(1); - exit(1); - } - signal(SIGABRT, &kill_child); - signal(SIGTERM, &kill_child); - assert(init_process_group(&pgroup, child, include_children) == 0); - int i; - double tot_usage = 0; - for (i=0; i<100; i++) - { - update_process_group(&pgroup); - struct list_node *node = NULL; - int count = 0; - for (node=pgroup.proclist->first; node!= NULL; node=node->next) { - struct process *p = (struct process*)(node->data); - assert(p->pid == child); - assert(p->ppid == getpid()); - assert(p->cpu_usage <= 1.2); - tot_usage += p->cpu_usage; - count++; - } - assert(count == 1); - struct timespec interval; - interval.tv_sec = 0; - interval.tv_nsec = 50000000; - nanosleep(&interval, NULL); - } - assert(tot_usage / i < 1.1 && tot_usage / i > 0.8); - assert(close_process_group(&pgroup) == 0); - kill(child, SIGINT); + struct process_group pgroup; + assert(init_process_group(&pgroup, -1, 0) == 0); + assert(pgroup.proclist->count == 0); + update_process_group(&pgroup); + assert(pgroup.proclist->count == 0); + assert(init_process_group(&pgroup, 9999999, 0) == 0); + assert(pgroup.proclist->count == 0); + update_process_group(&pgroup); + assert(pgroup.proclist->count == 0); + assert(close_process_group(&pgroup) == 0); } -void test_process_name(const char * command) +static void test_find_process_by_pid(void) { - struct process_iterator it; - struct process process; - struct process_filter filter; - filter.pid = getpid(); - filter.include_children = 0; - init_process_iterator(&it, &filter); - assert(get_next_process(&it, &process) == 0); - assert(process.pid == getpid()); - assert(process.ppid == getppid()); - #ifdef __APPLE__ - // proc_pidinfo only gives us the first 15 chars - // of the basename of the command on OSX. - assert(strncmp(basename((char*)command), process.command, 15) == 0); - #else - assert(strncmp(command, process.command, strlen(process.command)) == 0); - #endif - assert(get_next_process(&it, &process) != 0); - close_process_iterator(&it); + assert(find_process_by_pid(getpid()) == getpid()); } -void test_process_group_wrong_pid() +static void test_find_process_by_name(void) { - struct process_group pgroup; - assert(init_process_group(&pgroup, -1, 0) == 0); - assert(pgroup.proclist->count == 0); - update_process_group(&pgroup); - assert(pgroup.proclist->count == 0); - assert(init_process_group(&pgroup, 9999999, 0) == 0); - assert(pgroup.proclist->count == 0); - update_process_group(&pgroup); - assert(pgroup.proclist->count == 0); - assert(close_process_group(&pgroup) == 0); + char *wrong_name; + size_t len; + + wrong_name = (char *)malloc(PATH_MAX + 1); + assert(wrong_name != NULL); + + /* + * 'command' is the name of the current process (equivalent to argv[0]). + * Verify that the find_process_by_name function can find the current process + * (PID should match the return value of getpid()). + */ + assert(find_process_by_name(command) == getpid()); + + /* + * Test Case 1: Pass an empty string to find_process_by_name. + * Expectation: Should return 0 (process not found). + */ + strcpy(wrong_name, ""); + assert(find_process_by_name(wrong_name) == 0); + + /* + * Test Case 2: Pass an incorrect process name by appending 'x' + * to the current process's name. + * Expectation: Should return 0 (process not found). + */ + strcpy(wrong_name, command); /* Copy the current process's name */ + strcat(wrong_name, "x"); /* Append 'x' to make it non-matching */ + assert(find_process_by_name(wrong_name) == 0); + + /* + * Test Case 3: Pass a copy of the current process's name with + * the last character removed. + * Expectation: Should return 0 (process not found). + */ + strcpy(wrong_name, command); /* Copy the current process's name */ + len = strlen(wrong_name); + wrong_name[len - 1] = '\0'; /* Remove the last character */ + assert(find_process_by_name(wrong_name) == 0); + + free(wrong_name); +} + +static void test_getppid_of(void) +{ + struct process_iterator it; + struct process *process; + struct process_filter filter; + filter.pid = 0; + filter.include_children = 0; + process = (struct process *)malloc(sizeof(struct process)); + assert(process != NULL); + init_process_iterator(&it, &filter); + while (get_next_process(&it, process) == 0) + { + assert(getppid_of(process->pid) == process->ppid); + } + free(process); + close_process_iterator(&it); + assert(getppid_of(getpid()) == getppid()); } -int main(int argc, char **argv) +int main(int argc __attribute__((unused)), char *argv[]) { -// printf("Pid %d\n", getpid()); - test_single_process(); - test_multiple_process(); - test_all_processes(); - test_process_group_all(); - test_process_group_single(0); - test_process_group_single(1); - test_process_group_wrong_pid(); - test_process_name(argv[0]); - return 0; + /* ignore SIGINT and SIGTERM during tests*/ + struct sigaction sa; + sa.sa_handler = ignore_signal; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + command = argv[0]; + increase_priority(); + test_single_process(); + test_multiple_process(); + test_all_processes(); + test_process_group_all(); + test_process_group_single(0); + test_process_group_single(1); + test_process_group_wrong_pid(); + test_process_name(); + test_find_process_by_pid(); + test_find_process_by_name(); + test_getppid_of(); + return 0; } diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e69de29b..8b137891 100644 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -0,0 +1 @@ +