본문 바로가기
Computer Science/UNIX & Linux

[UNIX/Linux] ep2) 파일 다루기

by 클레어몬트 2024. 9. 24.

"... 은 모든 디렉터리에 항상 존재하는 파일 이름이며, 디렉터리가 생성될 때 자동적으로 포함된다"

 

ㅁ디렉터리 허가

- 읽기(r): 디렉터리 내의 파일이나 부디렉터리의 이름을 리스트

- 쓰기(w): 디렉터리 내의 파일을 제거하거나 새로운 파일을 생성

- 실행(x): cd 혹은 chdir 로 디렉터리 내부로 들어갈 수 있음

 

 

(리눅스 파일 함수)

리눅스 파일 함수 정리

 

 

[파일 정보 검색 함수] - inode 정보 검색

stat() 함수로 검색한 inode 정보는 stat 구조체에 저장되어 리턴된다

stat 구조체의 세부 구조는 man -s 2 stat 으로 확인할 수 있다 (stat은 status의 약자이다)

struct stat {
    dev_t st_dev; // 파일이 저장되어 있는 장치의 번호를 저장
    ino_t st_ino; // 파일의 inode 번호를 저장
    mode_t st_mode; // 파일의 종류와 접근권한을 저장
    nlink_t st_nlink; // 하드 링크의 개수를 저장
    uid_t st_uid; // 파일 소유자의 UID를 저장 (User ID)
    gid_t st_gid; // 파일 소유 그룹의 GID를 저장 (Group ID)
    dev_t st_rdev; // 장치 파일이면 주 장치 번호와 부 장치 번호를 저장 (장치 파일이 아니면 의미 X)
    off_t st_size; 
    blksize_t st_blksize; // 파일 내용을 입출력할 때 사용하는 버퍼의 크기를 저장
    blkcnt_t st_blocks; // 파일에 할당된 파일 시스템의 블록 수 (블록 크기: 512byte)
    
    // timespec: 초와 나노초를 저장하기 위한 구조체
    // 리눅스 커널 2.6부터 나노초 수준의 정밀도를 지원
    struct timespec st_atim; // 마지막으로 파일을 읽거나 실행한 시각
    struct timespec st_mtim; // 마지막으로 파일의 내용을 변경(쓰기)한 시각
    struct timespec st_ctim; // 마지막으로 inode의 내용을 변경한 시각
    
    // 리눅스 커널 2.6 이전 버전과의 호환성을 위해 #define 으로 타임스탬프 값을 맵핑
    #define st_atime st_atim.tv_sec
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
};

 

ㅇint stat(const char* pathname, struct stat* statbuf): 파일명으로 파일 정보 검색

- pathname : 파일명

- statbuf : 검색한 파일 정보를 저장할 구조체 주소

성공하면 0을 반환하고 stat 구조체에 파일 정보를 저장, 실패하면 -1을 반환

pathname에 지정한 파일의 정보를 검색한다

※ stat() 함수로 파일의 정보를 검색할 때 파일에 대한 읽기/쓰기/실행 권한이 반드시 있어야 하는 것은 아니지만, 파일에 이르는 경로의 각 디렉터리에 대한 읽기 권한은 있어야 한다

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

int main() {
    struct stat statbuf;
    stat("linux.txt", &statbuf); // linux.txt 파일의 정보를 읽어옴
    
    printf("inode = %d\n", (int)statbuf.st_ino);
    printf("mode = %o\n", (unsigned int)statbuf.st_mode);
    printf("Nlink = %o\n", (unsigned int)statbuf.st_nlink);
    printf("UID = %d\n", (int)statbuf.st_uid);
    printf("GID = %d\n", (int)statbuf.st_gid);
    printf("SIZE = %d\n", (int)statbuf.st_size);
    printf("blksize = %d\n", (int)statbuf.st_blksize);
    printf("blocks = %d\n", (int)statbuf.st_blocks);

    printf("** timespec style\n");
    printf("Atime = %d\n", (int)statbuf.st_atim.tv_sec);
    printf("Mtime = %d\n", (int)statbuf.st_mtim.tv_sec);
    printf("Ctime = %d\n", (int)statbuf.st_ctim.tv_sec);
    printf("** old style\n");
    printf("Atime = %d\n", (int)statbuf.st_atime);
    printf("Mtime = %d\n", (int)statbuf.st_mtime);
    printf("Ctime = %d\n", (int)statbuf.st_ctime);

    return 0;
}

 

 

