3
\$\begingroup\$

https://github.com/speedrun-program/load-extender

It’s a pair of programs for extending load times, one for Windows and one for Linux. The Windows version is written in C++, and the Linux version is written in C so it can work with LD_PRELOAD.

I'm mainly interested in making sure I'm not doing anything that could cause a crash since I know these languages have a lot of undefined behavior, but I'll try to fix any other problems that get pointed out too. I'm sure there are problems with how it's written because I have very limited coding experience, and this is the first thing I've ever written in C/C++.

Here's the code for Linux:

fopen_interceptor.c:

#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>
#include <time.h>
#include <pthread.h>
#include "khash.h"
// thank you for making this hash map, attractivechaos.
// here's his github: https://github.com/attractivechaos

// these are global so fopen and khash_destruction can see them
static pthread_mutex_t lock;
static unsigned char LOCK_SUCCESS = 0; // used so it won't try to destroy lock if it failed to be made
static unsigned char SETUP_SUCCEEDED = 0; // if this is 0 then the fopen interception function skips to calling the real fopen function
static const int khStrInt = 32;
KHASH_MAP_INIT_STR(khStrInt, unsigned int*)
static khash_t(khStrInt) *my_khash_map;

#include "khash_setup_header.h" // included here so it recognizes kh_khStrInt_t

static void __attribute__((constructor)) khash_setup();
static void khash_setup()
{
    my_khash_map = kh_init(khStrInt);
    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("mutex init failed\n");
        return;
    }
    LOCK_SUCCESS = 1; // LOCK_SUCCESS only changed here
    FILE *my_file;
    FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    my_file = original_fopen("./files_and_delays.txt", "r");
    if (my_file == NULL)
    {
        printf("error opening files_and_delays.txt\n");
        return;
    }
    // longest_name, longest_sequence, longest_delay, and line_count made here so they can be printed in fopen_interceptor_test
    size_t longest_name = 1; // starts at 1 for null character
    size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char longest_delay = 1; // starts at 1 for null character
    unsigned int line_count = 1; // starts at 1 because there's always at least 1 line. khash map largest value is UINT_MAX
    unsigned char khash_succeeded = prepare_to_setup(my_khash_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
    fclose(my_file);
    if (khash_succeeded == 0) // khash failed to be made. Error message is printed in khash_setup_header in prepare_to_setup.
    {
        return;
    }
    SETUP_SUCCEEDED = 1; // SETUP_SUCCEEDED only changed here
}

static void __attribute__((destructor)) khash_destruction();
static void khash_destruction()
{
    khiter_t khash_map_iter;
    for (khash_map_iter = 0; khash_map_iter < kh_end(my_khash_map); ++khash_map_iter)  // kh_destroy doesn't free keys and values, they need to be freed like this
    {
        if (kh_exist(my_khash_map, khash_map_iter))
        {
            free((char*)kh_key(my_khash_map, khash_map_iter));
            kh_key(my_khash_map, khash_map_iter) = NULL;
            free((unsigned int*)kh_val(my_khash_map, khash_map_iter));
            kh_val(my_khash_map, khash_map_iter) = NULL;
        }
    }
    kh_destroy(khStrInt, my_khash_map);
    if (LOCK_SUCCESS == 1)
    {
        pthread_mutex_destroy(&lock);
    }
}

FILE *fopen(const char *path, const char *mode)
{
    if (SETUP_SUCCEEDED == 1) // skip to calling original fopen if setup failed
    {
        short slash_index = -1;
        for (unsigned short i = 0; path[i] != '\0'; i++) // look for start of filename by finding last occurence of '/' character
        {
            if (path[i] == '/')
            {
                slash_index = i % SHRT_MAX; // will cause key to not be found, but prevents unsigned int overflow
            }
        }
        // +1 so '/' isn't included. If '/' not found in path, slash_index stays -1 and entire path argument is checked as key since "path + -1 + 1" is the same as just "path"
        khiter_t khash_map_iter = kh_get(khStrInt, my_khash_map, path + slash_index + 1);
        if (khash_map_iter != kh_end(my_khash_map)) // key found
        {
            unsigned int *delay_sequence = kh_val(my_khash_map, khash_map_iter);
            pthread_mutex_lock(&lock); // don't let other threads change delay sequence when it's being read or changed
            if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
            {
                unsigned int *sequence_to_reset;
                kh_foreach_value(my_khash_map, sequence_to_reset, {sequence_to_reset[1] = 2;});
                pthread_mutex_unlock(&lock);
            }
            else if (delay_sequence[1] < delay_sequence[0])
            {
                if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
                {
                    delay_sequence[1] = 2;
                }
                unsigned int sleep_time = delay_sequence[delay_sequence[1]];
                delay_sequence[1] += 1;
                pthread_mutex_unlock(&lock);
                long time_nanoseconds = (sleep_time % 1000) * 1000000;
                struct timespec ts = {sleep_time / 1000, time_nanoseconds};
                nanosleep(&ts, NULL);
            }
            else // delay sequence already finished
            {
                pthread_mutex_unlock(&lock);
            }
        }
    }
    FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    return (*original_fopen)(path, mode);
}

fopen_interceptor_test.c:

#define _GNU_SOURCE

#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include "khash.h"
// thank you for making this hash map, attractivechaos.
// here's his github: https://github.com/attractivechaos

// these are global so fopen and khash_destruction can see them
static pthread_mutex_t lock;
static unsigned char LOCK_SUCCESS = 0; // used so it won't try to destroy lock if it failed to be made
static unsigned char SETUP_SUCCEEDED = 0; // if this is 0 then the fopen interception function skips to calling the real fopen function
static const int khStrInt = 32;
KHASH_MAP_INIT_STR(khStrInt, unsigned int*)
static khash_t(khStrInt) *my_khash_map;

#include "khash_setup_header.h" // included here so it recognizes kh_khStrInt_t

static void __attribute__((constructor)) khash_setup();
static void khash_setup()
{
    my_khash_map = kh_init(khStrInt);
    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("mutex init failed\n");
        return;
    }
    LOCK_SUCCESS = 1; // LOCK_SUCCESS only changed here
    FILE *my_file;
    /*FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    my_file = original_fopen("./files_and_delays.txt", "r");*/
    my_file = fopen("./files_and_delays.txt", "r");
    if (my_file == NULL)
    {
        printf("error opening files_and_delays.txt\n");
        return;
    }
    size_t longest_name = 1; // starts at 1 for null character
    size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char longest_delay = 1; // starts at 1 for null character
    unsigned int line_count = 1; // starts at 1 because there's always at least 1 line. khash map largest value is UINT_MAX
    unsigned char khash_succeeded = prepare_to_setup(my_khash_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
    fclose(my_file);
    if (khash_succeeded == 0) // khash failed to be made. Error message is printed in khash_setup_header in prepare_to_setup.
    {
        return;
    }
    printf("\nlongest filename (plus null character and whitespace in file): %zu\n"
    "longest delay sequence (+2 for current delay and sequence length): %zu\n"
    "most digits in delay (+1 for null character, max delay is UINT_MAX): %u\n"
    "lines in file (overestimates slightly): %u\n"
    "khash map size (lines should be less than 70%% of this): %zu\n", longest_name, longest_sequence, longest_delay, line_count, kh_n_buckets(my_khash_map));
    SETUP_SUCCEEDED = 1; // SETUP_SUCCEEDED only changed here
}

