QFileInfo::isWritable()だけじゃない!Qtでファイル書き込み権限を確認する代替手段

2025-05-31

QFileInfo::isWritable() は、Qt の QFileInfo クラスのメンバー関数です。この関数は、QFileInfo オブジェクトが表すファイルまたはディレクトリが、現在のユーザーによって書き込み可能であるかどうかを判定します。

戻り値

  • 書き込み不可能であれば false を返します。
  • ファイルまたはディレクトリが書き込み可能であれば true を返します。

主な用途

この関数は、ファイル操作を行う前に、指定されたファイルやディレクトリにデータを書き込む権限があるかどうかを確認するために使用されます。例えば、以下のようなシナリオで役立ちます。

  • アプリケーションがログファイルなどを特定の場所に作成する際に、その場所が書き込み可能かを確認する。
  • 既存のファイルを編集して保存する前に、そのファイルが書き込み可能であるかを確認する。
  • ユーザーがファイルを保存しようとする際に、保存先のディレクトリに書き込み権限があるかを確認する。

使用例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug> // qWarning() などでデバッグ出力するため

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filePath = "C:/temp/my_file.txt"; // Windows の例
    // Linux/macOS の例: QString filePath = "/tmp/my_file.txt";
    // ディレクトリの例: QString dirPath = "/tmp/my_directory";

    QFileInfo fileInfo(filePath);

    // ファイルが存在するかどうかを確認
    if (fileInfo.exists()) {
        qDebug() << "ファイルが存在します:" << filePath;
        // 書き込み可能かどうかを確認
        if (fileInfo.isWritable()) {
            qDebug() << "このファイルは書き込み可能です。";
        } else {
            qDebug() << "このファイルは書き込み不可能/読み取り専用です。";
        }
    } else {
        qDebug() << "ファイルは存在しません:" << filePath;
        // ファイルが存在しない場合でも、そのパスに書き込み可能かを確認することは可能
        // (ただし、ディレクトリの書き込み権限に依存します)
        QFileInfo dirInfo(fileInfo.dir().path()); // ファイルの親ディレクトリの情報を取得
        if (dirInfo.isWritable()) {
             qDebug() << "親ディレクトリは書き込み可能です。ファイルを作成できる可能性があります。";
        } else {
            qDebug() << "親ディレクトリは書き込み不可能/読み取り専用です。ファイルを作成できません。";
        }
    }

    return 0;
}

注意点

  • シンボリックリンク: QFileInfo がシンボリックリンクを指している場合、isWritable() は、リンクが指す実際のファイル(ターゲット)の書き込み権限をチェックします。
  • QFile::open() との違い: QFile クラスの isWritable() は、ファイルが現在のモードで書き込み用に開かれているかどうかを示します。一方、QFileInfo::isWritable() は、ファイルシステム上での権限をチェックします。これは非常に重要な違いです。ファイルが存在しない場合でも、QFileInfo::isWritable() はそのパスが書き込み可能かどうかをチェックすることができます(具体的には親ディレクトリの書き込み権限を考慮します)。
  • キャッシュ: QFileInfo は、ファイルシステムから取得した情報をキャッシュすることがあります。ファイルシステムが外部から変更された場合(他のプログラムによる変更など)、QFileInfo の情報が古くなっている可能性があります。最新の情報を取得するには、refresh() 関数を呼び出す必要があります。
  • OSによる挙動の違い: isWritable() の結果は、オペレーティングシステム(OS)のファイルシステムやアクセス権限の管理方法に強く依存します。特に Windows と Unix 系OS (Linux, macOS) では、権限の解釈が異なる場合があります。
    • Windows の NTFS ファイルシステムでは、パフォーマンス上の理由から、所有権や権限のチェックがデフォルトで無効になっている場合があります。これを有効にするには、特定のQt設定(QT_WIN_NO_ACL_PERMS を定義しないなど)が必要になることがあります。
    • 一部のOSや設定では、ディレクトリが「書き込み可能」と判断されても、その中に実際にファイルを作成できるかどうかは、別の要因(ディスク容量、特定のセキュリティポリシーなど)に影響されることがあります。


