/* 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;
}
