본문 바로가기

프로그래밍(TA, AA)/C C++

[시스템프로그래밍] 파일과 디렉토리

유닉스에서 파일은 일반 파일과 특수 파일 그리고 디렉토리로 구분할 수 있습니다. 일반 파일은 텍스트 파일, 실행 파일 등 텍스트나 바이너리 형태의 데이터를 저장하고 있는 파일입니다. 유닉스에서 파일은 데이터를 저장하는 데는 물론, 장치를 구동하는 데도 사용합니다. 다른 파일과 달리 장치 관련 특수 파일을 장치 파일이라고도 합니다. 디렉토리는 해당 디렉토리에 속한 파일을 관리하는 특별한 파일입니다. 유닉스에서는 디렉토리도 파일로 취급합니다.


유닉스에서 파일은 파일명과 inode, 데이터 블록으로 구성됩니다. 파일명은 사용자가 파일에 접근할 때 사용합니다. inode는 외부적으로는 번호로 표시되며, 파일의 소유자나 크기 등 파일에 관한 정보와 파일의 실제 데이터를 저장하고 있는 데이터 블록의 위치를 나타내는 주소들이 저장되어 있습니다. 데이터 블록은 실제로 데이터가 저장되는 하드 디스크의 공간입니다.


유닉스에서는 파일 정보를 inode에서 읽어옵니다. inode로부터 검색할 수 있는 정보는 파일의 종류, 접근 권한, 하드 링크 개수, 소유자의 UID와 GID, 파일의 크기, 파일 접근 시각, 수정 시각, 파일의 inode 변경 시각 등입니다. inode 정보는 <sys/stat.h> 파일에 정의되어 있는 stat 구조체에 저장됩니다. inode 정보를 검색하고 파일 접근 권한을 확인하고 변경하는 데 사용할 수 있는 함수를 보여줍니다.


 기능

 함수원형

 파일 정보 검색

 int stat(const char *restrict path, struct stat *buf);

 int fstat(int fd, struct stat *buf);

 파일 접근 권한 확인

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

 파일 접근 권한 변경

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

 int fchmod(int fd, mode_t mode);


링크는 기존 파일이나 디렉토리에 접근할 수 있는 새로운 이름을 의미합니다. 링크를 사용하면 복잡한 파일명을 간단한 다른 파일명으로 접근할 수 있게 하거나, 플랫폼에 따라 달라지는 특수 파일에 같은 링크 파일명을 제공해 동일한 방식으로 접근하게 할 수 있다는 장점이 있습니다. 이렇게 링크를 만들 수 있는 기능으로 하드 링크와 심볼릭 링크가 있습니다. 하드 링크는 기존 파일과 동일한 inode를 사용합니다. 심볼릭 링크는 기존 파일에 접근할 수 있는 다른 파일을 만드는데, 기존 파일과 다른 inode를 사용하며 기존 파일의 경로를 저장합니다.


 기능

 함수원형

 하드 링크 생성

 int link(const char *existing, const char *new);

 심볼릭 링크 생성

 int synlink(const char *name1, const char *name2);


심볼릭 링크에 저장된 내용을 검색하려면 아래 함수를 사용합니다.


 기능

 함수원형

 심볼릭 링크 정보 검색

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

 심볼릭 링크 내용 읽기

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

 심볼릭 링크 원본 파일 경로 검색

 char *realpath(const char *restrict file_name, char *restrict resolved_name);


파일을 생성하는 것처럼 디렉토리도 함수를 사용해 생성하고 삭제할 수 있습니다. 디렉토리를 이동시키거나 디렉토리의 내용을 읽을 수도 있습니다.


 기능

 함수원형

 디렉토리 생성

 int mkdir(const char *path, mode_t mode);

 디렉토리 삭제

 int rmdir(const char *path);

 디렉토리 이름 변경

 int rename(const char *old, const char *new);

 현재 위치 확인

 char *getcwd(char *buf, size_t size);

 디렉토리 이동

 int chdir(const char *path);

 디렉토리 열기

 DIR *opendir(const char *dirname);

 디렉토리 닫기

 int closedir(DIR *dirp);

 디렉토리 내용 읽기

 struct dirent *readdir(DIR *dirp);

 디렉토리 오프셋

 long telldir(DIR *dirp);

 void seekdir(DIR *dirp, long loc);

 void rewinddir(DIR *dirp);




유닉스에서 파일은 데이터 저장, 장치 구동, 프로세스 간 통신(interprocess communication) 등에 사용합니다. 이 절에서는 파일의 종류와 구성 요소에 관해 알아보겠습니다.


파일의 종류


앞서 언급했듯이 유닉스에서 파일은 크게 일반 파일과 특수 파일 그리고 디렉토리로 구분할 수 있습니다.



일반 파일

텍스트 파일, 실행 파일, 라이브러리, 이미지 등 유닉스에서 사용하는 대부분의 파일이 일반 파일에 해당합니다. 일반 파일은 데이터 블록에 텍스트나 바이너리 형태의 데이터를 저장하고 있습니다. vi 같은 편집기를 사용해 만들기도 하고, 컴파일러나 다른 응용 프로그램에서 생성할 수도 있습니다. 예를 들어, /usr/bin 디렉토리에 있는 파일을 살펴보면 다음과 같습니다.


