/* 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 <cstring>
#include <cstddef>
#include <cstdlib>
#include <ostream>
#include <set>
#include <string>
#include <utility>
#include <vector>

#ifdef DEBUG
#include <iostream>
#endif

#include "Iso9660.h"
#include "Iso9660Check.h"

inline bool d_character(char c)
      { return (c >= '0' && c <= '9') ||
               (c >= 'A' && c <= 'Z') ||
               c == '_'; }

inline bool d1_character(char c) 
      { return c != '.' && c != ';'; }

inline bool a_character(char c)
      { return c == ' ' || c == '!' || c == '"' ||
               (c >= '%' && c <= '?') ||
               (c >= 'A' && c <= 'Z') ||
               c == '_'; }

bool is_blank(const char *cs, std::size_t sz)
{
  for (std::size_t i = 0u; i != sz; i++)
      if (cs[i] != ' ')
          return false;

  return true;
}
    
// We place no restrictions on a1-characters.
// For d1-characters, we assume that everything is allowable except for
// the characters . and ;, which are used as separators.

bool valid_a_sequence(bool is_primary, const char *cs, std::size_t sz)
{
  if (!is_primary)
     return true;

  for (std::size_t i = 0u; i != sz; i++)
      if (!a_character(cs[i]))
         return false;

  return true;
}

#define SKIP_D_OR_D1_SEQUENCE(is_primary, i, cs, sz) \
  if ((is_primary)) \
  { \
    for (; (i) != (sz); (i)++) \
        if (!d_character((cs)[(i)])) \
           break; \
  } else \
  { \
    for (; (i) != (sz); (i)++) \
        if (!d1_character((cs)[(i)])) \
           break; \
  }

// Sequence may be padded with blanks at end.
bool valid_d_sequence(bool is_primary, const char *cs, std::size_t sz)
{
  std::size_t i = 0;

  SKIP_D_OR_D1_SEQUENCE(is_primary, i, cs, sz);

  while (i != sz && cs[i] == ' ')
        i++;

  return (i == sz);
}

// No padding allowed.
bool valid_d_sequence(bool is_primary, const std::string &str)
{
  std::size_t i = 0;

  SKIP_D_OR_D1_SEQUENCE(is_primary, i, str.data(), str.size());

  return (i == str.size());
}

bool valid_iso9660_filename_a
        (bool allow_padding, 
         bool is_primary, const char *cs, std::size_t sz,
         bool *p_meets_83_restriction, std::size_t *p_name_plus_ext_len)
{
  std::size_t i = 0;
  std::size_t fn_len, ext_len;

  SKIP_D_OR_D1_SEQUENCE(is_primary, i, cs, sz);

  if ((fn_len = i) == sz || cs[i++] != '.')
     return false;

  SKIP_D_OR_D1_SEQUENCE(is_primary, i, cs, sz);

  if ((ext_len = i) == sz || cs[i++] != ';')
     return false;

  ext_len -= (fn_len + 1u);
 
  if (ext_len == 0u && fn_len == 0u)
     return false;

  u32 version = 0u;

  for (; i != sz; i++)
  {
    if (cs[i] < '0' && cs[i] > '9')
       break;

    version = version * 10u + (cs[i] - '0');
    if (version > 32767u)
       return false;
  }

  if (version == 0u)
     return false;

  if (allow_padding)
  {
    while (i != sz && cs[i] == ' ')
          i++;
  }

  if (i != sz)
     return false;

  if (p_meets_83_restriction != NULL)
     *p_meets_83_restriction = (fn_len <= 8u) && (ext_len <= 3u);

  if (p_name_plus_ext_len != NULL)
     *p_name_plus_ext_len = fn_len + ext_len;

  return true;
}

// Verify filename as per 7.5.1.
// Filename may be padded with blanks at end.
// This is for non-directory files only, directory names being 
// nonempty d- or d1-sequences (7.6.1.)
bool valid_iso9660_filename
        (bool is_primary, const char *cs, std::size_t sz,
         bool *p_meets_83_restriction, std::size_t *p_name_plus_ext_len)
{
  return valid_iso9660_filename_a(true, is_primary, cs, sz,
                                  p_meets_83_restriction, p_name_plus_ext_len);
}

// No padding allowed.
bool valid_iso9660_filename
        (bool is_primary, const std::string &str,
         bool *p_meets_83_restriction, std::size_t *p_name_plus_ext_len)
{
  return valid_iso9660_filename_a(false, is_primary, str.data(), str.size(),
                                  p_meets_83_restriction, p_name_plus_ext_len);
}

// Should only be called when D is a directory.
// Checks to see if file named with SZ characters starting @ CS is
// in the directory.  The SZ characters starting at CS are padded with blanks.
bool found_in_directory
    (const DirectoryRecord &d, const char *cs, std::size_t sz)
{
  assert(d.p_contents != NULL && d.p_contents->records.size() >= 2u);
  while (sz != 0u && cs[sz - 1] == ' ')
        sz--;
  for (std::vector<DirectoryRecord>::const_iterator 
                 p = d.p_contents->records.begin() + 2u;
                 p != d.p_contents->records.end(); ++p)
      if (p->name.size() == sz && 
          p->name.compare(0u, p->name.size(), cs, sz) == 0)
         return true;

  return false;
}

void check_volume_info(const VolumeInfo &vi, CheckingOutput *p_out)
{
  if (vi.volume_set_size == 0u ||
      vi.volume_sequence_number == 0u ||
      vi.volume_sequence_number > vi.volume_set_size)
     p_out->no_volume_seq_problem = false;
}

void check_boot_record(const VolumeInfo &, 
                       const BootRecord &br, 
                       CheckingOutput *p_out)
{
  if (!valid_a_sequence(true, br.boot_system_identifier, 32u) ||
      !valid_a_sequence(true, br.boot_identifier, 32u))
     p_out->character_set_restrictions_ok = false;
}

void check_volume_descriptor(const VolumeInfo &,
                             const VolumeDescriptor &vd,
                             CheckingOutput *p_out)
{
  if (!valid_a_sequence(vd.is_primary, vd.system_identifier, 32u) ||
      !valid_d_sequence(vd.is_primary, vd.volume_identifier, 32u) ||
      !valid_d_sequence(vd.is_primary, vd.volume_set_identifier, 128u))
     p_out->character_set_restrictions_ok = false;

  if (!valid_a_sequence(vd.is_primary, vd.publisher_identifier, 128u))
     p_out->character_set_restrictions_ok = false;

  if (vd.publisher_identifier[0] == '_')
  { 
    bool meets_83;

    // 8.4.20 / 8.5.14.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.publisher_identifier + 1, 127u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.publisher_identifier + 1, 127u))
        p_out->descriptor_files_found_in_root = false;
  } 

  if (!valid_a_sequence(vd.is_primary, vd.data_preparer_identifier, 128u))
     p_out->character_set_restrictions_ok = false;

  if (vd.data_preparer_identifier[0] == '_')
  { 
    bool meets_83;

    // 8.4.21 / 8.5.15.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.data_preparer_identifier + 1, 127u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.data_preparer_identifier + 1, 127u))
        p_out->descriptor_files_found_in_root = false;
  } 

  if (!valid_a_sequence(vd.is_primary, vd.application_identifier, 128u))
     p_out->character_set_restrictions_ok = false;

  if (vd.application_identifier[0] == '_')
  { 
    bool meets_83;

    // 8.4.22 / 8.5.16.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.application_identifier + 1, 127u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.application_identifier + 1, 127u))
        p_out->descriptor_files_found_in_root = false;
  } 

  if (!is_blank(vd.copyright_file_identifier, 37u))
  {
    bool meets_83;

    // 8.4.23 / 8.5.17.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.copyright_file_identifier, 37u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.copyright_file_identifier, 37u))
        p_out->descriptor_files_found_in_root = false;
  }
    
  if (!is_blank(vd.abstract_file_identifier, 37u))
  {
    bool meets_83;

    // 8.4.24 / 8.5.18.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.abstract_file_identifier, 37u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.abstract_file_identifier, 37u))
        p_out->descriptor_files_found_in_root = false;
  }
    
  if (!is_blank(vd.bibliographic_file_identifier, 37u))
  {
    bool meets_83;

    // 8.4.25 / 8.5.19.  NB: No 8.3 restriction in SVD.
    if (!valid_iso9660_filename
           (vd.is_primary, vd.bibliographic_file_identifier, 37u,
            &meets_83, (std::size_t *)NULL) ||
        (vd.is_primary && !meets_83))
        p_out->filename_restrictions_ok = false;

    if (!found_in_directory
             (vd.root_directory, vd.bibliographic_file_identifier, 37u))
        p_out->descriptor_files_found_in_root = false;
  }
}

void check_volume_partition(const VolumeInfo &vi,
                            const VolumePartition &vp,
                            CheckingOutput *p_out)
{
  if (!valid_a_sequence(true, vp.system_identifier, 32u) ||
      !valid_d_sequence(true, vp.volume_partition_identifier, 32u))
     p_out->character_set_restrictions_ok = false;

  // Presumably, the partition must be within the volume space.
  if ((double)vp.start + (double)vp.size > (double)vi.volume_space_size)
     p_out->blocks_within_range = false; 

  if (vi.logical_sector_size > vi.logical_block_size)
  {
    // 6.4.7: partition must start in lowest block of sector.
    if (vp.start % (vi.logical_sector_size / vi.logical_block_size) != 0)
       p_out->blocks_lowest_sector = false;
  }
}

void check_directory_record(const VolumeInfo &vi,
                            const DirectoryRecord &dr,
                            CheckingOutput *p_out)
{
  if (dr.file_unit_size != 0u || dr.interleave_gap != 0u)
  {
    // File unit size must be nonzero iff interleave gap is nonzero.
    // (this was enforced on reading)
    assert(dr.file_unit_size != 0u && dr.interleave_gap != 0u);

    // 6.4.3.4: extended attributes length == file unit size, if nonzero.
    if (dr.extended_attributes_length != 0u &&
        dr.extended_attributes_length != dr.file_unit_size)
       p_out->xar_length_ok = false;
  }

  if (dr.volume_sequence_number != vi.volume_sequence_number)
  // Multivolume.  Can't verify remaining issues.
  {
    if (dr.volume_sequence_number > vi.volume_set_size ||
        dr.volume_sequence_number == 0u)
       p_out->no_multivolume_problem = false;

    p_out->is_not_multivolume = false;
    return;
  }

  if (dr.is_directory())
  {
    // 6.8.1, 6.8.1.[123]: directory starts in the lowest block of a sector.
    if (vi.logical_sector_size > vi.logical_block_size)
    {
      if ((dr.start + (u32)dr.extended_attributes_length) %
              (vi.logical_sector_size / vi.logical_block_size) != 0)
         p_out->blocks_lowest_sector = false;
    }
  } 

  if (dr.file_unit_size != 0u || dr.interleave_gap != 0u)
  {
    u32 num_file_units = dr.length / 
                           (vi.logical_block_size * (u32)dr.file_unit_size);

    if (num_file_units % (vi.logical_block_size * (u32)dr.file_unit_size)
                         != 0u)
       num_file_units++;

    if (dr.extended_attributes_length != 0u)
       num_file_units++;

    if (num_file_units > 0u)
    {
      if ((double)dr.start + 
          (double)(num_file_units - 1) * (double)dr.interleave_gap + 
          (double)num_file_units * (double)dr.file_unit_size >
          (double)vi.volume_space_size)
         p_out->blocks_within_range = false;
    } 
    
    // 6.4.3.1: each file unit must start in the lowest block of the
    // logical sector.
    if (vi.logical_sector_size > vi.logical_block_size)
    {
      if (dr.start % (vi.logical_sector_size / vi.logical_block_size) != 0)
          p_out->blocks_lowest_sector = false;

      if (num_file_units > 1u &&
          ((u32)dr.file_unit_size + (u32)dr.interleave_gap) % 
           (vi.logical_sector_size / vi.logical_block_size) != 0)
          p_out->blocks_lowest_sector = false;
    }
  } else
  {
    u32 num_logical_blocks = dr.length / vi.logical_block_size;
    if (dr.length % vi.logical_block_size != 0u)
       num_logical_blocks++;
    num_logical_blocks += (u32)dr.extended_attributes_length;
    if (num_logical_blocks > 0u)
    {
      if ((double)dr.start + (double)num_logical_blocks > 
          (double)vi.volume_space_size)
         p_out->blocks_within_range = false;
    }
  }
}

//
// Check coherence of a chunk of dir records which represent the same
// file or directory.
//
void check_file_coherency
    (const std::vector<DirectoryRecord>::const_iterator &a,
     const std::vector<DirectoryRecord>::const_iterator &b,
     CheckingOutput *p_out)
{
  assert(a != b);

  if (a + 1 != b)
     p_out->no_multisection_files = false;

  if (a->is_directory())
  {
    // Directories can't be assoc'd files (already checked.)
    assert((a->flags & 4u) == 0u);
    // 6.5.1: Each directory should have exactly one section,
    // and not have the same ID as any other directory or file.
    if (a + 1 != b)
       p_out->directory_coherency = false;
  }

  // MULTI-EXTENT should be set for all records x/c last.
  for (std::vector<DirectoryRecord>::const_iterator q = a; q != b - 1; ++q)
      if ((q->flags & 128u) == 0u)
          p_out->directory_coherency = false;
 
  if (((b - 1)->flags & 128u) != 0u)
     p_out->directory_coherency = false;

  for (std::vector<DirectoryRecord>::const_iterator q = a + 1;
       q != b; ++q)
  {
    // 9.2: all flag bits same x/c PROTECTION, MULTI-EXTENT.
    if ((a->flags & 111u) != (q->flags & 111u))
       p_out->directory_coherency = false;
  }

  bool have_recfm = false;
  unsigned char the_recfm = 0u;
  unsigned char the_record_attributes = 0u;
  u32 the_record_length = 0u;

  for (std::vector<DirectoryRecord>::const_iterator q = a; q != b; ++q)
  {
    // We may have been unable to read in the XAR owing to
    // the file being on a different volume.
    if (q->extended_attributes_length != 0u && q->p_xar == NULL)
       continue;

    // If the PROTECTION bit is set, the XAR should be present,
    // and with conditions as specified (9.1.6).  If it is not, either the
    // XAR should not be present, or the conditions should be as specified
    // (9.1.6).
    if ((q->flags & 16u) != 0u)
    {
      if (q->p_xar == NULL || q->p_xar->owner_id == 0u ||
          (q->p_xar->permissions & 0x5555u == 0u))
          p_out->directory_coherency = false;
    } else
    {
      if (q->p_xar != NULL && q->p_xar->owner_id != 0u)
          p_out->directory_coherency = false;
    }

    // If the record format is specified and nonzero anywhere, we 
    // require it to be the same everywhere (6.4.6), and we also require the 
    // record attributes to be identical (9.6), and the record length if 
    // fixed-format (9.6).  Also, we require the RECORD bit in the flags
    // (9.1.6) to be set iff RECFM is present and nonzero.
    if ((q->flags & 8u) != 0u)
    {
      if (q->p_xar == NULL || q->p_xar->record_format == 0u)
          p_out->directory_coherency = false;
    } else
    {
      if (q->p_xar != NULL && q->p_xar->record_format != 0u)
          p_out->directory_coherency = false;
    }

    if (!have_recfm)
    {
      if (q->p_xar == NULL || q->p_xar->record_format == 0u)
         continue;

      have_recfm = true;
      the_recfm = q->p_xar->record_format;
      the_record_attributes = q->p_xar->record_attributes;
      the_record_length = q->p_xar->record_length;
    } else
    {
      if (q->p_xar == NULL)
         p_out->directory_coherency = false;
      else if (q->p_xar->record_format != the_recfm ||
               q->p_xar->record_attributes != the_record_attributes ||
               (the_recfm == 1u &&
                q->p_xar->record_length != the_record_length))
         p_out->directory_coherency = false;
    }
  }
}

//
// Check coherence of a chunk of dir records which have the same 
// file or directory identifier.
//
void check_chunk_coherency
    (const std::vector<DirectoryRecord>::const_iterator &a,
     const std::vector<DirectoryRecord>::const_iterator &b,
     CheckingOutput *p_out)
{
  assert(a != b);

  // Associated files must come before non-associated (9.3).
  // Also, an assoc file must be assoc'd with some file.
  if ((a->flags & 4u) != 0u)
  {
    std::vector<DirectoryRecord>::const_iterator q = a;
    while (q != b && ((q->flags & 4u) != 0u))
          ++q;
    if (q == b)
        p_out->directory_coherency = false;
    else
        check_file_coherency(q, b, p_out);
    assert(q != a);
    check_file_coherency(a, q, p_out);
  } else
    check_file_coherency(a, b, p_out);
}

bool check_less_than
        (SortingType sort_type, bool a_isdir, bool b_isdir,
         const std::string &a, const std::string &b)
{
  switch (sort_type)
  {
    case Sort_None:
         return true;

    case Sort_Lex:
         return a < b;

    case Sort_Iso9660:
         // We compare using (a) lex order of filename, (b) lex order of
         // extension, and (c) reverse numerical order of version number,
         // in that order.  In the case of a PVD, this is equivalent  
         // to the sort order prescribed in 9.3, as all d-characters are
         // >0x20.  In the case of an SVD, this is not equivalent to
         // the standard.  It appears to be what is done in Joliet, however.

         // Directories are treated as having null extensions 
         // and version numbers.
         {
           if (a_isdir)
           {
             if (b_isdir)
                return a < b;
  
             std::string::size_type j = b.find_first_of(".");
             return b.compare(0u, j, a) > 0;
           } else if (b_isdir)
           {
             std::string::size_type i = a.find_first_of(".");
             return a.compare(0u, i, b) < 0;
           }
           std::string::size_type i = a.find_first_of(".");
           std::string::size_type j = b.find_first_of(".");
           int cv = a.compare(0u, i, b, 0u, j);
           if (cv != 0)
              return cv < 0;
  
           if (i == std::string::npos)
              i = 0u;
           else
              i++;
           if (j == std::string::npos)
              j = 0u;
           else
              j++;
  
           std::string::size_type i2 = a.find_first_of(";", i);
           std::string::size_type j2 = b.find_first_of(";", j);
           int cv2 = a.compare(i, i2 - i, b, j, j2 - j);
           if (cv2 != 0)
              return cv2 < 0;
  
           // Compare version numbers
           int a_vn = 
              ((i2 == std::string::npos) ? 0 : atoi(a.c_str() + i2 + 1u));
           int b_vn = 
              ((j2 == std::string::npos) ? 0 : atoi(b.c_str() + j2 + 1u));
  
           return a_vn > b_vn;
         }

    default:
         assert(0);
         return true; 
  }
  /*NOTREACHED*/
}

