directory 관련 함수

sesame·2022년 1월 3일
0

교육

목록 보기
13/46

디렉터리 엔트리를 list하는 API

opendir(3)

DIR 타입은 디렉터리를 읽어 들이기 위한 스트림을 관리하는 구조체

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *path);
  • path로 지정한 디렉터리를 읽기 위해 open하고 DIR 타입에 대한 포인터를 반환

readdir(3)

디렉터리 스트림 d로부터 엔트리를 하나씩 읽어 들여 struct dirent(DIRectory ENTry)타입으로 반환

#include <sys/types.h>
#include <dirent.h>

struct dirent *readdir(DIR *d);

return 디렉터리 스트림 d로부터 엔트리를 하나씩 읽어 들여 struct dirent(DIRectory ENTry)타입으로 반환, 더 읽을 엔트리가 없거나 읽어 들이는 데 실패하면 NULL 반환

  • struct dirent의 내용
    운영체제마다 다르나, 리눅스에는 적어도 엔트리의 이름에 해당하는 char *d_name 존재
    d_name은 '\0'을 마지막에 담고 있는 문자열이라 printf()나 fputs()에서 그대로 사용 가능

  • readdir()이 반환한 포인터는 다시 호출했을 때 덮어 쓰이므로 주의

closedir(3)

디렉터리 스트림 d를 닫는 함수

#include <sys/types.h>
#include <dirent.h>

int closedir(DIR *d);

return 성공하면 0, 실패하면 -1 반환

ls.c

#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 arguments\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;

	//경로에 있는 디렉터리를 opendir로 열기
    d = opendir(path);
    //path가 존재하지 않거나 디렉터리가 아니어서 NULL 반환된 경우 exit()
    if (!d) {
        perror(path);
        exit(1);
    }
    //더 읽어들일 엔트리가 없을 때까지(NULL이 반환될 때까지) readdir() 반복해 이름 출력
    while (ent = readdir(d)) {
        printf("%s\n", ent->d_name);
    }
    //opendir() 후에는 반드시 closedir()
    closedir(d);
}

dirent 구조체
struct dirent{
long d_ino; // 파일이 가지고 있는 자신만의 번호 inode를 가리키는 숫자
off_t d_off; // dirent의 offset
unsigned short d_reclen; // d_name의 길이
char d_name [NAME_MAX+1]; //파일 혹은 디렉토리의 이름(없다면 NULL로 종료)
}

  • '.': 현재 위치의 디렉터리 의미
  • '..': 한 단계 위의 디렉터리 의미

시스템의 ls 커맨드는 -a 옵션을 사용하지 않는 한 '.'으로 시작하는 엔트리를 표시하지 않으므로 일반적으로는 '.'와 '..'는 표시되지 않음

  • 그러나 이는 어디까지 ls 커맨드가 그렇게 구현한 것이고, readdir()의 결과에는 포함되어 있음

readdir()의 결과는 이름순으로 정렬되어 있지 않음

  • 시스템의 ls 커맨드는 자체적으로 이름순으로 정렬해 출력하는 것

디렉터리 트리의 순회

  • 디렉터리 안의 또 다른 디렉터리까지 접근하고 싶은 경우(즉, 재귀적으로 접근하고 싶은 경우)의 조작을 디렉터리 트리 순회라고 함

  • 기본적으로 순회할 때도 opendir(), readdir(), closedir() 세 가지를 사용해 꾸준히 디렉터리를 타고 들어가면 되지만, 주의사항 존재

void
traverse(path){
    DIR *d = opendir(path);
    struct dirent *ent;

    while (ent = readdir(d)) {
        if /* ent가 디렉터리이면 */ {
            traverse(path/ent);
        }
        /* 처리 */
    }
}
  1. '.'와 '..'을 신경쓰지 않고 프로그램을 작성
  • '.'을 배제하지 않으므로 'dir', 'dir/.', 'dir/./.', 'dir/././.' 순서로 추적하게 되어 무한 재귀에 빠짐
  • 또한 '..'을 배재하지 않으므로 결국 루트 디렉터리까지 거슬러 올라가게 되어 파일 시스템 전체를 순회하게 됨
  1. 심볼릭 링크를 고려하지 않음
  • 루트 디렉터리를 가리키는 심볼릭 링크가 있다면, 루트 디렉터리로 처리가 이동하고, 그 안에서 또 루트 디렉터리를 가리키는 심볼릭 링크가 있으면 무한 루프에 들어가게 됨
  • 이 문제를 피하기 위해서는 lstate()를 사용해 심볼릭 링크를 명시적으로 제외해야

