/*
 * dos.c
 * Detection of DOS parition maps and file systems
 *
 * Copyright (c) 2003-2006 Christoph Pfisterer
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 */

#include "global.h"

/*
 * DOS partition types
 *
 * Taken from fdisk/i386_sys_types.c and fdisk/common.h of
 * util-linux 2.11n (as packaged by Debian), Feb 08, 2003.
 */

struct systypes {
  unsigned char type;
  char *name;
};

struct systypes i386_sys_types[] = {
  { 0x00, "Empty" },
  { 0x01, "FAT12" },
  { 0x02, "XENIX root" },
  { 0x03, "XENIX usr" },
  { 0x04, "FAT16 <32M" },
  { 0x05, "Extended" },
  { 0x06, "FAT16" },
  { 0x07, "HPFS/NTFS" },
  { 0x08, "AIX" },
  { 0x09, "AIX bootable" },
  { 0x0a, "OS/2 Boot Manager" },
  { 0x0b, "Win95 FAT32" },
  { 0x0c, "Win95 FAT32 (LBA)" },
  { 0x0e, "Win95 FAT16 (LBA)" },
  { 0x0f, "Win95 Ext'd (LBA)" },
  { 0x10, "OPUS" },
  { 0x11, "Hidden FAT12" },
  { 0x12, "Compaq diagnostics" },
  { 0x14, "Hidden FAT16 <32M" },
  { 0x16, "Hidden FAT16" },
  { 0x17, "Hidden HPFS/NTFS" },
  { 0x18, "AST SmartSleep" },
  { 0x1b, "Hidden Win95 FAT32" },
  { 0x1c, "Hidden Win95 FAT32 (LBA)" },
  { 0x1e, "Hidden Win95 FAT16 (LBA)" },
  { 0x24, "NEC DOS" },
  { 0x39, "Plan 9" },
  { 0x3c, "PartitionMagic recovery" },
  { 0x40, "Venix 80286" },
  { 0x41, "PPC PReP Boot" },
  { 0x42, "SFS / MS LDM" },
  { 0x4d, "QNX4.x" },
  { 0x4e, "QNX4.x 2nd part" },
  { 0x4f, "QNX4.x 3rd part" },
  { 0x50, "OnTrack DM" },
  { 0x51, "OnTrack DM6 Aux1" },
  { 0x52, "CP/M" },
  { 0x53, "OnTrack DM6 Aux3" },
  { 0x54, "OnTrackDM6" },
  { 0x55, "EZ-Drive" },
  { 0x56, "Golden Bow" },
  { 0x5c, "Priam Edisk" },
  { 0x61, "SpeedStor" },
  { 0x63, "GNU HURD or SysV" },
  { 0x64, "Novell Netware 286" },
  { 0x65, "Novell Netware 386" },
  { 0x70, "DiskSecure Multi-Boot" },
  { 0x75, "PC/IX" },
  { 0x78, "XOSL" },
  { 0x80, "Old Minix" },
  { 0x81, "Minix / old Linux" },
  { 0x82, "Linux swap / Solaris" },
  { 0x83, "Linux" },
  { 0x84, "OS/2 hidden C: drive" },
  { 0x85, "Linux extended" },
  { 0x86, "NTFS volume set" },
  { 0x87, "NTFS volume set" },
  { 0x8e, "Linux LVM" },
  { 0x93, "Amoeba" },
  { 0x94, "Amoeba BBT" },
  { 0x9f, "BSD/OS" },
  { 0xa0, "IBM Thinkpad hibernation" },
  { 0xa5, "FreeBSD" },
  { 0xa6, "OpenBSD" },
  { 0xa7, "NeXTSTEP" },
  { 0xa9, "NetBSD" },
  { 0xaf, "Mac OS X" },
  { 0xb7, "BSDI fs" },
  { 0xb8, "BSDI swap" },
  { 0xbb, "Boot Wizard hidden" },
  { 0xc1, "DRDOS/sec (FAT-12)" },
  { 0xc4, "DRDOS/sec (FAT-16 < 32M)" },
  { 0xc6, "DRDOS/sec (FAT-16)" },
  { 0xc7, "Syrinx" },
  { 0xda, "Non-FS data" },
  { 0xdb, "CP/M / CTOS / ..." },
  { 0xde, "Dell Utility" },
  { 0xdf, "BootIt" },
  { 0xe1, "DOS access" },
  { 0xe3, "DOS R/O" },
  { 0xe4, "SpeedStor" },
  { 0xeb, "BeOS fs" },
  { 0xee, "EFI GPT protective" },
  { 0xef, "EFI System (FAT)" },
  { 0xf0, "Linux/PA-RISC boot" },
  { 0xf1, "SpeedStor" },
  { 0xf4, "SpeedStor" },
  { 0xf2, "DOS secondary" },
  { 0xfd, "Linux raid autodetect" },
  { 0xfe, "LANstep" },
  { 0xff, "BBT" },
  { 0, 0 }
};


