/tmp

[과제]고급프로그래밍. 나만의 쉘 만들기

잊어버린 과거

나만의 쉘 만들기라고해서 작년에 했던 과제다. 지금보면 참 엉성하고 무식하게 프로그램을 짰다는 생각을 한다. 실제 아주 기본적인 쉘 프로그램이 주어졌을 때 세가지의 추가적인 기능을 넣는 것이 과제였다. 필요한 분들은 그냥 가져다 써도 좋다. 소스코드까지 다 있으니 가져다 쓰기 좋다.

 

작동은 당연히 운영체제가 유닉스 or 리눅스 계열에서 가능하다.

 

 

[고급프로그래밍 과제] : 나만의 쉘 만들기 | 2012/10/15(월) 제출

컴퓨터공학과 2010151035 장용하

 

 

1. Design

  1.1 Data Structure Modeling


  쉘 프로그램에서 가장 중요한 자료구조는 입력한 문자열을 담는 역할을 하는 배열입니다. 여기서는 makeargv()함수를 이용하여 입력된 문자열들을 체계적으로 정리합니다. 아래 <그림 1>에서 이와같은 일이 어떤 방식으로 일어나는지 나타내고 있습니다.


 

 

<그림 1>


  위 그림은 makeargv()함수의 결과물입니다. 먼저 우측 하단의 박스들이 입력된 문자열을 의미합니다. 이 문자열은 구분자(띄어쓰기, 엔터 등)로 인해 여러 부분으로 나뉘게 됩니다. 위 그림에서는 \0가 구분자의 역할을 하고 있는 모습을 보이고 있습니다. 이 구분자를 기준으로 문자열을 나누게 되고 이렇게 나눠진 문자열들의 첫 부분이 포인터에 의해 가리켜지게 되고 마지막으로 이 포인터들의 집합의 첫 부분을 또 다른 포인터가 가리키게 되어 체계적으로 입력된 문자열을 정리하게 됩니다.

  이를위해 makeargv()함수는 내부적으로, 문자열을 토큰으로 나누는 역할을 하는 strtok()함수를 이용하고 있습니다.





  1.2 Operation Modeling


  제가 만든 쉘에서 존재하는 직 간접적으로 쉘 프로그램을 동작시키는데 실행되는 핵심 함수(기능)들을 나열해보면 다음과 같이 요약될 수 있습니다.


  [1]. executecmd() : 쉘 프로그램에서 문자열을 입력하면 이를 바탕으로 시스템명령을 실행하는 함수. 기본적으로 파이프라인(|)이 포함된 문자열에 대한처리능력도 가지고 있습니다. 이하 [2], [3] 함수를 직접적으로호출합니다.

  [2]. makeargv() : 쉘 프로그램에서 입려된 문자열을 포인터배열로 정의하는 함수.

  [3]. executeredirect() : executecmd()의 직접적인 호출을 받으며 재지향(‘<’, ‘>’)에대한 처리능력이 있는 함수. 이를 위해 이하 [4],[5] 함수를 직접적으로 호출합니다.

  [4]. parseandredirectin() : 재지향기호 ‘<’를 찾아 처리하는 능력이 있는 함수

  [5]. parseandredirectout() : 재지향기호 ‘>’를 찾아 처리하는 능력이 있는 함수




 1.2.1 Definition of Major Operations


  ■ executecmd

    형  식 : void executecmd(char *cmds)

    리턴값 : 없음

    설  명 : makeargv로 입력된 문자열들을 (파이프 문자열(|)을 기준으로)정렬한 후 각 문자열 부분마다 파이프와 자식프로세스를 생성한 뒤 마지막 명령을 제외한각 문자열의 표준 출력을 다음 명령의 표준 입력으로 재지향 합니다. 이 재지향을 통해 부모의 출력을 자식이 받아 문자열을 처리할 수 있게 되어있습니다. 그러나 파이프를 사용하지않는 문자열은 위의 절차 없이 바로 executeredirect()를 호출합니다. 


  ■ makeargv

    형  식 : int makeargv(const char *s, const char *delimiters, char ***argvp)

    리턴값 : 성공시 토큰 수, 실패시 -1

    설  명 : delimiter를 구분자로하여, 문자열 s로부터 argvp가 가리키는 곳에 인자 배열을 만듭니다. 내부적으로 strtok함수를 호출하여 문자열에 대한 토큰 수를 계산한뒤 이 토큰수를 이용하여 배열의 크기를 할당하고 이어 각각에 토큰에 대한 포인터를 만드는 작업등을 합니다.


  ■ executeredirect

    형  식 : void executeredirect(char *s, int in, int out)

    리턴값 : 없음

    설  명 : 파라미터 in이 0이 아니면 표준입력이 재지향되는 것을 허용하고, 파라미터 out이 0이 아니면 표준출력이 재지향되는것을 허용합니다. 이것을 바탕으로 exevp명령을 통하여 시스템 명령어를 수행합니다.



 1.2.2 Definition of Minor Operations


  위에서 언급한 재지향과 파이프라인 이외에도 제가 작성한 쉘 프로그램은 여러 가지 기능들을 포함하고 있습니다. 포그라운드 상태의 시그널처리를위한 sigaction()함수, siglongjmp()함수와 SIGINT, SIGOUT시그널 활용, & 기호에의한 백그라운드 프로세스 처리, waitpid()함수를 이용 좀비 백그라운드 처리가 이에 해당합니다.




 1.3 Flow-chart Modeling

    

 

    