QFileInfo::isWritable() は、ファイルやディレクトリの書き込み権限をチェックするための便利な関数ですが、特に異なるOS環境や特定の状況下では、期待通りの結果が得られないことがあります。以下に、よくあるエラーとそのトラブルシューティング方法を挙げます。

Windows環境で isWritable() が false を返す(実際は書き込み可能なのに)

問題の現象
Windows 環境において、ファイルやディレクトリに書き込み権限があるはずなのに、QFileInfo::isWritable()false を返すことがあります。これは特にNTFSファイルシステムで顕著です。

原因

  • ファイルが別のプロセスによって開かれている
    ファイルが排他的に開かれている場合、たとえ権限があっても書き込み不可と判断されることがあります。
  • UAC (User Account Control) の影響
    アプリケーションが管理者権限で実行されていない場合、UACによって一部のシステムディレクトリやプログラムファイルディレクトリへの書き込みが制限されることがあります。
  • NTFSパーミッションチェックの無効化(Qtのデフォルト設定)
    Qtは、NTFSファイルシステム上でのパフォーマンス低下を避けるため、デフォルトではACL(Access Control List)に基づく詳細なパーミッションチェックを行いません。そのため、一般的な「読み取り専用」属性以外の高度な権限設定を正確に反映できない場合があります。

トラブルシューティング

  • ファイルの排他ロックの確認
    別のアプリケーションやプロセスが対象のファイルを排他的に開いていないか確認してください。タスクマネージャーやリソースモニターなどでファイルハンドルを確認できる場合があります。

  • QFile::open() の戻り値を確認する
    QFileInfo::isWritable() はあくまで「権限があるか」のチェックであり、実際にファイルを書き込みモードで開けるかは別の問題です。ファイルを開く前にisWritable()でチェックし、さらに QFile::open(QIODevice::WriteOnly) の戻り値も確認するようにしましょう。QFile::open()true を返せば、実際に書き込みが可能であると判断できます。

    QFile file(filePath);
    if (fileInfo.isWritable()) {
        qDebug() << "QFileInfo::isWritable() は true を返しました。";
    } else {
        qDebug() << "QFileInfo::isWritable() は false を返しました。";
    }
    
    if (file.open(QIODevice::WriteOnly | QIODevice::Append)) { // 既存ファイルに追記する場合
        qDebug() << "QFile::open() で書き込み用に開けました。";
        // 書き込み処理
        file.close();
    } else {
        qWarning() << "QFile::open() で書き込み用に開けませんでした。エラー:" << file.errorString();
    }
    
  • 管理者権限での実行
    アプリケーションを管理者として実行してみてください。特に、C:\Program Files のようなシステムが管理するディレクトリへの書き込みを試みる場合は、管理者権限が必要です。

  • NTFSパーミッションチェックの有効化
    アプリケーションの起動時(main 関数内など)に、以下のコードを追加することで、NTFSパーミッションチェックを強制的に有効にできます。

    #include <QtGlobal> // qglobal.h をインクルード
    // ...
    extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
    qt_ntfs_permission_lookup++; // パーミッションチェックをオンにする
    

    これにより、isWritable() がより正確な結果を返すようになる可能性があります。ただし、パフォーマンスが低下する可能性があるので注意が必要です。

Linux/macOS 環境で isWritable() が期待通りに動かない

問題の現象
Unix系OS(Linux、macOS)では、ファイルパーミッションがより厳密に適用されます。isWritable() が期待通りの結果を返さない場合、それは実際のパーミッション設定と異なる解釈をしている可能性があります。

原因

  • root権限の必要性
    //usr のようなシステムディレクトリへの書き込みは、通常root権限が必要です。
  • ファイルシステムの種類
    一部の特殊なファイルシステムでは、通常のパーミッションルールが適用されない場合があります。
  • 所有者の不一致
    アプリケーションを実行しているユーザーが、ファイルやディレクトリの所有者ではない、または所属グループが書き込み権限を持っていない。

