/* gen_tables64.c -- Generator of lookup tables with 64-bit support.
 * Copyright 2025 Red Hat Inc.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors:
 *      Steve Grubb <sgrubb@redhat.com> (64-bit extensions)
 *      Based on previous work by Miloslav Trmač
 */

#include "config.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <linux/net.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <sys/personality.h>
#include <sys/mount.h>
#ifndef MS_DIRSYNC
#include <linux/fs.h>
#endif
#include "gen_tables64.h"
#include "libaudit.h"
#include "auparse-defs.h"

/* This is from asm/ipc.h. Copying it for now as some platforms
 *  * have broken headers. */
#define SEMOP            1
#define SEMGET           2
#define SEMCTL           3
#define SEMTIMEDOP       4
#define MSGSND          11
#define MSGRCV          12
#define MSGGET          13
#define MSGCTL          14
#define SHMAT           21
#define SHMDT           22
#define SHMGET          23
#define SHMCTL          24
#define DIPC            25

/*
 * Defines EHWPOISON to the value found in uapi/asm-generic/errno.h,
 * which is correct for most (but not all architectures).
 */
#ifndef EHWPOISON
#define EHWPOISON      133
#endif

/* The ratio of table size to number of non-empty elements allowed for a
   "direct" s2i lookup table.  Dense integer ranges can be represented as
   direct-index arrays for O(1) i2s lookups, while sparse tables fall back
   to bsearch() generated tables.  String-to-integer conversions always use
   binary search since strings can't be indexed directly.

   2 looks like a lot at a first glance, but the bsearch tables need twice as
   much space per element, so with the ratio equal to 2 the direct table uses
   no more memory and is faster. */
#define DIRECT_THRESHOLD 2

/* Allow more than one string defined for a single integer value */
static bool allow_duplicate_ints; /* = false; */

/* Flag to indicate if we're generating 64-bit tables */
static bool use_64bit = false;

struct value {
    int64_t val;     /* Changed from int to int64_t to support 64-bit values */
    const char *s;
    size_t s_offset;
    size_t orig_index;
};

/* The mapping to store. */
static struct value values[] = {
#define _S(VAL, S) { (VAL), (S), 0, 0 },
#include TABLE_H
#undef _S
};

#define NUM_VALUES (sizeof(values) / sizeof(*values))

/* Compare two "struct value" members by name. */
static int
cmp_value_strings(const void *xa, const void *xb)
{
    const struct value *a, *b;

    a = xa;
    b = xb;
    return strcmp(a->s, b->s);
}

/* Compare two "struct value" members by value. */
static int
cmp_value_vals(const void *xa, const void *xb)
{
    const struct value *a, *b;

    a = xa;
    b = xb;
    if (a->val > b->val)
        return 1;
    if (a->val < b->val)
        return -1;
    /* Preserve the original order if there is an ambiguity, to always use
       the first specified value. */
    if (a->orig_index > b->orig_index)
        return 1;
    if (a->orig_index < b->orig_index)
        return -1;
    return 0;
}

/* Compare two "struct value" members by orig_index. */
static int
cmp_value_orig_index(const void *xa, const void *xb)
{
    const struct value *a, *b;

    a = xa;
    b = xb;
    if (a->orig_index > b->orig_index)
        return 1;
    if (a->orig_index < b->orig_index)
        return -1;
    return 0;
}

/* Output the string table, initialize values[*]->s_offset. */
static void
output_strings(const char *prefix)
{
    size_t i, offset;

    offset = 0;
    for (i = 0; i < NUM_VALUES; i++) {
        values[i].s_offset = offset;
        offset += strlen(values[i].s) + 1;
    }
    printf("static const char %s_strings[] = \"", prefix);
    assert(NUM_VALUES > 0);
    for (i = 0; i < NUM_VALUES; i++) {
        const char *c;

        if (i != 0 && i % 10 == 0)
            fputs("\"\n"
                  "\t\"", stdout);
        for (c = values[i].s; *c != '\0'; c++) {
            assert(*c != '"' && *c != '\\'
                   && isprint((unsigned char)*c));
            putc(*c, stdout);
        }
        if (i != NUM_VALUES - 1)
            fputs("\\0", stdout);
    }
    fputs("\";\n", stdout);
}

/* Output the string to integer mapping code.
   Assume strings are all uppercase or all lowercase if specified by
   parameters; in that case, make the search case-insensitive.
   values must be sorted by strings. */
