[42] Pipex

KURTY·2022년 11월 15일
0

42_SEOUL

목록 보기
5/9

서브젝트 뜯어보기

Common Instructions

  • 여러분의 프로젝트는 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 와 같이 처리하시면 됩니다.

  • 프로그램에 메모리 누수가 발생하면 안 됩니다.

  • 다음과 같은 함수들을 사용 가능합니다 :

    • access
    • open
    • unlink
    • close
    • read
    • write
    • malloc
    • waitpid
    • wait
    • free
    • pipe
    • dup
    • dup2
    • execve
    • fork
    • perror
    • strerror
    • exit

Objectives

여러분의 목표는 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

function definition

access() 함수는 파일 또는 디렉토리의 사용자 권한을 체크한다.

How to use

#include <unistd.h>
int access(const char *pathname, int mode);

사용하고자 하는 프로그램상에 unistd.h 헤더파일을 포함하고 access() 함수에 매개 인자를 넘겨서 사용한다.

pathname에는 체크하고자할 디렉토리 또는 파일명을 넣고, mode 변수에는 적절한 마스크 값을 넣는다. mode 변수에 들아갈 수 있는 마스크 값은 다음과 같다.

  • R_OK: 파일 존재 여부, 읽기 권한 여부
  • W_OK: 파일 존재 여부, 쓰기 권한 여부
  • X_OK: 파일 존재 여부, 실행 권한 여부
  • F_OK: 파일 존재 여부

해당 함수의 결과 값은 int형으로 성공하면 0, 실패하면 -1을 반환한다. 오류의 원인은 errno에 세팅된다.

open

function definition

open() 함수는 파일을 사용할 수 있게 해준다.

How to use

#include <fcntl.h>
int open(const char *filename, int flags, -[mode_t mode]);

filename: 파일명
flag

  • O_RDONLY: 파일을 읽기 전용으로 연다
  • O_RDWR: 파일을 쓰기와 읽기용으로 연다
  • O_CREAT: 파일이 없으면 생성한다. open함수에 Permission 정보를 추가로 받아야 한다. 파일이 존재하면 연다.
  • O_TRUNC: 파일이 이미 존재하고 write-only, read-write 모드로 열 수 있는 경우, 파일 사이즈를 0으로 초기화 시킨다.

mode: O_CREAT 옵션 사용에 의해 파일이 생성될 때 지정되는 파일 접근 권한

  • 읽기 권한: 4
  • 쓰기 권한: 2
  • 실행 권한: 1

function definition

인자인 경로명 pathname에서 지정한 파일을 삭제한다.

파일을 삭제한다는 것은 디렉토리 파일에서 그 파일의 엔트리를 삭제한다는 것이다.

How to use

#include <unistd.h>
int unlink(const char *pathname)

close

function definition

open으로 연 파일의 사용을 종료

How to use

#include <unistd.h>
int close(int fd);

fd: 파일 디스크립터
성공 시 0 반환, 실패 시 -1 반환하고 에러를 errno에 저장

read

function definition

open() 함수로 열기한 파일의 내용을 읽는다

How to use

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbyters);

fd: 파일 디스크립터
buf: 파일을 읽어 들일 버퍼
nbytes: 버퍼의 크기

실패 시 -1반환, 성공 시 읽어들인 바이트의 수 반환

write

function definition

open() 함수로 열기 한 파일에 쓰기

How to use

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t n);

fd: 파일 디스크립터
buf: 파일에 쓰기를 할 내용을 담은 버퍼
n: 쓰기할 바이트 개수

실패 시 -1 반환, 성공 시 쓴 바이트 개수 반환

malloc

function definition

힙 영역에 메모리를 생성하여 할당함

How to use

#include <stdlib.h>
void *malloc(size_t size);

size: 동적으로 할당할 메모리의 크기

성공 시 할당한 메모리의 첫번째 주소, 실패시 NULL 반환

waitpid

function definition

특정 pid를 지닌 자식 프로세스의 상태를 획득하고, 필요시 메모리를 회수한다.

How to use

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

