/*
 * unix.c
 * Detection of general Unix file systems
 *
 * Copyright (c) 2003 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"

/*
 * JFS (for Linux)
 */

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

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

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

  /* tell the user */
  version = get_le_long(buf + 4);
  print_line(level, "JFS file system, version %d", version);

  get_string(buf + 101, 11, s);
  print_line(level + 1, "Volume name \"%s\"", s);

  blocksize = get_le_long(buf + 24);
  blockcount = get_le_quad(buf + 8);
  format_blocky_size(s, blockcount, blocksize, "h/w blocks", NULL);
  print_line(level + 1, "Volume size %s", s);
}

/*
 * XFS
 */

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

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

  /* check signature */
  if (get_be_long(buf) != 0x58465342)
    return;

  /* tell the user */
  raw_version = get_be_short(buf + 0x64);
  version = raw_version & 0x000f;
  print_line(level, "XFS file system, version %d", version);

  get_string(buf + 0x6c, 12, s);
  print_line(level + 1, "Volume name \"%s\"", s);

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

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

/*
 * UFS file system from various BSD strains
 */

void detect_ufs(SECTION *section, int level)
{
  unsigned char *buf;
  int i, at, en, namelen, offsets[5] = { 0, 8, 64, 256, -1 };
  u4 magic;
  char s[256];

  /* NextStep/OpenStep apparently can move the superblock further into
     the device. Linux uses steps of 8 blocks (of the applicable block
     size) to scan for it. We only scan at the canonical location for now. */
  /* Possible block sizes: 512 (old formats), 1024 (most), 2048 (CD media) */

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

    for (en = 0; en < 2; en++) {
      magic = get_ve_long(en, buf + 1372);

      if (magic == 0x00011954) {
	print_line(level, "UFS file system, %d KiB offset, %s",
		   at, get_ve_name(en));
      } else if (magic == 0x00095014) {
	print_line(level, "UFS file system, %d KiB offset, long file names, %s",
		   at, get_ve_name(en));
      } else if (magic == 0x00195612) {
	print_line(level, "UFS file system, %d KiB offset, fs_featurebits, %s",
		   at, get_ve_name(en));
      } else if (magic == 0x05231994) {
	print_line(level, "UFS file system, %d KiB offset, fs_featurebits, >4GB support, %s",
		   at, get_ve_name(en));
      } else if (magic == 0x19540119) {
	print_line(level, "UFS2 file system, %d KiB offset, %s",
		   at, get_ve_name(en));
      } else
	continue;

      /* volume name by FreeBSD convention */
      get_string(buf + 680, 32, s);
      if (s[0])
	print_line(level + 1, "Volume name \"%s\" (in superblock)", s);

      /* last mount point */
      get_string(buf + 212, 255, s);  /* actually longer, but varies */
      if (s[0])
	print_line(level + 1, "Last mounted at \"%s\"", s);

      /* volume name by Darwin convention */
      if (get_buffer(section, 7 * 1024, 1024, (void **)&buf) == 1024) {
	if (get_ve_long(en, buf) == 0x4c41424c &&  /* "LABL" */
	    get_ve_long(en, buf + 8) == 1) {       /* version 1 */
	  namelen = get_ve_short(en, buf + 16);
	  get_string(buf + 18, namelen, s);  /* automatically limits to 255 */
	  print_line(level + 1, "Volume name \"%s\" (in label v%lu)",
		     s, get_ve_long(en, buf + 8));
	}
      }

      return;
    }
  }
}

/*
 * System V file system
 */

