여러분의 프로젝트는 C언어로 작성되어야 합니다.
프로젝트는 Norm 규칙에 맞춰 작성되어야 합니다. 보너스 파일/함수가 존재할 경우, 그 또한 norm 검사에 포함되며 norm error가 있을 시 0점을 받게 됩니다.
정의되지 않은 동작을 제외하고, 여러분이 작성하신 프로그램이 예기치 않게 중단되어서는 안됩니다. (예를 들어, segmentation fault, bus error, double free 등) 만약 여러분의 프로그램이 예기치 않게 종료된다면, 제대로 작동하지 않은 것으로 간주되어 평가에서 0점을 받게 됩니다.
필요한 경우 heap에 할당된 모든 메모리 공간은 적절하게 해제되어야 합니다. 메모리 누수는 용납될 수 없습니다.
과제에서 필요한 경우, -Wall -Wextra -Werror 플래그를 지정하여 컴파일을 수행하는 Makefile을 제출해야 합니다. Makefile은 relink 되어서는 안 됩니다.
Makefile은 최소한 $(NAME), all, clean, fclean, re 규칙을 포함해야 합니다.
프로젝트에 보너스를 제출하려면, Makefile에 bonus 규칙을 포함해야 합니다. 이 보너스 규칙은 프로젝트의 메인 파트에서 금지되었던 모든 다양한 헤더, 라이브러리, 또는 함수들을 추가하여야 합니다. 보너스 과제는 반드시 _bonus.{c/h}라는 별도의 파일에 있어야 합니다. 반드시 수행하여야 하는 메인 파트의 평가와 보너스 파트의 평가는 별도로 이뤄집니다.
만일 프로젝트에서 여러분의 libft 사용을 허용한다면, libft 소스들과 관련 Makefile을 함께 루트 폴더 안에 있는 libft 폴더에 복사해야 합니다. 프로젝트의 Makefile은 우선 libft의 Makefile을 사용하여 라이브러리를 컴파일한 다음, 프로젝트를 컴파일해야 합니다.
이 과제물을 제출할 필요가 없고, 채점 받을 필요가 없을지라도, 저희는 여러분들이 프로젝트를 위한 테스트 프로그램을 만들 것을 권장합니다. 이것은 여러분의 과제물과 동료들의 과제물을 쉽게 테스트할 수 있게 도울 것입니다. 또한, 평가를 진행할 때 이러한 테스트 프로그램들이 특히 유용하다는 사실을 알게 될 것입니다. 평가 시에는 여러분의 테스트 프로그램과 평가 받는 동료의 테스트 프로그램들을 당연히 자유롭게 사용할 수 있습니다.
할당된 git 저장소에 과제물을 제출하세요. 오직 git 저장소에 있는 과제물만 등급이 매겨질 것입니다. Deepthought가 평가하는 과제의 경우엔, 동료평가 이후에 Deepthought가 수행됩니다. 만약 Deepthought 평가 중에 오류가 발생한다면, 그 즉시 평가는 중지될 것입니다.
실행 파일명은 반드시 pipex 여야 합니다.
여러분은 반드시 오류를 세심하게 처리하셔야 합니다. 어떠한 이유 (Segmentation fault, bus error, double free 등) 에서도 프로그램이 예상치 못하게 종료되면 안 됩니다. 오류 처리에 확신이 들지 않는다면, 쉘 커맨드 < file1 cmd1 | cmd2 > file2 와 같이 처리하시면 됩니다.
프로그램에 메모리 누수가 발생하면 안 됩니다.
다음과 같은 함수들을 사용 가능합니다 :
여러분의 목표는 Pipex 프로그램을 작성하는 것입니다.
프로그램은 다음과 같이 실행될 것입니다:
./pipex file1 cmd1 cmd2 file2
설명해 드리자면: file1과 file2는 파일명이고, cmd1과 cmd2에는 쉘 명령어와 그에 대한 인자값이 들어갑니다.
pipex 프로그램의 동작 결과는 다음 명령줄을 쉘에서 실행할 때의 결과와 동일하여야 합니다.
file1 cmd1 | cmd2 > file2
./pipex infile "ls -l" "wc -l" outfile
는 “< infile ls -l | wc -l > outfile” 와 같이 동작하여야 합니다.
./pipex infile "grep a1" "wc -w" outfile
는 “< infile grep a1 | wc -w > outfile” 와 같이 동작하여야 합니다.
access() 함수는 파일 또는 디렉토리의 사용자 권한을 체크한다.
#include <unistd.h>
int access(const char *pathname, int mode);
사용하고자 하는 프로그램상에 unistd.h 헤더파일을 포함하고 access() 함수에 매개 인자를 넘겨서 사용한다.
pathname에는 체크하고자할 디렉토리 또는 파일명을 넣고, mode 변수에는 적절한 마스크 값을 넣는다. mode 변수에 들아갈 수 있는 마스크 값은 다음과 같다.
해당 함수의 결과 값은 int형으로 성공하면 0, 실패하면 -1을 반환한다. 오류의 원인은 errno에 세팅된다.
open() 함수는 파일을 사용할 수 있게 해준다.
#include <fcntl.h>
int open(const char *filename, int flags, -[mode_t mode]);
filename: 파일명
flag
mode: O_CREAT 옵션 사용에 의해 파일이 생성될 때 지정되는 파일 접근 권한
인자인 경로명 pathname에서 지정한 파일을 삭제한다.
파일을 삭제한다는 것은 디렉토리 파일에서 그 파일의 엔트리를 삭제한다는 것이다.
#include <unistd.h>
int unlink(const char *pathname)
open으로 연 파일의 사용을 종료
#include <unistd.h>
int close(int fd);
fd: 파일 디스크립터
성공 시 0 반환, 실패 시 -1 반환하고 에러를 errno에 저장
open() 함수로 열기한 파일의 내용을 읽는다
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbyters);
fd: 파일 디스크립터
buf: 파일을 읽어 들일 버퍼
nbytes: 버퍼의 크기
실패 시 -1반환, 성공 시 읽어들인 바이트의 수 반환
open() 함수로 열기 한 파일에 쓰기
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t n);
fd: 파일 디스크립터
buf: 파일에 쓰기를 할 내용을 담은 버퍼
n: 쓰기할 바이트 개수
실패 시 -1 반환, 성공 시 쓴 바이트 개수 반환
힙 영역에 메모리를 생성하여 할당함
#include <stdlib.h>
void *malloc(size_t size);
size: 동적으로 할당할 메모리의 크기
성공 시 할당한 메모리의 첫번째 주소, 실패시 NULL 반환
특정 pid를 지닌 자식 프로세스의 상태를 획득하고, 필요시 메모리를 회수한다.
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid: 프로세스의 id
status: 프로세스 종료시의 상태를 보관할 변수의 주소
options
부모 프로세스가 자식 프로세스의 종료 상태를 얻기 위해 사용
#include <sys/wait.h>
pid_t wait(int *statloc);
성공: 프로세스 ID 반환, 실패: -1 반환
할당된 메모리를 해제
#include <stdlib.h>
void free(void *ptr);
ptr: 해제하고자 하는 메모리의 포인터
프로세스 간 통신을 위해 fd 쌍을 생성하는 함수
#include <unistd.h>
int pipe(int fd[2]);
fd[2]: 파일 디스크립터 배열, fd[0]은 파이프의 출구로 데이터를 입력받는 fd가 담기고, fd[1]에는 파이프의 입구로 데이터를 출력하는 fd가 답긴다.
반환값은 성공 시 0, 실패 시 -1
fd로 전달받은 fd를 복제하여 반환한다. dup가 돌려주는 fd는 가장 fd를 반환한다.
#include <unistd.h>
int dup(int fd);
성공 시 fd, 오류시 -1 반환
파일 디스크립터를 복제하는 함수. fd를 복제해서 fd2로 지정한다.
fd2가 사용중이라면 기존 fd2를 닫은 후 복제한다.
#include <unistd.h>
int dup2(int fd, int fd2);
성공 시 새로운 fd(fd2), 실패시 -1 반환
파일을 실행하는 함수
#include <unistd.h>
int execve(const char *file, char *const *argv, char * const *envp);
exec계열 함수들은 기본적으로 파일의 경로를 첫번째 인자로 받아와서 실행하는 함수이다.
v는 vector, e는 environment의 매개변수를 의미한다.
file: 디렉터리 포함 전체 파일이름
argv: 인자 목록
envp: 환경설정 목록
실패시 -1, 성공시에는 반환을 받을 수 없음
자식 프로세스를 생성하는 함수
#include <unistd.h>
pid_t fork(void);
성공시 pid, 실패시 -1
자식 프로세스는 부모 프로세스의 메모리 상태를 그대로 복사해서 생성하기 때문에 부모 프로세스의 반환 값은 자식 프로세스의 pid지만 자식 프로세스에서의 반환값은 0이다. (-1 or 0 or Other numbers로 분기 가능)
시스템 에러 메세지 출력 함수
#include <stdio.h>
void perror(const char *str);
str: 출력할 문구
str을 표준 에러로 출력하게 되는데, str 뒤에 에러와 errno를 함께 출력한다.
오류 메시지 문자열을 가리키는 포인터를 얻어온다.
#include <string.h>
char *strerror(int errnum);
errnum: 오류번호
오류 번호에 해당하는 오류 문자열을 가리키는 포인터를 반환한다.
프로세스를 종료할 때 사용한다. 시스템의 입장에서는 return과 동일
#include <stdlib.h>
void exit(int status);
부모 프로세스에게 종료 상태를 알려줄 수 있는 값이 된다.
부모 프로세스는 자식 프로세스의 종료 상태값을 얻어서 자식 프로세스가 어떤 상태로 종료 되었는지를 알 수 있다.
자식 프로세스가 비정상적으로 종료되었을 경우 커널에서는 종료 상태를 별도로 설정한다.
부모 프로세스는 자식의 종료 상태를 wait() or waitpid()로 얻을 수 있다.
#include "../inc/pipex.h"
int find_command(t_pipexdata *data, char *cmd, char **full_path)
{
char *tmp;
int i;
i = -1;
*full_path = NULL;
while (data->env_path && data->env_path[++i])
{
free(*full_path);
tmp = ft_strjoin(data->env_path[i], "/");
if (!tmp)
return (-2);
*full_path = ft_strjoin(tmp, cmd);
free(tmp);
if (!full_path)
return (-2);
if (access(*full_path, F_OK) == 0)
break ;
}
if (!data->env_path || !data->env_path[i])
{
free(*full_path);
return (-1);
}
return (0);
}
t_list *parse_commands(int argc, char **argv, t_pipexdata *data)
{
int i;
char *full_path;
char **cmd;
t_list *cmds;
int tmp;
cmds = NULL;
i = 1;
full_path = NULL;
while (++i < argc -1)
{
data->cmds = cmds;
cmd = ft_split(argv[i], ' ');
if (!cmd || !*cmd)
return ((t_list *)pipex_exit(data, NULL, CMD_NOT_FOUND, &cmd));
tmp = find_command(data, *cmd, &full_path);
if (!*cmd || tmp == -1)
return (pipex_exit(data, *cmd, CMD_NOT_FOUND, &cmd));
if (tmp == -2)
return (pipex_exit(data, NULL, NO_MEMORY, &cmd));
ft_lstadd_back(&cmds, pipex_lstnew(full_path, cmd));
free(full_path);
}
return (cmds);
}
int main(int argc, char **argv, char **envp)
{
t_pipexdata *data;
if (argc != 5)
return (*(int *)pipex_exit(NULL, NULL, INV_ARGS, NULL));
if (access(argv[1], F_OK) == -1)
return (*(int *)pipex_exit(NULL, argv[1], NO_FILE, NULL));
if (access(argv[1], R_OK) == -1)
return (*(int *)pipex_exit(NULL, argv[1], NO_PERM, NULL));
data = pipex_get_data(argc, argv, 0, envp);
data->cmds = parse_commands(argc, argv, data);
pipex(data, envp);
return (*(int *)pipex_exit(data, NULL, 1, NULL));
}
#include "../inc/pipex.h"
static t_pipexcmd *pipex_newcmd(char *full_path, char **cmd)
{
t_pipexcmd *newcmd;
if (!cmd)
return (NULL);
newcmd = malloc(sizeof(t_pipexcmd));
if (!newcmd)
return (NULL);
newcmd->full_path = ft_strdup(full_path);
if (full_path && !newcmd->full_path)
{
free(newcmd);
return (NULL);
}
newcmd->cmd = cmd;
return (newcmd);
}
t_list *pipex_lstnew(char *full_path, char **cmd)
{
t_list *newnode;
newnode = malloc(sizeof(t_list));
if (!newnode)
return (NULL);
newnode->content = pipex_newcmd(full_path, cmd);
if (!newnode->content)
{
free(newnode);
return (NULL);
}
newnode->next = NULL;
return (newnode);
}
void pipex_freecmd(void *node)
{
t_pipexcmd *tmp;
tmp = (struct s_pipexcmd *)node;
free(tmp->full_path);
ft_free_matrix(&tmp->cmd);
free(tmp);
}
void *pipex_exit(t_pipexdata *data, char *param, int err, char ***cmd)
{
if (err < 1 || param)
pipex_perror(param, err);
if (cmd)
ft_free_matrix(cmd);
if (data)
{
close(STDIN_FILENO);
close(data->in_fd);
close(data->out_fd);
if (data->cmds)
ft_lstclear(&data->cmds, pipex_freecmd);
if (data->env_path)
ft_free_matrix(&data->env_path);
free(data);
}
exit(0);
return (0);
}
// memory free about matrix
void ft_free_matrix(char ***m)
{
int i;
i = 0;
while (m[0][i])
{
free(m[0][i]);
i++;
}
free(m[0]);
m = NULL;
}
#include "../inc/pipex.h"
t_pipexdata *pipex_get_data(int argc, char **argv, int here_doc, char **envp)
{
t_pipexdata *data;
int i;
i = 0;
data = malloc(sizeof(struct s_pipexdata));
if (!data)
return ((t_pipexdata *)pipex_exit(data, NULL, NO_MEMORY, NULL));
data->env_path = NULL;
data->cmds = NULL;
data->in_fd = open(argv[1], O_RDONLY);
if (data->in_fd == -1)
return ((t_pipexdata *)pipex_exit(data, argv[1], NO_FILE, NULL));
if (!here_doc)
data->out_fd = open(argv[argc - 1], O_CREAT | O_RDWR | O_TRUNC, 0666);
if (access(argv[argc - 1], F_OK) == -1)
return ((t_pipexdata *)pipex_exit(data, argv[argc - 1], NO_FILE, NULL));
if (access(argv[argc - 1], W_OK) == -1)
return ((t_pipexdata *)pipex_exit(data, argv[argc - 1], NO_PERM, NULL));
while (envp[i] && !ft_strnstr(envp[i], "PATH=", ft_strlen(envp[i])))
i++;
data->env_path = ft_split(envp[i], ':');
if (!data->env_path)
return ((t_pipexdata *)pipex_exit(data, NULL, NO_PATH, NULL));
return (data);
}
void *pipex_child(t_pipexdata *data, int fd[2], t_list *start, char **envp)
{
t_pipexcmd *cmd;
cmd = start->content;
close(fd[READ_END]);
if (start->next && dup2(fd[WRITE_END], STDOUT_FILENO) == -1)
return (pipex_exit(data, NULL, DUP_ERR, NULL));
if (!start->next && dup2(data->out_fd, STDOUT_FILENO) == -1)
return (pipex_exit(data, NULL, DUP_ERR, NULL));
close(fd[WRITE_END]);
close(data->in_fd);
close(data->out_fd);
execve(cmd->full_path, cmd->cmd, envp);
return (pipex_exit(data, NULL, CMD_FAIL, NULL));
}
void pipex_perror(char *param, int err)
{
ft_putstr_fd("pipex: ", 2);
if (err == CMD_NOT_FOUND)
ft_putstr_fd("command not found: ", 2);
if (err == NO_FILE)
ft_putstr_fd("no such file or directory: ", 2);
if (err == NO_PERM)
ft_putstr_fd("permission denied: ", 2);
if (err == CMD_FAIL)
ft_putstr_fd("command failed: ", 2);
if (err == INV_ARGS)
ft_putstr_fd("invalid number of arguments", 2);
if (err == NO_MEMORY)
ft_putstr_fd("no memory left on device", 2);
if (err == DUP_ERR)
ft_putstr_fd("could not dup fd", 2);
if (err == PIPE_ERR)
ft_putstr_fd("could not create pipe", 2);
if (err == FORK_ERR)
ft_putstr_fd("could not fork process", 2);
if (err == NO_PATH)
ft_putstr_fd("PATH variable is not set", 2);
if (param && (err == CMD_NOT_FOUND || err == NO_FILE \
|| err == NO_PERM || err == CMD_FAIL))
ft_putstr_fd(param, 2);
ft_putstr_fd("\n", 2);
}
void *pipex(t_pipexdata *d, char **envp)
{
int fd[2];
pid_t pid;
t_list *start;
start = d->cmds;
if (dup2(d->in_fd, STDIN_FILENO) == -1)
return (pipex_exit(d, NULL, DUP_ERR, NULL));
close(d->in_fd);
while (start)
{
if (pipe(fd) == -1)
return (pipex_exit(d, NULL, PIPE_ERR, NULL));
pid = fork();
if (pid == -1)
return (pipex_exit(d, NULL, FORK_ERR, NULL));
if (!pid)
pipex_child(d, fd, start, envp);
close(fd[WRITE_END]);
if (start->next && dup2(fd[READ_END], STDIN_FILENO) == -1)
return (pipex_exit(d, NULL, DUP_ERR, NULL));
waitpid(pid, NULL, 0);
close(fd[READ_END]);
start = start->next;
}
return (NULL);
}