static void __attribute__((destructor)) khash_destruction();
static void khash_destruction()
{
    khiter_t khash_map_iter;
    for (khash_map_iter = 0; khash_map_iter < kh_end(my_khash_map); ++khash_map_iter)  // kh_destroy doesn't free keys and values, they need to be freed like this
    {
        if (kh_exist(my_khash_map, khash_map_iter))
        {
            free((char*)kh_key(my_khash_map, khash_map_iter));
            kh_key(my_khash_map, khash_map_iter) = NULL;
            free((unsigned int*)kh_val(my_khash_map, khash_map_iter));
            kh_val(my_khash_map, khash_map_iter) = NULL;
        }
    }
    kh_destroy(khStrInt, my_khash_map);
    if (LOCK_SUCCESS == 1)
    {
        pthread_mutex_destroy(&lock);
    }
}

static void fopen_test(const char *path, const char *mode)
{
    if (SETUP_SUCCEEDED == 1) // skip to calling original fopen if setup failed
    {
        short slash_index = -1;
        for (unsigned short i = 0; path[i] != '\0'; i++) // look for start of filename by finding last occurence of '/' character
        {
            if (path[i] == '/')
            {
                slash_index = i % SHRT_MAX; // will cause key to not be found, but prevents unsigned int overflow
            }
        }
        // +1 so '/' isn't included. If '/' not found in path, slash_index stays -1 and entire path argument is checked as key since "path + -1 + 1" is the same as just "path"
        khiter_t khash_map_iter = kh_get(khStrInt, my_khash_map, path + slash_index + 1);
        if (khash_map_iter != kh_end(my_khash_map)) // key found
        {
            unsigned int *delay_sequence = kh_val(my_khash_map, khash_map_iter);
            printf("%s successfully found in khash map\n", path + slash_index + 1);
            pthread_mutex_lock(&lock); // don't let other threads change delay sequence when it's being read or changed
            if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
            {
                unsigned int *sequence_to_reset;
                kh_foreach_value(my_khash_map, sequence_to_reset, {sequence_to_reset[1] = 2;});
                pthread_mutex_unlock(&lock);
                printf("all delay sequences reset\n");
            }
            else if (delay_sequence[1] < delay_sequence[0])
            {
                if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
                {
                    delay_sequence[1] = 2;
                    printf("%s delay sequence reset\n", path + slash_index + 1);
                }
                unsigned int sleep_time = delay_sequence[delay_sequence[1]];
                delay_sequence[1] += 1;
                pthread_mutex_unlock(&lock);
                long time_nanoseconds = (sleep_time % 1000) * 1000000;
                struct timespec ts = {sleep_time / 1000, time_nanoseconds};
                //nanosleep(&ts, NULL);
                printf("sleep for %ld second(s) and %ld millisecond(s)\n", ts.tv_sec, ts.tv_nsec / 1000000);
            }
            else // delay sequence already finished
            {
                pthread_mutex_unlock(&lock);
                printf("%s delay sequence already finished\n", path + slash_index + 1);
            }
        }
        else // key not found
        {
            printf("%s not found in khash map\n", path + slash_index + 1);
        }
    }
    /*FILE *(*original_fopen)(const char*, const char*);
    original_fopen = dlsym(RTLD_NEXT, "fopen");
    return (*original_fopen)(path, mode);*/
}

static size_t find_longest_input_length(FILE *test_input) // find how much space to give to array used to hold input
{
    size_t longest_input = 1;
    size_t current_input = 1; // starts at 1 for null character
    char ch = '\0';
    while (ch != EOF && current_input < SIZE_MAX)
    {
        ch = fgetc(test_input);
        if (ch == '\n' || ch == EOF)
        {
            if (current_input > longest_input)
            {
                longest_input = current_input;
            }
            current_input = 1;
        }
        else
        {
            current_input += 1;
        }
    }
    if (current_input == SIZE_MAX)
    {
        return SIZE_MAX; // a test path was too big
    }
    return longest_input;
}

static void print_khash()
{
    printf("\n---------- khash map current state ----------\n");
    const char *key_to_print;
    unsigned int *sequence_to_print;
    kh_foreach(my_khash_map, key_to_print, sequence_to_print,
    {
        printf("%s / ", key_to_print);
        for (unsigned int i = 0; i < sequence_to_print[0]; i++)
        {
            if (sequence_to_print[i] == UINT_MAX) // it's a reset point
            {
                printf("RESET"); // it doesn't need a space because the line always ends here
            }
            else
            {
                printf("%u ", sequence_to_print[i]);
            }
        }
        printf("\n");
    });
    printf("---------------------------------------------\n\n");
}

static void test_all_inputs(FILE *test_input, size_t longest_input)
{
    char *input_array = (char*)malloc(longest_input); // find_longest_input_length accounts for null character.  Always greater than 0, shouldn't return NULL
    if (input_array == NULL)
    {
        printf("malloc failed in test_all_inputs\n");
        return;
    }
    size_t ch_position = 0;
    char ch = '\0';
    while (ch != EOF)
    {
        ch = fgetc(test_input);
        if (ch == '\n' || ch == EOF)
        {
            input_array[ch_position] = '\0';
            ch_position = 0;
            if (ch != EOF) // avoids extra test
            {
                printf("testing fopen input: %s\n", input_array);
                fopen_test(input_array, "r");
                print_khash();
            }
        }
        else
        {
            input_array[ch_position] = ch;
            ch_position += 1;
        }
    }
    free((char*)input_array);
    input_array = NULL;
}

int main()
{
    FILE *test_input;
    test_input = fopen("./test_input.txt", "r");
    if (test_input == NULL)
    {
        printf("error opening test_input.txt\n");
        return 1;
    }
    size_t longest_input = find_longest_input_length(test_input);
    if (longest_input == SIZE_MAX) // check_file will return SIZE_MAX if a test path is too big
    {
        printf("a test path is too big\n");
        fclose(test_input);
        return 1;
    }
    if (fseek(test_input, 0, SEEK_SET) != 0)
    {
        printf("fseek on test_input.txt failed\n");
        fclose(test_input);
        return 1;
    }
    printf("\nlongest test path (+1 for null character): %zu\n", longest_input);
    printf("\ntest start\n");
    print_khash();
    test_all_inputs(test_input, longest_input);
    fclose(test_input);
    printf("test finish\n\n");
    return 0;
}

khash_setup_header.h:

#include <stdint.h>

static unsigned char TOO_BIG_DELAY_SEEN = 0; // used so user won't be alerted more than once that they entered a delay that's too big