static void
output_s2i(const char *prefix, bool uppercase, bool lowercase)
{
    size_t i;

    for (i = 0; i < NUM_VALUES - 1; i++) {
        assert(strcmp(values[i].s, values[i + 1].s) <= 0);
        if (strcmp(values[i].s, values[i + 1].s) == 0) {
            fprintf(stderr, "Duplicate value `%s': %" PRId64 ", %" PRId64 "\n",
                values[i].s, values[i].val, values[i + 1].val);
            abort();
        }
    }
    printf("static const unsigned %s_s2i_s[] = {", prefix);
    for (i = 0; i < NUM_VALUES; i++) {
        if (i % 10 == 0)
            fputs("\n\t", stdout);
        assert(values[i].s_offset <= UINT_MAX);
        printf("%zu,", values[i].s_offset);
    }
    printf("\n"
           "};\n");

    /* Output either int or int64_t array based on use_64bit flag */
    if (use_64bit) {
        printf("static const int64_t %s_s2i_i[] = {", prefix);
    } else {
        printf("static const int %s_s2i_i[] = {", prefix);
    }

    for (i = 0; i < NUM_VALUES; i++) {
        if (i % 10 == 0)
            fputs("\n\t", stdout);

        if (use_64bit) {
            printf("%" PRId64 ",", values[i].val);
        } else {
            /* For 32-bit compatibility, check if values fit in 32-bit int range */
            if (values[i].val > INT_MAX || values[i].val < INT_MIN) {
                fprintf(stderr, "Warning: Value %" PRId64 " exceeds 32-bit int range. "
                        "Consider using --64bit option.\n", values[i].val);
            }
            printf("%d,", (int)values[i].val);
        }
    }
    fputs("\n"
          "};\n", stdout);

    assert(!(uppercase && lowercase));
    if (uppercase) {
        for (i = 0; i < NUM_VALUES; i++) {
            const char *c;

            for (c = values[i].s; *c != '\0'; c++)
                assert(isascii((unsigned char)*c)
                       && !GT_ISLOWER(*c));
        }
    } else if (lowercase) {
        for (i = 0; i < NUM_VALUES; i++) {
            const char *c;

            for (c = values[i].s; *c != '\0'; c++)
                assert(isascii((unsigned char)*c)
                       && !GT_ISUPPER(*c));
        }
    }

    /* Generate the appropriate s2i function based on 32/64-bit mode */
    if (use_64bit) {
        if (uppercase || lowercase) {
            printf("static int %s_s2i(const char *s, int64_t *value) {\n"
                   "\tsize_t len, i;\n"
                   "\t if (s == NULL || value == NULL)\n"
                   "\t\treturn 0;\n"
                   "\tlen = strlen(s);\n"
                   "\t{ char copy[len + 1];\n"
                   "\tfor (i = 0; i < len; i++) {\n"
                   "\t\tchar c = s[i];\n", prefix);
            if (uppercase)
                fputs("\t\tcopy[i] = GT_ISLOWER(c) ? c - 'a' + 'A' "
                                                  ": c;\n", stdout);
            else
                fputs("\t\tcopy[i] = GT_ISUPPER(c) ? c - 'A' + 'a' "
                                                  ": c;\n", stdout);
            printf("\t}\n"
                   "\tcopy[i] = 0;\n"
                   "\treturn s2i_64__(%s_strings, %s_s2i_s, %s_s2i_i, %zu, "
                                  "copy, value);\n"
                   "\t}\n"
                   "}\n", prefix, prefix, prefix, NUM_VALUES);
        } else {
            printf("static int %s_s2i(const char *s, int64_t *value) {\n"
                   "\treturn s2i_64__(%s_strings, %s_s2i_s, %s_s2i_i, %zu, s, "
                                  "value);\n"
                   "}\n", prefix, prefix, prefix, prefix, NUM_VALUES);
        }
    } else {
        /* Original 32-bit code */
        if (uppercase || lowercase) {
            printf("static int %s_s2i(const char *s, int *value) {\n"
                   "\tsize_t len, i;\n"
                   "\t if (s == NULL || value == NULL)\n"
                   "\t\treturn 0;\n"
                   "\tlen = strlen(s);\n"
                   "\t{ char copy[len + 1];\n"
                   "\tfor (i = 0; i < len; i++) {\n"
                   "\t\tchar c = s[i];\n", prefix);
            if (uppercase)
                fputs("\t\tcopy[i] = GT_ISLOWER(c) ? c - 'a' + 'A' "
                                                  ": c;\n", stdout);
            else
                fputs("\t\tcopy[i] = GT_ISUPPER(c) ? c - 'A' + 'a' "
                                                  ": c;\n", stdout);
            printf("\t}\n"
                   "\tcopy[i] = 0;\n"
                   "\treturn s2i__(%s_strings, %s_s2i_s, %s_s2i_i, %zu, "
                                  "copy, value);\n"
                   "\t}\n"
                   "}\n", prefix, prefix, prefix, NUM_VALUES);
        } else {
            printf("static int %s_s2i(const char *s, int *value) {\n"
                   "\treturn s2i__(%s_strings, %s_s2i_s, %s_s2i_i, %zu, s, "
                                  "value);\n"
                   "}\n", prefix, prefix, prefix, prefix, NUM_VALUES);
        }
    }
}

