jext2.DataInode.java Source code

Java tutorial

Introduction

Here is the source code for jext2.DataInode.java

Source

/*
 * Copyright (c) 2011 Marcel Lauhoff.
 * 
 * This file is part of jext2.
 * 
 * jext2 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.
 * 
 * jext2 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 jext2.  If not, see <http://www.gnu.org/licenses/>.
 */

package jext2;

import java.nio.ByteBuffer;
import java.util.Date;
import java.util.LinkedList;
import java.util.logging.Logger;

import org.apache.commons.lang.builder.HashCodeBuilder;

import jext2.exceptions.FileTooLarge;
import jext2.exceptions.IoError;
import jext2.exceptions.JExt2Exception;
import jext2.exceptions.NoSpaceLeftOnDevice;

/**
 * Base class for inodes with data blocks. Like Symlinks, Directories, Regular Files
 */
public class DataInode extends Inode {
    Superblock superblock = Superblock.getInstance();
    BlockAccess blockAccess = BlockAccess.getInstance();
    DataBlockAccess dataAccess = null;

    private long[] block;
    private long blocks = 0;

    public final long getBlocks() {
        return this.blocks;
    }

    public final long[] getBlock() {
        return this.block;
    }

    public final void setBlocks(long blocks) {
        this.blocks = blocks;
    }

    public final void setBlock(long[] block) {
        this.block = block;
    }

    @Override
    public boolean hasDataBlocks() {
        return getBlocks() > 0;
    }

    /**
     * Get the data access provider to read and write to the data area of this
     * inode
     */
    public DataBlockAccess accessData() {
        if (dataAccess == null)
            dataAccess = DataBlockAccess.fromInode(this);
        return dataAccess;
    }

    /**
     * Read Inode data
     * @param  size    size of the data to be read
     * @param  offset  start address in data area
     * @return buffer of size size containing data.
     * @throws FileTooLarge
     * @throws IoError
     */
    public ByteBuffer readData(int size, long fileOffset) throws JExt2Exception, FileTooLarge {
        /* Returning null may break things somewhere..
         * Zero length buffer breaks something in jlowfuse's c code */
        if (getSize() == 0)
            return ByteBuffer.allocateDirect(1);

        /*
         * size may be larger than the inode.size, it doesn't make sense to return
         * 4k of zeros
         */
        if (size > getSize())
            size = (int) getSize();

        ByteBuffer buf = ByteBuffer.allocateDirect(size);

        int blocksize = superblock.getBlocksize();

        long i = 0;
        long firstBlock = fileOffset / blocksize;
        long offset = fileOffset % blocksize;

        /*
         * just as size may be larger than the inode's data, the number of blocks
         * may also be.
         */
        long approxBlocks = (size / blocksize) + 1;
        long maxBlocks = this.getBlocks() / (superblock.getBlocksize() / 512);
        if (approxBlocks > maxBlocks)
            approxBlocks = maxBlocks;

        while (i < approxBlocks) {
            long start = firstBlock + i;
            long stop = firstBlock + approxBlocks;

            LinkedList<Long> b = accessData().getBlocks(start, stop);
            int blocksRead;

            /*
             * Note on the sparse file support:
             * getBlocks will return null if there is no data block for this
             * logical address. So just move the position count blocks forward.
             */

            if (b == null) { /* hole */
                blocksRead = 1;

                int unboundedLimit = buf.position() + blocksize;
                int limit = Math.min(unboundedLimit, buf.capacity());

                assert limit <= buf.capacity() : "New position, limit " + limit + " is beyond buffer's capacity, "
                        + buf;

                buf.limit(limit);
                buf.position(limit);

                assert buf.limit() == buf.position();

            } else { /* blocks */
                blocksRead = b.size();

                long pos = b.getFirst() * blocksize + offset;
                int unboundedLimit = buf.position() + blocksRead * blocksize;
                int limit = Math.min(unboundedLimit, buf.capacity());

                assert limit <= buf.capacity() : "New limit " + limit + " is beyond buffer's capacity, " + buf;

                buf.limit(limit);
                blockAccess.readToBufferUnsynchronized(pos, buf);
            }

            i += blocksRead;
            offset = 0;

            /* This should be removed soon. IllegalMonitorStateException happen
             * occasionally for unknown reasons.
             */
            try {
                accessData().getHierarchyLock().readLock().unlock();
            } catch (IllegalMonitorStateException e) {
                Logger log = Filesystem.getLogger();
                log.warning("IllegalMonitorStateException encountered in readData, inode=" + this);
                log.warning(String.format(
                        "context for exception: blocks=%s i=%d approxBlocks=%d off=%d buf=%s readlock=%s lock.readlock.holds=%s",
                        b, i, approxBlocks, fileOffset, buf, accessData().getHierarchyLock(),
                        accessData().getHierarchyLock().getReadHoldCount()));
            }

            if (buf.capacity() == buf.limit())
                break;
        }

        assert buf.position() == buf.limit() : "Buffer wasn't filled completely";
        assert buf.limit() == size : "Read buffer size does not match request size";

        if (buf.limit() > getSize())
            buf.limit((int) getSize());

        buf.rewind();
        return buf;
    }

