Project 1: Build Your Own Shell

  1. How to use readline library?
    /*
     * While compiling program utilizing readline library, you need to link it with
     * both readline and curses library.  For example:
     *
     * % g++ readline_example.cc -o readline -lreadline -lcurses
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <readline/readline.h>
    #include <readline/history.h>
    
    #define HISTORY_FILE    ".readline_history"
    
    int main(void) {
        char prompt[256];
        char history_file[256];
    
        // Construct the path of the history file
        snprintf(history_file, sizeof(history_file), "%s/%s",
    	     getenv("HOME"), HISTORY_FILE);
    
        // Read back previous input saved in the history file
        read_history(history_file);
    
        while (true) {
    	// Create a prompt (based on current time) dynamically
    	time_t now = time(NULL);
    	strftime(prompt, sizeof(prompt), "%H:%M:%S > ", localtime(&now));
    
    	// Call readline() to get input
    	char *line;
    	if ((line = readline(prompt)) == NULL) {
    	    // Ctrl-D has been just pressed
    	    break;
    	}
    
    	// Only add command into history if it's not blank
    	if (*line) {
    	    add_history(line);
    	}
    
    	// Determine the actions here
    	if (strcmp(line, "quit") == 0) {
    	    break;
    	}
    
    	free(line);
        }
    
        // Write all input back to the history file
        write_history(history_file);
    
        return (0);
    }
    
  2. How to display color on xterm?
    #include <stdio.h>
    
    #define FROM_COLOR  0
    #define TO_COLOR    50
    
    int main (void){
        for (int i = FROM_COLOR; i < TO_COLOR; i++) {
    	printf ("\033[%dm %d. Hello world! \033[m\n", i, i);
        }
        return 0;
    }
    
  3. How to use signal to catch SIGCHLD?
    #include <stdio.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <signal.h>
    
    /* Declare pid here so that handler() can access it */
    pid_t pid;
    
    /*
     * Exec ls 
     */
    void do_execvp(void) {
        char *argv[] = {
    	"ls",
    	"-l",
    	NULL
        };
    
        if (execvp("ls", argv) < 0) {
    	fprintf(stderr, "Failed to call execvp(): %s\n", strerror(errno));
        }
    }
    
    void handler(int signo) {
        switch (signo) {
        case SIGCHLD: {
    	int status = 0;
    	int wait_pid = waitpid(pid, &status, 0);
    	printf("PID %d exited with status %d\n", wait_pid, status);
    	break;
        }
        default:
    	fprintf(stderr, "Caught unknown signal: %d\n", signo);
    	break;
        }
    }
    
    int main(void) {
        signal(SIGCHLD, handler);
    
        switch (pid = fork()) {
        case -1:
    	/* Error */
    	fprintf(stderr, "Failed to call fork(): %s\n", strerror(errno));
    	return (1);
        case 0:
    	/* Child process */
    	do_execvp();
    	/* Should not reach here */
    	break;
        default:
    	/* Parent process */
    
    	/* No calling wait() here, instead, do it in the signal handler */
    	break;
        }
    
        return (0);
    }
    
  4. Must to call exit(errno) when fail to call execvp() in the child process.
        if (execvp(command, argv) < 0) {
    	fprintf(stderr, "Failed to execute %s: %s\n",
    	        command, strerror(errno));
    	exit(errno);
        }
    
  5. Running commands in the background is just running commands without calling wait(). But, you have to keep track of all the process ID running in the background and call waitpid(pid, NULL, WNOHANG) for each pid in the list before printing out the prompt.
  6. Alternatively, it might be easier to catch SIGCHLD signal to handle the exiting processes running in the background.
  7. Must use waidpid(pid, NULL, 0) to wait for immediate child process instead of using wait(NULL) after forking because wait(NULL) will wait for all child processes including those executed in the background!