char * get_name_for_mbrtype(int type)
{
  int i;

  for (i = 0; i386_sys_types[i].name; i++)
    if (i386_sys_types[i].type == type)
      return i386_sys_types[i].name;
  return "Unknown";
}

/*
 * DOS-style partition map / MBR
 */

static void detect_dos_partmap_ext(SECTION *section, u8 extbase,
				   int level, int *extpartnum);

void detect_dos_partmap(SECTION *section, int level)
{
  unsigned char *buf;
  int i, off, used, type, types[4], bootflags[4];
  u4 start, size, starts[4], sizes[4];
  int extpartnum = 5;
  char s[256], append[64];

  /* partition maps only occur at the start of a device */
  if (section->pos != 0)
    return;

  if (get_buffer(section, 0, 512, (void **)&buf) < 512)
    return;

  /* check signature */
  if (buf[510] != 0x55 || buf[511] != 0xAA)
    return;

  /* get entries and check */
  used = 0;
  for (off = 446, i = 0; i < 4; i++, off += 16) {
    /* get data */
    bootflags[i] = buf[off];
    types[i] = buf[off + 4];
    starts[i] = get_le_long(buf + off + 8);
    sizes[i] = get_le_long(buf + off + 12);

    /* bootable flag: either on or off */
    if (bootflags[i] != 0x00 && bootflags[i] != 0x80)
      return;
    /* size non-zero -> entry in use */
    if (starts[i] && sizes[i])
      used = 1;
  }
  if (!used)
    return;

  /* parse the data for real */
  print_line(level, "DOS/MBR partition map");
  for (i = 0; i < 4; i++) {
    start = starts[i];
    size = sizes[i];
    type = types[i];
    if (start == 0 || size == 0)
      continue;

    sprintf(append, " from %lu", start);
    if (bootflags[i] == 0x80)
      strcat(append, ", bootable");
    format_blocky_size(s, size, 512, "sectors", append);
    print_line(level, "Partition %d: %s",
	       i+1, s);

    print_line(level + 1, "Type 0x%02X (%s)", type, get_name_for_mbrtype(type));

    if (type == 0x05 || type == 0x0f || type == 0x85) {
      /* extended partition */
      detect_dos_partmap_ext(section, start, level + 1, &extpartnum);
    } else if (type != 0xee) {
      /* recurse for content detection */
      analyze_recursive(section, level + 1,
			(u8)start * 512, (u8)size * 512, 0);
    }
  }
}

static void detect_dos_partmap_ext(SECTION *section, u8 extbase,
				   int level, int *extpartnum)
{
  unsigned char *buf;
  u8 tablebase, nexttablebase;
  int i, off, type, types[4];
  u4 start, size, starts[4], sizes[4];
  char s[256], append[64];

  for (tablebase = extbase; tablebase; tablebase = nexttablebase) {
    /* read sector from linked list */
    if (get_buffer(section, tablebase << 9, 512, (void **)&buf) < 512)
      return;

    /* check signature */
    if (buf[510] != 0x55 || buf[511] != 0xAA) {
      print_line(level, "Signature missing");
      return;
    }

    /* get entries */
    for (off = 446, i = 0; i < 4; i++, off += 16) {
      types[i] = buf[off + 4];
      starts[i] = get_le_long(buf + off + 8);
      sizes[i] = get_le_long(buf + off + 12);
    }

    /* parse the data for real */
    nexttablebase = 0;
    for (i = 0; i < 4; i++) {
      start = starts[i];
      size = sizes[i];
      type = types[i];
      if (size == 0)
	continue;

      if (type == 0x05 || type == 0x85) {
	/* inner extended partition */

	nexttablebase = extbase + start;

      } else {
	/* logical partition */

	sprintf(append, " from %llu+%lu", tablebase, start);
	format_blocky_size(s, size, 512, "sectors", append);
	print_line(level, "Partition %d: %s",
		   *extpartnum, s);
	(*extpartnum)++;
	print_line(level + 1, "Type 0x%02X (%s)", type, get_name_for_mbrtype(type));

	/* recurse for content detection */
	if (type != 0xee) {
	  analyze_recursive(section, level + 1,
			    (tablebase + start) * 512, (u8)size * 512, 0);
	}
      }
    }
  }
}

