[pipex](3)pipex 함수 파악하기2
1️⃣ 목표
- 이전 포스트에서 본 pipex함수중 남은 함수를 이어서 알아볼 예정입니다. 1.execve2.access3.unlink4.dup5.dup26.perror7.strerror
2️⃣ dup(), dup2(), perror() 함수
(1) dup() 함수
int dup(int fd);
- 인자로 fd(파일디스크립터)를 받고 복사하여 반환합니다.
- 오류시 -1을 반환합니다.
int main(void)
{
    int fd[2];
    int fd_temp;
    char buffer[7];
    pipe(fd);
    fd_temp = dup(fd[1]);
    printf("fd[1]: %d\nfd_temp: %d\n", fd[1], fd_temp);
    /* fd_temp에 입력해보기 */
    write(fd_temp, "Hello\n", 7);
    read(fd[0], buffer, 7);
    printf("%s", buffer);
	close(fd[0]);
	close(fd[1]);
	close(fd_temp);
}
fd[1]: 4
fd_temp: 5
Hello
- fd[1] == 4디스크립터를 fd_temp에- 5로 복사해줬지만 똑같은 기능으로 잘 복사됌을 알 수 있습니다.
(2) dup2() 함수
int dup2(int fd, int fd2);
- 두번째 인자가 첫번째 인자의 복제가 됩니다.
- 반환값은 두번째 인자값과 같습니다.
- dup()함수와 차이점은 직접- fd값지정한 곳에 복사할 수 있다는 것 입니다.
- 만약 두번째 인자의 fd값이 열려있다면 닫은 후 복제됩니다. (닫은 후 복제된다는 뜻은 아래 예시에서 파악해볼 예정)
int main(void)
{
    int temp_fd;
    int fd[2];
    int fd2[2];
    char buffer[6];
    char buffer2[6];
    pipe(fd);
    pipe(fd2);
    temp_fd = dup2(fd[1], fd2[1]);
    printf("temp_fd: %d\nfd[1]: %d\nfd2[1]: %d\n", temp_fd, fd[1], fd2[1]);
    /* fd2[1]에 입력해보기 */
    write(fd2[1], "hello", 6);
    read(fd[0], buffer, 6);
    read(fd2[0], buffer2, 6);
    printf("\nfd[0]: %s\nfd2[0]: %s\n", buffer, buffer2);
    /* close() 생략 */
}
temp_fd: 7
fd[1]: 4
fd2[1]: 7
fd[0]: hello
fd2[0]:
- 위의 예시에서 fd2[1]가fd[1]의 기능을 하도록dup2()를 이용하여 복제했습니다.
- 역시 fd2[1]에 입력을 하면fd2 파이프가 아닌fd 파이프에 데이터가 쌓임을 알 수 있습니다.
- 즉, 위에서 "닫은 후 복제" 라는 뜻은 기존의fd2[1]의 기능을 잃는다는 뜻입니다.
(3) dup2() 활용 예시(perror()함수)
void perror(const char* str);
- dup2()를 활용하여- 0, 1, 2(표준 입출력, 에러)에도 적용할 수 있습니다.
int main(void)
{
    perror("ERROR!");
}
ERROR!: Undefined error: 0
- perror()함수는 fd(파일디스크립터)가- 2로 에러출력을 담당합니다.
- 위와 같이 지정해준 에러메시지와 errno 의 값을 해석하여 출력해줍니다. (OS나 컴파일러에 따라 해석이 달라집니다.)
int main(void)
{
    int fd[2];
    pipe(fd);
    dup2(fd[1], 2);
    perror("ERROR!");
    /* close() 생략 */
}
- dup2()에 의해- 2에- fd[1]의 기능이 복제됐습니다. 결과적으로- perror()이 출력이 안된 것을 확인할 수 있습니다.
int main(void)
{
    int fd[2];
    char buffer[30];
    pipe(fd);
    dup2(fd[1], 2);
    perror("ERROR!");
    read(fd[0], buffer, 30);
    printf("%s", buffer);
    /* close() 생략 */
}
ERROR!: Undefined error: 0
- 2뿐만아니라- 0, 1디스크립터에도 적용이 가능합니다.
3️⃣ strerror() 함수
char* strerror(int errnum);
- perror()함수는- errono값을 해석하여- 인자값과 함께 출력해줍니다.
- strerror()함수는- errono값만을 해석하여 출력해줍니다.
- <string.h>헤더에 정의되어 있습니다.
#include <stdio.h>
#include <string.h>
int main(void)
{
    char *err;
    printf("* * * * 에러 메시지 * * * *\n");
    for (int i = 0; i <= 108; i++)
    {
        err = strerror(i);
        printf("[%d]: %s\n", i, err);
    }
}
코드실행 결과 - 116가지 오류출력 메시지 보기 (클릭)
* * * * 에러 메시지 * * * *
[0]: Undefined error: 0
[1]: Operation not permitted
[2]: No such file or directory
[3]: No such process
[4]: Interrupted system call
[5]: Input/output error
[6]: Device not configured
[7]: Argument list too long
[8]: Exec format error
[9]: Bad file descriptor
[10]: No child processes
[11]: Resource deadlock avoided
[12]: Cannot allocate memory
[13]: Permission denied
[14]: Bad address
[15]: Block device required
[16]: Resource busy
[17]: File exists
[18]: Cross-device link
[19]: Operation not supported by device
[20]: Not a directory
[21]: Is a directory
[22]: Invalid argument
[23]: Too many open files in system
[24]: Too many open files
[25]: Inappropriate ioctl for device
[26]: Text file busy
[27]: File too large
[28]: No space left on device
[29]: Illegal seek
[30]: Read-only file system
[31]: Too many links
[32]: Broken pipe
[33]: Numerical argument out of domain
[34]: Result too large
[35]: Resource temporarily unavailable
[36]: Operation now in progress
[37]: Operation already in progress
[38]: Socket operation on non-socket
[39]: Destination address required
[40]: Message too long
[41]: Protocol wrong type for socket
[42]: Protocol not available
[43]: Protocol not supported
[44]: Socket type not supported
[45]: Operation not supported
[46]: Protocol family not supported
[47]: Address family not supported by protocol family
[48]: Address already in use
[49]: Can't assign requested address
[50]: Network is down
[51]: Network is unreachable
[52]: Network dropped connection on reset
[53]: Software caused connection abort
[54]: Connection reset by peer
[55]: No buffer space available
[56]: Socket is already connected
[57]: Socket is not connected
[58]: Can't send after socket shutdown
[59]: Too many references: can't splice
[60]: Operation timed out
[61]: Connection refused
[62]: Too many levels of symbolic links
[63]: File name too long
[64]: Host is down
[65]: No route to host
[66]: Directory not empty
[67]: Too many processes
[68]: Too many users
[69]: Disc quota exceeded
[70]: Stale NFS file handle
[71]: Too many levels of remote in path
[72]: RPC struct is bad
[73]: RPC version wrong
[74]: RPC prog. not avail
[75]: Program version wrong
[76]: Bad procedure for program
[77]: No locks available
[78]: Function not implemented
[79]: Inappropriate file type or format
[80]: Authentication error
[81]: Need authenticator
[82]: Device power is off
[83]: Device error
[84]: Value too large to be stored in data type
[85]: Bad executable (or shared library)
[86]: Bad CPU type in executable
[87]: Shared library version mismatch
[88]: Malformed Mach-o file
[89]: Operation canceled
[90]: Identifier removed
[91]: No message of desired type
[92]: Illegal byte sequence
[93]: Attribute not found
[94]: Bad message
[95]: EMULTIHOP (Reserved)
[96]: No message available on STREAM
[97]: ENOLINK (Reserved)
[98]: No STREAM resources
[99]: Not a STREAM
[100]: Protocol error
[101]: STREAM ioctl timeout
[102]: Operation not supported on socket
[103]: Policy not found
[104]: State not recoverable
[105]: Previous owner died
[106]: Interface output queue is full
[107]: Unknown error: 107
[108]: Unknown error: 108
- OS나 컴파일러에 따라서 errno 의 갯수와 해석이 달라집니다.
- errno 는 가장 최근의 에러코드로 덮여씌워집니다.
4️⃣ access() 함수
int access(const char *pathname, int mode);
- 파일에 대해 확인하는 함수입니다.
- 첫번째인자로 파일 주소를- 두번째인자로 체크할 내용(mode)를 받습니다.
- 파일주소가 심볼릭 링크 라면 원본을 체크합니다.
- mode가 가능하면 0을 실패시-1을 반환합니다.
| mode | 확인내용 | 
|---|---|
| R_OK | 읽기가능? | 
| W_OK | 쓰기가능? | 
| X_OK | 실행가능? | 
| F_OK | 파일존재? | 
int main(void)
{
    char *is_txt = "./test.txt";
    char *is_exe = "./a.exe";
    printf("txt읽기: %d\n", access(is_txt, R_OK));
    printf("txt쓰기: %d\n", access(is_txt, W_OK));
    printf("txt실행: %d\n", access(is_txt, X_OK));
    printf("txt존재: %d\n", access(is_txt, F_OK));
    printf("exe읽기: %d\n", access(is_exe, R_OK));
    printf("exe쓰기: %d\n", access(is_exe, W_OK));
    printf("exe실행: %d\n", access(is_exe, X_OK));
    printf("exe존재: %d\n", access(is_exe, F_OK));
}
txt읽기: 0
txt쓰기: 0
txt실행: -1
txt존재: 0
exe읽기: 0
exe쓰기: 0
exe실행: 0
exe존재: 0
- -1로 반환될 때 다음예시와 같이 적절한- errno값으로 세팅됩니다.
int main(void)
{
    /* 실행 불가 파일 */
    printf("txt실행: %d\n", access("./test.txt", X_OK));
    printf("%s\n", strerror(errno));
    /* 존재하지 않는 파일 */
    printf("\n존재하지않는 파일: %d\n", access("./empty", R_OK));
    printf("%s\n", strerror(errno));
}
txt실행: -1
Permission denied
존재하지않는 파일: -1
No such file or directory
5️⃣ unlink() 함수
int unlink(const char *pathname);
- 링크를 삭제하는 함수로 인자로 파일주소를 받습니다.
- 성공 시 0을 실패시-1을 반환합니다.
#include <unistd.h>
int main(void)
{
    unlink("./a.exe");
}
- 꼭 링크 파일이 아니더라도 파일이 삭제 되었습니다. 
6️⃣ execve() 함수
int execve(const char *filename, char *const argv[], char *const envp[]);
- execve()함수는- exec계열함수 입니다. exec계열함수의 종류는 여러가지가 있습니다.
- 첫번째 인자로 받은 path의 파일을 실행하고argv, envp를 인자로 전달합니다.
- exec뒤에 오는- v와- e의 의미는 다음과 같습니다. _- v: argv가 파라미터를- char _[]로 한번에 받는다는 뜻입니다. 배열의 마지막값은- NULL 이어야 합니다. *- e: 설정할 환경변수를 파라미터로 받는데- char \*[]배열로 한번에 받습니다.
(1) execve() 사용예시1
/* test2.c */
#include <unistd.h>
int main(int argc, char **argv)
{
    char *envp[] = {0};
    execve("/bin/ls", argv, envp);
    return (0);
}
$> gcc test2.c
$> ./a.out -l
total 120
-rwxr-xr-x 1 kimkirim staff 50082 Aug 9 12:22 a.out
-rw-r--r-- 1 kimkirim staff 633 Aug 8 21:48 test.c
-rw-r--r-- 1 kimkirim staff 149 Aug 9 12:21 test2.c
(2) execve() 사용예시2
#include <unistd.h>
int main(int argc, char** argv)
{
    char *envp[] = {0};
    execve("/bin/mv", argv, envp);
    return (0);
}
$> gcc test2.c
$> ./a.out test.c ./cc/
< 실행 전 파일리스트>

< 실행 후 파일리스트>

(3) 커맨드파일 위치
- 위의 예시에서 봤듯이 execve()함수를 이용하여 커맨드 명령어를 실행하기 위해서는 첫번째 인자인 파일경로에해당 커맨드명령어파일의 경로 를 넣어주어야 합니다.
- ls,- mv와 같은 명령어들은- "/bin" 경로에 위치했습니다. 
- 하지만 - wc와 같은 명령어는- "/bin" 경로가 아닌- "usr/bin" 경로에 위치하였습니다. 
- 상대적으로 명령어 파일의 수가 적은 "/bin" 경로의 명령어들을 예외처리하여 경로가 지정되도록 코드를 구현하면 될 것같습니다.
