Computer Systems Fundamentals

$ dcc exec.c
$ a.out
good-bye cruel world
$

#include <stdio.h>
#include <unistd.h>

// simple example of program replacing itself with exec
int main(void) {
    char *echo_argv[] = {"/bin/echo","good-bye","cruel","world",NULL};
    execv("/bin/echo", echo_argv);

    // if we get here there has been an error
    perror("execv");
    return 1;
}
$ dcc fork.c
$ a.out

I am the parent because fork() returned 2884551.
I am the child because fork() returned 0. $

#include <stdio.h>
#include <unistd.h>

int main(void) {

    // fork creates 2 identical copies of program
    // only return value is different

    pid_t pid = fork();

    if (pid == -1) {
         perror("fork");  // print why the fork failed
    } else if (pid == 0) {
        printf("I am the child because fork() returned %d.\n", pid);
    } else {
        printf("I am the parent because fork() returned %d.\n", pid);
    }

    return 0;
}
simple example of classic fork/exec run date --utc to print current UTC
use posix_spawn instead
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();

    if (pid == -1) {
         perror("fork"); // print why fork failed
    } else if (pid == 0) { // child

        char *date_argv[] = {"/bin/date", "--utc", NULL};

        execv("/bin/date", date_argv);

        perror("execvpe"); // print why exec failed

    } else { // parent

        int exit_status;
        if (waitpid(pid, &exit_status, 0) == -1) {
            perror("waitpid");
            exit(1);
        }
        printf("/bin/date exit status was %d\n", exit_status);
    }

    return 0;
}
simple example of system
#include <stdio.h>
#include <stdlib.h>

int main(void) {

    // system passes string to a shell for evaluation
    // brittle and highly vulnerable to security exploits
    // system is suitable for quick debugging and throw-away programs only
    // run date --utc to print current UTC
    int exit_status = system("/bin/date --utc");
    printf("/bin/date exit status was %d\n", exit_status);
    return 0;
}
$ dcc spawn.c
$ a.out

Tue 3 Nov 23:51:27 UTC 2022 /bin/date exit status was 0

simple example of posix_spawn run date --utc to print current UTC
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>

int main(void) {

    pid_t pid;
    extern char **environ;
    char *date_argv[] = {"/bin/date", "--utc", NULL};

    // spawn "/bin/date" as a separate process
    int ret = posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv, environ);
    if (ret != 0) {
        errno = ret;   //posix_spawn returns error code, does not set errno
        perror("spawn");
        exit(1);
    }

    // wait for spawned processes to finish
    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        exit(1);
    }

    printf("/bin/date exit status was %d\n", exit_status);
    return 0;
}
spawn ls -ld adding as argument the arguments we have been given
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    char *ls_argv[argc + 2];
    ls_argv[0] = "/bin/ls";
    ls_argv[1] = "-ld";
    for (int i = 1; i <= argc; i++) {
        ls_argv[i + 1] = argv[i];
    }

    pid_t pid;
    extern char **environ;
    int ret = posix_spawn(&pid, "/bin/ls", NULL, NULL, ls_argv, environ);
    if (ret != 0) {
        errno = ret;
        perror("spawn");
        return 1;
    }
    
    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        exit(1);
    }

    // exit with whatever status ls exited with
    return exit_status;
}
spawn ls -ld adding as argument the arguments we have been given
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char *ls = "/bin/ls -ld";
    int command_length = strlen(ls);
    for (int i = 1; i < argc; i++) {
        command_length += strlen(argv[i]) + 1;
    }

    // create command as string
    char command[command_length + 1];
    strcpy(command, ls);
    for (int i = 1; i <= argc; i++) {
        strcat(command, " ");
        strcat(command, argv[i]);
    }

    int exit_status = system(command);
    return exit_status;
}
print all environment variables
#include <stdio.h>

int main(void) {
    // print all environment variables
    extern char **environ;

    for (int i = 0; environ[i] != NULL; i++) {
        printf("%s\n", environ[i]);
    }
}
simple example of using environment variableto change program behaviour run date -to print time
Perth time printed, due to TZ environment variable
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>

