Tuesday, December 5, 2017

java walkFileTree to mimic chmod -R 755 does not work

Leave a Comment

I want to ensure correct permissions in a directory tree (0755 for directories and 644 for files). Here is my class:

package NRF_Utils;  import java.io.IOException; import java.nio.file.FileVisitResult; import static java.nio.file.FileVisitResult.*; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import java.util.logging.Logger; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions;  public class DirectoryTreeOperations {     private static final Logger log = Logger.getLogger("spv." + DirectoryTreeOperations.class.getName());      public static boolean setPermissions(String root, String directoryPermissions, String filePermissions) {         boolean result = true;         //declaring the path to delete         final Path path = Paths.get(root);         final Set<PosixFilePermission> dirPermissions = PosixFilePermissions.fromString(directoryPermissions);         final Set<PosixFilePermission> filPermissions = PosixFilePermissions.fromString(filePermissions);          try {             Files.walkFileTree(path, new FileVisitor<Path>() {                  @Override                 public FileVisitResult preVisitDirectory(Path dir,                         BasicFileAttributes attrs) throws IOException {                     System.out.println("setting dir permission on " + dir);                     Files.setPosixFilePermissions(dir, dirPermissions);                                      return CONTINUE;                 }                  @Override                 public FileVisitResult visitFile(Path file,                         BasicFileAttributes attrs) throws IOException {                     System.out.println("setting file permission on " + file);                     Files.setPosixFilePermissions(file, filPermissions);                     return CONTINUE;                 }                  @Override                 public FileVisitResult visitFileFailed(Path file, IOException exc)                         throws IOException {                     log.severe("visitFileFailed failed on " + file + " : " + exc);                     return CONTINUE;                 }                  @Override                 public FileVisitResult postVisitDirectory(Path dir,                         IOException exc) throws IOException {                     return CONTINUE;                 }              });         } catch (IOException e) {             log.severe("setPermissions failed " + e);             result = false;         }         return result;     }      // TODO remove and provide a proper unit test     public static void main(String [] arg) {         System.out.println ("setPermissions returned: " + setPermissions("/tmp/x", "rwxr-xr-x", "rw-r--r--"));     } } 

To perform test, I have done:

mkdir -p /tmp/x/y/z;touch /tmp/x/y/z/f;chmod 000 /tmp/x/y/z 

The output is:

setting dir permission on /tmp/x setting dir permission on /tmp/x/y setPermissions returned: true nov. 26, 2017 2:45:00 PM NRF_Utils.DirectoryTreeOperations$1 visitFileFailed GRAVE: visitFileFailed failed on /tmp/x/y/z : java.nio.file.AccessDeniedException: /tmp/x/y/z 

It seems java does not want to explore the directory tree because the permissions are wrong, but my aim was to explore the directory tree to fix permissions. Is the API crazy ?

3 Answers

Answers 1

It does seem strange it would behave this way, so maybe not a crazy API, but the implementation certainly doesn't cater to the specific case of using preVisitDirectory() to make the directory accessible.

The Files.walkFileTree() doc says:

Where the file is a directory, and the directory could not be opened, then the visitFileFailed method is invoked with the I/O exception, after which, the file tree walk continues, by default, at the next sibling of the directory.

and looking at the implementation, it does actually try to open the directory before it calls visitor.preVisitDirectory() which explains the issue you are running into.

As a solution, you could grab the FileTreeWalker.java source, and make the minor adjustment in your own local version of the FileTreeWalker - it's straight forward just look for the call to visitor.preVisitDirectory() and move the open directory code to occur after that. Then change your code to:

  new FileTreeWalker(EnumSet.noneOf(FileVisitOption.class), new FileVisitor<Path>() {      ...   }, Integer.MAX_VALUE).walk(path); 

When you run, you'll get:

setting dir permission on /tmp/x setting dir permission on /tmp/x/y setting dir permission on /tmp/x/y/z setting file permission on /tmp/x/y/z/f setPermissions returned: true 

which shows your of implementation of FileVisitor is fine.

Hope that helps.

Answers 2

When you do chmod 755 /tmp/x/y/z from the command line, it does not check the permission on z itself. As long as you have sufficient access to y, you can perform chmod on z.

OTOH, if you look at the source code of Files.walkFileTree, it actually tries to access every target file in its own visit method, to gather file attributes etc., before it calls your visitor methods. Hence the access denied error.

By the look of it, you might have to roll your own file tree walker, where you would just go ahead change the permission without trying to access the target path first.

Answers 3

The only issue with walkFileTree is that it doesn't call preVisitDirectory() on directories it doesn't have permission to search. You can work around that by fixing the permissions just ahead of where walkFileTree is looking. I.e., by manually scanning the direct children of any directory handed to preVisitDirectoy() and applying permissions there (rather than in visitFile):

            @Override             public FileVisitResult preVisitDirectory(Path dir,                     BasicFileAttributes attrs) throws IOException {                 for(File f : new File(dir.toUri()).listFiles()) { //iterate over the contents of the directory                     System.out.println("setting dir permission on " + f);                     Files.setPosixFilePermissions(dir.resolve(f.getName()), dirPermissions);                 }                  //System.out.println("setting dir permission on " + dir);                 //Files.setPosixFilePermissions(dir, dirPermissions);                 return CONTINUE;             } 

Then you can comment out visitFile()'s body:

            @Override             public FileVisitResult visitFile(Path file,                     BasicFileAttributes attrs) throws IOException {             //    System.out.println("setting file permission on " + file);             //    Files.setPosixFilePermissions(file, filPermissions);                 return CONTINUE;             } 

Since we're now applying permissions one layer below where the walk has made it to, we need an additional call just inside the try block:

        System.out.println("setting dir permission on " + path); //in case the initial path needs fixing         Files.setPosixFilePermissions(path, dirPermissions); 

Here's the whole thing together (well, I took off your package declaration for convenience):

import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import static java.nio.file.FileVisitResult.*; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import java.util.logging.Logger; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions;  public class DirectoryTreeOperations {     private static final Logger log = Logger.getLogger("spv." + DirectoryTreeOperations.class.getName());      public static boolean setPermissions(String root, String directoryPermissions, String filePermissions) {         boolean result = true;         //declaring the path to delete         final Path path = Paths.get(root);         final Set<PosixFilePermission> dirPermissions = PosixFilePermissions.fromString(directoryPermissions);         final Set<PosixFilePermission> filPermissions = PosixFilePermissions.fromString(filePermissions);          try {             System.out.println("setting dir permission on " + path); //in case the initial path needs fixing             Files.setPosixFilePermissions(path, dirPermissions);              Files.walkFileTree(path, new FileVisitor<Path>() {                 @Override                 public FileVisitResult preVisitDirectory(Path dir,                         BasicFileAttributes attrs) throws IOException {                     for(File f : new File(dir.toUri()).listFiles()) { //iterate over the contents of the directory                         System.out.println("setting dir permission on " + f);                         Files.setPosixFilePermissions(dir.resolve(f.getName()), dirPermissions);                     }                      //System.out.println("setting dir permission on " + dir);                     //Files.setPosixFilePermissions(dir, dirPermissions);                     return CONTINUE;                 }                  @Override                 public FileVisitResult visitFile(Path file,                         BasicFileAttributes attrs) throws IOException {                 //    System.out.println("setting file permission on " + file);                 //    Files.setPosixFilePermissions(file, filPermissions);                     return CONTINUE;                 }                  @Override                 public FileVisitResult visitFileFailed(Path file, IOException exc)                         throws IOException {                     log.severe("visitFileFailed failed on " + file + " : " + exc);                     return CONTINUE;                 }                  @Override                 public FileVisitResult postVisitDirectory(Path dir,                         IOException exc) throws IOException {                     return CONTINUE;                 }              });         } catch (IOException e) {             log.severe("setPermissions failed " + e);             result = false;         }         return result;     }      // TODO remove and provide a proper unit test     public static void main(String [] arg) {         System.out.println ("setPermissions returned: " + setPermissions("/tmp/x", "rwxr-xr-x", "rw-r--r--"));     } } 

When run on the example case:

$ mkdir -p /tmp/x/y/z;touch /tmp/x/y/z/f;chmod 000 /tmp/x/y/z $ find /tmp/x /tmp/x /tmp/x/y /tmp/x/y/z find: â/tmp/x/y/zâ: Permission denied $ java DirectoryTreeOperations setting dir permission on /tmp/x setting dir permission on /tmp/x/y setting dir permission on /tmp/x/y/z setting dir permission on /tmp/x/y/z/f setPermissions returned: true $ find /tmp/x /tmp/x /tmp/x/y /tmp/x/y/z /tmp/x/y/z/f 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment