/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.fs.ext2; import java.io.IOException; import java.nio.ByteBuffer; import org.apache.log4j.Logger; import org.jnode.fs.FSFileSlackSpace; import org.jnode.fs.FileSystemException; import org.jnode.fs.ReadOnlyFileSystemException; import org.jnode.fs.spi.AbstractFSFile; import org.jnode.util.ByteBufferUtils; /** * @author Andras Nagy */ public class Ext2File extends AbstractFSFile implements FSFileSlackSpace { String name; INode iNode; private final Logger log = Logger.getLogger(getClass()); public Ext2File(Ext2Entry entry) { this((Ext2FileSystem) entry.getFileSystem(), entry.getINode(), entry.getName()); } public Ext2File(Ext2FileSystem fs, INode iNode, String name) { super(fs); this.iNode = iNode; this.name = name; } @Override public long getLength() { // log.debug("getLength(): "+iNode.getSize()); return iNode.getSize(); } @Override public void setLength(long length) throws IOException { if (!canWrite()) throw new ReadOnlyFileSystemException("FileSystem or File is readonly"); long blockSize = iNode.getExt2FileSystem().getBlockSize(); // synchronize to the inode cache to make sure that the inode does not // get // flushed between reading it and locking it synchronized (((Ext2FileSystem) getFileSystem()).getInodeCache()) { // reread the inode before synchronizing to it to make sure // all threads use the same instance rereadInode(); // lock the inode into the cache so it is not flushed before // synchronizing to it // (otherwise a new instance of INode referring to the same inode // could be put // in the cache resulting in the possibility of two threads // manipulating the same // inode at the same time because they would synchronize to // different INode instances) iNode.incLocked(); } // a single inode may be represented by more than one Ext2Directory // instances, // but each will use the same instance of the underlying inode (see // Ext2FileSystem.getINode()), // so synchronize to the inode synchronized (iNode) { try { // if length<getLength(), then the file is truncated if (length < getLength()) { long blockNr = length / blockSize; long blockOffset = length % blockSize; long nextBlock; if (blockOffset == 0) nextBlock = blockNr; else nextBlock = blockNr + 1; for (long i = iNode.getAllocatedBlockCount() - 1; i >= nextBlock; i--) { log.debug("setLength(): freeing up block " + i + " of inode"); iNode.freeDataBlock(i); } iNode.setSize(length); iNode.setMtime(System.currentTimeMillis() / 1000); return; } // if length>getLength(), then new blocks are allocated for the // file // The content of the new blocks is undefined (see the // setLength(long i) // method of java.io.RandomAccessFile if (length > getLength()) { long len = length - getLength(); long blocksAllocated = getLengthInBlocks(); long bytesAllocated = getLength(); long bytesCovered = 0; while (bytesCovered < len) { long blockIndex = (bytesAllocated + bytesCovered) / blockSize; long blockOffset = (bytesAllocated + bytesCovered) % blockSize; long newSection = Math.min(len - bytesCovered, blockSize - blockOffset); // allocate a new block if needed if (blockIndex >= blocksAllocated) { iNode.allocateDataBlock(blockIndex); blocksAllocated++; } bytesCovered += newSection; } iNode.setSize(length); iNode.setMtime(System.currentTimeMillis() / 1000); return; } } catch (Throwable ex) { final IOException ioe = new IOException(); ioe.initCause(ex); throw ioe; } finally { // setLength done, unlock the inode from the cache iNode.decLocked(); } } // synchronized(inode) } @Override public void read(long fileOffset, ByteBuffer destBuf) throws IOException { if (fileOffset + destBuf.remaining() > getLength()) throw new IOException("Can't read past the file!"); readImpl(fileOffset, destBuf); } /** * A read implementation that doesn't check the file length. * * @param fileOffset the offset to read from. * @param destBuf the destination buffer. * @throws IOException if an error occurs reading. */ public void readImpl(long fileOffset, ByteBuffer destBuf) throws IOException { final int len = destBuf.remaining(); final int off = 0; // TODO optimize it also to use ByteBuffer at lower level final ByteBufferUtils.ByteArray destBA = ByteBufferUtils.toByteArray(destBuf); final byte[] dest = destBA.toArray(); // synchronize to the inode cache to make sure that the inode does not // get flushed between reading it and locking it synchronized (((Ext2FileSystem) getFileSystem()).getInodeCache()) { // reread the inode before synchronizing to it to make sure // all threads use the same instance rereadInode(); // lock the inode into the cache so it is not flushed before // synchronizing to it // (otherwise a new instance of INode referring to the same inode // could be put // in the cache resulting in the possibility of two threads // manipulating the same // inode at the same time because they would synchronize to // different INode instances) iNode.incLocked(); } if (log.isDebugEnabled()) { log.debug("File:" + name + " size:" + getLength() + " read offset: " + fileOffset + " len: " + dest.length); } // a single inode may be represented by more than one Ext2Directory // instances, // but each will use the same instance of the underlying inode (see // Ext2FileSystem.getINode()), // so synchronize to the inode synchronized (iNode) { try { if ((iNode.getMode() & Ext2Constants.EXT2_S_IFLNK) == Ext2Constants.EXT2_S_IFLNK) { // Sym-links are a special case: the data seems to be stored inline in the iNode System.arraycopy(iNode.getINodeBlockData(), 0, dest, 0, Math.min(64, dest.length)); } else { long blockSize = iNode.getExt2FileSystem().getBlockSize(); long bytesRead = 0; while (bytesRead < len) { long blockNr = (fileOffset + bytesRead) / blockSize; long blockOffset = (fileOffset + bytesRead) % blockSize; long copyLength = Math.min(len - bytesRead, blockSize - blockOffset); log.debug("blockNr: " + blockNr + ", blockOffset: " + blockOffset + ", copyLength: " + copyLength + ", bytesRead: " + bytesRead); System.arraycopy(iNode.getDataBlock(blockNr), (int) blockOffset, dest, off + (int) bytesRead, (int) copyLength); bytesRead += copyLength; } } } catch (Throwable ex) { final IOException ioe = new IOException(); ioe.initCause(ex); throw ioe; } finally { // read done, unlock the inode from the cache iNode.decLocked(); } } destBA.refreshByteBuffer(); } @Override public void write(long fileOffset, ByteBuffer srcBuf) throws IOException { final int len = srcBuf.remaining(); final int off = 0; // TODO optimize it also to use ByteBuffer at lower level final byte[] src = ByteBufferUtils.toArray(srcBuf); if (getFileSystem().isReadOnly()) { throw new ReadOnlyFileSystemException("write in readonly filesystem"); } // synchronize to the inode cache to make sure that the inode does not // get // flushed between reading it and locking it synchronized (((Ext2FileSystem) getFileSystem()).getInodeCache()) { // reread the inode before synchronizing to it to make sure // all threads use the same instance rereadInode(); // lock the inode into the cache so it is not flushed before // synchronizing to it // (otherwise a new instance of INode referring to the same inode // could be put // in the cache resulting in the possibility of two threads // manipulating the same // inode at the same time because they would synchronize to // different INode instances) iNode.incLocked(); } try { // a single inode may be represented by more than one Ext2File // instances, // but each will use the same instance of the underlying inode (see // Ext2FileSystem.getINode()), // so synchronize to the inode synchronized (iNode) { if (fileOffset > getLength()) throw new IOException( "Can't write beyond the end of the file! (fileOffset: " + fileOffset + ", getLength()" + getLength()); if (off + len > src.length) throw new IOException("src is shorter than what you want to write"); log.debug("write(fileOffset=" + fileOffset + ", src, off, len=" + len + ")"); final long blockSize = iNode.getExt2FileSystem().getBlockSize(); long blocksAllocated = iNode.getAllocatedBlockCount(); long bytesWritten = 0; while (bytesWritten < len) { long blockIndex = (fileOffset + bytesWritten) / blockSize; long blockOffset = (fileOffset + bytesWritten) % blockSize; long copyLength = Math.min(len - bytesWritten, blockSize - blockOffset); // If only a part of the block is written, then read the // block and update its contents with the data in src. If the // whole block is overwritten, then skip reading it. byte[] dest; if (!((blockOffset == 0) && (copyLength == blockSize)) && (blockIndex < blocksAllocated)) dest = iNode.getDataBlock(blockIndex); else dest = new byte[(int) blockSize]; System.arraycopy(src, (int) (off + bytesWritten), dest, (int) blockOffset, (int) copyLength); // allocate a new block if needed if (blockIndex >= blocksAllocated) { try { iNode.allocateDataBlock(blockIndex); } catch (FileSystemException ex) { final IOException ioe = new IOException("Internal filesystem exception"); ioe.initCause(ex); throw ioe; } blocksAllocated++; } // write the block iNode.writeDataBlock(blockIndex, dest); bytesWritten += copyLength; } iNode.setSize(fileOffset + len); iNode.setMtime(System.currentTimeMillis() / 1000); } } catch (IOException ex) { // ... this avoids wrapping an IOException inside another one. throw ex; } catch (Throwable ex) { final IOException ioe = new IOException(); ioe.initCause(ex); throw ioe; } finally { // write done, unlock the inode from the cache iNode.decLocked(); } } @Override public void flush() throws IOException { log.debug("Ext2File.flush()"); iNode.update(); // update the group descriptors and superblock: needed if blocks have // been allocated or deallocated iNode.getExt2FileSystem().updateFS(); } private long getLengthInBlocks() { return iNode.getSizeInBlocks(); } private void rereadInode() throws IOException { long iNodeNr = iNode.getINodeNr(); try { iNode = ((Ext2FileSystem) getFileSystem()).getINode(iNodeNr); } catch (FileSystemException ex) { final IOException ioe = new IOException(); ioe.initCause(ex); throw ioe; } } @Override public byte[] getSlackSpace() throws IOException { int blockSize = ((Ext2FileSystem) getFileSystem()).getBlockSize(); int slackSpaceSize = blockSize - (int) (getLength() % blockSize); if (slackSpaceSize == blockSize) { slackSpaceSize = 0; } byte[] slackSpace = new byte[slackSpaceSize]; readImpl(getLength(), ByteBuffer.wrap(slackSpace)); return slackSpace; } }