トラブルシューティング

  • chmod や chown コマンドの使用
    必要に応じて、ファイルのパーミッションを chmod で変更したり、所有者を chown で変更したりして、書き込み権限を付与してください。(ただし、ユーザーがこれを行う権限を持っている必要があります。)

  • ディレクトリの書き込み権限
    新しいファイルを作成しようとしている場合は、その親ディレクトリの書き込み権限が必要です。ファイル自体の書き込み権限があっても、親ディレクトリに書き込み権限がなければファイルを作成することはできません。QFileInfo(QDir::homePath()).isWritable() のように、親ディレクトリの情報を取得してチェックするのも有効です。

  • ユーザーとグループの確認
    アプリケーションを実行しているユーザーが、ファイルやディレクトリの所有者であるか、または書き込み権限を持つグループに所属しているかを確認します。

QFileInfo オブジェクトが古い情報を保持している

問題の現象
ファイルやディレクトリの権限が外部から変更されたにもかかわらず、QFileInfo::isWritable() が古い情報を返す。

原因

  • キャッシュの利用
    QFileInfo は、ファイルシステムから取得した情報を内部的にキャッシュして、パフォーマンスを向上させます。

トラブルシューティング

  • refresh() 関数の呼び出し
    ファイルシステム上の情報が変更された可能性がある場合は、QFileInfo::refresh() を呼び出してキャッシュを更新してください。

    QFileInfo fileInfo(filePath);
    // ... 何らかの処理でファイルの権限が変更された可能性 ...
    fileInfo.refresh(); // 最新の情報に更新
    if (fileInfo.isWritable()) {
        qDebug() << "更新後、このファイルは書き込み可能です。";
    } else {
        qDebug() << "更新後も、このファイルは書き込み不可能/読み取り専用です。";
    }
    

QFileInfo オブジェクトのパスが正しくない

問題の現象
QFileInfo::isWritable() が常に false を返す、または予期しない結果を返す。

原因

  • 相対パスの問題
    相対パスを使用している場合、現在の作業ディレクトリによって解決されるパスが異なるため、意図しないファイルを参照している可能性がある。
  • 無効なパス
    QFileInfo に渡されたパスが存在しない、または正しくない(タイポ、間違ったドライブレターなど)。

トラブルシューティング

  • 絶対パスの使用
    特に問題が頻発する場合は、QDir::toNativeSeparators(QDir::currentPath() + "/your_file.txt")QStandardPaths::writableLocation() などを使用して、常に絶対パスを指定することを検討してください。

  • exists() と isDir() / isFile() の確認
    isWritable() を呼び出す前に、fileInfo.exists() でファイル/ディレクトリが存在すること、fileInfo.isDir() または fileInfo.isFile() でそれが意図した種類であることを確認します。

  • filePath() でパスを確認
    qDebug() << fileInfo.filePath(); を使って、QFileInfo オブジェクトが実際にどのパスを指しているかを確認します。

シンボリックリンクの取り扱い

問題の現象
シンボリックリンクに対して isWritable() を呼び出した際、期待通りの結果が得られない。

原因

  • QFileInfo::isWritable() は、シンボリックリンクが指す実際のターゲットの書き込み権限をチェックします。リンク自体の権限ではありません。

トラブルシューティング

  • リンクのターゲットの権限がどうなっているかを確認します。QFileInfo::symLinkTarget() でターゲットパスを取得できます。


QFileInfo::isWritable() は、ファイルやディレクトリへの書き込み権限を確認する際に非常に役立ちます。いくつかの典型的な使用シナリオとコード例を見ていきましょう。

例1: ファイルが存在し、書き込み可能かを確認する