디렉터리 만들기

mkdir(2)

int mkdir(const char *dirname);

dirname : 생성할 디렉토리 경로와 이름
return : 정상 일 때 0, 에러 시 -1

Error
EEXIST: 만들려는 폴더가 이미 존재
ENOENT: 상위디렉터라가 없을경우
ENOTDIR: path로 지정한 상위디렉터라가 디렉터리가 아닐경우
EPERM: 상위 디렉터리에 대한 변경 권한이 없다

umask(2)

mode_t umask(mode_t mask);

mkdir 이나 open 을 사용할 때 만들어질 파일의 권한을 지정할 수 있지만, 두 경우 모두 지정한 값이 그대로 사용되는 것은 아니다. umask를 사용해서 변경된 값이 사용된다.

umask는 프로세스의 속성중 하나로, 가장 일반적인 값은 8진수 022다. open 이나 mkdir 에서 실제로 사용되는 권한은 c언어로 표현하면, 'mode & ~ umask로 계산된다. 즉, 인자로 지정한 mode 로부터 umask에 포함되는 비트를 빼는 것이다. 예를 들어 인자로 지정한 mode가 0777이고 mask가 022라면 실제 권한은 0755가 된다.

mkdir.c

#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)

rmdir 은 path로 지정한 디렉터리를 삭제
디렉토리 내에 파일이 존재하거나 사용중이면 삭제할 수 없다.
삭제 전에 디렉토리 존재유무 확인 후 삭제해야 한다.

int rmdir( const char *dirname );

//dirname : 삭제할 디렉토리 경로
//반환값 : 성공하면 0, 에러 시 -1/errno 설정

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.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++) {
        // -1 리턴되면 실패(에러처리)
        if (rmdir(argv[i]) < 0) {
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}

하드 링크

리눅스에서는 하나의 파일에 두개 이상의 이름을 지정할 수 있다.
이를 위해 링크라는 개념이 존재
rm이 삭제하는 것은 파일이 아니고 실체를 가리키고있는 파일의 이름이 모두 없어진 시점에(링크 카운트 0)이 되었을 때 비로소 실체가 삭제

link(2)

링크를 작성

int link( const char *oldpath, const char *newpath)

oldpath: 이미 존재하는 파일 이름
newpath: 만들고자하는 링크 이름
return : 성공 0, 실패 -1/errno

  • 주의 : 존재하는 파일 이름과, 만들고자 하는 링크 이름이 하나의 파일 시스템에 있어야 한다. - 또한 인자로 디렉터리는 사용할 수 없다.

ln.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]){
        if (argc != 3) {
                fprintf(stderr, "%s: wrong arguments\n", argv[0]);
                exit(1);
        }
        이미 존재하는 파일에 대해서 argv[2]이름으로 링크를 작성
        if (link(argv[1], argv[2]) < 0) {
                perror(argv[1]);
                exit(1);
        }
        exit(0);
}

심볼릭 링크

하드링크는 이름과 실체를 연결하는 구조
심볼릭 링크는 이름에 이름을 연결하는 구조

  • 심볼릭 링크에 대응하는 실체가 존재하지 않아도 된다. 심볼릭 링크는 실제로 액세스 할때가 아니면 이름과 실체의 매핑을 하지 않기 때문에 실체가 없어도 만들 수 있다.

  • 파일 시스템의 경계를 뛰어넘어 별명을 만들 수 있다. 하드링크는 하나의 파일 시스템 내에서만 만들 수 있는 제약이 있다. 그러나 심볼릭 링크는 파일 시스템의 경계와 관계없이 만들 수 있다.

  • 디렉터리에도 별명을 붙일 수가 있다. 디렉터리에 대해서는 하드링크는 만들 수 없지만 심볼릭 링크는 만들 수 있다.

  • 원본 파일이 삭제되면 심볼릭 링크로 원본파일에 접근할 수 없다.(고아링크), 하드링크는 가능하다.

