/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <qdisamint.h>


static bool checkContinue(void);
static bool zerobits(unsigned char * base, unsigned bitCount, long offset, unsigned startBit, unsigned runLength);
static bool bit(unsigned char * base, unsigned offset);
static void bitset(unsigned char * base, unsigned offset, unsigned bitCount);
static void bitset(unsigned char * base, unsigned offset);
static void bitclr(unsigned char * base, unsigned offset, unsigned bitCount);
static void bitclr(unsigned char * base, unsigned offset);
static long block2fptr(unsigned long block);



static void bitset(unsigned char * base, unsigned offset) {
  unsigned o, b;
  o = offset / 8;
  b = offset % 8;
  switch (b) {
  case 0: base[o] |= 0x80; break;
  case 1: base[o] |= 0x40; break;
  case 2: base[o] |= 0x20; break;
  case 3: base[o] |= 0x10; break;
  case 4: base[o] |= 0x08; break;
  case 5: base[o] |= 0x04; break;
  case 6: base[o] |= 0x02; break;
  case 7: base[o] |= 0x01; break;
  };
}

static void bitclr(unsigned char * base, unsigned offset, unsigned bitCount) {
  unsigned i;
  for (i=0;i<bitCount;i++) bitclr(base, offset+i);
}

static void bitclr(unsigned char * base, unsigned offset) {
  unsigned o, b;
  o = offset / 8;
  b = offset % 8;
  switch (b) {
  case 0: base[o] &= 0x7f; break;
  case 1: base[o] &= 0xbf; break;
  case 2: base[o] &= 0xdf; break;
  case 3: base[o] &= 0xef; break;
  case 4: base[o] &= 0xf7; break;
  case 5: base[o] &= 0xfb; break;
  case 6: base[o] &= 0xfd; break;
  case 7: base[o] &= 0xfe; break;
  };
}

static void bitset(unsigned char * base, unsigned offset, unsigned bitCount) {
  unsigned i;
  for (i=0;i<bitCount;i++) bitset(base, offset+i);
}

static bool bit(unsigned char * base, unsigned offset) {
  unsigned o, b;
  o = offset / 8;
  b = offset % 8;
  switch (b) {
  case 0: return ((base[o] & 0x80) != 0);
  case 1: return ((base[o] & 0x40) != 0);
  case 2: return ((base[o] & 0x20) != 0);
  case 3: return ((base[o] & 0x10) != 0);
  case 4: return ((base[o] & 0x08) != 0);
  case 5: return ((base[o] & 0x04) != 0);
  case 6: return ((base[o] & 0x02) != 0);
  case 7: return ((base[o] & 0x01) != 0);
  };
  return false;
}

static bool zerobits(unsigned char * base, unsigned bitCount, long offset, unsigned startBit, unsigned runLength) {
// Divide the run we're looking for into an initial byte, a length of null bytes, and a final byte.
  unsigned initialByte = 0;
  unsigned finalByte = 0;
  unsigned nullLength = 0;
  unsigned i;

  if ((offset * 8 + startBit + runLength) > bitCount) return false;

  if (startBit + runLength <= 8) {
// We're looking within a single byte.
    switch(startBit) {
    case 0: initialByte = 0x80; break;
    case 1: initialByte = 0x40; break;
    case 2: initialByte = 0x20; break;
    case 3: initialByte = 0x10; break;
    case 4: initialByte = 0x08; break;
    case 5: initialByte = 0x04; break;
    case 6: initialByte = 0x02; break;
    case 7: initialByte = 0x01; break;
    };
    for (i=1;i<runLength; i++) initialByte |= (initialByte >> 1);
    return ((base[offset] & initialByte) == 0);
  } else {
// We're stretching over at least two bytes.
    switch (startBit) {
    case 0: initialByte = 0xff; break;
    case 1: initialByte = 0x7f; break;
    case 2: initialByte = 0x3f; break;
    case 3: initialByte = 0x1f; break;
    case 4: initialByte = 0x0f; break;
    case 5: initialByte = 0x07; break;
    case 6: initialByte = 0x03; break;
    case 7: initialByte = 0x01; break;
    };

    if ((base[offset] & initialByte) != 0) return false;

    nullLength = ((startBit + runLength - 1) / 8) - 1;

    for (i=1;i<=nullLength;i++) if (base[offset + i] != 0) return false;

    switch ((startBit + runLength) % 8) {
    case 0: finalByte = 0xff;
    case 1: finalByte = 0x80;
    case 2: finalByte = 0xc0;
    case 3: finalByte = 0xe0;
    case 4: finalByte = 0xf0;
    case 5: finalByte = 0xf8;
    case 6: finalByte = 0xfc;
    case 7: finalByte = 0xfe;
    };
    return ((base[offset+nullLength+1] & finalByte) == 0);
  };
}



