파일 시스템 관련 API

Soyun Park·2023년 10월 13일
0
post-thumbnail

1. 디렉터리 내용 읽어 들이기

1-1. 디렉터리 엔트리

  • 디렉터리도 일반 파일과 비슷하다. open()하고 read()한 후 close()하면 된다.
  • 디렉터리를 읽으면 디렉터리에 담긴 파일들의 정보를 얻을 수 있다. 파일 1개당 하나의 구조체에 대응되어, 디렉터리를 읽으면 구조체의 배열을 얻는다.
  • 즉, 디렉터리는 바이트 배열임과 동시에 구조체의 배열이다. 이 구조체를 디렉터리 엔트리라고 한다.
  • 리눅스의 디렉터리 API는 디렉터리 엔트리 배열 단위로 조작한다.

1-2. opendir(3)

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

DIR *opendir(const char * path);
  • opendir()은 path로 지정한 디렉터리를 읽기 위해 open하고 DIR 타입에 대한 포인터를 반환한다.
  • DIR 타입은 디렉터리 스트림을 관리하는 구조체이다.

1-3. readdir(3)

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

struct dirent *readdir(DIR *d);
  • readdir()은 디렉터리 스트림 d로부터 엔트리를 하나씩 읽어 들여 struct dirent디렉터리 엔트리 타입으로 반환한다.
  • struct dirent에는 엔트리의 이름인 char *d_name이 있다.
  • d_name은 \0을 포함한 문자열이기 때문에 printf()에서 그대로 사용할 수 있다.

1-4. closedir(3)

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

Int closedir(DIR *d);
  • closedir()은 디렉터리 스트림 d를 닫는다.

1-5. 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 arguments\n", argv[0]);
        exit(1);
    }
    for (i = 1; i < argc; i++) { // 실행 인자가 있다면 do_ls() 실행
        do_ls(argv[i]);
    }
    exit(0);
}

static void do_ls(char *path){
    DIR *dir_info;
    struct dirent *dir_entry;

    dir_info = opendir(path);
    if (!dir_info) {
        perror(path);
        exit(1);
    }
    dir_entry = readdir(dir_info);
    while (dir_entry) {
        printf("%s\n", dir_entry->d_name);
        dir_entry = readdir(dir_info);
    }
    closedir(dir_info);
}
  • main() 함수

    echo x y z
    • 위 예에서 echo는 명령어이고 x, y, z를 실행 인자라고 한다.
    • 소스코드를 컴파일하면 실행 파일이 생성된다. 실행 파일에 실행 인자를 넣어서 테스트하기 위한 코드이다.
    • argc는 실행 인자의 개수를, argv는 실행 인자의 내용을 문자열 배열 형태로 담고 있다.
    • 실행 인자로 x, y, z를 전달했을 때 argv의 구조는 다음과 같다.

    • 그림을 보면 argv[0]에는 프로그램을 실행할 때 입력한 명령어가 들어있다.
    • 따라서 argc는 항상 1 이상이다.
    • 실제 프로그램에 전달된 실행 인자는 argv[1]에서부터 시작된다.
  • do_ls() 함수

    • 매개변수 path는 main() 함수에서 넘겨준 argv[1:] 배열 안에 들어있는 실행 인자이다.
    • path의 디렉터리 경로를 opendir()로 열고 DIR 타입 포인터를 d에 저장한다.
    • readdir()은 d로부터 디렉터리 엔트리를 하나씩 읽어드리고 반환값을 ent에 저장한다.
    • 디렉터리 엔트리 구조체에 포함된 d_name엔트리 이름을 출력한다.
    • closedir()로 d를 닫는다.

    1-6. 작성한 ls 커맨드 실행 예

  • 실행인자 . 은 현재 디렉터리를 의미한다.

  • 현재 디렉터리의 모든 파일이 파일명을 출력한 결과이다.



2. 디렉터리 만들기