symlink(2)

int symlink(const char *oldpath, const char *newpath);

return 성공 0, 실패 -1/errno

  • oldpath 파일에 대한 심볼릭 링크 newpath 를 만든다.
  • 만일 심볼릭 링크 newpath가 이미 존재한다면 이를 덮어쓰지 않는다.

readlink(2)

ssize_t readlink(const char *path, char *buf, size_t bufsiz);

return 성공 buf에 포함된 바이트 수, 실패 -1/errno

  • readlink()는 심볼릭 링크 path 가 가리키는 이름을 buf에 담아준다. 이때 최대 bufsize 바이트를 담아주며, bufsize는 보통 buf의 크기로 지정한다.
  • 또한 readlink()는 문자열의 마지막에 \0을 기록하지 않기 때문에 주의해야 한다.

symlink.c

$ ln -s

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]){
        if (argc != 3) {
                fprintf(stderr, "%s: wrong number of arguments\n", argv[0]);
                exit(1);
        }
        if (symlink(argv[1], argv[2]) < 0) {
                perror(argv[1]);
                exit(1);
        }
        exit(0);
}

파일 삭제 unlink(2)

 int unlink(const char *pathname);

정확하게는 unlink는 hard link의 이름을 삭제하고 hard link가 참조하는 count를 1감소시킨다.
hard link의 참조 count가 0이 되면, 실제 파일의 내용이 저장되어 있는 disk space를 free하여 OS가 다른 파일을 위해서 사용할 수 있도록 한다.
따라서 hard link를 생성하지 않은 파일은 바로 disk space를 해제하여 사실상 파일 삭제한다.

#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 (unlink(argv[i]) < 0) {
            perror(argv[1]);
            exit(1);
        }
    }
    exit(0);
}

파일 이동

리눅스에서 파일을 이동한다는 것은 하나의 실체에 대한 이름을 변경한다는 것과 대체로 동일

$ mv a b

$ ln a b
$ rm a
는 최종적인 결과는 같지만 작업과정에는 중대한 차이가 있음
ln과 rm을 사용하는 경우에는 작업 도중에 이름 a, b가 모두 존재하는 순간이 존재하지만 mv는 그렇지않다.(하나의 명령어로 이름이 변경되기 때문에)

int rename( const char *oldname, const char *newname );

oldname: 파일 또는 디렉토리의 경로나 이름
newname: 변경할 파일 또는 디렉토리의 이름
return 성공 시 0, 실패 시 errno(EACCES, ENOENT, EINVAL)

어떤 종류의 파일이라도 이동할 수 있지만, 파일 시스템을 넘어서 이동할 수는 없다. oldname, newname이 존재하는 파일 시스템이 서로 다른 경우 rename은 실패하고, errno에 상수 EXDEV가 설정된다.

메타 정보 획득하기

파일 정보 검색

int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);

return 성공 0, 실패 -1/errno

파일 접근 권한 확인

int access(const char *path, int amode);

파일 접근 권한 변경

메타 정보 변경하는 시스템콜
권한: chmod(2)
오너와 그룹: chown(2)
최종 액세스 시각과 최종 갱신 시간: utime(2)

chmod(2)

int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);

return 성공 0, 실패 -1/errno

chown(2)

파일 path의 소유 사용자르라 owner로 소유 그룹을 group로 변경

int chown(const char *path, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);

return 성공 0, 실패 -1/errno

  • lchown()이 있으면 lchmod()도 있을 것 같지만, 리눅스에서는 심볼릭 링크 자체에 권한이 없어 lchmod()란 존재하지 않는다. 그러나 BSD 계열의 유닉스에는 존재

utime(2)

int utime(const char *filepath, const struct utimbuf *time);

struct utimbuf {
    time_t actime;		// 접근 시간
    time_t modtime;		// 수정 시간
};

filepath: 대상 파일의 경로
time: 변경될 시간
return 성공 시 0, 에러 시 -1/errno

0개의 댓글

관련 채용 정보