pid: 프로세스의 id

  • pid == -1: 임의의 자식 프로세스를 기다림
  • pid > 0: 프로세스의 아이디가 pid와 같은 자식 프로세스를 기다림
  • pid == 0: waitpid를 호출한 프로세스 그룹 pid와 같은 그룹 id를 지닌 프로세스를 기다림
  • pid < 0: 프로세스 그룹 id가 pid의 절댓값과 같은 자식 프로세스를 기다림

status: 프로세스 종료시의 상태를 보관할 변수의 주소

options

  • WNOHANG: 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수할 수 없는 상황에서 호출자는 차단되지 않고 반환값으로 0을 받음

wait

function definition

부모 프로세스가 자식 프로세스의 종료 상태를 얻기 위해 사용

How to use

#include <sys/wait.h>
pid_t wait(int *statloc);

성공: 프로세스 ID 반환, 실패: -1 반환

free

function definition

할당된 메모리를 해제

How to use

#include <stdlib.h>
void free(void *ptr);

ptr: 해제하고자 하는 메모리의 포인터

pipe

function definition

프로세스 간 통신을 위해 fd 쌍을 생성하는 함수

How to use

#include <unistd.h>
int pipe(int fd[2]);

fd[2]: 파일 디스크립터 배열, fd[0]은 파이프의 출구로 데이터를 입력받는 fd가 담기고, fd[1]에는 파이프의 입구로 데이터를 출력하는 fd가 답긴다.

반환값은 성공 시 0, 실패 시 -1

dup

function definition

fd로 전달받은 fd를 복제하여 반환한다. dup가 돌려주는 fd는 가장 fd를 반환한다.

How to use

#include <unistd.h>
int dup(int fd);

성공 시 fd, 오류시 -1 반환

dup2

function definition

파일 디스크립터를 복제하는 함수. fd를 복제해서 fd2로 지정한다.

fd2가 사용중이라면 기존 fd2를 닫은 후 복제한다.

How to use

#include <unistd.h>
int dup2(int fd, int fd2);

성공 시 새로운 fd(fd2), 실패시 -1 반환

execve

function definition

파일을 실행하는 함수

How to use

#include <unistd.h>
int execve(const char *file, char *const *argv, char * const *envp);

exec계열 함수들은 기본적으로 파일의 경로를 첫번째 인자로 받아와서 실행하는 함수이다.

v는 vector, e는 environment의 매개변수를 의미한다.

file: 디렉터리 포함 전체 파일이름
argv: 인자 목록
envp: 환경설정 목록

실패시 -1, 성공시에는 반환을 받을 수 없음

fork

function definition

자식 프로세스를 생성하는 함수

How to use

#include <unistd.h>
pid_t fork(void);

성공시 pid, 실패시 -1
자식 프로세스는 부모 프로세스의 메모리 상태를 그대로 복사해서 생성하기 때문에 부모 프로세스의 반환 값은 자식 프로세스의 pid지만 자식 프로세스에서의 반환값은 0이다. (-1 or 0 or Other numbers로 분기 가능)

perror

function definition

시스템 에러 메세지 출력 함수

How to use

#include <stdio.h>
void perror(const char *str);

str: 출력할 문구
str을 표준 에러로 출력하게 되는데, str 뒤에 에러와 errno를 함께 출력한다.

strerror

function definition

오류 메시지 문자열을 가리키는 포인터를 얻어온다.

How to use

#include <string.h>
char *strerror(int errnum);

errnum: 오류번호
오류 번호에 해당하는 오류 문자열을 가리키는 포인터를 반환한다.

exit

function defintion

프로세스를 종료할 때 사용한다. 시스템의 입장에서는 return과 동일

How to use

#include <stdlib.h>
void exit(int status);

부모 프로세스에게 종료 상태를 알려줄 수 있는 값이 된다.

부모 프로세스는 자식 프로세스의 종료 상태값을 얻어서 자식 프로세스가 어떤 상태로 종료 되었는지를 알 수 있다.

자식 프로세스가 비정상적으로 종료되었을 경우 커널에서는 종료 상태를 별도로 설정한다.

부모 프로세스는 자식의 종료 상태를 wait() or waitpid()로 얻을 수 있다.


코드 보기

main.c

#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));
}

pipex_utils.c

#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;
}

pipex.c

#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);
}
profile
진짜 공부하자

0개의 댓글