Operating Systems 2026F: Assignment 2

From Soma-notes

Please submit the answers to the following questions via Brightspace by October 16, 2026. There are 20 points in 9 questions.

Submit your answers as a plain text file following this template. Name your answer file "comp3000-a2-<username>.txt" (where username is your MyCarletonOne username). Please make sure to use your correct student ID number otherwise your grades may not be properly recorded.

Your answers will be parsed by a script in order to help with grading so please preserve the format of the template. No other formats will be accepted.

Note that the file should be a UNIX text file, not a Windows text file (so LF line endings). See the Wikipedia page on text files to learn more. Most modern text editors can convert between different types of text files. Alternately, install the program dos2unix on the VM to convert.

You may use this validator page to make sure your answer file is properly named and formatted.

Don't forget to include what outside resources you used to complete each of your answers, including other students, man pages, and web resources. You do not need to list help from the instructor, TA, or information found in the textbook.

Questions

Please answer the following questions about 3000menu2.c. Note that your code changes should make use of existing program functionality and should require adding as little code as possible. Solutions that implement functionality from scratch where not needed will not receive credit.

  1. [1] What menu options do not require running an external program? How do you know?
  2. [2] What does 3000menu2 do with environment variables? Explain
  3. [2] When 3000menu2 runs a program in the background, when does it find out the program has finished? When does it report this event to the user?
  4. [3] One menu option does not work well when run in the background. Which one? What exactly happens? And why?
  5. [2] Does the functionality of 3000menu2 change depending upon the ownership and privileges of its binary? How do you know?
  6. [2] How could you change 3000menu2 to make the change user functionality work? Will this functionality always work? Explain.
  7. [2] How could you change 3000menu2's change user functionality (once you've gotten it working) so it can accept both user IDs as well as usernames?
  8. [2] How could you change 3000menu2 so it had the option to redirect the output of a program to a file? It should ask "Redirect output to file (blank for none):" after asking whether to run the program in the background.
  9. [4] How could you change 3000menu2 so it had the option to redirect the input of a program to a file? It should ask "Redirect input from a file (blank for none):" after asking whether to run the program in the background but before redirecting output.

Code

Download 3000menu2.c

