Find the directory for a FileStore
Asked Answered
D

4

14

I'm trying to find a way to detect when a flash drive has been plugged into my computer. So far, the solution I found was to poll FileSystem#getFileStores for changes. This does indeed tell me when the flash drive has been inserted, but as far as I can tell there is no way to retrieve the location for it. FileStore#type and FileStore#name both seem highly unreliable as their return value is implementation specific, but they appear to be the only methods that might return any relevant information that might help find the directory for the FileStore.

With that in mind, the following code:

public class Test {
    public static void main(String[] args) throws IOException {
        for (FileStore store : FileSystems.getDefault().getFileStores()) {
            System.out.println(store);
            System.out.println("\t" + store.name());
            System.out.println("\t" + store.type());
            System.out.println();
        }
    }
}

Gave me this output:

/ (/dev/sda5)
    /dev/sda5
    ext4

/* snip */

/media/TI103426W0D (/dev/sda2)
    /dev/sda2
    fuseblk

/media/flashdrive (/dev/sdb1)
    /dev/sdb1
    vfat

As it turns out, FileStore#type returns the format of the drive and FileStore#name returns the location of the device file for the drive. As far as I can tell, the only method which has the location of the drive is the toString method, but extracting the path name out of it seems dangerous because I'm not sure how well that particular solution would hold up on other operating systems and future versions of Java.

Is there something I'm missing here or is this simply not possible purely with Java?

System Information:

$ java -version
java version "1.7.0_03"
OpenJDK Runtime Environment (IcedTea7 2.1.1pre) (7~u3-2.1.1~pre1-1ubuntu2)
OpenJDK Client VM (build 22.0-b10, mixed mode, sharing)

$ uname -a
Linux jeffrey-pc 3.2.0-24-generic-pae #37-Ubuntu SMP Wed Apr 25 10:47:59 UTC 2012 i686 athlon i386 GNU/Linux
Dupre answered 21/5, 2012 at 0:25 Comment(0)
D
10

Here's a temporary work around until a better solution is found:

public Path getRootPath(FileStore fs) throws IOException {
    Path media = Paths.get("/media");
    if (media.isAbsolute() && Files.exists(media)) { // Linux
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(media)) {
            for (Path p : stream) {
                if (Files.getFileStore(p).equals(fs)) {
                    return p;
                }
            }
        }
    } else { // Windows
        IOException ex = null;
        for (Path p : FileSystems.getDefault().getRootDirectories()) {
            try {
                if (Files.getFileStore(p).equals(fs)) {
                    return p;
                }
            } catch (IOException e) {
                ex = e;
            }
        }
        if (ex != null) {
            throw ex;
        }
    }
    return null;
}

As far as I know, this solution will only work for Windows and Linux systems.

You have to catch the IOException in the Windows loop because if there is no CD in the CD drive an exception is thrown when you try to retrieve the FileStore for it. This might happen before you iterate over every root.

Dupre answered 21/5, 2012 at 0:25 Comment(6)
Does this really work for Windows? It seems like it would miss a drive which I mount at C:\Data. The whole point I'm using this newer API is that it promised to find me all the mount points and not just the roots.Rip
Also, /media? Don't you mean /mnt?Rip
@Trejkaz I didn't know you could do that. It works for the standard case where drives are mounted as letters. At least on Ubuntu, things are mounted by default to /media. You could also use /etc/mtab for a more flexible work around on linux systems (I don't really use Windows that much, so I don't know of another).Dupre
People in Linux land seem to recommend running mount and parsing the output - since the output of these tools is regular enough for shell script parsing to deal with, Java doesn't have much problem. Still not a very satisfying workaround however, and I am back to having no solution at all for Windows. Also I think you mean the common case, not the standard case. The word "standard" rarely applies to Windows as it is. :DRip
@Trejkaz Fair enough. I submitted a RFE to Oracle about this a while back, hopefully they'll do something about it within this decade ;)Dupre
Indeed. In the meantime, there are hacks. :DRip
R
4

This is what I have ended up doing. This is limited to Windows + UNIX but avoids using external tools or additional library calls. It steals the information Java already has in the FileStore objects

LinuxFileStore definitely extends UnixFileStore, so it will work. Same deal for Solaris. Since Mac OS X is UNIX, it probably works there but I'm not sure because I couldn't see its subclass in any place I was looking.