// Shallow compare.  Don't check contents, XAR, system use, root/./.. flags,
//                               names, or dates.
bool equal_directory_records_except_for_date
     (const DirectoryRecord &a, const DirectoryRecord &b)
{
  return 
       a.extended_attributes_length == b.extended_attributes_length &&
       a.start == b.start && 
       a.length == b.length && 
       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;
}

//
// Check coherence of hierarchy: . and .. present, equal to desired directories
// 
void check_directory_hierarchy
   (bool is_primary, SortingType sort_type, const VolumeInfo &vi, 
    DirectoryRecord &d, const DirectoryRecord &parent,
    std::size_t level, std::size_t preceding_length,
    CheckingOutput *p_out)
{
  // Checked on read-in
  assert(d.volume_sequence_number == vi.volume_sequence_number &&
         d.p_contents != NULL &&
         d.p_contents->records.size() >= 2u);

  // Check level
  if (level > p_out->max_level)
     p_out->max_level = level;

  std::vector<DirectoryRecord>::iterator p = 
                                       d.p_contents->records.begin();

  // We allow the `system use' fields to vary (necessary for RR.)
  if (!equal_directory_records(*p, d))
     p_out->dot_coherency = false;
 
  if (!equal_directory_records_except_for_date(*p, d))
     p_out->almost_dot_coherency = false;

  if (!equal_directory_records(p[1], parent))
     p_out->dotdot_coherency = false;

  if (!equal_directory_records_except_for_date(p[1], parent))
     p_out->almost_dotdot_coherency = false;

  // Check directory records, and attempt to parse XARs.  Also, check lengths.
  for (p = d.p_contents->records.begin(); p != d.p_contents->records.end(); ++p)
  {
    check_directory_record(vi, *p, p_out);

    // Prohibit empty identifiers (7.5, 7.6)
    if (p->name.empty())
       p_out->directory_coherency = false;

    // Attempt to read XAR, if possible & necessary.
    assert(p->p_xar == NULL);

    if (p->volume_sequence_number == vi.volume_sequence_number &&
        p->extended_attributes_length != 0u)
    {
      assert(p->xar.size() == (u32)p->extended_attributes_length *
                              (u32)vi.logical_block_size);

      if ((p->p_xar = parse_xar(p->xar)) == NULL)
          p_out->xars_parse_ok = false;
      else
      {
        // 9.5.11
        if (!valid_a_sequence(is_primary, p->p_xar->system_identifier, 32u))
           p_out->character_set_restrictions_ok = false;
      }
    }

    if (p == d.p_contents->records.begin() ||
        p == d.p_contents->records.begin() + 1)
       // This is . or .., so don't check name.
       continue;

    std::size_t pathname_length = preceding_length + level + p->name.size();

    if (p->is_directory())
    {
      // 7.6.1
      if (!valid_d_sequence(is_primary, p->name))
         p_out->filename_restrictions_ok = false;

      // 10.1
      if (p->name.size() > 8)
         p_out->level_1_name_restrictions_ok = false;

      if (p->name.size() > p_out->max_directory_identifier_length)
         p_out->max_directory_identifier_length = p->name.size();

      if (pathname_length > p_out->max_directory_pathname_length)
         p_out->max_directory_pathname_length = pathname_length;
    } else
    {
      bool meets_83;
      std::size_t ne_len;

      // 7.5.1
      if (!valid_iso9660_filename(is_primary, p->name, &meets_83, &ne_len))
         p_out->filename_restrictions_ok = false;
      else 
      {
        if (ne_len > p_out->max_non_dir_name_plus_extension_length)
           p_out->max_non_dir_name_plus_extension_length = ne_len;

        // 10.1
        if (!meets_83)
           p_out->level_1_name_restrictions_ok = false;
      }
       
      if (p->name.size() > p_out->max_non_dir_identifier_length)
         p_out->max_non_dir_identifier_length = p->name.size();

      if (pathname_length > p_out->max_non_dir_pathname_length)
         p_out->max_non_dir_pathname_length = pathname_length;
    }
  }

  // Check uniqueness of identifiers (6.5.1) and coherency
  // of multiple records for the same file.
  // We assume that identical identifiers are placed together.
  if (d.p_contents->records.size() > 2u)
  {
    std::set<std::string> names;
    std::vector<DirectoryRecord>::const_iterator 
              last = d.p_contents->records.begin() + 2;

    assert(names.insert(last->name).second);

    for (p = d.p_contents->records.begin() + 3; 
         p != d.p_contents->records.end(); ++p)
    {
      if (p->name != last->name)
      {
        check_chunk_coherency(last, p, p_out);
        if (!names.insert(p->name).second)
           p_out->directory_coherency = false;
        if (!check_less_than(sort_type, last->is_directory(), p->is_directory(),
                                        last->name, p->name))
           p_out->directory_sorted = false;
        last = p;
      }
    }
    if (last != p)
        check_chunk_coherency(last, p, p_out);
  }

  // Recurse.
  for (p = d.p_contents->records.begin() + 2; 
       p != d.p_contents->records.end(); ++p)
      if (p->is_directory())
          check_directory_hierarchy(is_primary, sort_type, vi, *p, d, 
                                    level + 1u, 
                                    preceding_length + p->name.size(),
                                    p_out);
}