これは最も基本的な使用例です。既存のファイルを編集したり、追記したりする前に、そのファイルが書き込み可能であるかをチェックします。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QFile> // ファイルを実際に開くために使用

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filePath = "example.txt"; // 現在の作業ディレクトリ内のファイル
    // または絶対パス: QString filePath = "C:/Users/YourUser/Documents/example.txt"; (Windows)
    // QString filePath = "/home/youruser/documents/example.txt"; (Linux/macOS)

    QFileInfo fileInfo(filePath);

    qDebug() << "ファイルパス:" << filePath;

    if (fileInfo.exists()) {
        qDebug() << "ファイルは存在します。";
        if (fileInfo.isWritable()) {
            qDebug() << "ファイルは書き込み可能です。";

            // 実際にファイルを書き込みモードで開いてみる
            QFile file(filePath);
            if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
                QTextStream out(&file);
                out << "この行は" << QDateTime::currentDateTime().toString() << "に追加されました。\n";
                file.close();
                qDebug() << "ファイルに書き込みが成功しました。";
            } else {
                qWarning() << "ファイルを書き込み用に開けませんでした:" << file.errorString();
            }

        } else {
            qDebug() << "ファイルは書き込み可能ではありません(読み取り専用か権限不足)。";
        }
    } else {
        qDebug() << "ファイルは存在しません。";
        // ファイルが存在しない場合でも、そのパスが書き込み可能か(つまり親ディレクトリが書き込み可能か)をチェックできます。
        QFileInfo parentDirInfo(fileInfo.absoluteDir().path());
        if (parentDirInfo.isWritable()) {
            qDebug() << "親ディレクトリは書き込み可能です。このパスにファイルを作成できる可能性があります。";
            // 新しいファイルを作成してみる
            QFile newFile(filePath);
            if (newFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
                QTextStream out(&newFile);
                out << "これは新しく作成されたファイルです。\n";
                newFile.close();
                qDebug() << "新しいファイルの作成と書き込みに成功しました。";
            } else {
                qWarning() << "新しいファイルを作成できませんでした:" << newFile.errorString();
            }
        } else {
            qDebug() << "親ディレクトリは書き込み可能ではありません。このパスにファイルを作成できません。";
        }
    }

    return 0;
}

解説

  • 実際にファイルを書き込む際は、QFile::open() を使って書き込みモードで開きます。QFile::open() の戻り値も確認することで、より確実に書き込みの成否を判断できます。
  • ファイルが存在しない場合でも、fileInfo.absoluteDir().path() で親ディレクトリのパスを取得し、そのディレクトリが書き込み可能かを QFileInfo(parentDirInfo.path()).isWritable() で確認できます。これは、新しいファイルをそのディレクトリに作成できるかどうかの判断に役立ちます。
  • fileInfo.isWritable() で、ファイルが書き込み可能かをチェックします。
  • fileInfo.exists() でファイルが存在するかをチェックします。
  • QFileInfo fileInfo(filePath); で、指定されたパスのファイル情報を取得します。

例2: ディレクトリの書き込み権限を確認し、新しいファイルを作成する

特定のディレクトリにファイルを保存するアプリケーションでよく使用されます。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QDateTime>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString targetDirPath = QDir::homePath() + "/MyAppData"; // ユーザーのホームディレクトリに独自のディレクトリを作成
    // Windows: C:\Users\YourUser\MyAppData
    // Linux/macOS: /home/youruser/MyAppData

    QDir targetDir(targetDirPath);
    QFileInfo dirInfo(targetDirPath);

    qDebug() << "ターゲットディレクトリパス:" << targetDirPath;

    // ターゲットディレクトリが存在しない場合、作成を試みる
    if (!targetDir.exists()) {
        qDebug() << "ターゲットディレクトリが存在しません。作成を試みます。";
        if (targetDir.mkpath(targetDirPath)) { // ディレクトリと必要な親ディレクトリを再帰的に作成
            qDebug() << "ターゲットディレクトリを作成しました。";
        } else {
            qWarning() << "ターゲットディレクトリを作成できませんでした。";
            return 1; // アプリケーション終了
        }
    }

    // ディレクトリの書き込み権限を確認
    if (dirInfo.isWritable()) {
        qDebug() << "ターゲットディレクトリは書き込み可能です。";

        QString newFilePath = targetDirPath + "/log_" + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".txt";
        QFile logFile(newFilePath);

        if (logFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
            QTextStream out(&logFile);
            out << "ログエントリ: アプリケーションが起動しました。\n";
            logFile.close();
            qDebug() << "ログファイルが作成され、書き込まれました:" << newFilePath;
        } else {
            qWarning() << "ログファイルを作成または書き込みできませんでした:" << logFile.errorString();
        }
    } else {
        qDebug() << "ターゲットディレクトリは書き込み可能ではありません。ファイルを保存できません。";
    }

    return 0;
}

