CodeGym /Cursos /JAVA 25 SELF /Permissões e acesso ao sistema de arquivos

Permissões e acesso ao sistema de arquivos

JAVA 25 SELF
Nível 38 , Lição 3
Disponível

1. Permissões de acesso no SO

Ao trabalhar com arquivos e pastas, é importante lembrar: o sistema operacional (SO) os protege por meio de um sistema de permissões. Isso significa que nem todo programa (e nem todo usuário) pode ler, alterar ou excluir qualquer arquivo.

POSIX (Linux, macOS e outros)

Em sistemas POSIX (Unix, Linux, macOS), cada arquivo e pasta possui permissões para três categorias:

  • Proprietário (user)
  • Grupo (group)
  • Outros (others)

Para cada categoria, definem-se três tipos de permissão:

  • r — read (leitura)
  • w — write (gravação)
  • x — execute (execução)

Exemplo:
-rw-r--r--
Isso significa: o proprietário pode ler e gravar; os demais — apenas ler.

Windows

No Windows, as permissões são definidas pelo sistema ACL (Access Control List) — listas de permissões para usuários e grupos. É possível configurar de forma flexível quem pode fazer o quê com um arquivo ou pasta (ler, gravar, modificar, executar etc.).

Importante:
Um programa Java trabalha com arquivos dentro das permissões do usuário sob o qual foi executado. Se o usuário não tiver permissão para um arquivo — o programa também não conseguirá lê-lo ou modificá-lo.

2. Exceção AccessDeniedException e suas causas

Ao trabalhar com arquivos em Java (especialmente via a API NIO), você pode se deparar com a exceção:

java.nio.file.AccessDeniedException

Essa exceção é lançada quando seu programa não tem permissão para executar uma operação em um arquivo ou diretório.

Causas principais:

  • Sem permissão de leitura do arquivo (por exemplo, o arquivo está protegido contra leitura).
  • Sem permissão de gravação no arquivo ou na pasta (por exemplo, tentando gravar em uma pasta do sistema).
  • Sem permissão de execução do arquivo (relevante para executar programas).
  • A pasta ou o arquivo estão protegidos contra alterações (por exemplo, somente leitura).
  • O arquivo ou a pasta estão em uso por outro processo (muito comum no Windows).

Exemplo:

Path path = Paths.get("/etc/shadow"); // arquivo de sistema do Linux
Files.readAllLines(path); // AccessDeniedException!

O que fazer?

  • Verifique as permissões do arquivo/pasta.
  • Execute o programa com um usuário que tenha as permissões necessárias.
  • Evite gravar em diretórios do sistema sem necessidade.

3. Verificação de permissões no Java: métodos Files.isReadable(), isWritable(), isExecutable()

O Java fornece métodos práticos para verificar permissões em arquivos ou pastas:

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

System.out.println(Files.isReadable(path));   // true, se for possível ler
System.out.println(Files.isWritable(path));   // true, se for possível gravar
System.out.println(Files.isExecutable(path)); // true, se for possível executar

Esses métodos mostram como o sistema enxerga suas permissões naquele momento.

Mas!
Eles não garantem que a operação realmente vá concluir com sucesso. As razões podem variar: o arquivo pode estar bloqueado por outro programa, as permissões podem ter mudado após a verificação, em unidades de rede os resultados dependem do servidor e, às vezes, o SO informa uma coisa mas aplica outras restrições.

Por isso, no Java é melhor primeiro verificar as permissões e, em seguida, envolver a operação real de leitura ou escrita em try-catch — isso é mais confiável.

Problema TOCTOU (Time Of Check To Time Of Use)

Isso está diretamente associado ao problema TOCTOU, quando algo muda entre a verificação e o momento do uso. Por exemplo:

  1. Você verificou que o arquivo está disponível para gravação (isWritable).
  2. Nesse meio-tempo, outro processo ou usuário alterou as permissões — agora o arquivo está protegido.
  3. Você tenta gravar os dados — recebe AccessDeniedException.

Conclusão:
A verificação de permissões fornece apenas uma dica do estado atual, mas não garante sucesso. Sempre trate as exceções ao trabalhar com arquivos.

4. Princípio de “gravação segura” (atomic write)

Por que precisamos de um modo de gravação seguro (atômico)?

Às vezes, durante a gravação de um arquivo, pode ocorrer uma falha: o programa caiu, faltou energia, não havia espaço suficiente em disco... Como resultado, o arquivo pode ficar corrompido ou parcialmente gravado. Isso é especialmente perigoso para dados importantes (por exemplo, configurações, bancos de dados, documentos).

Gravação segura — é uma maneira de garantir que o arquivo ou seja totalmente atualizado, ou permaneça no estado anterior. Essa abordagem é chamada de gravação atômica (atomic write).

