HOWTO · C

C에서 새 디렉토리 만들기

이 기사에서는 C로 새 디렉토리를 만드는 방법을 소개합니다.

이 페이지의 내용

POSIX mkdir 함수를 사용하면 C에서 디렉터리 하나를 직접 만들 수 있습니다. 이 함수는 경로명과 권한 비트를 인수로 받으며, 반환값과 errno를 통해 실패를 알립니다. 요청한 권한은 프로세스의 umask에 의해 걸러지므로 실제 모드는 호출에 전달한 모드보다 더 제한적일 수 있습니다. 프로세스의 현재 작업 디렉터리가 아니라 특정 열린 디렉터리를 기준으로 경로를 해석해야 한다면 mkdirat를 사용할 수 있습니다.

mkdir로 디렉터리 만들기

mkdir를 사용하려면 <sys/stat.h>를 포함합니다. 첫 번째 인수는 새 디렉터리의 경로이고, 두 번째 인수는 0750 같은 모드입니다. 이 모드에서는 소유자가 디렉터리를 읽고, 쓰고, 탐색할 수 있으며 그룹 구성원은 읽고 탐색할 수 있습니다. 다른 사용자에게는 이 요청된 권한 비트로 권한이 부여되지 않습니다. 프로세스의 umask는 권한을 제거할 수 있지만 추가할 수는 없습니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdir-demo-XXXXXX";
    char path[sizeof(parent) + sizeof("/reports")];

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    int path_length = snprintf(path, sizeof(path), "%s/reports", parent);
    if (path_length < 0 || (size_t)path_length >= sizeof(path)) {
        puts("path formatting failed");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdir(path, 0750) == -1) {
        perror("mkdir");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    puts("Created reports directory.");
    int result = EXIT_SUCCESS;
    if (rmdir(path) == -1) {
        perror("rmdir child");
        result = EXIT_FAILURE;
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}
Created reports directory.

제공된 실행 기록에는 성공한 경우 Created reports directory.를 출력한 것으로 나와 있습니다. 이 기록에는 실패 사례가 없습니다. 예제는 임시 상위 디렉터리를 만든 뒤 그 아래에 reports를 만들고, 성공 메시지를 출력한 다음 두 디렉터리를 제거합니다. 실제 프로그램에서는 임시 경로를 필요한 위치로 바꾸고, 선택한 권한이 그곳에 저장할 데이터에 적절한지 판단하세요. 코드와 출력 블록은 바이트 단위로 보존되므로 메시지와 표시 레이블은 영어로 남아 있습니다.

오류와 이미 존재하는 경로 처리하기

mkdir는 성공하면 0, 실패하면 -1을 반환합니다. 실패하면 errno를 설정합니다. 가능한 한 바로 perror를 호출하거나, 값을 바꿀 수 있는 다른 작업을 하기 전에 errno를 저장하세요. 상위 디렉터리가 없거나, 상위 디렉터리를 수정할 권한이 부족하거나, 경로명이 시스템 한도를 초과하거나, 경로 구성 요소가 디렉터리가 아닌 경우 등이 흔한 실패 원인입니다. 대상이 이미 있으면 그 대상이 디렉터리든 다른 파일 형식이든 mkdir는 EEXIST 오류로 실패합니다.

생성 호출의 결과를 기준으로 판단하세요. mkdir를 호출하기 전에 stat으로 확인해도 생성 시점까지 경로가 없으리라고 보장할 수는 없습니다. 확인과 호출 사이에 다른 프로세스가 경로를 만들 수 있기 때문입니다. 프로그램에서 기존 디렉터리를 성공으로 간주한다면 EEXIST를 처리한 뒤 stat 또는 lstat으로 경로의 유형을 확인하세요. 경로가 동시에 바뀔 수 있다면 경쟁 상태도 고려해야 합니다. 이미 존재하는 일반 파일을 확인 없이 허용하지 마세요.

0750 인수는 요청 모드이지 반드시 최종 모드인 것은 아닙니다. 예를 들어 제한적인 umask는 그룹이나 소유자의 권한 비트를 지울 수 있습니다. 정확한 권한이 필요하다면 생성된 디렉터리의 모드를 확인하고 명확한 권한 정책을 적용하세요. 다른 프로세스가 경로를 바꾸었을 수 있는 상황에서 권한을 변경할 때는 보안상 영향을 고려해야 합니다.

열린 디렉터리를 기준으로 mkdirat로 디렉터리 만들기

mkdirat는 디렉터리 파일 디스크립터, 경로명, 모드를 인수로 받습니다. 경로가 상대 경로이면 커널은 나중에 프로세스의 현재 작업 디렉터리가 바뀌더라도 해당 디렉터리 디스크립터를 기준으로 경로를 해석합니다. 애플리케이션이 디렉터리를 이미 열어 두었고 이후 작업을 그 위치를 기준으로 수행하려 할 때 유용합니다. 절대 경로는 디스크립터를 기준으로 해석되지 않습니다.

예제는 임시 상위 디렉터리를 열고, 그 디스크립터를 기준으로 하위 디렉터리를 만든 뒤 디스크립터를 닫습니다. 이후 임시 디렉터리를 제거합니다. 제공된 실행 기록에 따르면 성공한 경우 표준 출력은 비어 있습니다. 실패 사례의 실행 결과는 기록되어 있지 않습니다.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    char parent[] = "/tmp/mkdirat-demo-XXXXXX";
    char child[sizeof(parent) + sizeof("/reports")];
    int dirfd;
    int result = EXIT_FAILURE;
    int created = 0;

    if (mkdtemp(parent) == NULL) {
        perror("mkdtemp");
        return EXIT_FAILURE;
    }
    dirfd = open(parent, O_RDONLY);
    if (dirfd == -1) {
        perror("open");
        if (rmdir(parent) == -1) {
            perror("rmdir parent");
        }
        return EXIT_FAILURE;
    }

    if (mkdirat(dirfd, "reports", 0750) == -1) {
        perror("mkdirat");
    } else {
        created = 1;
        result = EXIT_SUCCESS;
    }

    if (close(dirfd) == -1) {
        perror("close");
        result = EXIT_FAILURE;
    }
    if (created) {
        int child_length = snprintf(child, sizeof(child), "%s/reports", parent);
        if (child_length < 0 || (size_t)child_length >= sizeof(child)) {
            puts("path formatting failed");
            result = EXIT_FAILURE;
        } else {
            if (rmdir(child) == -1) {
                perror("rmdir child");
                result = EXIT_FAILURE;
            }
        }
    }
    if (rmdir(parent) == -1) {
        perror("rmdir parent");
        result = EXIT_FAILURE;
    }
    return result;
}