장치 파일

유닉스에서 장치를 사용하려면 해당 장치와 연관된 특수 파일을 이용해야 합니다. 다른 파일과 달리 장치 관련 특수 파일을 장치 파일이라고도 합니다. 장치 파일은 데이터 블록을 사용하지 않습니다. 대신 장치의 종류를 나타내는 장치 번호를 inode에 저장합니다. 

장치 파일은 블록 장치 파일과 문자 장치 파일로 구분합니다. 블록 장치 파일은 블록 단위로 데이터를 읽고 씁니다. 솔라리스에서 기본 블록 크기는 8KB입니다. 문자 장치 파일은 하드 디스크인 경우 섹터 단위로 읽고 쓰는데, 솔라리스에서 기본 크기는 512byte입니다. 문자 장치 파일을 로우 디바이스라고도 합니다.


장치 파일은 ls -l 명령의 결과에서 파일의 크기를 나타내는 부분이 일반 파일과 달리 169, 0과 같은 형태로 출력됩니다. 이는 장치 파일의 크기가 아닌 장치 번호를 나타냅니다. 앞에 오는 169는 장치의 종류를, 뒤에 오는 0이나 1은 해당 장치의 개체 수를 의미합니다. 장치의 종류를 나타내는 번호를 주 장치 번호라고 하고, 장치의 개체 수를 의미하는 번호를 부 장치 번호라고 합니다. 기본적인 장치 파일은 처음 유닉스를 설치할 때 생성되며, 추가로 장착하는 장치의 장치 파일은 장치 추가 절차에 따라 생성됩니다.


디렉토리

유닉스에서는 디렉토리도 파일로 취급합니다. 디렉토리와 연관된 데이터 블록은 해당 디렉토리에 속한 파일의 목록과 inode를 저장합니다. 디렉토리를 생성하려면 mkdir, 삭제하려면 rmdir 또는 rm -r, 복사하려면 cp -r 명령을 사용합니다.


파일의 종류 구분

ls -l 명령을 사용하면 파일의 종류를 알 수 있습니다. ls -l 명령의 결과에서 첫 글자로 장치의 종류를 구분합니다. 명령의 결과 중 파일의 권한을 표시하는 부분인 -rw-r--r--에서 맨 앞에 오는 하이픈(-)이 파일의 종류를 나타냅니다. 다음은 파일의 종류 식별 문자입니다.


-(일반 파일) / d(디렉토리) / b(블록 장치 특수 파일) / c(문자 장치 특수 파일) / l(심볼릭 링크)




파일의 구성 요소


유닉스에서 파일은 파일명, inode, 데이터 블록으로 구성됩니다.


파일명

파일명은 사용자가 파일에 접근할 때 사용하며, 파일명과 관련된 inode가 반드시 있어야 합니다. 예전에는 유닉스의 파일명으로 최대 14자까지 사용할 수 있었지만, 현재는 대부분 255자까지 사용할 수 있습니다. 파일명에는 /를 제외한 모든 문자를 사용할 수 있습니다. /는 경로명에서 구분자로 사용하기 때문에 파일명에 사용할 수 없습니다. 파일명을 만들 때는 다음 사항에 유의해야 합니다.

  • 파일과 디렉토리 이름에는 알파벳, 숫자, 하이픈(-), 밑줄(_), 점(.)만을 사용한다.
  • 파일과 디렉토리 이름에 공백, *, &, |, ", ', ~ 과 같은 특수문자를 쓰지 않는다.
  • 파일과 디렉토리 이름에 사용하는 알파벳은 대소문자를 구분한다.
  • 파일과 디렉토리 이름이 점(.)으로 시작하면 숨김 파일이다.


inode

inode는 외부적으로는 번호로 표현하며, 내부적으로는 두 부분으로 나누어 정보를 저장합니다.




inode의 첫 번째 부분은 파일에 관한 정보를 저장하는 부분으로, 파일의 종류와 접근 권한, 하드 링크 수, 소유자, 그룹, 파일 크기, 파일 변경 시각, 파일명 등을 저장합니다. ls -l 명령은 inode의 정보를 읽어서 출력합니다. 두 번째 부분은 파일의 실제 데이터를 저장하고 있는 데이터 블록의 위치를 나타내는 주소들을 저장합니다. 파일의 inode 번호는 ls -i 명령으로 알 수 있습니다.


데이터 블록

데이터 블록은 실제로 데이터가 저장되는 하드 디스크의 공간입니다. 일반 파일이나 디렉토리, 심볼릭 링크는 데이터 블록에 관련 내용을 직접 저장하지만, 장치 파일은 데이터 블록을 사용하지 않고 장치에 관한 정보를 inode에 저장합니다.



파일 정보 검색


유닉스에서 파일의 정보를 검색하려면 ls 명령을 사용합니다. 파일에 관한 자세한 정보는 2절에서 살펴본 것처럼 inode에 저장되어 있습니다. inode의 정보를 검색하려면 stat, lstat, fstat 함수를 사용합니다. 이중 lstat은 심볼릭 링크 파일의 inode를 검색하는 함수로 다음절에 다룹니다.