2-1. mkdir(2)

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *path, mode_t mode);
  • mkdir()은 path로 지정한 디렉터리를 만든다.
  • mode는 권한을 지정한다. 지정된 값이 그대로 권한이 되지 않고 mode & ~umask 비트연산으로 계산된다.
  • 즉, 인자로 지정한 mode로부터 umask에 포함되는 비트를 뺀다.
  • 예를 들어 mode가 777이고 mask가 022라면 실제 권한은 0755가 된다.

  • mkdir()에서 많이 발생하는 실패 원인은 다음과 같다.

    메세지내용
    ENOENT상위 디렉터리가 없음
    ENOTDIRpath로 지정한 상위 디렉터리가 아님
    EEXISTpath로 지정한 경로에 이미 파일이나 디렉터리가 존재함
    EPERM상위 디렉터리에 대한 변경 권한이 없음

2-2. umask(2)

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
  • umask()는 프로세스의 umask 값을 mask로 변경하고 직전까지의 umask 값을 반환한다.

2-3. 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);
}

2-4. 작성한 mkdir 커맨드 실행 예

  • 실행 인자와 같은 디렉터리가 만들어졌음을 확인할 수 있다.
  • 실행인자가 없다면 에러 메세지를 출력한다.



3. 디렉터리 삭제하기

3-1. rmdir(2)

#include <unistd.h>
int rmdir(const char *path);
  • rmdir()은 path로 지정한 디렉터리를 삭제한다. 디렉터리는 반드시 비어 있어야 한다.

3-2. rmdir 명령어 만들기

#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);
		}
	}
	exit(0);
}

3-3. 작성한 rmdir 커맨드 실행 예

  • 위의 rmdir로 만들었던 dir 디렉터리가 삭제됐다.



4. 하드 링크

4-1. 하드 링크란?

  • 리눅스에서는 하나의 파일에 2개 이상의 이름을 지정할 수 있다.
  • 파일에 새로운 이름을 붙이는 것을 링크라고 한다.
  • 다음과 같이 a라는 파일을 만들었다고 하자.
    $ echo 'This is file.' > a

  • 여기서 파일 a의 실체에 새로운 이름 b를 붙여보자.
    $ ln a b

  • 이 작업을 파일 a를 가리키는 하드 링크 b를 만든다라고 한다.
  • a와 b는 같은 것을 가리키고 있고 이후 a의 내용을 변경하면 b의 내용도 동일하게 변경된다.

4-2. link(2)

#include <unistd.h>
int link(const char *src, const char *dest);
  • link()는 src로 지정한 파일에 새로운 이름 dest를 추가한다.
  • link()에는 다음과 같은 제약이 있다.
    • src와 dest는 동일한 파일 시스템에 있어야 한다.
    • src와 dest에 디렉터리는 사용할 수 없다. 즉, 디렉터리에 하드 링크를 붙일 수 없다.

4-3. ln 명령어 작성하기

#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);
	}
	if(link(argv[1],argv[2])<0){
		perror(argv[1]);
		exit(1);
	}
	exit(0);
}

4-4. 작성한 ln 커맨드 실행 예

  • ls -l 가 출력하는 항목 중 두 번째 열이 이름의 개수를 의미한다. 이를 링크 카운터라고 한다.
  • ln 커맨드를 실행하기 전에는 ln.c의 링크 카운터는 1이었다가 실행 후 2로 바뀌었다.
  • 링크 카운터는 실체에 기록된다.
  • ln.canothername 모두 같은 실체를 가리키고 있으므로 2로 증가했다.



5. 심볼릭 링크

5-1. 심볼릭 링크란?

  • 심볼링 링크는 하드 링크와 달리 이름에 이름을 연결하는 구조이다.
  • 심볼릭 링크에 액세스가 있을 때 비로소 이름의 실체를 찾는다.

  • 심볼릭 링크는 다음과 같은 특징이 있다.
    • 심볼릭 링크에는 대응하는 실체가 존재하지 않아도 된다.
      • 실제로 액세스 할 때가 아니면 이름과 실체의 매핑을 하지 않기 때문에 실체가 없어도 만들 수 있다.
    • 파일 시스템의 경계를 뛰어넘어 별명을 붙일 수 있다.
    • 디렉터리에도 별명을 붙일 수 있다.