/* Output the string to integer mapping table.
   values must be sorted by strings. */
static void
output_i2s(const char *prefix)
{
    struct value *unique_values;
    int64_t min_val, max_val;
    size_t i, n;

    assert(NUM_VALUES > 0);
    for (i = 0; i < NUM_VALUES - 1; i++) {
        assert(values[i].val <= values[i + 1].val);
        if (!allow_duplicate_ints
            && values[i].val == values[i + 1].val) {
            fprintf(stderr, "Duplicate value %" PRId64 ": `%s', `%s'\n",
                values[i].val, values[i].s, values[i + 1].s);
            abort();
        }
    }

    unique_values = malloc(NUM_VALUES * sizeof(*unique_values));
    if (unique_values == NULL) {
        fprintf(stderr,
		"Out of memory. Check %s file, %d line", __FILE__, __LINE__);
        abort();
    }
    n = 0;
    for (i = 0; i < NUM_VALUES; i++) {
        if (n == 0 || unique_values[n - 1].val != values[i].val) {
            unique_values[n] = values[i];
            n++;
        }
    }

    min_val = unique_values[0].val;
    max_val = unique_values[n - 1].val;

    /* Check if the direct table approach is feasible */
    /* For 64-bit values, we need to be more restrictive with direct tables
     * to avoid huge memory usage */
    bool use_direct = false;
    int64_t table_size = (int64_t)max_val - (int64_t)min_val + 1;

    if (use_64bit) {
        /* For 64-bit mode: Only use direct tables if the range is manageable
	 * (under 1024 entries) */
        if (table_size > 0 && table_size < 1024 &&
            ((double)table_size / n <= DIRECT_THRESHOLD)) {
            use_direct = true;
        }
    } else {
        /* Original 32-bit criteria */
        if (((double)max_val - (double)min_val) / n <= DIRECT_THRESHOLD) {
            use_direct = true;
        }
    }

    if (use_direct) {
        int64_t next_index;

        printf("static const unsigned %s_i2s_direct[] = {", prefix);
        next_index = min_val;
        i = 0;
        for (;;) {
            if ((next_index - min_val) % 10 == 0)
                fputs("\n\t", stdout);

            while (i < n && unique_values[i].val < next_index)
                /* This can happen if (allow_duplicate_ints) */
                i++;

            if (i < n && unique_values[i].val == next_index) {
                assert(unique_values[i].s_offset <= UINT_MAX);
                printf("%zu,", unique_values[i].s_offset);
            } else
                fputs("-1u,", stdout);

            if (next_index == max_val)
                /* Done like this to avoid integer overflow */
                break;
            next_index++;
        }

        printf("\n"
               "};\n");

        if (use_64bit) {
            printf("static const char *%s_i2s(int64_t v) {\n"
            "\treturn i2s_64_direct__(%s_strings, %s_i2s_direct, %" PRId64 ", "
                                 "%" PRId64 ", v);\n"
                   "}\n", prefix, prefix, prefix, min_val, max_val);
        } else {
            printf("static const char *%s_i2s(int v) {\n"
                   "\treturn i2s_direct__(%s_strings, %s_i2s_direct, %d, "
                                 "%d, v);\n"
                   "}\n", prefix, prefix, prefix, (int)min_val, (int)max_val);
        }
    } else {
        /* Use binary search tables */
        if (use_64bit) {
            printf("static const int64_t %s_i2s_i[] = {", prefix);
            for (i = 0; i < n; i++) {
                if (i % 10 == 0)
                    fputs("\n\t", stdout);
                printf("%" PRId64 ",", unique_values[i].val);
            }
        } else {
            printf("static const int %s_i2s_i[] = {", prefix);
            for (i = 0; i < n; i++) {
                if (i % 10 == 0)
                    fputs("\n\t", stdout);
                printf("%d,", (int)unique_values[i].val);
            }
        }

        printf("\n"
               "};\n"
               "static const unsigned %s_i2s_s[] = {", prefix);
        for (i = 0; i < n; i++) {
            if (i % 10 == 0)
                fputs("\n\t", stdout);
            assert(unique_values[i].s_offset <= UINT_MAX);
            printf("%zu,", unique_values[i].s_offset);
        }

        printf("\n"
               "};\n");

        if (use_64bit) {
            printf("static const char *%s_i2s(int64_t v) {\n"
                   "\treturn i2s_64_bsearch__(%s_strings, %s_i2s_i, %s_i2s_s, "
                          "%zu, v);\n"
                   "}\n", prefix, prefix, prefix, prefix, n);
        } else {
            printf("static const char *%s_i2s(int v) {\n"
                   "\treturn i2s_bsearch__(%s_strings, %s_i2s_i, %s_i2s_s, "
                          "%zu, v);\n"
                   "}\n", prefix, prefix, prefix, prefix, n);
        }
    }
    free(unique_values);
}

