/*-
* Copyright (C) 2006 Erik Larsson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catacombae.storage.io.win32;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.catacombae.util.Util;
import org.catacombae.io.ReadableRandomAccessStream;
public class ReadableWin32FileStream implements ReadableRandomAccessStream {
protected byte[] fileHandle;
protected final int sectorSize; //Detect this later..
protected long filePointer = 0;
private static final Object loadLibSync = new Object();
private static boolean libraryLoaded = false;
/**
* Set this variable to true if you want some messages printed to stderr when the library is
* loaded.
*/
public static boolean verboseLoadLibrary = false;
private enum ArchitectureIdentifier {
I386("i386"), AMD64("amd64"), IA64("ia64"),
POWERPC("ppc32"), POWERPC64("ppc64"),
SPARC("sparc32"), SPARC64("sparc64"),
MIPS("mips32"), MIPS64("mips64"), ALPHA("alpha"),
UNKNOWN;
private final String idString;
private ArchitectureIdentifier() {
this.idString = null;
}
private ArchitectureIdentifier(String idString) {
this.idString = idString;
}
public String getArchitectureString() {
return idString;
}
};
private static ArchitectureIdentifier getJVMArchitecture() {
// Trying to cover all thinkable cases here...
// Got some hints from http://lopica.sourceforge.net/os.html
final String osArch = System.getProperty("os.arch");
if(osArch.equalsIgnoreCase("x86") ||
osArch.equalsIgnoreCase("i386") ||
osArch.equalsIgnoreCase("i486") ||
osArch.equalsIgnoreCase("i586") ||
osArch.equalsIgnoreCase("i686"))
return ArchitectureIdentifier.I386;
else if(osArch.equalsIgnoreCase("amd64") ||
osArch.equalsIgnoreCase("x86_64") ||
osArch.equalsIgnoreCase("x64"))
return ArchitectureIdentifier.AMD64;
else if(osArch.equalsIgnoreCase("ia64") ||
osArch.equalsIgnoreCase("ia64n"))
return ArchitectureIdentifier.IA64;
else
return ArchitectureIdentifier.UNKNOWN;
}
public static boolean isSystemSupported() {
ArchitectureIdentifier archId = getJVMArchitecture();
return System.getProperty("os.name").toLowerCase().startsWith("windows") &&
(archId == ArchitectureIdentifier.I386 ||
archId == ArchitectureIdentifier.AMD64 ||
archId == ArchitectureIdentifier.IA64);
}
/**
* Does not check if the system is supported, and just tries to load the approprate library
* from the architecture string specified in this system's ArchitectureIdentifier.
*/
private static void loadLibrary() {
final ArchitectureIdentifier archId = getJVMArchitecture();
if(archId == ArchitectureIdentifier.UNKNOWN) {
System.err.println(System.getProperty("os.arch") + ": architecture unknown! Cannot locate appropriate native I/O library.");
throw new RuntimeException("loadLibrary(): CPU architecture unknown!");
}
else {
final String libName = "llio_" + archId.getArchitectureString();
try {
if(verboseLoadLibrary) System.err.println("Trying to load native library \"" + libName + "\"...");
System.loadLibrary(libName);
if(verboseLoadLibrary) System.err.println("Native library \"" + libName + "\" successfully loaded.");
libraryLoaded = true;
} catch(UnsatisfiedLinkError e) {
System.err.println("ERROR: Native library \"" + libName + "\" failed to load!");
System.err.println("java.library.path=\"" + System.getProperty("java.library.path") + "\"");
throw e;
}
}
}
public ReadableWin32FileStream(String filename) {
synchronized(loadLibSync) {
if(!libraryLoaded) {
loadLibrary();
}
}
boolean verbose = false;
fileHandle = open(filename);
//System.out.println("fileHandle: 0x" + Util.byteArrayToHexString(fileHandle));
int tmpSectorSize = getSectorSize(fileHandle);
if(tmpSectorSize > 0) {
if(verbose)
System.out.println("Sector size determined: " + tmpSectorSize);
sectorSize = tmpSectorSize;
}
else {
if(verbose)
System.out.println("Could not determine sector size.");
sectorSize = 512; // The only reasonable standard value
}
}
/* @Override */
public void seek(long pos) {
//System.err.println("WindowsLowLevelIO.seek(" + pos + ");");
if(fileHandle != null) {
// We seek to the beginning of the sector containing pos
seek((pos / sectorSize) * sectorSize, fileHandle);
filePointer = pos;
}
else
throw new RuntimeException("File closed!");
}
/* @Override */
public int read() {
//System.err.println("WindowsLowLevelIO.read();");
byte[] oneByte = new byte[1];
if(read(oneByte) == 1)
return oneByte[0] & 0xFF;
else
return -1;
}
/* @Override */
public int read(byte[] data) {
//System.err.println("WindowsLowLevelIO.read(byte[" + data.length + "]);");
return read(data, 0, data.length);
}
/* @Override */
public int read(byte[] data, int pos, int len) {
//System.err.println("WindowsLowLevelIO.read(byte[" + data.length + "], " + pos + ", " + len + ");");
if(fileHandle != null) {
/*
* First make sure that we are at the beginning of the sector containing
* filePointer.
*/
seek((filePointer / sectorSize) * sectorSize, fileHandle);
/*
* Calculate how many bytes we have to skip in order to get to the data that
* filePointer references. (fpDiff)
*/
long trueFp = getFilePointer(fileHandle);
long fpDiff = filePointer - trueFp;
if(fpDiff < 0)
throw new RuntimeException("Program error: fpDiff < 0 (" + fpDiff + " < 0)");
else if(fpDiff > sectorSize)
throw new RuntimeException("Program error: fpDiff > sectorSize (" + fpDiff + " > " + sectorSize + ")");
/* Add the bytes that we will have to skip to the total read length. */
int alignedLen = (int) fpDiff + len;
/*
* System.err.println("Before crash:");
* System.err.println(" trueFp=" + trueFp);
* System.err.println(" fpDiff=" + fpDiff);
* System.err.println(" alignedLen=" + alignedLen);
* System.err.println(" sectorSize=" + sectorSize);
* System.err.println(" alignedLen/sectorSize=" + (alignedLen/sectorSize));
* System.err.println(" alignedLen%sectorSize=" + (alignedLen%sectorSize));
* System.err.println(" (alignedLen%sectorSize!=0?1:0))*sectorSize=" + ((alignedLen%sectorSize!=0?1:0))*sectorSize);
*/
/* Allocate a sufficiently large temp buffer aligned to the sector size. */
byte[] tmp = new byte[(alignedLen / sectorSize + (alignedLen % sectorSize != 0 ? 1 : 0)) * sectorSize];
/*
* Read into the array tmp, which now should be aligned to sector size. Our
* position in the file should also be aligned to sector size through the
* initial seek. No problem should occur. I hope.
*/
int bytesRead = read(tmp, 0, tmp.length, fileHandle);
/* Trim away the unnecessary leading and trailing data length. */
bytesRead = (bytesRead >= alignedLen) ? len : bytesRead - (int) fpDiff; // trim bytesRead to len if >= len
filePointer += bytesRead; // update the (virtual) file pointer
System.arraycopy(tmp, (int) fpDiff, data, pos, bytesRead);
return bytesRead;
}
else
throw new RuntimeException("File closed!");
}
/* @Override */
public byte readFully() {
byte[] data = new byte[1];
readFully(data);
return data[0];
}
/* @Override */
public void readFully(byte[] data) {
readFully(data, 0, data.length);
}
/* @Override */
public void readFully(byte[] data, int offset, int length) {
if(fileHandle != null) {
int bytesRead = 0;
while(bytesRead < length) {
int curBytesRead = read(data, offset + bytesRead, length - bytesRead);
if(curBytesRead > 0)
bytesRead += curBytesRead;
else
throw new RuntimeException("Couldn't read the entire length.");
}
}
else
throw new RuntimeException("File closed!");
}
/* @Override */
public long length() {
//System.err.println("WindowsLowLevelIO.length();");
if(fileHandle != null) {
//throw new RuntimeException("Could not get file size.");
//System.err.println(" returning " + length(fileHandle));
return length(fileHandle);
}
else
throw new RuntimeException("File closed!");
}
/* @Override */
public long getFilePointer() {
//System.err.println("WindowsLowLevelIO.getFilePointer();");
if(fileHandle != null) {
//System.err.println(" returning " + filePointer);
return filePointer;//getFilePointer(fileHandle);
}
else
throw new RuntimeException("File closed!");
}
/* @Override */
public void close() {
if(fileHandle != null) {
close(fileHandle);
fileHandle = null;
}
else
throw new RuntimeException("File closed!");
}
public void ejectMedia() {
if(fileHandle != null)
ejectMedia(fileHandle);
else
throw new RuntimeException("File closed!");
}
public void loadMedia() {
if(fileHandle != null)
loadMedia(fileHandle);
else
throw new RuntimeException("File closed!");
}
protected byte[] open(String filename) {
//System.out.println("Java: WindowsLowLevelIO.open(" + filename + ");");
return openNative(filename);
}
protected static native byte[] openNative(String filename);
protected static native void seek(long pos, byte[] handle);
protected static native int read(byte[] data, int pos, int len, byte[] handle);
protected static native void close(byte[] handle);
protected static native void ejectMedia(byte[] handle);
protected static native void loadMedia(byte[] handle);
protected static native long length(byte[] handle);
protected static native long getFilePointer(byte[] handle);
protected static native int getSectorSize(byte[] handle);
// protected static native void getHandleType(byte[] handle);
// protected static native void getDeviceLength(byte[] handle);
// protected static native void getFileLength(byte[] handle);
public static void main(String[] args) {
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
ReadableWin32FileStream wllio1 = new ReadableWin32FileStream(args[0]);
try {
if(args[1].equals("testread")) {
// When reading directly from block devices, the buffer must be a multiple of the sector size of the device. Also, reading must start at a value dividable by the sector size. Calling DeviceIoControl with IOCTL_DISK_GET_DRIVE_GEOMETRY_EX will get the drive geometry for the device.
System.out.println("Seeking to 1024...");
wllio1.seek(1024);
byte[] buf = new byte[4096];
System.out.println("Reading " + buf.length + " bytes from file: ");
int bytesRead = wllio1.read(buf);
System.out.println(" Bytes read: " + bytesRead);
System.out.println(" As hex: 0x" + Util.byteArrayToHexString(buf));
System.out.println(" As string: \"" + new String(buf, "US-ASCII") + "\"");
}
else if(args[1].equals("eject")) {
System.out.print("Press any key to eject media...");
stdin.readLine();
wllio1.ejectMedia();
System.out.print("Press any key to load media...");
stdin.readLine();
wllio1.loadMedia();
}
else
System.out.println("Nothing to do.");
} catch(Exception e) {
e.printStackTrace();
}
wllio1.close();
}
}