    /**
     * Write data in buffer to disk. This works best when whole blocks which
     * are a multiple of blocksize in size are written. Partial blocks are
     * written by first reading the block and then writing the new data
     * to that buffer than write that new buffer to disk.
     * @throws NoSpaceLeftOnDevice
     * @throws FileTooLarge
     */
    public int writeData(ByteBuffer buf, long offset) throws JExt2Exception, NoSpaceLeftOnDevice, FileTooLarge {
        /*
         * Note on sparse file support:
         * getBlocksAllocate does not care if there are holes. Just write as much
         * blocks as the buffer requires at the desired location an set inode.size
         * accordingly.
         */

        int blocksize = superblock.getBlocksize();
        long start = offset / blocksize;
        long end = (buf.capacity() + blocksize) / blocksize + start;
        int startOff = (int) (offset % blocksize);

        if (startOff > 0)
            end += 1;

        buf.rewind();

        while (start < end) {
            LinkedList<Long> blockNrs = accessData().getBlocksAllocate(start, 1);
            int bytesLeft = buf.capacity() - buf.position();

            if (bytesLeft < blocksize || startOff > 0) { /* write partial block */
                ByteBuffer onDisk = blockAccess.read(blockNrs.getFirst());

                onDisk.position(startOff);

                assert onDisk.limit() == blocksize;

                buf.limit(buf.position() + Math.min(bytesLeft, onDisk.remaining()));

                onDisk.put(buf);

                onDisk.position(startOff);
                blockAccess.writeFromBufferUnsynchronized((blockNrs.getFirst() & 0xffffffff) * blocksize, onDisk);
            } else { /* write whole block */
                buf.limit(buf.position() + blocksize);

                blockAccess.writeFromBufferUnsynchronized((blockNrs.getFirst() & 0xffffffff) * blocksize, buf);
            }

            start += 1;
            startOff = 0;
            accessData().unlockHierarchyChanges();

        }
        int written = buf.position();
        assert written == buf.capacity();

        /* increase inode.size if we grew the file */
        if (offset + written > getSize()) { /* file grew */
            setStatusChangeTime(new Date());
            setSize(offset + written);
        }

        return written;
    }

    protected DataInode(long blockNr, int offset) {
        super(blockNr, offset);
    }

    @Override
    protected void read(ByteBuffer buf) throws IoError {
        super.read(buf);
        this.blocks = Ext2fsDataTypes.getLE32U(buf, 28 + offset);

        if (!isFastSymlink()) {
            this.block = new long[Constants.EXT2_N_BLOCKS];
            for (int i = 0; i < Constants.EXT2_N_BLOCKS; i++) {
                this.block[i] = Ext2fsDataTypes.getLE32U(buf, 40 + (i * 4) + offset);
            }
        }
    }

    @Override
    protected void write(ByteBuffer buf) throws IoError {
        if (!isFastSymlink()) {
            for (int i = 0; i < Constants.EXT2_N_BLOCKS; i++) {
                Ext2fsDataTypes.putLE32U(buf, this.block[i], 40 + (i * 4));
            }
        }
        Ext2fsDataTypes.putLE32U(buf, this.blocks, 28);
        super.write(buf);
    }

    @Override
    public void write() throws IoError {
        ByteBuffer buf = allocateByteBuffer();
        write(buf);
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().appendSuper(super.hashCode()).append(blocks).append(block).toHashCode();
    }

}