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.hfd : 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에서 한 줄만 받고 중지된다는 것이다 …….. ㅜㅜ 머가 문제일까효