/*
 * EFI GPT partition map
 */

struct gpttypes {
  char *guid;
  char *name;
};

struct gpttypes gpt_types[] = {
  { "\x28\x73\x2A\xC1\x1F\xF8\xD2\x11\xBA\x4B\x00\xA0\xC9\x3E\xC9\x3B", "EFI System (FAT)" },
  { "\x41\xEE\x4D\x02\xE7\x33\xD3\x11\x9D\x69\x00\x08\xC7\x81\xF3\x9F", "MBR partition scheme" },
  { "\x16\xE3\xC9\xE3\x5C\x0B\xB8\x4D\x81\x7D\xF9\x2D\xF0\x02\x15\xAE", "MS Reserved" },
  { "\xA2\xA0\xD0\xEB\xE5\xB9\x33\x44\x87\xC0\x68\xB6\xB7\x26\x99\xC7", "Basic Data" },
  { "\xAA\xC8\x08\x58\x8F\x7E\xE0\x42\x85\xD2\xE1\xE9\x04\x34\xCF\xB3", "MS LDM Metadata" },
  { "\xA0\x60\x9B\xAF\x31\x14\x62\x4F\xBC\x68\x33\x11\x71\x4A\x69\xAD", "MS LDM Data" },
  { "\x1E\x4C\x89\x75\xEB\x3A\xD3\x11\xB7\xC1\x7B\x03\xA0\x00\x00\x00", "HP/UX Data" },
  { "\x28\xE7\xA1\xE2\xE3\x32\xD6\x11\xA6\x82\x7B\x03\xA0\x00\x00\x00", "HP/UX Service" },
  { "\x0F\x88\x9D\xA1\xFC\x05\x3B\x4D\xA0\x06\x74\x3F\x0F\x84\x91\x1E", "Linux RAID" },
  { "\x6D\xFD\x57\x06\xAB\xA4\xC4\x43\x84\xE5\x09\x33\xC8\x4B\x4F\x4F", "Linux Swap" },
  { "\x79\xD3\xD6\xE6\x07\xF5\xC2\x44\xA2\x3C\x23\x8F\x2A\x3D\xF9\x28", "Linux LVM" },
  { "\x39\x33\xA6\x8D\x07\x00\xC0\x60\xC4\x36\x08\x3A\xC8\x23\x09\x08", "Linux Reserved" },
  { "\xB4\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", "FreeBSD Data" },
  { "\xB5\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", "FreeBSD Swap" },
  { "\xB6\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", "FreeBSD UFS" },
  { "\xB8\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", "FreeBSD Vinum" },
  { "\x00\x53\x46\x48\x00\x00\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", "Mac HFS+" },
  { 0, 0 }
};

static char * get_name_for_guid(void *guid)
{
  int i;

  for (i = 0; gpt_types[i].name; i++)
    if (memcmp(gpt_types[i].guid, guid, 16) == 0)
      return gpt_types[i].name;
  return "Unknown";
}