class UsedBlockSet
{
  struct UsedBlock
  {
    u32 start;
    u32 length;
    bool cant_share;   // false for file pieces, true o.w.

    UsedBlock() : start(0u), length(0u), cant_share(true) { }

    UsedBlock(u32 st, u32 len, bool non_sharable) :
      start(st), length(len), cant_share(non_sharable) { }

  };
  friend bool operator <(const UsedBlock &a, const UsedBlock &b);

  std::set<UsedBlock> block_chunks;
  bool have_overlap;

public:

  UsedBlockSet() : block_chunks(), have_overlap(false) { }

  void add(u32 st, u32 len, bool non_sharable = true)
  {
    if (len == 0u)
       return;

    std::pair<std::set<UsedBlock>::const_iterator, bool> rv =
                      block_chunks.insert(UsedBlock(st, len, non_sharable));

    if (!rv.second && non_sharable)
    {
      have_overlap = true;
      return;
    }
  }
 
  bool check_ok(u32 *);
};

bool UsedBlockSet::check_ok(u32 *p_num_blocks)
{
  UsedBlock last_block;

  if (have_overlap)
     return false;

  for (std::set<UsedBlock>::const_iterator q = block_chunks.begin();
       q != block_chunks.end(); ++q)
  {
#ifdef DEBUG
    std::cout << "block start " << q->start << " len " << q->length << '\n';
#endif
    if (q != block_chunks.begin())
    {
      assert(last_block.start <= q->start);
      if ((double)q->start < (double)last_block.start + 
                             (double)last_block.length)
         return false;
    }

    last_block = *q;
  }

  if (p_num_blocks != NULL)
  {
    if (block_chunks.empty())
       *p_num_blocks = 0u;
    else
       *p_num_blocks = last_block.start + last_block.length;
  }
  return true;
}

// No directory can belong to >1 hierarchy (6.8.3.)
// Also, verify tree structure of hierarchy.
// We also verify that blocks in file/directory extents 
// are within the volume space.
void check_directory_hierarchy_blocks(UsedBlockSet *p_block_chunk_set,
                   bool check_non_directories,
                   const VolumeInfo &vi, const DirectoryRecord &d)
{
  if (check_non_directories || d.is_directory())
  {
    if (d.file_unit_size != 0u)
    {
      u32 num_file_units = d.length / 
                             (vi.logical_block_size * (u32)d.file_unit_size);
  
      if (num_file_units % (vi.logical_block_size * (u32)d.file_unit_size)
                           != 0u)
         num_file_units++;
  
      if (d.extended_attributes_length != 0u)
         num_file_units++;
 
      for (u32 i = 0; i < num_file_units; i++)
          p_block_chunk_set->add(d.start + 
                          i * ((u32)d.file_unit_size + (u32)d.interleave_gap),
                          (u32)d.file_unit_size, d.is_directory() );
    } else
    {
      u32 num_logical_blocks = d.length / vi.logical_block_size;
      if (d.length % vi.logical_block_size != 0u)
         num_logical_blocks++;
      num_logical_blocks += (u32)d.extended_attributes_length;

      if (num_logical_blocks != 0u)
         p_block_chunk_set->add
                   (d.start, num_logical_blocks, d.is_directory() );
    }
  }

  if (!d.is_directory())
     return;

  // Checked on read-in.
  assert(d.p_contents != NULL && d.p_contents->records.size() >= 2u);

  // Recurse.
  for (std::vector<DirectoryRecord>::const_iterator 
            p = d.p_contents->records.begin() + 2u; 
            p != d.p_contents->records.end(); ++p)
       check_directory_hierarchy_blocks
             (p_block_chunk_set, check_non_directories, vi, *p);
}