파일명으로 파일 정보 검색: stat(2)

#include <fcntl.h>
#include <sts/types.h>
#include <sys/stat.h>

int stat(const char *restrict path, struct stat *buf);

파일 정보를 검색하는 데 가장 많이 사용하는 함수는 stat입니다. stat 함수는 path에 지정한 파일의 정보를 검색해 buf로 지정한 구조체에 저장합니다. stat 함수로 파일의 정보를 검색할 때 파일에 대한 읽기/쓰기/실행 권한이 반드시 있어야 하는 것은 아닙니다. 다만 파일에 이르는 경로의 각 디렉토리에 대한 읽기 권한은 있어야 합니다.


stat 구조체

stat 함수로 검색한 inode 정보는 stat 구조체에 저장됩니다. stat 구조체(struct stat)는 <sys/stat.h> 파일에 정의되어 있으며, 구조는 다음과 같습니다.


struct stat {
    dev_t       st_dev;
    ino_t       st_ino;
    mode_t      st_mode;
    nlink_t     st_nlink;
    uid_t       st_uid;
    gid_t       st_gid;
    dev_t       st_rdev;
    off_t       st_size;
    time_t      st_atime;
    time_t      st_mtime;
    time_t      st_ctime;
    blksize_t   st_blksize;
    blkcnt_t    st_blocks;
    char        st_fstype[_ST_FSTYPSZ];
};
  • st_dev: inode가 저장되어 있는 장치의 장치 번호를 저장한다.
  • st_ino: 해당 파일의 inode 번호를 저장한다.
  • st_mode: 파일의 형식과 접근 권한을 저장한다
  • st_nlink: 하드링크의 개수를 저장한다.
  • st_uid: 파일 소유자의 UID를 저장한다.
  • st_gid: 파일 소유 그룹의 GID를 저장한다.
  • st_rdev: 장치 파일이면 주 장치 번호와 부 장치 번호를 저장한다. 장치 파일이 아니면 아무 의미가 없다.
  • st_atime: 마지막으로 파일을 읽거나, 실행한 시각을 저장한다. 이때 시각은 1970년 1월 1일 이후의 시간을 초 단위로 저장한다.
  • st_mtime: 마지막으로 파일의 내용을 변경(쓰기)한 시각을 저장한다.
  • st_ctime: 마지막으로 inode의 내용을 변경한 시각을 저장한다. inode의 내용은 소유자/그룹 변경, 파일 크기 변경, 링크 개수 변경 등을 수행할 때 변경된다.
  • st_blksize: 파일의 내용을 입출력할 때 사용하는 버퍼의 크기를 저장한다.
  • st_blocks: 파일을 512바이트씩 블록으로 나눈 개수를 저장한다.
  • st_fstype: 파일시스템 종류 정보를 저장한다.

stat 함수를 사용해 unix.txt 파일의 정보를 검색하고 출력해 보겠습니다.


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

int main(void) {

    struct stat buf;

    stat("unix.txt", &buf);

    printf("Inode = %d\n", (int)buf.st_ino);
    printf("Mode = %o\n", (unsigned int)buf.st_mode);
    printf("Nlink = %o\n", (unsigned int)buf.st_nlink);
    printf("UID = %d\n", (int)buf.st_uid);
    printf("GID = %d\n", (int)buf.st_gid);
    printf("SIZE = %d\n", (int)buf.st_size);
    printf("Atime = %d\n", (int)buf.st_atime);
    printf("Mtime = %d\n", (int)buf.st_mtime);
    printf("Ctime = %d\n", (int)buf.st_ctime);
    printf("Blksize = %d\n", (int)buf.st_blksize);
    printf("Blocks = %d\n", (int)buf.st_blocks);
    // printf("FStype = %d\n", buf.st_fstype);

    return 0;

}


[root@dev-web ch03]# ./ex3_1_exe

Inode = 134725011

Mode = 100644

Nlink = 1

UID = 0

GID = 0

SIZE = 24

Atime = 1504182768

Mtime = 1504182768

Ctime = 1504182768

Blksize = 4096

Blocks = 8



파일 기술자로 파일 정보 검색: fstat(2)

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

int fstat(int fd, struct stat *buf);

fstat 함수는 파일 경로 대신 현재 열려 있는 파일의 파일 기술자를 인자로 받아 파일 정보를 검색한 후 buf로 지정한 구조체에 저장합니다. fstat 함수를 사용해 파일의 정보를 검색해보겠습니다.


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

int main(void) {
    int fd;
    struct stat buf;

    fd = open("unix.txt", O_RDONLY);
    if(fd == -1) {
        perror("open: unix.txt");
        exit(1);
    }

    fstat(fd, &buf);

    printf("Inode = %d\n", (int)buf.st_ino);
    printf("UID = %d\n", (int)buf.st_uid);
    close(fd);

    return 0;
}

[root@dev-web ch03]# ./ex3_2_exe

Inode = 134725011

UID = 0