필요한 동안 디스크립터를 열어 두고 모든 실행 경로에서 닫으세요. 실제 프로그램에서는 프로그램이 직접 만든 객체만 정리 과정에서 제거해야 합니다. 보안에 민감한 작업이라면 플랫폼에 적합한 플래그를 사용해 디렉터리를 열고, 사용하려는 디렉터리가 올바른지 확인하는 방법을 고려하세요. mkdirat는 상대 경로의 기준을 고정하지만, 전달된 경로의 모든 구성 요소를 검증해 주지는 않습니다.

디렉터리 생성에 system()을 사용하지 말아야 하는 이유

C에서 이미 mkdir를 제공하므로, 셸 명령을 실행하기 위해 system()을 호출할 필요는 대체로 없습니다. 셸을 시작하고 명령을 해석하는 과정이 추가되며, 경로를 바탕으로 명령 문자열을 만들면 신뢰할 수 없는 경로의 셸 메타문자가 실행 내용을 바꿀 수 있습니다. 셸마다 경로를 올바르게 인용하는 방법도 복잡합니다. 대신 mkdir 또는 mkdirat를 직접 호출하고, 결과를 확인한 다음 errno를 보고하세요. 애플리케이션이 의도적으로 셸 기능을 필요로 하고, 별도로 신중하게 설계한 보안 정책에 따라 명령 구성과 실행을 처리하는 경우에만 셸을 사용하세요.