/*- * Copyright (C) 2007 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 org.catacombae.io.RandomAccessStream; import org.catacombae.util.Util; /** BUG: Writing at the end of a file will always expand it to fit the sector size! Not very good. Fixit. Note: Is this fixed? TODO ?! */ public class Win32FileStream extends ReadableWin32FileStream implements RandomAccessStream { private byte[] sectorBuffer; public Win32FileStream(String filename) { super(filename); sectorBuffer = new byte[sectorSize]; } public void write(byte[] b) { write(b, 0, b.length); } public void write(byte[] b, int pos, int len) { /* NOTE: The reason for all this code is that we have to align write operations to sectors on the disk. */ if(fileHandle != null) { final long fileLength = length(fileHandle); // We only record the length of the file at the beginning // Will we allow writing beyond this file (extending it)? We currently do (but maybe windows prohibits it) System.out.println("write(b, " + pos + ", " + len + ");"); long startFP = filePointer; long endFP = filePointer+len; long startSectorFP = (startFP/sectorSize)*sectorSize; int startSectorFPOffset = (int)(startFP-startSectorFP); long endSectorFP = (endFP/sectorSize)*sectorSize; int endSectorFPLength = (int)(endFP-endSectorFP); int inputPos = pos; /* First make sure that we are at the beginning of the sector containing filePointer. */ seek(startSectorFP, fileHandle); /* Read the contents of the start sector. */ int bytesToRead = (int)((sectorBuffer.length > fileLength) ? fileLength : sectorBuffer.length); if(read(sectorBuffer, 0, bytesToRead, fileHandle) != bytesToRead) throw new RuntimeException("Could not read contents of starting sector!"); System.out.println("Writing first bytes."); /* Replace the contents starting at filePointer, seek back to start pos and write to disk. */ seek(startSectorFP, fileHandle); int remainingSpaceInBuffer = sectorBuffer.length-startSectorFPOffset; int bytesToReplace = (len < remainingSpaceInBuffer) ? len : remainingSpaceInBuffer; System.arraycopy(b, inputPos, sectorBuffer, startSectorFPOffset, bytesToReplace); inputPos += bytesToReplace; write(sectorBuffer, 0, startSectorFPOffset+bytesToReplace, fileHandle); /* Burst all non-complicated sectors... */ long currentSectorFP = startSectorFP+sectorSize; while(currentSectorFP < endSectorFP) { System.out.println("Writing non-complicated sector " + ((currentSectorFP-startSectorFP)/sectorSize) + "..."); System.arraycopy(b, inputPos, sectorBuffer, 0, sectorBuffer.length); inputPos += sectorBuffer.length; write(sectorBuffer, 0, sectorBuffer.length, fileHandle); currentSectorFP += sectorSize; } /* If start and end sector are the same, the work has already been done (before the loop). */ if(startSectorFP != endSectorFP && endSectorFPLength > 0) { int finalWriteLength = sectorSize; if(fileLength < endSectorFP+sectorSize) finalWriteLength = endSectorFPLength; // The file isn't sector aligned, so we'll just write the final bytes. /* Read the contents of the end sector. */ if(read(sectorBuffer, 0, finalWriteLength, fileHandle) != finalWriteLength) throw new RuntimeException("Could not read contents of end sector!"); System.arraycopy(b, inputPos, sectorBuffer, 0, endSectorFPLength); seek(endSectorFP, fileHandle); write(sectorBuffer, 0, finalWriteLength, fileHandle); } /* That should be all. Restore state and update variables. */ seek(endFP); // Since we have gone beyond it when writing the last sector } else throw new RuntimeException("File closed!"); } public void write(int b) { write(new byte[] { (byte)(b & 0xFF) }); } /** We override the open method in order to get the file opened in write mode */ protected byte[] open(String filename) { //System.out.println("Java: WritableWin32File.open(" + filename + ");"); return openNative(filename); } protected static native byte[] openNative(String filename); /** Will throw exception whenever all bytes can't be written. */ protected static native void write(byte[] data, int off, int len, byte[] handle); /*--------------------- TEST CODE FOLLOWS ---------------------*/ private static java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); private static Win32FileStream wwf; private static java.io.RandomAccessFile ref; private static long currentSeekPos; private static int currentBufferSize; public static void main(String[] args) { if(args.length != 2) { System.out.println("The program takes two different files with identical contents as arguments."); return; } try { /* Bashing test. Run this on a test file for some time. If the test file doesn't get corrupted, we probably have a winner. */ wwf = new Win32FileStream(args[0]); ref = new java.io.RandomAccessFile(args[1], "rw"); java.util.Random rnd = new java.util.Random(); if(wwf.length() != ref.length()) { System.out.println("The two files must be equal in length! (" + wwf.length() + " != " + ref.length() + ")"); return; } java.io.BufferedReader stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); int maxBufferSize = 100000; int constantBufferSize = 3912; byte[] constantBufferLeft = new byte[constantBufferSize]; byte[] constantBufferRight = new byte[constantBufferSize]; while(true) { wwf.seek(0); /* For each iteration: randomize the position and read length. Read and write back. */ long currentSeekPos = (long)((wwf.length()-1)*rnd.nextDouble()); int currentBufferSize = rnd.nextInt(maxBufferSize); if(currentSeekPos+currentBufferSize > wwf.length()) currentBufferSize = (int)(wwf.length()-currentSeekPos); /* - Seek to random position * - Read random amount of data, check if both equal * - Read constant amount of data, check if both equal * - Seek back to 0 * - Seek to same position * - Write data back * - Read data again, check if equal */ byte[] currentBufferLeft = new byte[currentBufferSize]; byte[] currentBufferRight = new byte[currentBufferSize]; wwf.seek(currentSeekPos); ref.seek(currentSeekPos); wwf.readFully(currentBufferLeft); ref.readFully(currentBufferRight); testEquality(currentBufferLeft, currentBufferRight, "(1) Data not equal after reads!"); if(currentSeekPos+currentBufferSize+constantBufferSize <= wwf.length()) { wwf.readFully(constantBufferLeft); ref.readFully(constantBufferRight); testEquality(currentBufferLeft, currentBufferRight, "(2) Data following reads not equal!"); } wwf.seek(0); ref.seek(0); wwf.seek(currentSeekPos); ref.seek(currentSeekPos); wwf.write(currentBufferLeft); ref.write(currentBufferRight); wwf.seek(currentSeekPos); ref.seek(currentSeekPos); wwf.readFully(currentBufferLeft); ref.readFully(currentBufferRight); testEquality(currentBufferLeft, currentBufferRight, "(3) Data not equal after writes!"); } } catch(Exception e) { e.printStackTrace(); } } private static void testEquality(byte[] a, byte[] b, String idMessage) throws Exception { if(!Util.arraysEqual(a, b)) { System.out.println(idMessage); System.out.println(" currentSeekPos=" + currentSeekPos); System.out.println(" currentBufferSize=" + currentBufferSize); System.out.println(" wwf.length()=" + wwf.length()); System.out.println(" ref.length()=" + ref.length()); System.out.println(" wwf.getFilePointer()=" + wwf.getFilePointer()); System.out.println(" ref.getFilePointer()=" + ref.getFilePointer()); // System.out.println(" // System.out.println(" // System.out.println(" System.out.print("Press enter to continue: "); stdin.readLine(); } } }