bool checkContinue(void) {
  char line[80];
  fflush(0);
  fprintf(stdout, "\nContinue (Y/N)?"); fflush(stdout);
  fgets(line, sizeof(line), stdin);
  fprintf(stdout, "\n");
  if ((line[0] == 'y') || (line[0] == 'Y')) {
    return true;
  };
  return false;
}

dbHeader_t dbHeader;
unsigned long logicalBlocksize;
unsigned long physicalBlocksize;
unsigned long initialElements;
unsigned long idxRecords;
unsigned long elements;
unsigned long datBlocks;
unsigned long bitBytes;
unsigned long key;
unsigned long keymap_size;
unsigned char * keymap;

static long block2fptr(unsigned long block) {
  if (block == 0) return -1l;
  return (block-1) * physicalBlocksize + sizeof(dbHeader_t);
}


int main(int argc, char * argv[]) {

  FILE * idxFile;
  FILE * datFile;
  FILE * bitFile;
  int filearg;
  char fname[1024];
  unsigned  i;
  unsigned long offset;
  index_record_t idx;
  block_header_t block_header;
  unsigned char * buffer;
  unsigned char * bitmap;
  bool displayRecords;
  unsigned long bytesLeft;
  unsigned long bytesThisBlock;
  unsigned long flags;
  record_header_t record_header;
  unsigned long blockNumber;

  if (argc < 2) {
    fprintf(stderr, "Usage: OsView <Objectstore>\n");
    return EXIT_FAILURE;
  };
  if (argc > 2) {
    fprintf(stderr, "Usage: OsView <Objectstore>\n");
    return EXIT_FAILURE;
  };

  filearg = 1;

  strcpy(fname, argv[filearg]);
  strcat(fname, ".jdx");
  
  if ((idxFile = fopen(fname, "rb")) == NULL) {
    fprintf(stderr, "Error: Can't open index file %s\n", fname);
    return EXIT_FAILURE;
  };


  strcpy(fname, argv[filearg]);
  strcat(fname, ".jdt");
  
  if ((datFile = fopen(fname, "rb")) == NULL) {
    fprintf(stderr, "Error: Can't data index file %s\n", fname);
    return EXIT_FAILURE;
  };


  strcpy(fname, argv[filearg]);
  strcat(fname, ".jbm");
  
  if ((bitFile = fopen(fname, "rb")) == NULL) {
    fprintf(stderr, "Error: Can't open bitmap file %s\n", fname);
    return EXIT_FAILURE;
  };

  if (fread(&dbHeader, sizeof(dbHeader), 1, datFile) != 1) {
    fprintf(stderr, "Error reading header from data file.  File is too short - Database irrecoverable\n");
    return EXIT_FAILURE;
  };

  if (memcmp(dbHeader.ident, st_ident, sizeof(st_ident)) != 0) {
    fprintf(stderr, "Datafile header ident incorrect.\n");
    fprintf(stderr, "Expecting: \"%s\"\n", st_ident);
    fprintf(stderr, "    Found: \"%s\"\n", dbHeader.ident);
    if (!checkContinue()) return EXIT_FAILURE;
  };

  logicalBlocksize = dbHeader.val.blocksize;
  physicalBlocksize = logicalBlocksize + sizeof(block_header_t);
  initialElements = 8 * ((dbHeader.val.elements + 7) / 8);

  fprintf(stdout, "Datafile logical blocksize: %ld\n", logicalBlocksize);
  fprintf(stdout, "Datafile physical blocksize: %ld\n", physicalBlocksize);
  fprintf(stdout, "Datafile creation size (elements): %ld\n", initialElements);

  fseek(idxFile, 0, SEEK_END);
  idxRecords = ftell(idxFile) / sizeof(index_record_t);
  elements = idxRecords;

  fseek(datFile, 0, SEEK_END);
  datBlocks = (ftell(datFile) - sizeof(dbHeader_t)) / physicalBlocksize;

  fseek(bitFile, 0, SEEK_END);
  bitBytes = ftell(bitFile);

  fprintf(stdout, "Indexfile records: %ld\n", idxRecords);
  fprintf(stdout, "Datafile blocks: %ld\n", datBlocks);
  fprintf(stdout, "Bitmap size: %ld\n", bitBytes);

  if (((datBlocks + 7) / 8) < bitBytes) {
    fprintf(stderr, "The data file is too small cf. the index size\n");
    if (!checkContinue()) return EXIT_FAILURE;
  };

  keymap_size = (elements + 7) / 8;

  if ((keymap = (unsigned char *)malloc(keymap_size)) == NULL) {
    fprintf(stderr, "Error allocating keymap.  Requested size = %ld bytes\n", keymap_size);
    return EXIT_FAILURE;
  };

  memset(keymap, 0, keymap_size);

  fprintf(stdout, "Building keymap..."); fflush(stdout);

  fseek(idxFile, 0, SEEK_SET);
  for (i=0; i<elements; i++) {
    fread(&idx, sizeof(index_record_t), 1, idxFile);
    if (idx.blockOffset == 0) bitclr(keymap, i);
    else bitset(keymap, i);
  };

  fprintf(stdout, " Built.\n"); fflush(stdout);

  if ((buffer = (unsigned char *)malloc(physicalBlocksize)) == NULL) {
    fprintf(stderr, "Error allocating block buffer.  Requested size = %ld bytes\n", physicalBlocksize);
    return EXIT_FAILURE;
  };

  if ((bitmap = (unsigned char *)malloc(bitBytes)) == NULL) {
    fprintf(stderr, "Error allocating bitmap buffer.  Requested size = %ld bytes\n", bitBytes);
    return EXIT_FAILURE;
  };

  fseek(bitFile, 0, SEEK_SET);
  if (fread(bitmap, bitBytes, 1, bitFile) != 1) {
    fprintf(stderr, "Error - Couldn't read allocation map.  Continuing...\n");
    free(bitmap);
    bitmap = NULL;
  };


  fprintf(stdout, "Database fully open.  Checking records...\n");
  key = 1;
  
  displayRecords = (idxRecords < 20);
 
  while (key <= idxRecords) {
    fprintf(stdout, "%ld : ", key);fflush(stdout);
    fseek(idxFile, (key-1) * sizeof(idx), SEEK_SET);
    if (fread(&idx, sizeof(idx), 1, idxFile) != 1) {
      fprintf(stderr, 
              "\nError reading index record (%d bytes) from offset %ld in index file\n", 
              sizeof(idx), 
              (key-1) * sizeof(idx));
      return EXIT_FAILURE;
    };
    offset = idx.blockOffset;
    
    if (offset == 0) {
      fprintf(stdout, "<Empty>\n");
    } else {
      flags = idx.flags;
      
      fprintf(stdout, "Flags: %lx", flags);
  
      offset = block2fptr(offset);
      fprintf(stdout, ", Block offset: %ld bytes", offset);
      
      fseek(datFile, offset, SEEK_SET);
      if (fread(&block_header, sizeof(block_header_t), 1, datFile) != 1) {
        fprintf(stderr, 
                "\nError reading block header (%d bytes) from offset %ld in data file\n", 
                sizeof(block_header_t), 
                offset);
        return EXIT_FAILURE;
      };
  
      if (block_header.key != key) {
        fprintf(stderr, 
                "\nError: Block at offset %ld does not contain record %ld\n",
                offset,
                key);
                fprintf(stderr, "Key found: %ld\n", block_header.key);
        return EXIT_FAILURE;
      };
  
      if (fread(&record_header, sizeof(record_header_t), 1, datFile) != 1) {
        fprintf(stderr, 
                "\nError reading record header (%d bytes) from offset %ld in data file\n", 
                sizeof(record_header_t),
                offset + sizeof(block_header_t));
        return EXIT_FAILURE;
      };
  
      bytesLeft = record_header.recordLength;
   
      fprintf(stdout, ", Length: %ld bytes", bytesLeft); fflush(stdout);
  
      if (bytesLeft < logicalBlocksize - sizeof(record_header_t)) bytesThisBlock = bytesLeft;
      else bytesThisBlock = logicalBlocksize - sizeof(record_header_t);
  
      i = 1;
      while (bytesLeft > 0) {
        if (fread(buffer, bytesThisBlock, 1, datFile) != 1) {
          fprintf(stderr, 
                  "\nError reading record segment %d (%ld bytes) from data file\n", 
                  i,
                  bytesThisBlock);
          return EXIT_FAILURE;
        };
        bytesLeft -= bytesThisBlock;
        if (bytesLeft > 0) {
          i++;
          if (fread(&block_header, sizeof(block_header_t), 1, datFile) != 1) {
            fprintf(stderr, 
                    "\nError reading record segment header %d (%ld bytes) from data file\n", 
                    i,
                    sizeof(block_header_t));
            return EXIT_FAILURE;
          };
          if (block_header.key != key) {
            fprintf(stderr, "\nError: Record segment %d has wrong key (%ld)\n", i, block_header.key);
            return EXIT_FAILURE;
          };
          if (bytesLeft < logicalBlocksize) bytesThisBlock = bytesLeft;
          else bytesThisBlock = logicalBlocksize;
        };
      };
      fprintf(stdout, ", OK\n"); fflush(stdout);
    };
    key++;
  };

  fprintf(stdout, "\n\nFinished analyzing data file.\n");
  if (bitmap == NULL) {
    fflush(NULL);
    fprintf(stderr, "Allocation map not present.  Exiting.\n");
    return EXIT_FAILURE;
  };

  fprintf(stdout, "Verifying allocation map...\n");
 
  for (blockNumber = 1; blockNumber <= bitBytes * 8; blockNumber++) {
    offset = block2fptr(blockNumber);
    fprintf(stdout, "Block %ld: ", blockNumber); fflush(stdout);     
    fseek(datFile, offset, SEEK_SET);
    if (fread(&block_header, sizeof(block_header_t), 1, datFile) != 1) {
      fprintf(stderr, 
              "\nError reading block header (%d bytes) from offset %ld in data file\n", 
              sizeof(block_header_t), 
              offset);
      return EXIT_FAILURE;
    };
    if (block_header.key == 0) {
      if ( bit(bitmap, blockNumber-1) ) {
        fprintf(stdout, "Marked allocated in allocmap, but contains no data\n");
      } else {
        fprintf(stdout, "Free\n");
      };
    } else {
      if ( bit(bitmap, blockNumber-1) ) {
        fprintf(stdout, "In use (record %ld)\n", block_header.key);
      } else {
        fprintf(stdout, "Marked unused in allocMap, but contains data for record %ld\n", block_header.key);
      };
    };
  };
  
  fprintf(stdout, "AllocMap complete.\n");
  
  fprintf(stdout, "Verifying key map...\n");
 
  for (key = 1; key <= keymap_size * 8; key++) {
    if (key <= elements) {
      fprintf(stdout, "Key %ld: ", key); fflush(stdout);     
 
      fseek(idxFile, (key-1) * sizeof(idx), SEEK_SET);
      if (fread(&idx, sizeof(idx), 1, idxFile) != 1) {
        fprintf(stderr, 
                "\nError reading index record (%d bytes) from offset %ld in index file\n", 
                sizeof(idx), 
                (key-1) * sizeof(idx));
        return EXIT_FAILURE;
      };
      
      offset = idx.blockOffset;
      
      if (offset == 0) {
        if ( bit(keymap, key-1) ) {
          fprintf(stdout, "Unused, but marked in-use in keymap\n");
        } else {
          fprintf(stdout, "Unused\n");
        };
      } else {
        if ( bit(keymap, key-1) ) {
          fprintf(stdout, "In Use\n");
        } else {
          fprintf(stdout, "In use, but marked available in keymap\n");
        };
      };
    } else {
      if ( bit(keymap, key-1) ) {
        fprintf(stdout, "Unused (> elements), but marked in-use in keymap\n");
      } else {
        fprintf(stdout, "Unused (> elements)\n");
      };
    };
  };
    
 
  fprintf(stdout, "KeyMap complete.\n");
  
 
  
  return EXIT_SUCCESS;
}