Como implementar a gravação segura em Java?

Padrão:

  • Gravamos os dados em um arquivo temporário (geralmente na mesma pasta).
  • Se a gravação for bem-sucedida — movemos de forma atômica o arquivo temporário para o lugar do arquivo principal (substituindo-o).

Por que isso funciona?
A operação de mover um arquivo (rename/move) dentro de um mesmo sistema de arquivos geralmente é atômica: ou o arquivo é substituído por completo, ou não é. Se algo der errado — o arquivo original permanece intacto.

Exemplo de código: gravação segura de arquivo

import java.nio.file.*;

public class SafeWriteDemo {
    public static void safeWrite(Path target, byte[] data) throws Exception {
        // 1. Criamos um arquivo temporário na mesma pasta
        Path tempFile = Files.createTempFile(target.getParent(), "tmp_", ".tmp");

        try {
            // 2. Gravamos os dados no arquivo temporário
            Files.write(tempFile, data);

            // 3. Movemos de forma atômica o arquivo temporário para o destino
            Files.move(
                tempFile,
                target,
                StandardCopyOption.REPLACE_EXISTING,
                StandardCopyOption.ATOMIC_MOVE
            );
        } finally {
            // Se algo der errado — removemos o arquivo temporário
            Files.deleteIfExists(tempFile);
        }
    }

    public static void main(String[] args) throws Exception {
        Path file = Paths.get("important.txt");
        byte[] content = "Dados muito importantes".getBytes();

        safeWrite(file, content);
        System.out.println("Arquivo gravado com segurança!");
    }
}

Atenção:

  • Use Files.createTempFile() para criar o arquivo temporário.
  • Para mover, utilize a opção ATOMIC_MOVE — isso garante atomicidade (se suportado pelo SO e pelo sistema de arquivos).
  • Se algo der errado — o arquivo temporário é removido (Files.deleteIfExists).

Quando isso é especialmente importante?

  • Ao trabalhar com arquivos de configuração, bancos de dados e logs.
  • Se o arquivo puder ser lido por outro processo a qualquer momento.
  • Se uma falha de gravação puder causar perda ou corrupção de dados.

5. Registro em log e tratamento de erros de acesso

Como tratar corretamente erros de acesso?

Ao trabalhar com arquivos, sempre use tratamento de exceções (try-catch). Isso permite:

  • Informar corretamente o usuário sobre o problema (por exemplo, “Sem permissão para gravar na pasta”).
  • Registrar o erro no log para análise posterior.
  • Evitar “derrubar” todo o programa por causa de uma única operação mal‑sucedida.

Exemplo: tratamento de AccessDeniedException

import java.nio.file.*;

public class FileAccessDemo {
    public static void main(String[] args) {
        Path file = Paths.get("/etc/shadow"); // exemplo para Linux

        try {
            Files.readAllLines(file);
        } catch (AccessDeniedException ade) {
            System.err.println("Erro de acesso: sem permissão para leitura do arquivo " + file);
            // Você pode registrar no log ou oferecer ao usuário escolher outro arquivo
        } catch (Exception e) {
            System.err.println("Outro erro: " + e.getMessage());
        }
    }
}

Registro em log de erros

Em aplicações reais, utilize bibliotecas de logging (por exemplo, java.util.logging, Log4j, SLF4J). Isso permite:

  • Registrar erros com detalhes (stack trace, horário, usuário).
  • Analisar os logs para localizar e corrigir problemas.
  • Não exibir mensagens “assustadoras” ao usuário; registre-as apenas no log.

Exemplo com registro em log:

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("Sem acesso ao arquivo: " + file);
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Erro ao trabalhar com o arquivo", e);
        }
    }
}

6. Erros comuns

Erro nº 1: ignorar exceções ao trabalhar com arquivos.
Nunca escreva simplesmente Files.write(path, data) sem try-catch — se algo der errado, o programa vai cair.

Erro nº 2: verificar permissões sem tratar TOCTOU.
Não confie apenas em Files.isWritable() e métodos semelhantes. Mesmo que retornem “pode”, a operação pode falhar. Sempre trate as exceções (por exemplo, AccessDeniedException).

Erro nº 3: sobrescrever um arquivo existente sem backup.
Se o arquivo for importante — faça um backup antes de gravar ou use gravação atômica com StandardCopyOption.ATOMIC_MOVE.

Erro nº 4: não remover arquivos temporários após falhas.
Se algo der errado durante a gravação atômica — o arquivo temporário pode permanecer. Use finally e Files.deleteIfExists().

Erro nº 5: não registrar erros de acesso.
Se o programa não conseguiu gravar ou ler um arquivo — o usuário deve ser informado, e você deve ver os detalhes no log. Use java.util.logging/SLF4J e registre as exceções com o stack trace.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION