/* * $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.fat; import java.io.IOException; import java.nio.ByteBuffer; import org.jnode.driver.block.BlockDeviceAPI; import org.jnode.fs.FSFile; import org.jnode.fs.ReadOnlyFileSystemException; /** * A File instance is the in-memory representation of a single file (chain of * clusters). * * @author epr */ public class FatFile extends FatObject implements FSFile { private long startCluster; private long length; private FatDirectory dir; private int clusterSize; private boolean isDir; private final FatDirEntry myEntry; public FatFile(FatFileSystem fs, FatDirEntry myEntry, long startCluster, long length, boolean isDir) { super(fs); this.myEntry = myEntry; this.startCluster = startCluster; this.length = length; this.clusterSize = fs.getClusterSize(); this.isDir = isDir; } public synchronized void read(long fileOffset, ByteBuffer destBuf) throws IOException { int len = destBuf.remaining(); final long max = (isDir) ? getLengthOnDisk() : getLength(); if (fileOffset + len > max) { throw new IOException("Cannot read beyond the EOF"); } final FatFileSystem fs = getFatFileSystem(); final long[] chain = fs.getFat().getChain(startCluster); final BlockDeviceAPI api = fs.getApi(); int chainIdx = (int) (fileOffset / clusterSize); if (fileOffset % clusterSize != 0) { int clusOfs = (int) (fileOffset % clusterSize); int size = Math.min(len, (int) (clusterSize - (fileOffset % clusterSize) - 1)); destBuf.limit(destBuf.position() + size); api.read(getDevOffset(chain[chainIdx], clusOfs), destBuf); fileOffset += size; len -= size; chainIdx++; } while (len > 0) { int size = Math.min(clusterSize, len); destBuf.limit(destBuf.position() + size); api.read(getDevOffset(chain[chainIdx], 0), destBuf); len -= size; chainIdx++; } } public synchronized void write(long fileOffset, ByteBuffer srcBuf) throws IOException { int len = srcBuf.remaining(); if (getFileSystem().isReadOnly()) { throw new ReadOnlyFileSystemException("write in readonly filesystem"); } final long max = (isDir) ? getLengthOnDisk() : getLength(); if (fileOffset > max) { throw new IOException("Cannot write beyond the EOF"); } if (fileOffset + len > max) { // this is too short increase the size // of the file setLength(fileOffset + len); } final FatFileSystem fs = getFatFileSystem(); final long[] chain = fs.getFat().getChain(getStartCluster()); final BlockDeviceAPI api = fs.getApi(); int chainIdx = (int) (fileOffset / clusterSize); if (fileOffset % clusterSize != 0) { int clusOfs = (int) (fileOffset % clusterSize); int size = Math.min(len, (int) (clusterSize - (fileOffset % clusterSize) - 1)); srcBuf.limit(srcBuf.position() + size); api.write(getDevOffset(chain[chainIdx], clusOfs), srcBuf); fileOffset += size; len -= size; chainIdx++; } while (len > 0) { int size = Math.min(clusterSize, len); srcBuf.limit(srcBuf.position() + size); api.write(getDevOffset(chain[chainIdx], 0), srcBuf); len -= size; chainIdx++; } } /** * Sets the length. * * @param length The length to set */ public synchronized void setLength(long length) throws IOException { if (getFileSystem().isReadOnly()) { throw new ReadOnlyFileSystemException("setLength in readonly filesystem"); } if (this.length == length) { // Do nothing return; } final FatFileSystem fs = getFatFileSystem(); final Fat fat = fs.getFat(); final int nrClusters = (int) ((length + clusterSize - 1) / clusterSize); if (this.length == 0) { final long[] chain = fat.allocNew(nrClusters); this.startCluster = chain[0]; this.myEntry.setStartCluster((int) startCluster); } else { final long[] chain = fs.getFat().getChain(startCluster); if (nrClusters != chain.length) { if (nrClusters > chain.length) { // Grow int count = nrClusters - chain.length; while (count > 0) { fat.allocAppend(getStartCluster()); count--; } } else { // Shrink fat.setEof(chain[nrClusters - 1]); for (int i = nrClusters; i < chain.length; i++) { fat.setFree(chain[i]); } } } } this.length = length; this.myEntry.updateLength(length); } /** * Returns the length. * * @return long */ public long getLength() { return length; } /** * Gets the size this file occupies on disk * * @return long */ public long getLengthOnDisk() { if (this.length == 0) { return 0; } else { final FatFileSystem fs = getFatFileSystem(); final long[] chain = fs.getFat().getChain(getStartCluster()); return ((long) chain.length) * fs.getClusterSize(); } } /** * Returns the startCluster. * * @return long */ public long getStartCluster() { return startCluster; } /** * Gets the directory contained in this file. * * @return Directory */ public synchronized FatDirectory getDirectory() throws IOException { if (dir == null) { final FatFileSystem fs = getFatFileSystem(); dir = new FatLfnDirectory(fs, this); } return dir; } /** * Calculates the device offset (0-based) for the given cluster and offset * within the cluster. * @param cluster * @param clusterOffset * @return long */ protected long getDevOffset(long cluster, int clusterOffset) { final FatFileSystem fs = getFatFileSystem(); final long filesOffset = FatUtils.getFilesOffset(fs.getBootSector()); return filesOffset + clusterOffset + ((cluster - FatUtils.FIRST_CLUSTER) * clusterSize); } /** * Flush any changes in this file to persistent storage * @throws IOException */ public void flush() throws IOException { if (dir != null) { dir.flush(); } } }