[pipex](3)pipex 함수 파악하기2
1️⃣ 목표
- 이전 포스트에서 본
pipex함수
중 남은 함수를 이어서 알아볼 예정입니다. 1.execve
2.access
3.unlink
4.dup
5.dup2
6.perror
7.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" 경로의 명령어들을 예외처리하여 경로가 지정되도록 코드를 구현하면 될 것같습니다.