ㅇint fstat(int fd, struct stat* statbuf): 파일 기술자(fd)로 파일 정보 검색

- fd : 열려있는 파일의 파일 기술자

statbuf : 검색한 파일 정보를 저장할 구조체 주소

성공하면 0을 반환하고 stat 구조체에 파일 정보를 저장, 실패하면 -1을 반환

파일경로 대신 현재 열려있는 파일의 파일 기술자를 인자로 받아 파일 정보를 검색한 후 statbuf로 지정한 구조체에 저장

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

int main() {
    int fd;
    struct stat statbuf;

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

    fstat(fd, &statbuf); // linux.txt 파일의 정보를 읽어옴

    printf("inode = %d\n", (int)statbuf.st_ino);
    printf("UID = %d\n", (int)statbuf.st_uid);
    clsoe(fd);

    return 0;
}

 

 

ㅁst_mode의 구조

st_mode 항목의 데이터형인 mode_t 는 unsigned int 로 정의되어 있다

실제로는 16비트를 사용한다

sys/stat.h 파일에 정의된 상수와 매크로는 구조로 저장된 값과 상수를 AND 연산한 값을 추출하는 것

 

st_mode의 bit 구조

 

파일의 소유자는 사용자 식별 번호로 구별한다

유효 사용자 식별 번호(EUID, Effective User-ID): 파일에 대해 실제 소유권을 갖는 사용자의 식별 번호

진짜 사용자 식별 번호(RUID, Real User-ID): 실제로 프로세스를 갖는 사용자의 식별 번호

※ 유효 그룹 식별 번호(EGID, Effective Group-ID)와 진짜 그룹 식별 번호(RGID, Real Group-ID)는 대부분 동일하다

 

04000(SUID, Set User-ID): 생성된 프로세스에게 그 프로세스를 시작시킨 사용자의 UID 대신 파일 소유자의 유효 사용자 식별 번호를 부여

02000(SGID, Set Group-ID): 생성된 프로세스에게 그 프로세스를 시작시킨 그룹의 GID 대신 파일 그룹의 유효 사용자 식별 번호를 부여

01000(Sticky Bit): 공유 디렉터리(/tmp)에 대한 접근 권한 or 텍스트-이미지를 swap 영역에 남겨둠(과거 리눅스)

 

 

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

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

int main() {
    struct stat statbuf;
    stat("linux.txt", &statbuf);
    printf("mode = %o\n", (unsigned int)statbuf.st_mode); // st_mode 값을 8진수로 출력

    int kind = statbuf.st_mode & S_IFMT; // AND(&) 연산을 kind 변수에 저장
    printf("kind = %o\n", kind);
    switch (kind) {
        case S_IFLNK:
            printf("linux.txt: symbolic link\n");
            break;
        case S_IFDIR:
            printf("linux.txt: directory\n");
            break;
        case S_IFREG: // linux.txt 파일은 일반 파일임을 알 수 있음
            printf("linux.txt: regular file\n");
            break;
    }

    return 0;
}

 

 

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

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

int main() {
    struct stat statbuf;
    stat("linux.txt", &statbuf);
    printf("mode = %o\n", (unsigned int)statbuf.st_mode); // st_mode 값을 8진수로 출력

    if (S_ISLNK(statbuf.st_mode)) {
        printf("linux.txt: symbolic link\n");
    }
    if (S_ISDIR(statbuf.st_mode)) {
        printf("linux.txt: directory\n");
    }
    if (S_ISREG(statbuf.st_mode)) {
        printf("linux.txt: regular file\n");
    }

    return 0;
}

 

 

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

st_mode의 값을 왼쪽으로 3비트 이동시키거나 상수값을 오른쪽으로 3비트 이동시킨 후 AND(&) 연산을 수행하면

"그룹의 접근 권한을 알 수 있음"

st_mode & (S_IREAD >> 3)

 

 

POSIX에서는 이와 같이 번거롭게 시프트 연산을 하는 대신 직접 AND(&) 연산이 가능한 다른 상수를 정의한다

POSIX: IEEE가 제정한 UNIX API 규격

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

int main() {
    struct stat statbuf;
    stat("linux.txt", &statbuf);
    printf("mode = %o\n", (unsigned int)statbuf.st_mode); // st_mode 값을 8진수로 출력

    if ((statbuf.st_mode & S_IREAD) != 0) { // 소유자의 읽기 권한을 확인
        printf("linux.txt: user has a read permission\n");
    }
    if (statbuf.st_mode & (S_IREAD >> 3) != 0) { // 그룹의 읽기 권한을 확인
        printf("linux.txt: group has a read permission\n");
    }
    if ((statbuf.st_mode & S_IROTH) != 0) { // 기타 사용자의 읽기 권한을 확인
        printf("linux.txt: other have a read permission\n");
    }

    return 0;
}

 

 

[파일 접근 권한 함수] - inode 접근 권한 정보 확인

ㅇint access(const char* pathname, int mode): 함수를 이용한 접근권한 검색

- pathname : 접근권한을 알고자 하는 파일의 경로

- mode : 접근권한

접근권한이 있으면 0을 반환하고 오류가 있으면 -1을 반환

pathname에 지정된 파일이 mode로 지정한 권한을 지니고 있는지 확인한다

유효 사용자 ID가 아닌 실제 사용자 ID에 대한 접근권한만 확인할 수 있다

※ ENOENT: 해당 파일이 존재하지 않거나 심볼릭 링크의 경우 원본 파일이 없음

※ EACCES: 접근권한이 없음

 

(두 번째 인자 mode 에 사용할 수 있는 상수)

R_OK : 읽기 권한 확인

W_OK : 쓰기 권한 확인

X_OK : 실행 권한 확인

F_OK : 파일이 존재하는지 확인

#include <stdio.h>
#include <sys/errno.h>
#include <unistd.h>

extern int errno;

int main() {
    int perm; // permission

    // F_OK를 지정해 linux.bak 파일이 존재하는지 확인
    // errno 변수에 저장된 메시지가 ENOENT 라면 파일이 없다는 의미
    if (access("linux.bak", F_OK) == -1 && errno == ENOENT) { 
        printf("linux.bak: file not exist\n");
    }

    perm = access("linux.txt", R_OK); // R_OK로 읽기 권한 여부 검색

    if (perm == 0) {
        printf("linux.txt: read permission is permitted\n");
    }
    else if (perm == -1 && errno == EACCES) {
        printf("linux.txt: read permission is not permitted\n");
    }

    return 0;
}

 

 

ㅇint chmod(const char* pathname, mode_t mode): 파일명으로 접근권한 변경

- pathname : 접근 권한을 변경하려는 파일의 경로

- mode : 접근 권한 파일의 접근 권한을 검색할 수 있는 시스템 호출

접근 권한을 변경할 파일의 경로를 받아 mode에 지정한 상수값으로 권한을 변경한다

소유자/그룹/기타 사용자의 접근권한을 변경할 때는 상수를 이용

특수 접근권한을 변경할 때는 S_ISUID, S_ISGID, S_ISVTX를 이용

 

기존 접근권한과 관계없이 접근권한을 모두 새로 지정하려면 상수를 이용해 권한을 생성한 후 인자로 지정

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

 

기존 접근 권한을 변경해 권한을 조정할 수도 있음

mode |= S_IWGRP;

 

접근 권한을 제거하려면 제거하려는 권한의 상수값을 NOT 연산한 후 AND 연산을 실행

mode &= ~(S_IROTH);

 

mode 값을 변경한 후 chmod() 함수를 호출해야 변경된 접근 권한이 적용

chmod(pathname, mode);

 

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

int main() {
    struct stat statbuf;

    // 기존 권한에 관계없이 linux.txt 파일의 권한을 변경
    // 소유자는 읽기/쓰기/실행 권한, 그룹은 읽기/실행 권한, 기타 사용자는 읽기 권한(754)으로 변경
    chmod("linux.txt", S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH);

    stat("linux.txt", &statbuf);
    printf("1. mode = %o\n", (unsigned int)statbuf.st_mode);

    statbuf.st_mode |= S_IWGRP; // st_mode에 그룹 쓰기 권한 추가
    statbuf.st_mode &= ~(S_IROTH); // st_mode에 기타 사용자 읽기 권한 제거

    chmod("linux.txt", statbuf.st_mode); // 앞서 설정한 값으로 chmod() 실행

    stat("linux.txt", &statbuf);
    printf("2. mode = %o\n", (unsigned int)statbuf.st_mode);
}
// 실행 결과: linux.txt 파일의 접근 권한이 변경

linux.txt 파일의 접근 권한이 변경됨을 알 수 있다

 

 

ㅇint fchmod(int fd, mode_t mode): 파일 기술자(fd)로 접근권한 변경

- fd : 열려있는 파일의 파일 기술자

- mode : 접근권한

접근권한을 변경할 파일의 파일 기술자(fd)를 받아 mode에 미리 정의한 상수값으로 변경할 권한을 지정

이미 열려있는 파일의 파일 기술자를 받아 접근권한을 변경시킨다

(접근 권한 지정 방법은 chmod() 함수와 같음)

 

 

[링크 함수] 

ㅁ링크(link): 기존 파일이나 기존 디렉터리에 접근할 수 있는 새로운 이름을 의미(여러 이름으로 접근할 수 있게 하는 것)

링크의 방법에는 하드 링크심볼릭 링크(소프트 링크)가 있다

링크 기능의 장점: 이전 시스템과의 호환성 유지, 경로가 복잡한 파일에 간단한 경로를 제공

 

1. 하드 링크: 파일에 접근할 수 있는 파일명을 새로 생성하는 것

기존 파일과 동일한 inode를 사용하며, 하드 링크를 생성하면 inode에 저장된 링크 개수가 증가한다

※ 터미널 명령어: ln testdata.txt testdata.ln

 

2. 심볼릭 링크(소프트 링크): 기존 파일에 접근할 수 있는 다른 파일을 만드는 것

기존 파일과 다른 inode를 사용하며, 기존 파일의 경로를 저장한다

※ 터미널 명령어: ln -s testdata.txt testdata.sym

 

ㅇint link(const char* oldpath, const char* newpath): 하드 링크를 생성할 때 사용

- oldpath : 기존 파일의 경로

- newpath : 새로 생성할 링크의 경로

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

(하드 링크는 같은 파일 시스템에 있어야 하므로 두 경로를 반드시 같은 파일 시스템으로 지정해야 한다)

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

int main() {
    struct stat statbuf;

    // 하드 링크를 생성하기 이전의 링크 개수를 확인
    stat("linux.txt", &statbuf); 
    printf("before link count = %d\n", (int)statbuf.st_nlink);

    link("linux.txt", "linux.ln");

    // 하드 링크를 생성한 이후의 링크 개수를 확인
    stat("linux.txt", &statbuf);
    printf("after link count = %d\n", (int)statbuf.st_nlink);

    return 0;
}
// 실행결과: linux.ln 파일이 생성되었고, 링크 개수가 변경됨
// linux.txt와 linux.ln 파일의 inode가 1048600으로 동일하다는 것도 알 수 있음

하드 링크: inode가 1048600으로 동일

 

 

ㅇint symlink(const char* target, const char* linkpath): 심볼릭 링크를 생성할 때 사용

- target : 기존 파일의 경로

- linkpath : 새로 생성할 심볼릭 링크의 경로

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

(심볼릭 링크는 기존 파일과 다른 파일 시스템에도 생성할 수 있다)

#include <unistd.h>

int main() {
    // linux.txt 파일의 심볼릭 링크인 linux.sym 생성
    symlink("linux.txt", "linux.sym");

    return 0;
}
// 실행결과: linux.sym 파일이 생성
// 심볼릭 링크를 생성했지만 기존 파일에는 아무 변화가 없음을 알 수 있음

기존 파일에 변화가 아무것도 없다

 

 

ㅇint lstat(const char* pathname, struct stat* statbuf): 심볼릭 링크 자체의 파일 정보를 검색

- pathname : 심볼릭 링크의 경로

- statbuf : 새로 생성할 링크의 경로

※ 심볼릭 링크를 stat() 함수로 검색하면 원본 파일에 대한 정보가 검색된다는 점에 주의

(첫 번째 인자로 심볼릭 링크가 온다는 것을 제외하면 stat() 함수와 동일한 방식으로 사용된다)

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

int main() {
    struct stat statbuf;

    // 기존 linux.txt 파일의 정보를 stat() 함수로 검색해 링크 개수와 inode 번호를 출력
    printf("1. stat : linux.txt ---\n");
    stat("linux.txt", &statbuf);
    printf("linux.txt : Link Count = %d\n", (int)statbuf.st_nlink);
    printf("linux.txt : Inode = %d\n", (int)statbuf.st_ino);

    // stat() 함수를 사용해 심볼릭 링크 파일인 linux.sym의 정보를 검색하고 링크 개수와 inode 번호를 출력
    printf("2. stat : linux.sym ---\n");
    stat("linux.sym", &statbuf);
    printf("linux.sym : Link Count = %d\n", (int)statbuf.st_nlink);
    printf("linux.sym : Inode = %d\n", (int)statbuf.st_ino);

    // lstat() 함수를 사용해 심볼릭 링크 파일인 linux.sym의 정보를 검색하고 링크 개수와 inode 번호를 출력
    printf("3. lstat : linux.sym ---\n");
    lstat("linux.sym", &statbuf);
    printf("linux.sym : Link Count = %d\n", (int)statbuf.st_nlink);
    printf("linux.sym : Inode = %d\n", (int)statbuf.st_ino);

    return 0;
}
// 실행결과: 첫번째와 두번째의 검색 정보가 동일함
// 즉, 심볼릭 링크 정보를 stat() 함수로 검색하면 원본 파일의 정보가 검색되는 것을 알 수 있음
// 세번째 lstat() 함수로 검색한 심볼릭 링크 정보는 링크 파일 자체에 대한 정보로 다른 값이 출력됨