int main(void) {
    pid_t pid;

    char *date_argv[] = { "/bin/date", NULL };
    char *date_environment[] = { "TZ=Australia/Perth", NULL };
    // print time in Perth
    int ret = posix_spawn(&pid, "/bin/date", NULL, NULL, date_argv,
              date_environment);  
    if (ret != 0) {
        errno = ret;
        perror("spawn");
        return 1;
    }

    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        return 1;
    }

    printf("/bin/date exit status was %d\n", exit_status);
    return 0;
}


$ dcc get_status.c -o get_status $ STATUS=ok ./get_status
Environment variable 'STATUS' has value 'ok' $

#include <stdio.h>
#include <stdlib.h>

// simple example of accessing an environment variable
int main(void) {
    // print value of environment variable STATUS
    char *value = getenv("STATUS");
    printf("Environment variable 'STATUS' has value '%s'\n", value);
    return 0;
}


$ dcc set_status.c -o set_status $ dcc get_status.c -o get_status $ ./set_status
Environment variable 'STATUS' has value 'great' $
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/wait.h>
#include <errno.h>

// simple example of setting an environment variable
int main(void) {

    // set environment variable STATUS
    setenv("STATUS", "great", 1);

    char *getenv_argv[] = {"./get_status", NULL};
    pid_t pid;
    extern char **environ;
    int ret = posix_spawn(&pid, "./get_status", NULL, NULL, 
    	      getenv_argv, environ);
    if (ret != 0) {
        errno = ret;
        perror("spawn");
        return 1;
    }  
    
    
    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        exit(1);
    }

    // exit with whatever status s exited with
    return exit_status;
}
simple example of use to popen to capture output from a process
#include <stdio.h>
#include <stdlib.h>

int main(void) {

    // popen passes string to a shell for evaluation
    // brittle and highly-vulnerable to security exploits
    // popen is suitable for quick debugging and throw-away programs only

    FILE *p = popen("/bin/date --utc", "r");
    if (p == NULL) {
        perror("");
        return 1;
    }

    char line[256];
    if (fgets(line, sizeof line, p) == NULL) {
        fprintf(stderr, "no output from date\n");
        return 1;
    }

    printf("output captured from /bin/date was: '%s'\n", line);

    pclose(p); // returns command exit status
    return 0;
}
simple example of use to popen to capture output
#include <stdio.h>
#include <stdlib.h>

int main(void) {

    // popen passes command to a shell for evaluation
    // brittle and highly-vulnerable to security exploits
    // popen is suitable for quick debugging and throw-away programs only
    //
    // tr a-z A-Z - passes stdin to stdout converting lower case to upper case

    FILE *p = popen("tr a-z A-Z", "w");
    if (p == NULL) {
        perror("");
        return 1;
    }

    fprintf(p, "plz date me - I know every SPIM system call\n");

    pclose(p); // returns command exit status
    return 0;
}
simple example using a pipe with posix_spawn to capture output from spawned process
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>

int main(void) {
    // create a pipe
    int pipe_file_descriptors[2];
    if (pipe(pipe_file_descriptors) == -1) {
        perror("pipe");
        return 1;
    }

    // create a list of file actions to be carried out on spawned process
    posix_spawn_file_actions_t actions;
    if (posix_spawn_file_actions_init(&actions) != 0) {
        perror("posix_spawn_file_actions_init");
        return 1;
    }

    // tell spawned process to close unused read end of pipe
    // without this - spawned process would not receive EOF
    // when read end of the pipe is closed below,
    if (posix_spawn_file_actions_addclose(&actions, pipe_file_descriptors[0]) != 0) {
        perror("posix_spawn_file_actions_init");
        return 1;
    }

    // tell spawned process to replace file descriptor 1 (stdout)
    // with write end of the pipe
    if (posix_spawn_file_actions_adddup2(&actions, pipe_file_descriptors[1], 1) != 0) {
        perror("posix_spawn_file_actions_adddup2");
        return 1;
    }

    pid_t pid;
    extern char **environ;
    char *date_argv[] = {"/bin/date", "--utc", NULL};
    if (posix_spawn(&pid, "/bin/date", &actions, NULL, date_argv, environ) != 0) {
        perror("spawn");
        return 1;
    }

    // close unused write end of pipe
    // in some case processes will deadlock without this
    // not in this case, but still good practice
    close(pipe_file_descriptors[1]);

    // create a stdio stream from read end of pipe
    FILE *f = fdopen(pipe_file_descriptors[0], "r");
    if (f == NULL) {
        perror("fdopen");
        return 1;
    }

    // read a line from read-end of pipe
    char line[256];
    if (fgets(line, sizeof line, f) == NULL) {
        fprintf(stderr, "no output from date\n");
        return 1;
    }

    printf("output captured from /bin/date was: '%s'\n", line);

    // close read-end of the pipe
    // spawned process will now receive EOF if attempts to read input
    fclose(f);

    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        return 1;
    }
    printf("/bin/date exit status was %d\n", exit_status);

    // free the list of file actions
    posix_spawn_file_actions_destroy(&actions);

    return 0;
}
simple example of using a pipe to with posix_spawn to sending input to spawned process
#include <stdio.h>
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>

int main(void) {
    // create a pipe
    int pipe_file_descriptors[2];
    if (pipe(pipe_file_descriptors) == -1) {
        perror("pipe");
        return 1;
    }

    // create a list of file actions to be carried out on spawned process
    posix_spawn_file_actions_t actions;
    if (posix_spawn_file_actions_init(&actions) != 0) {
        perror("posix_spawn_file_actions_init");
        return 1;
    }

    // tell spawned process to close unused write end of pipe
    // without this - spawned process will not receive EOF
    // when write end of the pipe is closed below,
    // because spawned process also has the write-end open
    // deadlock will result
    if (posix_spawn_file_actions_addclose(&actions, pipe_file_descriptors[1]) != 0) {
        perror("posix_spawn_file_actions_init");
        return 1;
    }

    // tell spawned process to replace file descriptor 0 (stdin)
    // with read end of the pipe
    if (posix_spawn_file_actions_adddup2(&actions, pipe_file_descriptors[0], 0) != 0) {
        perror("posix_spawn_file_actions_adddup2");
        return 1;
    }


    // create a process running /usr/bin/sort
    // sort reads lines from stdin and prints them in sorted order
    char *sort_argv[] = {"sort", NULL};
    pid_t pid;
    extern char **environ;
    if (posix_spawn(&pid, "/usr/bin/sort", &actions, NULL, sort_argv, environ) != 0) {
        perror("spawn");
        return 1;
    }

    // close unused read end of pipe
    close(pipe_file_descriptors[0]);

    // create a stdio stream from write-end of pipe
    FILE *f = fdopen(pipe_file_descriptors[1], "w");
    if (f == NULL) {
        perror("fdopen");
        return 1;
    }

    // send some input to the /usr/bin/sort process
    //sort with will print the lines to stdout in sorted order
    fprintf(f, "sort\nwords\nplease\nthese\n");

    // close write-end of the pipe
    // without this sort will hang waiting for more input
    fclose(f);

    int exit_status;
    if (waitpid(pid, &exit_status, 0) == -1) {
        perror("waitpid");
        return 1;
    }
    printf("/usr/bin/sort exit status was %d\n", exit_status);

    // free the list of file actions
    posix_spawn_file_actions_destroy(&actions);

    return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void) {
    // fork creates 2 identical copies of the program
    // only return value is different
    printf("This is the start of the program.\n");
    int p[2];
    if (pipe(p) == -1) { // reader reads from p[0]
        perror("pipe");  // writer writes to p[1]
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");  // print why the fork failed
        return 1;
    } else if (pid == 0) {
        close(p[0]); // Close unused read end in child
        FILE *f = fdopen(p[1], "w");
        if (f == NULL) {
            perror("fdopen");
            return 1;
        }
        printf("I am the child because fork() returned %d.\n", pid);
        fprintf(f, "hello parent\n");
        fclose(f);
        close(p[1]); // Close write end after use
    } else {
        close(p[1]); // Close unused write end in parent
        FILE *f = fdopen(p[0], "r");
        if (f == NULL) {
            perror("fdopen");
            return 1;
        }
        printf("I am the parent because fork() returned %d.\n", pid);
        char buf[256];
        if (fgets(buf, sizeof(buf), f) != NULL) {
            printf("I got this message from the child: %s", buf);
        } else {
            perror("fgets");
        }
        fclose(f);
        close(p[0]); // Close read end after use
        wait(NULL); // Wait for child process to terminate
    }

    return 0;
}