/*
 * linux.c
 * Detection of Linux file systems and boot codes
 *
 * 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"

/*
 * ext2/ext3 file system
 */

void detect_ext23(SECTION *section, int level)
{
  unsigned char *buf;
  char s[256];
  u4 blocksize;
  u8 blockcount;

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

  if (get_le_short(buf + 56) == 0xEF53) {
    if (get_le_long(buf + 96) & 0x0008)       /* JOURNAL_DEV flag */
      print_line(level, "Ext3 external journal");
    else if (get_le_long(buf + 92) & 0x0004)  /* HAS_JOURNAL flag */
      print_line(level, "Ext3 file system");
    else
      print_line(level, "Ext2 file system");

    get_string(buf + 120, 16, s);
    if (s[0])
      print_line(level + 1, "Volume name \"%s\"", s);

    format_uuid(buf + 104, s);
    print_line(level + 1, "UUID %s", s);

    get_string(buf + 136, 64, s);
    if (s[0])
      print_line(level + 1, "Last mounted at \"%s\"", s);

    blocksize = 1024 << get_le_long(buf + 24);
    blockcount = get_le_long(buf + 4);
    format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
    print_line(level + 1, "Volume size %s", s);

    /* 76 4 s_rev_level */
    /* 62 2 s_minor_rev_level */
    /* 72 4 s_creator_os */
    /* 92 3x4 s_feature_compat, s_feature_incompat, s_feature_ro_compat */
  }
}

/*
 * ReiserFS file system
 */

void detect_reiser(SECTION *section, int level)
{
  unsigned char *buf;
  int i, at, newformat;
  int offsets[3] = { 8, 64, -1 };
  char s[256];
  u8 blockcount;
  u4 blocksize;

  for (i = 0; offsets[i] >= 0; i++) {
    at = offsets[i];
    if (get_buffer(section, at * 1024, 1024, (void **)&buf) < 1024)
      continue;

    /* check signature */
    if (memcmp(buf + 52, "ReIsErFs", 8) == 0) {
      print_line(level, "ReiserFS file system (old 3.5 format, standard journal, starts at %d KiB)", at);
      newformat = 0;
    } else if (memcmp(buf + 52, "ReIsEr2Fs", 9) == 0) {
      print_line(level, "ReiserFS file system (new 3.6 format, standard journal, starts at %d KiB)", at);
      newformat = 1;
    } else if (memcmp(buf + 52, "ReIsEr3Fs", 9) == 0) {
      newformat = get_le_short(buf + 72);
      if (newformat == 0) {
	print_line(level, "ReiserFS file system (old 3.5 format, non-standard journal, starts at %d KiB)", at);
      } else if (newformat == 2) {
	print_line(level, "ReiserFS file system (new 3.6 format, non-standard journal, starts at %d KiB)", at);
	newformat = 1;
      } else {
	print_line(level, "ReiserFS file system (v3 magic, but unknown version %d, starts at %d KiB)", newformat, at);
	continue;
      }
    } else
      continue;

    /* get data */
    blockcount = get_le_long(buf);
    blocksize = get_le_short(buf + 44);
    /* for new format only:
       hashtype = get_le_long(buf + 64);
    */

    /* get label */
    get_string(buf + 100, 16, s);
    if (s[0])
      print_line(level + 1, "Volume name \"%s\"", s);

    format_uuid(buf + 84, s);
    print_line(level + 1, "UUID %s", s);

    /* print size */
    format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
    print_line(level + 1, "Volume size %s", s);

    /* TODO: print hash code */
  }
}

/*
 * Reiser4 file system
 */

void detect_reiser4(SECTION *section, int level)
{
  unsigned char *buf;
  char s[256];
  int layout_id;
  char layout_name[64];
  u4 blocksize;
  u8 blockcount;

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

  /* check signature */
  if (memcmp(buf, "ReIsEr4", 7) != 0)
    return;

  /* get data from master superblock */
  layout_id = get_le_short(buf + 16);
  blocksize = get_le_short(buf + 18);
  if (layout_id == 0)
    strcpy(layout_name, "4.0 layout");
  else
    sprintf(layout_name, "Unknown layout with ID %d", layout_id);

  format_size(s, blocksize);
  print_line(level, "Reiser4 file system (%s, block size %s)",
	     layout_name, s);

  /* get label and UUID */
  get_string(buf + 36, 16, s);
  if (s[0])
    print_line(level + 1, "Volume name \"%s\"", s);

  format_uuid(buf + 20, s);
  print_line(level + 1, "UUID %s", s);

  if (layout_id == 0) {
    /* read 4.0 superblock */
    if (get_buffer(section, 17 * 4096, 1024, (void **)&buf) < 1024)
      return;
    if (memcmp(buf + 52, "ReIsEr40FoRmAt", 14) != 0) {
      print_line(level + 1, "Superblock for 4.0 format missing");
      return;
    }

    blockcount = get_le_quad(buf);
    format_blocky_size(s, blockcount, blocksize, "blocks", NULL);
    print_line(level + 1, "Volume size %s", s);
  }
}

