JavaRush /Курсы /JAVA 25 SELF /Права и доступ к файловой системе

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

JAVA 25 SELF
38 уровень , 3 лекция
Открыта

1. Права доступа в ОС

Когда вы работаете с файлами и папками, важно помнить: операционная система (ОС) защищает их с помощью системы прав доступа. Это значит, что не любая программа (и не любой пользователь) может читать, изменять или удалять любой файл.

POSIX (Linux, macOS и др.)

В системах POSIX (Unix, Linux, macOS) у каждого файла и папки есть права доступа для трёх категорий:

  • Владелец (user)
  • Группа (group)
  • Остальные (others)

Для каждой категории задаются три типа прав:

  • r — read (чтение)
  • w — write (запись)
  • x — execute (исполнение)

Пример:
-rw-r--r--
Это значит: владелец может читать и писать, остальные — только читать.

Windows

В Windows права доступа задаются через систему ACL (Access Control List) — списки разрешений для пользователей и групп. Здесь можно гибко настраивать, кто и что может делать с файлом или папкой (читать, писать, изменять, запускать и т.д.).

Важно:
Java-программа работает с файлами в рамках прав пользователя, под которым она запущена. Если у пользователя нет прав на файл — программа тоже не сможет его прочитать или изменить.

2. Исключение AccessDeniedException и его причины

Когда вы работаете с файлами в Java (особенно через NIO API), вы можете столкнуться с исключением:

java.nio.file.AccessDeniedException

Это исключение выбрасывается, если у вашей программы нет прав на выполнение операции с файлом или директорией.

Основные причины:

  • Нет прав на чтение файла (например, файл защищён от чтения).
  • Нет прав на запись в файл или папку (например, пытаетесь записать в системную папку).
  • Нет прав на исполнение файла (актуально для запуска программ).
  • Папка или файл защищены от изменений (например, только для чтения).
  • Файл или папка заняты другим процессом (особенно часто на Windows).

Пример:

Path path = Paths.get("/etc/shadow"); // системный файл Linux
Files.readAllLines(path); // AccessDeniedException!

Что делать?

  • Проверьте права доступа к файлу/папке.
  • Запустите программу от имени пользователя с нужными правами.
  • Не пытайтесь писать в системные директории без необходимости.

3. Проверка прав в Java: методы Files.isReadable(), isWritable(), isExecutable()

Java предоставляет удобные методы для проверки прав на файл или папку:

Path path = Paths.get("example.txt");

System.out.println(Files.isReadable(path));   // true, если можно читать
System.out.println(Files.isWritable(path));   // true, если можно писать
System.out.println(Files.isExecutable(path)); // true, если можно запускать

Эти методы показывают, как система видит ваши права на данный момент.

Но!
Они не гарантируют, что операция действительно пройдёт успешно. Причины могут быть разными: файл может быть заблокирован другой программой, права могли измениться после проверки, на сетевых дисках результаты зависят от сервера, а иногда ОС сообщает одно, а применяет другие ограничения.

Поэтому в Java лучше сначала проверить права, а затем обернуть реальную операцию чтения или записи в try-catch — это надёжнее.

Проблема TOCTOU (Time Of Check To Time Of Use)

С этим напрямую связана ситуация TOCTOU, когда между проверкой права и самой операцией что-то меняется. Например:

  1. Вы проверили, что файл доступен для записи (isWritable).
  2. В этот момент другой процесс или пользователь изменил права — теперь файл защищён.
  3. Вы пытаетесь записать данные — получаете AccessDeniedException.

Вывод:
Проверка прав даёт лишь подсказку о текущем состоянии, но не гарантирует успешную операцию. Всегда обрабатывайте исключения при работе с файлами.

4. Принцип «безопасной записи» (atomic write)

Зачем нужен безопасный (атомарный) способ записи?

Иногда при записи файла может произойти сбой: программа упала, отключили электричество, не хватило места на диске... В результате файл может оказаться повреждённым или частично записанным. Это особенно опасно для важных данных (например, настроек, базы данных, документов).

Безопасная запись — это способ гарантировать, что файл либо полностью обновлён, либо остался в прежнем состоянии. Такой подход называют атомарной записью (atomic write).

Как реализовать безопасную запись в Java?

Шаблон:

  • Записываем данные во временный файл (обычно в той же папке).
  • Если запись прошла успешно — атомарно перемещаем временный файл на место основного (заменяя его).

Почему это работает?
Операция перемещения файла (rename/move) в пределах одной файловой системы обычно атомарна: либо файл полностью заменён, либо нет. Если что-то пошло не так — основной файл не тронут.

Пример кода: безопасная запись файла

import java.nio.file.*;