bool operator <(const UsedBlockSet::UsedBlock &a, 
                const UsedBlockSet::UsedBlock &b)
{
  if (a.start < b.start) return true;
  if (a.start > b.start) return false;
  if (a.length < b.length) return true;
  if (a.length > b.length) return false;
  return a.cant_share < b.cant_share;
}

void check_volume(Volume &v, const CheckingOptions &opts, CheckingOutput *p_out)
{
  *p_out = CheckingOutput();

  if (opts.do_volume_info)
  {
    check_volume_info(v.volume_info, p_out);
    p_out->num_volume_space_blocks = v.volume_info.volume_space_size;
  }

  if (opts.do_boot_records)
  for (std::vector<BootRecord>::const_iterator p =
             v.boot_records.begin();
        p != v.boot_records.end(); ++p)
      check_boot_record(v.volume_info, *p, p_out);

  if (opts.do_volume_partitions)
  for (std::vector<VolumePartition>::const_iterator p =
             v.volume_partitions.begin();
        p != v.volume_partitions.end(); ++p)
      check_volume_partition(v.volume_info, *p, p_out);

  if (opts.do_volume_descriptor == -1)
  {
    for (std::vector<VolumeDescriptor>::iterator p =
               v.volume_descriptors.begin();
         p != v.volume_descriptors.end(); ++p)
    {
      check_volume_descriptor(v.volume_info, *p, p_out);
      check_directory_record(v.volume_info, p->root_directory, p_out);
      check_directory_hierarchy
          (p->is_primary, 
           (p->is_primary ? opts.primary_sorting_type : 
                            opts.secondary_sorting_type), 
           v.volume_info, p->root_directory, p->root_directory, 1u, 1u, p_out);
    }
  }
  else if (opts.do_volume_descriptor >= 0 &&
         (unsigned int)opts.do_volume_descriptor < v.volume_descriptors.size())
  {
    VolumeDescriptor *p = &v.volume_descriptors[opts.do_volume_descriptor];

    check_volume_descriptor(v.volume_info, *p, p_out);
    check_directory_record(v.volume_info, p->root_directory, p_out);
    check_directory_hierarchy
        (p->is_primary, 
         (p->is_primary ? opts.primary_sorting_type : 
                          opts.secondary_sorting_type), 
         v.volume_info, p->root_directory, p->root_directory, 1u, 1u, p_out);
  }

  if (opts.do_overlapping_partitions)
  {
    UsedBlockSet block_chunks;

    // System area 
    block_chunks.add(0u,
  16u * (v.volume_info.logical_sector_size / v.volume_info.logical_block_size)
               );
    for (std::vector<VolumePartition>::const_iterator p =
               v.volume_partitions.begin();
          p != v.volume_partitions.end(); ++p)
        block_chunks.add(p->start, p->size);

    p_out->no_overlapping_partitions = 
	              block_chunks.check_ok(&p_out->num_partition_blocks);
  }

  if (opts.do_overlapping_hierarchies)
  {
    UsedBlockSet block_chunks;

    assert
  (v.volume_info.logical_sector_size % v.volume_info.logical_block_size == 0);

    // System area + descriptors
    block_chunks.add(0u,
  (16u + v.volume_info.num_descriptor_sectors) 
       * (v.volume_info.logical_sector_size / v.volume_info.logical_block_size)
               );
    for (std::vector<VolumeDescriptor>::const_iterator p 
                 = v.volume_descriptors.begin();
         p != v.volume_descriptors.end(); ++p)
    {
      u32 path_table_blocks = 
            p->path_table_size / v.volume_info.logical_block_size;
      if (p->path_table_size % v.volume_info.logical_block_size != 0u)
         path_table_blocks++;

      block_chunks.add(p->path_l_location[0], path_table_blocks);
      if (p->path_l_location[1] != 0u)
          block_chunks.add(p->path_l_location[1], path_table_blocks);

      block_chunks.add(p->path_m_location[0], path_table_blocks);
      if (p->path_m_location[1] != 0u)
          block_chunks.add(p->path_m_location[1], path_table_blocks);

      check_directory_hierarchy_blocks(&block_chunks, false,
                                       v.volume_info, p->root_directory);
    }

    p_out->no_overlapping_hierarchies = 
	              block_chunks.check_ok(&p_out->num_hierarchy_blocks);
  }

  if (opts.do_overlapping_files)
  {
    UsedBlockSet block_chunks;

    assert(v.volume_info.logical_sector_size % 
           v.volume_info.logical_block_size == 0);

    // System area + descriptors
    block_chunks.add(0u,
  (16u + v.volume_info.num_descriptor_sectors) 
       * (v.volume_info.logical_sector_size / v.volume_info.logical_block_size)
               );
    for (std::vector<VolumeDescriptor>::const_iterator p 
                 = v.volume_descriptors.begin();
         p != v.volume_descriptors.end(); ++p)
    {
      u32 path_table_blocks = 
            p->path_table_size / v.volume_info.logical_block_size;
      if (p->path_table_size % v.volume_info.logical_block_size != 0u)
         path_table_blocks++;

      block_chunks.add(p->path_l_location[0], path_table_blocks);
      if (p->path_l_location[1] != 0u)
          block_chunks.add(p->path_l_location[1], path_table_blocks);

      block_chunks.add(p->path_m_location[0], path_table_blocks);
      if (p->path_m_location[1] != 0u)
          block_chunks.add(p->path_m_location[1], path_table_blocks);

      check_directory_hierarchy_blocks(&block_chunks, true,
                                       v.volume_info, p->root_directory);
    }

    p_out->no_overlapping_files = 
	              block_chunks.check_ok(&p_out->num_file_blocks);
  }
}