解説

  • ディレクトリ作成後、そのディレクトリの QFileInfo::isWritable() を使って書き込み権限を確認し、新しいログファイルを作成しています。
  • QDir::mkpath() を使って、指定されたパスのディレクトリと必要な親ディレクトリを再帰的に作成します。
  • QDir::homePath() を使って、OSに依存しないユーザーのホームディレクトリパスを取得します。

例3: isWritable() の結果が期待と異なる場合のデバッグと更新

特にWindows環境でisWritable()が意図せずfalseを返す場合に役立つデバッグとキャッシュ更新の例です。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QFile>
#include <QDir>

// Windows環境でのNTFSパーミッションチェックを有効にするための宣言
#ifdef Q_OS_WIN
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
#endif

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString testFilePath = "test_file_for_permissions.txt";
    QFile testFile(testFilePath);

    // テストファイルを作成し、一旦閉じる
    if (testFile.open(QIODevice::WriteOnly)) {
        testFile.write("Hello, world!");
        testFile.close();
        qDebug() << "テストファイルを作成しました:" << testFilePath;
    } else {
        qWarning() << "テストファイルを作成できませんでした:" << testFile.errorString();
        return 1;
    }

    QFileInfo fileInfo(testFilePath);

    qDebug() << "--- 初期チェック ---";
    qDebug() << "ファイルが存在しますか?" << fileInfo.exists();
    qDebug() << "ファイルは書き込み可能ですか?" << fileInfo.isWritable();

#ifdef Q_OS_WIN
    // Windowsの場合、NTFSパーミッションチェックを有効にしてみる
    qDebug() << "--- Windows: NTFSパーミッションチェックを有効にする ---";
    qt_ntfs_permission_lookup++; // これを設定することで、ACLに基づくパーミッションチェックが有効になります
    QFileInfo fileInfoAfterEnable(testFilePath); // 新しいQFileInfoインスタンスを作成するか、refresh()を呼び出す
    qDebug() << "ファイルは書き込み可能ですか(NTFSチェック有効後)?" << fileInfoAfterEnable.isWritable();
#endif

    // 外部でファイルのパーミッションが変更されたと仮定する
    // 例: 手動でファイルを読み取り専用にする、または別のプロセスが変更する

    // ここで、たとえばターミナルで chmod -w test_file_for_permissions.txt を実行するなどして、
    // ファイルを読み取り専用に変更すると仮定します。
    // QProcess を使ってプログラムからパーミッションを変更する例(推奨はしませんがデモ用)
    // #ifdef Q_OS_UNIX
    //     QProcess::execute("chmod", QStringList() << "-w" << testFilePath);
    //     qDebug() << "chmod -w を実行しました。";
    // #endif

    qDebug() << "--- 外部変更後のチェック(refreshなし) ---";
    // QFileInfoオブジェクトはキャッシュされた情報を使用している可能性がある
    qDebug() << "ファイルは書き込み可能ですか(refresh前)?" << fileInfo.isWritable();

    qDebug() << "--- 外部変更後のチェック(refreshあり) ---";
    fileInfo.refresh(); // QFileInfoのキャッシュを更新
    qDebug() << "ファイルは書き込み可能ですか(refresh後)?" << fileInfo.isWritable();


    // テストファイルを削除
    if (QFile::remove(testFilePath)) {
        qDebug() << "テストファイルを削除しました:" << testFilePath;
    } else {
        qWarning() << "テストファイルを削除できませんでした:" << testFilePath;
    }

    return 0;
}
  • この例では、手動でファイルを読み取り専用に設定すること(または chmod コマンドを使用すること)を想定しています。
  • fileInfo.refresh() を呼び出すことで、QFileInfo オブジェクトが保持するファイルシステム情報を最新の状態に更新します。外部でファイル属性が変更された場合に、正しい isWritable() の結果を得るために重要です。
  • qt_ntfs_permission_lookup++ を使用して、Windows環境でNTFSのアクセス制御リスト(ACL)に基づく詳細なパーミッションチェックを有効にします。これにより、より正確な isWritable() の結果が得られる可能性があります。
  • Q_OS_WIN マクロを使って、Windows固有のコードを条件付きでコンパイルしています。


