8

I am using a library that uses Java NIO in order to directly map files to memory, but I am having trouble reading disks directly.

I can read the disks directly using FileInputStream with UNC, such as

File disk = new File("\\\\.\\PhysicalDrive0\\");
try (FileInputStream fis = new FileInputStream(disk);
    BufferedInputStream bis = new BufferedInputStream(fis)) {
    byte[] somebytes = new byte[10];
    bis.read(somebytes);
} catch (Exception ex) {
    System.out.println("Oh bother");
}

However, I can't extend this to NIO:

File disk = new File("\\\\.\\PhysicalDrive0\\");
Path path = disk.toPath();
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)){
    System.out.println("No exceptions! Yay!");
} catch (Exception ex) {
    System.out.println("Oh bother");
}

The stacktrace (up to the cause) is:

java.nio.file.FileSystemException: \\.\PhysicalDrive0\: The parameter is incorrect.

at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:115)
at java.nio.channels.FileChannel.open(FileChannel.java:287)
at java.nio.channels.FileChannel.open(FileChannel.java:334)
at hdreader.HDReader.testcode(HDReader.java:147)

I haven't been able to find a solution, though I saw something close on How to access specific raw data on disk from java. The answer by Daniel Alder suggesting the use of GLOBALROOT seems to be relevant, as the answer uses FileChannel in the answer, but I can't seem to find the drive using this pattern. Is there a way to list all devices under GLOBALROOT or something like that?

At the moment I am looking at replacing uses of NIO with straight InputStreams, but I want to avoid this if I can. Firstly, NIO was used for a reason, and secondly, it runs through a lot of code and will require a lot of work. Finally, I'd like to know how to implement something like Daniel's solution so that I can write to devices or use NIO in the future.

So in summary: how can I access drives directly with Java NIO (not InputStreams), and/or is there a way to list all devices accessible through GLOBALROOT so that I might use Daniel Alser's solution?

Summary of Answers: I have kept the past edits (below) to avoid confusion. With the help of EJP and Apangin I think I have a workable solution. Something like

private void rafMethod(long posn) {
    ByteBuffer buffer = ByteBuffer.allocate(512);
    buffer.rewind();
    try (RandomAccessFile raf = new RandomAccessFile(disk.getPath(), "r");
            SeekableByteChannel sbc = raf.getChannel()) {
        sbc.read(buffer);
    } catch (Exception ex) {
        System.out.println("Oh bother: " + ex);
        ex.printStackTrace();
    }

    return buffer;
}

This will work as long as the posn parameter is a multiple of the sector size (set at 512 in this case). Note that this also works with the Channels.newChannel(FileInputStream), which seems to always return a SeekableByteStream in this case and it appears it is safe to cast it to one.

From quick and dirty testing it appears that these methods truly do seek and don't just skip. I searched for a thousand locations at the start of my drive and it read them. I did the same but added an offset of half of the disk size (to search the back of the disk). I found:

  • Both methods took almost the same time.
  • Searching the start or the end of the disk did not affect time.
  • Reducing the range of the addresses did reduce time.
  • Sorting the addresses did reduce time, but not by much.

This suggests to me that this is truly seeking and not merely reading and skipping (as a stream tends to). The speed is still terrible at this stage and it makes my hard drive sound like a washing machine, but the code was designed for a quick test and has yet to be made pretty. It may still work fine.

Thanks to both EJP and Apangin for the help. Read more in their respective answers.

Edit: I have since run my code on a Windows 7 machine (I didn't have one originally), and I get a slightly different exception (see below). This was run with admin privileges, and the first piece of code still works under the same conditions.

java.nio.file.FileSystemException: \\.\PhysicalDrive0\: A device attached to the system is not functioning.

    at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
    at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:115)
    at java.nio.channels.FileChannel.open(FileChannel.java:287)
    at java.nio.channels.FileChannel.open(FileChannel.java:335)
    at testapp.TestApp.doStuff(TestApp.java:30)
    at testapp.TestApp.main(TestApp.java:24)

