디렉터리 내용 읽어 들이기
-
먼저 디렉터리 엔트리를 리스트 하는 API를 설명할 것이다.
-
디렉터리도 일반적인 파일과 비슷하다. 즉
open()
하고read()
한 후에close()
하면 된다. -
디렉터리를 읽으면 디렉터리에 담긴 파일들의 정보를 읽을 수 있다. 파일 한 개당 하나의 구조체에 대응되어, 디렉터리를 읽으면 구조체의 배열을 얻을 수 있다.
-
디렉터리는 바이트 배열임과 동시에 구조체의 배열인 것이다. 이 구조체를 디렉터리 엔트리라고 한다.
-
리눅스의 디렉터리 API는 디렉터리 엔트리 배열 단위로 조작하는 API를 제공하여 개발자가 편리하게 사용할 수 있다. 이 API는 일반적인 파일을 취급하는 API와 비슷하게
opendir(), readdir(), closedir()
라는 함수를 제공한다.
openddir(3)
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *path);
-
opendir()
은 경로로 지정한 디렉터리를 얻기 위해서open()
하고 DIR 타입에 대한 포인터를 반환한다. -
DIR 타입은 디렉터리를 읽어들이기 위한 스트림을 관리하는 구조체로, 파일을 읽을 때 사용한 FILE 타입에 대응하는 개념이라고 생각하면 된다.
readdir(3)
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir(DIR *d);
-
readdir()
은 디렉터리 스트림 d로부터 엔트리를 하나씩 읽어 들여struct dirent(DIRectory Entry)
타입으로 반환한다. -
더 읽을 엔트리가 없거나 읽어 들이는데 실패하면 NULL을 반환한다.
-
struct dirent
의 내용은 운영체제에 따라서 다른데, 리눅스에는 적어도 엔트리의 이름에 해당하는char *d_name
이 있다.d_name
은 ‘\0’을 마지막에 담고 있는 문자열이라서printf(), fputs()
에서 그대로 사용할 수 있다. -
readdir()
가 반환한 포인터는 다시 호출했을 때 덮어 쓰이므로 주의해야 한다.
closedir(3)
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *d);
-
closedir()
은 디렉터리 스트림 d를 닫는 함수이다. 성공하면 0을, 실패하면 -1을 반환한다. -
이 외에도 파일을 다룰 때 사용한
fseek()
와ftell()
에 대응하는seekdir()
와telldir()
도 있다.
ls 명령어 만들어보기
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
static void do_ls(char *path);
int main(int argc, char *argv[]) {
int i;
if (argc < 2) {
fprintf(stderr, "%s: no argument\n", argv[0]);
exit(1);
}
for (i = 1; i < argc; i++) {
do_ls(argv[i]);
}
exit(0);
}
static void do_ls(char *path) {
DIR *d;
struct dirent *ent;
d = opendir(path);
if (!d) {
perror(path);
exit(1);
}
while (ent = readdir(d)) {
printf("%s\n", ent->d_name); // 더 읽어 들일 엔트리가 없을 때까지 (즉, NULL이 반환될 때까지) 이름을 출력한다.
}
closedir(d);
}
- 실행 결과는 위와 같다.
디렉터리 트리의 순회
-
지금까지는 디렉터리 밑에 있는 파일에 접근하는 방법을 설명하였지만, 디렉터리 안에는 또 다른 디렉터리가 있을 수도 있는데, 그 안까지 접근하고 싶은 경우에는, 즉 재귀적으로 접근하고 싶을 경우도 있을 것이다.
-
즉 재귀적으로 접근하고 싶은 경우가 있을 텐데. 이러한 조작을 ‘디렉터리 순회’라고 한다.
-
기본적으로 순회할 때도
openddir()
,readdir()
,closedir()
의 세 가지를 사용해서 꾸준히 디렉터리를 타고 들어가면 된다. 단 이때 주의해야할 것은 ‘.’, ‘..‘의 존재이다.
void
traverse(path) {
DIR *d = openddir(path);
struct dirent *ent;
while (ent = readdir(d)) {
if /* ent가 디렉터리이면 */ {
traverse(path/ent);
}
/* 처리 */
}
}
-
위의 코드의 문제는 ‘.‘를 배제하지 않으므로 무한 재귀에 빠지게 된다. 또한 ‘..’ 를 배제하지 않았으므로 결국 루트 디렉터릮자ㅣ 거슬러 올라와 파일 시스템 전체를 순회하게 된다.
-
이 문제를 해결하려면 명시적으로 ‘.’, ‘..‘를 제외해야 한다.
-
두 번재 문제로는 심볼릭 링크를 고려하지 않았다. 예를 들면 처리 중인 디렉터리 안에 루트 디렉터리를 가리키는 심볼릭 링크가 있다면 루트 디렉터리로 처리가 이동하고, 그 안에서 또 루트 디렉터리를 가리키는 심볼릭 링크가 있으면 무한 루프에 들어가게 된다.
-
이 문제를 피하기 위해서는 뒤에서 서술하는
lstate()
를 사용해서 심볼릭 링크를 명시적으로 제외할 필요가 있다. -
이렇듯이 디렉터리 순회 코드는 꽤 주의해서 작성해야한다.
디렉터리 만들기
mkdir(2)
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *path, mode_t mode);
-
mkdir()
은path
로 지정한 디렉터리를 만든다. 성공하면 0을 반환하고, 실패하면 -1을 반환하고 errno를 설정한다. -
두 번째 인자인
mode
에는 권한을 지정한다. 단 여기서 지정된 값이 그대로 권한이 되는 것은 아니고, 먼저umask
라는 값과 비트 연산이 이루어진다. -
mkdir()
은 다른 시스템 콜에 비해서 꽤 빈번하게 실패하는데, 많이 발생하는 실패 원인은 아래와 같다.
- ENOENT 상위 디렉터리가 없다.
- ENOTDIR path로 지정한 상위 디렉터리가 디렉터리가 아니다.
- EEXIST path로 지정한 경로에 이미 파일이나 디렉터리가 존재한다.
- EPERM 상위 디렉터리에 대한 변경 권한이 없다.
umask
-
mkdir()
이나open()
을 사용할 때 만들어질 파일의 권한을 지정할 수 있지만, 두 경우 모두 지정한 값이 그대로 사용되는 것은 아니다.umask()
를 사용해서 변경된 값이 사용된다. -
umask
는 프로세스 속성 중 하나로, 가장 일반적인 값은 8진수 022다.
- open()이나 mkdir()에서 실제로 사용하는 권한은 C 언어로 표현하면 ‘mode & ~umask’로 계산된다.
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
umask()
는 프로세스의umask()
값을mask
로 변경하고, 직전까지의umask()
값을 반환한다.umask()
는 절대로 실패하지 않는다.
mkdir 명령어 작성하기
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
int i;
if (argc < 2) {
fprintf(stderr, "%s: no arguments\n", argv[0]);
exit(1);
}
for (i = 1; i < argc; i++) {
if (mkdir(argv[i], 0777) < 0) {
perror(argv[i]);
exit(1);
}
}
exit(0);
}
디렉터리 삭제하기
rmdir(2)
#include <unistd.h>
int rmdir(const char *path);
rmdir()
은 path로 지정한 디렉터리를 삭제한다. 디렉터리는 반드시 비어있어야 한다. 성공하면 0을 반환하고 실패하면 -1을 반환하고나서 errno를 설정한다.
reallinux@ubuntu:~/git/system$ clear
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int i;
if (argc < 2) {
fprintf(stderr, "%s: no arguments\n", argv[0]);
exit(1);
}
for (i = 1; i < argc; i++) {
if (rmdir(argv[i]) < 0) {
perror(argv[i]);
exit(1);
}
}
}