/*
 * Linux RAID persistent superblock
 */

static char *levels[] = {
  "Multipath",
  "\'HSM\'",
  "\'translucent\'",
  "Linear",
  "RAID0",
  "RAID1",
  NULL, NULL,
  "RAID4(?)",
  "RAID5"
};

void detect_linux_raid(SECTION *section, int level)
{
  unsigned char *buf;
  u8 pos;
  int rlevel, nr_disks, raid_disks, spare;
  u1 uuid[16];
  char s[256];

  /* don't do this if:
   *  - the size is unknown (0)
   *  - the size is too small for the calculation
   *  - it is inefficient to read from the end of the source
   */
  if (section->size < 65536 || section->source->sequential)
    return;

  /* get RAID superblock from the end of the device */
  pos = (section->size & ~65535) - 65536;
  if (get_buffer(section, pos, 4096, (void **)&buf) < 4096)
    return;

  /* signature */
  if (get_le_long(buf) != 0xa92b4efc)
    return;

  print_line(level, "Linux RAID disk, version %lu.%lu.%lu",
	     get_le_long(buf + 4), get_le_long(buf + 8),
	     get_le_long(buf + 12));

  /* get some data */
  rlevel = (int)(long)get_le_long(buf + 28);   /* is signed, actually */
  nr_disks = get_le_long(buf + 36);
  raid_disks = get_le_long(buf + 40);
  spare = nr_disks - raid_disks;

  /* find the name for the personality in the table */
  if (rlevel < -4 || rlevel > 5 || levels[rlevel+4] == NULL) {
    print_line(level + 1, "Unknown RAID level %d using %d regular %d spare disks",
	       rlevel, raid_disks, spare);
  } else {
    print_line(level + 1, "%s set using %d regular %d spare disks",
	       levels[rlevel+4], raid_disks, spare);
  }

  /* get the UUID */
  memcpy(uuid, buf + 5*4, 4);
  memcpy(uuid + 4, buf + 13*4, 3*4);
  format_uuid(uuid, s);
  print_line(level + 1, "RAID set UUID %s", s);
}

/*
 * Linux LVM1
 */

void detect_linux_lvm(SECTION *section, int level)
{
  unsigned char *buf;
  char s[256];
  int minor_version, pv_number;
  u8 pe_size, pe_count, pe_start;

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

  /* signature */
  if (buf[0] != 'H' || buf[1] != 'M')
    return;
  /* helpful sanity check... */
  if (get_le_long(buf + 36) == 0 || get_le_long(buf + 40) == 0)
    return;

  minor_version = get_le_short(buf + 2);
  print_line(level, "Linux LVM1 volume, version %d%s",
	     minor_version,
	     (minor_version < 1 || minor_version > 2) ? " (unknown)" : "");

  /* volume group name */
  get_string(buf + 172, 128, s);
  print_line(level + 1, "Volume group name \"%s\"", s);

  /* "UUID" of this physical volume */
  format_uuid_lvm(buf + 0x2c, s);
  print_line(level + 1, "PV UUID %s", s);

  /* number of this physical volume */
  pv_number = get_le_long(buf + 432);
  print_line(level + 1, "PV number %d", pv_number);

  /* volume size */
  pe_size = get_le_long(buf + 452);
  pe_count = get_le_long(buf + 456);
  format_blocky_size(s, pe_count, pe_size * 512, "PEs", NULL);
  print_line(level + 1, "Useable size %s", s);

  /* get start of first PE */
  if (minor_version == 1) {
    /* minor format 1: first PE starts after the declared length of the PE tables */
    pe_start = get_le_long(buf + 36) + get_le_long(buf + 40);
  } else if (minor_version == 2) {
    /* minor format 2: a field in the header indicates this */
    pe_start = get_le_long(buf + 464) << 9;
  } else {
    /* unknown minor format */
    pe_start = 0;
  }

  /* try to detect from first PE */
  if (pe_start > 0) {
    analyze_recursive(section, level + 1,
		      pe_start, 0, 0);
    /* TODO: elaborate on this by reading the PE allocation map */
  }
}

/*
 * Linux LVM2
 */

void detect_linux_lvm2(SECTION *section, int level)
{
  unsigned char *buf;
  int at, i;
  char s[256];
  u8 labelsector;
  u4 labeloffset;
  u8 pvsize, mdoffset, mdsize;
  int mda_version;

  for (at = 0; at < 4; at++) {
    if (get_buffer(section, at * 512, 512, (void **)&buf) < 512)
      continue;

    /* check signature */
    if (memcmp(buf, "LABELONE", 8) != 0)
      continue;

    labelsector = get_le_quad(buf + 8);
    labeloffset = get_le_long(buf + 20);

    if (memcmp(buf + 24, "LVM2 001", 8) != 0) {
      get_string(buf + 24, 8, s);
      print_line(level, "LABELONE label at sector %d, unknown type \"%s\"",
		 at, s);
      return;
    }

    print_line(level, "Linux LVM2 volume, version 001");
    print_line(level + 1, "LABELONE label at sector %d",
	       at);

    if (labeloffset >= 512 || labelsector > 256 ||
	labelsector != at) {
      print_line(level + 1, "LABELONE data inconsistent, aborting analysis");
      return;
    }

    /* "UUID" of this physical volume */
    format_uuid_lvm(buf + labeloffset, s);
    print_line(level + 1, "PV UUID %s", s);

    /* raw volume size */
    pvsize = get_le_quad(buf + labeloffset + 32);
    format_size_verbose(s, pvsize);
    print_line(level + 1, "Volume size %s", s);

    /* find first metadata area in list */
    mdoffset = 0;
    for (i = 0; i < 16; i++)
      if (get_le_quad(buf + labeloffset + 40 + i * 16) == 0) {
	i++;
	mdoffset = get_le_quad(buf + labeloffset + 40 + i * 16);
	mdsize = get_le_quad(buf + labeloffset + 40 + i * 16 + 8);
	break;
      }
    if (mdoffset == 0)
      return;

    if (get_buffer(section, mdoffset, mdsize, (void **)&buf) < mdsize)
      return;

    if (memcmp(buf + 4, " LVM2 x[5A%r0N*>", 16) != 0)
      return;
    mda_version = get_le_long(buf + 20);

    print_line(level + 1, "Meta-data version %d", mda_version);

    /* TODO: parse the metadata area (big task...) */

    return;
  }
}

/*
 * Linux swap area
 */

void detect_linux_swap(SECTION *section, int level)
{
  int i, en, pagesize;
  unsigned char *buf;
  u4 version, pages;
  char s[256];
  int pagesizes[] = { 4096, 8192, 0 };

  for (i = 0; pagesizes[i]; i++) {
    pagesize = pagesizes[i];

    if (get_buffer(section, pagesize - 512, 512, (void **)&buf) != 512)
      break;  /* assumes page sizes increase through the loop */

    if (memcmp((char *)buf + 512 - 10, "SWAP-SPACE", 10) == 0) {
      print_line(level, "Linux swap, version 1, %d KiB pages",
		 pagesize >> 10);
    }
    if (memcmp((char *)buf + 512 - 10, "SWAPSPACE2", 10) == 0) {
      if (get_buffer(section, 1024, 512, (void **)&buf) != 512)
	break;  /* really shouldn't happen */

      for (en = 0; en < 2; en++) {
	version = get_ve_long(en, buf);
	if (version >= 1 && version < 10)
	  break;
      }
      if (en < 2) {
	print_line(level, "Linux swap, version 2, subversion %d, %d KiB pages, %s",
		   (int)version, pagesize >> 10, get_ve_name(en));
	if (version == 1) {
	  pages = get_ve_long(en, buf + 4) - 1;
	  format_blocky_size(s, pages, pagesize, "pages", NULL);
	  print_line(level + 1, "Swap size %s", s);
	}
      } else {
	print_line(level, "Linux swap, version 2, illegal subversion, %d KiB pages",
		   pagesize >> 10);
      }
    }
  }
}

/*
 * various file systems
 */

void detect_linux_misc(SECTION *section, int level)
{
  int magic, fill, off, en;
  unsigned char *buf;
  char s[256];
  u8 size, blocks, blocksize;

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

  /* minix file system */
  if (fill >= 2048) {
    int version = 0, namesize = 14;

    magic = get_le_short(buf + 1024 + 16);
    if (magic == 0x137F)
      version = 1;
    if (magic == 0x138F) {
      version = 1;
      namesize = 30;
    }
    if (magic == 0x2468)
      version = 2;
    if (magic == 0x2478) {
      version = 2;
      namesize = 30;
    }
    if (version) {
      print_line(level, "Minix file system (v%d, %d chars)",
		 version, namesize);
      if (version == 1)
	blocks = get_le_short(buf + 1024 + 2);
      else
	blocks = get_le_long(buf + 1024 + 20);
      blocks = (blocks - get_le_short(buf + 1024 + 8))
	<< get_le_short(buf + 1024 + 10);
      format_blocky_size(s, blocks, 1024, "blocks", NULL);
      print_line(level + 1, "Volume size %s", s);
    }
  }

  /* Linux romfs */
  if (memcmp(buf, "-rom1fs-", 8) == 0) {
    size = get_be_long(buf + 8);
    print_line(level, "Linux romfs");
    print_line(level+1, "Volume name \"%.300s\"", (char *)(buf + 16));
    format_size_verbose(s, size);
    print_line(level+1, "Volume size %s", s);
  }

  /* Linux cramfs */
  for (off = 0; off <= 512; off += 512) {
    if (fill < off + 512)
      break;
    for (en = 0; en < 2; en++) {
      if (get_ve_long(en, buf + off) == 0x28cd3d45) {
	print_line(level, "Linux cramfs, starts sector %d, %s",
		   off >> 9, get_ve_name(en));

	get_string(buf + off + 48, 16, s);
	print_line(level + 1, "Volume name \"%s\"", s);

	size = get_ve_long(en, buf + off + 4);
	blocks = get_ve_long(en, buf + off + 40);
	format_size_verbose(s, size);
	print_line(level + 1, "Compressed size %s", s);
	format_blocky_size(s, blocks, 4096, "blocks", " -assumed-");
	print_line(level + 1, "Data size %s", s);
      }
    }
  }

  /* Linux squashfs */
  for (en = 0; en < 2; en++) {
    if (get_ve_long(en, buf) == 0x73717368) {
      int major, minor;

      major = get_ve_short(en, buf + 28);
      minor = get_ve_short(en, buf + 30);
      print_line(level, "Linux squashfs, version %d.%d, %s",
		 major, minor, get_ve_name(en));

      if (major > 2)
	size = get_ve_quad(en, buf + 63);
      else
	size = get_ve_long(en, buf + 8);
      if (major > 1)
	blocksize = get_ve_long(en, buf + 51);
      else
	blocksize = get_ve_short(en, buf + 32);

      format_size_verbose(s, size);
      print_line(level + 1, "Compressed size %s", s);
      format_size(s, blocksize);
      print_line(level + 1, "Block size %s", s);
    }
  }
}

/*
 * various boot code signatures
 */

void detect_linux_loader(SECTION *section, int level)
{
  int fill, executable, id;
  unsigned char *buf;

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

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

  executable = (get_le_short(buf + 510) == 0xaa55) ? 1 : 0;

  /* boot sector stuff */
  if (executable && (memcmp(buf + 2, "LILO", 4) == 0 ||
		     memcmp(buf + 6, "LILO", 4) == 0))
    print_line(level, "LILO boot loader");
  if (executable && memcmp(buf + 3, "SYSLINUX", 8) == 0)
    print_line(level, "SYSLINUX boot loader");
  if (fill >= 1024 && find_memory(buf, fill, "ISOLINUX", 8) >= 0)
    print_line(level, "ISOLINUX boot loader");

  /* we know GRUB a little better... */
  if (executable &&
      find_memory(buf, 512, "Geom\0Hard Disk\0Read\0 Error\0", 27) >= 0) {
    if (buf[0x3e] == 3) {
      print_line(level, "GRUB boot loader, compat version %d.%d, boot drive 0x%02x",
		 (int)buf[0x3e], (int)buf[0x3f], (int)buf[0x40]);
    } else if (executable && buf[0x1bc] == 2 && buf[0x1bd] <= 2) {
      id = buf[0x3e];
      if (id == 0x10) {
	print_line(level, "GRUB boot loader, compat version %d.%d, normal version",
		   (int)buf[0x1bc], (int)buf[0x1bd]);
      } else if (id == 0x20) {
	print_line(level, "GRUB boot loader, compat version %d.%d, LBA version",
		   (int)buf[0x1bc], (int)buf[0x1bd]);
      } else {
	print_line(level, "GRUB boot loader, compat version %d.%d",
		   (int)buf[0x1bc], (int)buf[0x1bd]);
      }
    } else {
      print_line(level, "GRUB boot loader, unknown compat version %d",
		 buf[0x3e]);
    }
  }

  /* Linux kernel loader */
  if (fill >= 1024 && memcmp(buf + 512 + 2, "HdrS", 4) == 0) {
    print_line(level, "Linux kernel build-in loader");
  }

  /* Debian install floppy splitter */
  /* (not exactly boot code, but should be detected before gzip/tar) */
  if (memcmp(buf, "Floppy split ", 13) == 0) {
    char *name = (char *)buf + 32;
    char *number = (char *)buf + 164;
    char *total = (char *)buf + 172;
    print_line(level, "Debian floppy split, name \"%s\", disk %s of %s",
	       name, number, total);
  }
}

/* EOF */


syntax highlighted by Code2HTML, v. 0.9.1