QFile::open() を直接試行する

これは最も直接的で、かつ最も信頼性の高い方法と言えるかもしれません。QFileInfo::isWritable() が「書き込み可能であるはず」というパーミッションの概念をチェックするのに対し、QFile::open() は実際にファイルを書き込みモードで開けるかどうかを試します。

メリット

  • ファイルが存在しない場合でも、その親ディレクトリへの書き込み権限があれば、ファイルを新規作成できるかどうかを判断できます。
  • OSレベルでのアクセス拒否(ファイルがロックされている、ディスク容量不足など)も同時に検知できます。
  • 実際のファイルシステム操作に基づいているため、最も正確な結果が得られます。

デメリット

  • 非常に頻繁にチェックを行う必要がある場合、パフォーマンスに影響を与える可能性があります。
  • ファイルを実際に開いて閉じるため、オーバーヘッドが発生する可能性があります(ただし、ほとんどのアプリケーションでは無視できるレベル)。

コード例

#include <QCoreApplication>
#include <QFile>
#include <QDebug>
#include <QIODevice> // QIODevice::WriteOnly などを使用するため

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filePath = "test_file.txt"; // 現在の作業ディレクトリ
    // QString filePath = "C:/path/to/my_file.txt"; // Windowsの例
    // QString filePath = "/tmp/my_file.txt"; // Linux/macOSの例

    QFile file(filePath);

    // QIODevice::WriteOnly でファイルを開くことを試みる
    // QIODevice::Append は、既存ファイルに追記する場合に使う
    if (file.open(QIODevice::WriteOnly | QIODevice::Append)) {
        qDebug() << QString("ファイル '%1' は書き込み可能です。").arg(filePath);
        // 実際に何か書き込むことも可能
        // QTextStream out(&file);
        // out << "テスト書き込み\n";
        file.close(); // 開いたファイルを忘れずに閉じる
    } else {
        qWarning() << QString("ファイル '%1' は書き込み不可能か、開けません。エラー: %2").arg(filePath).arg(file.errorString());
        // errorString() で具体的なエラーメッセージを確認できる
    }

    // 新しいファイルを作成できるかディレクトリをチェックする場合
    QString newFileName = "new_file.txt";
    QString dirPath = QDir::currentPath(); // 現在のディレクトリ
    // QDir targetDir(dirPath); // または特定のディレクトリ
    
    // QFileInfo を使ってディレクトリの情報を取得
    QFileInfo dirInfo(dirPath);

    // 存在しないファイルを作成しようとする
    QFile newFile(dirPath + QDir::separator() + newFileName);
    if (newFile.open(QIODevice::WriteOnly)) {
        qDebug() << QString("ディレクトリ '%1' は書き込み可能で、ファイル '%2' を作成できました。").arg(dirPath).arg(newFileName);
        newFile.close();
        // 作成したファイルを削除する(クリーンアップ)
        QFile::remove(newFile.fileName());
    } else {
        qWarning() << QString("ディレクトリ '%1' は書き込み不可能か、ファイル '%2' を作成できません。エラー: %3").arg(dirPath).arg(newFileName).arg(newFile.errorString());
    }

    return 0;
}

QFileInfo::permissions() とパーミッションフラグの組み合わせ

QFileInfo::isWritable() は、内部的に QFileInfo::permissions() を使用して、現在のユーザーに対する書き込み権限があるかをチェックしています。より詳細なパーミッションチェックが必要な場合は、permissions() 関数から返される QFile::Permissions フラグを直接調べることができます。

