Subversion Repositories mkgmap

Rev

Rev 1751 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2006 Steve Ratcliffe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: 30-Nov-2006
 */

package uk.me.parabola.imgfmt.sys;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;

/**
 * An entry within a directory.  This holds its name and a list
 * of blocks that go to make up this file.
 *
 * A directory entry may take more than block in the file system.
 *
 * <p>All documentation seems to point to the block numbers having to be
 * contiguous, but seems strange so I shall experiment.
 *
 * <p>Entries are in blocks of 512 bytes, regardless of the block size.
 *
 * @author Steve Ratcliffe
 */

class Dirent implements DirectoryEntry {
        protected static final Logger log = Logger.getLogger(Dirent.class);

        // Constants.
        static final int MAX_FILE_LEN = 8;
        static final int MAX_EXT_LEN = 3;

        // Offsets
        static final int OFF_FILE_USED = 0x00;
        static final int OFF_NAME = 0x01;
        static final int OFF_EXT = 0x09;
        static final int OFF_FLAG = 0x10;
        static final int OFF_FILE_PART = 0x11;
        private static final int OFF_SIZE = 0x0c;

        // File names are a base+extension
        private String name;
        private String ext;

        // The file size.
        private int size;

        private final BlockManager blockManager;

        // The block table holds all the blocks that belong to this file.  The
        // documentation suggests that block numbers are always contiguous.
        private final BlockTable blockTable;

        private boolean special;
        private static final int OFF_USED_FLAG = 0;
        private boolean initialized;

        Dirent(String name, BlockManager blockManager) {
                this.blockManager = blockManager;

                int dot = name.lastIndexOf('.');
                if (dot >= 0) {
                        setName(name.substring(0, dot));
                        setExt(name.substring(dot+1));
                } else
                        throw new IllegalArgumentException("Filename did not have dot");

                blockTable = new BlockTable();
        }

        /**
         * Write this entry out to disk.  Note that these are 512 bytes, regardless
         * of the block size.
         *
         * @param file The file to write to.
         * @throws IOException If writing fails for any reason.
         */

        void sync(ImgChannel file) throws IOException {
                int ntables = blockTable.getNBlockTables();
                ByteBuffer buf = ByteBuffer.allocate(ENTRY_SIZE * ntables);
                buf.order(ByteOrder.LITTLE_ENDIAN);

                for (int part = 0; part < ntables; part++) {
                        log.debug("position at part", part, "is", buf.position());
                       
                        buf.put((byte) 1);

                        buf.put(Utils.toBytes(name, MAX_FILE_LEN, (byte) ' '));
                        buf.put(Utils.toBytes(ext, MAX_EXT_LEN, (byte) ' '));

                        // Size is only present in the first part
                        if (part == 0) {
                                log.debug("dirent", name, '.', ext, "size is going to", size);
                                buf.putInt(size);
                        } else {
                                buf.putInt(0);
                        }

                        buf.put((byte) (special? 0x3: 0));
                        buf.putChar((char) part);

                        // Write out the allocation of blocks for this entry.
                        buf.position(ENTRY_SIZE * part + 0x20);
                        blockTable.writeTable(buf, part);
                }

                buf.flip();
                file.write(buf);
        }

        /**
         * Get the file name.
         *
         * @return The file name.
         */

        public String getName() {
                return name;
        }

        /**
         * Get the file extension.
         *
         * @return The file extension.
         */

        public String getExt() {
                return ext;
        }

        /**
         * The full name is of the form 8+3 with a dot in between the name and
         * extension.  The full name is used as the index in the directory.
         *
         * @return The full name.
         */

        public String getFullName() {
                return name + '.' + ext;
        }

        /**
         * Read in the block numbers from the given buffer.  If this is the first
         * directory block for this file, then the size is set too.
         *
         * @param buf The data as read from the file.
         */

        void initBlocks(ByteBuffer buf) {

                byte used = buf.get(OFF_USED_FLAG);
                if (used != 1)
                        return;

                int part = buf.get(OFF_FILE_PART) & 0xff;
                if (part == 0 || (isSpecial() && part == 3))
                        size = buf.getInt(OFF_SIZE);

                blockTable.readTable(buf);
                initialized = true;
        }

        /**
         * Get the file size.
         *
         * @return The size of the file in bytes.
         */

        public int getSize() {
                return size;
        }

        /**
         * Set the file name.  The name should be exactly eight characters long
         * and it is truncated or left padded with zeros to make this true.
         *
         * @param name The file name.
         */

        private void setName(String name) {
                int len = name.length();
                if (len > MAX_FILE_LEN) {
                        this.name = name.substring(0, 8);
                } else if (len < MAX_FILE_LEN) {
                        StringBuffer sb = new StringBuffer();
                        for (int i = 0; i < MAX_FILE_LEN - len; i++) {
                                sb.append('0');
                        }
                        sb.append(name);
                        this.name = sb.toString();
                } else
                        this.name = name;
        }

        /**
         * Set the file extension.  Can't be longer than three characters.
         * @param ext The file extension.
         */

        private void setExt(String ext) {
                log.debug("ext len", ext.length());
                if (ext.length() != MAX_EXT_LEN)
                        throw new IllegalArgumentException("File extension is wrong size");
                this.ext = ext;
        }

        /**
         * The number of blocks that the header covers.  The header includes
         * the directory for the purposes of this routine.
         *
         * @return The total number of header basic blocks (blocks of 512 bytes).
         */

        int numberHeaderBlocks() {
                return blockTable.getNBlockTables();
        }

        void setSize(int size) {
                if (log.isDebugEnabled())
                        log.debug("setting size", getName(), getExt(), "to", size);
                this.size = size;
        }

        /**
         * Add a block without increasing the size of the file.
         *
         * @param n The block number.
         */

        void addBlock(int n) {
                blockTable.addBlock(n);
        }

        /**
         * Set for the first directory entry that covers the header and directory
         * itself.
         *
         * @param special Set to true to mark as the special first entry.
         */

        public void setSpecial(boolean special) {
                this.special = special;
        }

        public boolean isSpecial() {
                return special;
        }

        /**
         * Converts from a logical block to a physical block.  If the block does
         * not exist then 0xffff will be returned.
         *
         * @param lblock The logical block in the file.
         * @return The corresponding physical block in the filesystem.
         */

        public int getPhysicalBlock(int lblock) {
                return blockTable.physFromLogical(lblock);
        }

        public BlockManager getBlockManager() {
                return blockManager;
        }

        protected void setInitialized(boolean initialized) {
                this.initialized = initialized;
        }
       
        protected boolean isInitialized() {
                return initialized;
        }
}