const char *name(SortingType s)
{
  switch (s)
  {
    case Sort_Iso9660: return "ISO 9660";
    case Sort_Lex: return "lexicographic";
    case Sort_None: return "none";
    default: assert(0); return "";
  }
}

void print_options_report(std::ostream &o, const CheckingOptions &opts)
{
  o << "  Check volume info: " << yes_no(opts.do_volume_info) << '\n'
    << "  Check boot records: " << yes_no(opts.do_boot_records) << '\n'
    << "  Check volume partitions: " << yes_no(opts.do_volume_partitions) 
                     << '\n'
    << "  Check volume descriptor(s): " 
                    << yes_no(opts.do_volume_descriptor >= -1) << '\n';

  if (opts.do_volume_descriptor == -1)
     o << "  Volume descriptors to check: all\n";
  else if (opts.do_volume_descriptor >= 0)
     o << "  Volume descriptor to check: " << opts.do_volume_descriptor << '\n';

  o << "  Check overlapping volume partitions: " 
                     << yes_no(opts.do_overlapping_partitions) << '\n'
    << "  Check overlapping hierarchies: " 
                     << yes_no(opts.do_overlapping_hierarchies) << '\n'
    << "  Check overlapping files: " 
                     << yes_no(opts.do_overlapping_files) << '\n'
    << "  Primary sorting type: " << name(opts.primary_sorting_type) << '\n'
    << "  Secondary sorting type: " 
                            << name(opts.secondary_sorting_type) << '\n';

  o << "\n\n";
}