メリット

  • isWritable() と同様に、ファイルシステムへの実際のアクセスは行いません(キャッシュされた情報を使用)。
  • 読み取り、書き込み、実行の各権限を詳細に判断できます。

デメリット

  • Windows環境では、NTFSのACL(Access Control List)の複雑な権限設定を完全に反映できない場合があります。QtはデフォルトでNTFSの詳細なACLチェックを無効にしているため、isWritable() と同じ制限があります。

コード例

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>
#include <QFile> // QFile::Permissions を使用するため

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filePath = "test_file_perms.txt";
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly)) { // まずファイルを作成(または既存ファイルを使用)
        qWarning() << "ファイルを作成できませんでした。";
        return 1;
    }
    file.close();

    QFileInfo fileInfo(filePath);

    // QFileInfo::permissions() を使用して、現在のパーミッションを取得
    QFile::Permissions perms = fileInfo.permissions();

    qDebug() << "ファイルパス:" << filePath;
    qDebug() << "パーミッション:" << QString::number(perms, 8); // 8進数で表示 (Unix風)

    // 現在のユーザーが書き込み可能か
    if (perms & QFile::WriteOwner) {
        qDebug() << "所有者(現在のユーザー)は書き込み可能です。";
    } else {
        qDebug() << "所有者(現在のユーザー)は書き込み不可能です。";
    }

    // グループが書き込み可能か
    if (perms & QFile::WriteGroup) {
        qDebug() << "グループは書き込み可能です。";
    } else {
        qDebug() << "グループは書き込み不可能です。";
    }

    // その他(everyone)が書き込み可能か
    if (perms & QFile::WriteOther) {
        qDebug() << "その他は書き込み可能です。";
    } else {
        qDebug() << "その他は書き込み不可能です。";
    }

    // 読み取り専用フラグを確認する(Windowsでよく使われる属性)
    if (fileInfo.isWritable()) { // isWritable()は内部的にこれらのフラグを複合的にチェックする
        qDebug() << "QFileInfo::isWritable() は true を返しました。";
    } else {
        qDebug() << "QFileInfo::isWritable() は false を返しました。";
    }

    // QtのNTFSパーミッションチェックを有効にして再確認 (Windowsのみ)
#ifdef Q_OS_WIN
    extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
    qt_ntfs_permission_lookup++;
    fileInfo.refresh(); // キャッシュを更新
    qDebug() << "--- NTFSパーミッションチェック有効後 ---";
    if (fileInfo.isWritable()) {
        qDebug() << "QFileInfo::isWritable() は true を返しました(NTFSチェック有効後)。";
    } else {
        qDebug() << "QFileInfo::isWritable() は false を返しました(NTFSチェック有効後)。";
    }
#endif

    QFile::remove(filePath); // クリーンアップ
    return 0;
}

一時ファイルを作成して書き込みを試行する

これは、特定のディレクトリへの書き込み権限があるかを確認する「ダーティ」な方法ですが、非常に信頼性が高いです。特にディレクトリに対する isWritable() が期待通りに機能しないWindows環境で有効です。

メリット

  • QTemporaryFile を使用すれば、自動的にファイルが削除されるため、後処理が不要です。
  • 実際にファイルを書き込めるかどうかを最も確実に判断できます。

デメリット

  • 頻繁に呼び出すとパフォーマンスに影響を与える可能性があります。
  • 実際にディスクI/Oが発生します。

コード例

#include <QCoreApplication>
#include <QTemporaryFile>
#include <QDebug>
#include <QDir>