void detect_gpt_partmap(SECTION *section, int level)
{
  unsigned char *buf;
  u8 diskblocks, partmap_start, start, end, size;
  u4 partmap_count, partmap_entry_size;
  u4 i;
  char s[256], append[64];
  int last_unused;

  /* partition maps only occur at the start of a device */
  if (section->pos != 0)
    return;

  /* get LBA 1: GPT header */
  if (get_buffer(section, 512, 512, (void **)&buf) < 512)
    return;

  /* check signature */
  if (memcmp(buf, "EFI PART", 8) != 0)
    return;

  /* get header information */
  if (get_le_quad(buf + 0x18) != 1)
    return;
  diskblocks = get_le_quad(buf + 0x20) + 1;
  partmap_start = get_le_quad(buf + 0x48);
  partmap_count = get_le_long(buf + 0x50);
  partmap_entry_size = get_le_long(buf + 0x54);

  print_line(level, "GPT partition map, %d entries", (int)partmap_count);
  format_blocky_size(s, diskblocks, 512, "sectors", NULL);
  print_line(level+1, "Disk size %s", s);
  format_guid(buf + 0x38, s);
  print_line(level+1, "Disk GUID %s", s);

  /* get entries */
  last_unused = 0;
  for (i = 0; i < partmap_count; i++) {
    if (get_buffer(section, (partmap_start * 512) + i * partmap_entry_size, partmap_entry_size, (void **)&buf) < partmap_entry_size)
      return;

    if (memcmp(buf, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16) == 0) {
      if (last_unused == 0)
	print_line(level, "Partition %d: unused", i+1);
      last_unused = 1;
      continue;
    }
    last_unused = 0;

    /* size */
    start = get_le_quad(buf + 0x20);
    end = get_le_quad(buf + 0x28);
    size = end + 1 - start;

    sprintf(append, " from %llu", start);
    format_blocky_size(s, size, 512, "sectors", append);
    print_line(level, "Partition %d: %s", i+1, s);

    /* type */
    format_guid(buf, s);
    print_line(level+1, "Type %s (GUID %s)", get_name_for_guid(buf), s);

    /* partition name */
    format_utf16_le(buf + 0x38, 72, s);
    print_line(level+1, "Partition Name \"%s\"", s);

    /* GUID */
    format_guid(buf + 0x10, s);
    print_line(level+1, "Partition GUID %s", s);

    /* recurse for content detection */
    if (start > 0 && size > 0) {  /* avoid recursion on self */
      analyze_recursive(section, level + 1,
			start * 512, size * 512, 0);
    }
  }
}

/*
 * FAT12/FAT16/FAT32 file systems
 */

static char *fatnames[] = { "FAT12", "FAT16", "FAT32" };

void detect_fat(SECTION *section, int level)
{
  int i, score, fattype;
  u4 sectsize, clustersize, reserved, fatcount, dirsize, fatsize;
  u8 sectcount, clustercount;
  u2 atari_csum;
  unsigned char *buf;
  char s[256];

  if (get_buffer(section, 0, 512, (void **)&buf) < 512)
    return;

  /* first, some hard tests */
  /* sector size has four allowed values */
  sectsize = get_le_short(buf + 11);
  if (sectsize != 512 && sectsize != 1024 &&
      sectsize != 2048 && sectsize != 4096)
    return;
  /* sectors per cluster: must be a power of two */
  clustersize = buf[13];
  if (clustersize == 0 || (clustersize & (clustersize - 1)))
    return;
  /* since the above is also present on NTFS, make sure it's not NTFS... */
  if (memcmp(buf + 3, "NTFS    ", 8) == 0)
    return;

  /* next, some soft tests, taking score */
  score = 0;

  /* boot jump */
  if ((buf[0] == 0xEB && buf[2] == 0x90) || 
      buf[0] == 0xE9)
    score++;
  /* boot signature */
  if (buf[510] == 0x55 && buf[511] == 0xAA)
    score++;
  /* reserved sectors */
  reserved = get_le_short(buf + 14);
  if (reserved == 1 || reserved == 32)
    score++;
  /* number of FATs */
  fatcount = buf[16];
  if (fatcount == 2)
    score++;
  /* number of root dir entries */
  dirsize = get_le_short(buf + 17);
  /* sector count (16-bit and 32-bit versions) */
  sectcount = get_le_short(buf + 19);
  if (sectcount == 0)
    sectcount = get_le_long(buf + 32);
  /* media byte */
  if (buf[21] == 0xF0 || buf[21] >= 0xF8)
    score++;
  /* FAT size in sectors */
  fatsize = get_le_short(buf + 22);
  if (fatsize == 0)
    fatsize = get_le_long(buf + 36);

  /* determine FAT type */
  dirsize = ((dirsize * 32) + (sectsize - 1)) / sectsize;
  clustercount = sectcount - (reserved + (fatcount * fatsize) + dirsize);
  clustercount /= clustersize;

  if (clustercount < 4085)
    fattype = 0;
  else if (clustercount < 65525)
    fattype = 1;
  else
    fattype = 2;

  /* check for ATARI ST boot checksum */
  atari_csum = 0;
  for (i = 0; i < 512; i += 2)
    atari_csum += get_be_short(buf + i);

  /* tell the user */
  s[0] = 0;
  if (atari_csum == 0x1234)
    strcpy(s, ", ATARI ST bootable");
  print_line(level, "%s file system (hints score %d of %d%s)",
	     fatnames[fattype], score, 5, s);

  if (sectsize > 512)
    print_line(level + 1, "Unusual sector size %lu bytes", sectsize);

  format_blocky_size(s, clustercount, clustersize * sectsize,
		     "clusters", NULL);
  print_line(level + 1, "Volume size %s", s);

  /* get the cached volume name if present */
  if (fattype < 2) {
    if (buf[38] == 0x29) {
      memcpy(s, buf + 43, 11);
      s[11] = 0;
      for (i = 10; i >= 0 && s[i] == ' '; i--)
	s[i] = 0;
      if (strcmp(s, "NO NAME") != 0)
	print_line(level + 1, "Volume name \"%s\"", s);
    }
  } else {
    if (buf[66] == 0x29) {
      memcpy(s, buf + 71, 11);
      s[11] = 0;
      for (i = 10; i >= 0 && s[i] == ' '; i--)
	s[i] = 0;
      if (strcmp(s, "NO NAME") != 0)
	print_line(level + 1, "Volume name \"%s\"", s);
    }
  }
}

/*
 * NTFS file system
 */

void detect_ntfs(SECTION *section, int level)
{
  u4 sectsize, clustersize;
  u8 sectcount;
  unsigned char *buf;
  char s[256];

  if (get_buffer(section, 0, 512, (void **)&buf) < 512)
    return;

  /* check signatures */
  if (memcmp(buf + 3, "NTFS    ", 8) != 0)
    return;
  /* disabled for now, mkntfs(8) doesn't generate it
  if (memcmp(buf + 0x24, "\x80\x00\x80\x00", 4) != 0)
    return;
  */

  /* sector size: must be a power of two */
  sectsize = get_le_short(buf + 11);
  if (sectsize < 512 || (sectsize & (sectsize - 1)))
    return;
  /* sectors per cluster: must be a power of two */
  clustersize = buf[13];
  if (clustersize == 0 || (clustersize & (clustersize - 1)))
    return;

  /* get size in sectors */
  sectcount = get_le_quad(buf + 0x28);

  /* tell the user */
  print_line(level, "NTFS file system");

  format_blocky_size(s, sectcount, sectsize, "sectors", NULL);
  print_line(level + 1, "Volume size %s", s);
}

/*
 * HPFS file system
 */

void detect_hpfs(SECTION *section, int level)
{
  unsigned char *buf;
  char s[256];
  u8 sectcount;

  if (get_buffer(section, 16*512, 512, (void **)&buf) < 512)
    return;

  if (memcmp(buf, "\xF9\x95\xE8\x49\xFA\x53\xE9\xC5", 8) != 0)
    return;

  print_line(level, "HPFS file system (version %d, functional version %d)",
	     (int)buf[8], (int)buf[9]);

  sectcount = get_le_long(buf + 16);
  format_blocky_size(s, sectcount, 512, "sectors", NULL);
  print_line(level + 1, "Volume size %s", s);

  /* TODO: BPB in boot sector, volume label -- information? */
}

/*
 * DOS/Windows boot loaders
 */

void detect_dos_loader(SECTION *section, int level)
{
  int fill;
  unsigned char *buf;

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

  fill = get_buffer(section, 0, 2048, (void **)&buf);
  if (fill < 512)
    return;

  if (find_memory(buf, fill, "NTLDR", 5) >= 0)
    print_line(level, "Windows NTLDR boot loader");
  else if (find_memory(buf, 512, "WINBOOT SYS", 11) >= 0)
    print_line(level, "Windows 95/98/ME boot loader");
  else if (find_memory(buf, 512, "MSDOS   SYS", 11) >= 0)
    print_line(level, "Windows / MS-DOS boot loader");
}

/* EOF */


syntax highlighted by Code2HTML, v. 0.9.1