/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/***********************************************************************
**
** Name: dbmalloc.c
**
** Description: Testing malloc (OBSOLETE)
**
** Modification History:
** 14-May-97 AGarcia- Converted the test to accomodate the debug_mode flag.
**           The debug mode will print all of the printfs associated with this
*test.
**           The regress mode will be the default mode. Since the regress tool
*limits
**           the output to a one line status:PASS or FAIL,all of the printf
*statements
**           have been handled with an if (debug_mode) statement.
***********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "nspr.h"

void usage(void) {
  fprintf(stderr,
          "Usage: dbmalloc ('-m'|'-s') '-f' num_fails ('-d'|'-n') filename "
          "[...]\n");
  exit(0);
}

typedef struct node_struct {
  struct node_struct *next, *prev;
  int line;
  char value[4];
} node_t, *node_pt;

node_pt get_node(const char* line) {
  node_pt rv;
  int l = strlen(line);
  rv = (node_pt)PR_MALLOC(sizeof(node_t) + l + 1 - 4);
  if ((node_pt)0 == rv) {
    return (node_pt)0;
  }
  memcpy(&rv->value[0], line, l + 1);
  rv->next = rv->prev = (node_pt)0;
  return rv;
}

void dump(const char* name, node_pt node, int mf, int debug) {
  if ((node_pt)0 != node->prev) {
    dump(name, node->prev, mf, debug);
  }
  if (0 != debug) {
    printf("[%s]: %6d: %s", name, node->line, node->value);
  }
  if (node->line == mf) {
    fprintf(stderr, "[%s]: Line %d was allocated!\n", name, node->line);
  }
  if ((node_pt)0 != node->next) {
    dump(name, node->next, mf, debug);
  }
  return;
}

void release(node_pt node) {
  if ((node_pt)0 != node->prev) {
    release(node->prev);
  }
  if ((node_pt)0 != node->next) {
    release(node->next);
  }
  PR_DELETE(node);
}

int t2(const char* name, int mf, int debug) {
  int rv;
  FILE* fp;
  int l = 0;
  node_pt head = (node_pt)0;
  char buffer[BUFSIZ];

  fp = fopen(name, "r");
  if ((FILE*)0 == fp) {
    fprintf(stderr, "[%s]: Cannot open \"%s.\"\n", name, name);
    return -1;
  }

  /* fgets mallocs a buffer, first time through. */
  if ((char*)0 == fgets(buffer, BUFSIZ, fp)) {
    fprintf(stderr, "[%s]: \"%s\" is empty.\n", name, name);
    (void)fclose(fp);
    return -1;
  }

  rewind(fp);

  if (PR_SUCCESS != PR_ClearMallocCount()) {
    fprintf(stderr, "[%s]: Cannot clear malloc count.\n", name);
    (void)fclose(fp);
    return -1;
  }

  if (PR_SUCCESS != PR_SetMallocCountdown(mf)) {
    fprintf(stderr, "[%s]: Cannot set malloc countdown to %d\n", name, mf);
    (void)fclose(fp);
    return -1;
  }

  while (fgets(buffer, BUFSIZ, fp)) {
    node_pt n;
    node_pt* w = &head;

    if ((strlen(buffer) == (BUFSIZ - 1)) && (buffer[BUFSIZ - 2] != '\n')) {
      buffer[BUFSIZ - 2] == '\n';
    }

    l++;

    n = get_node(buffer);
    if ((node_pt)0 == n) {
      printf("[%s]: Line %d: malloc failure!\n", name, l);
      continue;
    }

    n->line = l;

    while (1) {
      int comp;

      if ((node_pt)0 == *w) {
        *w = n;
        break;
      }

      comp = strcmp((*w)->value, n->value);
      if (comp < 0) {
        w = &(*w)->next;
      } else {
        w = &(*w)->prev;
      }
    }
  }

  (void)fclose(fp);

  dump(name, head, mf, debug);

  rv = PR_GetMallocCount();
  PR_ClearMallocCountdown();

  release(head);

  return rv;
}

int nf = 0;
int debug = 0;

void test(const char* name) {
  int n, i;

  extern int nf, debug;

  printf("[%s]: starting test 0\n", name);
  n = t2(name, 0, debug);
  if (-1 == n) {
    return;
  }
  printf("[%s]: test 0 had %ld allocations.\n", name, n);

  if (0 >= n) {
    return;
  }

  for (i = 0; i < nf; i++) {
    int which = rand() % n;
    if (0 == which) {
      printf("[%s]: starting test %d -- no allocation should fail\n", name,
             i + 1);
    } else {
      printf("[%s]: starting test %d -- allocation %d should fail\n", name,
             i + 1, which);
    }
    (void)t2(name, which, debug);
    printf("[%s]: test %d done.\n", name, i + 1);
  }

  return;
}

int main(int argc, char** argv) {
  int okay = 0;
  int multithread = 0;

  struct threadlist {
    struct threadlist* next;
    PRThread* thread;
  }* threadhead = (struct threadlist*)0;

  extern int nf, debug;

  srand(time(0));

  PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);

  printf("[main]: We %s using the debugging malloc.\n",
         PR_IsDebuggingMalloc() ? "ARE" : "ARE NOT");

  while (argv++, --argc) {
    if ('-' == argv[0][0]) {
      switch (argv[0][1]) {
        case 'f':
          nf = atoi(argv[0][2] ? &argv[0][2] : --argc ? *++argv : "0");
          break;
        case 'd':
          debug = 1;
          break;
        case 'n':
          debug = 0;
          break;
        case 'm':
          multithread = 1;
          break;
        case 's':
          multithread = 0;
          break;
        default:
          usage();
          break;
      }
    } else {
      FILE* fp = fopen(*argv, "r");
      if ((FILE*)0 == fp) {
        fprintf(stderr, "Cannot open \"%s.\"\n", *argv);
        continue;
      }

      okay++;
      (void)fclose(fp);
      if (multithread) {
        struct threadlist* n;

        n = (struct threadlist*)malloc(sizeof(struct threadlist));
        if ((struct threadlist*)0 == n) {
          fprintf(stderr, "This is getting tedious. \"%s\"\n", *argv);
          continue;
        }

        n->next = threadhead;
        n->thread = PR_CreateThread(PR_USER_THREAD, (void (*)(void*))test,
                                    *argv, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
                                    PR_JOINABLE_THREAD, 0);
        if ((PRThread*)0 == n->thread) {
          fprintf(stderr, "Can't create thread for \"%s.\"\n", *argv);
          continue;
        } else {
          threadhead = n;
        }
      } else {
        test(*argv);
      }
    }
  }

  if (okay == 0) {
    usage();
  } else
    while ((struct threadlist*)0 != threadhead) {
      struct threadlist* x = threadhead->next;
      (void)PR_JoinThread(threadhead->thread);
      PR_DELETE(threadhead);
      threadhead = x;
    }

  return 0;
}