void detect_sysv(SECTION *section, int level)
{
  unsigned char *buf;
  int i, at, en, offsets[5] = { 512, 1024, -1 };
  s4 blocksize_code;
  char s[256];

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

    for (en = 0; en < 2; en++) {
      if (get_ve_long(en, buf + 1016) == 0x2b5544) {
	blocksize_code = get_ve_long(en, buf + 1020);
	s[0] = 0;
	if (blocksize_code == 1)
	  strcpy(s, "512 byte blocks");
	else if (blocksize_code == 2)
	  strcpy(s, "1 KiB blocks");
	else if (blocksize_code == 3)
	  strcpy(s, "2 KiB blocks");
	else
	  snprintf(s, 255, "unknown block size code %d", (int)blocksize_code);

	print_line(level, "XENIX file system (SysV variant), %s, %s",
		   get_ve_name(en), s);
	return;
      }

      if (get_ve_long(en, buf + 504) == 0xfd187e20) {
	blocksize_code = get_ve_long(en, buf + 508);
	s[0] = 0;
	if (blocksize_code == 1)
	  strcpy(s, "512 byte blocks");
	else if (blocksize_code == 2)
	  strcpy(s, "1 KiB blocks");
	else
	  snprintf(s, 255, "unknown block size code %d", (int)blocksize_code);

	print_line(level, "SysV file system, %s, %s",
		   get_ve_name(en), s);
	return;
      }
    }
  }
}

/*
 * BSD disklabel
 */

static char * bsdtype_names[] = {
  "Unused",
  "swap",
  "Sixth Edition",
  "Seventh Edition",
  "System V",
  "V7 with 1 KiB blocks",
  "Eighth Edition, 4 KiB blocks",
  "4.2BSD fast file system",
  "ext2 or MS-DOS",
  "4.4BSD log-structured file system",
  "\"Other\"",
  "HPFS",
  "ISO9660",
  "bootstrap",
  "AmigaDOS fast file system",
  "Macintosh HFS",
  "Digital Unix AdvFS",
};

static char * get_name_for_bsdtype(int type)
{
  if (type >= 0 && type <= 16)
    return bsdtype_names[type];
  return "Unknown";
}

void detect_bsd_disklabel(SECTION *section, int level)
{
  unsigned char *buf;
  int i, off, partcount, types[16], min_offset_valid, did_recurse;
  u4 starts[16], sizes[16];
  u4 sectsize, nsectors, ntracks, ncylinders, secpercyl, secperunit;
  u8 offset, min_offset, base_offset;
  char s[256], append[64], pn;

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

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

  if (get_le_long(buf)       != 0x82564557 ||
      get_le_long(buf + 132) != 0x82564557)
    return;

  sectsize = get_le_long(buf + 40);
  nsectors = get_le_long(buf + 44);
  ntracks = get_le_long(buf + 48);
  ncylinders = get_le_long(buf + 52);
  secpercyl = get_le_long(buf + 56);
  secperunit = get_le_long(buf + 60);

  partcount = get_le_short(buf + 138);

  if (partcount <= 8) {
    print_line(level, "BSD disklabel (at sector 1), %d partitions", partcount);
  } else if (partcount > 8 && partcount <= 16) {
    print_line(level, "BSD disklabel (at sector 1), %d partitions (more than usual, but valid)",
	       partcount);
  } else if (partcount > 16) {
    print_line(level, "BSD disklabel (at sector 1), %d partitions (broken, limiting to 16)",
	       partcount);
    partcount = 16;
  }
  if (sectsize != 512) {
    print_line(level + 1, "Unusual sector size %d bytes, your mileage may vary");
  }

  min_offset = 0;
  min_offset_valid = 0;
  for (i = 0, off = 148; i < partcount; i++, off += 16) {
    starts[i] = get_le_long(buf + off + 4);
    sizes[i] = get_le_long(buf + off);
    types[i] = buf[off + 12];

    if (types[i] != 0 || i == 2) {
      offset = (u8)starts[i] * 512;
      if (!min_offset_valid || offset < min_offset) {
	min_offset = offset;
	min_offset_valid = 1;
      }
    }
  }
  /* if min_offset_valid is still 0, the default of min_offset=0 is okay */

  if (section->pos == min_offset) {
    /* either min_offset is zero, or we were analyzing the whole disk */
    base_offset = section->pos;
  } else if (section->pos == 0) {
    /* are we analyzing the slice alone? */
    print_line(level + 1, "Adjusting offsets for disklabel in a DOS partition at sector %llu", min_offset >> 9);
    base_offset = min_offset;
  } else if (min_offset == 0) {
    /* assume relative offsets after all */
    base_offset = 0;
  } else {
    print_line(level + 1, "Warning: Unable to adjust offsets, your mileage may vary");
    base_offset = section->pos;
  }

  /* loop over partitions: print and analyze */
  did_recurse = 0;
  for (i = 0; i < partcount; i++) {
    pn = 'a' + i;
    if (types[i] == 0 && i != 2)
      continue;

    sprintf(append, " from %lu", starts[i]);
    format_blocky_size(s, sizes[i], 512, "sectors", append);
    print_line(level, "Partition %c: %s",
	       pn, s);

    print_line(level + 1, "Type %d (%s)",
	       types[i], get_name_for_bsdtype(types[i]));

    if (types[i] == 0 || sizes[i] == 0)
      continue;

    offset = (u8)starts[i] * 512;
    if (offset < base_offset) {
      print_line(level + 1, "(Illegal start offset, no detection)");
    } else if (offset == base_offset) {
      print_line(level + 1, "Includes the disklabel and boot code");

      /* recurse for content detection, but carefully */
      analyze_recursive(section, level + 1,
			offset - base_offset, (u8)sizes[i] * 512,
			FLAG_IN_DISKLABEL);
      did_recurse = 1;
    } else {
      /* recurse for content detection */
      analyze_recursive(section, level + 1,
			offset - base_offset, (u8)sizes[i] * 512,
			0);
    }
  }

  if (did_recurse)
    stop_detect();  /* don't run other detectors; we already did that
		       for an overlapping partition. */
}