5-2. symlink(2)

#include <unistd.h>
int symlink(const char *src, const char *dest);
  • symlink()는 지정한 경로 src에 대한 새로운 심볼릭 링크 dest를 만든다.

5-3. readlink(2)

#include <unistd.h>
int readlink(const char *path, char *buf, size_t bufsize);
  • readlink()는 심볼릭 링크 path가 가리키는 이름을 buf에 담는다.
  • 이 때 최대 bufsize 바이트를 담아 주며, bufsize는 보통 buf의 크기로 지정한다.
  • 문자열 마지막에 \0을 기록하지 않는다.

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

  • 실행 후 ls -l 의 출력 결과 anothername2symlink.c를 가리키고 있다.
  • anothername2symlink.c의 심볼릭 링크 파일임을 알 수 있다.

6. 파일 삭제

6-1. unlink(2)

#include <unistd.h>
int unlink(const char *path);
  • 리눅스에서 파일을 삭제한다는 것은 실체에 붙인 이름의 개수를 줄인다는 뜻이다.
  • unlink()는 path로 지정한 이름을 삭제한다. 그러나 디렉터리를 삭제할 수는 없다.
  • 심볼릭 링크를 unllink()하면 심볼링 링크만 삭제되고 심볼릭 링크가 가리키는 실체 파일은 삭제되지 않는다.

6-2. rm 명령어 작성하기

#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);
		}
	}
	exit(0);
}

6-3. 작성한 rm 커맨드 실행 예

  • 두 번째 ls 명령어 실행 결과 junk 파일이 없어진 것을 확인할 수 있다.
  • 디렉터리를 삭제할 수 없다.



7. 파일 이동

7-1. 파일 이동의 과정

  • 리눅스에서 파일을 이동한다는 것은 하나의 실체에 대한 이름을 변경한다는 것과 대체로 동일하다.
  • 즉, 별도의 하드 링크를 만들고 나서 원래 이름을 지우는 셈이 된다.
    $ mv a b
    $ ln a b
     $ rm a
  • ln과 rm을 사용하는 경우에 수행 도중 a와 b가 모두 존재하는 순간이 존재하지만 mv는 그렇지 않다는 차이가 있다.

7-2. rename(2)

#nclude <stdio.h>
int rename(const char *src, const char *dest);
  • rename()은 파일명 src를 파일명 dest로 변경한다.
  • 어떠한 종류의 파일이라도 이동할 수 있지만, 파일 시스템을 넘어서 이동할 수는 없다.
  • src와 dest가 존재하는 파일 시스템이 서로 다른 경우 errno에 상수 EXDEV가 설정된다.

7-3. mv 명령어 작성하기

#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);
	}
	if(rename(argv[1],argv[2])<0){
		perror(argv[1]);
		exit(1);
	}
	exit(0);
}

7-4. 작성한 mv 커맨드 실행 예

  • 현재 디렉터리에 있는 mv.c 파일명을 mv2.c 로 바꾸었다.

  • 현재 디렉터리에 있는 mv2.c 파일을 mvdir 디렉터리로 이동시켰다.

  • mvdir 디렉터리에 있는 mv2.c 파일을 상위 디렉터리로 이동시키고 파일명을 mv.c 로 바꾸었다.



8. 메타 정보 획득하기

8-1. 파일 시스템의 정보

  • 파일 시스템에는 데이터 본체 이외에 다음과 같은 정보도 저장되어 있다.
    • 파일의 종류
    • 크기
    • 권한
    • 소유자
    • 그룹
    • 작성 시각
    • 변경 시각
    • 액세스 시각
  • 이를 stat()과 lstat() 시스템 콜로 획득할 수 있다.