// checks line count to know how much space to give to khash
// updates longest_name and longest_sequence to know how much space to give to arrays used to hold key and value
static void check_file(FILE *my_file, size_t *longest_name, size_t *longest_sequence, unsigned char *longest_delay, unsigned int *line_count, unsigned char digits_in_uint)
{
    unsigned char checking_sequence = 0; // 0 if checking a filename's length, 1 if checking a delay sequence's length
    size_t current_name = 1; // starts at 1 for null character
    size_t current_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char current_delay = 1; // starts at 1 for null character
    char ch = '\0';
    // longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
    while (ch != EOF && current_name < SIZE_MAX && current_sequence < (SIZE_MAX / sizeof(unsigned int)) && *line_count < UINT_MAX && current_sequence < UINT_MAX)
    {
        ch = fgetc(my_file);
        if (ch == EOF || ch == '\n')
        {
            if (checking_sequence == 1)
            {
                if (current_delay > *longest_delay)
                {
                    *longest_delay = current_delay;
                }
                if (current_sequence > *longest_sequence)
                {
                    *longest_sequence = current_sequence;
                }
                checking_sequence = 0;
                current_sequence = 3;
                current_delay = 1;
            }
            *line_count += 1; // if for some reason there are a lot of hash collisions, the user can put empty lines in files_and_delays.txt to try to fix it
            current_name = 1;
        }
        else if (ch == '/')
        {
            if (checking_sequence == 0)
            {
                if (current_name > *longest_name)
                {
                    *longest_name = current_name;
                }
                checking_sequence = 1;
            }
            else
            {
                if (current_delay > *longest_delay)
                {
                    *longest_delay = current_delay;
                }
                current_sequence += 1;
                current_delay = 1;
            }
        }
        else if (checking_sequence == 0)
        {
            current_name += 1;
        }
        // ignores non-digits and leading zeros. maximum space is digits_in_uint + 1. +1 is for null character
        else if (((ch >= 49 && ch <= 57) || (ch == 48 && current_delay > 1)) && current_delay < digits_in_uint + 1)
        {
            current_delay += 1;
        }
    }
    if (current_name == SIZE_MAX || current_sequence == (SIZE_MAX / sizeof(unsigned int)) || current_sequence == UINT_MAX)
    {
        *line_count = UINT_MAX; // something was too big
    }
}

// reads the next filename and writes it in name_array
static size_t read_name(FILE *my_file, char *ch, char *name_array)
{
    size_t ch_position = 0;
    *ch = fgetc(my_file);
    while (*ch != EOF && *ch != '\n' && *ch != '/')
    {
        name_array[ch_position] = *ch;
        ch_position += 1;
        *ch = fgetc(my_file);
    }
    name_array[ch_position] = '\0';
    if (*ch == '/') // filename given and line didn't end abruptly
    {
        for (; ch_position > 0 && (name_array[ch_position - 1] == ' ' || name_array[ch_position - 1] == '\t'); ch_position--)
        {
            name_array[ch_position - 1] = '\0'; // remove whitespace between '/' and end of filename if the user has it written like that
        }
    }
    return ch_position + 1; // +1 for null character
}

static unsigned int my_atoi(char *delay_array, unsigned int max_power_of_ten, unsigned char digits_in_uint, unsigned char number_position)
{
    unsigned char delay_too_big = 0;
    unsigned int current_power_of_ten = 1;
    unsigned int accumulator = 0;
    if (delay_array[0] == '\0') // no digits found, assume delay is 0
    {
        return 0;
    }
    // delay has more digits than UINT_MAX or it has as many digits and the most significant digit is bigger in the delay than in UINT_MAX
    else if (delay_array[0] == '+' || (number_position == digits_in_uint && delay_array[0] - 48 > UINT_MAX / max_power_of_ten))
    {
        delay_too_big = 1;
    }
    else
    {
        number_position -= 1;
        for (; number_position > 0; number_position--) // sum everything except most significant digit
        {
            accumulator += ((delay_array[number_position] - 48) * current_power_of_ten);
            current_power_of_ten *= 10;
        }
        // checking that adding final value won't make accumulator overflow
        // delay_array[0] won't be bigger than UINT_MAX / max_power_of_ten here because the other else if block checks for that
        if (current_power_of_ten == max_power_of_ten && UINT_MAX - ((delay_array[0] - 48) * max_power_of_ten) - 1 < accumulator)
        {
            delay_too_big = 1;
        }
        else
        {
            accumulator += ((delay_array[0] - 48) * current_power_of_ten);
        }
    }
    if (delay_too_big == 1)
    {
        if (TOO_BIG_DELAY_SEEN == 0)
        {
            printf("\nWARNING: maximum delay time is %u\n", UINT_MAX - 1);
            TOO_BIG_DELAY_SEEN = 1; // TOO_BIG_DELAY_SEEN only changed here
        }
        return UINT_MAX - 1;
    }
    return accumulator;
}

// reads the next delay sequence and writes it in sequence_array
// also returns how many numbers are in the sequence so the right amount will be copied into the array given to khash
// there can't be more than UINT_MAX delays in one sequence
static unsigned int read_sequence(FILE *my_file, char *ch, char *delay_array, unsigned int *sequence_array, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
    unsigned char number_position = 0; // next digit in current delay time. Also used in my_atoi to see if delay is too big
    size_t sequence_position = 2; // starts at 2 because first two indexes are used to store sequence length and what the next delay is
    delay_array[0] = '\0'; // initializing to use in condition
    while (*ch != EOF && *ch != '\n')
    {
        *ch = fgetc(my_file);
        if (*ch == EOF || *ch == '\n')
        {
            delay_array[number_position] = '\0';
            unsigned int last_delay = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
            if (last_delay != 0) // no point adding 0 as final delay time
            {
                sequence_array[sequence_position] = last_delay;
                sequence_position += 1;
            }
        }
        else if (*ch == '/') // end of current delay, about to read next delay
        {
            delay_array[number_position] = '\0';
            sequence_array[sequence_position] = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
            sequence_position += 1;
            number_position = 0;
        }
        // ch is a digit and the digit isn't a leading zero and the maximum delay isn't reached
        else if (((*ch >= 49 && *ch <= 57) || (*ch == 48 && number_position > 0)) && delay_array[0] != '+')
        {
            if (number_position != digits_in_uint) // if it's already equal to digits_in_uint then the delay is too big
            {
                delay_array[number_position] = *ch;
                number_position += 1;
            }
            else
            {
                delay_array[0] = '+'; // '+' used to check if the delay is too big in my_atoi
            }
        }
        else if (*ch == '-')
        {
            sequence_array[sequence_position] = UINT_MAX; // used for saying reset should happen
            sequence_position += 1;
            break; // no point checking delay times beyond where reset happens
        }
    }
    while (*ch != EOF && *ch != '\n') // finish reading line if it's not already read
    {
        *ch = fgetc(my_file);
    }
    if (sequence_array[sequence_position - 1] != UINT_MAX) // numbers entered and not a reset-all-sequences file
    {
        for (unsigned int i = sequence_position - 1; i > 1 && sequence_array[i] == 0; i--) // don't copy pointless zeros
        {
            sequence_position -= 1;
        }
    }
    return sequence_position;
}