/* Output the string to integer mapping table as a transtab[].
   values must be sorted in the desired order. */
static void
output_i2s_transtab(const char *prefix)
{
    size_t i;
    char *uc_prefix;

    if (use_64bit) {
        printf("static const struct transtab64 %s_table[] = {", prefix);
        for (i = 0; i < NUM_VALUES; i++) {
            if (i % 10 == 0)
                fputs("\n\t", stdout);
            printf("{%" PRId64 ",%zu},", values[i].val, values[i].s_offset);
        }
    } else {
        printf("static const struct transtab %s_table[] = {", prefix);
        for (i = 0; i < NUM_VALUES; i++) {
            if (i % 10 == 0)
                fputs("\n\t", stdout);
            printf("{%d,%zu},", (int)values[i].val, values[i].s_offset);
        }
    }

    uc_prefix = strdup(prefix);
    if (uc_prefix == NULL) {
        fprintf(stderr,
		"Out of memory. Check %s file, %d line", __FILE__, __LINE__);
        abort();
    }
    for (i = 0; uc_prefix[i] != '\0'; i++)
        uc_prefix[i] = toupper((unsigned char)uc_prefix[i]);
    printf("\n"
           "};\n"
           "#define %s_NUM_ENTRIES "
           "(sizeof(%s_table) / sizeof(*%s_table))\n", uc_prefix, prefix,
           prefix);
    free(uc_prefix);
}

int
main(int argc, char **argv)
{
    bool gen_i2s, gen_i2s_transtab, gen_s2i, uppercase, lowercase;
    char *prefix;
    size_t i;

    /* This is required by gen_tables.h */
    assert(NUM_VALUES <= (SSIZE_MAX / 2 + 1));

    /* To make sure GT_ISUPPER and GT_ISLOWER work. */
    assert('Z' == 'A' + 25 && 'z' == 'a' + 25);
    gen_i2s = false;
    gen_i2s_transtab = false;
    gen_s2i = false;
    uppercase = false;
    lowercase = false;
    prefix = NULL;
    assert (argc > 1);
    for (i = 1; i < (size_t)argc; i++) {
        if (strcmp(argv[i], "--i2s") == 0)
            gen_i2s = true;
        else if (strcmp(argv[i], "--i2s-transtab") == 0)
            gen_i2s_transtab = true;
        else if (strcmp(argv[i], "--s2i") == 0)
            gen_s2i = true;
        else if (strcmp(argv[i], "--uppercase") == 0)
            uppercase = true;
        else if (strcmp(argv[i], "--lowercase") == 0)
            lowercase = true;
        else if (strcmp(argv[i], "--duplicate-ints") == 0)
            allow_duplicate_ints = true;
        else if (strcmp(argv[i], "--64bit") == 0)
            use_64bit = true;
        else {
            assert(*argv[i] != '-');
            assert(prefix == NULL);
            prefix = argv[i];
        }
    }
    assert(prefix != NULL);
    assert(!(uppercase && lowercase));

    printf("/* This is a generated file, see Makefile.am for its "
           "inputs. */\n");

    /* Add include for inttypes.h if using 64-bit mode */
    if (use_64bit) {
        printf("#include <inttypes.h>\n");
    }

    for (i = 0; i < NUM_VALUES; i++)
        values[i].orig_index = i;
    qsort(values, NUM_VALUES, sizeof(*values), cmp_value_strings);
    /*
     * Generated lookup tables are in lexicographic order. Alternative
     * orderings, such as reordering BFS nodes or retaining the
     * original input order, provide negligible cache benefits.
     */
    output_strings(prefix);
    if (gen_s2i)
        output_s2i(prefix, uppercase, lowercase);
    if (gen_i2s) {
        qsort(values, NUM_VALUES, sizeof(*values), cmp_value_vals);
        output_i2s(prefix);
    }
    if (gen_i2s_transtab) {
        qsort(values, NUM_VALUES, sizeof(*values),
              cmp_value_orig_index);
        output_i2s_transtab(prefix);
    }
    return EXIT_SUCCESS;
}