첫번째와 두번째는 같은 걸 알 수 있다

 

 

ㅇssize_t readlink(const char* pathname, char* buf, size_t bufsiz): 심볼릭 링크의 내용 읽기

- pathname : 심볼릭 링크의 경로

- buf : 읽어온 내용을 저장할 버퍼

- bufsiz : 버퍼의 크기

성공하면 읽어 온 데이터의 크기(바이트 수)를 반환, 실패하면 –1을 반환

심볼릭 링크의 경로를 받아 해당 파일의 내용을 읽는다

(호출 시 읽은 내용을 저장할 버퍼와 해당 버퍼의 크기를 인자로 지정)

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

int main() {
    char buf[BUFSIZ];
    int n;

    // linux.sym 파일의 내용을 readlink() 함수로 읽어 buf에 저장
    n = readlink("linux.sym", buf, BUFSIZ);
    if (n == -1) {
        perror("readlink");
        exit(1);
    }
    buf[n] = '\0'; // buf의 끝에 NULL 문자를 추가
    printf("linux.sym : READLINK = %s\n", buf); // 저장된 문자를 출력
    
    return 0;
}
// 심볼릭 링크를 ls -l 명령으로 확인해보자
// -> 다음에 오는 원본 파일의 경로가 심볼릭 링크 데이터 블록에 저장된 내용이다
// 심볼릭 링크의 크기는 'linux.txt'의 바이트 수인 9임을 알 수 있음

ls -l 명령으로 확인하면 심볼릭 링크 크기는 'linux.txt'의 바이트 수인 9

 

 

ㅇchar* realpath(const char* path, char* resolved_path): 심볼릭 링크 원본 파일의 경로 읽기

- path : 심볼릭 링크의 경로명

- resolved _path : 경로명을 저장할 버퍼 주소

성공하면 실제 경로명이 저장된 곳의 주소를, 실패하면 NULL을 리턴

심볼릭 링크명을 받아 실제 경로명을 resolved_name에 저장

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

int main() {
    char buf[BUFSIZ];
    
    // 심볼릭 링크인 linux.sym의 원본 파일 경로를 얻고 출력
    realpath("linux.sym", buf);
    printf("linux.sym : REALPATH = %s\n", buf);
    
    return 0;
}

 

 

ㅇint unlink(const char* pathname): 링크 끊기

- pathname : 삭제할 링크의 경로

unlink() 함수에서 연결을 끊은 경로명이 그 파일을 참조하는 마지막 링크라면 파일을 삭제

만약 인자로 지정한 경로명이 심볼릭 링크면 링크가 가리키는 원본 파일이 아니라 심볼릭 링크 파일이 삭제

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

int main() {
    struct stat statbuf;

    // linux.ln 파일의 정보를 검색해 링크 개수를 출력
    stat("linux.ln", &statbuf); 
    printf("1.linux.ln : Link Count = %d\n", (int)statbuf.st_nlink);
    unlink("linux.ln"); // 하드 링크 파일인 linux.ln의 링크를 끊어 삭제

    // 원본 파일인 linux.txt 파일의 정보를 검색해 링크 개수를 출력
    stat("linux.txt", &statbuf);
    printf("2.linux.txt: Link Count = %d\n", (int)statbuf.st_nlink);
    unlink("linux.sym"); // 심볼릭 링크 파일인 linux.sym의 링크를 끊어 삭제

    return 0;
}
// 첫번째에서 검색한 하드 링크 파일의 링크 개수는 2
// 첫번째에서 unlink()로 하드 링크를 삭제한 뒤 원본 파일의 링크 개수를 확인한 결과 1로 변경됨
// 심볼릭 링크 파일이 삭제된 것을 알 수 있음

링크의 개수가 2에서 1로 감소

 

 

 

 

 

 

 

참고 및 출처: 시스템 프로그래밍 리눅스&유닉스(이종원)