Edit 2: In response to EJP, I have tried:

    byte[] bytes = new byte[20];
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    bb.rewind();

    File disk = new File("\\\\.\\PhysicalDrive0\\");
    try (FileInputStream fis = new FileInputStream(disk);
            ReadableByteChannel rbc = Channels.newChannel(new FileInputStream(disk))) {

        System.out.println("Channel created");
        int read = rbc.read(bb);
        System.out.println("Read " + read + " bytes");
        System.out.println("No exceptions! Yay!");
    } catch (Exception ex) {
        System.out.println("Oh bother: " + ex);
    }

When I try this I get the following output:

Channel created
Oh bother: java.io.IOException: The parameter is incorrect

So it appears that I can create a FileChannel or ReadableByteChannel, but I can't use it; that is, the error is simply deferred.

Community
  • 1
  • 1
timbo
  • 1,533
  • 1
  • 15
  • 26
  • You didn't try my actual code. Try it with a buffer size of 4096 like I did. You may have to read in exact disk block multiples. – user207421 Nov 06 '14 at 22:22
  • I'm not near a computer at the moment, so I will try on Monday. I'm using NO so that I can get fast random access of the disk. Perhaps I should have said that in the question. So I need to be able to seek, grab parts (a block should work) and preferably use transfer from and transfer to. – timbo Nov 06 '14 at 22:23
  • I'm prepared to bet that the raw disk driver just won't support partial block reads. If that's your requirement you'd be better off using `FileInput/OutputStreams` with `BufferedInput/OutputStreams` around them. – user207421 Nov 06 '14 at 22:29
  • FileInput streams don't do the job unfortunately. It's ok for a read from start to end, but there are issues with random access. It either doesn't allow it, or it takes forever. If entertain any other cross platform Java solution. I'm not tied to NIO. – timbo Nov 06 '14 at 23:29
  • That's true, but (though I accept I may be mistaken) I thought all FileChannel objects support mapping and transferTo and transferFrom. The libraries I use use NIO for the efficiency of working with large files via mapping. I have little experience with NIO because until I had a large file (a whole disk or disk image) FileInputStream was about as efficient (it got a lot more efficient about java 6 or 7; just recompiling sped my code up). However, seeking is a lot faster than skipping. – timbo Nov 07 '14 at 07:48
  • I did things like this using JNI and later JNative and yet later in JNA. It's probably unsafer that way since you're using MSDN functions, and not platform independent (though possible in JNA with checks, because it supports multiple 'platform' libraries) and needs the JNA library, but at least it gives you full freedom. Ping me if you want to know more. JNI doesn't use external libraries but was a serious PITA. (I have backups of those if you really want them) – Mark Jeronimus Nov 08 '14 at 20:32
  • Recompiling won't have sped your code up, but using a newer JDK may have. – user207421 Nov 08 '14 at 21:36
  • Yes, sorry, that's what I meant. Recompiling with the newer JDK. I said recompiling because (though I could be wrong) I think simply changing the JRE was not enough. It actually had to be recompiled with the newer JDK to make a difference. – timbo Nov 08 '14 at 21:52
  • I just stumbled upon this. I've done things similar to this with java using NIO. The code in **Edit 2** looks reasonable if run under Administrator **except** I am pretty sure on Windows the buffer you use has to be a multiple of the sector size which is generally 512. What happens if you modify `byte[] bytes = new byte[20];` to be `byte[] bytes = new byte[512];` ? – Michael Petch Nov 12 '14 at 23:02

3 Answers3

4

When accessing physical drive without buffering, you can read only complete sectors. This means, if a sector size is 512 bytes, you can read only multiple of 512 bytes. Change your buffer length to 512 or 4096 (whatever your sector size is) and FileChannel will work fine:

ByteBuffer buf = ByteBuffer.allocate(512);

try (RandomAccessFile raf = new RandomAccessFile("\\\\.\\PhysicalDrive0", "r");
     FileChannel fc = raf.getChannel()) {
    fc.read(buf);
    System.out.println("It worked! Read bytes: " + buf.position());
} catch (Exception e) {
    e.printStackTrace();
}

See Alignment and File Access Requirements.

Your original FileInputStream code works obviously because of BufferedInputStream which has the default buffer size of 8192. Take it away - and the code will fail with the same exception.