<그림 2>


위 <그림 2>는 전체적인 쉘 프로그램의 실행흐름을 나타냅니다. 대표적인 작업은 사각형 박스 내에 이름을 붙여놓았고 그 외의 작업들은 화살표 속에 있다고 생각하시면 됩니다. 이러한 규칙으로 보면 main()으로 들어오는 화살표는 쉘 프로그램의 실행을 의미하는 것 입니다. 중간에 fork()가 있는 이유는 자식 프로세스를 생성하여 명령어를 실행하도록 하였기 때문입니다.



 1.4 my operations for shell program


   1.4.1 cd(Change Directory) 기능

    

  쉘 프로그램에서 작업 디렉토리를 바꿀 수 없다는 것은 상상할 수 없었습니다. 이는 execvp명령에서 할 수 없는 명령이었으며, chdir()함수를 이용하여 따로 구현하였습니다. 더불어 system(“pwd")명령을 실행함으로 인해 바뀐 작업 디렉토리를 추가조치없이 곧바로 확인할 수 있도록 하였습니다.



   1.4.2 IPC기법을 이용한 rhistory(Real History) 기능


  보통 일반적인 shell의 history기능은 모든 입력한 문자열에 대해 기록을 남기지만, 여기서 구현한 rhistory기능은 실제 실행가능한 명령들에 대해서만 기록을 합니다. 이 쉘 프로그램에서는 자식 프로세스가 실제 명령을 실행하기에 부모 프로세스에게 해당 명령이 성공했는지 실패했는지에 대한 데이터를 주고받기위해 pipe()함수를 이용한 IPC기법을 사용했고 이 정보를 이용하여 명령을 기록하거나 기록하지 않거나를 결정하게 됩니다.


  1.4.3 signal을 이용한 사용시간 알림 기능


  쉘을 사용하다보면 시간이 어느정도 지났는지 본인도 모르게되는 경우가 종종 있습니다. 이를위해 매 10분마다 사용시간이 얼만큼 되지는지에 대한 정보를 출력하도록 했습니다. 이를위해 signal()함수와 SIGALRM시그널을 이욯하였으며 alarmHandler()함수를 주기적으로 호출하게 됩니다.




2. Shell program code


#include <errno.h>

#include <stdlib.h>

#include <string.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#include <limits.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <sys/stat.h>

#include <setjmp.h>


#define MAX_CANON 256

#define QUIT_STRING "q"

#define BLANK_STRING " "

#define PROMPT_STRING "[2010151035YHJang]SH>> "

#define BACK_SYMBOL '&'

//#define FFLAG (O_WRONLY | O_CREAT | O_TRUNC)

//#define FMODE (S_IRUSR | S_IWUSR)


void executeredirect(char *s, int in, int out);

void executecmd(char *);

int makeargv(const char *s, const char *delimiters, char ***argvp);

int signalsetup(struct sigaction *def, sigset_t *mask, void (*handler)(int));

int parseandredirectin(char *);

int parseandredirectout(char *);

static void perror_exit(char *);

void alarmHandler();


int minute = 10;

int desc[2];


static sigjmp_buf jumptoprompt;

static volatile sig_atomic_t okaytojump = 0;


/* ARGSUSED */

static void jumphd(int signalnum) {

        if(!okaytojump) return;

        okaytojump = 0;

        siglongjmp(jumptoprompt, 1);

}


int main(void) {

        pid_t childpid;

        char inbuf[MAX_CANON];

        int len;

        sigset_t blockmask;

        struct sigaction defhandler;

        char *backp;

        int inbackground;

        char **str;

        int tcnt=0;

        char pipebuf[101];

        int j, k;

        int str_len;

        char rhistory[1000];

        int hcnt=0, st=0;

        int recordok = 0;


        for(k=0; k<999; k++)

                rhistory[k] = '\0';


        pipe(desc);


        if(signalsetup(&defhandler, &blockmask, jumphd) == -1) {

                perror("Failed to set up shell signal handling");

                return 1;

        }


        if(sigprocmask(SIG_BLOCK, &blockmask, NULL) == -1 ) {

                perror("Failed to block signals");

                return 1;

        }

        alarm(600);

        

        while(1) {


                signal(SIGALRM, alarmHandler);

        

                if((sigsetjmp(jumptoprompt, 1)) && (fputs("\n", stdout) ==EOF))

                        continue;

                okaytojump = 1;

                //printf("%d", (int)getpid());


                if(fputs(PROMPT_STRING, stdout) == EOF)

                        continue;

                if(fgets(inbuf, MAX_CANON, stdin) == NULL)

                        continue;

                len = strlen(inbuf);

                if(inbuf[len-1] == '\n')

                        inbuf[len-1] = 0;

                if(strcmp(inbuf, QUIT_STRING) == 0)

                        break;

                if((backp = strchr(inbuf, BACK_SYMBOL)) == NULL)

                        inbackground = 0;

                else {

                        inbackground = 1;

                        *backp = 0;

                }

                if(sigprocmask(SIG_BLOCK, &blockmask, NULL) == -1)

                        perror("Failed to block signals");




                makeargv(inbuf, " \t", &str);


                /* cd명령어(시작) */

                if(strcmp(str[0],"cd") == 0) {

                        chdir(str[1]);

                        system("pwd");


                        continue;

                }

                /* cd명령어(끝) */




                /* 자식 프로세스로부터 데이터 받기(시작) */

                for(j=0; j<100; j++)

                        pipebuf[j] = '\0';


                write(desc[1], " ", sizeof(char));

                str_len = read(desc[0], pipebuf, 100);

                pipebuf[str_len]=0;

                

                printf("[%s | %d] \n", pipebuf, str_len);

                /* 자식 프로세스로부터 데이터 받기(끝) */            


                /* rhistory에 기록(시작) */

                recordok = 1;

                for(j=0; j<100; j++) {

                        if(j+4 < 100) {

                                if(pipebuf[j] == '/' && pipebuf[j+1] == '5' && pipebuf[j+2] == '9' && pipebuf[j+3] == '9'&& pipebuf[j+4] == '9') {

                                        recordok = 0;

                                        break;

                                }

                        }

                }

                

                if(recordok == 1) {

                        for(k=0; k<100; k++) {

                                if(pipebuf[k] != NULL) {

                                        rhistory[hcnt++] = pipebuf[k];

                                        //printf("pipe[%c]\n", pipebuf[k]);

                                }

                        }

                        rhistory[hcnt++] = ';';

                }

                /* rhistory에 기록(끝) */  




                /* rhistory 명령어(시작)*/

                if(strcmp(str[0],"rhistory") == 0) {

                        st = 1;

                        printf("(!Notice)[Real_History_For_Your_Commends] : ");

                        for(k=0; k<hcnt; k++) {

                                if(rhistory[k] == ';' && rhistory[k+1] != ' ' && k+1 < hcnt) {

                                        printf("\n");

                                        printf("[%d]'st : ", st++);

                                }

                                else if(rhistory[k] != ';')

                                        printf("%c",rhistory[k]);

                        }

                        printf("\n");

                }

                /* rhistory 명령어(끝)*/



                if((childpid = fork()) == -1) {

                        perror("Failed to fork child to execute command");

                }

                else if(childpid == 0) {

                        if(inbackground && (setpgid(0,0) == -1))

                                return 1;

                        if((sigaction(SIGINT, &defhandler, NULL) == -1) || (sigaction(SIGQUIT, &defhandler, NULL) == -1) || (sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)) {

                                perror("Failed to set signal handling for command ");

                                return 1;

                        }

                        executecmd(inbuf);

                        return 1;

                }

                if(sigprocmask(SIG_UNBLOCK, &blockmask, NULL) == -1)

                        perror("Failed to unblock signals");

                if(!inbackground)

                        waitpid(childpid, NULL, 0);

                while(waitpid(-1, NULL, WNOHANG) > 0);

        }

        return 0;

}



void alarmHandler()

{

   printf("(!Notice)[Shell] : you've been with me for %d minutes!!!\n", minute);

   alarm(600);

   minute = minute + 10;

}



static void perror_exit(char *s) {

        perror(s);

        exit(1);

}


int makeargv(const char *s, const char *delimiters, char ***argvp) {

        int error;

        int i;

        int numtokens;

        const char *snew;

        char *t;


        if((s==NULL) || (delimiters==NULL) || (argvp==NULL)) {

                errno = EINVAL;

                return -1;

        }

        *argvp = NULL;

        snew = s+strspn(s, delimiters);

        if((t = (char *)malloc(strlen(snew) + 1)) == NULL)

                return -1;

        strcpy(t, snew);

        numtokens = 0;

        if(strtok(t, delimiters) != NULL)

                for(numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++);


        if((*argvp = malloc((numtokens +1)*sizeof(char *))) == NULL) {

                error = errno;

                free(t);

                errno = error;

                return -1;

        }


        if(numtokens == 0)

                free(t);

        else {

                strcpy(t, snew);

                **argvp = strtok(t, delimiters);

                for(i=1; i<numtokens; i++)

                        *((*argvp) + i) = strtok(NULL, delimiters);

        }

        *((*argvp) + numtokens) = NULL;

        return numtokens;

}


int parseandredirectin(char *cmd) {

        int error;

        int infd;

        char *infile;


        if((infile = strchr(cmd, '<')) == NULL)

                return 0;

        *infile = 0;

        infile = strtok(infile+1, " \t");

        if(infile == NULL)

                return 0;

        if((infd = open(infile, O_RDONLY)) == -1)

                return -1;

        if(dup2(infd, STDIN_FILENO) == -1) {

                error = errno;

                close(infd);

                errno = error;

                return -1;

        }

        return close(infd);

}


int parseandredirectout(char *cmd) {

        int error;

        int outfd;

        char *outfile;


        if((outfile = strchr(cmd, '>')) == NULL)

                return 0;

        *outfile = 0;

        outfile = strtok(outfile+1, " \t");

        if(outfile == NULL)

                return 0;

        if((outfd = open(outfile, O_WRONLY)) == -1)

                return -1;

        if(dup2(outfd, STDOUT_FILENO) == -1) {

                error = errno;

                close(outfd);

                errno = error;

                return -1;

        }

        return close(outfd);

}


void executecmd(char *cmds) {

        int child;

        int count;

        int fds[2];

        int i;

        char **pipelist;


        /* 파이프라인 시작 */

        count = makeargv(cmds, "|", &pipelist);

        if(count <=0 ) {

                fprintf(stderr, "Failed\n");

                exit(1);

        }

        for(i=0; i<count-1; i++) {

                if(pipe(fds) == -1)

                        perror_exit("Failed to create pipes");

                else if((child = fork()) == -1)

                        perror_exit("Failed to create process to run command");

                else if(child) {

                        if(dup2(fds[1], STDOUT_FILENO) == -1)

                                perror_exit("Failed to connect pipeline");

                        if(close(fds[0]) || close(fds[1]))

                                perror_exit("Failed to close needed files");

                        

                        executeredirect(pipelist[i], i==0, 0);

                        exit(1);

                }

                if(dup2(fds[0], STDIN_FILENO) == -1)

                        perror_exit("Failed to connect last component");

                if(close(fds[0]) || close(fds[1]))

                        perror_exit("Failed to do final close");

        }

        executeredirect(pipelist[i], i==0, 1);

        exit(1);


        /* 파이프라인 끝 */

}


void executeredirect(char *s, int in, int out) {

        char **chargv;

        char *pin;

        char *pout;


        int i, j;


        if(in && ((pin = strchr(s, '<')) != NULL) && out && ((pout=strchr(s,'>')) !=NULL) && (pin>pout)) {

                if(parseandredirectin(s) == -1) {

                        perror("Failed to redirect input");

                        return;

                }

        }


        /* 재지향 시작 */

        if(out && parseandredirectout(s) == -1)

                perror("Failed to redirect output");

        else if(in && parseandredirectin(s) == -1)

                perror("failed to redirect input");

        else if(makeargv(s, " \t", &chargv) <= 0)

                fprintf(stderr, "failed to parse command line\n");

        else {

                

                for(i=0; chargv[i] != 0; i++) {

                        for(j=0; chargv[i][j] != 0; j++)  {

                                write(desc[1], &chargv[i][j], sizeof(char));

                        }

                        write(desc[1], " ", sizeof(char));

                }

                execvp(chargv[0], chargv);

                perror("failed to execute command");


                write(desc[1], "/5999", sizeof("/5999"));


        }

        exit(1);

        /* 재지향 끝 */

}


int signalsetup(struct sigaction *def, sigset_t *mask, void (*handler)(int)) {

        struct sigaction catch;


        catch.sa_handler = handler;

        def->sa_handler = SIG_DFL;

        catch.sa_flags = 0;

        def->sa_flags = 0;

        if((sigemptyset(&(def->sa_mask)) == -1) || (sigemptyset(&(catch.sa_mask)) == -1) || (sigaddset(&(catch.sa_mask), SIGINT) == -1) || (sigaddset(&(catch.sa_mask), SIGQUIT) == -1) || (sigaction(SIGINT, &catch, NULL) == -1) || (sigaction(SIGQUIT, &catch, NULL) == -1) || (sigemptyset(mask) == -1) || (sigaddset(mask, SIGINT) == -1) || (sigaddset(mask, SIGQUIT) == -1))

                return -1;

        return 0;


}


3. Excution