static unsigned char set_keys_and_values(kh_khStrInt_t *my_khash_map, FILE *my_file, size_t longest_name, size_t longest_sequence, unsigned char longest_delay, unsigned int line_count, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
    khiter_t khash_map_iter;
    char *name_array = (char*)malloc(longest_name); // array for filename text. Always greater than 0, shouldn't return NULL.
    unsigned int *sequence_array = (unsigned int*)malloc(longest_sequence * sizeof(unsigned int)); // array for delay sequence. Always greater than 0, shouldn't return NULL.
    char *delay_array = (char*)malloc(longest_delay); // array for delay text. Always greater than 0, shouldn't return NULL.
    if (name_array == NULL || sequence_array == NULL || delay_array == NULL)
    {
        return 0;
    }
    if ((size_t)(UINT_MAX / 2) < line_count) // khash resizes by powers of two, so if line_count is this big then khash can't resize any bigger
    {
        kh_resize(khStrInt, my_khash_map, UINT_MAX);
    }
    else
    {
        kh_resize(khStrInt, my_khash_map, (unsigned int)((line_count / 0.7) + 1)); // khash map will always be at 70% or less capacity
    }
    sequence_array[0] = 2; // first index stores length of array
    sequence_array[1] = 2; // second index stores what the next delay to use in the sequence is
    size_t chars_to_copy;
    unsigned int numbers_to_copy;
    char ch = '\0';
    while (ch != EOF)
    {
        chars_to_copy = read_name(my_file, &ch, name_array); // accounts for null character
        if (ch != EOF && ch != '\n') // checks if the line had a delay sequence. If it didn't, moves on to next line
        {
            numbers_to_copy = read_sequence(my_file, &ch, delay_array, sequence_array, max_power_of_ten, digits_in_uint);
            if (numbers_to_copy > 2) // read_sequence returns 2 if either the sequence was pointless or the user didn't enter anything for it
            {
                int ret;
                sequence_array[0] = numbers_to_copy;
                char *key_for_khash = (char*)malloc(chars_to_copy); // always greater than 0, shouldn't return NULL
                unsigned int *value_for_khash = (unsigned int*)malloc(numbers_to_copy * sizeof(unsigned int)); // always greater than 0, shouldn't return NULL
                if (key_for_khash == NULL || value_for_khash == NULL)
                {
                    return 0;
                }
                for (size_t i = 0; i < chars_to_copy; i++)
                {
                    key_for_khash[i] = name_array[i];
                }
                for (unsigned int i = 0; i < numbers_to_copy; i++)
                {
                    value_for_khash[i] = sequence_array[i];
                }
                khash_map_iter = kh_put(khStrInt, my_khash_map, key_for_khash, &ret);
                kh_val(my_khash_map, khash_map_iter) = value_for_khash;
            }
        }
        sequence_array[0] = 2;
        sequence_array[1] = 2;
    }
    free((char*)name_array);
    name_array = NULL;
    free((unsigned int*)sequence_array);
    sequence_array = NULL;
    free((char*)delay_array);
    delay_array = NULL;
    return 1;
}

static unsigned char prepare_to_setup(kh_khStrInt_t *my_khash_map, FILE *my_file, size_t *longest_name, size_t *longest_sequence, unsigned char *longest_delay, unsigned int *line_count)
{
    unsigned int max_power_of_ten = 1; // biggest power of 10 less than UINT_MAX, used in my_atoi
    unsigned char digits_in_uint = 1; // how many digits are in UINT_MAX, used for helping to see if a delay is too big
    for (; UINT_MAX / max_power_of_ten > 9; max_power_of_ten *= 10)
    {
        digits_in_uint += 1;
    }
    check_file(my_file, longest_name, longest_sequence, longest_delay, line_count, digits_in_uint);
    if (fseek(my_file, 0, SEEK_SET) != 0)
    {
        printf("fseek on files_and_delays.txt failed\n");
        return 0;
    }
    // longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
    if (*line_count == UINT_MAX) // check_file will return UINT_MAX if something is too big
    {
        printf("filename, delay, delay sequence, and/or number of files is too big\n");
        return 0;
    }
    unsigned char khash_succeeded = set_keys_and_values(my_khash_map, my_file, *longest_name, *longest_sequence, *longest_delay, *line_count, max_power_of_ten, digits_in_uint);
    if (khash_succeeded == 0) // set_keys_and_values returns 0 if malloc fails
    {
        printf("malloc failed in set_keys_and_values\n");
        return 0;
    }
    return 1;
}

Here's the code for Windows:

load_extender_exe.cpp:

#include <tchar.h>
#include <iostream>
#include <string>
#include <Windows.h>
#include <easyhook.h>

void get_exit_input()
{
    std::wcout << "Press Enter to exit";
    std::wstring input;
    std::getline(std::wcin, input);
}

int _tmain(int argc, _TCHAR* argv[])
{
    WCHAR* dllToInject32 = NULL;
    WCHAR* dllToInject64 = NULL;
    LPCWSTR lpApplicationName = argv[0];
    DWORD lpBinaryType;
    if (GetBinaryType(lpApplicationName, &lpBinaryType) == 0 || (lpBinaryType != 0 && lpBinaryType != 6))
    {
        std::wcout << "ERROR: This exe wasn't identified as 32-bit or as 64-bit";
        get_exit_input();
        return 1;
    }
    else if (lpBinaryType == 0)
    {
        dllToInject32 = (WCHAR*)L"load_extender_32.dll";
    }
    else
    {
        dllToInject64 = (WCHAR*)L"load_extender_64.dll";
    }
    DWORD processId;
    std::wcout << "Enter the target process Id: ";
    std::cin >> processId;

    wprintf(L"Attempting to inject dll\n\n");

    // Inject dllToInject into the target process Id, passing 
    // freqOffset as the pass through data.
    NTSTATUS nt = RhInjectLibrary(
        processId,   // The process to inject into
        0,           // ThreadId to wake up upon injection
        EASYHOOK_INJECT_DEFAULT,
        dllToInject32, // 32-bit
        dllToInject64, // 64-bit
        NULL, // data to send to injected DLL entry point
        0 // size of data to send
    );

    if (nt != 0)
    {
        printf("RhInjectLibrary failed with error code = %d\n", nt);
        PWCHAR err = RtlGetLastErrorString();
        std::wcout << err << "\n";
        get_exit_input();
        return 1;
    }
    else
    {
        std::wcout << L"Library injected successfully.\n";
    }

    get_exit_input();
    return 0;
}

load_extender_dll.cpp:

#include <string>
#include <mutex>
#include <Windows.h>
#include <easyhook.h>
#include "robin_hood.h"
// thank you for making this hash map, martinus.
// here's his github: https://github.com/martinus/robin-hood-hashing

namespace rh = robin_hood;

// included here so alias can be used
#include "rh_map_setup_header.h"

static class rh_map_handler // this is used to free all the unsigned int array memory when the program ends
{
public:
    rh::unordered_flat_map<std::wstring, unsigned int*> obj_map;

    ~rh_map_handler()
    {
        for (auto it = obj_map.begin(); it != obj_map.end();)
        {
            free((unsigned int*)it->second);
            it->second = NULL;
            it = obj_map.erase(it); // erasing so obj_map's destructor doesn't have to do as much
        }
    }
};

// my_map_handler is global so it will free all the unsigned int arrays when the program ends
// my_rh_map and mtx are global so the NtOpenFile hook function can see them
static rh_map_handler my_map_handler;
static rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map = my_map_handler.obj_map;
static std::mutex mtx;

