Package org.jnode.fs.ext2

Source Code of org.jnode.fs.ext2.Ext2FileSystem

/*
* $Id$
*
* Copyright (C) 2003-2014 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 java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jnode.driver.Device;
import org.jnode.fs.FSDirectory;
import org.jnode.fs.FSEntry;
import org.jnode.fs.FSFile;
import org.jnode.fs.FileSystemException;
import org.jnode.fs.ReadOnlyFileSystemException;
import org.jnode.fs.ext2.cache.Block;
import org.jnode.fs.ext2.cache.BlockCache;
import org.jnode.fs.ext2.cache.INodeCache;
import org.jnode.fs.spi.AbstractFileSystem;

/**
* @author Andras Nagy
*/
public class Ext2FileSystem extends AbstractFileSystem<Ext2Entry> {
    private Superblock superblock;

    private GroupDescriptor groupDescriptors[];

    private INodeTable iNodeTables[];

    private int groupCount;

    private BlockCache blockCache;

    private INodeCache inodeCache;

    private final Logger log = Logger.getLogger(getClass());

    // private Object groupDescriptorLock;
    // private Object superblockLock;

    // private final boolean DEBUG=true;

    // TODO: SYNC_WRITE should be made a parameter
    /**
     * if true, writeBlock() does not return until the block is written to disk
     */
    private boolean SYNC_WRITE = true;

    /**
     * Constructor for Ext2FileSystem in specified readOnly mode
     *
     * @throws FileSystemException
     */
    public Ext2FileSystem(Device device, boolean readOnly, Ext2FileSystemType type) throws FileSystemException {
        super(device, readOnly, type);
        log.setLevel(Level.DEBUG);

        blockCache = new BlockCache(50, (float) 0.75);
        inodeCache = new INodeCache(50, (float) 0.75);

        // groupDescriptorLock = new Object();
        // superblockLock = new Object();
    }

    public void read() throws FileSystemException {
        ByteBuffer data;

        try {
            data = ByteBuffer.allocate(Superblock.SUPERBLOCK_LENGTH);

            // skip the first 1024 bytes (bootsector) and read the superblock
            // TODO: the superblock should read itself
            getApi().read(1024, data);
            // superblock = new SuperBlock(data, this);
            superblock = new Superblock();
            superblock.read(data.array(), this);

            // read the group descriptors
            groupCount = (int) Ext2Utils.ceilDiv(superblock.getBlocksCount(), superblock.getBlocksPerGroup());
            groupDescriptors = new GroupDescriptor[groupCount];
            iNodeTables = new INodeTable[groupCount];

            for (int i = 0; i < groupCount; i++) {
                // groupDescriptors[i]=new GroupDescriptor(i, this);
                groupDescriptors[i] = new GroupDescriptor();
                groupDescriptors[i].read(i, this);

                iNodeTables[i] = new INodeTable(this, (int) groupDescriptors[i].getInodeTable());
            }

        } catch (FileSystemException e) {
            throw e;
        } catch (Exception e) {
            throw new FileSystemException(e);
        }

        // check for unsupported filesystem options
        // (an unsupported INCOMPAT feature means that the fs may not be mounted
        // at all)
        if (hasIncompatFeature(Ext2Constants.EXT2_FEATURE_INCOMPAT_COMPRESSION)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (COMPRESSION) disallows mounting");
        if (hasIncompatFeature(Ext2Constants.EXT2_FEATURE_INCOMPAT_META_BG)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (META_BG) disallows mounting");
        if (hasIncompatFeature(Ext2Constants.EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (JOURNAL_DEV) disallows mounting");
        // if (hasIncompatFeature(Ext2Constants.EXT3_FEATURE_INCOMPAT_RECOVER))
        // throw new FileSystemException(getDevice().getId() +
        // " Unsupported filesystem feature (RECOVER) disallows mounting");
        // if (hasIncompatFeature(Ext2Constants.EXT4_FEATURE_INCOMPAT_EXTENTS))
        // throw new FileSystemException(getDevice().getId() +
        // " Unsupported filesystem feature (EXTENTS) disallows mounting");
        if (hasIncompatFeature(Ext2Constants.EXT4_FEATURE_INCOMPAT_64BIT)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (64BIT) disallows mounting");
        if (hasIncompatFeature(Ext2Constants.EXT4_FEATURE_INCOMPAT_MMP)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (MMP) disallows mounting");
        if (hasIncompatFeature(Ext2Constants.EXT4_FEATURE_INCOMPAT_FLEX_BG)) throw new FileSystemException(
            getDevice().getId() + " Unsupported filesystem feature (FLEX_BG) disallows mounting");

        // an unsupported RO_COMPAT feature means that the filesystem can only
        // be mounted readonly
        if (hasROFeature(Ext2Constants.EXT2_FEATURE_RO_COMPAT_LARGE_FILE)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (LARGE_FILE) forces readonly mode");
            setReadOnly(true);
        }
        if (hasROFeature(Ext2Constants.EXT2_FEATURE_RO_COMPAT_BTREE_DIR)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (BTREE_DIR) forces readonly mode");
            setReadOnly(true);
        }
        if (hasROFeature(Ext2Constants.EXT4_FEATURE_RO_COMPAT_HUGE_FILE)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (HUGE_FILE) forces readonly mode");
            setReadOnly(true);
        }
        if (hasROFeature(Ext2Constants.EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (GDT_CSUM) forces readonly mode");
            setReadOnly(true);
        }
        if (hasROFeature(Ext2Constants.EXT4_FEATURE_RO_COMPAT_DIR_NLINK)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (DIR_NLINK) forces readonly mode");
            setReadOnly(true);
        }
        if (hasROFeature(Ext2Constants.EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE)) {
            log.info(getDevice().getId() + " Unsupported filesystem feature (EXTRA_ISIZE) forces readonly mode");
            setReadOnly(true);
        }

        // if the filesystem has not been cleanly unmounted, mount it readonly
        if (superblock.getState() == Ext2Constants.EXT2_ERROR_FS) {
            log.info(getDevice().getId() + " Filesystem has not been cleanly unmounted, mounting it readonly");
            setReadOnly(true);
        }

        // if the filesystem has been mounted R/W, set it to "unclean"
        if (!isReadOnly()) {
            log.info(getDevice().getId() + " mounting fs r/w");
            superblock.setState(Ext2Constants.EXT2_ERROR_FS);
            // Mount successfull, update some superblock informations.
            superblock.setMntCount(superblock.getMntCount() + 1);
            superblock.setMTime(Ext2Utils.encodeDate(new Date()));
            superblock.setWTime(Ext2Utils.encodeDate(new Date()));
        }
        SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy");
        log.debug(" superblock: " + "\n" + "  #Mount: " + superblock.getMntCount() + "\n" + "  #MaxMount: "
            + superblock.getMaxMntCount() + "\n" + "  Last mount time: "
            + sdf.format(Ext2Utils.decodeDate(superblock.getMTime()).getTime()) + "\n" + "  Last write time: "
            + sdf.format(Ext2Utils.decodeDate(superblock.getWTime()).getTime()) + "\n" + "  #blocks: "
            + superblock.getBlocksCount() + "\n" + "  #blocks/group: " + superblock.getBlocksPerGroup() + "\n"
            + "  #block groups: " + groupCount + "\n" + "  block size: " + superblock.getBlockSize() + "\n"
            + "  #inodes: " + superblock.getINodesCount() + "\n" + "  #inodes/group: "
            + superblock.getINodesPerGroup());
    }

    public void create(BlockSize blockSize) throws FileSystemException {
        try {
            // create the superblock
            superblock = new Superblock();
            superblock.create(blockSize, this);

            // create the group descriptors
            groupCount = (int) Ext2Utils.ceilDiv(superblock.getBlocksCount(), superblock.getBlocksPerGroup());
            groupDescriptors = new GroupDescriptor[groupCount];

            iNodeTables = new INodeTable[groupCount];

            for (int i = 0; i < groupCount; i++) {
                groupDescriptors[i] = new GroupDescriptor();
                groupDescriptors[i].create(i, this);
            }

            // create each block group:
            // create the block bitmap
            // create the inode bitmap
            // fill the inode table with zeroes
            for (int i = 0; i < groupCount; i++) {
                log.debug("creating group " + i);

                byte[] blockBitmap = new byte[blockSize.getSize()];
                byte[] inodeBitmap = new byte[blockSize.getSize()];

                // update the block bitmap: mark the metadata blocks allocated
                long iNodeTableBlock = groupDescriptors[i].getInodeTable();
                long firstNonMetadataBlock = iNodeTableBlock + INodeTable.getSizeInBlocks(this);
                int metadataLength = (int) (firstNonMetadataBlock - (superblock.getFirstDataBlock() + i
                    * superblock.getBlocksPerGroup()));
                for (int j = 0; j < metadataLength; j++)
                    BlockBitmap.setBit(blockBitmap, j);

                // set the padding at the end of the last block group
                if (i == groupCount - 1) {
                    for (long k = superblock.getBlocksCount(); k < groupCount * superblock.getBlocksPerGroup(); k++)
                        BlockBitmap.setBit(blockBitmap, (int) (k % superblock.getBlocksPerGroup()));
                }

                // update the inode bitmap: mark the special inodes allocated in
                // the first block group
                if (i == 0) {
                    for (int j = 0; j < superblock.getFirstInode() - 1; j++)
                        INodeBitmap.setBit(inodeBitmap, j);
                }

                // create an empty inode table
                byte[] emptyBlock = new byte[blockSize.getSize()];
                for (long j = iNodeTableBlock; j < firstNonMetadataBlock; j++)
                    writeBlock(j, emptyBlock, false);

                iNodeTables[i] = new INodeTable(this, (int) iNodeTableBlock);

                writeBlock(groupDescriptors[i].getBlockBitmap(), blockBitmap, false);
                writeBlock(groupDescriptors[i].getInodeBitmap(), inodeBitmap, false);
            }

            log.info("superblock.getBlockSize(): " + superblock.getBlockSize());

            buildRootEntry();

            // write everything to disk
            flush();

        } catch (IOException ioe) {
            throw new FileSystemException("Unable to create filesystem", ioe);
        }

    }

    /**
     * Flush all changed structures to the device.
     *
     * @throws IOException
     */
    public void flush() throws IOException {
        log.info("Flushing the contents of the filesystem");
        // update the inodes
        synchronized (inodeCache) {
            try {
                log.debug("inodecache size: " + inodeCache.size());
                for (INode iNode : inodeCache.values()) {
                    iNode.flush();
                }
            } catch (FileSystemException ex) {
                final IOException ioe = new IOException();
                ioe.initCause(ex);
                throw ioe;
            }
        }

        // update the group descriptors and the superblock copies
        updateFS();

        // flush the blocks
        synchronized (blockCache) {
            for (Block block : blockCache.values()) {
                block.flush();
            }
        }

        log.info("Filesystem flushed");
    }

    protected void updateFS() throws IOException {
        // updating one group descriptor updates all its copies
        for (int i = 0; i < groupCount; i++)
            groupDescriptors[i].updateGroupDescriptor();
        superblock.update();
    }

    public void close() throws IOException {
        // mark the filesystem clean
        superblock.setState(Ext2Constants.EXT2_VALID_FS);
        super.close();
    }

    /**
     * @see org.jnode.fs.spi.AbstractFileSystem#createRootEntry()
     */
    public Ext2Entry createRootEntry() throws IOException {
        try {
            return new Ext2Entry(getINode(Ext2Constants.EXT2_ROOT_INO), "/", Ext2Constants.EXT2_FT_DIR, this, null);
        } catch (FileSystemException ex) {
            final IOException ioe = new IOException();
            ioe.initCause(ex);
            throw ioe;
        }
    }

    /**
     * Return the block size of the file system
     */
    public int getBlockSize() {
        return superblock.getBlockSize();
    }

    /**
     * Gets the group descriptors for the file system.
     *
     * @return the group descriptors.
     */
    public GroupDescriptor[] getGroupDescriptors() {
        return groupDescriptors;
    }

    /**
     * Read a data block and put it in the cache if it is not yet cached, otherwise get it from the cache. Synchronized
     * access to the blockCache is important as the bitmap operations are synchronized to the blocks (actually, to
     * Block.getData()), so at any point in time it has to be sure that no two copies of the same block are stored in
     * the cache.
     *
     * @return data block nr
     */
    public byte[] getBlock(long nr) throws IOException {
        if (isClosed()) throw new IOException("FS closed (fs instance: " + this + ")");
        // log.debug("blockCache size: "+blockCache.size());

        int blockSize = superblock.getBlockSize();
        Block result;

        Integer key = Integer.valueOf((int) nr);
        synchronized (blockCache) {
            // check if the block has already been retrieved
            if (blockCache.containsKey(key)) {
                result = blockCache.get(key);
                return result.getData();
            }
        }

        // perform the time-consuming disk read outside of the synchronized
        // block
        // advantage:
        // -the lock is held for a shorter time, so other blocks that are
        // already in the cache can be returned immediately and
        // do not have to wait for a long disk read
        // disadvantage:
        // -a single block can be retrieved more than once. However,
        // the block will be put in the cache only once in the second
        // synchronized block
        ByteBuffer data = ByteBuffer.allocate(blockSize);
        log.debug("Reading block " + nr + " (offset: " + nr * blockSize + ") from disk");
        getApi().read(nr * blockSize, data);

        // synchronize again
        synchronized (blockCache) {
            // check if the block has already been retrieved
            if (!blockCache.containsKey(key)) {
                result = new Block(this, nr, data.array());
                blockCache.put(key, result);
                return result.getData();
            } else {
                // it is important to ALWAYS return the block that is in
                // the cache (it is used in synchronization)
                result = blockCache.get(key);
                return result.getData();
            }
        }
    }

    /**
     * Update the block in cache, or write the block to disk
     *
     * @param nr         block number
     * @param data       block data
     * @param forceWrite if forceWrite is false, the block is only updated in the cache (if it was in the cache). If
     *                   forceWrite is true, or the block is not in the cache, write it to disk.
     * @throws IOException
     */
    public void writeBlock(long nr, byte[] data, boolean forceWrite) throws IOException {
        if (isClosed()) throw new IOException("FS closed");

        if (isReadOnly()) throw new ReadOnlyFileSystemException("Filesystem is mounted read-only!");

        Block block;

        Integer key = Integer.valueOf((int) nr);
        int blockSize = superblock.getBlockSize();
        // check if the block is in the cache
        synchronized (blockCache) {
            if (blockCache.containsKey(key)) {
                block = blockCache.get(key);
                // update the data in the cache
                block.setData(data);
                if (forceWrite || SYNC_WRITE) {
                    // write the block to disk
                    ByteBuffer dataBuf = ByteBuffer.wrap(data, 0, blockSize);
                    getApi().write(nr * blockSize, dataBuf);
                    // timedWrite(nr, data);
                    block.setDirty(false);

                    log.debug("writing block " + nr + " to disk");
                } else block.setDirty(true);
            } else {
                // If the block was not in the cache, I see no reason to put it
                // in the cache when it is written.
                // It is simply written to disk.
                ByteBuffer dataBuf = ByteBuffer.wrap(data, 0, blockSize);
                getApi().write(nr * blockSize, dataBuf);
                // timedWrite(nr, data);
            }
        }
    }

    /*
     * Helper class for timedWrite
     * @author blind
     */
    /*
     * class TimeoutWatcher extends TimerTask { Thread mainThread; public TimeoutWatcher(Thread mainThread) {
     * this.mainThread = mainThread; } public void run() { mainThread.interrupt(); } } private static final long TIMEOUT
     * = 100;
     */
    /*
     * timedWrite writes to disk and waits for timeout, if the operation does not finish in time, restart it. DO NOT
     * CALL THIS DIRECTLY! ONLY TO BE CALLED FROM writeBlock()! @param nr the number of the block to write
     * @param data the data in the block
     */
    /*
     * private void timedWrite(long nr, byte[] data) throws IOException{ boolean finished = false; Timer writeTimer;
     * while(!finished) { finished = true; writeTimer = new Timer(); writeTimer.schedule(new
     * TimeoutWatcher(Thread.currentThread()), TIMEOUT); try{ getApi().write(nr*getBlockSize(), data, 0,
     * (int)getBlockSize()); writeTimer.cancel(); }catch(IOException ioe) { //IDEDiskDriver will throw an IOException
     * with a cause of an InterruptedException //it the write is interrupted if(ioe.getCause() instanceof
     * InterruptedException) { writeTimer.cancel(); log.debug("IDE driver interrupted during write operation: probably
     * timeout"); finished = false; } } } } private void timedRead(long nr, byte[] data) throws IOException{ boolean
     * finished = false; Timer readTimer; while(!finished) { finished = true; readTimer = new Timer();
     * readTimer.schedule(new TimeoutWatcher(Thread.currentThread()), TIMEOUT); try{ getApi().read( nr*getBlockSize(),
     * data, 0, (int)getBlockSize()); readTimer.cancel(); }catch(IOException ioe) { //IDEDiskDriver will throw an
     * IOException with a cause of an InterruptedException //it the write is interrupted if(ioe.getCause() instanceof
     * InterruptedException) { readTimer.cancel(); log.debug("IDE driver interrupted during read operation: probably
     * timeout"); finished = false; } } } }
     */

    public Superblock getSuperblock() {
        return superblock;
    }

    /**
     * Return the inode numbered inodeNr (the first inode is #1) Synchronized access to the inodeCache is important as
     * the file/directory operations are synchronized to the inodes, so at any point in time it has to be sure that only
     * one instance of any inode is present in the filesystem.
     */
    public INode getINode(int iNodeNr) throws IOException, FileSystemException {
        if ((iNodeNr < 1) || (iNodeNr > superblock.getINodesCount())) throw new FileSystemException("INode number ("
            + iNodeNr + ") out of range (0-" + superblock.getINodesCount() + ")");

        Integer key = Integer.valueOf(iNodeNr);

        log.debug("iNodeCache size: " + inodeCache.size());

        synchronized (inodeCache) {
            // check if the inode is already in the cache
            if (inodeCache.containsKey(key)) return inodeCache.get(key);
        }

        // move the time consuming disk read out of the synchronized block
        // (see comments at getBlock())

        int group = (int) ((iNodeNr - 1) / superblock.getINodesPerGroup());
        int index = (int) ((iNodeNr - 1) % superblock.getINodesPerGroup());

        // get the part of the inode table that contains the inode
        INodeTable iNodeTable = iNodeTables[group];
        INode result = new INode(this, new INodeDescriptor(iNodeTable, iNodeNr, group, index));
        result.read(iNodeTable.getInodeData(index));

        synchronized (inodeCache) {
            // check if the inode is still not in the cache
            if (!inodeCache.containsKey(key)) {
                inodeCache.put(key, result);
                return result;
            } else return inodeCache.get(key);
        }
    }

    /**
     * Checks whether block <code>blockNr</code> is free, and if it is, then allocates it with preallocation.
     *
     * @param blockNr
     * @return the block reservation
     * @throws IOException
     */
    public BlockReservation testAndSetBlock(long blockNr) throws IOException {

        if (blockNr < superblock.getFirstDataBlock() || blockNr >= superblock.getBlocksCount())
            return new BlockReservation(
                false, -1, -1);
        int group = translateToGroup(blockNr);
        int index = translateToIndex(blockNr);

        /*
         * Return false if the block is not a data block but a filesystem metadata block, as the beginning of each block
         * group is filesystem metadata: superblock copy (if present) block bitmap inode bitmap inode table Free blocks
         * begin after the inode table.
         */
        long iNodeTableBlock = groupDescriptors[group].getInodeTable();
        long firstNonMetadataBlock = iNodeTableBlock + INodeTable.getSizeInBlocks(this);

        if (blockNr < firstNonMetadataBlock) return new BlockReservation(false, -1, -1);

        // synchronize to the blockCache to avoid flushing the block between
        // reading it
        // and synchronizing to it
        synchronized (blockCache) {
            byte[] bitmap = getBlock(groupDescriptors[group].getBlockBitmap());
            synchronized (bitmap) {
                BlockReservation result = BlockBitmap.testAndSetBlock(bitmap, index);
                // update the block bitmap
                if (result.isSuccessful()) {
                    writeBlock(groupDescriptors[group].getBlockBitmap(), bitmap, false);
                    modifyFreeBlocksCount(group, -1 - result.getPreallocCount());
                    // result.setBlock(
                    // result.getBlock()+superblock.getFirstDataBlock() );
                    result.setBlock(blockNr);
                }
                return result;
            }
        }

    }

    /**
     * Create a new INode
     *
     * @param preferredBlockBroup first try to allocate the inode in this block group
     * @return the INode
     */
    protected INode createINode(int preferredBlockBroup, int fileFormat, int accessRights, int uid, int gid)
        throws FileSystemException, IOException {
        if (preferredBlockBroup >= superblock.getBlocksCount()) throw new FileSystemException("Block group "
            + preferredBlockBroup + " does not exist");

        int groupNr = preferredBlockBroup;
        // first check the preferred block group, if it has any free inodes
        INodeReservation res = findFreeINode(groupNr);

        // if no free inode has been found in the preferred block group, then
        // try the others
        if (!res.isSuccessful()) {
            for (groupNr = 0; groupNr < superblock.getBlockGroupNr(); groupNr++) {
                res = findFreeINode(groupNr);
                if (res.isSuccessful()) {
                    break;
                }
            }
        }

        if (!res.isSuccessful()) throw new FileSystemException("No free inodes found!");

        // a free inode has been found: create the inode and write it into the
        // inode table
        INodeTable iNodeTable = iNodeTables[preferredBlockBroup];
        // byte[] iNodeData = new byte[INode.INODE_LENGTH];
        int iNodeNr = res.getINodeNr((int) superblock.getINodesPerGroup());
        INode iNode = new INode(this, new INodeDescriptor(iNodeTable, iNodeNr, groupNr, res.getIndex()));
        iNode.create(fileFormat, accessRights, uid, gid);
        // trigger a write to disk
        iNode.update();

        log.debug("** NEW INODE ALLOCATED: inode number: " + iNode.getINodeNr());

        // put the inode into the cache
        synchronized (inodeCache) {
            Integer key = Integer.valueOf(iNodeNr);
            if (inodeCache.containsKey(key)) throw new FileSystemException(
                "Newly allocated inode is already in the inode cache!?");
            else inodeCache.put(key, iNode);
        }

        return iNode;
    }

    /**
     * Find a free INode in the inode bitmap and allocate it
     *
     * @param blockGroup
     * @return the INode reservation
     * @throws IOException
     */
    protected INodeReservation findFreeINode(int blockGroup) throws IOException {
        GroupDescriptor gdesc = groupDescriptors[blockGroup];
        if (gdesc.getFreeInodesCount() > 0) {
            // synchronize to the blockCache to avoid flushing the block between
            // reading it
            // and synchronizing to it
            synchronized (blockCache) {
                byte[] bitmap = getBlock(gdesc.getInodeBitmap());

                synchronized (bitmap) {
                    INodeReservation result = INodeBitmap.findFreeINode(bitmap);

                    if (result.isSuccessful()) {
                        // update the inode bitmap
                        writeBlock(gdesc.getInodeBitmap(), bitmap, true);
                        modifyFreeInodesCount(blockGroup, -1);

                        result.setGroup(blockGroup);

                        return result;
                    }
                }
            }
        }
        return new INodeReservation(false, -1);
    }

    protected int translateToGroup(long i) {
        return (int) ((i - superblock.getFirstDataBlock()) / superblock.getBlocksPerGroup());
    }

    protected int translateToIndex(long i) {
        return (int) ((i - superblock.getFirstDataBlock()) % superblock.getBlocksPerGroup());
    }

    /**
     * Modify the number of free blocks in the block group
     *
     * @param group
     * @param diff  can be positive or negative
     */
    protected void modifyFreeBlocksCount(int group, int diff) {
        GroupDescriptor gdesc = groupDescriptors[group];
        gdesc.setFreeBlocksCount(gdesc.getFreeBlocksCount() + diff);

        superblock.setFreeBlocksCount(superblock.getFreeBlocksCount() + diff);
    }

    /**
     * Modify the number of free inodes in the block group
     *
     * @param group
     * @param diff  can be positive or negative
     */
    protected void modifyFreeInodesCount(int group, int diff) {
        GroupDescriptor gdesc = groupDescriptors[group];
        gdesc.setFreeInodesCount(gdesc.getFreeInodesCount() + diff);

        superblock.setFreeInodesCount(superblock.getFreeInodesCount() + diff);
    }

    /**
     * Modify the number of used directories in a block group
     *
     * @param group
     * @param diff
     */
    protected void modifyUsedDirsCount(int group, int diff) {
        GroupDescriptor gdesc = groupDescriptors[group];
        gdesc.setUsedDirsCount(gdesc.getUsedDirsCount() + diff);
    }

    /**
     * Free up a block in the block bitmap.
     *
     * @param blockNr
     * @throws FileSystemException
     * @throws IOException
     */
    public void freeBlock(long blockNr) throws FileSystemException, IOException {
        if (blockNr < 0 || blockNr >= superblock.getBlocksCount()) throw new FileSystemException(
            "Attempt to free nonexisting block (" + blockNr + ")");

        int group = translateToGroup(blockNr);
        int index = translateToIndex(blockNr);
        GroupDescriptor gdesc = groupDescriptors[group];

        /*
         * Throw an exception if an attempt is made to free up a filesystem metadata block (the beginning of each block
         * group is filesystem metadata): superblock copy (if present) block bitmap inode bitmap inode table Free blocks
         * begin after the inode table.
         */
        long iNodeTableBlock = groupDescriptors[group].getInodeTable();
        long firstNonMetadataBlock = iNodeTableBlock + INodeTable.getSizeInBlocks(this);

        if (blockNr < firstNonMetadataBlock) throw new FileSystemException(
            "Attempt to free a filesystem metadata block!");

        // synchronize to the blockCache to avoid flushing the block between
        // reading it
        // and synchronizing to it
        synchronized (blockCache) {
            byte[] bitmap = getBlock(gdesc.getBlockBitmap());

            // at any time, only one copy of the Block exists in the cache, so
            // it is
            // safe to synchronize to the bitmapBlock object (it's part of
            // Block)
            synchronized (bitmap) {
                BlockBitmap.freeBit(bitmap, index);
                // update the bitmap block
                writeBlock(groupDescriptors[group].getBlockBitmap(), bitmap, false);
                modifyFreeBlocksCount(group, 1);
            }
        }
    }

    /**
     * Find free blocks in the block group <code>group</code>'s block bitmap. First check for a whole byte of free
     * blocks (0x00) in the bitmap, then check for any free bit. If blocks are found, mark them as allocated.
     *
     * @param group     the block group to check
     * @param threshold find the free blocks only if there are at least <code>threshold</code> number of free blocks
     * @return the index of the block (from the beginning of the partition)
     */
    public BlockReservation findFreeBlocks(int group, long threshold) throws IOException {
        GroupDescriptor gdesc = groupDescriptors[group];
        // see if it's worth to check the block group at all
        if (gdesc.getFreeBlocksCount() < threshold) return new BlockReservation(false, -1, -1,
            gdesc.getFreeBlocksCount());

        /*
         * Return false if the block is not a data block but a filesystem metadata block, as the beginning of each block
         * group is filesystem metadata: superblock copy (if present) block bitmap inode bitmap inode table Free blocks
         * begin after the inode table.
         */
        long iNodeTableBlock = groupDescriptors[group].getInodeTable();
        long firstNonMetadataBlock = iNodeTableBlock + INodeTable.getSizeInBlocks(this);
        int metadataLength = (int) (firstNonMetadataBlock - (superblock.getFirstDataBlock() + group
            * superblock.getBlocksPerGroup()));
        log.debug("group[" + group + "].getInodeTable()=" + iNodeTableBlock + ", iNodeTable.getSizeInBlocks()="
            + INodeTable.getSizeInBlocks(this));
        log.debug("metadata length for block group(" + group + "): " + metadataLength);

        BlockReservation result;

        // synchronize to the blockCache to avoid flushing the block between
        // reading it
        // and synchronizing to it
        synchronized (blockCache) {
            byte[] bitmapBlock = getBlock(gdesc.getBlockBitmap());

            // at any time, only one copy of the Block exists in the cache, so
            // it is
            // safe to synchronize to the bitmapBlock object (it's part of
            // Block)
            synchronized (bitmapBlock) {
                result = BlockBitmap.findFreeBlocks(bitmapBlock, metadataLength);

                // if the reservation was successful, write the bitmap data to
                // disk
                // within the same synchronized block
                if (result.isSuccessful()) {
                    writeBlock(groupDescriptors[group].getBlockBitmap(), bitmapBlock, true);
                    modifyFreeBlocksCount(group, -1 - result.getPreallocCount());
                }
            }
        }

        if (result.isSuccessful()) {
            result.setBlock(group * getSuperblock().getBlocksPerGroup() + superblock.getFirstDataBlock()
                + result.getBlock());
            result.setFreeBlocksCount(gdesc.getFreeBlocksCount());
        }

        return result;
    }

    /**
     * Returns the number of groups.
     *
     * @return int
     */
    protected int getGroupCount() {
        return groupCount;
    }

    /**
     * Check whether the filesystem uses the given RO feature (S_FEATURE_RO_COMPAT)
     *
     * @param mask
     * @return {@code true} if the filesystem uses the feature, otherwise {@code false}.
     */
    protected boolean hasROFeature(long mask) {
        return (mask & superblock.getFeatureROCompat()) != 0;
    }

    /**
     * Check whether the filesystem uses the given COMPAT feature (S_FEATURE_INCOMPAT)
     *
     * @param mask
     * @return {@code true} if the filesystem uses the feature, otherwise {@code false}.
     */
    protected boolean hasIncompatFeature(long mask) {
        return (mask & superblock.getFeatureIncompat()) != 0;
    }

    /**
     * utility function for determining if a given block group has superblock and group descriptor copies
     *
     * @param a positive integer
     * @param b positive integer > 1
     * @return true if an n integer exists such that a=b^n; false otherwise
     */
    private boolean checkPow(int a, int b) {
        if (a <= 1) return true;
        while (true) {
            if (a == b) return true;
            if (a % b == 0) {
                a = a / b;
                continue;
            }
            return false;
        }
    }

    /**
     * With the sparse_super option set, a filesystem does not have a superblock and group descriptor copy in every
     * block group.
     *
     * @param groupNr
     * @return true if the block group <code>groupNr</code> has a superblock and a group descriptor copy, otherwise
     *         false
     */
    protected boolean groupHasDescriptors(int groupNr) {
        if (hasROFeature(Ext2Constants.EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) return (checkPow(groupNr, 3)
            || checkPow(groupNr, 5) || checkPow(groupNr, 7));
        else return true;
    }

    /**
     *
     */
    protected FSFile createFile(FSEntry entry) throws IOException {
        Ext2Entry e = (Ext2Entry) entry;
        return new Ext2File(e);
    }

    /**
     *
     */
    protected FSDirectory createDirectory(FSEntry entry) throws IOException {
        Ext2Entry e = (Ext2Entry) entry;
        return new Ext2Directory(e);
    }

    protected FSEntry buildRootEntry() throws IOException {
        // a free inode has been found: create the inode and write it into the
        // inode table
        INodeTable iNodeTable = iNodeTables[0];
        // byte[] iNodeData = new byte[INode.INODE_LENGTH];
        int iNodeNr = Ext2Constants.EXT2_ROOT_INO;
        INode iNode = new INode(this, new INodeDescriptor(iNodeTable, iNodeNr, 0, iNodeNr - 1));
        int rights = 0xFFFF & (Ext2Constants.EXT2_S_IRWXU | Ext2Constants.EXT2_S_IRWXG | Ext2Constants.EXT2_S_IRWXO);
        iNode.create(Ext2Constants.EXT2_S_IFDIR, rights, 0, 0);
        // trigger a write to disk
        iNode.update();

        // add the inode to the inode cache
        synchronized (inodeCache) {
            inodeCache.put(Integer.valueOf(Ext2Constants.EXT2_ROOT_INO), iNode);
        }

        modifyUsedDirsCount(0, 1);

        Ext2Entry rootEntry = new Ext2Entry(iNode, "/", Ext2Constants.EXT2_FT_DIR, this, null);
        ((Ext2Directory) rootEntry.getDirectory())
            .addINode(Ext2Constants.EXT2_ROOT_INO, ".", Ext2Constants.EXT2_FT_DIR);
        ((Ext2Directory) rootEntry.getDirectory()).addINode(Ext2Constants.EXT2_ROOT_INO, "..",
            Ext2Constants.EXT2_FT_DIR);
        rootEntry.getDirectory().addDirectory("lost+found");
        return rootEntry;
    }

    protected void handleFSError(Exception e) {
        // mark the fs as having errors
        superblock.setState(Ext2Constants.EXT2_ERROR_FS);
        if (superblock.getErrors() == Ext2Constants.EXT2_ERRORS_RO) setReadOnly(true); // remount readonly

        if (superblock.getErrors() == Ext2Constants.EXT2_ERRORS_PANIC) throw new RuntimeException(
            "EXT2 FileSystem exception", e);
    }

    /**
     * @return Returns the blockCache (outside of this class only used to synchronize to)
     */
    protected synchronized BlockCache getBlockCache() {
        return blockCache;
    }

    /**
     * @return Returns the inodeCache (outside of this class only used to syncronized to)
     */
    protected synchronized INodeCache getInodeCache() {
        return inodeCache;
    }

    public long getFreeSpace() {
        return superblock.getFreeBlocksCount() * superblock.getBlockSize();
    }

    public long getTotalSpace() {
        return superblock.getBlocksCount() * superblock.getBlockSize();
    }

    public long getUsableSpace() {
        // TODO implement me
        return -1;
    }

    @Override
    public String getVolumeName() throws IOException {
        return superblock.getVolumeName();
    }
}
TOP

Related Classes of org.jnode.fs.ext2.Ext2FileSystem

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.