/* 3000menu2.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>

#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
#define COMM_SIZE 32

char *menu[] = {
        "Change user",
        "pinfo",
        "/usr/bin/ls",
        "/usr/bin/ps",
        "/usr/bin/id",
        "/usr/bin/top",
        "QUIT",
        NULL
};
        
pid_t process_exited = 0;
int process_exited_status = 0;
char *become_user = NULL;

extern char **environ;

void make_args(char *program, char *buffer, char **args, 
                size_t args_size, size_t *nargs)
{
        char *maybe_args[args_size];
        char **next_arg, *rest_of_buffer;
        size_t i, j;
        
        rest_of_buffer = buffer;
        maybe_args[0] = program;
        args[0] = program;

        for (next_arg = maybe_args + 1;
             (*next_arg = strsep(&rest_of_buffer, " \n\t")) != NULL;
             next_arg++) {
                
                if (*next_arg != NULL) {
                        if (next_arg >= &maybe_args[args_size])
                                break;
                } 
        }

        j = 0;
        for (i=0; maybe_args[i] != NULL; i++) {
                if (strlen(maybe_args[i]) > 0) {
                        args[j] = maybe_args[i];
                        j++;
                }
        }
        
        *nargs = j;
        args[j] = NULL;
}

void pinfo(void)
{
        printf("uid=%d, euid=%d, gid=%d, egid=%d\n",
               getuid(), geteuid(), getgid(), getegid());
}

int become(char *username)
{
        int result;
        struct passwd *pw_entry;

        pw_entry = getpwnam(username);
        if (pw_entry == NULL) {
                fprintf(stderr, "Could not find user %s.\n", username);
                return -1;
        }
                       
        result = setgid(pw_entry->pw_gid);
        if (result != 0) {
                fprintf(stderr, "Failed to change to gid %d\n",
                        pw_entry->pw_gid);
                return -2;
        }

        result = setuid(pw_entry->pw_uid);
        if (result != 0) {
                fprintf(stderr, "Failed to change to uid %d\n",
                        pw_entry->pw_uid);
                return -3;
        }

        return 0;
}

void run_program(char *args[], int background, char *stdout_fn,
                 char *envp[], char *new_user)
{
        pid_t pid;
        int result;
        int fd;

        pid = fork();
        if (pid) {
                if (background) {
                        fprintf(stderr,
                                "Process %d running in the background.\n",
                                pid);
                        return;
                } else {
                        pid = wait(NULL);
                }
        } else {
                if (new_user) {
                        result = become(new_user);
                        if (result != 0) {
                                fprintf(stderr, "Error becoming %s, aborting\n",
                                        new_user);
                                return;
                        }
                } else {
                        if (setegid(getgid())) {
                                fprintf(stderr,
                                        "Dropping of group privileges failed.\n");
                        }
                        if (seteuid(getuid())) {
                                fprintf(stderr,
                                        "Dropping of user privileges failed.\n");
                        }
                }
                
                if (stdout_fn != NULL) {
                        fd = creat(stdout_fn, 0666);
                        dup2(fd, 1);
                        close(fd);
                }
                
                if (execve(args[0], args, envp)) {
                        puts(strerror(errno));
                        exit(127);
                }
        }
}

void run_chosen_program(int choice)
{
        char *input = NULL;
        char *all_args[ARR_SIZE];
        int result;
        int background = 0;
        size_t n, nargs;
        char *program = menu[choice];
        
        printf("Run in background (Y/N)? ");
        result = getline(&input, &n, stdin);

        if (strcmp(input, "Y\n") == 0) {
                background = 1;
        }

        free(input);
        input = NULL;
        
        printf("Enter arguments for %s: ", menu[choice]);
        result = getline(&input, &n, stdin);

        if (result > 0) {
                make_args(program, input, all_args, ARR_SIZE, &nargs);
        } else {
                all_args[0] = program;
                all_args[1] = '\0';
        }
        
        if (background) {
               printf("Running %s in the background\n", program);
        } else {
               printf("Running %s\n", program);
        }
        
        run_program(all_args, background, NULL, environ, become_user);
                
        return;
}

int choose_program(void)
{
        int i = 0;
        int choice;
        char *input = NULL;
        size_t result, n = 0;

        printf("\nChoose an option:\n\n");
        
        while (menu[i] != NULL) {
                printf("%d. %s\n", i + 1, menu[i]);
                i++;
        }

        if (become_user) {
                printf("Running programs as user %s.\n", become_user);
        } else {
                printf("Running programs as you.\n");
        }

        printf("\nYour choice? ");

        result = getline(&input, &n, stdin);
        choice = atoi(input);
        free(input);

        if ((result > 0) && (choice > 0) && (choice <= i)) {
                return choice - 1;
        } else {
                return -1;
        }
}

void signal_handler(int the_signal)
{
        int pid, status;

        if (the_signal != SIGCHLD) {
                return;
        }

        pid = wait(&status);

        if (pid == -1) {
                return;
        }

        process_exited = pid;
        process_exited_status = WEXITSTATUS(status);
}

void setup_signal_handlers()
{
       struct sigaction signal_handler_struct;

        memset (&signal_handler_struct, 0, sizeof(signal_handler_struct));
        signal_handler_struct.sa_handler = signal_handler;
        signal_handler_struct.sa_flags = SA_RESTART;
        
        if (sigaction(SIGCHLD, &signal_handler_struct, NULL)) {
                fprintf(stderr, "Couldn't register SIGCHLD handler.\n");
        }
}

void handle_exited_process(void)
{
        if (process_exited) {
                if (WIFEXITED(process_exited_status)) {
                        fprintf(stderr, "\nProcess %d exited with status %d.\n",
                                process_exited,
                                WEXITSTATUS(process_exited_status));
                } else {
                        fprintf(stderr, "\nProcess %d aborted.\n",
                                process_exited);
                }

                process_exited = 0;
        }
}

void change_user(void)
{
        printf("Should change the user.");
}

void choice_loop(void)
{
        int choice;

        while (1) {
                handle_exited_process();
                
                choice = choose_program();

                if (choice == -1) {
                        printf("Invalid choice. Please try again.\n");
                } else if (strcmp("QUIT", menu[choice]) == 0) {
                        printf("Goodbye!\n");
                        return;
                } else if (strcmp("Change user", menu[choice]) == 0) {
                        change_user();
                } else if (strcmp("pinfo", menu[choice]) == 0) {
                        pinfo();
                } else {
                        run_chosen_program(choice);
                }
        }
}

int main()
{
        setup_signal_handlers();
        choice_loop();

        return 0;
}