static unsigned char rh_map_setup()
{
    FILE* my_file = NULL;
    if (fopen_s(&my_file, ".\\files_and_delays.txt", "r") != 0)
    {
        return 0;
    }
    size_t longest_name = 1; // starts at 1 for null character
    size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char longest_delay = 1; // starts at 1 for null character
    size_t line_count = 1; // starts at 1 because there's always at least 1 line
    unsigned char rh_map_succeeded = prepare_to_setup(my_rh_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
    if (my_file != NULL) // this makes a warning go away
    {
        fclose(my_file);
    }
    if (rh_map_succeeded == 0) // rh map failed to be made. Error message is printed in rh_map_setup_header in prepare_to_setup.
    {
        return 0;
    }
    return 1;
}

static unsigned char SETUP_SUCCEEDED = rh_map_setup(); // if this is 0 then the hook function skips to calling the real function

static NTSTATUS WINAPI NtOpenFileHook(
    PHANDLE           FileHandle,
    ACCESS_MASK        DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK  IoStatusBlock,
    ULONG              ShareAccess,
    ULONG              OpenOptions) {
    if (SETUP_SUCCEEDED == 1) // skip to calling original NtOpenProcess if setup failed
    {
        std::wstring file_path = ObjectAttributes->ObjectName->Buffer;
        // +1 so '\' isn't included. If '\' not found in path, the whole wstring is checked because npos is -1
        std::wstring file_name = file_path.substr(file_path.rfind(L"\\") + 1);
        rh::unordered_flat_map<std::wstring, unsigned int*>::const_iterator rh_map_iter = my_rh_map.find(file_name);
        if (rh_map_iter != my_rh_map.end()) // key found
        {
            unsigned int* delay_sequence = rh_map_iter->second;
            mtx.lock(); // don't let other threads change delay sequence when it's being read or changed
            if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
            {
                for (auto& it : my_rh_map)
                {
                    it.second[1] = 2;
                }
                mtx.unlock();
            }
            else if (delay_sequence[1] < delay_sequence[0])
            {
                if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
                {
                    delay_sequence[1] = 2;
                }
                unsigned int sleep_time = delay_sequence[delay_sequence[1]];
                delay_sequence[1] += 1;
                mtx.unlock();
                Sleep(sleep_time);
            }
            else // delay sequence already finished
            {
                mtx.unlock();
            }
        }
    }
    return NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
}

extern "C" void __declspec(dllexport) __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO * inRemoteInfo);

void __stdcall NativeInjectionEntryPoint(REMOTE_ENTRY_INFO* inRemoteInfo) {
    HOOK_TRACE_INFO hHook1 = { NULL };
    LhInstallHook(
        GetProcAddress(GetModuleHandle(TEXT("ntdll")), "NtOpenFile"),
        NtOpenFileHook,
        NULL,
        &hHook1);

    ULONG ACLEntries[1] = { 0 };
    LhSetExclusiveACL(ACLEntries, 1, &hHook1);
    return;
}

load_extender_test.cpp:

#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include <string>
#include <mutex>
#include "robin_hood.h"
// thank you for making this hash map, martinus.
// here's his github: https://github.com/martinus/robin-hood-hashing

namespace rh = robin_hood;

// included here so alias can be used
#include "rh_map_setup_header.h"

static class rh_map_handler // this is used to free all the unsigned int array memory when the program ends
{
public:
    rh::unordered_flat_map<std::wstring, unsigned int*> obj_map;

    ~rh_map_handler()
    {
        for (auto it = obj_map.begin(); it != obj_map.end();)
        {
            free((unsigned int*)it->second);
            it->second = NULL;
            it = obj_map.erase(it); // erasing so obj_map's destructor doesn't have to do as much
        }
    }
};

// my_map_handler is global so it will free all the unsigned int arrays when the program ends
// my_rh_map and mtx are global so the NtOpenFile hook function can see them
static rh_map_handler my_map_handler;
static rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map = my_map_handler.obj_map;
static std::mutex mtx;

static unsigned char rh_map_setup()
{
    FILE* my_file = NULL;
    if (fopen_s(&my_file, ".\\files_and_delays.txt", "r") != 0)
    {
        printf("error opening files_and_delays.txt\n");
        return 0;
    }
    size_t longest_name = 1; // starts at 1 for null character
    size_t longest_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char longest_delay = 1; // starts at 1 for null character
    size_t line_count = 1; // starts at 1 because there's always at least 1 line
    unsigned char rh_map_succeeded = prepare_to_setup(my_rh_map, my_file, &longest_name, &longest_sequence, &longest_delay, &line_count);
    if (my_file != NULL) // this makes a warning go away
    {
        fclose(my_file);
    }
    if (rh_map_succeeded == 0) // rh map failed to be made. Error message is printed in rh_map_setup_header in prepare_to_setup.
    {
        return 0;
    }
    printf("\nlongest filename (plus null character and whitespace in file): %zu\n"
        "longest delay sequence (+2 for current delay and sequence length): %zu\n"
        "most digits in delay (+1 for null character, max delay is UINT_MAX): %u\n"
        "lines in file (overestimates slightly): %zu\n", longest_name, longest_sequence, longest_delay, line_count);
    return 1;
}

static unsigned char SETUP_SUCCEEDED = rh_map_setup(); // if this is 0 then the hook function skips to calling the real function

static void NtOpenFile_test(std::wstring file_path)
{
    if (SETUP_SUCCEEDED == 1) // skip to calling original NtOpenFile if setup failed
    {
        // +1 so '\' isn't included. If '\' not found in path, the whole wstring is checked because npos is -1
        std::wstring file_name = file_path.substr(file_path.rfind(L"\\") + 1);
        rh::unordered_flat_map<std::wstring, unsigned int*>::const_iterator rh_map_iter = my_rh_map.find(file_name);
        if (rh_map_iter != my_rh_map.end()) // key found
        {
            unsigned int* delay_sequence = rh_map_iter->second;
            printf("%ls successfully found in hash map\n", file_name.c_str());
            mtx.lock(); // don't let other threads change delay sequence when it's being read or changed
            if (delay_sequence[2] == UINT_MAX) // reset all delay sequences
            {
                for (auto& it : my_rh_map)
                {
                    it.second[1] = 2;
                }
                mtx.unlock();
                printf("all delay sequences reset\n");
            }
            else if (delay_sequence[1] < delay_sequence[0])
            {
                if (delay_sequence[delay_sequence[1]] == UINT_MAX) // reset delay sequence
                {
                    delay_sequence[1] = 2;
                    printf("%ls delay sequence reset\n", file_name.c_str());
                }
                unsigned int sleep_time = delay_sequence[delay_sequence[1]];
                delay_sequence[1] += 1;
                mtx.unlock();
                //Sleep(sleep_time);
                printf("sleep for %u second(s) and %u millisecond(s)\n", sleep_time / 1000, sleep_time % 1000);
            }
            else // delay sequence already finished
            {
                mtx.unlock();
                printf("%ls delay sequence already finished\n", file_name.c_str());
            }
        }
        else // key not found
        {
            printf("%ls not found in khash map\n", file_name.c_str());
        }
    }
    // NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
}

static size_t find_longest_input_length(FILE* test_input) // find how much space to give to array used to hold input
{
    size_t longest_input = 1;
    size_t current_input = 1; // starts at 1 for null character
    wchar_t ch = L'\0';
    while (ch != WEOF && current_input < SIZE_MAX)
    {
        ch = fgetwc(test_input);
        if (ch == L'\n' || ch == WEOF)
        {
            if (current_input > longest_input)
            {
                longest_input = current_input;
            }
            current_input = 1;
        }
        else
        {
            current_input += 1;
        }
    }
    if (current_input == SIZE_MAX)
    {
        return SIZE_MAX; // a test path was too big
    }
    return longest_input;
}

