/* * 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; } }