パーソナルツール
現在の場所: ホーム オペレーティングシステム論 演習 2006年度 第3回 exsh-3.c
文書操作

exsh-3.c

作成者 管理者 最終変更日時 2006年07月27日 02時04分

Click here to get the file

サイズ 7.4 kB - File type text/x-csrc

ファイルのコンテンツ

/* 
 * $Id: exsh-3.c,v 1.19 2006/05/31 11:37:07 yamamoto Exp yamamoto $
 * 
 * (C) Copyright:       S.Yamamoto  2006
 *                      This file is a product of the project Exsh.
 */

#include <setjmp.h>             /* For setjmp() and longjmp(). */
#include <signal.h>             /* For sigaction(). */
#include <stdarg.h>             /* For variable argument lists. */
#include <stdio.h>
#include <stdlib.h>             /* For EXIT_SUCCESS. */
#include <string.h>             /* For strtok(). */
#include <sys/types.h>          /* For pid_t. */
#include <sys/wait.h>           /* For waitpid(). */
#include <unistd.h>             /* For execl() family. */

#ifndef FALSE
#define FALSE           0
#define TRUE            1
#endif

#define MAX_INPUT       81      /* 入力の最大長(NULL terminatorを含む). */

/* 引数の最大個数(コマンド名とNULL terminatorを含む). */
#define ARG_MAX         (1 + 8 +1)

#define JMP_SIGINT      1

static int          debug_mode = FALSE; /* デバッグモード. */
static char         input[MAX_INPUT];   /* 入力行. */
static sigjmp_buf   jmp;                /* setjump()とlongjmp()用. */

static void parse_option(int argc, char *argv[]);
static void set_signal_handler(void);

static int parse_input(char x[MAX_INPUT], char *a[ARG_MAX]);
static int read_command(char *a[ARG_MAX]);
static void terminate_zombie(void);

static void handler_sigint(int x);

static void debug(const char *format, ...);

int main(int argc, char *argv[])
{
    int         status;
    pid_t       pid;

    parse_option(argc, argv);           /* オプション解析. */
    set_signal_handler();               /* シグナルハンドラの登録. */

    debug("getpid() %d.\n", getpid());
    while (TRUE) {
        char    *args[ARG_MAX];         /* 解析済み入力. */
        int     num_args;               /* 引数の個数. */
        int     ret_setjmp;
        int     is_bg_job;              /* バックグラウンドジョブ(BG)か. */

        ret_setjmp = sigsetjmp(jmp, 1); /* スタックの保存. */
        debug("getpid() %d, setjmp(): %d.\n", getpid(), ret_setjmp);
        if (ret_setjmp != 0) {          /* longjmp()による復帰. */
            fputs("\n", stderr);
            continue;
        }

        terminate_zombie();             /* 終了した子プロセスの後始末. */

        num_args = read_command(args);  /* 入力. */
        debug("num_args %d.\n", num_args);
        if (args[0] == NULL) {
            continue;
        } else if (!strcmp(args[0], "cd")) {
            chdir(num_args == 1 ? getenv("HOME") : args[1]);
            continue;
        } else if (!strcmp(args[0], "exit")) {
            exit (EXIT_SUCCESS);
        }
        is_bg_job = !strcmp(args[num_args - 1], "&");   /* バックグラウンドの判定. */
        
        pid = fork();                   /* プロセスをforkする(分身の術). */
        if (pid < 0) {                  /* fork()に失敗. */
            perror(NULL);
        } else if (pid == 0) {          /* 子プロセス. */
            int     retval;
            
            if (is_bg_job) {            /* 最後の"&"を除去. */
                args[num_args - 1] = NULL;
            }
            debug("Child: getpid() %d, fork() %d.\n", getpid(), pid);
            retval = execvp(args[0], args);     /* コマンド実行(変身の術). */
            perror(args[0]);            /* execvp()に失敗. */  
            exit(EXIT_FAILURE);
        } else {                        /* 親プロセス. */
            debug("Parent: getpid() %d, fork() %d.\n", getpid(), pid);
            
            /* BGならなら次の入力へ,FGなら子プロセスの終了を待つ. */
            if (is_bg_job) {
                fprintf(stderr, "Backgroud job %d\n", pid);
            } else {                
                waitpid(pid, &status, 0);
            }
        }
    }

    exit (EXIT_SUCCESS);
}

/* 
 * オプション解析.
 */

static void parse_option(int argc, char *argv[])
{
    int     ch;

    while ((ch = getopt(argc, argv, "d")) != -1) {
        switch (ch) {
          case 'd':                         /* Debug mode. */
            debug_mode = TRUE;
            break;
          default:
            fprintf(stderr, "'%c': Invalid option.\n", ch);
            exit(EXIT_FAILURE);
            break;
        }
    }
}

/*
 * シグナルハンドラを設定する.
 */

static void set_signal_handler(void)
{
    static struct sigaction    action;

    action.sa_handler = handler_sigint;         /* シグナルハンドラ. */

    /* ハンドラ内でSIGINTをブロックする. */
    sigemptyset(&action.sa_mask);               /* マスクのクリア. */  
    sigaddset(&action.sa_mask, SIGINT);

    action.sa_flags = SA_RESTART;               /* BSDとの互換性(おまじない). */

    sigaction(SIGINT, &action, NULL);           /* SIGINTに設定.*/
}

/* 
 * 終了済み子プロセスの後始末.
 */

static void terminate_zombie(void)
{
    int         status;
    pid_t       child_pid;
            
    while (0 < (child_pid = waitpid(-1, &status, WNOHANG))) {
        fprintf(stderr, "Terminated process %d with return code %d.\n",
                child_pid, WEXITSTATUS(status));
    }
}

/* 
 * 1行入力し,空白で引数に分離する.
 * 
 * 戻り値:
 * 引数の個数(コマンド名を含む).
 * 
 * 分離された引数.
 *     a[0]: コマンド名.
 *     a[1] ... a[n]: 引数.
 *     a[n + 1]: NULL.
 * 
 * 入力が空行の場合,a[0]はNULLとなる.
 * 
 * !! quoteなどを処理する必要がある.
 * !! 1行がMAX_INPUTよりも長いと,分離されて次の入力となってしまう.
 */

static int read_command(char *a[ARG_MAX])
{
    char    *cp;

    if (isatty(fileno(stdin))) {        /* 端末の場合だけプロンプト出力. */
        fputs("> ", stderr);
    }

    fgets(input, MAX_INPUT, stdin);     /* 1行入力. */
    if ((cp = strchr(input, '\n')) != NULL) {       /* 改行除去. */
        *cp = '\0';
    }

    if (feof(stdin)) {                  /* EOF (end of file)状態の場合. */
        exit(EXIT_SUCCESS);
    }
    debug("\"%s\".\n", input);

    return (parse_input(input, a));
}

/* 
 * 入力行を空白で引数に分離する.
 */

static int parse_input(char x[MAX_INPUT], char *a[ARG_MAX])
{
    int     i = 0;

    a[i] = strtok(x, " ");
    debug("a[%d] \"%s\".\n", i, a[i]);
    i++;
    while (a[i - 1] != NULL) {
        if (ARG_MAX <= i) {
            fprintf(stderr, "Too many arguments.\n");
            a[0] = NULL;
            break;
        }
        a[i] = strtok(NULL, " ");
        debug("a[%d] \"%s\".\n", i, a[i]);
        i++;
    }
    return (i - 1);     /* Not including a terminator. */
}

/* 
 * SIGINTのシグナルハンドラ. 
 */

static void handler_sigint(int x)
{
    debug("Catch signal SIGINT.\n");
    siglongjmp(jmp, JMP_SIGINT);
}

/* 
 * 可変個の引数を持つデバッグ出力.
 * 
 * C99なら可変個の引数を持つマクロも使用可能.
 * #define debug(format, ...) \
 *         if (debug) fprintf(stderr, "Debug: " format, __VA_ARGS__)
 */

static void debug(const char *format, ...)
{
    va_list     args;
    
    if (!debug_mode) {
        return;
    }

    fflush(stdout);
    fflush(stderr);

    fputs("Debug: ", stderr);
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);

    fflush(stderr);
}