Pipex는 ./pipex infile cmd1 cmd2 outfile 로 입력받아 infile을 읽어 cmd1, cmd2를 실행한 후 그 결과를 outfile에 출력하는 과제이다.
명령어는 2개 이상이어야 하며 infile 대신 here_doc이 들어오는 경우에는 문장들을 입력받은 후 동일하게 진행해야한다.
pipe()
#include <unistd.h>
int pipe(int fd[2]);
pipe()
함수는 말그대로 파이프
를 생성해준다고 생각하면 될 것같습니다. 이 파이프
에 데이터를 넣고 뺄 수 있습니다.FIFO
(First In First Out, 선입선출)의 방식으로 데이터를 주고받을 수 있습니다.int
형의 크기가 2인 배열을 넣어주어야합니다. pipe()
함수는 이 fd[0]을 입력fd로 fd[1]을 출력fd로 만들어 줍니다.int main(void)
{
int fd1[2];
int fd2[2];
int fd3[2];
pipe(fd1);
pipe(fd2);
pipe(fd3);
printf("%d %d\n", fd1[0], fd1[1]);
printf("%d %d\n", fd2[0], fd2[1]);
printf("%d %d\n", fd3[0], fd3[1]);
}
//출력값
// 34
// 57
// 89
파이프
를 3개만들어준 뒤 각각의 fd요소
를 확인해 보는 코드입니다.3
부터 겹치지 않게 배정되어 졌음을 알 수 있습니다.fd(파일디스크립터)
에서 0, 1, 2
는 다음으로 배정되어 있습니다.0
: 표준입력1
: 표준출력(콘솔출력)2
: 에러출력open()
함수를 이용했을때도 fd를 배정받는데 사용한 후에 close()
함수를 이용하여 닫아줬습니다. pipe()
함수 역시 fd를 배정받기에 사용 후 혹은 사용하지않는 fd(파일디스크립터)는 close()
함수를 이용하여 닫아주어야 합니다.pipe()
함수가 실패했을 경우 1
을 반환합니다.execve()
#include <unistd.h>
#include <stdio.h>
int main(int argc, char * const *argv, char **envp)
{
char *arr[] = {"ls", "-al", NULL};
int returnv = execve("/bin/ls", arr, envp);
printf("value = %d\n", returnv);
}
fork()
https://reakwon.tistory.com/104
#include <unistd.h>
pid_t fork(void);
fork()
함수는 프로세스를 복사해주는 함수입니다. 이렇게 복사된 프로세스를 "자식 프로세스" 기존의 프로세스를 "부모 프로세스"라고 부릅니다.자식프로세스의 pid
값을 반환합니다. 여기서 pid
란 Process IDentifier의 약자로, 프로세스의 고유 ID입니다. 자료형타입으로 pid_t
를 사용하는데 int
형으로 선언해도 잘 동작합니다. (자세한 이유는 모르겠지만 pid_t는 0 ~ 32767의 범위를 갖는다고 합니다. -1 == 32768경우도 포함)fork()
함수가 실패할 경우 1
을 반환합니다.fork()
함수가 호출된 이후부터 진행합니다. 그렇기 때문에 자식프로세스에서는 fork()
반환값을 저장한 변수의 값이 0
입니다. 그렇기 때문에 다음의 코드예시와 같이 fork()
함수가 반환한 자식함수pid
를 이용하여 부모프로세스 혹은 자식프로세스에서만 동작하는 함수를 만들 수 있습니다.int main(void)
{
pid_t pid;
pid = fork();
if (pid == -1)
{
printf("fork() error");
exit(1);
}
if (pid == 0)
{
printf("\n****자식프로세스****\n");
printf("변수pid값: %d\n", pid);
printf("자식피드: %d\n", getpid());
}
else
{
printf("\n****부모프로세스****\n");
printf("변수pid값: %d\n", pid);
printf("부모피드: %d\n", getpid());
}
return (0);
}
/* 출력 */
****부모프로세스****
변수pid값: 6239
부모피드: 6238
****자식프로세스****
변수pid값: 0
자식피드: 6239
dup2()
unistd.h
fd
: fd로 전달받은 파일디스크립트를 복제하여 반환fd2
: 새 디스크립트의 값을 fd2
로 지정하고, 만약 fd2가 열려있다면, 닫은 후 복제가 됨.1
pipe 관련 내용 참고 https://iworldt.tistory.com/18
pipe 관련 연습 코드
<단방향 연결>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[]) {
//pipe 생성 후 디스크립터를 fd[0], fd[1]에 저장
int fd[2];
if (pipe(fd) == -1) {
fprintf(stderr, "pipe error: %s\n", strerror(errno));
return 1;
}
//printf("fd[0] = %d fd[1] = %d\n",fd[0], fd[1]);
pid_t pid = fork(); //pid는 자식프로세스
if (pid == -1) {
fprintf(stderr, "fork error : %s\n", strerror(errno));
return 1;
}
// 자식 프로세서가 처리하는 코드
if (pid == 0) {
// 표준 출력을 파이프를 쓰는 쪽으로 설정
dup2(fd[1], 1); // 복제 1 = fd[1]
//0
//1 = fd[1] -> 1 = pipe 출력
//2
//3(pipe 입력)ㅇ이 = fd[0]
//4(pipe 출력) = fd[1]
// 자식 프로세스는 파이프에서 읽지 않으므로 읽는 쪽을 닫는다
close(fd[0]);
int ste = execlp("ls", "ls", "grep a", NULL);
if (ste == -1) {
fprintf(stderr, "run error: %s\n", strerror(errno));
return 1;
}
}
// 부모 프로세스만 여기에 도달
// 표준 입력을 파이프의 읽는 쪽으로 리다이렉션한다.
dup2(fd[0], 0);
// 0 = fd[0] -> 0 = pipe 입력
// 부모 프로세스는 파이프에 쓰지 않으므로 파이프의 쓰는 쪽을 닫는다
close(fd[1]);
char line[255];
// 표준 입력이 파이프에 연결되어 있으므로 표준 입력에서 읽는다.
// fd[0]을 사용해도 된다.
while (fgets(line, sizeof(line), stdin) != 0) {
printf("%s", line);
}
return 0;
// 결과로 aux 명령의 결과가 출력됨
}
<양방향 연결>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
pid_t pid;
int testVal = 5;
int fdf, fd1[2], fd2[2];
char fixed_str[] = " - test"; // char *fixed_str = " - test"; 와 같음
char input_str[100], concat_str[256];
fdf = open("aaa.txt", O_RDWR | O_CREAT, 0644);
if (fdf == -1)
{
perror("Open Failed");
return 1;
}
if (pipe(fd1) == -1) {
perror("Pipe Failed");
return 1;
}
if (pipe(fd2) == -1) {
perror("Pipe Failed");
return 1;
}
scanf("%s", input_str); // 문자열 입력(99바이트 초과하면 buffer overflow 발생)
pid = fork();
if (pid < 0) {
perror("fork Failed");
return 1;
}
/*
※ fd1, fd2, fdf를 비롯한 모든 변수는 자식이 부모것을 복제한 것이기 때문에
부모에서 close(fd1[1])을 한다고 하여 자식의 것도 close()되는 것은 아님.
부모와 자식은 완전 별개의 공간에 각각의 변수와 file descriptor를 가지고 있음
※ concat_str은 부모와 자식이 공유할 수 있는 것이 아니고 완전 별개 공간임,
프로세서간에 메모리 공유는 그냥은 할 수 없고 공유메모리(Shared Memory)라는
개념을 이용하고 shmget()등의 함수를 이용해야 함
*/
if (pid > 0) { // Parent process (요기는 부모)
int status;
char concat_str[100];
testVal = 10; // 부모와 자식이 공유하는지 확인하고자 다른 값으로 변경해 봄
// dprintf() 함수는 fd에 출력하는 printf() 함수
dprintf(fdf, "parent process testVal=%d\n", testVal); // aaa.txt에 쓰기
close(fdf);
close(fd1[0]); // 사용하지 않는 첫 번째 pipe의 읽기 fd 닫기
close(fd2[1]); // 사용하지 않는 두 번째 pipe의 쓰기 fd 닫기
// 첫 번째 pipe에 입력 받은 문자열 input_str을 출력함
write(fd1[1], input_str, strlen(input_str) + 1);
close(fd1[1]); // 첫 번째 pipe의 쓰기 fd 닫기
// pid의 프로세서가 종료되길 대기함(=자식 프로세서 종료 대기)
waitpid(pid, &status, 0); // wait(&status)는 waitpid(-1, &wstatus, 0)과 같음
read(fd2[0], concat_str, 100 + sizeof(fixed_str)); // 자식이 보내온 것 읽기
close(fd2[0]); // 두 번째 pipe의 읽기 fd 닫기
printf("Concat string = %s\n", concat_str);
} else { // child process (요기는 자식)
char *src, *dest;
dprintf(fdf, "clild process testVal=%d\n", testVal); // aaa.txt에 쓰기
close(fdf);
close(fd1[1]); // 사용하지 않는 첫 번째 pipe의 쓰기 fd 닫기
close(fd2[0]); // 사용하지 않는 두 번째 pipe의 읽기 fd 닫기
read(fd1[0], concat_str, 100); // 부모 프로세서가 pipe로 보내준 것 읽기
close(fd1[0]); // 첫 번째 pipe의 읽기 fd 닫기
src = fixed_str;
dest = concat_str + strlen(concat_str); // concat_str에는 부모가 보내준 input_str의 값이 있음
while (*src) *dest++ = *src++;
*dest = '\0';
write(fd2[1], concat_str, strlen(concat_str) + 1); // 부모에게 pipe로 보내줌
close(fd2[1]); // 두 번째 pipe의 쓰기 fd 닫기
exit(0); // 요거 실행되면 부모의 waitpid() 함수가 대기를 종료함
}
}
pipex 과제를 하며 어떤 명령이 잘못되었는지, 어떤 fd를 dup2하는데 실패하였는지 등을 알고 싶어서 출력을 하려다가 ft_printf()로만 출력을 하려니까 자식 프로세스들이 동시에 시작되면서 문자들이 겹쳐 나오는 현상을 발견했다. 따라서 snprintf 및 vsnprintf를 구현하여 buf에 저장하고 이를 한 번에 write하는 방식으로 진행하려한다.
이를 다음과 같이 구현하였다.
다른 함수들은 ft_printf 참고
int ft_vsnprintf(char *buf, int buf_size, const char *format, va_list ap)
{
t_info inf;
if (!format || !buf || buf_size <= 0)
return (0);
inf.buf_size = buf_size;
inf.buf = buf;
va_copy(inf.ap, ap);
print_format(&inf, format);
if (inf.count >= inf.buf_size)
inf.count--;
buf[inf.count] = '\0';
return (inf.count);
}
int ft_snprintf(char *buf, int buf_size, const char *format, ...)
{
va_list ap;
va_start(ap, format);
return (ft_vsnprintf(buf, buf_size, format, ap));
}
다음과 같이 선언하여 **envp 변수에 환경변수를 받아올 수 있도록 한다.
int main(int argc, char *argv[], char *envp[])
heredoc인 경우와 heredoc이 아닌 경우를 분리하여 heredoc인 경우에는 heredoc_fd 파이프를 생성하고 get_next_line을 통해 표준입력으로 들어오는 문장을 읽어옴. heredoc_fd의 입력 fd를 prev_in_fd에 대입하고, heredoc이 아닌 경우는 입력받은 fd를 대입해준다. + 나머지 변수들 초기화
마지막 자식이 아닐 때까지 파이프를 생성하고 fork()를 통해 자식 프로세스를 생성한다.
부모 프로세스는 사용하지 않는 prev_in_fd 를 close해주고 파이프의 입력을 prev_in_fd에 대입해준다.
자식 프로세스에서는 dup2를 통해 표준 입력과 prev_in_fd를 연결하여 prev_in_fd를 표준 입력으로 받고, 마지막이 아닌 경우에는 표준 출력을 pipe의 출력으로, 마지막인 경우에는 main에서 입력받았던 out_fd를 넣어주어 out_fd로 표준 출력을 하도록 한다.
자식 프로세스에서 명령어를 실행하기 위해 execve()함수를 사용하는데, 이는 아래와 같으며 경로 및 명령어의 인수 목록이 함께 주어져야한다.
int execve( const char *path, char *const argv[], char *const envp[]);
환경변수에서 PATH=가 있는 배열을 찾는다면 : 으로 구분되어 있다 :를 구분자로 하여 split 하고 이와 명령어를 join하여 실행가능한 경로인지 확인하고 execve의 첫 번째 인자로 넣어준다.
PATH=/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin
입력받은 명령어를 **argv에 띄어쓰기를 구분자로 구분한 배열을 대입하고 명령어를 담은 배열로 사용한다. 이를 execve의 두 번째 인자로 준다. 마지막 배열의 값은 NULL어야한다.
#include "pipex.h"
static void pipex_child(t_pipex *px, int idx)
{
char **argv;
char *pathfile;
if (px->heredoc_limiter)
close(px->heredoc_fd[1]);
pipex_dup2(px->prev_in_fd, STDIN_FILENO);
if (idx >= px->lastchildidx)
pipex_dup2(px->out_fd, STDOUT_FILENO);
else
{
close(px->out_fd);
close(px->pipe_fd[0]);
pipex_dup2(px->pipe_fd[1], STDOUT_FILENO);
}
argv = ft_split(px->cmdv[idx], ' ');
if (!argv)
pipex_memory_error();
pathfile = find_path(argv[0], px->envp);
if (!pathfile)
pipex_error("%s: command not found", argv[0]);
execve(pathfile, argv, px->envp);
free(pathfile);
ft_free_array(argv);
pipex_perror("execve failed");
}
static void pipex_parent(t_pipex *px, int idx)
{
if (idx < px->lastchildidx)
{
close(px->prev_in_fd);
px->prev_in_fd = px->pipe_fd[0];
close(px->pipe_fd[1]);
}
}
static int pipex_process(t_pipex *px, int idx)
{
int pid;
if ((unsigned int)idx > (unsigned int)px->lastchildidx)
return (1);
if (idx < px->lastchildidx && pipe(px->pipe_fd) == -1)
return (pipex_perror("pipe failed"));
pid = fork();
if (pid == -1)
return (pipex_perror("fork failed"));
if (pid > 0)
{
pipex_parent(px, idx);
}
else
{
pipex_child(px, idx);
exit(0);
}
return (pipex_process(px, idx + 1));
}
static int pipex_init(t_pipex *px, int argc, char *argv[], char *envp[])
{
int firstcmdidx;
if (argc < 5)
return (pipex_error("argument error"));
firstcmdidx = 2;
if (ft_strcmp(argv[1], "here_doc") != 0)
{
px->heredoc_limiter = NULL;
px->in_fd = open(argv[1], O_RDONLY);
if (px->in_fd < 0)
return (pipex_perror("%s open failed", argv[1]));
}
else
{
firstcmdidx++;
px->heredoc_limiter = argv[2];
if (pipe(px->heredoc_fd) == -1)
return (pipex_perror("here_doc pipe failed"));
}
px->lastchildidx = argc - firstcmdidx - 2;
px->cmdv = argv + firstcmdidx;
px->envp = envp;
return (1);
}
int main(int argc, char *argv[], char *envp[])
{
t_pipex px;
int flag;
pipex_init(&px, argc, argv, envp);
if (px.heredoc_limiter)
{
px.prev_in_fd = px.heredoc_fd[0];
flag = O_APPEND;
}
else
{
px.prev_in_fd = px.in_fd;
flag = O_TRUNC;
}
px.out_fd = open(argv[argc - 1], O_WRONLY | O_CREAT | flag, 0644);
if (px.out_fd < 0)
return (pipex_perror("%s open failed", argv[argc - 1]));
if (pipex_process(&px, 0) > 0)
{
if (px.heredoc_limiter)
pipex_heredoc(&px);
wait(NULL);
}
close(px.out_fd);
return (0);
}
#include "pipex.h"
static char *join_path(char *path, char *file)
{
char *mem;
char *p;
mem = (char *)malloc(sizeof(char) * (ft_strlen(path)
+ ft_strlen(file) + 2));
if (!mem)
return (pipex_memory_error());
p = mem;
while (*path)
*p++ = *path++;
if (p > mem && p[-1] != '/')
*p++ = '/';
while (*file)
*p++ = *file++;
*p = '\0';
return (mem);
}
static char *find_file(char *pathlist, char *file)
{
int i;
char **patharr;
char *pathfile;
patharr = ft_split(pathlist, ':');
if (!patharr)
return (pipex_memory_error());
i = 0;
while (patharr[i])
{
pathfile = join_path(patharr[i], file);
if (!pathfile || access(pathfile, X_OK) == 0)
break ;
free(pathfile);
i++;
}
if (!patharr[i])
pathfile = NULL;
ft_free_array(patharr);
return (pathfile);
}
char *find_path(char *cmd, char *envp[])
{
int i;
if (ft_strchr(cmd, '/'))
{
if (access(cmd, X_OK) != 0)
return (NULL);
cmd = ft_strdup(cmd);
if (!cmd)
return (pipex_memory_error());
return (cmd);
}
i = 0;
while (envp[i])
{
if (ft_strncmp(envp[i], "PATH=", 5) == 0)
return (find_file(envp[i] + 5, cmd));
i++;
}
return (NULL);
}
#include "pipex.h"
#include <stdarg.h>
#include <errno.h>
int pipex_error(char *format, ...)
{
va_list ap;
int len;
char buf[128];
va_start(ap, format);
len = ft_snprintf(buf, 128, "pipex: ");
len += ft_vsnprintf(buf + len, 128 - len, format, ap);
buf[len++] = '\n';
buf[len] = '\0';
write(STDERR_FILENO, buf, len);
exit(1);
return (-1);
}
int pipex_perror(char *format, ...)
{
va_list ap;
int len;
char buf[128];
va_start(ap, format);
len = ft_snprintf(buf, 128, "pipex: ");
len += ft_vsnprintf(buf + len, 128 - len, format, ap);
len += ft_snprintf(buf + len, 128 - len, ": %s\n", strerror(errno));
write(STDERR_FILENO, buf, len);
exit(1);
return (-1);
}
void *pipex_memory_error(void)
{
pipex_perror("memory error");
return (NULL);
}
int pipex_dup2(int fd1, int fd2)
{
if (dup2(fd1, fd2) != -1)
return (0);
return (pipex_perror("dup2(%d,%d) failed", fd1, fd2));
}
int pipex_heredoc(t_pipex *px)
{
char *line;
size_t len;
len = ft_strlen(px->heredoc_limiter);
while (1)
{
ft_printf("pipex here_doc> ");
line = get_next_line(STDIN_FILENO);
if (!line)
return (pipex_perror("get_next_line error"));
if (ft_strlen(line) - 1 == len
&& !ft_strncmp(line, px->heredoc_limiter, len))
break ;
ft_putstr_fd(line, px->heredoc_fd[1]);
free(line);
}
close(px->heredoc_fd[1]);
return (1);
}
근데 평가를 받는 중 오류를 발견했다 ㅜㅜ
바로 heredoc에서 한 줄만 받고 중지된다는 것이다 …….. ㅜㅜ 머가 문제일까효