Работа с файлами и директориями в Qt

Работа с правами доступа к файлам в Qt: QFile::permissions на разных ОС

Введение

Права доступа к файлам — одна из тех областей, где различия между Windows, Linux и macOS особенно заметны. POSIX-системы используют модель «владелец/группа/прочие» с битами чтения, записи и выполнения, в то время как Windows опирается на ACL (списки контроля доступа) NTFS. Qt пытается абстрагировать эти различия через QFile::permissions() и QFileDevice::Permissions, но полного паритета между платформами не существует.

Концепция

QFileDevice::Permissions — это набор флагов (ReadOwner, WriteOwner, ExeOwner, ReadGroup, WriteGroup, ExeGroup, ReadOther, WriteOther, ExeOther, плюс специальные ReadUser/WriteUser/ExeUser), которые в сумме напоминают классические Unix-права rwxrwxrwx. Метод QFile::permissions(path) (статический) или QFile::permissions() (метод объекта) позволяет прочитать текущие права, а setPermissions() — установить новые.

На Windows эта модель — упрощённое отображение реальных ACL NTFS. Qt транслирует POSIX-подобные флаги в базовые атрибуты («только чтение» и т.п.), но не предоставляет полного управления ACL — для этого нужны платформенные API через QFile::handle() или сторонние библиотеки.

Пример кода: Работа с правами доступа к файлам в Qt

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

void printPermissions(const QString &path)
{
    QFileInfo info(path);
    if (!info.exists()) {
        qWarning() << "Файл не существует:" << path;
        return;
    }

    QFileDevice::Permissions perms = QFile::permissions(path);

    QString result;
    result += (perms & QFileDevice::ReadOwner)  ? "r" : "-";
    result += (perms & QFileDevice::WriteOwner) ? "w" : "-";
    result += (perms & QFileDevice::ExeOwner)   ? "x" : "-";
    result += (perms & QFileDevice::ReadGroup)  ? "r" : "-";
    result += (perms & QFileDevice::WriteGroup) ? "w" : "-";
    result += (perms & QFileDevice::ExeGroup)   ? "x" : "-";
    result += (perms & QFileDevice::ReadOther)  ? "r" : "-";
    result += (perms & QFileDevice::WriteOther) ? "w" : "-";
    result += (perms & QFileDevice::ExeOther)   ? "x" : "-";

    qDebug() << path << ":" << result
             << "(владелец:" << info.owner() << ")"
             << "только чтение:" << !info.isWritable();
}

bool makeReadOnly(const QString &path)
{
    QFileDevice::Permissions perms = QFile::permissions(path);

    // Снимаем все права на запись, оставляя права на чтение
    perms &= ~(QFileDevice::WriteOwner | QFileDevice::WriteGroup | QFileDevice::WriteOther);

    if (!QFile::setPermissions(path, perms)) {
        qWarning() << "Не удалось изменить права доступа для" << path;
        return false;
    }
    return true;
}

bool makeExecutable(const QString &path)
{
    QFileDevice::Permissions perms = QFile::permissions(path);
    perms |= QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeUser;

    return QFile::setPermissions(path, perms);
}

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

    QString testFile = "script.sh";
    QFile f(testFile);
    f.open(QIODevice::WriteOnly);
    f.write("#!/bin/bash\necho Hello from Qt\n");
    f.close();

    printPermissions(testFile);

    if (makeExecutable(testFile)) {
        qDebug() << "Файл сделан исполняемым";
        printPermissions(testFile);
    }

    if (makeReadOnly(testFile)) {
        qDebug() << "Файл сделан доступным только для чтения";
        printPermissions(testFile);
    }

    return 0;
}

Пояснения к коду

Флаги Permissions объединяются битовыми операциями |, &, ~, что позволяет точечно изменять отдельные биты, не затрагивая остальные — именно так и нужно работать с правами, а не присваивать новое значение «с нуля», теряя существующие настройки.

QFileInfo::owner(), ownerId(), group(), groupId() дают информацию о владельце на POSIX-системах; на Windows эти методы возвращают данные, полученные через соответствующие Win32 API, но концептуально модель «владелец/группа» там вторична по отношению к ACL.

Подводные камни

  1. Windows не различает Owner/Group/Other в классическом смысле. Установка WriteGroup или WriteOther без WriteOwner на Windows может не дать ожидаемого эффекта — реальное поведение определяется ACL файла, а не этими тремя уровнями. Если нужен точный контроль доступа на Windows, требуется работать с SetNamedSecurityInfo через WinAPI напрямую.
  1. ExeOwner на Windows. Понятие «исполняемый файл» на Windows определяется расширением (.exe, .bat) и ассоциациями, а не битом прав доступа, как в Unix. Установка ExeOwner для .txt-файла на Windows не сделает его исполняемым в общепринятом смысле.
  1. Права не применяются мгновенно к открытым файловым дескрипторам. Изменение прав через setPermissions() влияет на новые попытки открытия файла; уже открытый QFile продолжает работать с правами, действовавшими на момент открытия (зависит от ОС).
  1. Сетевые файловые системы и контейнеры. Внутри Docker-контейнеров или на смонтированных сетевых ресурсах (особенно при использовании bind-mount с Windows-хостом) права могут отображаться некорректно или игнорироваться полностью.
  1. Требуются права самого процесса. setPermissions() завершится ошибкой, если процесс не имеет прав на изменение атрибутов файла (например, не является владельцем на Linux и не запущен от root/sudo).
  1. umask и права по умолчанию. Qt не учитывает umask при создании новых файлов через QFile — по умолчанию создаются файлы с правами, зависящими от ОС и системных настроек, и их может потребоваться скорректировать сразу после создания через setPermissions().

При разработке кросс-платформенных приложений важно помнить: QFile::permissions() даёт достаточный уровень абстракции для большинства задач (сделать файл доступным только для чтения, проверить, можно ли в него писать), но не заменяет платформо-специфичный API там, где нужен тонкий контроль через ACL.

================================================================================