/* * $Id$ * * Copyright (C) 2003-2013 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.partitions.ibm; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import org.jnode.driver.ApiNotFoundException; import org.jnode.driver.Device; import org.jnode.driver.block.BlockDeviceAPI; import org.jnode.driver.bus.ide.IDEConstants; import org.jnode.partitions.PartitionTable; import org.jnode.partitions.PartitionTableType; import org.jnode.util.BigEndian; import org.jnode.util.LittleEndian; /** * @author epr */ public class IBMPartitionTable implements PartitionTable<IBMPartitionTableEntry> { public static final int TABLE_SIZE = 4; /** * The set of known filesystem markers. */ private static final Set<String> FILESYSTEM_OEM_NAMES = new HashSet<String>(); static { // FAT OEM names FILESYSTEM_OEM_NAMES.add("MSDOS5.0"); FILESYSTEM_OEM_NAMES.add("MSWIN4.1"); FILESYSTEM_OEM_NAMES.add("IBM 3.3"); FILESYSTEM_OEM_NAMES.add("IBM 7.1"); FILESYSTEM_OEM_NAMES.add("mkdosfs\u0000"); FILESYSTEM_OEM_NAMES.add("FreeDOS "); // NTFS FILESYSTEM_OEM_NAMES.add("NTFS "); } /** * The type of partition table */ private final IBMPartitionTableType tableType; /** * The partition entries */ private final IBMPartitionTableEntry[] partitions; /** * The device */ private final Device driveDevice; /** * Extended partition */ private final ArrayList<IBMPartitionTableEntry> extendedPartitions = new ArrayList<IBMPartitionTableEntry>(); /** * My logger */ private static final Logger log = Logger.getLogger(IBMPartitionTable.class); /** * The position of the extendedPartition in the table */ private int extendedPartitionEntry = -1; /** * Create a new instance * * @param bootSector */ public IBMPartitionTable(IBMPartitionTableType tableType, byte[] bootSector, Device device) { // this.bootSector = bootSector; this.tableType = tableType; this.driveDevice = device; if (containsPartitionTable(bootSector)) { this.partitions = new IBMPartitionTableEntry[TABLE_SIZE]; for (int partNr = 0; partNr < partitions.length; partNr++) { log.debug("try part " + partNr); partitions[partNr] = new IBMPartitionTableEntry(this, bootSector, partNr); if (partitions[partNr].isExtended()) { extendedPartitionEntry = partNr; log.debug("Found Extended partitions"); handleExtended(partitions[partNr]); } } } else { partitions = null; } } /** * Fill the extended Table */ private void handleExtended(IBMPartitionTableEntry current) { final long startLBA = current.getStartLba(); final ByteBuffer sector = ByteBuffer.allocate(IDEConstants.SECTOR_SIZE); try { log.debug("Try to read the Extended Partition Table"); BlockDeviceAPI api = driveDevice.getAPI(BlockDeviceAPI.class); api.read(startLBA * IDEConstants.SECTOR_SIZE, sector); } catch (ApiNotFoundException e) { // I think we can't get it log.error("API Not Found Exception"); } catch (IOException e) { // I think we can't get it log.error("IOException"); } IBMPartitionTableEntry entry; for (int i = 0; i < TABLE_SIZE; i++) { entry = new IBMPartitionTableEntry(this, sector.array(), i); if (entry.isValid() && !entry.isEmpty()) { // correct the offset if (entry.isExtended()) { entry.setStartLba(entry.getStartLba() + partitions[extendedPartitionEntry].getStartLba()); handleExtended(entry); } else { entry.setStartLba(entry.getStartLba() + current.getStartLba()); extendedPartitions.add(entry); } } } } public boolean hasExtended() { return !extendedPartitions.isEmpty(); } /** * Does the given boot sector contain an IBM partition table? * * @param bootSector the data to check. * @return {@code true} if the data contains an IBM partition table, {@code false} otherwise. */ public static boolean containsPartitionTable(byte[] bootSector) { if (bootSector.length < 0x200) { // Not enough data for detection return false; } if (LittleEndian.getUInt16(bootSector, 510) != 0xaa55) { log.debug("No aa55 magic"); return false; } if (LittleEndian.getUInt16(bootSector, 428) == 0x5678) { // Matches the AAP MBR extra signature, probably an valid partition table log.debug("Has AAP MBR extra signature"); return true; } if (LittleEndian.getUInt16(bootSector, 380) == 0xa55a) { // Matches the AST/NEC MBR extra signature, probably an valid partition table log.debug("Has AST/NEC MBR extra signature"); return true; } if (LittleEndian.getUInt16(bootSector, 252) == 0x55aa) { // Matches the Disk Manager MBR extra signature, probably an valid partition table log.debug("Has Disk Manager MBR extra signature"); return true; } if (LittleEndian.getUInt32(bootSector, 2) == 0x4c57454e) { // Matches the NEWLDR MBR extra signature, probably an valid partition table log.debug("Has NEWLDR MBR extra signature"); return true; } if (LittleEndian.getUInt32(bootSector, 6) == 0x4f4c494c) { // Matches the LILO signature, probably an valid partition table log.debug("Has LILO signature"); return true; } if (BigEndian.getUInt32(bootSector, 0) == 0x33ffbe00 && BigEndian.getUInt32(bootSector, 4) == 0x028ed7bc) { // Matches HP boot code. It is not possible to match the strings here because they are localised. E.g: // "\r\nMissing operating system\r\n\u0000\r\nMaster Boot Record Error\r\n\u0000\r\nPress a key.\r\n\u0000" // "\r\nManglende operativ system\r\n\u0000\r\nFeil i hovedoppstartsposten\r\n\u0000\r\nTrykk en tast" log.debug("Has HP boot code signature"); return true; } String bootSectorAsString = new String(bootSector, 0, 512, Charset.forName("US-ASCII")); if (bootSectorAsString.contains("Invalid partition table\u001eError loading operating system\u0018Missing operating system")) { // Matches DOS 2.0 partition boot code error message signature // see: // http://thestarman.narod.ru/asm/mbr/200MBR.htm log.debug("Has DOS 2.0 code error string signature"); return true; } if (bootSectorAsString.contains("Invalid partition table\u0000Error loading operating system\u0000Missing operating system")) { // Matches Microsoft partition boot code error message signature // see: // http://thestarman.pcministry.com/asm/mbr/VistaMBR.htm // http://thestarman.narod.ru/asm/mbr/Win2kmbr.htm // http://thestarman.narod.ru/asm/mbr/95BMEMBR.htm // http://thestarman.narod.ru/asm/mbr/STDMBR.htm log.debug("Has Microsoft code error string signature"); return true; } if (LittleEndian.getUInt32(bootSector, 296) == 0xC3F961D6L) { // Matches Microsoft Windows 2000 partition boot code. Starting from Windows 2000 the boot code error // messages are localised, so the check above won't match them. // // see: // http://thestarman.narod.ru/asm/mbr/Win2kmbr.htm log.debug("Has w2k boot code signature"); return true; } if (bootSectorAsString.contains("Read\u0000Boot\u0000 error\r\n\u0000")) { // Matches BSD partition boot code error message signature log.debug("Has BSD code error string signature"); return true; } if (bootSectorAsString.contains("GRUB \u0000Geom\u0000Hard Disk\u0000Read\u0000 Error")) { // Matches GRUB string signature log.debug("Has GRUB string signature"); return true; } if (bootSectorAsString.contains("\u0000Multiple active partitions.\r\n")) { // Matches SYSLINUX string signature log.debug("Has SYSLINUX string signature"); return true; } if (bootSectorAsString.contains("MAKEBOOT")) { // Matches MAKEBOOT string extra signature log.debug("Has MAKEBOOT string signature"); return true; } if (bootSectorAsString.contains("MBR \u0010\u0000")) { // Matches MBR string extra signature log.debug("Has MBR string signature"); return true; } if (LittleEndian.getUInt32(bootSector, 241) == 0x41504354) { // Matches TCPA signature. Seen at offsets: // * 0xF1 - Windows Vista // * 0x18E - Windows PE // see http://thestarman.pcministry.com/asm/mbr/VistaMBR.htm log.debug("Has TCPA extra signature"); return true; } String bsdNameTabString = new String(bootSector, 416, 16, Charset.forName("US-ASCII")); if (bsdNameTabString.contains("Linu\ufffd") || bsdNameTabString.contains("FreeBS\ufffd")) { // Matches BSD nametab entries signature log.debug("Has BSD nametab entries"); return true; } // Rule out the Linux kernel binary if (bootSector.length > 520) { String linuxKernelHeaderString = new String(bootSector, 514, 4, Charset.forName("US-ASCII")); if ("HdrS".equals(linuxKernelHeaderString)) { // Matches Linux kernel header signature log.debug("Has Linux kernel header signature"); return false; } } // Check if this looks like a filesystem instead of a partition table String oemName = new String(bootSector, 3, 8, Charset.forName("US-ASCII")); if (FILESYSTEM_OEM_NAMES.contains(oemName)) { log.debug("Looks like a file system instead of a partition table."); return false; } if (LittleEndian.getUInt32(bootSector, 0xc) == 0x504E0000) { // Matches the 'NP' signature log.debug("Matches the 'NP' signature"); return true; } // Nothing matched, fall back to validating any specified partition entries log.debug("Checking partitions"); List<IBMPartitionTableEntry> entries = new ArrayList<IBMPartitionTableEntry>(); for (int partitionNumber = 0; partitionNumber < TABLE_SIZE; partitionNumber++) { IBMPartitionTableEntry partition = new IBMPartitionTableEntry(null, bootSector, partitionNumber); if (partition.isValid()) { entries.add(partition); } } // Check that none of the entries are overlapping with each other for (int partitionNumber = 0; partitionNumber < entries.size(); partitionNumber++) { IBMPartitionTableEntry partition = entries.get(partitionNumber); for (int i = 0; i < entries.size(); i++) { if (i != partitionNumber) { IBMPartitionTableEntry otherPartition = entries.get(i); if (partition.getStartLba() <= otherPartition.getStartLba() + otherPartition.getNrSectors() - 1 && otherPartition.getStartLba() <= partition.getStartLba() + partition.getNrSectors() - 1) { log.error("Parition table entries overlap: " + partition + " " + otherPartition); return false; } } } } // Finally, check if there is at least one entry that seems valid return !entries.isEmpty(); } public Iterator<IBMPartitionTableEntry> iterator() { return new Iterator<IBMPartitionTableEntry>() { private int index = 0; private final int last = (partitions == null) ? 0 : partitions.length; public boolean hasNext() { return index < last; } public IBMPartitionTableEntry next() { return partitions[index++]; } public void remove() { throw new UnsupportedOperationException(); } }; } /** * @return Returns the extendedPartitions. */ public List<IBMPartitionTableEntry> getExtendedPartitions() { return extendedPartitions; } /** * @see org.jnode.partitions.PartitionTable#getType() */ public PartitionTableType getType() { return tableType; } }