bool canWriteToDirectory(const QString &dirPath)
{
    // 一時ファイルを作成して書き込みを試みる
    // QTemporaryFile は自動的に unique なファイル名を生成し、スコープを抜けるときに削除される
    QTemporaryFile tempFile(dirPath + QDir::separator() + "temp_XXXXXX.tmp");
    
    // QIODevice::WriteOnly でファイルを開くことを試みる
    if (tempFile.open(QIODevice::WriteOnly)) {
        tempFile.close();
        return true; // 書き込み可能
    } else {
        qWarning() << QString("ディレクトリ '%1' に一時ファイルを作成/書き込みできませんでした。エラー: %2").arg(dirPath).arg(tempFile.errorString());
        return false; // 書き込み不可能
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString writablePath = QDir::tempPath(); // OSの一時ディレクトリ(通常書き込み可能)
    QString readOnlyPath = "/root"; // Linux/macOSの例 (通常root以外書き込み不可)
    // Windowsの例: QString readOnlyPath = "C:/Program Files";

    qDebug() << QString("'%1' は書き込み可能ですか?").arg(writablePath);
    if (canWriteToDirectory(writablePath)) {
        qDebug() << "はい、書き込み可能です。";
    } else {
        qDebug() << "いいえ、書き込み不可能です。";
    }

    qDebug() << QString("'%1' は書き込み可能ですか?").arg(readOnlyPath);
    if (canWriteToDirectory(readOnlyPath)) {
        qDebug() << "はい、書き込み可能です。";
    } else {
        qDebug() << "いいえ、書き込み不可能です。";
    }

    return 0;
}

Qtはクロスプラットフォームの抽象化を提供しますが、場合によってはOSのネイティブAPIを直接呼び出す必要が生じるかもしれません。これはQtの意図するところではありませんし、クロスプラットフォーム互換性を損なうため、最終手段としてのみ検討すべきです。

例(Unix系OS向け)
access() 関数

#include <QCoreApplication>
#include <QDebug>
#include <unistd.h> // access() 関数を使用するため (Unix系OS)
#include <errno.h>  // errno を使用するため

#ifdef Q_OS_UNIX // Unix系OS (Linux, macOSなど) でのみコンパイル
bool isWritableUnix(const QString &filePath)
{
    QByteArray path = filePath.toLocal8Bit(); // Cスタイル文字列に変換
    if (access(path.constData(), W_OK) == 0) {
        return true;
    } else {
        qWarning() << QString("Unix access() エラー for '%1': %2").arg(filePath).arg(strerror(errno));
        return false;
    }
}
#endif

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

#ifdef Q_OS_UNIX
    QString testFile = "/tmp/test_writable.txt";
    QFile file(testFile);
    file.open(QIODevice::WriteOnly); // ファイルが存在しないと access() はディレクトリの権限をチェックしない
    file.close();

    qDebug() << QString("Unix-specific check for '%1':").arg(testFile);
    if (isWritableUnix(testFile)) {
        qDebug() << "はい、書き込み可能です。";
    } else {
        qDebug() << "いいえ、書き込み不可能です。";
    }
    QFile::remove(testFile);
#else
    qDebug() << "この例はUnix系OSでのみ機能します。";
#endif

    return 0;
}

解説

  • Windowsではこの関数は利用できません。 Windowsで同様のチェックを行うには、Windows APIの PathIsWritableGetFileAttributesEx などの関数を使用する必要がありますが、これらはさらに複雑になり、クロスプラットフォーム性は完全に失われます。
  • errno を確認することで、より詳細なエラー原因(例: EACCES アクセス拒否、ENOENT ファイルまたはディレクトリが存在しない)を特定できます。
  • access() 関数は、パスに対して指定された操作(W_OK は書き込み)が可能かどうかをチェックします。

ほとんどのケースでは、QFileInfo::isWritable()QFile::open() の組み合わせで十分です。

  • 詳細なパーミッションフラグの検査
    QFileInfo::permissions() を使用する。
  • WindowsでのNTFSパーミッションの精度向上
    qt_ntfs_permission_lookup++ を使用する。
  • ディレクトリへの新規ファイル作成可能性の確認
    そのディレクトリで QFile::open(QIODevice::WriteOnly) を試すか、QTemporaryFile を使用する。
  • 実際の書き込み可能性の確認とエラー詳細
    QFile::open()
  • 簡単な権限チェック
    QFileInfo::isWritable()