8-2. stat(2)

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct sat *buf);
  • stat() path로 지정한 엔트리 정보를 취득해서 buf에 써넣는다.

  • lstat()은 stat()과 거의 같지만 path가 심볼릭 링크일 경우 해당 링크를 따라가지 않고 심볼릭 링크 자신의 정보를 반환하는 차이점이 있다.

  • struct stat 타입의 멤버들은 다음과 같다.

    타입멤버이름설명
    dev_tst_dev디바이스 번호
    ino_tst_inoi 노드 번호
    mode_tst_mode파일 타입과 권한을 포함한 플래그
    nlink_tst_nlink링크 카운터
    uid_tst_uid소유 사용자 ID
    gid_tst_gid소유 그룹 ID
    dev_tst_rdev디바이스 파일의 종류를 나타내는 번호
    off_tst_size파일 크기(바이트 단위)
    blksize_tst_blksize파일의 블록 크기
    blkcnt_tst_blocks블록 수
    time_tst_atime.tv_sec최종 액세스 시각의 초 단위
    longst_atime.tv_nsec최종 액세스 시각의 나노 초 단위
    time_tst_mtime.tv_sec최종 변경 시각의 초 단위
    longst_mtime.tv_nsec최종 변경 시각의 나노 초 단위
    time_tst_ctime.tv_sec메타 정보의 최종 변경 시각의 초 단위
    longst_ctime.tv_nsec메타 정보의 최종 변경 시각의 나노 초 단위

8-3. stat 명령어 만들기

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

static char *filetype(mode_t mode);

int
main(int argc, char *argv[])
{
    struct stat st;

    if (argc != 2) {
        fprintf(stderr, "wrong argument\n");
        exit(1);
    }
    if (lstat(argv[1], &st) < 0) {
        perror(argv[1]);
        exit(1);
    }
    printf("type\t%o (%s)\n", (st.st_mode & S_IFMT), filetype(st.st_mode));
    printf("mode\t%o\n", st.st_mode & ~S_IFMT);
    printf("dev\t%llu\n", (unsigned long long)st.st_dev);
    printf("ino\t%lu\n", (unsigned long)st.st_ino);
    printf("rdev\t%llu\n", (unsigned long long)st.st_rdev);
    printf("nlink\t%lu\n", (unsigned long)st.st_nlink);
    printf("uid\t%d\n", st.st_uid);
    printf("gid\t%d\n", st.st_gid);
    printf("size\t%ld\n", st.st_size);
    printf("blksize\t%lu\n", (unsigned long)st.st_blksize);
    printf("blocks\t%lu\n", (unsigned long)st.st_blocks);
    printf("atime\t%s", ctime(&st.st_atime));
    printf("mtime\t%s", ctime(&st.st_mtime));
    printf("ctime\t%s", ctime(&st.st_ctime));
    exit(0);
}

static char*
filetype(mode_t mode)
{
    if (S_ISREG(mode)) return "file";
    if (S_ISDIR(mode)) return "directory";
    if (S_ISCHR(mode)) return "chardev";
    if (S_ISBLK(mode)) return "blockdev";
    if (S_ISFIFO(mode)) return "fifo";
    if (S_ISLNK(mode)) return "symlink";
    if (S_ISSOCK(mode)) return "socket";
    return "unknown";
}
  • 심볼릭 링크의 경우 심볼릭 링크 자신의 정보를 취하는 것이 적절하므로 stat() 대신 lstat()을 사용했다.
  • st_mode 멤버애서 파일 유형을 꺼내기 위해 S_IFMT와 비트 마스크를 했고, 파일의 종류를 판정하기 위해 S_ISREG() 등의 매크로를 사용했다.

8-4. 작성한 stat 커맨드 실행 예

  • 파일의 메타 정보를 출력한다.



9. 메타 정보 변경하기

9-1. 메타 정보를 변경하는 시스템 콜

  • 메타 정보를 변경하는 시스템 콜은 다음과 같다.

    변경 대상시용하는 시스템 콜
    권한chmod(2)
    오너와 그룹chown(2)
    최종 액세스 시각과 최종 갱신 시각utime(2)

9-2. chmod(2)

#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
  • chmod()는 path로 지정한 파일의 모드를 mode로 변경한다.

  • mode는 다음과 같은 상수를 OR 로 묶어서 지정하거나 0755 같은 숫자를 사용한다.

    상수의미
    S_IRUSR, S_IREAD00400소유한 사용자가 읽기 가능
    S_IWUSR, S_IWRITE00200소유한 사용자가 쓰기 가능
    S_IXUSR, S_IEXEC00100소유한 사용자가 실행 가능
    S_IRGRP00040소유한 사용자가 속한 그룹이 읽기 가능
    S_IWGRP00020소유한 사용자가 속한 그룹이 쓰기 가능
    S_IXGRP00010소유한 사용자가 속한 그룹이 실행 가능
    S_IROTH00004그 외의 사용자가 읽기 가능
    S_IWOTH00002그 외의 사용자가 쓰기 가능
    S_IXOTH00001그 외 사용자가 실행 가능
  • C언어에서 숫자에 0을 앞에 두면 8진수가 된다.

  • 예를 들어 권한 644의 경우, 0644 또는 S_IRUSR | S_lWUSR | S_IRGRP | S_IROTH | 로 지정한다.

9-3. chown(2)

#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
  • chown()은 파일 path의 소유 사용자를 owner로, 소유 그룹을 group으로 변경한다. owner는 사용자 ID, group은 그룹 ID다.
  • lchown()의 동작은 chown()과 유사하지만 path가 심볼릭 링크인 경우에는 그 심볼릭 링크 자체의 정보를 변경하는 점이 다르다.
  • chown()은 심볼릭 링크가 가리키는 파일의 정보를 변경한다.

9-4. utime(2)

#include <sys/types.h>
#include <utime.h>
int utime(const char *path, struct utimbuf *buf);
struct utimebuf{
	time_t actime; // 최종 액세스 시각
    time_t modtime; // 최종 갱신 시각
};
  • utime()은 path로 지정한 파일의 최종 액세스 시각st_atime과 최종 갱신 시각st_mtime을 변경한다.
  • buf가 NULL이 아니면 buf의 내용에 따라 actime과 modtime이 설정된다.
  • buf가 NULL이라면 둘 다 현재 시각으로 변경된다.

9-5. chmod 명령어 작성하기

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int
main(int argc, char *argv[])
{
    int mode;
    int i;

    if (argc < 2) {
        fprintf(stderr, "no mode given\n");
        exit(1);
    }
    mode = strtol(argv[1], NULL, 8);
    for (i = 2; i < argc; i++) {
        if (chmod(argv[i], mode) < 0) {
            perror(argv[i]);
        }
    }
    exit(0);
}
  • 첫 번째 인자가 모드, 두 번째 인자가 대상 파일명이다.

9-6. 작성한 chmod 커맨드 실행 예

  • chmod.c 파일의 권한이 다음과 같이 변경됨을 확인할 수 있다.



10. 파일 시스템과 스트림

10-1. open(2)

#include <sys/types.h>
#includde <sys/stat.h>
#include <fcntln.h>
int open(const *path, int flags);
int open(const char *path, int flags, mode_t mode);
  • open()은 path로 지정한 경로의 파일에 대한 스트림을 만들고 그 스트림을 가리키는 파일 디스크럽터를 반환한다.

  • flags에는 파일을 어떤 모드로 열 것인지 flag들의 비트 연산자 OR 을 사용하여 지정한다.

    flag의미
    O_RDONLY읽기 전용
    O_WRONLY쓰기 전용
    O_RDWR읽고 쓰기
    O_CREATE파일이 존재하지 않으면 새롭게 만든다
    O_EXCLO_CREAT와 함께 사용되어 이미 파일이 존재하면 에러가 된다
    O_TRUNCO_CREAT와 함께 사용되어 이미 파일이 존재하면 파일의 크기를 0으로 만든다
    O_APPENDwrite() 함수가 항상 파일의 끝에 쓰도록 설정한다
  • mode는 flags에 O_CREAT를 설정했을 때만 유효한 인자다.

  • umask라는 값과 비트 연산되어 그 파일의 권한을 지정한다.

0개의 댓글