public class SafeWriteDemo {
    public static void safeWrite(Path target, byte[] data) throws Exception {
        // 1. Создаём временный файл в той же папке
        Path tempFile = Files.createTempFile(target.getParent(), "tmp_", ".tmp");

        try {
            // 2. Записываем данные во временный файл
            Files.write(tempFile, data);

            // 3. Атомарно перемещаем временный файл на место основного
            Files.move(
                tempFile,
                target,
                StandardCopyOption.REPLACE_EXISTING,
                StandardCopyOption.ATOMIC_MOVE
            );
        } finally {
            // Если что-то пошло не так — удаляем временный файл
            Files.deleteIfExists(tempFile);
        }
    }

    public static void main(String[] args) throws Exception {
        Path file = Paths.get("important.txt");
        byte[] content = "Очень важные данные".getBytes();

        safeWrite(file, content);
        System.out.println("Файл записан безопасно!");
    }
}

Обратите внимание:

  • Используем Files.createTempFile() для создания временного файла.
  • Для перемещения используем опцию ATOMIC_MOVE — это гарантирует атомарность (если поддерживается ОС и файловой системой).
  • Если что-то пошло не так — временный файл удаляется (Files.deleteIfExists).

Когда это особенно важно?

  • При работе с конфигурационными файлами, базами данных, журналами.
  • Если файл может быть прочитан другим процессом в любой момент.
  • Если сбой при записи может привести к потере или повреждению данных.

5. Логирование и обработка ошибок доступа

Как правильно обрабатывать ошибки доступа?

При работе с файлами всегда используйте обработку исключений (try-catch). Это позволит:

  • Корректно сообщить пользователю о проблеме (например, «Нет прав на запись в папку»).
  • Записать ошибку в лог для последующего анализа.
  • Не «ронять» всю программу из-за одной неудачной операции.

Пример: обработка AccessDeniedException

import java.nio.file.*;

public class FileAccessDemo {
    public static void main(String[] args) {
        Path file = Paths.get("/etc/shadow"); // пример для Linux

        try {
            Files.readAllLines(file);
        } catch (AccessDeniedException ade) {
            System.err.println("Ошибка доступа: нет прав на чтение файла " + file);
            // Можно записать в лог или предложить пользователю выбрать другой файл
        } catch (Exception e) {
            System.err.println("Другая ошибка: " + e.getMessage());
        }
    }
}

Логирование ошибок

В реальных приложениях используйте системы логирования (например, java.util.logging, Log4j, SLF4J). Это позволит:

  • Записывать ошибки с подробностями (стек вызовов, время, пользователь).
  • Анализировать логи для поиска и устранения проблем.
  • Не показывать пользователю «страшные» сообщения, а выводить их только в лог.

Пример с логированием:

import java.nio.file.*;
import java.util.logging.*;

public class FileLoggerDemo {
    private static final Logger logger = Logger.getLogger(FileLoggerDemo.class.getName());

    public static void main(String[] args) {
        Path file = Paths.get("data.txt");

        try {
            Files.readAllLines(file);
        } catch (AccessDeniedException ade) {
            logger.severe("Нет доступа к файлу: " + file);
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Ошибка при работе с файлом", e);
        }
    }
}

6. Типичные ошибки

Ошибка №1: Игнорирование исключений при работе с файлами.
Никогда не пишите просто Files.write(path, data) без try-catch — если что-то пойдёт не так, программа упадёт.

Ошибка №2: Проверка прав без обработки TOCTOU.
Не полагайтесь только на Files.isWritable() и подобные методы. Даже если они возвращают «можно», операция может не пройти. Всегда обрабатывайте исключения (например, AccessDeniedException).

Ошибка №3: Запись «поверх» существующего файла без резервной копии.
Если файл важен — делайте резервную копию перед записью или используйте атомарную запись с StandardCopyOption.ATOMIC_MOVE.

Ошибка №4: Не удаляете временные файлы после сбоя.
Если при атомарной записи что-то пошло не так — временный файл может остаться. Используйте finally и Files.deleteIfExists().

Ошибка №5: Не логируете ошибки доступа.
Если программа не смогла записать или прочитать файл — пользователь должен узнать об этом, а вы — увидеть подробности в логе. Используйте java.util.logging/SLF4J и фиксируйте исключения со стеком вызовов.

1
Задача
JAVA 25 SELF, 38 уровень, 3 лекция
Недоступна
Доступ к секретным чертежам: проверка разрешения на просмотр 👁️
Доступ к секретным чертежам: проверка разрешения на просмотр 👁️
1
Задача
JAVA 25 SELF, 38 уровень, 3 лекция
Недоступна
Подготовка склада для новых поставок: проверка разрешения на хранение 📦
Подготовка склада для новых поставок: проверка разрешения на хранение 📦
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Gusen14ka Уровень 41
14 июля 2026
Наконец-то реально новая и полезная инфа про atomic write и логгирование, но задач, конечно же, на эти темы не будет)
Shanechka Уровень 54
3 января 2026
Прям нахватает задачек на безопасную запись и логирование, было бы прикольно тоже попрактиковаться в этой главе)