/* Copyright 2008 David Moews (dmoews@fastmail.fm)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 
This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
#include <cassert>
#include <cstddef>
#include <iostream>
#include <limits>
#include <cstring>
#include <string>
#include <vector>

#include "Iso9660.h"
#include "io.h"
#include "Types.h"

bool is_power_of_2(u32 s)
{
  if (s == 0u)
     return false;

  int sh = 0;
  u32 t = s;

  while (t != 1u)
  {
    t >>= 1;
    sh++;
  }

  return (s == (1u << sh));
}

bool is_valid_sector_size(u32 s) { return(s >= 2048u && is_power_of_2(s)); }
bool is_valid_block_size(u32 s) { return(s >= 512u && is_power_of_2(s)); }

u32 get_721(const char *p_c) { return (((u32)(unsigned char)p_c[1]) << 8) | 
                                        (u32)(unsigned char)p_c[0]; }
u32 get_722(const char *p_c) { return (((u32)(unsigned char)p_c[0]) << 8) | 
                                        (u32)(unsigned char)p_c[1]; }

// Returns succ/failure
bool get_723(const char *p_c, u32 *p_i)
{
  u32 a = get_721(p_c);
  u32 b = get_722(p_c + 2);
  if (a != b)
     return false;

  if (p_i)
     *p_i = a;
  return true;
}

u32 get_731(const char *p_c)
{
  return (((u32)(unsigned char)p_c[3]) << 24) |
         (((u32)(unsigned char)p_c[2]) << 16) |
         (((u32)(unsigned char)p_c[1]) << 8) |
         ((u32)(unsigned char)p_c[0]);
}

u32 get_732(const char *p_c)
{
  return (((u32)(unsigned char)p_c[0]) << 24) |
         (((u32)(unsigned char)p_c[1]) << 16) |
         (((u32)(unsigned char)p_c[2]) << 8) |
         ((u32)(unsigned char)p_c[3]);
}

// Returns succ/failure
bool get_733(const char *p_c, u32 *p_i)
{
  u32 a = get_731(p_c);
  u32 b = get_732(p_c + 4);
  if (a != b)
     return false;

  if (p_i)
     *p_i = a;
  return true;
}

bool operator ==(const Iso9660Date &a, const Iso9660Date &b)
{
  return 
       a.year == b.year && a.month == b.month && a.day == b.day &&
       a.hour == b.hour && a.minute == b.minute && a.second == b.second &&
       a.hundredths == b.hundredths && a.offset == b.offset;
}

inline bool operator !=(const Iso9660Date &a, const Iso9660Date &b)
                                { return !(a == b); }

// Get date recorded according to 8.4.26.1
// Returns succ/failure
bool get_numeric_date(const char *p_c, Iso9660Date *p_d)
{
  int iii;

  for (iii = 0; iii < 17; iii++)
      if (p_c[iii] != '\0')
          break;

  if (iii == 17)
  {
    std::cerr << "Warning: date binary 0, should be digits 0; continuing.\n";
    p_d->year = 0;
    p_d->month = 0;
    p_d->day = 0;
    p_d->hour = 0;
    p_d->minute = 0;
    p_d->second = 0;
    p_d->hundredths = 0;
    p_d->offset = 0;

    return true;
  }

  for (int i = 0; i < 16; i++)
      if (p_c[i] < '0' || p_c[i] > '9')
         return false;

  int y = (p_c[0] - '0') * 1000 + (p_c[1] - '0') * 100 +
              (p_c[2] - '0') * 10 + (p_c[3] - '0');
  int month = (p_c[4] - '0') * 10 + p_c[5] - '0';
  int d = (p_c[6] - '0') * 10 + p_c[7] - '0';
  int h = (p_c[8] - '0') * 10 + p_c[9] - '0';
  int m = (p_c[10] - '0') * 10 + p_c[11] - '0';
  int s = (p_c[12] - '0') * 10 + p_c[13] - '0';
  int ss = (p_c[14] - '0') * 10 + p_c[15] - '0';
  int ofs = (signed char)p_c[16];

  if (y == 0)
  {
    if (month != 0 || d != 0 || h != 0 || m != 0 || s != 0 || ss != 0 ||
        ofs != 0)
       return false;
  } else
  {
    if (y < 1 || y > 9999 || month < 1 || month > 12 || 
        d < 1 || d > 31 || h > 23 || m > 59 || s > 59 || ss > 99 ||
        ofs < -48 || ofs > 52)
       return false;
  }

  p_d->year = y;
  p_d->month = month;
  p_d->day = d;
  p_d->hour = h;
  p_d->minute = m;
  p_d->second = s;
  p_d->hundredths = ss;
  p_d->offset = ofs;

  return true;
}

// Returns succ/failure
bool get_binary_date(const char *p_c, Iso9660Date *p_d)
{
  if (p_c[0] == '\0')
  {
    if (p_c[1] != '\0' || p_c[2] != '\0' || p_c[3] != '\0' || p_c[4] != '\0'
                       || p_c[5] != '\0')
    //		       || p_c[6] != '\0')
       return false;

    // More breakage
    if (p_c[6] != '\0')
    {
       std::cerr << 
	        "Warning: zone offset in empty date nonzero.  Continuing.\n";
    }

    p_d->year = 0;
    p_d->month = 0;
    p_d->day = 0;
    p_d->hour = 0;
    p_d->minute = 0;
    p_d->second = 0;
    p_d->hundredths = 0;
    p_d->offset = 0;
  } else
  {
    if (p_c[1] < 1 || p_c[1] > 12 || p_c[2] < 1 || p_c[2] > 31 ||
        p_c[3] < 0 || p_c[3] > 23 || p_c[4] < 0 || p_c[4] > 59 ||
        p_c[5] < 0 || p_c[5] > 59)
       return false;

    int ofs = (signed char)p_c[6];

    if (ofs < -48 || ofs > 52)
       return false;

    p_d->year = 1900 + ((unsigned char)p_c[0]);
    p_d->month = p_c[1];
    p_d->day = p_c[2];
    p_d->hour = p_c[3];
    p_d->minute = p_c[4];
    p_d->second = p_c[5];
    p_d->hundredths = 0;
    p_d->offset = ofs;
  }

  return true;
}

std::ostream &operator <<(std::ostream &o, const Iso9660Date &d)
{
  if (d.year == 0)
  {
     o << "unspecified";
     return o;
  }

  o << d.year << '/' << d.month << '/' << d.day << ' '
    << (d.hour < 10 ? "0" : "") << d.hour << ':'
    << (d.minute < 10 ? "0" : "") << d.minute << ':'
    << (d.second < 10 ? "0" : "") << d.second << '.'
    << (d.hundredths < 10 ? "0" : "") << d.hundredths 
    << ", GMT offset " << (double)d.offset / 4.0;

  return o;
}

bool operator ==(const VolumeInfo &a, const VolumeInfo &b)
{
  return 
       a.volume_space_size == b.volume_space_size &&
       a.volume_set_size == b.volume_set_size &&
       a.volume_sequence_number == b.volume_sequence_number &&
       a.logical_block_size == b.logical_block_size &&
       a.logical_sector_size == b.logical_sector_size &&
       a.num_descriptor_sectors == b.num_descriptor_sectors &&
       a.creation_time == b.creation_time &&
       a.modification_time == b.modification_time &&
       a.expiration_time == b.expiration_time &&
       a.effective_time == b.effective_time;
}

inline bool operator !=(const VolumeInfo &a, const VolumeInfo &b)
                                 { return !(a == b); }

// Shallow compare.  Don't check contents, XAR, system use, root/./.. flags,
//                               or names.
bool equal_directory_records(const DirectoryRecord &a, const DirectoryRecord &b)
{
  return 
       a.extended_attributes_length == b.extended_attributes_length &&
       a.start == b.start && 
       a.length == b.length && 
       a.recording_time == b.recording_time && 
       a.flags == b.flags &&
       a.file_unit_size == b.file_unit_size &&
       a.interleave_gap == b.interleave_gap &&
       a.volume_sequence_number == b.volume_sequence_number;
}

void print_escaped_char(std::ostream &o, char cc)
{
  o << '\\';
  switch (cc)
  {
    case '\0':
        o << '0';
        break;

    case '\n':
        o << 'n';
        break;
       
    case '\r':
        o << 'r';
        break;
       
    case '\t':
        o << 't';
        break;
      
    case '"': 
        o << '"';
        break;

    case '\\':
        o << '\\';
        break;

    default:
        o << 'x';
        o << "0123456789abcdef"[((unsigned char)cc) >> 4];
        o << "0123456789abcdef"[cc & 15];
        break;
  }
}


void print_string(std::ostream &o, std::size_t len, const char *p_c)
{
  o << '"';
  for (std::size_t i = 0; i < len; i++, p_c++)
  {
    if (*p_c != '\\' && *p_c != '"' && *p_c >= ' ' && *p_c <= '~')
       o << *p_c;
    else
       print_escaped_char(o, *p_c);
  }
  o << '"';
}

void print_string(std::ostream &o, const std::string &s)
{
  print_string(o, s.size(), s.data());
}

void print_trimmed_string(std::ostream &o, std::size_t len, const char *p_c)
{
  while (len > 0 && p_c[len - 1] == ' ')
        len--;

  print_string(o, len, p_c);
}

std::ostream &operator <<(std::ostream &o, const DirectoryRecord &a)
{
  if (a.extended_attributes_length != 0u)
     o << " Extd attr len: " << (unsigned int)a.extended_attributes_length;
  o << " Start: " << a.start << " Length: " << a.length;
  o << " Recording time: " << a.recording_time;
  if (a.flags != 0u)
     o << " Flags: " << (unsigned int)a.flags;
  assert((a.interleave_gap != 0u) == (a.file_unit_size != 0u));
  if (a.interleave_gap != 0u)
     o << " File unit size: " << (unsigned int)a.file_unit_size
       << " Interleave gap: " << (unsigned int)a.interleave_gap;
  o << " Vol seq #: " << a.volume_sequence_number
    << " Name: ";
  print_string(o, a.name);
  if (!a.system_use.empty())
  {
    o << " System use: ";
    print_string(o, a.system_use);
  }

  return o;
}

// Shallow compare; doesn't compare # of times recorded
bool operator ==(const VolumeDescriptor &a, const VolumeDescriptor &b)
{
  return 
     a.is_primary == b.is_primary &&
     a.volume_flags == b.volume_flags &&
     memcmp(a.system_identifier, b.system_identifier, 32) == 0 &&
     memcmp(a.volume_identifier, b.volume_identifier, 32) == 0 &&
     memcmp(a.escape_sequences, b.escape_sequences, 32) == 0 &&
     a.path_table_size == b.path_table_size &&
     a.path_l_location[0] == b.path_l_location[0] &&
     a.path_l_location[1] == b.path_l_location[1] &&
     a.path_m_location[0] == b.path_m_location[0] &&
     a.path_m_location[1] == b.path_m_location[1] &&
     equal_directory_records(a.root_directory, b.root_directory) &&
     memcmp(a.volume_set_identifier, b.volume_set_identifier, 128) == 0 &&
     memcmp(a.publisher_identifier, b.publisher_identifier, 128) == 0 &&
     memcmp(a.data_preparer_identifier, b.data_preparer_identifier, 128) == 0 &&
     memcmp(a.application_identifier, b.application_identifier, 128) == 0 &&
     memcmp(a.copyright_file_identifier, b.copyright_file_identifier, 37) == 0
             &&
     memcmp(a.abstract_file_identifier, b.abstract_file_identifier, 37) == 0
             &&
     memcmp(a.bibliographic_file_identifier, 
            b.bibliographic_file_identifier, 37) == 0
             &&
     memcmp(a.application_use, b.application_use, 512) == 0;
}

inline bool operator !=(const VolumeDescriptor &a, const VolumeDescriptor &b)
                           { return !(a == b); }

// Returns succ/failure
bool get_directory_record(const char *p_c, DirectoryRecord *p_dr, int *p_len)
{
  u32 start, length;
  Iso9660Date rt;
  u32 seq_num;

  if (!get_733(p_c + 2, &start) || !get_733(p_c + 10, &length) ||
      !get_binary_date(p_c + 18, &rt) || !get_723(p_c + 28, &seq_num))
     return false;

  int len = (unsigned char)p_c[0];
  int file_id_len = (unsigned char)p_c[32];

  if (file_id_len + 33 > len)
     return false;

  if (file_id_len % 2 == 0)
  {
    if (file_id_len + 33 == len || p_c[33 + file_id_len] != '\0')
       return false;
  }

  if (p_c[25] & 96)
     // Reserved flags
     return false;

  if (p_c[25] & 2)
  {
     // Directories can't be interleaved, formatted, multi-extent, or assoc'd
     if ((p_c[26] != '\0') || (p_c[25] & 140))
        return false;
  }

  if ((p_c[26] != '\0') != (p_c[27] != '\0'))
     // Can't have nonzero interleave gap and zero file unit size, or v.v.
     return false;

  p_dr->extended_attributes_length = (unsigned char)p_c[1];
  p_dr->start = start;
  p_dr->length = length;
  p_dr->recording_time = rt;
  p_dr->flags = (unsigned char)p_c[25];
  p_dr->file_unit_size = (unsigned char)p_c[26];
  p_dr->interleave_gap = (unsigned char)p_c[27];
  p_dr->volume_sequence_number = seq_num;

  p_dr->name.assign(p_c + 33, file_id_len);

  if (file_id_len % 2 == 0)
     p_dr->system_use.assign(p_c + 34 + file_id_len, len - (34 + file_id_len));
  else
     p_dr->system_use.assign(p_c + 33 + file_id_len, len - (33 + file_id_len));

  p_dr->xar.clear();
  p_dr->p_xar = NULL;
  p_dr->p_contents = NULL;
  p_dr->is_root = p_dr->is_dot = p_dr->is_dotdot = false;

  if (p_len)
     *p_len = len;

  return true;
}

// Read XAR, if required.
// Returns succ/failure.
bool read_xar(std::istream &ii, const VolumeInfo &vi, 
              DirectoryRecord *p_dr, bool *p_is_multivolume)
{
  if (p_dr->extended_attributes_length == 0u)
     return true;

  if (p_dr->volume_sequence_number != vi.volume_sequence_number)
  // Multivolume.  Punt.
  {
    if (p_is_multivolume != NULL)
       *p_is_multivolume = true;

    return true;
  }

  if ((double)p_dr->start + (double)p_dr->extended_attributes_length >
      (double)vi.volume_space_size)
     return false;

  // Try to read XAR
  char *cs = new char[(std::size_t)vi.logical_block_size * 
                      (std::size_t)p_dr->extended_attributes_length];
  if (!read_blocks(ii, p_dr->start, p_dr->extended_attributes_length, 
                   vi.logical_block_size, cs))
  {
    delete[] cs;
    return false;
  }

  p_dr->xar.assign(cs, (std::size_t)vi.logical_block_size *
                       (std::size_t)p_dr->extended_attributes_length);
  delete[] cs;
  return true;
}

// Returns succ/failure.  Assumes not multivolume.
bool read_directory(std::istream &ii, const VolumeInfo &vi,
                    u32 starting_block_number, unsigned char xar_len,
                    u32 length,
                    DirectoryContents **p_p_out)
{
  // 6.8.1, 6.8.1.[123]: directory is integral number of sectors long
  // (and therefore an integral number of blocks.)
  if (length % vi.logical_sector_size != 0u)
     return false;

  assert(length % vi.logical_block_size == 0u);

  u32 num_blocks = length / vi.logical_block_size;

  if ((double)starting_block_number + (double)xar_len + (double)num_blocks > 
      (double)vi.volume_space_size)
     return false;

  char *cs = new char[(std::size_t)length];
  if (!read_blocks(ii, starting_block_number + (u32)xar_len, num_blocks,
                       vi.logical_block_size, cs))
  {
    delete[] cs;
    return false;
  }

  std::vector<DirectoryRecord> rv;
  char *ps = cs;

  for (u32 i = 0; i < (length / vi.logical_sector_size); i++)
  {
    u32 j = vi.logical_sector_size;

    while (j != 0u && *ps != '\0')
    {
      int len;
      DirectoryRecord d;

      if ((unsigned char)*ps > j || !get_directory_record(ps, &d, &len))
      {
        delete[] cs;
        return false;
      }

      rv.push_back(d);
      ps += len;
      j -= len;
    }

    // 6.8.1.1
    while (j != 0u)
    {
      if (*ps++ != '\0')
      {
        delete[] cs;
        return false;
      }
      j--;
    }
  }
  delete[] cs;
  if (p_p_out != NULL)
  {
    *p_p_out = new DirectoryContents;
    (*p_p_out)->records = rv;
  }
  return true;
}

// Returns succ/failure
bool get_boot_record(const char *p_c, BootRecord *p_br)
{
  assert(p_br != NULL);

  memcpy(p_br->boot_system_identifier, p_c + 7, 32);
  memcpy(p_br->boot_identifier, p_c + 39, 32);
  memcpy(p_br->boot_system_use, p_c + 71, 1977);

  return true;
}

// Returns succ/failure
bool get_volume_descriptor
   (const char *p_c, bool is_primary, VolumeInfo *p_vi, VolumeDescriptor *p_vd)
{
  assert(p_vi != NULL && p_vd != NULL);

  // Hack: breakage in RL: must comment this out
  // if (p_c[7] != '\0' && p_c[7] != '\1') 
  //   return false;
  if (p_c[7] != '\0' && p_c[7] != '\1')
  {
    std::cerr << "Warning: byte 8 flags in SVD not 0 or 1; continuing.\n";
  }

  if ((is_primary && p_c[7] != '\0') || p_c[881] != '\1' || p_c[882] != '\0')
     return false;
   
  for (int i = 72; i < 80; i++)
      if (p_c[i] != '\0')
         return false;

  for (int i = 1395; i < 2048; i++)
      if (p_c[i] != '\0')
         return false;

  if (is_primary)
  {
    int i;

    for (i = 88; i < 120; i++)
        if (p_c[i] != '\0')
           break;

    if (i != 120)
       std::cerr << 
	  "Warning: nonzero flags in primary volume descriptor; continuing.\n";
  }

  u32 volume_space_size, path_table_size;
  u32 volume_set_size, volume_sequence_number, logical_block_size;
  Iso9660Date cre, mod, exp, eff;
  int root_dir_len;
  DirectoryRecord root_dir;

  if (!get_733(p_c + 80, &volume_space_size) ||
      !get_723(p_c + 120, &volume_set_size) || 
      !get_723(p_c + 124, &volume_sequence_number) || 
      !get_723(p_c + 128, &logical_block_size) || 
      !is_valid_block_size(logical_block_size) ||
      !get_733(p_c + 132, &path_table_size) ||
      !get_numeric_date(p_c + 813, &cre) ||
      !get_numeric_date(p_c + 830, &mod) ||
      !get_numeric_date(p_c + 847, &exp) ||
      !get_numeric_date(p_c + 864, &eff) ||
      !get_directory_record(p_c + 156, &root_dir, &root_dir_len) ||
      // Hack, breakage in real life: must comment this out
      // !root_dir.system_use.empty() ||
      !root_dir.is_directory() ||
      root_dir.name.size() != 1u ||
      root_dir.name[0] != '\0' ||
      // Hack, breakage in real life: must comment this out
      // root_dir_len != 34
      root_dir_len < 34)
     return false;

  // Hack.
  if (root_dir_len != 34)
  {
    std::cerr << "Warning: root dir len in volume descr is " << root_dir_len
              << " and not 34; continuing.\n";
  }

  p_vi->volume_space_size = volume_space_size;
  p_vi->volume_set_size = volume_set_size;
  p_vi->volume_sequence_number = volume_sequence_number;
  p_vi->logical_block_size = logical_block_size;
  p_vi->creation_time = cre;
  p_vi->modification_time = mod;
  p_vi->expiration_time = exp;
  p_vi->effective_time = eff;
  p_vi->logical_sector_size = 0u;
  p_vi->num_descriptor_sectors = 0;

  p_vd->volume_flags = (p_c[7] != '\0');
  p_vd->path_table_size = path_table_size;
  p_vd->root_directory = root_dir;
  p_vd->root_directory.is_root = true;

  memcpy(p_vd->system_identifier, p_c + 8, 32);
  memcpy(p_vd->volume_identifier, p_c + 40, 32);
  memcpy(p_vd->escape_sequences, p_c + 88, 32);

  p_vd->path_l_location[0] = get_731(p_c + 140);
  p_vd->path_l_location[1] = get_731(p_c + 144);
  p_vd->path_m_location[0] = get_732(p_c + 148);
  p_vd->path_m_location[1] = get_732(p_c + 152);
 
  memcpy(p_vd->volume_set_identifier, p_c + 190, 128);
  memcpy(p_vd->publisher_identifier, p_c + 318, 128);
  memcpy(p_vd->data_preparer_identifier, p_c + 446, 128);
  memcpy(p_vd->application_identifier, p_c + 574, 128);

  memcpy(p_vd->copyright_file_identifier, p_c + 702, 37);
  memcpy(p_vd->abstract_file_identifier, p_c + 739, 37);
  memcpy(p_vd->bibliographic_file_identifier, p_c + 776, 37);

  memcpy(p_vd->application_use, p_c + 883, 512);

  p_vd->is_primary = is_primary;
  p_vd->number_recorded = 1;

  return true;
}

// Returns succ/failure
bool get_volume_partition(const char *p_c, const VolumeInfo &,
                          VolumePartition *p_vp)
{
  assert(p_vp != NULL);

  if (p_c[7] != '\0')
     return false;

  u32 start, size;

  if (!get_733(p_c + 72, &start) || !get_733(p_c + 80, &size))
     return false;
  
  p_vp->start = start;
  p_vp->size = size;

  memcpy(p_vp->system_identifier, p_c + 8, 32);
  memcpy(p_vp->volume_partition_identifier, p_c + 40, 32);
  memcpy(p_vp->system_use, p_c + 88, 1960);

  return true;
}

// Returns succ/failure.  May alter *p_v on failure.
bool read_iso9660_header(std::istream &i, u32 sector_size, Volume *p_v)
{
  assert(p_v != NULL);
 
  if (!is_valid_sector_size(sector_size))
     return false;
 
  u32 sector_number = 16u;
  char sector_chars[2048];
  bool done = false;
  bool read_volume_info = false;
  bool read_primary = false;
  int last_record = -1;
  int this_record = -1;
  VolumeInfo the_vi;

  the_vi.logical_sector_size = sector_size;
  the_vi.num_descriptor_sectors = 0;

  do
  {
    if (!read_sector(i, sector_number, sector_size, sector_chars) ||
        memcmp(&sector_chars[1], "CD001\1", 6u) != 0) 
       return false;

    sector_number++;

    switch (sector_chars[0])
    {
      case '\0':
           // Boot record
           {
             BootRecord b;

             if (!read_primary || !get_boot_record(sector_chars, &b))
                 return false;
             p_v->boot_records.push_back(b);
             this_record = 3;
           }
           break;

      case '\1':
           // PVD
      case '\2':
           // SVD
           {
             VolumeDescriptor vd;
             VolumeInfo vi;

             bool is_primary = (sector_chars[0] == '\1');

             this_record = (is_primary ? 0 : 1);

             if (!read_primary && !is_primary) 
                return false;

             if (!get_volume_descriptor(sector_chars, is_primary, &vi, &vd))
                return false;

             vi.logical_sector_size = sector_size;
             if (vi.logical_block_size > vi.logical_sector_size)
                return false;

             if (!read_volume_info)
             {
               read_volume_info = true;
               the_vi = vi;
             } else
             {
               if (vi != the_vi)
                  return false;
             }
             // There is only one PVD, but it may be recorded more than once.
             if (read_primary && is_primary)
             {
               assert(p_v->volume_descriptors.size() == 1);
               if (is_primary && p_v->volume_descriptors[0] != vd)
                     return false;
             }
             read_primary |= is_primary;
             std::vector<VolumeDescriptor>::iterator
                           p = p_v->volume_descriptors.begin();
             for (; p != p_v->volume_descriptors.end(); ++p)
                 if (*p == vd)
                    break;
             if (p != p_v->volume_descriptors.end())
                p->number_recorded++;
             else
                p_v->volume_descriptors.push_back(vd);
           }
           break;

      case '\3':
           // Volume partition
           {
             VolumePartition vp; 

             if (!read_primary ||
                 !get_volume_partition(sector_chars, the_vi, &vp))
                return false;
             p_v->volume_partitions.push_back(vp);
             this_record = 2;
           }
           break;

      case '\xff':
           // End of volume information
           if (!read_primary)
              return false;

           for (int i = 7; i < 2048; i++)
               if (sector_chars[i] != '\0')
                  return false;

           done = true;
           this_record = 4;
           break;

      default:
           return false;
           break;
    }

    if (this_record < last_record)
       // Order of records (6.7.1): PVD, SVD, volume partition, boot record
       return false;

    this_record = last_record;
  }
  while (!done);

  if ((double)sector_number *
      (double)(the_vi.logical_sector_size / the_vi.logical_block_size) >
      (double)the_vi.volume_space_size)
     return false;

  the_vi.num_descriptor_sectors = sector_number - 16u;
  p_v->volume_info = the_vi;

  return true;
}

// Check directory entry, read in XAR, and recurse,
// starting at given directory entry.
// Returns succ/failure.  May alter *p_directory even on failure.
// Sets is_dot, is_dotdot flags.
bool read_directory_hierarchy(std::istream &ii, const VolumeInfo &vi, 
                              DirectoryRecord *p_directory)
{
  bool is_multivolume = false;

  if (!read_xar(ii, vi, p_directory, &is_multivolume))
     return false;

  // This might be a non-directory, in which case we don't want to read it in.
  if (!p_directory->is_directory())
     return true;

  // This is a directory.
  // Each volume must contain a full directory hierarchy (6.6).
  // Therefore, it is an error if this is on a different volume.
  if (is_multivolume)
     return false;

  // This may be . or .., in which case we don't want to descend through it.
  if (p_directory->is_dot || p_directory->is_dotdot)
     return true;

  if (!read_directory(ii, vi, p_directory->start, 
                      p_directory->extended_attributes_length, 
                      p_directory->length,
                      &p_directory->p_contents))
     return false;

  unsigned int count = 0;

  for (std::vector<DirectoryRecord>::iterator p = 
                p_directory->p_contents->records.begin();
       p != p_directory->p_contents->records.end();
       ++p, count++)
  {
    // First two entries must be `.' (00) and `..' (01).
    if (count <= 1u)
    {
      if (!p->is_directory() || p->name.size() != 1u 
                             || (unsigned char)p->name[0] != count)
         return false;
      if (count == 0u)
         p->is_dot = true;
      else
         p->is_dotdot = true;
    } 
 
    if (!read_directory_hierarchy(ii, vi, &(*p)))
       return false;
  }

  return (count >= 2);
}

struct PathTableRecord
{
  unsigned char extended_attributes_length;
  u32 start;
  std::string name;
  unsigned int level;

  u32 parent;
  std::vector<std::size_t> children;
};

// Return succ/failure.
bool verify_path_table(std::vector<PathTableRecord> *p_ptrs,
                      const char *cs, u32 len, bool is_m_type)
{
  std::size_t number = 0;

  for (; len > 0; number++)
  {
    unsigned int entry_len = (unsigned char)*cs;
    unsigned int total_len = entry_len + 8u + (entry_len % 2u);

    if (total_len > len ||
        ((entry_len % 2u) != 0 && cs[entry_len + 8] != '\0'))
       return false;

    PathTableRecord r_new;
    u32 parent_number;

    r_new.extended_attributes_length = (unsigned char)cs[1];

    if (is_m_type)
    {
      r_new.start = get_732(cs + 2);
      parent_number = get_722(cs + 6);
    } else
    {
      r_new.start = get_731(cs + 2);
      parent_number = get_721(cs + 6);
    }

    r_new.name.assign(cs + 8, entry_len);

    cs += total_len;
    len -= total_len;

    if (number == 0)
    {
      if (entry_len != 1 || r_new.name[0] != '\0' || parent_number != 1)
         return false;

      r_new.level = 0u;
      r_new.parent = 0;
      p_ptrs->push_back(r_new);
      continue;
    }

    if (parent_number == 0)
       return false;

    if (--parent_number >= number)
       return false;
 
    assert(number >= 1 && number == p_ptrs->size());

    // Check ordering (6.9.1)
    if ((*p_ptrs)[parent_number].level + 1 < (*p_ptrs)[number - 1].level)
       return false;

    if ((*p_ptrs)[parent_number].level + 1 == (*p_ptrs)[number - 1].level)
    {
      u32 last_parent = (*p_ptrs)[number - 1].parent;

      // Don't check name lex order here (if it matches the directory hierarchy,
      // and the hierarchy is sorted, it must be correct.)
      if (last_parent > parent_number)
         return false;
    }

    r_new.level = (*p_ptrs)[parent_number].level + 1;
    r_new.parent = parent_number;
    p_ptrs->push_back(r_new);
    (*p_ptrs)[parent_number].children.push_back(number);  
  }
  return true;
}

// Return succ/failure.
bool compare_path_table_with_hierarchy
   (std::size_t offset, const std::vector<PathTableRecord> &ptrs, 
    const DirectoryRecord &dir)
{
  if (ptrs[offset].name != dir.name ||
      ptrs[offset].extended_attributes_length != 
                         dir.extended_attributes_length ||
      ptrs[offset].start != dir.start ||
      dir.p_contents == NULL || 
      dir.p_contents->records.size() < 2u)
     return false;

  std::vector<std::size_t>::const_iterator q = ptrs[offset].children.begin();

  for (std::vector<DirectoryRecord>::const_iterator p = 
            dir.p_contents->records.begin() + 2;
       p != dir.p_contents->records.end(); ++p)
  {
    if (!p->is_directory())
      continue;

    if (!compare_path_table_with_hierarchy(*q++, ptrs, *p))
       return false;
  }

  return q == ptrs[offset].children.end();
}

struct PathTableSon
{
  std::string name;
  std::size_t offset;

  PathTableSon(const std::string &n, std::size_t o) 
                    : name(n), offset(o) { }
};

bool compare_path_table_sons
    (const PathTableSon &a, const PathTableSon &b)
{
  return a.name < b.name;
}

bool compare_directory_record_ptrs
    (const DirectoryRecord *a, const DirectoryRecord *b)
{
  return a->name < b->name;
}

// Return succ/failure.
bool compare_path_table_with_hierarchy_unsorted
   (std::size_t offset, const std::vector<PathTableRecord> &ptrs, 
    const DirectoryRecord &dir)
{

  std::vector<const DirectoryRecord *> dir_children;
  std::vector<PathTableSon> path_table_children;

  if (ptrs[offset].name != dir.name ||
      ptrs[offset].extended_attributes_length != 
                         dir.extended_attributes_length ||
      ptrs[offset].start != dir.start ||
      dir.p_contents == NULL || 
      dir.p_contents->records.size() < 2u)
     return false;

  for (std::vector<std::size_t>::const_iterator 
         q = ptrs[offset].children.begin();
         q != ptrs[offset].children.end();
         ++q)
      path_table_children.push_back
               (PathTableSon(ptrs[*q].name, *q));

  for (std::vector<DirectoryRecord>::const_iterator 
        p = dir.p_contents->records.begin() + 2;
        p != dir.p_contents->records.end(); 
        ++p)
  {
    if (!p->is_directory())
      continue;

    dir_children.push_back(&(*p));
  }

  if (dir_children.size() != path_table_children.size())
     return false;

  sort(dir_children.begin(), 
       dir_children.end(), 
       compare_directory_record_ptrs);

  sort(path_table_children.begin(), 
       path_table_children.end(),
       compare_path_table_sons);

  std::vector<PathTableSon>::const_iterator 
       p = path_table_children.begin();
  std::vector<const DirectoryRecord *>::const_iterator 
       q = dir_children.begin();

  for (; p != path_table_children.end() &&
         q != dir_children.end(); ++p, ++q)
  {
    if (!compare_path_table_with_hierarchy_unsorted
                   (p->offset, ptrs, **q))
       return false;
  }

  if (p != path_table_children.end() ||
      q != dir_children.end())
     return false;

  return true;
}

// Return succ/failure.
bool read_and_verify_path_table(std::istream &ii, bool is_m_type, 
                       const VolumeInfo &vi, const DirectoryRecord &root_dir,
                       u32 size, u32 starting_block)
{
  u32 path_table_blocks = size / vi.logical_block_size;
  if (size % vi.logical_block_size != 0u)
     path_table_blocks++;

  if ((double)path_table_blocks + (double)starting_block > 
      (double)vi.volume_space_size)
     return false;

  char *cs = new char[(std::size_t)vi.logical_block_size * 
                      (std::size_t)path_table_blocks];
  if (!read_blocks(ii, starting_block, path_table_blocks, 
                       vi.logical_block_size, cs))
  {
    delete[] cs;
    return false;
  }

  std::vector<PathTableRecord> ptrs;

  if (!verify_path_table(&ptrs, cs, size, is_m_type))
  {
    delete[] cs;
    return false;
  }

  delete[] cs;

  if (!compare_path_table_with_hierarchy(0u, ptrs, root_dir))
  {
    if (!compare_path_table_with_hierarchy_unsorted(0u, ptrs, root_dir))
       return false;

    std::cerr << "Warning: path table and directory \
hierarchy ordering do not match; continuing.\n";
  } else assert(
        compare_path_table_with_hierarchy_unsorted(0u, ptrs, root_dir));

  return true;
}

// Return succ/failure
// May alter *p_v even on failure
bool read_iso9660(std::istream &i, u32 sector_size, Volume *p_v)
{
  if (!read_iso9660_header(i, sector_size, p_v))
     return false;

  for (std::vector<VolumeDescriptor>::iterator p =
             p_v->volume_descriptors.begin();
        p != p_v->volume_descriptors.end(); ++p)
  {
    if (!read_directory_hierarchy(i, p_v->volume_info, &p->root_directory))
       return false;

    if (!read_and_verify_path_table
                   (i, false, p_v->volume_info, p->root_directory,
                           p->path_table_size, p->path_l_location[0]))
       return false;

    if (p->path_l_location[1] != 0 &&
        !read_and_verify_path_table
                    (i, false, p_v->volume_info, p->root_directory,
                           p->path_table_size, p->path_l_location[1]))
       return false;

    if (!read_and_verify_path_table
                   (i, true, p_v->volume_info, p->root_directory,
                           p->path_table_size, p->path_m_location[0]))
       return false;

    if (p->path_m_location[1] != 0 &&
        !read_and_verify_path_table
                   (i, true, p_v->volume_info, p->root_directory,
                           p->path_table_size, p->path_m_location[1]))
       return false;
  }

  return true;
}

// Returns pointer to new XAR record, or NULL on failure.
XAR *parse_xar(const std::string &s)
{
  assert(s.size() >= 250u);

  const char *cs = s.data();

  if (cs[180] != '\1')
     return false;

  for (int i = 182; i < 246; i++)
      if (cs[i] != '\0')
         return false;

  XAR xar;

  xar.record_format = (unsigned char)cs[78];
  xar.record_attributes = (unsigned char)cs[79];
  unsigned char escape_length = (unsigned char)cs[181];
  u32 application_length;

  if ((xar.record_format >= 4u && xar.record_format < 128u) ||
      (xar.record_format != 0u && xar.record_attributes >= 3u) ||
      (((unsigned char)cs[8] & 170u) != 170u) ||
      (((unsigned char)cs[9] & 170u) != 170u) ||
      !get_723(cs, &xar.owner_id) ||
      !get_723(cs + 4, &xar.group_id) ||
      ((xar.owner_id == 0) != (xar.group_id == 0)) ||
      !get_numeric_date(cs + 10, &xar.creation_time) ||
      !get_numeric_date(cs + 27, &xar.modification_time) ||
      !get_numeric_date(cs + 44, &xar.expiration_time) ||
      !get_numeric_date(cs + 61, &xar.effective_time) ||
      !get_723(cs + 80, &xar.record_length) ||
      (xar.record_format == 0u && xar.record_length != 0u) ||
      // 6.10.3: recfm F ==> lrecl != 0
      (xar.record_format == 1u && xar.record_length == 0u) ||
      !get_723(cs + 246, &application_length) ||
      application_length + (u32)escape_length + 250u != s.size())
     return false;
     
  xar.permissions = (((u32)(unsigned char)cs[8]) << 8) ||
                     ((u32)(unsigned char)cs[9]);
  memcpy(xar.system_identifier, cs + 84, 32u);
  memcpy(xar.system_use, cs + 116, 64u); 
  xar.escape_sequences.assign(cs + 250, application_length);
  xar.application_use.assign(cs + 250 + application_length, escape_length);

  return new XAR(xar);
}

void print_xar(std::ostream &o, const XAR &xar)
{
  o << "Owner id: " << xar.owner_id << " Group id: " << xar.group_id
    << " Permissions: " << xar.permissions
    << " Creation time: " << xar.creation_time
    << " Modification time: " << xar.modification_time
    << " Expiration time: " << xar.expiration_time
    << " Effective time: " << xar.effective_time
    << " Record format: " << (unsigned int)xar.record_format
    << " Record attributes: " << (unsigned int)xar.record_attributes
    << " Record length: " << xar.record_length;
  o << " System identifier: ";
  print_trimmed_string(o, 32u, xar.system_identifier);
  o << " System use: ";
  print_string(o, 64u, xar.system_use);
  o << " Escape sequences: ";
  print_string(o, xar.escape_sequences);
  o << " Application use: ";
  print_string(o, xar.application_use);
}

void print_directory_hierarchy(std::ostream &o, unsigned int indent,
                               const DirectoryRecord &d)
{
  for (unsigned int i = 0; i < indent; i++)
      o << ' ';
  o << d << '\n';
  if (!d.xar.empty())
  {
    for (unsigned int i = 0; i < indent; i++)
        o << ' ';

    XAR *p_xar = parse_xar(d.xar);

    if (p_xar == NULL)
    {
      o << "Unparseable XAR record, raw data: ";
      print_string(o, d.xar);
      o << '\n';
    } else
    {
      o << "XAR record: ";
      print_xar(o, *p_xar);
      o << '\n';

      delete p_xar;
    }
  }
  if (d.p_contents == NULL)
     return;

  for (std::vector<DirectoryRecord>::iterator 
                p = d.p_contents->records.begin();
                p != d.p_contents->records.end(); ++p)
      print_directory_hierarchy(o, indent + 2u, *p);
}

void print_iso9660(std::ostream &o, const Volume &v, bool print_hierarchies)
{
  o << "Volume info:\n";
  o << "  Sector size: " << v.volume_info.logical_sector_size << '\n';
  o << "  Number of descriptor sectors: " 
                   << v.volume_info.num_descriptor_sectors << '\n';
  o << '\n';
  o << "  Volume space size: " << v.volume_info.volume_space_size << '\n';
  o << "  Volume set size: " << v.volume_info.volume_set_size << '\n';
  o << "  Volume sequence number: " 
            << v.volume_info.volume_sequence_number << '\n';
  o << "  Logical block size: " 
            << v.volume_info.logical_block_size << '\n';
  o << "  Creation time: " << v.volume_info.creation_time << '\n';
  o << "  Modification time: " << v.volume_info.modification_time << '\n';
  o << "  Expiration time: " << v.volume_info.expiration_time << '\n';
  o << "  Effective time: " << v.volume_info.effective_time << '\n';

  for (std::vector<VolumeDescriptor>::const_iterator p =
             v.volume_descriptors.begin();
        p != v.volume_descriptors.end(); ++p)
  {
    o << "Volume descriptor:\n";
    o << "  Primary: " << yes_no(p->is_primary) << '\n';
    o << "  Number of times recorded: " << p->number_recorded << '\n';
    o << "  System identifier: ";
    print_trimmed_string(o, 32u, p->system_identifier);
    o << '\n';
    o << "  Volume identifier: ";
    print_trimmed_string(o, 32u, p->volume_identifier);
    o << '\n';
    if (!p->is_primary)
    {
      o << "  Volume flags: " << (p->volume_flags ? "1" : "0") << '\n';
      
      o << "  Escape sequences: ";
      print_string(o, 32u, p->escape_sequences);
      o << '\n';
    }
    o << "  Path table size: " << p->path_table_size << '\n';
    o << "  Path table L 0 start: " << p->path_l_location[0] << '\n';
    if (p->path_l_location[1] != 0u)
       o << "  Path table L 1 start: " << p->path_l_location[1] << '\n';
    else
       o << "  Path table L 1: not present\n";
    o << "  Path table M 0 start: " << p->path_m_location[0] << '\n';
    if (p->path_m_location[1] != 0u)
       o << "  Path table M 1 start: " << p->path_m_location[1] << '\n';
    else
       o << "  Path table M 1: not present\n";

    o << "  Volume set identifier: ";
    print_trimmed_string(o, 128u, p->volume_set_identifier);
    o << '\n';
    o << "  Publisher identifier: ";
    print_trimmed_string(o, 128u, p->publisher_identifier);
    o << '\n';
    o << "  Data preparer identifier: ";
    print_trimmed_string(o, 128u, p->data_preparer_identifier);
    o << '\n';
    o << "  Application identifier: ";
    print_trimmed_string(o, 128u, p->application_identifier);
    o << '\n';

    o << "  Copyright file identifier: ";
    print_trimmed_string(o, 37u, p->copyright_file_identifier);
    o << '\n';
    o << "  Abstract file identifier: ";
    print_trimmed_string(o, 37u, p->abstract_file_identifier);
    o << '\n';
    o << "  Bibliographic file identifier: ";
    print_trimmed_string(o, 37u, p->bibliographic_file_identifier);
    o << '\n';

    o << "  Application use: ";
    print_string(o, 512u, p->application_use);
    o << '\n';
    o << '\n';

    if (print_hierarchies)
    {
      o << "  Directory hierarchy:\n";
      print_directory_hierarchy(o, 4u, p->root_directory);
    } else
      o << "  Root directory: " << p->root_directory << '\n';
  }
      

  for (std::vector<VolumePartition>::const_iterator p =
             v.volume_partitions.begin();
        p != v.volume_partitions.end(); ++p)
  {
    o << "Volume partition:\n";
    o << "  System identifier: ";
    print_trimmed_string(o, 32u, p->system_identifier);
    o << '\n';
    o << "  Volume partition identifier: ";
    print_trimmed_string(o, 32u, p->volume_partition_identifier);
    o << '\n';
    o << "  Start: " << p->start << '\n';
    o << "  Size: " << p->size << '\n';
    o << "  System use: ";
    print_string(o, 1960u, p->system_use);
    o << '\n';
  }
  
  for (std::vector<BootRecord>::const_iterator p =
             v.boot_records.begin();
        p != v.boot_records.end(); ++p)
  {
    o << "Boot record:\n";
    o << "  Boot system identifier: ";
    print_trimmed_string(o, 32u, p->boot_system_identifier);
    o << '\n';
    o << "  Boot identifier: ";
    print_trimmed_string(o, 32u, p->boot_identifier);
    o << '\n';
    o << "  System use: ";
    print_string(o, 1977u, p->boot_system_use);
    o << '\n';
  }
}
