HOWTO · C
C 言語で新しいディレクトリを作成する
この記事では、C 言語で新規ディレクトリを作成する方法を紹介します。
このページの内容
POSIX の mkdir 関数を使うと、C からディレクトリを1つ直接作成できます。パス名と権限ビットを受け取り、失敗は戻り値と errno で通知されます。指定した権限はプロセスの umask によって制限されるため、実際のモードは呼び出しに渡したモードより制限が厳しくなることがあります。プロセスの作業ディレクトリではなく、特定の開いているディレクトリを基準にパス名を解決するには、mkdirat を使います。
mkdir でディレクトリを作成する
mkdir を使うには <sys/stat.h> をインクルードします。第1引数は新しいディレクトリのパス、第2引数は 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 を報告してください。アプリケーションが意図的にシェルの機能を必要とし、コマンドの組み立てと実行を別途慎重に設計したセキュリティ方針で扱う場合に限り、シェルの使用が適切です。