cdup2setitimer

Why do setitimer and dup2 work for a child proccess after execvp?


First let me say that there are a lot of questions in here.

One of the tasks for my thesis requires me to write a program that executes a sub-program and kills it if it running time ( not wall-time but user+sys ) is more then a specific value or it's RAM consumption is more then another specified value.

While I have not figured out the RAM part yet. The time killing I do with setitmer and the ITIMER_PROF signal. ( Because ITIMER_PROF gathers actual CPU usages rather then setting a starting point in time and then count for x amount of time )

The reason I use setitimer is because I need less then second precision. ( E.G. kill the process after 1.75 seconds ( 1750000 microseconds ). The setrlimit method only has a second's one.

Question 1 Why doesn't the setitimer with ITIME_PROF work when it's set in the parent process ? The CPU / System calls for the child are not collected by it ?

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {
    execvp(args[0], args);
    exit(1);
}
// Parent
else{
    // Using a ITIMER_PROF inside the parent program will not work!
    // The child may take 1 hour to execute and the parent will wait it out!
    // To fix this we need to use a ITIMER_REAL ( wall-time ) but that's not an accurate measurement 
    struct itimerval timer;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 500000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 500000;
    setitimer ( ITIMER_PROF, &timer, NULL);

    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
}

Question 2 Why does this WORK!? Doesn't the execvp overwrite all the functions ( timeout_sigprof, main and any other)? And couldn't someone potentially catch the signal in the child program and supersede the original function ?

void timeout_sigprof( int signum ){
    fprintf(stderr, "The alarm SIGPROF is here !\nThe actual pid: %d\n", getpid());
    //TODO: Write output and say the child terminated with
    // ram or time limit exceeded
    exit(105); // Note the 105 !
}

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {
    // 
    struct sigaction sa;
    memset (&sa, 0, sizeof (sa));
    sa.sa_handler = &timeout_sigprof;
    sigaction (SIGPROF, &sa, NULL);

    struct itimerval timer;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 250000;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 250000;
    setitimer ( ITIMER_PROF, &timer, NULL);

    execvp(args[0], args);
    exit(1);
}
// Parent process
else {
    // Waiting for the child
    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
    exit(0);
}

Question 3 Why does the dup2 placed here actually work and let's the child's input / output to be redirected ?

childPID = fork();

if (childPID == -1){
        printf( "Puff paff ... fork() did not work !\n" );
        exit(1);
}

// Child 
if(childPID == 0) {

    // Redirect all I/O to/from a file
    int outFileId = open("output", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);

    // Redirect the output for the CHILD program. Still don't know why it works.
    dup2(outFileId, 1)

    // No idea why these dup2's work ! As i close the file descriptors here ?!
    close(outFileId);

    execvp(args[0], args);
    exit(1);
}
// Parent process
else {
    // Waiting for the child
    int status;
    waitpid(childPID,&status,0);
    if (WIFEXITED(status)) {
        fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
    }
    exit(0);
}

Here is the code I wrote that goes and kills a program only after it was running after X amount of time ( x = 500ms ).

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>

volatile pid_t childPID;

// This function should exist only in the parent! The child show not have it after a exec* acording to :
// The  exec()  family  of  functions  replaces  the current process image with a new process image.
void timeout_sigprof( int signum ){
    fprintf(stderr, "The alarm SIGPROF is here !\nThe actual pid: %d\n", getpid());
    //TODO: Write output and say the child terminated with a ram or time limit exceeded
    exit(105); // Note the 105 !
}

int main(int argc, char *argv[]) {
    int cstatus;
    pid_t cPID;

    char *args[2];
    args[0] = "/home/ddanailov/Projects/thesis/programs/prime/prime";
    args[1] = NULL; // Indicates the end of arguments.

    // Handle the SIGPROF signal in the function time_handler in both the child and 
    struct sigaction sa;
    memset (&sa, 0, sizeof (sa));
    sa.sa_handler = &timeout_sigprof;
    sigaction (SIGPROF, &sa, NULL);

    childPID = fork();

    if (childPID == -1){
            printf( "Puff paff ... fork() did not work !\n" );
            exit(1);
    }

    // Child 
    if(childPID == 0) {
        struct itimerval timer;
        timer.it_value.tv_sec = 0;
        timer.it_value.tv_usec = 250000;
        timer.it_interval.tv_sec = 0;
        timer.it_interval.tv_usec = 250000;
        setitimer ( ITIMER_PROF, &timer, NULL);

        // Redirect all I/O to/from a file
        int outFileId = open("output", O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
        // int inFileId = open("input");

        // Redirect the output for the CHILD program. Still don't know why it works.
        //dup2(inFileId, 0);
        dup2(outFileId, 1);
        //dup2(outFileId, 2);

        // No idea why these dup2's work ! As i close the file descriptors here ?!
        close(outFileId);
        close(inFileId);

        execvp(args[0], args);
        exit(1);
    }
    // Parent process
    else {
        // Waiting for the child
        int status;
        waitpid(childPID,&status,0);
        if (WIFEXITED(status)) {
            fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", cPID, WEXITSTATUS(status) );
        }
        exit(0);
    }

    return 0;
}

Any help / explanation will be much appreciated !

Thank you all in advance,

Ex


Solution

  • Question 1

    Why doesn't the setitimer with ITIME_PROF work when it's set in the parent process ? The CPU / System calls for the child are not collected by it ?

    No, they are not. The timer related to ITIME_PROF only decrements when the process which has the timer set is executing, or when system calls are executing on it's behalf, not when child processes are executing.

    These signals are normally used by profiling instrumentation which is contained in libraries that you link to the program you are trying to profile.

    However: you probably do not need to have the signal sent to the parent process anyway. If your goal is to terminate the program once it has exceeded the usage allowed, then let it receive the SIGPROF and exit (as seen in my answer to Q2 below). Then, after waitpid returns and you detect that the program has exited due to SIGPROF, you can find out the actual amount of time that the child used by calling times or getrusage.

    The only drawback to this is that the child program could subvert this process by setting it's own signal handler on SIGPROF.

    Question 2

    Why does this WORK!? Doesn't the execvp overwrite all the functions ( timeout_sigprof, main and any other)? And couldn't someone potentially catch the signal in the child program and supersede the original function ?

    It doesn't, or at least not the way you may be thinking. As you say, the signal handler you have installed in the parent process are replaced by the new image that is loaded by execvp.

    The reason it appears to work is that if the new program does not set a signal handler for SIGPROF, then when that signal is sent to the process it will terminate. Recall that any signal sent to a process which that process has not set up a handler for, or specifically decided to ignore, will cause the process to terminate.

    If the program which is being executed by execvp does set a signal handler for SIGPROF, then it will not be terminated.

    Update

    After seeing your comment, I thought I had better try your program. I added another branch to the if statement after waitpid, like this:

        waitpid(childPID,&status,0);
        if (WIFEXITED(status)) {
            fprintf(stderr, "Nice nice, the child exited ... with cPID = %d with status = %d \n", childPID, WEXITSTATUS(status) );
        } else if (WIFSIGNALED(status)) {
            fprintf(stderr, "Process pid=%d received signal %d\n",childPID,WTERMSIG(status));
        }
    

    When I run this, I see the following:

    $ ./watcher
    Process pid=1045 received signal 27
    

    This validates what I am saying above. I do not see the string "The alarm SIGPROF is here !" printed, and I do see an indication in the parent that the child was killed by signal 27, which is SIGPROF.

    I can only think of one scenario under which you would see the signal handler execute, and that would be if the timer is set so low that it fires before execv actually manages to load the new image. This does not look entirely likely though.

    Another possibility is that you have inadvertently installed the same signal handler in your target program (copy paste error?).

    Question 3

    Why does the dup2 placed here actually work and let's the child's input / output to be redirected ?

    I assume from the comments in the code that you mean "why does it work even though I have closed the original file descriptors immediately after dup2?"

    dup2 duplicates the old FD into the new FD, so after executing:

    dup2(outFileId, 1);
    

    you have two file descriptors referencing the same file description: the one contained in variable outFileId, and FD 1 (which is stdout). Also note that the original stdout will be closed by this action.

    File Descriptors are like references to an underlying file description data structure, which represents the open file. After calling dup2, there are two file descriptors pointing to the same file description.

    The man page for close says:

    If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed

    So it is working as it should: you still have one FD open, FD 1 (stdout), and the new child process can write to it.