static void print_rh_map()
{
    printf("\n---------- khash map current state ----------\n");
    for (auto& it : my_rh_map)
    {
        printf("%ls / ", it.first.c_str());
        unsigned int* sequence_to_print = it.second;
        for (unsigned int i = 0; i < sequence_to_print[0]; i++)
        {
            if (sequence_to_print[i] == UINT_MAX) // it's a reset point
            {
                printf("RESET"); // it doesn't need a space because the line always ends here
            }
            else
            {
                printf("%u ", sequence_to_print[i]);
            }
        }
        printf("\n");
    }
    printf("---------------------------------------------\n\n");
}

static void test_all_inputs(FILE* test_input, size_t longest_input)
{
    wchar_t* input_array = (wchar_t*)malloc(longest_input * sizeof(wchar_t)); // find_longest_input_length accounts for null character.  Always greater than 0, shouldn't return NULL
    if (input_array == NULL)
    {
        printf("malloc failed in test_all_inputs\n");
        return;
    }
    size_t ch_position = 0;
    wchar_t ch = L'\0';
    while (ch != WEOF)
    {
        ch = fgetwc(test_input);
        if (ch == L'\n' || ch == WEOF)
        {
            input_array[ch_position] = L'\0';
            ch_position = 0;
            if (ch != WEOF) // avoids extra test
            {
                printf("testing NtOpenFile input: %ls\n", input_array);
                NtOpenFile_test((std::wstring)input_array);
                print_rh_map();
            }
        }
        else
        {
            input_array[ch_position] = ch;
            ch_position += 1;
        }
    }
    free((wchar_t*)input_array);
    input_array = NULL;
}

static void get_exit_input()
{
    printf("Press Enter to exit\n");
    std::wstring input;
    std::getline(std::wcin, input);
}

static int _tmain(int argc, _TCHAR* argv[])
{
    LPCWSTR lpApplicationName = argv[0];
    DWORD lpBinaryType;
    if (GetBinaryType(lpApplicationName, &lpBinaryType) == 0)
    {
        printf("\nERROR: couldn't read if this file is 32 bit or 64 bit\n");
        get_exit_input();
        return 1;
    }
    else if (lpBinaryType != 0)
    {
        printf("\nError: this file was incorrectly identified as not 32-bit\n");
        get_exit_input();
        return 1;
    }
    else
    {
        printf("\nthis file was correctly identified as 32-bit\n");
    }
    FILE* test_input;
    if (fopen_s(&test_input, ".\\test_input.txt", "r") != 0)
    {
        printf("error opening test_input.txt\n");
        get_exit_input();
        return 1;
    }
    size_t longest_input = find_longest_input_length(test_input);
    if (longest_input == SIZE_MAX) // check_file will return SIZE_MAX if a test path is too big
    {
        printf("a test path is too big\n");
        if (test_input != NULL) // this makes a warning go away
        {
            fclose(test_input);
        }
        get_exit_input();
        return 1;
    }
    // test_input != NULL check makes warning go away
    if (test_input != NULL && fseek(test_input, 0, SEEK_SET) != 0)
    {
        printf("fseek on test_input.txt failed\n");
        if (test_input != NULL) // this makes a warning go away
        {
            fclose(test_input);
        }
        get_exit_input();
        return 1;
    }
    printf("\nlongest test path (+1 for null character): %zu\n", longest_input);
    printf("\ntest start\n");
    print_rh_map();
    test_all_inputs(test_input, longest_input);
    if (test_input != NULL) // this makes a warning go away
    {
        fclose(test_input);
    }
    get_exit_input();
    return 0;
}

rh_map_setup_header:

#include <stdint.h>

// printf calls are used for debugging in load_extender_test.exe

static unsigned char TOO_BIG_DELAY_SEEN = 0; // used so user won't be alerted more than once that they entered a delay that's too big

// checks line count to know how much space to give to robin_hood map
// updates longest_name and longest_sequence to know how much space to give to arrays used to hold key and value
static void check_file(FILE* my_file, size_t* longest_name, size_t* longest_sequence, unsigned char* longest_delay, size_t* line_count, unsigned char digits_in_uint, size_t map_max_size)
{
    unsigned char checking_sequence = 0; // 0 if checking a filename's length, 1 if checking a delay sequence's length
    size_t current_name = 1; // starts at 1 for null character
    size_t current_sequence = 3; // starts at 3 for index holding array length, index holding current position, and first number in delay sequence
    unsigned char current_delay = 1; // starts at 1 for null character
    wchar_t ch = L'\0';
    // longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
    while (ch != WEOF && current_name < SIZE_MAX && current_sequence < (SIZE_MAX / sizeof(unsigned int)) && *line_count < SIZE_MAX && current_sequence < UINT_MAX)
    {
        ch = fgetwc(my_file);
        if (ch == WEOF || ch == L'\n')
        {
            if (checking_sequence == 1)
            {
                if (current_delay > * longest_delay)
                {
                    *longest_delay = current_delay;
                }
                if (current_sequence > * longest_sequence)
                {
                    *longest_sequence = current_sequence;
                }
                checking_sequence = 0;
                current_sequence = 3;
                current_delay = 1;
            }
            *line_count += 1; // if for some reason there are a lot of hash collisions, the user can put empty lines in files_and_delays.txt to try to fix it
            current_name = 1;
        }
        else if (ch == L'/')
        {
            if (checking_sequence == 0)
            {
                if (current_name > * longest_name)
                {
                    *longest_name = current_name;
                }
                checking_sequence = 1;
            }
            else
            {
                if (current_delay > * longest_delay)
                {
                    *longest_delay = current_delay;
                }
                current_sequence += 1;
                current_delay = 1;
            }
        }
        else if (checking_sequence == 0)
        {
            current_name += 1;
        }
        // ignores non-digits and leading zeros. maximum space is digits_in_uint + 1. +1 is for null character
        else if (((ch >= 49 && ch <= 57) || (ch == 48 && current_delay > 1)) && current_delay < digits_in_uint + 1)
        {
            current_delay += 1;
        }
    }
    if (current_name == SIZE_MAX ||
        current_sequence == (SIZE_MAX / sizeof(unsigned int)) ||
        current_sequence == UINT_MAX
        || *line_count > map_max_size)
    {
        *line_count = SIZE_MAX; // something was too big
    }
}

// reads the next filename and writes it in name_array
static void read_name(FILE* my_file, wchar_t* ch, wchar_t* name_array)
{
    size_t ch_position = 0;
    *ch = fgetwc(my_file);
    while (*ch != WEOF && *ch != L'\n' && *ch != L'/')
    {
        name_array[ch_position] = *ch;
        ch_position += 1;
        *ch = fgetwc(my_file);
    }
    name_array[ch_position] = L'\0';
    if (*ch == L'/') // filename given and line didn't end abruptly
    {
        for (; ch_position > 0 && (name_array[ch_position - 1] == L' ' || name_array[ch_position - 1] == L'\t'); ch_position--)
        {
            name_array[ch_position - 1] = L'\0'; // remove whitespace between '/' and end of filename if the user has it written like that
        }
    }
}

