COMP 3000 2026F Carleton University Assignment 2 Solutions ---------------------- 1. [1] What menu options do not require running an external program? How do you know? A: The first two options, "Change user" and "pinfo", do not require running an external program. We know this because choice_loop() calls change_user() and pinfo(), respectively, for these options; the other options call run_chosen_program() which eventually does an execve in run_program.() 2. [2] What does 3000menu2 do with environment variables? Explain A: It basically does nothing with environment variables. It gets access to them through the external declaration environ, and then it passes them on to programs that are execve'd by run_program(). Thus it doesn't directly read them or write to them. 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? A: 3000menu2 finds out a program finishes when its child process running the program terminates and the kernel sends the parent 3000menu2 process a SIGCHLD signal, causing signal_handler() to run. This function just stores information on the terminated process in the process_exited and process_exited_status global variables. handle_exited_process() reports to the user that a process has exited, and it is called at the start of each loop in choice_loop(), just before choose_program() is called to display the menu and get the user's choice. Thus the user only finds out that a child process has terminated just before choosing a new program; if a program is running in the foreground for a while, the reporting of the terminated process could be delayed arbitrarily, even though the parent process has received word of it terminating. (If multiple child processes terminate before being reported to the user, only the most recent one will be reported as the past ones will get overwritten by signal_handler().) 4. [3] One menu option does not work well when run in the background. Which one? What exactly happens? And why? A: Option 6, top, does not work well when run in the background, because every second it keeps sending its output to the screen, obscuring the menu and the output of any other program, changing where text appears and how it appears from those programs, and it is difficult to give top any commands, including the quit command. This problem happens because both top and 3000menu2 are accessing the terminal device at the same time (/dev/pts/?? generally) and thus are interfering with each other. 5. [2] Does the functionality of 3000menu2 change depending upon the ownership and privileges of its binary? How do you know? A: The original version of 3000menu2 doesn't change its behavior based on the ownership and privileges of its binary because the change_user() function has not been implemented. (If you hardcode a username into the global become_user variable then this functionality will only work when the program is run as root.) 6. [2] How could you change 3000menu2 to make the change user functionality work? Will this functionality always work? Explain. A: Below is an implementation of change_user(). It uses getline() to get the new username, and takes care of managing the malloc'd string and resetting the saved username if no name is entered. Note that the string returned by getline() returns the delimiter as well (the newline that is entered at the end of input). This functionality will only work, in that programs will be run as the entered user, if the program binary is run as root, either by being root (through sudo, su, or other means) or if the binary is setuid root. void change_user(void) { size_t result, n; if (become_user) { free(become_user); become_user = NULL; } printf("\nNew user? "); result = getline(&become_user, &n, stdin); if (result > 2) { become_user[result-1] = '\0'; printf("Will run programs as %s.\n", become_user); } else { printf("No user specified, become user reset.\n"); free(become_user); become_user = NULL; } } 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? A: We can change the become() function to test whether the username is a number, and if it is, treating it as a uid by calling getpwuid() rather than getpwnam(). Below is a modified version of become(). #include int become(char *username) { int result; struct passwd *pw_entry; /* new code, replacing call to getpwnam() */ uid_t uid; char *user_or_uid; if (username && strlen(username) > 0 && isdigit(username[0])) { uid = atoi(username); pw_entry = getpwuid(uid); user_or_uid = "uid"; } else { pw_entry = getpwnam(username); user_or_uid = "user"; } /* end new code */ if (pw_entry == NULL) { /* fprintf changed to distinguish between uid and username */ fprintf(stderr, "Could not find %s %s.\n", user_or_uid, 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; } 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. A: run_program() already has support for redirecting standard out, so we just need to get the right filename in run_chose_program() and pass it on. The modified version of run_chosen_program() is below with comments indicating what was added or changed. 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]; /* added to store output file */ char *output_fn = NULL; printf("Run in background (Y/N)? "); result = getline(&input, &n, stdin); if (strcmp(input, "Y\n") == 0) { background = 1; } free(input); input = NULL; /* Added to get output file */ printf("Redirect output to file (blank for none): "); result = getline(&input, &n, stdin); if (result > 1) { input[result - 1] = '\0'; output_fn = input; } else { free(input); } input = NULL; /* end output file addition */ 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); } /* changed NULL to output_fn */ run_program(all_args, background, output_fn, environ, become_user); /* need to free output_fn if getline() allocated it */ if (output_fn) { free(output_fn); } return; } 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. A: To redirect input, we need to get the file to redirect input from and we also need to change run_program() to support this, as it originally does not. Fortunately we can basically duplicate the output redirection functionality to redirect input. First, here is the modified run_chosen_program(): 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]; /* added to store input and output file */ char *input_fn = NULL; char *output_fn = NULL; printf("Run in background (Y/N)? "); result = getline(&input, &n, stdin); if (strcmp(input, "Y\n") == 0) { background = 1; } free(input); input = NULL; /* Added to get input and output files */ printf("Redirect input to file (blank for none): "); result = getline(&input, &n, stdin); if (result > 1) { input[result - 1] = '\0'; input_fn = input; } else { free(input); } input = NULL; printf("Redirect output to file (blank for none): "); result = getline(&input, &n, stdin); if (result > 1) { input[result - 1] = '\0'; output_fn = input; } else { free(input); } input = NULL; /* end input and output file addition */ 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); } /* changed NULL to input_fn and output_fn */ run_program(all_args, background, input_fn, output_fn, environ, become_user); /* changed to deallocate filename inputs */ if (input_fn) { free(input_fn); } if (output_fn) { free(output_fn); } return; } Then, for run_program, we change the declaration: void run_program(char *args[], int background, char *stdin_fn, char *stdout_fn, char *envp[], char *new_user) And finally we add the following code just before handling of stdout_fn: if (stdin_fn != NULL) { fd = open(stdin_fn, O_RDONLY); dup2(fd, 0); close(fd); }