1. Droits d’accès dans le système d’exploitation
Lorsque vous travaillez avec des fichiers et des dossiers, il est important de se rappeler que le système d’exploitation (OS) les protège à l’aide d’un système de droits d’accès. Cela signifie que n’importe quel programme (et pas n’importe quel utilisateur) ne peut pas lire, modifier ou supprimer n’importe quel fichier.
POSIX (Linux, macOS, etc.)
Dans les systèmes POSIX (Unix, Linux, macOS), chaque fichier et dossier possède des droits d’accès pour trois catégories :
- Propriétaire (user)
- Groupe (group)
- Autres (others)
Pour chaque catégorie, trois types de droits sont définis :
- r — read (lecture)
- w — write (écriture)
- x — execute (exécution)
Exemple :
-rw-r--r--
Cela signifie : le propriétaire peut lire et écrire, les autres — seulement lire.
Windows
Sous Windows, les droits d’accès sont définis via le système ACL (Access Control List) — des listes d’autorisations pour les utilisateurs et les groupes. Vous pouvez y configurer finement qui peut faire quoi avec un fichier ou un dossier (lire, écrire, modifier, exécuter, etc.).
Important :
Un programme Java fonctionne avec les fichiers dans le cadre des droits de l’utilisateur sous lequel il est lancé. Si l’utilisateur n’a pas les droits sur un fichier, le programme ne pourra pas non plus le lire ou le modifier.
2. Exception AccessDeniedException et ses causes
Lorsque vous travaillez avec des fichiers en Java (surtout via l’API NIO), vous pouvez rencontrer l’exception :
java.nio.file.AccessDeniedException
Cette exception est levée si votre programme n’a pas les droits pour effectuer l’opération sur un fichier ou un répertoire.
Causes principales :
- Pas de droits de lecture du fichier (par exemple, le fichier est protégé en lecture).
- Pas de droits d’écriture dans le fichier ou le dossier (par exemple, vous essayez d’écrire dans un dossier système).
- Pas de droits d’exécution du fichier (pertinent pour le lancement de programmes).
- Le dossier ou le fichier est protégé contre les modifications (par exemple, lecture seule).
- Le fichier ou le dossier est occupé par un autre processus (très fréquent sous Windows).
Exemple :
Path path = Paths.get("/etc/shadow"); // fichier système Linux
Files.readAllLines(path); // AccessDeniedException!
Que faire ?
- Vérifiez les droits d’accès au fichier/dossier.
- Lancez le programme avec un utilisateur ayant les droits nécessaires.
- N’essayez pas d’écrire dans des répertoires système sans nécessité.
3. Vérification des droits en Java : méthodes Files.isReadable(), isWritable(), isExecutable()
Java fournit des méthodes pratiques pour vérifier les droits sur un fichier ou un dossier :
Path path = Paths.get("example.txt");
System.out.println(Files.isReadable(path)); // true si la lecture est possible
System.out.println(Files.isWritable(path)); // true si l'écriture est possible
System.out.println(Files.isExecutable(path)); // true si l'exécution est possible
Ces méthodes indiquent comment le système voit vos droits à l’instant T.
Mais !
Elles ne garantissent pas que l’opération réussira réellement. Les raisons peuvent être diverses : le fichier peut être verrouillé par un autre programme, les droits peuvent avoir changé après la vérification, sur des lecteurs réseau les résultats dépendent du serveur, et parfois l’OS indique une chose mais applique d’autres restrictions.
Par conséquent, en Java, mieux vaut d’abord vérifier les droits, puis encapsuler l’opération réelle de lecture ou d’écriture dans un try-catch — c’est plus fiable.
Problème TOCTOU (Time Of Check To Time Of Use)
Ce problème est directement lié au cas où, entre la vérification des droits et l’opération elle‑même, quelque chose change. Par exemple :
- Vous avez vérifié que le fichier est accessible en écriture (isWritable).
- Pendant ce temps, un autre processus ou utilisateur a modifié les droits — le fichier est désormais protégé.
- Vous essayez d’écrire des données — vous obtenez AccessDeniedException.
Conclusion :
La vérification des droits ne donne qu’une indication de l’état actuel, mais ne garantit pas la réussite de l’opération. Gérez toujours les exceptions lors du travail avec les fichiers.
4. Principe de « écriture sécurisée » (écriture atomique)
Pourquoi une méthode d’écriture sûre (atomique) est-elle nécessaire ?
Il peut arriver un incident lors de l’écriture d’un fichier : le programme plante, coupure de courant, manque d’espace disque... En conséquence, le fichier peut se retrouver corrompu ou partiellement écrit. C’est particulièrement dangereux pour des données importantes (par exemple, des paramètres, des bases de données, des documents).
L’écriture sécurisée — c’est une manière de garantir que le fichier est soit entièrement mis à jour, soit laissé dans son état précédent. Cette approche est appelée écriture atomique (atomic write).
Comment implémenter une écriture sécurisée en Java ?
Modèle :
- Écrire les données dans un fichier temporaire (en général dans le même dossier).
- Si l’écriture a réussi — déplacer de façon atomique le fichier temporaire à la place du fichier principal (en le remplaçant).
Pourquoi cela fonctionne‑t‑il ?
L’opération de déplacement d’un fichier (rename/move) au sein du même système de fichiers est généralement atomique : soit le fichier est entièrement remplacé, soit il ne l’est pas. Si quelque chose se passe mal — le fichier principal n’est pas touché.
Exemple de code : écriture sécurisée d’un fichier
import java.nio.file.*;
public class SafeWriteDemo {
public static void safeWrite(Path target, byte[] data) throws Exception {
// 1. Crée un fichier temporaire dans le même dossier
Path tempFile = Files.createTempFile(target.getParent(), "tmp_", ".tmp");
try {
// 2. Écrit les données dans le fichier temporaire
Files.write(tempFile, data);
// 3. Déplace atomiquement le fichier temporaire à la place du fichier cible
Files.move(
tempFile,
target,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.ATOMIC_MOVE
);
} finally {
// En cas de problème — on supprime le fichier temporaire
Files.deleteIfExists(tempFile);
}
}
public static void main(String[] args) throws Exception {
Path file = Paths.get("important.txt");
byte[] content = "Données très importantes".getBytes();
safeWrite(file, content);
System.out.println("Le fichier a été écrit en toute sécurité !");
}
}
À noter :
- Utilisez Files.createTempFile() pour créer le fichier temporaire.
- Pour le déplacement, utilisez l’option ATOMIC_MOVE — cela garantit l’atomicité (si elle est prise en charge par l’OS et le système de fichiers).
- En cas de problème — le fichier temporaire est supprimé (Files.deleteIfExists).
Quand est-ce particulièrement important ?
- Lors du travail avec des fichiers de configuration, des bases de données, des journaux.
- Si le fichier peut être lu à tout moment par un autre processus.
- Si un échec d’écriture peut conduire à une perte ou à une corruption des données.
5. Journalisation et gestion des erreurs d’accès
Comment bien gérer les erreurs d’accès ?
Lors du travail avec des fichiers, utilisez toujours la gestion des exceptions (try-catch). Cela permet de :
- Informer correctement l’utilisateur du problème (par exemple, « Pas de droits d’écriture dans le dossier »).
- Consigner l’erreur dans un journal pour une analyse ultérieure.
- Éviter de « faire tomber » toute l’application à cause d’une seule opération ratée.
Exemple : gestion de AccessDeniedException
import java.nio.file.*;
public class FileAccessDemo {
public static void main(String[] args) {
Path file = Paths.get("/etc/shadow"); // exemple pour Linux
try {
Files.readAllLines(file);
} catch (AccessDeniedException ade) {
System.err.println("Erreur d'accès : pas les droits de lecture du fichier " + file);
// Vous pouvez l’enregistrer dans le journal ou proposer à l’utilisateur de choisir un autre fichier
} catch (Exception e) {
System.err.println("Autre erreur : " + e.getMessage());
}
}
}
Journalisation des erreurs
Dans les applications réelles, utilisez des systèmes de journalisation (par exemple, java.util.logging, Log4j, SLF4J). Cela permet de :
- Consigner les erreurs avec des détails (traces d’appels, heure, utilisateur).
- Analyser les journaux pour trouver et corriger les problèmes.
- Éviter d’afficher des messages « effrayants » à l’utilisateur, et les envoyer uniquement dans le journal.
Exemple avec journalisation :
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("Accès refusé au fichier : " + file);
} catch (Exception e) {
logger.log(Level.SEVERE, "Erreur lors de la manipulation du fichier", e);
}
}
}
6. Erreurs courantes
Erreur n° 1 : ignorer les exceptions lors du travail avec des fichiers.
Ne jamais écrire simplement Files.write(path, data) sans try-catch — si quelque chose se passe mal, l’application plantera.
Erreur n° 2 : vérifier les droits sans traiter le TOCTOU.
Ne vous fiez pas uniquement à Files.isWritable() et aux méthodes similaires. Même si elles renvoient « possible », l’opération peut échouer. Gérez toujours les exceptions (par exemple, AccessDeniedException).
Erreur n° 3 : écraser un fichier existant sans sauvegarde.
Si le fichier est important — faites une sauvegarde avant d’écrire ou utilisez l’écriture atomique avec StandardCopyOption.ATOMIC_MOVE.
Erreur n° 4 : ne pas supprimer les fichiers temporaires après un échec.
Si quelque chose se passe mal pendant l’écriture atomique — un fichier temporaire peut rester. Utilisez finally et Files.deleteIfExists().
Erreur n° 5 : ne pas journaliser les erreurs d’accès.
Si l’application n’a pas pu écrire ou lire un fichier — l’utilisateur doit en être informé, et vous devez voir les détails dans le journal. Utilisez java.util.logging/SLF4J et consignez les exceptions avec la pile d’appels.
GO TO FULL VERSION