Operating Systems 2022F: Assignment 2

From Soma-notes

Please submit the answers to the following questions via Brightspace by October 11, 2022 by 11:30 AM. There are 20 points in 10 questions.

Submit your answers as a plain text file following this template. Name your answer file "comp3000-assign2-<username>.txt" (where username is your MyCarletonOne username).

Don't forget to include what outside resources you used to complete each of your answers, including other students, man pages, and web resources. Other students should be listed on the "Collaborators:" line, while other resources should be listed at the end of each question. You do not need to list help from the instructor (from lectures, tutorials, or other communication), TAs, man pages, or information found in the textbook.

If you submission:

  • Doesn't pass the validator,
  • Is not named "comp3000-assign2-myname" where myname is your MyCarletonOne username, or
  • Does not contain your Student ID number in the designated part of the template

Then your submission will automatically get a grade of zero, because it won't work with the submission scripts. You have been warned!

Questions

  1. [2] What internal commands does 3000shell2.c support? Are these the same as those supported by 3000shell.c? Explain briefly.
  2. [2] If you build 3000shell2.c the same as you did 3000shell.c (from Tutorial 3, the given gcc command), what does the command "pinfo" report? What about if you build it as you did 3000userlogin.c (from Tutorial 4, the Makefile)? Explain any differences you see.
  3. [2] What is the purpose of lines 188 and 189 (the calls to seteuid() and setegid())? Do they always do something useful?
  4. [1] What is the syntax of the "become" command?
  5. [1] What must be true for the "become" command to work?
  6. [2] Modify the "become" command so it takes a uid, not just a username.
  7. [2] How does the behavior of 3000shell2 change when it is run setuid root versus running as root? Why?
  8. [2] Does output redirection work for any of the internal commands? Explain briefly.
  9. [4] Create a command "background" that runs a program in the background, redirecting standard in, out, and error. background takes at least four arguments:
    • a file to be opened for standard input,
    • a file to be opened for standard output (erasing any existing file and creating it if necessary),
    • a file to be opened for standard error (erasing any existing file and created it if necessary), and
    • a program to run (either an absolute path or a program in the current PATH) with zero or more arguments.

    Note that background should ignore any > or & characters included.

  10. [2] How can you ensure that the new "background" can be combined with the "become" command? Why does this work?

Code

Download 3000shell2.c

/* 3000shell2.c */

/* variant of 3000shell.c from here:
   https://homeostasis.scs.carleton.ca/~soma/os-2019f/code/3000shell.c
 */
   
/* This version is under GPLv3, copyright 2022, Anil Somayaji */
/* You really shouldn't be incorporating parts of this in any other code,
   it is meant for teaching, not production */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>

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

const char *proc_prefix = "/proc";

void parse_args(char *buffer, char** args, 
                size_t args_size, size_t *nargs)
{
        char *buf_args[args_size]; /* You need C99 */
        char **cp, *wbuf;
        size_t i, j;
        
        wbuf=buffer;
        buf_args[0]=buffer; 
        args[0] =buffer;
        
        for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
                if ((*cp != NULL) && (++cp >= &buf_args[args_size]))
                        break;
        }
        
        for (j=i=0; buf_args[i]!=NULL; i++){
                if (strlen(buf_args[i]) > 0)
                        args[j++]=buf_args[i];
        }
        
        *nargs=j;
        args[j]=NULL;
}

void 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);
                exit(-2);
        }
                       
        result = setgid(pw_entry->pw_gid);
        if (result != 0) {
                fprintf(stderr, "Failed to change to gid %d\n",
                        pw_entry->pw_gid);
                exit(-3);
        }

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

void find_binary(char *name, char *path, char *fn, int fn_size) {
        char *n, *p;
        int r, stat_return;

        struct stat file_status;

        if (name[0] == '.' || name[0] == '/') {
                strncpy(fn, name, fn_size);
                return;
        }
        
        p = path;
        while (*p != '\0') {       
                r = 0;
                while (*p != '\0' && *p != ':' && r < fn_size - 1) {
                        fn[r] = *p;
                        r++;
                        p++;
                }

                fn[r] = '/';
                r++;
                
                n = name;
                while (*n != '\0' && r < fn_size) {
                        fn[r] = *n;
                        n++;
                        r++;
                }
                fn[r] = '\0';

                
                stat_return = stat(fn, &file_status);

                if (stat_return == 0) {
                        return;
                }

                if (*p != '\0') {
                        p++;
                }
        }
}

void setup_comm_fn(char *pidstr, char *comm_fn)
{
        char *c;

        strcpy(comm_fn, proc_prefix);
        c = comm_fn + strlen(comm_fn);
        *c = '/';
        c++;
        strcpy(c, pidstr);
        c = c + strlen(pidstr);
        strcpy(c, "/comm");
}

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

        if (the_signal == SIGHUP) {
                fprintf(stderr, "Received SIGHUP.\n");
                return;
        }
        
        if (the_signal != SIGCHLD) {
                fprintf(stderr, "Child handler called for signal %d?!\n",
                        the_signal);
                return;
        }

        pid = wait(&status);

        if (pid == -1) {
                /* nothing to wait for */
                return;
        }
        
        if (WIFEXITED(status)) {
                fprintf(stderr, "\nProcess %d exited with status %d.\n",
                        pid, WEXITSTATUS(status));
        } else {
                fprintf(stderr, "\nProcess %d aborted.\n", pid);
        }
}

void run_program(char *args[], int background, char *stdout_fn,
                 char *path, char *envp[], char *new_user)
{
        pid_t pid;
        int fd, *ret_status = NULL;
        char bin_fn[BUFFER_SIZE];

        pid = fork();
        if (pid) {
                if (background) {
                        fprintf(stderr,
                                "Process %d running in the background.\n",
                                pid);
                } else {
                        pid = wait(ret_status);
                }
        } else {
                if (new_user) {
                        become(new_user);
                } else {
                        setegid(getgid());
                        seteuid(getuid());
                }
                
                find_binary(args[0], path, bin_fn, BUFFER_SIZE);

                if (stdout_fn != NULL) {
                        fd = creat(stdout_fn, 0666);
                        dup2(fd, 1);
                        close(fd);
                }
                
                if (execve(bin_fn, args, envp)) {
                        puts(strerror(errno));
                        exit(127);
                }
        }
}

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

void prompt_loop(char *username, char *path, char *envp[])
{
        char buffer[BUFFER_SIZE];
        char *all_args[ARR_SIZE];
        char **args;
        
        int background;
        size_t nargs;
        char *s;
        int i, j;
        char *stdout_fn, *new_user;
        
        while(1){
                printf("%s $ ", username);
                s = fgets(buffer, BUFFER_SIZE, stdin);
                
                if (s == NULL) {
                        /* we reached EOF */
                        printf("\n");
                        exit(0);
                }
                
                parse_args(buffer, all_args, ARR_SIZE, &nargs); 
                
                if (nargs==0) continue;

                args = all_args;
                
                if (!strcmp(args[0], "exit")) {
                        exit(0);
                }
                
                if (!strcmp(args[0], "pinfo")) {
                        pinfo();
                        continue;
                }

                new_user = NULL;                       
                if (!strcmp(args[0], "become")) {
                        if (nargs > 2) {
                                new_user = args[1];
                                args = args + 2;
                                nargs = nargs - 2;
                        } else {
                                fprintf(stderr,
                                        "ERROR: become needs a username "
                                        "and a command\n");
                        }
                }                
                
                background = 0;            
                if (strcmp(args[nargs-1], "&") == 0) {
                        background = 1;
                        nargs--;
                        args[nargs] = NULL;
                }

                stdout_fn = NULL;
                for (i = 1; i < nargs; i++) {
                        if (args[i][0] == '>') {
                                stdout_fn = args[i];
                                stdout_fn++;
                                printf("Set stdout to %s\n", stdout_fn);
                                for (j = i; j < nargs - 1; j++) {
                                        args[j] = args[j+1];
                                }
                                nargs--;
                                args[nargs] = NULL;
                                break;
                        }
                }
                
                run_program(args, background, stdout_fn, path, envp, new_user);
        }    
}

int main(int argc, char *argv[], char *envp[])
{
        struct sigaction signal_handler_struct;
                
        char *username;
        char *default_username = "UNKNOWN";
        
        char *path;
        char *default_path = "/usr/bin:/bin";
        
        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");
        }
        
        if (sigaction(SIGHUP, &signal_handler_struct, NULL)) {
                fprintf(stderr, "Couldn't register SIGHUP handler.\n");
        }
        
        username = getenv("USER");
        if (!username) {
                username = default_username;
        }

        path = getenv("PATH");
        if (!path) {
                path = default_path;
        }

        prompt_loop(username, path, envp);
        
        return 0;
}

Solutions

Assignment 2 solutions