apangin
  • 92,924
  • 10
  • 193
  • 247
  • Is there a straightforward way to access the disk sector size? Or native is necessary? – LppEdd Dec 09 '20 at 14:50
  • 1
    @LppEdd See [this question](https://stackoverflow.com/questions/9465451/how-can-i-determine-the-sector-size-in-windows). Or just probe common sizes by trying to read and catching exception. – apangin Dec 09 '20 at 15:25
1

Run this as administrator. It really does work, as it's only a thin wrapper over java.io:

    try (FileInputStream fis = new FileInputStream(disk);
        ReadableByteChannel fc = Channels.newChannel(fis))
    {
        System.out.println("No exceptions! Yay!");
        ByteBuffer  bb = ByteBuffer.allocate(4096);
        int count = fc.read(bb);
        System.out.println("read count="+count);
    }
    catch (Exception ex)
    {
        System.out.println("Oh bother: "+ex);
        ex.printStackTrace();
    }

EDIT If you need random access, you're stuck with RandomAccessFile. There's no mapping from that via Channels. But the solution above isn't NIO anyway, just a Java NIO layer over FileInput/OutputStream.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thanks EJP, already done that. When I first asked my question I was using XP (didn't have Windows 7). When I ran it in Windows 7, I got exactly the error you mentioned. I then ran as admin, and got a new error: "A device attached to the system is not functioning." (see the bottom of my post). The device obviously is functioning, as the FileStream works, and it is also my OS disk. By the way, same thing happens on disks 1, 2 and 3. – timbo Nov 06 '14 at 04:08
  • OK, will try a cut and paste. – timbo Nov 06 '14 at 04:10
  • Ah. It is true, you can get a FileChannel that way, but it can't do anything. It just defers the exception until you try and read from it. I added a ByteBuffer and tried to read to it and got: java.io.IOException: The parameter is incorrect Interestingly, this is different to "A device attached . . ." – timbo Nov 06 '14 at 04:15
  • @EJP *"There's no mapping from that via Channels"* - what about `RandomAccessFile.getChannel()`? – apangin Nov 08 '14 at 17:09
  • @apangin That's not a mapping *via `Channels`.* Please read what I actually wrote. `getChannel()` will have exactly the same problem that the OP is already complaining about. – user207421 Nov 08 '14 at 21:14
  • @EJP Not sure what you mean. What is the problem with `RandomAccessFile.getChannel()`? – apangin Nov 08 '14 at 21:37
  • The problem is the one described in the OP's question. This is what the question is about. You don't seem to have read that either. – user207421 Nov 09 '14 at 00:06
  • @EJP You haven't answered my question. Neither you've answered OP's question. – apangin Nov 09 '14 at 02:32
  • If you can't read the original question or my comments accurately I don't know what good reiterating it all could possibly do. However, fr your further assistance, the OP is getting `'a device attached to the system is not functioning'` whenever he creates a `FileChannel` for the raw disk by any means, and he will get it via your answer too, once he gets over the trivial access problem you mention. I agree that my answer doesn't answer the question either as it doesn't let him perform random access. There evidently is no answer to that. – user207421 Nov 09 '14 at 23:32
  • @EJP Thanks for explaining, I've got your point now. Though it is wrong, sorry. `FileChannel.open` indeed results in exception, while `RandomAccessFile.getChannel` works quite well. I'd suggest next time you verify your allegations before writing uncivil comments. – apangin Nov 10 '14 at 16:17
1

Using NIO your original code only needs to change very slightly.

Path disk = Paths.get("d:\\.");
try (ByteChannel bc = Files.newByteChannel(disk, StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(10);
    bc.read(buffer);
} catch (Exception e){
    e.printStackTrace();
}

Is fine, workable code, but I get an access denied error in both your version and mine.

Steve K
  • 4,863
  • 2
  • 32
  • 41
  • Thanks for your answer Steve. I don't think the issue is with the reference (\\.\PhysicalDrive0\), though I will try anything. I got the same error as you with your code. The first piece of code using the FileInputStream does work with that reference, but unfortunately, FileInputStream is not up to my needs. If it was the reference, I would expect that to not work. Also, I get a different error when I use an invalid reference. – timbo Nov 06 '14 at 04:52