閉じる

C++でファイルの権限(permissions)を変更する方法|std::filesystem

C++を用いたシステム開発において、ファイルやディレクトリの権限(パーミッション)を制御することは、セキュリティとシステムの安定性を担保するために非常に重要です。

かつてはOS固有のシステムコール(POSIXのchmodなど)を直接呼び出す必要がありましたが、C++17から導入されたstd::filesystemライブラリにより、標準機能だけでポータブルに権限操作を行うことが可能になりました。

本記事では、モダンなC++におけるファイル権限の変更方法について、基礎から実践的なテクニックまで詳しく解説します。

ファイル権限操作の基本概念

C++で権限を操作する前に、まずは「権限(Permissions)」がコンピュータシステム内でどのように定義されているかを整理しましょう。

特にUnix系OS(LinuxやmacOS)とWindowsでは、権限の概念が大きく異なる点に注意が必要です。

権限の種類とビット表現

Unix系OSにおけるファイル権限は、一般的に「誰が」「何をできるか」をビットフラグで管理します。

C++のstd::filesystemでは、これらをstd::filesystem::permsという列挙型(enum class)で表現しています。

主要な権限の種類は以下の通りです。

権限の種類C++での定義(perms::…)意味
所有者:読取owner_readファイルの所有者が内容を読み取れる
所有者:書込owner_writeファイルの所有者が内容を変更できる
所有者:実行owner_execファイルの所有者がプログラムとして実行できる
グループ:読取group_read同一グループのユーザーが読み取れる
その他:読取others_read第三者が読み取れる

これらの値はビット単位の論理和(OR演算子 |)で組み合わせることができ、「所有者には読み書きを許可し、その他には何も許可しない」といった細かな設定が可能です。

Windowsにおける制限事項

std::filesystem::permissionsはクロスプラットフォームを目指していますが、OSによる実装の差は避けられません。

Windows環境では、Unix形式の複雑な権限モデルを完全にサポートしているわけではなく、主に「読み取り専用(Read-only)」属性の切り替えとして機能します。

例えば、Windowsでgroup_readを設定しようとしても、意図した動作にならない場合があるため、Windows専用の複雑なACL(アクセス制御リスト)を操作する必要がある場合は、プラットフォーム固有のAPIを検討する必要があります。

std::filesystem::permissions関数の使い方

C++で実際に権限を変更するには、std::filesystem::permissions関数を使用します。

この関数は、対象のパス、設定したい権限、および操作オプションの3つを引数に取ります。

基本的な権限の上書き

最もシンプルな使い方は、現在の権限に関わらず特定の権限セットで完全に上書きすることです。

C++
#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    const fs::path p = "example.txt";

    // テスト用のファイルを作成
    std::ofstream(p).close();

    try {
        // 所有者に読み取り・書き込み、グループに読み取り権限を与える(0640に相当)
        // デフォルトの動作は perm_options::replace です
        fs::permissions(p,
            fs::perms::owner_read | fs::perms::owner_write |
            fs::perms::group_read,
            fs::perm_options::replace);

        std::cout << "権限を正常に変更しました。" << std::endl;
    } catch (const fs::filesystem_error& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }

    return 0;
}
実行結果
権限を正常に変更しました。

このコードでは、perm_options::replaceを指定しています。

これにより、既存の権限はすべて破棄され、引数で指定した権限のみが有効になります。

perm_optionsによる柔軟な操作

権限の変更には、単なる「上書き」以外にも「追加」や「削除」といった操作が求められることがよくあります。

std::filesystem::perm_optionsを使用することで、現在の権限状態を維持したまま変更を加えることができます。

権限の追加(perm_options::add)

既存の権限を維持しつつ、特定の権限を付け加えたい場合はaddを使用します。

例えば、既に存在するログファイルに対して、後から「実行権限」を付与する場合などに便利です。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path p = "script.sh";
    
    // 既存の権限に「所有者の実行権限」を追加する
    // 他の権限(読み取り、書き込みなど)はそのまま保持される
    fs::permissions(p, fs::perms::owner_exec, fs::perm_options::add);

    std::cout << "実行権限を追加しました。" << std::endl;
    return 0;
}

権限の削除(perm_options::remove)

逆に、特定の権限を剥奪したい場合はremoveを使用します。

セキュリティ上の理由で「その他のユーザー」からの読み取りアクセスを禁止する際に非常に役立ちます。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path p = "private_data.db";
    
    // 「その他」の全権限を削除する
    fs::permissions(p, 
        fs::perms::others_read | fs::perms::others_write | fs::perms::others_exec, 
        fs::perm_options::remove);

    std::cout << "第三者からのアクセスを禁止しました。" << std::endl;
    return 0;
}

シンボリックリンクの扱い(perm_options::nofollow)

デフォルトでは、指定したパスがシンボリックリンクである場合、そのリンク先(実体)の権限が変更されます。

リンクそのものの権限を変更したい場合は、perm_options::nofollowを指定する必要があります。

ただし、多くのOSではシンボリックリンク自体の権限変更を許可していないため、このオプションの使用には注意が必要です。