void print_output_report(std::ostream &o, const CheckingOutput &out)
{
  o << "Errors:\n\n"
    << "  . coherency: " << yes_no(out.dot_coherency) << '\n' 
    << "  . coherency x/c for date: " 
                         << yes_no(out.almost_dot_coherency) << '\n' 
    << "  .. coherency: " << yes_no(out.dotdot_coherency) << '\n' 
    << "  .. coherency x/c for date: " 
                          << yes_no(out.almost_dotdot_coherency) << '\n' 
    << "  Directory coherency: " << yes_no(out.directory_coherency) << '\n' 
    << "  Directory sorted: " << yes_no(out.directory_sorted) << '\n' 
    << "  Blocks within range: " << yes_no(out.blocks_within_range) << '\n' 
    << "  Blocks @ lowest sector: " << yes_no(out.blocks_lowest_sector) << '\n' 
    << "  XAR lengths OK: " << yes_no(out.xar_length_ok) << '\n' 
    << "  XARs parse OK: " << yes_no(out.xars_parse_ok) << '\n' 
    << "  No volume seq problem: " << yes_no(out.no_volume_seq_problem) << '\n' 
    << "  No multivolume problem: " 
                  << yes_no(out.no_multivolume_problem) << '\n' 
    << "  No overlapping hierarchies, etc., or non-tree hierarchies: " 
                  << yes_no(out.no_overlapping_hierarchies) << '\n' 
    << "  ISO 9660 character set restrictions OK: " 
                  << yes_no(out.character_set_restrictions_ok) << '\n' 
    << "  ISO 9660 filename restrictions (id = name.ext;version-number) OK: " 
                  << yes_no(out.filename_restrictions_ok) << '\n' 
    << "  Files referenced in volume descriptor found in root: " 
                  << yes_no(out.descriptor_files_found_in_root) << '\n' 
    << "\n\nWarnings:\n\n"
    << "  Is not multivolume: " << yes_no(out.is_not_multivolume) << '\n' 
    << "  No multisection files (req'd for ISO 9660 level 1 or 2): " << yes_no(out.no_multisection_files) << '\n' 
    << "  No overlapping partitions: " 
                  << yes_no(out.no_overlapping_partitions) << '\n' 
    << "  No overlapping files: " 
                  << yes_no(out.no_overlapping_files) << '\n' 
    << "  ISO 9660 level 1 name restrictions OK: " 
                  << yes_no(out.level_1_name_restrictions_ok) << '\n' 
    << "\n\nMaxima:\n\n"
    << "  Maximum directory nesting level: " << out.max_level 
    << "\n          (maximum for standard: 8)\n"
    << "  Maximum non-directory pathname length: " 
            << out.max_non_dir_pathname_length
    << "\n          (maximum for standard: 255)\n"
    << "  Maximum directory pathname length: " 
            << out.max_directory_pathname_length
    << "\n          (maximum for standard: not specified)\n"
    << "  Maximum non-directory identifier length: " 
            << out.max_non_dir_identifier_length
    << "\n          (maximum for standard: not specified)\n"
    << "  Maximum non-directory name-plus-extension length: " 
            << out.max_non_dir_name_plus_extension_length
    << "\n          (maximum for standard: 30)\n"
    << "  Maximum directory identifier length: " 
            << out.max_directory_identifier_length
    << "\n          (maximum for standard: 31)\n"
    << "\n\nOther:\n\n"
    << "Number of logical blocks used (headers + partitions): " 
	   << out.num_partition_blocks << '\n'
    << "Number of logical blocks used (headers + directory hierarchy): " 
	   << out.num_hierarchy_blocks << '\n'
    << "Number of logical blocks used (headers + files): " 
	   << out.num_file_blocks << '\n'
    << "Number of logical blocks in volume space: "
	   << out.num_volume_space_blocks << '\n';
}
