/* $Id: mbr.c,v 1.19 2001/05/30 15:47:03 harbourn Exp $ 
 * MBR processing module for fatback 
 */

/* the goal of this module is to provide an abstract way of reading partitions.
 * one highlight is the linear partition numbering scheme (described in mbr.h)
 */
#include <stdlib.h>
#include "mbr.h"
#include "output.h"
#include "input.h"
#include "util.h"
#include "fatback.h"
#include "sig.h"
#include "vars.h"

struct ptable_list_s {
     struct ptable_list_s *next;
     struct ptable_entry *table;
     unsigned long offset;
};

struct ptable_entry {
     unsigned boot_indicator;
     unsigned start_head;
     unsigned start_cyl;
     unsigned start_sect;
     unsigned sys_indicator;
     unsigned end_head;
     unsigned end_cyl;
     unsigned end_sect;
     unsigned long offset;
     unsigned long sectors;
};

static struct ptable_list_s *Ptable_list;

static struct ptable_list_s *build_ptable(off_t);
static int read_ptable(off_t, struct ptable_entry *);
static int scheck_ptable(struct ptable_entry *, sig_t);
static int scheck_boot_indicator(struct ptable_entry *);
static int scheck_sys_indicator(struct ptable_entry *);
static int scheck_part_range(struct part_range_s *);
static int is_part(struct ptable_entry *);
static int is_extended_part(struct ptable_entry *);
static struct ptable_list_s *find_ext_pentry(off_t, struct ptable_entry *);
static int part_count(struct ptable_list_s *);

static const int NUM_PTABLE_ENTRIES = 4;

enum { NON_BOOT = 0x00,
       BOOTABLE = 0x80
};

/*
 * This is the main initialization interface to this module.
 * it reads in the partition tables recursively and sets the 
 * Sector_size variable for the module
 */
int map_partitions(void)
{
     if (!(Ptable_list = build_ptable((off_t)0))) {
          free(Ptable_list);
          display(VERBOSE, "Unable to map partitions\n");
          return 0;
     } else
          return part_count(Ptable_list);
}  

/*
 * returns a structure containing the beginning and ending
 * offsets of a partition.  Note: offsets are byte offsets
 * not sector numbers.
 */
struct part_range_s *get_prange(int part_num)
{
     int i = 0, j;
     fbvar_t *sectsize_var;
     unsigned sectsize;
     struct ptable_entry *entry = NULL;
     struct ptable_list_s *pnode, *matching_node = NULL;
     struct part_range_s *prange = emalloc(sizeof *prange);
     /* see mbr.h for a note about the partition numbering convention */
     
     /* retrieve the sector size from the fatback variable table */
     sectsize_var = get_fbvar("sectsize");
     if (!sectsize_var->val.ival) {
          display(NORMAL, "Error: sectsize set to 0!\n");
          free(sectsize_var);
          return NULL;
     }
     sectsize = sectsize_var->val.ival;
     free(sectsize_var);

     /* find the matching partition for the given part_num */
     for (pnode = Ptable_list; pnode && !entry; pnode = pnode->next) {
          for (j = 0; (j < NUM_PTABLE_ENTRIES) && !entry; j++) {
               struct ptable_entry *part = &pnode->table[j];
               if (is_part(part) && !is_extended_part(part)) {
                    if (i == part_num) {
                         matching_node = pnode;
                         entry = part;
                    } else
                         i++;
               }
          }
     }

     /* if a matching partition for part_num was found, 
      * compute the appropriate values */
     if (entry) {
          prange->start = (matching_node->offset + entry->offset);
          prange->start *= sectsize;
          prange->end = prange->start;
          prange->end += (entry->sectors * sectsize);
     }
     return entry? prange : NULL;
}     

/* 
 * Find out how many partitions have been maped
 */
static int part_count(struct ptable_list_s *pnode)
{
     int i, ptotal = 0;

     if (!pnode || !pnode->table)
          return 0;
     for (i = 0; i < NUM_PTABLE_ENTRIES; i++)
          if (is_part(&pnode->table[i]) && !is_extended_part(&pnode->table[i]))
               ptotal++;

     return ptotal + part_count(pnode->next);
}

/*
 * Find the extended partition entry in a partition table
 * and build the partition if it exists.
 */
