디렉터리 내용 읽어 들이기


  • 먼저 디렉터리 엔트리를 리스트 하는 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);
    }
  }
}

참고 문헌

>> Home