ファイルのコンテンツ
/*
* $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);
}