/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Release Version 2.4 A project from the Physics Dept, The University of Oxford Copyright (C) 2007-2010 The University of Oxford This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ Conceived and Developed by: Rhys Newman, Ian Preston, Chris Dennis End of licence header */ package org.jpc.support; import java.io.*; import java.net.*; import java.util.*; public class RemoteSeekableIODevice implements SeekableIODevice { public static final int DEFAULT_SECTOR_SIZE = 4*1024; public static final int DEFAULT_CACHE_SIZE = 32*1024*1024; public static final int NETWORK_TIMEOUT = 10000; private URI drive; private int sectorSize, cacheSectors; private long length, position; private HashMap writtenSectors; private LinkedHashMap sectorIndex; public RemoteSeekableIODevice() throws IOException { this(null); } public RemoteSeekableIODevice(URI drive) throws IOException { this(drive, DEFAULT_SECTOR_SIZE, DEFAULT_CACHE_SIZE); } public RemoteSeekableIODevice(URI drive, int sectorSize, int cacheSize) throws IOException { this.sectorSize = sectorSize; this.cacheSectors = Math.max(1, cacheSize / sectorSize); // Don't use URL caching - the plugin cache does not interpret HTTP 1.1 Ranges so you'll always just get the start of the data this.drive = drive; position = 0; if (drive != null) setImageLocation(drive); } public void configure(String spec) throws IOException { try { setImageLocation(new URI(spec)); } catch (URISyntaxException e) { throw new IOException("Invalid URI specified: '"+spec+"'"); } } public synchronized void setImageLocation(URI drive) throws IOException { position = 0; this.drive = drive; HttpURLConnection conn = (HttpURLConnection) drive.toURL().openConnection(); conn.setRequestMethod("HEAD"); conn.setUseCaches(false); conn.setConnectTimeout(NETWORK_TIMEOUT); conn.setReadTimeout(NETWORK_TIMEOUT); if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) throw new IOException("Remote HDD not found at '"+drive+"' Server returned HTTP "+conn.getResponseCode()); try { length = Long.parseLong(conn.getHeaderField("Content-Length").trim()); } catch (Exception e) { throw new IOException("Invalid content length in HTTP HEAD request to server"); } if (length <= 0) throw new IOException("Invalid length for remote HDD ("+length+")"); sectorIndex = new LinkedHashMap(cacheSectors, 1.0f, true); writtenSectors = new HashMap(); } public synchronized long length() { return length; } public boolean readOnly() { return false; } public synchronized void close() { drive = null; length = -1; } public synchronized void seek(long offset) throws IOException { if (length < 0) throw new IOException("Remote device closed"); if (offset > length) throw new IOException("Seek beyond the size of the block device "+offset+" > "+length); if (offset < 0) throw new IOException("Seek to negative offset "+offset); this.position = offset; } private synchronized byte[] getSector(int index) throws IOException { Integer key = Integer.valueOf(index); byte[] result = (byte[]) sectorIndex.get(key); if (result != null) return result; for (int tries=0; tries<10; tries++) { try { HttpURLConnection hconn = (HttpURLConnection) drive.toURL().openConnection(); hconn.setUseCaches(false); hconn.setConnectTimeout(NETWORK_TIMEOUT); hconn.setReadTimeout(NETWORK_TIMEOUT); long start = index*sectorSize; long end = Math.min(length, start+sectorSize); hconn.setRequestProperty("Range", "bytes="+start+"-"+end); InputStream input = hconn.getInputStream(); result = new byte[(int) (end-start)]; int pos = 0; while (true) { int read = input.read(result, pos, result.length - pos); if (read <= 0) throw new IOException("Failed to read remote device bytes"); pos += read; if (pos >= result.length) break; } input.close(); if (sectorIndex.size() >= cacheSectors) { Iterator itt = sectorIndex.keySet().iterator(); itt.next(); itt.remove(); } sectorIndex.put(key, result); return result; } catch (Exception e) { try { Thread.sleep(1000); } catch (Exception ee) {} } } throw new IOException("Could not contact remote disk server"); } public synchronized int read(byte[] data, int offset, int length) throws IOException { if (this.length < 0) throw new IOException("Remote device closed"); int toRead = Math.min(data.length - offset, length); if (this.length - position < toRead) toRead = (int) (this.length - position); int read = 0; int pos = offset; while (true) { if (toRead <= 0) return read; Integer index = Integer.valueOf((int) (position / sectorSize)); int off = (int) (position % sectorSize); byte[] s = (byte[]) writtenSectors.get(index); if (s == null) s = getSector(index.intValue()); int r = Math.min(s.length - off, toRead); System.arraycopy(s, off, data, pos, r); position += r; pos += r; toRead -= r; read += r; } } public synchronized int write(byte[] data, int offset, int length) throws IOException { if (this.length < 0) throw new IOException("Remote device closed"); int toWrite = Math.min(data.length - offset, length); if (this.length - position < toWrite) toWrite = (int) (this.length - position); int written = 0; int pos = offset; while (true) { if (toWrite <= 0) return written; Integer index = Integer.valueOf((int) (position / sectorSize)); int off = (int) (position % sectorSize); byte[] s = (byte[]) writtenSectors.get(index); if (s == null) { s = getSector(index.intValue()); sectorIndex.remove(index); writtenSectors.put(index, s); } int w = Math.min(s.length - off, toWrite); System.arraycopy(data, pos, s, off, w); position += w; pos += w; toWrite -= w; written += w; } } }