/*
 * FreeBSD boot loader (?)
 */

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

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

  if (get_buffer(section, 0, 512, (void **)&buf) == 512) {
    if (get_le_short(buf + 0x1b0) == 0xbb66) {
      print_line(level, "FreeBSD boot manager (i386 boot0 at sector 0)");
    } else if (get_le_long(buf + 0x1f6) == 0 &&
	       get_le_long(buf + 0x1fa) == 50000 &&
	       get_le_short(buf + 0x1fe) == 0xaa55) {
      print_line(level, "FreeBSD boot loader (i386 boot1 at sector 0)");
    }
  }

  if (get_buffer(section, 1024, 512, (void **)&buf) == 512) {
    if (memcmp(buf + 2, "BTX", 3) == 0) {
      print_line(level, "FreeBSD boot loader (i386 boot2/BTX %d.%02d at sector 2)",
		 (int)buf[5], (int)buf[6]);
    }
  }
}

/*
 * Solaris SPARC disklabel
 */

void detect_solaris_disklabel(SECTION *section, int level)
{
  unsigned char *buf;
  int i, off1, off2, types[8], did_recurse;
  u4 sizes[8];
  u8 starts[8], cylsize, offset;
  char s[256], append[256], pn;

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

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

  if (get_be_short(buf + 508) != 0xDABE)
    return;

  print_line(level, "Solaris SPARC disklabel");

  cylsize = (u8)get_be_short(buf + 436) * (u8)get_be_short(buf + 438);
  for (i = 0, off1 = 142, off2 = 444; i < 8; i++, off1 += 4, off2 += 8) {
    types[i] = get_be_short(buf + off1);
    starts[i] = get_be_long(buf + off2) * cylsize;
    sizes[i] = get_be_long(buf + off2 + 4);
  }

  /* loop over partitions: print and analyze */
  did_recurse = 0;
  for (i = 0; i < 8; i++) {
    pn = '0' + i;
    if (sizes[i] == 0)
      continue;

    sprintf(append, " from %llu", starts[i]);
    format_blocky_size(s, sizes[i], 512, "sectors", append);
    print_line(level, "Partition %c: %s", pn, s);

    print_line(level + 1, "Type %d",
               types[i]);

    offset = starts[i] * 512;
    if (offset == 0) {
      print_line(level + 1, "Includes the disklabel");

      /* recurse for content detection, but carefully */
      analyze_recursive(section, level + 1,
                        offset, (u8)sizes[i] * 512,
                        FLAG_IN_DISKLABEL);
      did_recurse = 1;
    } else {
      /* recurse for content detection */
      analyze_recursive(section, level + 1,
                        offset, (u8)sizes[i] * 512,
                        0);
    }
  }

  if (did_recurse)
    stop_detect();  /* don't run other detectors; we already did that
                       for the first partition, which overlaps with
                       the disklabel itself. */
}

