/* ========================================================================
 * Copyright 1988-2007 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 
 * ========================================================================
 */

/*
 * Program:	mix data file rebuild utility
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	14 May 2007
 * Last Edited:	14 May 2007
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define NIL 0
#define T 1
#define TMPLEN 1024

/* Globals */

char *version = "2006h.1";	/* program version */
int debugp = NIL;		/* flag saying debug */
int verbosep = NIL;		/* flag saying verbose */
int critical = NIL;		/* flag saying in critical code */
FILE *f = NIL;


/* MIX message metadata */

#define MESSAGE struct mix_message

MESSAGE {
  unsigned long uid;		/* unique identifier */
  char date[20];		/* yyymmddhhmmss+zzzz */
  unsigned long size;		/* full message size */
  unsigned long pos;		/* position of message in file */
  unsigned long offset;		/* offset of message text */
  MESSAGE *next;		/* next in line */
};


/* Function prototypes */

int main (int argc,char *argv[]);

/* Main program */

int main (int argc,char *argv[])
{
  int c;
  unsigned long pos,uid,size;
  char *s,tmp[TMPLEN];
  struct stat sbuf;
  FILE *f,*df;
  MESSAGE *msg = NIL;
  MESSAGE *cur,*tail;
  int bad = 0;
  int badseen = 0;
  if ((argc != 2) || ((strlen (argv[1]) + 100) >= TMPLEN)) {
    puts ("usage: mixdfix file");
    return 1;
  }
  if (!(f = fopen (argv[1],"rb"))) {
    perror (argv[1]);
    return 1;
  }
  if (fstat (fileno (f),&sbuf)) {
    perror (argv[1]);
    return 1;
  }
  pos = ftell (f);
  while ((pos < sbuf.st_size) && (fgets (tmp,TMPLEN,f))) {
    if (!(!strncmp (tmp,":msg:",5) && (s = tmp + 5) && isxdigit(*s++) &&
	  isxdigit(*s++) && isxdigit(*s++) && isxdigit(*s++) &&
	  isxdigit(*s++) && isxdigit(*s++) && isxdigit(*s++) &&
	  isxdigit(*s++) && (*s++ == ':') && isdigit(*s++) &&
	  isdigit(*s++) && isdigit(*s++) && isdigit(*s++) &&
	  isdigit(*s++) && isdigit(*s++) && isdigit(*s++) &&
	  isdigit(*s++) && isdigit(*s++) && isdigit(*s++) &&
	  isdigit(*s++) && isdigit(*s++) && isdigit(*s++) &&
	  isdigit(*s++) && (((c = *s++) == '+') || (c == '-')) &&
	  isdigit(*s++) && isdigit(*s++) && isdigit(*s++) &&
	  isdigit(*s++) && (*s ++ == ':') && isxdigit(*s++) &&
	  isxdigit(*s++) && isxdigit(*s++) && isxdigit(*s++) &&
	  isxdigit(*s++) && isxdigit(*s++) && isxdigit(*s++) &&
	  isxdigit(*s++) && (*s++ == ':') && (*s++ == '\r') &&
	  (*s++ == '\n') && !*s && (uid = strtoul (tmp+5,NIL,16)) &&
	  (size = strtoul (tmp+34,NIL,16)))) {
      if (!bad) {
	printf ("header error @ %ld: %s\n",pos,tmp);
	badseen = bad = 1;
      }
				/* stupid but I'm tired */
      if (fseek (f,++pos,SEEK_SET)) perror (argv[1]);
      continue;
    }
    if (bad) {
      printf ("found header again @ %ld\n",pos);
      bad = 0;
    }

    cur = (MESSAGE *) memset (malloc (sizeof (MESSAGE)),NIL,sizeof(MESSAGE));
    cur->uid = uid;
    memcpy (cur->date,tmp+14,19);
    cur->offset = ftell (f) - (cur->pos = pos);
    if (sbuf.st_size < (cur->offset + size))
      printf ("UID %lx @ %ld truncated from %ld to %ld\n",cur->size,
	      size = sbuf.st_size - cur->offset);
    cur->size = size;
    if (msg) {			/* have a list? */
      if (cur->uid > msg->uid) {
	tail->next = cur;
	tail = cur;
      }
      else if (cur->uid < msg->uid) {
	cur->next = msg;	/* make this the new head */
	msg = cur;
      }
      else {			/* scan for it */
	for (tail = msg; tail->next->uid < cur->uid; tail = tail->next);
	if (cur->uid != tail->next->uid) {
	  cur->next = tail->next;
	  tail->next = cur;
	}
	else {			/* hit a duplicate! */
	  printf ("replacing UID %lx @ %ld with same UID @ %ld\n",
		  cur->uid,tail->next->pos,cur->pos);
	  tail->next = cur;	/* replace it */
	  cur->next = tail->next->next;
	}
      }
    }
    else msg = tail = cur;	/* no, make new list */
    if (fseek (f,pos += cur->offset + cur->size,SEEK_SET)) perror (argv[1]);
  }
  if (ferror (f)) {
    perror (argv[1]);
    return 1;
  }
  if (badseen) {
    sprintf (tmp,"%s-rebuild",argv[1]);
    if (!(df = fopen (tmp,"wb"))) {
      perror (tmp);
      return 1;
    }
    for (cur = msg; cur; cur = cur->next) {
      fprintf (df,":msg:%08lx:%s:%08lx:\015\012",cur->uid,cur->date,cur->size);
      fseek (f,cur->pos + cur->offset,SEEK_SET);
				/* stupid but I'm tired */
      for (size = cur->size; size; --size) putc (getc (f),df);
    }
    fclose (df);
  }
  else printf ("%s: no repair needed\n",argv[1]);
  fclose (f);
  return 0;
}