static struct ptable_list_s *
find_ext_pentry(off_t offset, struct ptable_entry *table)
{
     int i;
     fbvar_t *sectsize_var;
     unsigned sectsize;

     /* get the sector size from the fatback variable table */
     sectsize_var = get_fbvar("sectsize");
     if (!sectsize_var->val.ival) {
          display(NORMAL, "Error: sectsize set to 0!\n");
          free(sectsize_var);
          return 0;
     }
     sectsize = sectsize_var->val.ival;
     free(sectsize_var);
     
     /* find the extend entry in the partition table */
     for (i = 0; i < NUM_PTABLE_ENTRIES; i++)
          if (is_extended_part(&table[i])) {
               offset += table[i].offset * sectsize;
               return (build_ptable(offset));
          }
     return NULL;
}

/* 
 * Construct a partition table from the input
 * and add it to the ptable list
 */
static struct ptable_list_s *build_ptable(off_t offset)
{
     struct ptable_list_s *ext_ptable;
     const size_t ptable_size = NUM_PTABLE_ENTRIES * sizeof *ext_ptable->table;
     fbvar_t *sectsize_var;
     unsigned sectsize;

     /* get the sector size from the fatback variable table */
     sectsize_var = get_fbvar("sectsize");
     if (!sectsize_var->val.ival) {
          display(NORMAL, "Error: sectsize set to 0!\n");
          free(sectsize_var);
          return 0;
     }
     sectsize = sectsize_var->val.ival;
     free(sectsize_var);

     ext_ptable = emalloc(sizeof *ext_ptable);
     ext_ptable->table = emalloc(ptable_size);
     ext_ptable->offset = offset / sectsize;
     if (!read_ptable(offset, ext_ptable->table)) {
          free(ext_ptable->table);
          free(ext_ptable);
          return NULL;
     }
     ext_ptable->next = find_ext_pentry(offset, ext_ptable->table);
     return ext_ptable;
}
          
/*
 * Actually read the partition table elements into their
 * appropriate structure.  byte order conversion handeling included
 */
static int read_ptable(off_t offset, struct ptable_entry *table)
{
     int i;
     sig_t signature;
     u_int8_t *index, *buffer;
     fbvar_t *sectsize_var;
     unsigned sectsize;
     enum { PTABLE_OFFSET      = 446,
            BOOT_INDICATOR_OFF = 0,
            START_HEAD_OFF     = 1,
            START_SECT_OFF     = 2,
            START_CYL_OFF      = 3,
            SYS_INDICATOR_OFF  = 4,
            END_HEAD_OFF       = 5,
            END_SECT_OFF       = 6,
            END_CYL_OFF        = 7,
            SECTOR_OFFSET_OFF  = 8,
            TOTAL_SECTORS_OFF  = 12,
            CYL_MASK           = 0xC0,
            SECT_MASK          = ~CYL_MASK,
            PTABLE_SIZE        = 16
     };

     if (!table)
          return 0;

     /* get the sector size from the fatback global variable table */
     sectsize_var = get_fbvar("sectsize");
     if (!sectsize_var->val.ival) {
          display(NORMAL, "Error: sectsize set to 0!\n");
          free(sectsize_var);
          return 0;
     }
     sectsize = sectsize_var->val.ival;
     free(sectsize_var);

     buffer = emalloc(sectsize);
     if (!read_data(buffer, offset, sectsize)) {
          return 0;
     }
     index = buffer + PTABLE_OFFSET;

     /* Load partition table elements into their apropriate struct.  This may
      * seem a little cumbersome, but it is the easiest way to do it PORTABLY
      */
     for (i = 0; i < NUM_PTABLE_ENTRIES; i++) {
          index += !!i * PTABLE_SIZE;
          table[i].boot_indicator = little_endian_8(index +BOOT_INDICATOR_OFF);
          table[i].start_head = little_endian_8(index + START_HEAD_OFF);
          table[i].start_cyl = little_endian_8(index + START_CYL_OFF);
          table[i].start_cyl += (index[START_SECT_OFF] & CYL_MASK) << 2;
          table[i].start_sect = index[START_SECT_OFF] & SECT_MASK;
          table[i].sys_indicator = little_endian_8(index + SYS_INDICATOR_OFF);
          table[i].end_head = little_endian_8(index + END_HEAD_OFF);
          table[i].end_cyl = little_endian_8(index + END_CYL_OFF);
          table[i].end_cyl += (index[END_SECT_OFF] & CYL_MASK) << 2;
          table[i].end_sect = index[END_SECT_OFF] & SECT_MASK;
          table[i].offset = little_endian_32(index + SECTOR_OFFSET_OFF);
          table[i].sectors = little_endian_32(index + TOTAL_SECTORS_OFF);
     }

     signature = read_sig(&buffer[sectsize - 2]);
     free(buffer);
     return scheck_ptable(table, signature);
}