static unsigned int my_atoi(wchar_t* delay_array, unsigned int max_power_of_ten, unsigned char digits_in_uint, unsigned char number_position)
{
    unsigned char delay_too_big = 0;
    unsigned int current_power_of_ten = 1;
    unsigned int accumulator = 0;
    if (delay_array[0] == L'\0') // no digits found, assume delay is 0
    {
        return 0;
    }
    // delay has more digits than UINT_MAX or it has as many digits and the most significant digit is bigger in the delay than in UINT_MAX
    else if (delay_array[0] == L'+' || (number_position == digits_in_uint && delay_array[0] - 48 > UINT_MAX / max_power_of_ten))
    {
        delay_too_big = 1;
    }
    else
    {
        number_position -= 1;
        for (; number_position > 0; number_position--) // sum everything except most significant digit
        {
            accumulator += ((delay_array[number_position] - 48) * current_power_of_ten);
            current_power_of_ten *= 10;
        }
        // checking that adding final value won't make accumulator overflow
        // delay_array[0] won't be bigger than UINT_MAX / max_power_of_ten here because the other else if block checks for that
        if (current_power_of_ten == max_power_of_ten && UINT_MAX - ((delay_array[0] - 48) * max_power_of_ten) - 1 < accumulator)
        {
            delay_too_big = 1;
        }
        else
        {
            accumulator += ((delay_array[0] - 48) * current_power_of_ten);
        }
    }
    if (delay_too_big == 1)
    {
        if (TOO_BIG_DELAY_SEEN == 0)
        {
            printf("\nWARNING: maximum delay time is %u\n", UINT_MAX - 1);
            TOO_BIG_DELAY_SEEN = 1; // TOO_BIG_DELAY_SEEN only changed here
        }
        return UINT_MAX - 1;
    }
    return accumulator;
}

// reads the next delay sequence and writes it in sequence_array
// also returns how many numbers are in the sequence so the right amount will be copied into the array given to robin_hood map
// there can't be more than UINT_MAX delays in one sequence
static unsigned int read_sequence(FILE* my_file, wchar_t* ch, wchar_t* delay_array, unsigned int* sequence_array, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
    unsigned char number_position = 0; // next digit in current delay time. Also used in my_atoi to see if delay is too big
    size_t sequence_position = 2; // starts at 2 because first two indexes are used to store sequence length and what the next delay is
    delay_array[0] = L'\0'; // initializing to use in condition
    while (*ch != WEOF && *ch != L'\n')
    {
        *ch = fgetwc(my_file);
        if (*ch == WEOF || *ch == L'\n')
        {
            delay_array[number_position] = L'\0';
            unsigned int last_delay = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
            if (last_delay != 0) // no point adding 0 as final delay time
            {
                sequence_array[sequence_position] = last_delay;
                sequence_position += 1;
            }
        }
        else if (*ch == L'/') // end of current delay, about to read next delay
        {
            delay_array[number_position] = L'\0';
            sequence_array[sequence_position] = my_atoi(delay_array, max_power_of_ten, digits_in_uint, number_position); // atoi can't be used because delay_array might have wrong input
            sequence_position += 1;
            number_position = 0;
        }
        // ch is a digit and the digit isn't a leading zero and the maximum delay isn't reached
        else if (((*ch >= 49 && *ch <= 57) || (*ch == 48 && number_position > 0)) && delay_array[0] != L'+')
        {
            if (number_position != digits_in_uint) // if it's already equal to digits_in_uint then the delay is too big
            {
                delay_array[number_position] = *ch;
                number_position += 1;
            }
            else
            {
                delay_array[0] = L'+'; // '+' used to check if the delay is too big in my_atoi
            }
        }
        else if (*ch == L'-')
        {
            sequence_array[sequence_position] = UINT_MAX; // used for saying reset should happen
            sequence_position += 1;
            break; // no point checking delay times beyond where reset happens
        }
    }
    while (*ch != WEOF && *ch != L'\n') // finish reading line if it's not already read
    {
        *ch = fgetwc(my_file);
    }
    if (sequence_array[sequence_position - 1] != UINT_MAX) // numbers entered and not a reset-all-sequences file
    {
        for (unsigned int i = sequence_position - 1; i > 1 && sequence_array[i] == 0; i--) // don't copy pointless zeros
        {
            sequence_position -= 1;
        }
    }
    return sequence_position;
}

static unsigned char set_keys_and_values(rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map, FILE* my_file, size_t longest_name, size_t longest_sequence, unsigned char longest_delay, size_t line_count, unsigned int max_power_of_ten, unsigned char digits_in_uint)
{
    wchar_t* name_array = (wchar_t*)malloc(longest_name * sizeof(wchar_t)); // array for filename text. Always greater than 0, shouldn't return NULL. Null character is accounted for.
    unsigned int* sequence_array = (unsigned int*)malloc(longest_sequence * sizeof(unsigned int)); // array for delay sequence. Always greater than 0, shouldn't return NULL.
    wchar_t* delay_array = (wchar_t*)malloc(longest_delay * sizeof(wchar_t)); // array for delay text. Always greater than 0, shouldn't return NULL.
    if (name_array == NULL || sequence_array == NULL || delay_array == NULL)
    {
        return 0;
    }
    my_rh_map.reserve(line_count);
    sequence_array[0] = 2; // first index stores length of array
    sequence_array[1] = 2; // second index stores what the next delay to use in the sequence is
    size_t chars_to_copy;
    unsigned int numbers_to_copy;
    wchar_t ch = L'\0';
    while (ch != WEOF)
    {
        read_name(my_file, &ch, name_array); // puts characters in name_array
        if (ch != WEOF && ch != L'\n') // checks if the line had a delay sequence. If it didn't, moves on to next line
        {
            numbers_to_copy = read_sequence(my_file, &ch, delay_array, sequence_array, max_power_of_ten, digits_in_uint);
            if (numbers_to_copy > 2) // read_sequence returns 2 if either the sequence was pointless or the user didn't enter anything for it
            {
                sequence_array[0] = numbers_to_copy;
                unsigned int* value_for_rh = (unsigned int*)malloc(numbers_to_copy * sizeof(unsigned int)); // always greater than 0, shouldn't return NULL
                if (value_for_rh == NULL)
                {
                    return 0;
                }
                std::wstring key_for_rh(name_array); // null character added to name_array in read_name function
                for (unsigned int i = 0; i < numbers_to_copy; i++)
                {
                    value_for_rh[i] = sequence_array[i];
                }
                my_rh_map[key_for_rh] = value_for_rh;
            }
        }
        sequence_array[0] = 2;
        sequence_array[1] = 2;
    }
    free((wchar_t*)name_array);
    name_array = NULL;
    free((unsigned int*)sequence_array);
    sequence_array = NULL;
    free((wchar_t*)delay_array);
    delay_array = NULL;
    return 1;
}