public class FileStoreHacks {
    /**
     * Stores the known hacks.
     */
    private static final Map<Class<? extends FileStore>, Hacks> hacksMap;
    static {
        ImmutableMap.Builder<Class<? extends FileStore>, Hacks> builder =
            ImmutableMap.builder();

        try {
            Class<? extends FileStore> fileStoreClass =
                Class.forName("sun.nio.fs.WindowsFileStore")
                    .asSubclass(FileStore.class);
            builder.put(fileStoreClass, new WindowsFileStoreHacks(fileStoreClass));
        } catch (ClassNotFoundException e) {
            // Probably not running on Windows.
        }

        try {
            Class<? extends FileStore> fileStoreClass =
                Class.forName("sun.nio.fs.UnixFileStore")
                    .asSubclass(FileStore.class);
            builder.put(fileStoreClass, new UnixFileStoreHacks(fileStoreClass));
        } catch (ClassNotFoundException e) {
            // Probably not running on UNIX.
        }

        hacksMap = builder.build();
    }

    private FileStoreHacks() {
    }

    /**
     * Gets the path from a file store. For some reason, NIO2 only has a method
     * to go in the other direction.
     *
     * @param store the file store.
     * @return the path.
     */
    public static Path getPath(FileStore store) {
        Hacks hacks = hacksMap.get(store.getClass());
        if (hacks == null) {
            return null;
        } else {
            return hacks.getPath(store);
        }
    }

    private static interface Hacks {
        Path getPath(FileStore store);
    }

    private static class WindowsFileStoreHacks implements Hacks {
        private final Field field;

        public WindowsFileStoreHacks(Class<?> fileStoreClass) {
            try {
                field = fileStoreClass.getDeclaredField("root");
                field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("file field not found", e);
            }
        }

        @Override
        public Path getPath(FileStore store) {
            try {
                String root = (String) field.get(store);
                return FileSystems.getDefault().getPath(root);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Denied access", e);
            }
        }
    }

    private static class UnixFileStoreHacks implements Hacks {
        private final Field field;

        private UnixFileStoreHacks(Class<?> fileStoreClass) {
            try {
                field = fileStoreClass.getDeclaredField("file");
                field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("file field not found", e);
            }
        }

        @Override
        public Path getPath(FileStore store) {
            try {
                return (Path) field.get(store);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Denied access", e);
            }
        }
    }
}
Rip answered 9/4, 2013 at 5:15 Comment(4)
Note to others: Since private fields are subject to change without notice, this will only work for the current version of Java until confirmed otherwise.Dupre
Yup. Definitely add unit tests to detect the behaviour changing, if you use this.Rip
Did not work on CentOS (6). FileStore is LinuxFileStore, but in hacksMap it was added as UnixFileStore. And Hacks hacks = hacksMap.get(store.getClass()); find nothing. Worse of it, the getDeclaredField("file") - returns original file, not mount point, that is hidden in container byte [] dir inside UnixMountEntry entryRelume
Yeah. you'd have to implement a new hack for LinuxFileStore, but it would work after you implement one.Rip
V
0

I've not really explored this area of java, but I found this, which seems to be related. It uses File.listRoots()

There also seems to be a number of related questions linked there too.

Verboten answered 21/5, 2012 at 0:48 Comment(4)
File.listRoots will only work for Windows, and I've reviewed those questions to no avail. They were all asked before nio2 came outDupre
Ahh >_> I'll keep looking and edit this answer if I find anythingVerboten
@Dupre it won't even work for Windows because on Windows, you can have drives not mounted at a drive letter.Rip
Better to use FileSystems.getDefault().getRootDirectories() than File.listRoots().Telltale
Q
0

This works for Windows:

    public Path getFileStoreRootPath(FileStore fs) throws Exception {
    for (Path root : FileSystems.getDefault().getRootDirectories()) {
        if (Files.isDirectory(root) && Files.getFileStore(root).equals(fs)) {
            return root;
        }
    }

    throw new RuntimeException("Root directory for filestore " + fs + " not found");
}

Basically, by filtering by condition Files.isDirectory(root) we are excluding all CD/DVD drives which will throw IOException when compact-disc is not inserted.

Quizmaster answered 23/3, 2020 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.