/*
 * tell if a partition table entry is an extended partition
 */
static int is_extended_part(struct ptable_entry *entry)
{
     /* This assumes that the partition is valid */
     if (entry->sys_indicator == MBR_FAT_EXT ||
         entry->sys_indicator == MBR_FAT_EXTX)
          return 1;
     else
          return 0;
}

/* 
 * Sanity check a partition table
 */
static int scheck_ptable(struct ptable_entry *table, sig_t sig)
{
     int i, num_partitions = 0;
     struct part_range_s prange[NUM_PTABLE_ENTRIES];
     fbvar_t *sectsize_var;
     unsigned sectsize;

     /* get the sector size from the fatback variable table */
     sectsize_var = get_fbvar("sectsize");
     if (!sectsize_var->val.ival) {
          display(NORMAL, "Error: sectsize set to 0!\n");
          free(sectsize_var);
          return 0;
     }
     sectsize = sectsize_var->val.ival;
     free(sectsize_var);

     /* make sure it has the proper signature bytes */
/*     if (!scheck_sig(sig)) {
 *         return 0;
 *    }
 */
     /* make sure all the entries in the table are valid */
     for (i = 0; i < NUM_PTABLE_ENTRIES; i++) {
          if (!is_part(&table[i])) {
               prange[i].start = 0;
               prange[i].end = 0;
               continue;
          }
          num_partitions++;
          /* make note of the boundries of the partition */
          prange[i].start = table[i].offset * sectsize;
          prange[i].end = prange[i].start + (table[i].sectors * sectsize);
     }
     /* check to see if the partitions overlap */
     if (num_partitions && scheck_part_range(prange))
          return 1;
     else
          return 0;
}

/*
 * Sanity check the boot indicator byte
 */
static int scheck_boot_indicator(struct ptable_entry *entry)
{
     if (entry->boot_indicator == BOOTABLE ||
         entry->boot_indicator == NON_BOOT)
          return 1;
     else 
          return 0;    
}

/* 
 * Sanity check the system indicator byte
 */
static int scheck_sys_indicator(struct ptable_entry *entry)
{
     switch (entry->sys_indicator) {
     case MBR_FAT16_S:
     case MBR_FAT_EXT:
     case MBR_FAT16_L:
     case MBR_FAT32:
     case MBR_FAT32X:
     case MBR_FAT16X:
     case MBR_FAT_EXTX:
          return 1;
          break;
     default:
          return 0;
     }
}

/* 
 * Sanity check the partition ranges of all the partitions
 * in a givin partition table.  (make sure they dont overlap)
 */
static int scheck_part_range(struct part_range_s *part_range)
{
     int i;
     
     for (i = 0; i < NUM_PTABLE_ENTRIES; i++) {
          int j;
          if (!part_range[i].start || !part_range[i].end) {
               /* break the loop cleanly, without goto */
               i = NUM_PTABLE_ENTRIES;
               continue;
          }
          for (j = i + 1; j < NUM_PTABLE_ENTRIES; j++) {
               if (!part_range[j].start || !part_range[j].end)
                    continue;
               if ((part_range[i].start >= part_range[j].start &&
                    part_range[i].start >= part_range[j].end) 
                   ||
                   (part_range[i].end >= part_range[j].start &&
                    part_range[i].end >= part_range[j].end)) {
                    display(VERBOSE, "Overlapping partitions detected\n");
                    return 0;
               }
          }
     }
     return 1;
} 

/*
 * tell if givin partition table entry is actually a partition
 */
static int is_part(struct ptable_entry *entry)
{
     if ((scheck_boot_indicator(entry)) &&
         (scheck_sys_indicator(entry))  &&
         (entry->offset && entry->sectors))
          return 1;
     else
          return 0;
}     


syntax highlighted by Code2HTML, v. 0.9.1