stat 구조체의 st_mode 항목에는 파일의 종류와 접근 권한 정보가 저장됩니다. st_mode의 값은 100644와 같이 숫자로 출력됩니다. st_mode 항목의 값을 해석하려면 <sys/stat.h>에 정의된 상수와 매크로를 이용해야 합니다.


st_mode 값의 구조


먼저, st_mode 항목에 저장되는 값의 구조를 알아보겠습니다. 이 구조를 알아야 상수와 매크로의 역할을 이해할 수 있습니다. st_mode 항목의 데이터형인 mode_t는 unsigned int로 정의되어 있습니다. 실제로는 이중 16비트를 사용하며, 아래와 같은 구조로 되어 있습니다.



<sys/stat.h> 파일에 정의된 상수와 매크로는 위 그림에 표현한 구조로 저장된 값과 상수를 AND 연산해서 값을 추출하는 것입니다.



파일의 종류 검색


파일의 종류를 검색하는 데 상수와 매크로를 이용할 수 있습니다.


상수를 이용한 파일 종류 검색

<sys/stat.h>에 파일의 종류 검색과 관련해 정의되어 있는 상수는 아래와 같습니다.


 상수명

 상수값(16진수)

 기능

 S_IFMT

 0xF000

 st_mode 값에서 파일의 종류를 정의한 부분을 가져옴

 S_IFIFO

 0x1000

 FIFO 파일

 S_IFCHR

 0x2000

 문자 장치 특수 파일

 S_IFDIR

 0x4000

 디렉토리

 S_IFBLK

 0x6000

 블록 장치 특수 파일

 S_IFREG

 0x8000

 일반 파일

 S_IFLNK

 0xA000

 심볼릭 링크 파일

 S_IFSOCK

 0xC000

 소켓 파일


S_IFMT 상수의 값이 0xF000이므로 st_mode의 값과 AND 연산하면 파일 종류 부분만 남습니다. 이 값을 다른 상수들과 비교하면 해당 파일의 종류를 알 수 있습니다. unix.txt 파일의 정보 중 st_mode 값에서 파일의 종류를 분석하는 방법을 보여주는 예를 살펴보겠습니다.


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

int main(void) {
    struct stat buf;
    int kind;

    stat("unix.txt", &buf);

    printf("Mode = %o (16진수: %x)\n", (unsigned int)buf.st_mode, (unsigned int)buf.st_mode);

    kind = buf.st_mode & S_IFMT;
    printf("Kind = %x\n", kind);

    switch (kind) {
        case S_IFIFO:
            printf("unix.txt : FIFO\n");
            break;
        case S_IFDIR:
            printf("unix.txt : Directory\n");
            break;
        case S_IFREG:
            printf("unix.txt : Regular File\n");
            break;
    }

    return 0;
}

[root@dev-web ch03]# ./ex3_3_exe

Mode = 100644 (16진수: 81a4)

Kind = 8000

unix.txt : Regular File



매크로를 이용한 파일 종류 검색

<sys/stat.h> 파일에는 상수 외에 매크로도 정의되어 있습니다. 파일의 종류 검색과 관련해 정의되어 있는 매크로는 아래와 같습니다. 이들 매크로는 POSIX 표준입니다.


 매크로명

 매크로 정의

 기 능

 S_ISFIFO(mode)

 (((mode)&0xF000) == 0x1000

 참이면 FIFO 파일

 S_ISCHR(mode)

 (((mode)&0xF000) == 0x2000)

 참이면 문자 장치 특수 파일

 S_ISDIR(mode)

 (((mode)&0xF000) == 0x4000)

 참이면 디렉토리

 S_ISBLK(mode)

 (((mode)&0xF000) == 0x6000)

 참이면 블록 장치 특수 파일

 S_ISREG(mode)

 (((mode)&0xF000) == 0x8000)

 참이면 일반 파일

 S_ISLNK(mode)

 (((mode)&0xF000) == 0xa000)

 참이면 심볼릭 링크 파일

 S_ISSOCK(mode)

 (((mode)&0xF000) == 0xc000)

 참이면 소켓 파일


각 매크로는 인자로 받은 mode 값을 0xF000과 AND 연산을 합니다. AND 연산의 결과를 파일의 종류별로 정해진 값과 비교해 참인지 여부로 파일의 종류를 판단합니다. 


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

int main(void) {
    struct stat buf;

    stat("unix.txt", &buf);

    printf("Mode = %o (16진수: %x)\n", (unsigned int)buf.st_mode, (unsigned int)buf.st_mode);

    if(S_ISFIFO(buf.st_mode)) printf("unix.txt : FIFO\n");
    if(S_ISDIR(buf.st_mode)) printf("unix.txt : Directory\n");
    if(S_ISREG(buf.st_mode)) printf("unix.txt : Regular File\n");

    return 0;
}



파일 접근 권한 검색


파일의 접근 권한도 st_mode의 값으로 알 수 있습니다. 이외에도 사용자가 파일에 대한 접근 권한이 있는지 확인할 수 있는 access 같은 함수가 제공됩니다.


상수를 이용한 파일 접근 권한 검색

st_mode의 값에서 파일의 접근 권한을 검색할 때도 <sys/stat.h>에 정의된 상수를 이용합니다. 아래는 접근 권한 검색에 사용하는 상수를 정리한 표입니다.


 상수명

 상수값

 기능

 S_ISUID

 0x800

 st_mode 값과 AND 연산이 0이 아니면 setuid가 설정됨

 S_ISGID

 0x400

 st_mode 값과 AND 연산이 0이 아니면 setgid가 설정됨

 S_ISVTX

 0x200

 st_mode 값과 AND 연산이 0이 아니면 스티키 비트가 설정됨

 S_IREAD

 00400

 st_mode 값과 AND 연산으로 소유자의 읽기 권한 확인

 S_IWRITE

 00200

 st_mode 값과 AND 연산으로 소유자의 쓰기 권한 확인

 S_IEXEC

 00100

 st_mode 값과 AND 연산으로 소유자의 실행 권한 확인


읽기/쓰기/실행 권한을 추출하는 상수값이 8진수라는 점을 주의해야합니다. 표를 보면 소유자의 접근 권한은 추출할 수 있지만, 그룹이나 기타 사용자의 접근 권한에 해당하는 상수는 별도로 정의되어 있지 않음을 알 수 있습니다. 그럼 소유자 외의 사용자에 대한 접근 권한은 어떻게 알 수 있을까요? st_node의 값을 왼쪽으로 3비트 이동시키거나 상수 값을 오른쪽으로 3비트 이동시킨 후 AND 연산을 수행하면 그룹의 접근 권한을 알수 있습니다.


기타 사용자의 경우 st_mode의 값을 왼쪽으로 6비트 이동시키거나 상수값을 오른쪽으로 6비트 이동시킨 후 AND 연산을 수행하면 됩니다. POSIX에서는 이와 같이 번거롭게 시프트 연산을 하는 대신 직접 AND 연산이 가능한 다른 상수를 정의했습니다. 소유자, 그룹, 기타 사용자별로 읽기/쓰기/실행 권한에 대한 상수가 개별적으로 정의되어 있음을 알 수 있습니다.


 상수명

 상수값

 기능

 S_IRWXU 00700

 소유자 읽기/쓰기/실행 권한

 S_IRUSR 00400 소유자 읽기 권한
 S_IWUSR 00200

 소유자 쓰기 권한

 S_IXUSR 00100

 소유자 실행 권한

 S_IRWXG 00070 그룹 읽기/쓰기/실행 권한

 S_IRGRP

 00040

 그룹 읽기 권한

 S_IWGRP 00020

 그룹 쓰기 권한

 S_IXGRP 00010 그룹 실행 권한
 S_IRWXO

 00007

 기타 사용자 읽기/쓰기/실행 권한

 S_IROTH 00004 기타 사용자 읽기 권한
 S_IWOTH 00002 기타 사용자 쓰기 권한

 S_IXOTH

 00001

 기타 사용자 실행 권한


상수를 이용해 unix.txt 파일의 읽기 권한을 검색해 보겠습니다.


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

int main(void) {
    struct stat buf;

    stat("unix.txt", &buf);
    printf("Mode = %o (16진수: %x)\n", (unsigned int)buf.st_mode, (unsigned int)buf.st_mode);

    if((buf.st_mode & S_IREAD) != 0) printf("unix.txt : user has a read permission\n");
    if((buf.st_mode & (S_IREAD >> 3)) != 0) printf("unix.txt: group has a read permission\n");
    if((buf.st_mode & S_IROTH) != 0) printf("unix.txt : other have a read permission\n");

    return 0;
}

[root@dev-web ch03]# ./ex3_5_exe

Mode = 100644 (16진수: 81a4)

unix.txt : user has a read permission

unix.txt: group has a read permission

unix.txt : other have a read permission



함수를 사용한 접근 권한 검색: access(2)

#include <unistd.h>

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

파일의 접근 권한을 검색할 수 있는 시스템 호출로 access 함수가 있습니다. access 함수는 path에 지정된 파일이 amode로 지정한 권한을 지니고 있는지 확인하고 리턴합니다. 단, access 함수는 유효 사용자 ID가 아닌 실제 사용자에 대한 접근 권한만 확인해 줍니다.


두번째 인자인 amode에 사용할 수 있는 상수는 다음과 같으며 <unistd.h>에 정의되어 있습니다. 필요에 따라 이들 상수를 OR로 연결해 사용할 수도 있습니다.


access 함수는 접근 권한이 있으면 0을, 오류가 있을 경우 -1을 리턴합니다. 오류 메시지가 ENOENT일 경우 파일이 존재하지 않아서 발생한 오류입니다.



파일 접근 권한 변경


파일의 접근 권한을 변경할 때 사용하는 명령은 chmod입니다. 이에 해당하는 시스템 호출 또한 chmod입니다. chmod 함수를 사용해 프로그램에서 파일의 접근 권한을 변경할 수 있습니다. fchmod 함수로도 접근 권한을 변경할 수 있습니다.


파일명으로 접근 권한 변경: chmod(2)

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

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

c

hmod 함수는 접근 권한을 변경할 파일의 경로를 받아 mode에 지정한 상수값으로 권한을 변경합니다. 특수 접근 권한을 변경할 때는 S_ISUID, S_ISGID, S_ISVTX를 이용합니다. 소유자/그룹/기타 사용자의 접근 권한을 변경할 때는 정의된 상수를 이용합니다. 기본 접근 권한과 관계없이 접근 권한을 모두 새로 지정하려면 상수를 이용해 권한을 생성한 후 인자로 지정합니다. 예를 들면, 다음과 같이 사용합니다.

chmod(path, S_IRWXU);
chmod(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH);


기존 접근 권한을 변경해 권한을 조정할 수도 있습니다. 이때 stat 함수로 기존 접근 권한을 읽고 여기에 더하거나 뺄 수 있습니다. 접근 권한을 더할 때는 OR 연산자를 사용합니다. 예를 들어, 그룹의 쓰기 권한을 추가하려면 다음과 같이 합니다.

mode |= S_IWGRP;


접근 권한을 제거하려면, 제거하려는 권한의 상수값을 NOT 연산한 후 AND 연산을 실행하면 됩니다. 예를 들어, 기타 사용자의 읽기 권한을 제거하려면 다음과 같이 합니다.

mode &= ~(S_IROTH);


물론 mode 값을 변경한 후 chmod 함수를 호출해야 변경된 접근 권한이 적용됩니다.

chmod(path, mode);


fchmod 함수는 접근 권한을 변경할 파일의 파일 기술자를 받아 mode에 미리 정의한 상수값으로 변경할 권한을 지정합니다. chmod 함수는 파일의 경로를 받지만, fchmod 함수는 이미 열려 있는 파일의 파일 기술자를 받아 접근 권한을 변경합니다. 접근 권한 지정 방법은 chmod 함수와 같습니다. 보통 chmod 함수를 많이 사용합니다.




링크는 이미 있는 파일이나 디렉토리에 접근할 수 있는 새로운 이름을 의미합니다. 같은 파일이나 디렉토리지만 여러 이름으로 접근할 수 있게 해주는 것입니다. 예를 들어, 솔라리스에서 하드 디스크를 나타내는 장치 파일은 /devices/pci@0,0/pci-ide@7,1/ide@0/cmdk@0,0:q와 같이 아주 복잡한 이름을 사용합니다. 사용자가 디스크에 접근할 때 이렇게 복잡한 파일명을 사용한다면 아주 난감할 것입니다. 이럴 때 이 파일에 접근할 수 있는 다른 파일명이 있다면 편리할 것입니다. 솔라리스에서는 디스크에 대한 링크로 /dev/dsk/c0t0d0s0을 제공합니다. 이러한 링크명의 사용은 플랫폼에 따라 실제 장치 파일명이 다르더라도 같은 링크명을 제공하면 동일한 방식으로 접근할 수 있다는 장점이 있습니다. 링크 생성 기능으로 하드 링크와 심볼릭 링크라는 두 가지 링크를 만들 수 있습니다. 링크는 ln 명령으로 생성하며, 프로그램에서도 함수를 사용해 하드 링크와 심볼릭 링크를 생성할 수 있습니다.



하드 링크


하드 링크는 파일에 접근할 수 있는 파일명을 새로 생성하는 것입니다. 하드 링크는 기존 파일과 동일한 inode를 사용합니다. 하드 링크를 생성하면 inode에 저장된 링크 개수가 증가합니다.


하드 링크 생성 : link(2)

#include <unistd.h>

int link(const char *existing, const char *new);

하드 링크를 생성할 때는 link 함수를 사용합니다. link 함수는 기존 파일의 경로를 첫 번째 인자(existing)로 받고, 새로 생성할 하드 링크 파일의 경로를 두번째 인자(new)로 받습니다. 하드 링크는 같은 파일 시스템에 존재해야 하므로 두 경로를 반드시 같은 파일 시스템으로 지정해야 합니다. link 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



심볼릭 링크


심볼릭 링크는 기존 파일에 접근할 수 있는 다른 파일을 만듭니다. 기존 파일과 다른 inode를 사용하며, 기존 파일의 경로를 저장합니다. 심볼릭 링크를 생성할 때는 symlink 함수를 사용합니다. 심볼릭 링크의 정보를 검색할 때는 stat 함수가 아닌 lstat 함수를 사용합니다. 심볼릭 링크 자체가 담고 있는 내용은 read-link 함수로 읽어올 수 있습니다.



심볼릭 링크 생성 : symlink(2)

#include <unistd.h>

int symlink(const char *name1, const char *name2);

symlink 함수는 기존 파일의 경로를 첫번째 인자(name1)로 받고, 새로 생성한 심볼릭 링크의 경로를 두번째 인자(name2)로 받습니다. 심볼릭 링크는 기존 파일과 다른 파일시스템에도 생성할 수 있습니다. symlink 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.


심볼릭 링크의 정보 검색 : lstat(2)

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

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

lstat 함수는 심볼릭 링크 자체의 파일 정보를 검색합니다. 심볼릭 링크를 stat 함수로 검색하면 원본 파일에 대한 정보가 검색된다는 점을 유념하는 것이 좋습니다. 첫번째 인자로 심볼릭 링크가 온다는 것을 제외하면 stat 함수와 동일한 방식으로 사용합니다.


vi 편집기로 심볼릭 링크를 열면 원본 파일이 열립니다. 그럼 심볼릭 링크의 데이터 블록에 저장된 내용은 무엇일까요? 심볼릭 링크 자체의 데이터를 읽으려면 readlink 함수를 사용해야 합니다. readlink 함수는 심볼릭 링크의 경로를 받아 해당 파일의 내용을 읽습니다. 호출시 읽어온 내용을 저장할 버퍼와 해당 버퍼의 크기를 인자로 지정합니다. readlink 함수는 수행을 성공하면 읽어온 데이터의 크기를, 실패하면 -1을 리턴합니다.


원본 파일의 경로 읽기: realpath(3)

#include <stdlib.h>

char *realpath(const char *restrict file_name, char *restrict resolved_name);

심볼릭 링크가 가리키는 원본 파일의 실제 경로명을 알고 싶으면 realpath 함수를 사용합니다. realpath 함수는 심볼릭 링크명을 받아 실제 경로명을 resolved_name에 저장하며, 성공하면 실제 경로명이 저장된 곳의 주소를, 실패하면 널 포인터를 리턴합니다.



디렉토리 관련 함수


유닉스에서는 디렉토리도 파일로 취급합니다. 파일을 생성하는 것처럼 디렉토리도 함수를 사용해 생성하고 삭제할 수 있습니다. 디렉토리를 이동하거나 디렉토리의 내용을 읽을 수도 있습니다. 이 절에서는 디렉토리 관련 함수를 알아보겠습니다.



디렉토리 생성과 삭제


디렉토리를 생성할 때는 mkdir 함수를 사용하고, 디렉토리를 삭제할 때는 rmdir 함수를 사용합니다.


디렉토리 생성: mkdir(2)

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

int mkdir(const char *path, mode_t mode);

디렉토리를 생성하는 mkdir 명령과 이름이 같습니다. mkdir 함수는 생성하려는 디렉토리명을 포함한 경로를 받고, 생성하는 디렉토리의 기본 접근 권한을 지정합니다. mkdir 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.


디렉토리 삭제: rmdir(2)

#include <unistd.h>

int rmdir(const char *path);

디렉토리를 삭제하는 함수는 rmdir입니다. 명령인 rmdir과 동일한 기능을 수행합니다. rmdir 함수는 삭제하려는 디렉토리명을 포함한 경로를 인자로 받습니다. rmdir 함수로 삭제하려는 디렉토리는 .과 ..을 제외하고 비어 있어야 합니다. rmdir 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



디렉토리 관리


디렉토리 관리는 디렉토리명 변경, 현재 작업 디렉토리 위치 검색, 디렉토리 이동과 같은 기능을 포함합니다.



디렉토리명 변경: rename(2)

#include <stdio.h>

int rename(const char *old, const char *new);

rename 함수는 이전 디렉토리명을 새로운 디렉토리명으로 바꿉니다. 만약 두 번째 인자로 지정한 이름이 이미 존재하면 해당 디렉토리를 지웁니다. 실행 도중 오류가 발생하면 원본과 새로운 디렉토리명이 모두 남습니다. rename 함수는 수행에 성공하면 0을, 실패하면 -1을 리턴합니다. 파일명을 변경하는데도 rename 함수를 사용할 수 있습니다.



현재 작업 디렉토리 위치: getcwd(3)

#include <unistd.h>

int *getcwd(char *buf, size_t size);

현재 디렉토리의 위치를 알려주는 명령은 pwd입니다. 프로그램에서 디렉토리의 위치를 알아내는 데는 getcwd 함수를 이용합니다. getcwd 함수는 현재 디렉토리의 절대 경로를 저장할 버퍼의 주소와 버퍼의 크기를 인자로 받습니다. 만약 버퍼의 주소가 널이면, getcwd는 직접 malloc으로 메모리를 할당하고 주소를 리턴합니다. getcwd는 수행 도중 오류가 발생하면 널을 리턴합니다.



디렉토리 이동: chdir(2)

#include <unistd.h>

int chdir(const char *path);

작업 디렉토리를 이동하려면 cd 명령을 사용합니다. 프로그램에서 디렉토리를 이동하는 데는 chdir 함수를 사용합니다. chdir 함수는 이동하려는 디렉토리의 경로를 인자로 받는데, 절대 경로와 상대 경로 모두 사용할 수 있습니다. chdir 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



디렉토리 정보 검색


디렉토리의 정보를 검색하려면 해당 디렉토리를 열고, 정보를 읽을 수 있어야 합니다. 파일을 열고 내용을 읽을 때 사용하는 함수와 마찬가지로 디렉토리를 대상으로 이와 유사한 기능을 수행하는 함수가 있습니다.



디렉토리 열기: opendir(3)

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

DIR *opendir(const char *dirname);

opendir 함수는 인자로 지정한 디렉토리를 읽기 전용으로 엽니다. opendir 함수는 수행을 성공하면 열린 디렉토리를 가리키는 DIR 포인터를 리턴합니다. DIR은 열린 디렉토리에 관한 정보를 담고 있는 구조체로, <dirent.h> 파일에 정의되어 있습니다. opendir 함수는 수행을 실패하면 널을 리턴합니다.



디렉토리 닫기: closedir(3)

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

DIR *closedir(DIR *dirp);

closedir 함수는 인자로 지정한 DIR 포인터가 가리키는 디렉토리를 닫습니다. closedir 함수는 수행을 성공하면 0을, 실패하면 -1을 리턴합니다.



디렉토리 정보 읽기: readdir(3)

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

struct dirent *readdir(DIR *dirp);

readdir 함수는 인자로 지정한 DIR 포인터가 가리키는 디렉토리의 내용을 한 번에 하나씩 읽어옵니다. 디렉토리의 내용을 차례로 읽어오고 더 이상 읽을 것이 없으면 널을 리턴합니다. readdir 함수는 디렉토리에 있는 항목의 정보를 담은 dirent 구조체를 리턴합니다.


dirent 구조체는 <sys/dirent.h> 파일에 정의되어 있습니다.

typedef struct dirent {
	ino_t		d_ino;		// readdir 함수로 읽어온 항목의 inode 번호
	off_t		d_off;		// 디렉토리 내에 있는 항목의 오프셋
	unsigned short	d_reclen;	// 디렉토리 항목의 레코드 길이
	char		d_name[1];	// 항목의 이름
} dirent_t;

readdir로 읽어온 항목에 관한 자세한 정보를 알고 싶으면 stat 함수를 사용해야 합니다. 이번에는 hanbit 디렉토리에서 읽어온 정보의 상세 정보를 검색해보겠습니다. 실행 결과를 쉽게 확인하기 위해 hanbit 디렉토리에 .과 ..외에 han.c 파일을 만들어 둡니다.


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

// 디렉토리 항목의 상세 정보 검색하기
int main(void) {
    DIR *dp;
    struct dirent *dent;
    struct stat sbuf;
    char path[BUFSIZ];

    if((dp = opendir("hanbit")) == NULL) {
        perror("opendir: hanbit");
        exit(1);
    }

    while((dent = readdir(dp))) {
        if (dent->d_name[0] == '.') continue;
        else break;
    }

    sprintf(path, "hanbit/%s", dent->d_name);
    stat(path, &sbuf);

    printf("Name : %s\n", dent->d_name);
    printf("Inode(dirent) : %d\n", (int)dent->d_ino);
    printf("Inode(stat) : %d\n", (int)sbuf.st_ino);
    printf("Mode : %o\n", (unsigned int)sbuf.st_mode);
    printf("Uid : %d\n", (int)sbuf.st_uid);

    closedir(dp);

    return 0;

}

# ./ex3_16_exe

Name : han.c

Inode(dirent) : 201482417

Inode(stat) : 201482417

Mode : 100644

Uid : 0



디렉토리 오프셋: telldir(3), seekdir(3), rewinddir(3)

파일을 열고 내용을 읽거나 쓸 때는 파일 오프셋이 이동합니다. 마찬가지로 디렉토리를 열고 정보를 읽으면 디렉토리 오프셋이 이동합니다. 오프셋의 위치를 읽고, 이동시키는 함수로 telldir, seekdir, rewinddir이 있습니다.


#include <dirent.h>

long telldir(DIR *dirp);
void seekdir(DIR *dirp, long loc);
void rewinddir(DIR *dirp);



요약


유닉스 파일의 특징

  • 유닉스에서는 일반 파일뿐만 아니라, 디렉토리, 장치도 파일로 접근한다.
  • 파일의 종류: 일반 파일, 특수 파일, 디렉토리
  • 파일의 구성 요소: 파일명, inode, 데이터 블록


파일 관련 함수

유닉스에서 파일의 정보는 inode에서 읽어옵니다. inode 정보의 검색과 파일에 대한 접근권한 확인과 변경을 위해 사용할 수 있는 함수들이 제공됩니다.

  • 파일 정보 검색을 위한 구조체: struct stat(stat.h에 정의)
  • 파일 접근 권한: stat 구조체의 st_mode 항목에 저장됨
  • 미리 정의된 상수와 매크로를 이용해 st_mode 항목의 값을 해석함


링크 파일 관련 함수

  • 링크는 이미 있는 파일이나 디렉토리에 접근할 수 있는 새로운 이름을 뜻한다.
  • 하드 링크는 기존의 파일과 같은 inode를 사용한다.
  • 심볼릭 링크는 기존의 파일에 접근할 수 있는 다른 파일을 만든다. 기존 파일과 다른 inode를 사용하며, 기존 파일의 경로를 저장하고 있다.


디렉토리 관련 함수

  • 유닉스에서는 디렉토리도 파일로 취급하며, 함수를 사용하여 디렉토리를 생성하고 삭제하고, 디렉토리를 이동하거나 디렉토리의 내용을 읽을 수 있다.