/*
 * Solaris x86 vtoc
 */

static char * vtoctype_names[] = {
  "Unused",
  "Boot",
  "Root",
  "Swap",
  "Usr",
  "Overlap",
  "Stand",
  "Var",
  "Home",
  "Alternate sector",
  "Cache"
};

static char * get_name_for_vtoctype(int type)
{
  if (type >= 0 && type <= 10)
    return vtoctype_names[type];
  return "Unknown";
}

void detect_solaris_vtoc(SECTION *section, int level)
{
  unsigned char *buf;
  int i, off, partcount, sectorsize, types[16], did_recurse;
  u4 starts[16], sizes[16];
  u4 version;
  u8 offset;
  char s[256], append[64];

  if (section->flags & FLAG_IN_DISKLABEL)
    return;

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

  if (get_le_long(buf + 12) != 0x600DDEEE)
    return;
  version = get_le_long(buf + 16);
  if (version != 1) {
    print_line(level, "Solaris x86 disklabel, unknown version %lu", version);
    return;
  }
  partcount = get_le_short(buf + 30);
  if (partcount > 16) {
    print_line(level, "Solaris x86 disklabel, version 1, %d partitions (limiting to 16)",
	       partcount);
    partcount = 16;
  } else {
    print_line(level, "Solaris x86 disklabel, version 1, %d partitions",
	       partcount);
  }

  sectorsize = get_le_short(buf + 28);
  if (sectorsize != 512)
    print_line(level + 1, "Unusual sector size %d bytes, your mileage may vary",
	       sectorsize);

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

  for (i = 0, off = 72; i < partcount; i++, off += 12) {
    types[i] = get_le_short(buf + off);
    starts[i] = get_le_long(buf + off + 4);
    sizes[i] = get_le_long(buf + off + 8);
  }

  /* loop over partitions: print and analyze */
  did_recurse = 0;
  for (i = 0; i < partcount; i++) {
    if (sizes[i] == 0)
      continue;

    sprintf(append, " from %lu", starts[i]);
    format_blocky_size(s, sizes[i], 512, "sectors", append);
    print_line(level, "Partition %d: %s",
	       i, s);

    print_line(level + 1, "Type %d (%s)",
	       types[i], get_name_for_vtoctype(types[i]));

    offset = (u8)starts[i] * 512;
    if (offset == 0) {
      print_line(level + 1, "Includes the disklabel");

      /* recurse for content detection, but carefully */
      analyze_recursive(section, level + 1,
			offset, (u8)sizes[i] * 512,
			FLAG_IN_DISKLABEL);
      did_recurse = 1;
    } else {
      /* recurse for content detection */
      analyze_recursive(section, level + 1,
			offset, (u8)sizes[i] * 512,
			0);
    }
  }

  if (did_recurse)
    stop_detect();  /* don't run other detectors; we already did that
		       for an overlapping partition. */
}

/*
 * QNX4 file system
 */

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

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

  /* check signature */
  if (get_le_long(buf) != 0x0000002f)
    return;
  /* NOTE: This is actually the string "/", the file name of the root
     directory. QNX4 fs does not have a real superblock, just an
     aggregate of 4 inodes for certain special files. */

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

/*
 * Veritas VxFS
 */

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

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

  /* check signature */
  for (en = 0; en < 2; en++) {
    if (get_ve_long(en, buf) == 0xA501FCF5) {
      version = get_ve_long(en, buf + 4);
      print_line(level, "Veritas VxFS file system, version %d, %s",
		 version, get_ve_name(en));

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

/* EOF */


syntax highlighted by Code2HTML, v. 0.9.1