static unsigned char prepare_to_setup(rh::unordered_flat_map<std::wstring, unsigned int*>& my_rh_map, FILE* my_file, size_t* longest_name, size_t* longest_sequence, unsigned char* longest_delay, size_t* line_count)
{
    unsigned int max_power_of_ten = 1; // biggest power of 10 less than UINT_MAX, used in my_atoi
    unsigned char digits_in_uint = 1; // how many digits are in UINT_MAX, used for helping to see if a delay is too big
    for (; UINT_MAX / max_power_of_ten > 9; max_power_of_ten *= 10)
    {
        digits_in_uint += 1;
    }
    size_t map_max_size = my_rh_map.max_size();
    check_file(my_file, longest_name, longest_sequence, longest_delay, line_count, digits_in_uint, map_max_size);
    if (fseek(my_file, 0, SEEK_SET) != 0)
    {
        printf("fseek on files_and_delays.txt failed\n");
        return 0;
    }
    // longest_sequence can't be bigger than UINT_MAX because the size is recorded in the array, and the array uses unsigned ints
    if (*line_count == SIZE_MAX) // check_file will return UINT_MAX if something is too big
    {
        printf("filename, delay, delay sequence, and/or number of files is too big\n");
        return 0;
    }
    unsigned char rh_succeeded = set_keys_and_values(my_rh_map, my_file, *longest_name, *longest_sequence, *longest_delay, *line_count, max_power_of_ten, digits_in_uint);
    if (rh_succeeded == 0) // set_keys_and_values returns 0 if malloc fails
    {
        printf("malloc failed in set_keys_and_values\n");
        return 0;
    }
    return 1;
}
```
\$\endgroup\$
5
  • \$\begingroup\$ I want the whole thing reviewed. So I should post the whole program? It's a lot of code. \$\endgroup\$ Jan 4, 2021 at 19:07
  • 3
    \$\begingroup\$ Please refer to this meta answer. \$\endgroup\$ Jan 4, 2021 at 19:19
  • 2
    \$\begingroup\$ Okay I edited it, thank you \$\endgroup\$ Jan 4, 2021 at 19:35
  • 3
    \$\begingroup\$ I spent a long time not understanding this, because of the ambiguity in the title - I understood it as extending the time to load programs into memory, rather than for those loaded programs to access files. \$\endgroup\$ Jan 6, 2021 at 8:10
  • \$\begingroup\$ I posted the revised version of this code: codereview.stackexchange.com/questions/254528/… \$\endgroup\$ Jan 10, 2021 at 18:55

1 Answer 1

6
\$\begingroup\$

Make use of the standard library

You are reinventing lots of wheels in your programs. There is already a lot that the standard library you are using will do for you. Even in C on Linux, you can use POSIX functions for managing hash tables: hcreate() and hsearch(). But even better is probably to write both the Linux and Windows versions in C++, and make use of std::unordered_map. With C++, you can also avoid all of the manual memory management you are doing.

You are also reading the input files character by character, which is very slow. Why not use std::getline() to read whole lines at a time, and then split them further as necessary? Or ensure your input is in such a format that you can use the >> operator to read each element. You even reimplemented number parsing in my_atoi(). It is much better to leave that up to a standard function, such as std::stoi().

There is almost nothing in your code that needs to be low level. Keep everything as simple and easy as possible.

Make your code as platform-independent as possible

You should not need two completely different implementations. A lot of code can be shared. The only things that are different between Linux and Windows are the function calls that need to be intercepted, and how you intercept them. You should be able to share the processing of the processing of the input file, lookups into the hash table, and the sleeping. So, the fopen() hook should look as simple as this:

static auto original_fopen = reinterpret_cast<FILE *(*)(const char *path, const char *mode)>(dlsym(RTLD_NEXT, "fopen"));

FILE *fopen(const char *path, const char *mode)
{
    delay_file(path);
    return original_fopen(path, mode);
}

And the Windows hook should look like:

static NTSTATUS WINAPI NtOpenFileHook(
    PHANDLE           FileHandle,
    ACCESS_MASK        DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PIO_STATUS_BLOCK  IoStatusBlock,
    ULONG              ShareAccess,
    ULONG              OpenOptions)
{
    delay_file(ObjectAttributes->ObjectName->Buffer);
    return NtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
}

The function delay_file() should take care of everything else. The string type used for filenames is different between Linux and Windows, but since you mentioned C++17, you should be able to use std::filesystem::path as the type without having to worry about that:

static std::unordered_map<std::filesystem::path, ...> delay_sequences;

delay_file(std::filesystem::path filename)
{
    if (auto it = delay_sequences.find(filename); it != delay_sequences.end()) {
        auto &sequence = it->second;
        ...
    }
}

Create a struct or class to manage the delay sequence

You store the delay sequence as an array of integers, but some elements of that array are special, and the length can supposedly vary. It is better to create a proper data structure for it, whether you want to do it in C or in C++. With C++, I would write something like:

struct delay_sequence {
    std::size_t index = 0;
    std::vector<std::chrono::milliseconds> delays;
}

The vector delays holds the actual delay values using a std::chrono::duration type. The advantage is that you can pass it directly to std::this_thread::sleep_for() to sleep for the given time. A std::vector knows its own length, so you don't have to store that separately. The only other item necessary is the current index.

Avoid manual mutex locking

Instead of manually calling mutex.lock() and mutex.unlock(), use a std::lock_guard object to do this for you.

Alternatively:

Consider using atomic variables

The index into the array of delays is just a simple counter that needs to be incremented or reset. Consider making it a std::atomic<std::size_t>, like so:

struct delay_sequence {
    std::atomic<std::size_t> index;
    std::vector<std::chrono::milliseconds> delays;
}

And then use it like so:

auto &sequence = it->second;
auto index = sequence.index++;

if (index < sequence.delays.size()) {
    std::this_thread::sleep_for(sequence.delays[index]);
} else {
    // we've already finished, if you are worried about index wrapping, do:
    sequence.index--;
}

You can also reset all counters atomically:

for (auto &[filename, sequence]: delay_sequences) {
    sequence.index = 0;
}

However, the trick with if (delay_sequence[...] == UINT_MAX) is not as nice with atomics. You could still have a special duration (perhaps a negative one?) indicate that you want to reset one or all of the indices, but I would rather a flag to struct delay_sequence to indicate whether you want to repeat a sequence, and another flag to indicate that you want to reset all indices when the given file is opened, like so:

struct delay_sequence {
    std::atomic<std::size_t> index;
    std::vector<std::chrono::milliseconds> delays;
    bool repeat;
    bool reset_all;
};

...

auto &sequence = it->second;

if (sequence.reset_all) {
    for (auto &[filename, sequence]: delay_sequences) {
        sequence.index = 0;
    }
} else {
    auto index = sequence.index++;

    if (sequence.repeat) {
        index %= sequence.delays.size();
    }

    if (index < sequence.delays.size()) {
        std::this_thread::sleep_for(sequence.delays[index]);
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ Thank you for taking the time to look at this. I’ll try to change it with your suggestions. I also think I should use forward_lists instead of vectors if I’m using structs. I can keep track of the current position in the struct and decide to reset it to the start of the forward_list when it reaches the end position. So I’ll try to rewrite it like that. \$\endgroup\$ Jan 6, 2021 at 20:11
  • 1
    \$\begingroup\$ I would still use std::vector to hold the delay times, since accessing a vector is faster than a list, and it seems to me that after reading in the configuration file, you never change the delays. \$\endgroup\$
    – G. Sliepen
    Jan 6, 2021 at 20:15
  • \$\begingroup\$ Ok, in that case I’ll use vectors. \$\endgroup\$ Jan 6, 2021 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.