現在の権限を確認する方法

権限を変更する前に現在の状態をチェックしたい、あるいは変更が正しく反映されたか確認したい場合は、std::filesystem::status関数を使用します。

権限のステータス取得と表示

取得したpermsは列挙型であるため、そのまま数値として表示するよりも、ビット演算で各フラグを確認するのが一般的です。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

void print_permissions(fs::perms p) {
    auto check = [p](fs::perms bit, char c) {
        return (p & bit) != fs::perms::none ? c : '-';
    };

    std::cout << check(fs::perms::owner_read,  'r')
              << check(fs::perms::owner_write, 'w')
              << check(fs::perms::owner_exec,  'x')
              << check(fs::perms::group_read,  'r')
              << check(fs::perms::group_write, 'w')
              << check(fs::perms::group_exec,  'x')
              << check(fs::perms::others_read, 'r')
              << check(fs::perms::others_write, 'w')
              << check(fs::perms::others_exec, 'x')
              << std::endl;
}

int main() {
    fs::path p = "test_file.txt";
    if (!fs::exists(p)) std::ofstream(p).close();

    // 現在の権限を表示
    std::cout << "初期状態: ";
    print_permissions(fs::status(p).permissions());

    // 権限変更
    fs::permissions(p, fs::perms::owner_all, fs::perm_options::replace);

    std::cout << "変更後  : ";
    print_permissions(fs::status(p).permissions());

    return 0;
}
実行結果
初期状態: rw-r--r--
変更後  : rwx------

このように、fs::status(p).permissions()を呼び出すことで、現在の権限セットをstd::filesystem::perms型として取得できます。

エラーハンドリングと安全性

ファイル操作は常に失敗の可能性がつきまといます(ファイルが存在しない、権限が不足している、ストレージが読み取り専用である等)。

std::filesystemには2種類のエラーハンドリング手法が用意されています。

例外によるハンドリング

これまで見てきた例のように、第3引数(または第4引数)にエラー情報を格納する変数を渡さない場合、関数はエラー時にstd::filesystem::filesystem_error例外をスローします。

C++
try {
    fs::permissions("non_existent.txt", fs::perms::owner_read);
} catch (const fs::filesystem_error& e) {
    // エラーメッセージとエラーコードの出力
    std::cerr << "エラーコード: " << e.code() << std::endl;
    std::cerr << "理由: " << e.what() << std::endl;
}

エラーコードによるハンドリング

例外を避けたい、あるいはパフォーマンスを重視する状況では、std::error_codeを受け取るオーバーロードを使用します。

C++
std::error_code ec;
fs::permissions("file.txt", fs::perms::owner_read, fs::perm_options::replace, ec);

if (ec) {
    std::cerr << "権限変更失敗: " << ec.message() << std::endl;
} else {
    std::cout << "成功" << std::endl;
}

この方法では例外が発生しないため、エラーが頻発することが予想されるロジックに適しています。

実践的なユースケース

ファイル権限の操作が必要となる具体的なシナリオをいくつか紹介します。

セキュアな一時ファイルの作成

機密データを一時的に保存する場合、作成した瞬間に「自分だけがアクセス可能」な状態に設定するのがセキュリティのベストプラクティスです。

C++
fs::path temp_file = "sensitive.tmp";
std::ofstream(temp_file) << "極秘データ";

// 所有者のみ読み書き可能、その他はアクセス不可
fs::permissions(temp_file, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace);

ログファイルのローテーションと読み取り専用化

アーカイブされた古いログファイルを、間違って変更されないように読み取り専用(Read-only)に設定する処理も一般的です。

C++
void archive_log(fs::path log_path) {
    // 書き込み権限を削除して読み取り専用にする
    fs::permissions(log_path, 
        fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write, 
        fs::perm_options::remove);
}

ディレクトリの実行権限

Unix系OSにおいて、ディレクトリに対する「実行権限(x)」は「そのディレクトリの中に入ることができる(移動できる)」ことを意味します。

ディレクトリを作成した際にアクセスできない場合は、この実行権限が付与されているかを確認してください。

まとめ

C++17以降、std::filesystem::permissionsを使用することで、プラットフォームを問わずに統一されたコードでファイル権限を管理できるようになりました。

以前のようなOS固有のAPIを意識する必要が減り、コードの可読性と保守性は飛躍的に向上しています。

最後に、重要なポイントを振り返ります。

  • std::filesystem::permsを用いてビットフラグ形式で権限を指定する。
  • perm_optionsにより、上書き(replace)、追加(add)、削除(remove)を選択できる。
  • Windows環境ではUnix形式の権限が完全に再現されるわけではなく、主に読み取り専用属性として機能する。
  • 例外処理、またはstd::error_codeを用いたエラーハンドリングを必ず実装する。

特にセキュリティが重要視される現代のソフトウェア開発において、ファイル権限の適切な制御は必須のスキルです。

本記事で紹介した手法を活用し、安全で堅牢なC++アプリケーションを構築してください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!