1346 lines
44 KiB
C
1346 lines
44 KiB
C
// A simple standard library full of functions reused a lot along my
|
|
// projects.
|
|
//
|
|
// Copyright 2024 ThoNohT <e.c.p.bataille@gmail.com>
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
#ifndef NOH_H_
|
|
#define NOH_H_
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <time.h>
|
|
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define _WINUSER_
|
|
#define _WINGDI_
|
|
#define _IMM_
|
|
#define _WINCON_
|
|
#include <windows.h>
|
|
#include <direct.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#endif // _WIN32
|
|
|
|
///////////////////////// Number definitions /////////////////////////
|
|
|
|
#ifndef int8
|
|
#define int8 signed char
|
|
#endif
|
|
|
|
#ifndef uint8
|
|
#define uint8 unsigned char
|
|
#endif
|
|
|
|
#ifndef int16
|
|
#define int16 short
|
|
#endif
|
|
|
|
#ifndef uint16
|
|
#define uint16 unsigned short
|
|
#endif
|
|
|
|
#ifndef uint
|
|
#define uint unsigned int
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
#ifndef int64
|
|
#define int64 long
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef uint64
|
|
#define uint64 unsigned long
|
|
#endif
|
|
|
|
#ifndef KB
|
|
#define KB << 10
|
|
#endif
|
|
|
|
#ifndef MB
|
|
#define MB << 20
|
|
#endif
|
|
|
|
#ifndef GB
|
|
#define GB << 30
|
|
#endif
|
|
|
|
///////////////////////// Core stuff /////////////////////////
|
|
|
|
#define max(a,b) \
|
|
({ __typeof__ (a) _a = (a); \
|
|
__typeof__ (b) _b = (b); \
|
|
_a > _b ? _a : _b; })
|
|
#define min(a,b) \
|
|
({ __typeof__ (a) _a = (a); \
|
|
__typeof__ (b) _b = (b); \
|
|
_a < _b ? _a : _b; })
|
|
|
|
#define noh_array_len(array) (sizeof(array)/sizeof(array[0]))
|
|
#define noh_array_get(array, index) \
|
|
(noh_assert(index >= 0), noh_assert(index < noh_array_get(array)), array[index])
|
|
|
|
// Allows returning a file after performing some deferred code.
|
|
// Usage:
|
|
// Define a result variable before the first call of this macro.
|
|
// Place a defer label at the end of the function where the work is done.
|
|
// Return result at the end of the deferred work.
|
|
#define noh_return_defer(value) do { result = (value); goto defer; } while(0)
|
|
|
|
void* noh_realloc_check_(void *target, size_t size);
|
|
|
|
// Reallocates some memory and crashes if it failed.
|
|
#define noh_realloc_check(target, size) noh_realloc_check_((void*)(target), (size))
|
|
|
|
// Returns the next argument as a c-string, moves the argv pointer to the next argument and decreases argc.
|
|
char *noh_shift_args(int *argc, char ***argv);
|
|
|
|
///////////////////////// Time /////////////////////////
|
|
|
|
// Returns the result of subtracting the second timespec from the first timespec, in milliseconds.
|
|
// There is no absolute compare function, since it is assumed that a higher precision than milliseconds will not be
|
|
// needed, and cannot really be expected to be reliable.
|
|
long noh_diff_timespec_ms(const struct timespec *time1, const struct timespec *time2);
|
|
|
|
// Returns a timespec that represents the local time with the specified number of second and milliseconds added.
|
|
// Negative values will lead to a time in the past.
|
|
struct timespec noh_get_time_in(int seconds, long milliseconds);
|
|
|
|
// Adds the specified number of seconds and milliseconds to a timespec.
|
|
void noh_time_add(struct timespec *time, int seconds, long milliseconds);
|
|
|
|
///////////////////////// Logging /////////////////////////
|
|
|
|
// An assert macro that outputs a better format for use with vim's make command.
|
|
#define noh_assert(condition) { \
|
|
if (!(condition)) { \
|
|
printf("%s:%i: Assertion failed: %s. \n", __FILE__, __LINE__, #condition); \
|
|
exit(1); \
|
|
} \
|
|
} \
|
|
|
|
// Possible log levels.
|
|
typedef enum {
|
|
NOH_INFO,
|
|
NOH_WARNING,
|
|
NOH_ERROR,
|
|
} Noh_Log_Level;
|
|
|
|
// Writes a formatted log message to stderr with the provided log level.
|
|
void noh_log(Noh_Log_Level level, const char *fmt, ...);
|
|
|
|
///////////////////////// Dynamic array /////////////////////////
|
|
|
|
#define NOH_DA_INIT_CAP 256
|
|
|
|
// Appends an element to a dynamic array, allocates more memory and moves all elements to newly allocated memory
|
|
// if needed.
|
|
#define noh_da_append(da, elem) \
|
|
do { \
|
|
if ((da)->count >= (da)->capacity) { \
|
|
(da)->capacity = (da)->capacity == 0 ? NOH_DA_INIT_CAP : (da)->capacity * 2; \
|
|
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
|
|
} \
|
|
\
|
|
(da)->elems[(da)->count++] = (elem); \
|
|
} while(0)
|
|
|
|
// Appends multiple elements to a dynamic array. Allocates more memory and moves all elements to newly allocated memory
|
|
// if needed.
|
|
#define noh_da_append_multiple(da, new_elems, new_elems_count) \
|
|
do { \
|
|
if ((da)->count + new_elems_count > (da)->capacity) { \
|
|
if ((da)->capacity == 0) (da)->capacity = NOH_DA_INIT_CAP; \
|
|
while ((da)->count + new_elems_count > (da)->capacity) (da)->capacity *= 2; \
|
|
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
|
|
} \
|
|
\
|
|
memcpy((da)->elems + (da)->count, new_elems, new_elems_count * sizeof(*(da)->elems)); \
|
|
(da)->count += new_elems_count; \
|
|
} while (0)
|
|
|
|
// Removes the element at the specified location.
|
|
#define noh_da_remove_at(da, index) \
|
|
do { \
|
|
noh_assert((index) < (da)->count && "Index out of bounds."); \
|
|
(da)->count -= 1; \
|
|
if ((index) < (da)->count) { \
|
|
size_t elem_size = sizeof(*(da)->elems); \
|
|
memmove( \
|
|
(void*)(da)->elems + (index) * elem_size, \
|
|
(void*)(da)->elems + ((index) + 1) * elem_size, \
|
|
((da)->count - (index)) * elem_size); \
|
|
} \
|
|
} while (0)
|
|
|
|
// Frees the elements in a dynamic array, and resets the count and capacity.
|
|
#define noh_da_free(da) \
|
|
do { \
|
|
if ((da)->capacity > 0) { \
|
|
(da)->count = 0; \
|
|
(da)->capacity = 0; \
|
|
free((da)->elems); \
|
|
} \
|
|
} while (0)
|
|
|
|
// Resets the count of a dynamic array to 0.
|
|
#define noh_da_reset(da) \
|
|
do { \
|
|
(da)->count = 0; \
|
|
} while (0)
|
|
|
|
///////////////////////// Circular buffer /////////////////////////
|
|
|
|
// Initializes a circular buffer, similar to a dynamic array, but adding elements should be done with noh_cb_insert.
|
|
// This call should be the only one to allocate memory to hold the data and set the capacity.
|
|
#define noh_cb_initialize(da, size) { \
|
|
noh_assert((da)->capacity == 0 && "Cannot initialize an already initialized circular buffer."); \
|
|
noh_assert((size) > 0 && "Cannot initialize an empty circular buffer."); \
|
|
\
|
|
(da)->capacity = size; \
|
|
(da)->elems = noh_realloc_check((da)->elems, (da)->capacity * sizeof(*(da)->elems)); \
|
|
(da)->start = 0; \
|
|
(da)->count = 0; \
|
|
} \
|
|
|
|
// Inserts an element in a dynamic array as if it is a circular buffer, will not extend beyond the capacity of the
|
|
// dynamic array but instead overwrite the oldest element.
|
|
#define noh_cb_insert(da, elem) \
|
|
do { \
|
|
noh_assert((da)->capacity > 0 && "Circular buffer is not initialized."); \
|
|
\
|
|
if ((da)->count < (da)->capacity) { \
|
|
(da)->elems[(da)->count++] = (elem); \
|
|
} else { \
|
|
(da)->elems[(da)->start] = (elem); \
|
|
(da)->start = ((da)->start + 1) % (da)->count; \
|
|
} \
|
|
} while(0)
|
|
|
|
///////////////////////// Arena /////////////////////////
|
|
|
|
#define NOH_ARENA_INIT_CAP 1<<10
|
|
|
|
// Checkpoints in an arena.
|
|
typedef struct {
|
|
size_t block_id;
|
|
size_t offset_in_block;
|
|
} Noh_Arena_Checkpoint;
|
|
|
|
typedef struct {
|
|
Noh_Arena_Checkpoint *elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_Arena_Checkpoints;
|
|
|
|
// Data blocks in an arena.
|
|
typedef struct {
|
|
char *data;
|
|
size_t size;
|
|
size_t capacity;
|
|
} Noh_Arena_Data_Block;
|
|
|
|
typedef struct {
|
|
Noh_Arena_Data_Block *elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_Arena_Data_Blocks;
|
|
|
|
// An arena for storing temporary data.
|
|
typedef struct {
|
|
Noh_Arena_Data_Blocks blocks; // Blocks are always in order of increasing capacity.
|
|
Noh_Arena_Checkpoints checkpoints;
|
|
size_t active_block; // The index of the block up to which data has been allocated.
|
|
} Noh_Arena;
|
|
|
|
// Initialize an empty arena with the specified capacity. A checkpoint is also saved at the empty arena.
|
|
Noh_Arena noh_arena_init(size_t capacity);
|
|
|
|
// Resets the size of an arena to 0, keeping the data reserved. Any checkpoints are removed and one is saved at the
|
|
// start of the arena. Requires that the arena is initialized with noh_arena_init.
|
|
void noh_arena_reset(Noh_Arena *arena);
|
|
|
|
// Frees all data in an arena. Any checkpoints are removed. The arena is no longer initialized, and cannot be used
|
|
// anymore.
|
|
void noh_arena_free(Noh_Arena *arena);
|
|
|
|
// Ensures that there is room available for the requested size of data. Does not return a pointer to the data to the
|
|
// caller. Used if you want to pre-allocate a larger set of data that will later be filled by multiple allocations,
|
|
// keeping it in a single block.
|
|
void noh_arena_reserve(Noh_Arena *arena, size_t size);
|
|
|
|
// Allocates data in an arena of the requested size, returns the start of the data.
|
|
// Requires at least one checkpoint, either from noh_arena_init, noh_arena_reset or noh_arena_save.
|
|
void *noh_arena_alloc(Noh_Arena *arena, size_t size);
|
|
|
|
// Saves the current position in of the arena in a checkpoint. Requires that the arena is initialized with
|
|
// noh_arena_init.
|
|
void noh_arena_save(Noh_Arena *arena);
|
|
|
|
// Rewinds an arena to the last saved checkpoint. Requires at least one checkpoint.
|
|
void noh_arena_rewind(Noh_Arena *arena);
|
|
|
|
// Copies a c-string to the arena.
|
|
char *noh_arena_strdup(Noh_Arena *arena, const char *cstr);
|
|
|
|
// Prints the specified formatted string to the arena.
|
|
char *noh_arena_sprintf(Noh_Arena *arena, const char *format, ...);
|
|
|
|
///////////////////////// Strings /////////////////////////
|
|
|
|
// Defines a string that can be extended.
|
|
typedef struct {
|
|
char *elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_String;
|
|
|
|
// Copies a Noh_String to the arena, and frees the Noh_String
|
|
char *noh_arena_consume_string(Noh_Arena *arena, Noh_String *string);
|
|
|
|
// Creates a Noh_String from a c-string.
|
|
Noh_String noh_string_from_cstr(const char *cstr);
|
|
|
|
// Appends a null-terminated string into a Noh_String.
|
|
void noh_string_append_cstr(Noh_String *string, const char *cstr);
|
|
|
|
// Appends null into a Noh_String.
|
|
void noh_string_append_null(Noh_String *string);
|
|
|
|
// Frees a Noh_String, freeing the memory used and settings the count and capacity to 0.
|
|
#define noh_string_free(string) noh_da_free(string)
|
|
|
|
// Resets a Noh_String, setting the count to 0.
|
|
#define noh_string_reset(string) noh_da_reset(string)
|
|
|
|
// Returns true if a file or directory exists, false otherwise.
|
|
bool noh_file_exists(const char *filename);
|
|
|
|
// Reads the contents of a file into a Noh_String.
|
|
bool noh_string_read_file(Noh_String *string, const char *filename);
|
|
|
|
// Writes the contents of a Noh_String to a file.
|
|
bool noh_string_write_file(Noh_String *string, const char *filename);
|
|
|
|
///////////////////////// String view /////////////////////////
|
|
|
|
// A view of a string, that does not own the data.
|
|
typedef struct {
|
|
size_t count;
|
|
const char *elems;
|
|
} Noh_String_View;
|
|
|
|
// Copies a c-string to the arena and returns it as a Noh_String_View.
|
|
Noh_String_View noh_sv_copy_cstr(Noh_Arena *arena, const char *cstr);
|
|
|
|
// Appends a Noh_String_View view into a Noh_String.
|
|
void noh_string_append_sv(Noh_String *string, Noh_String_View sv);
|
|
|
|
// Increases the position of a string view by the specified amount, reducing its count by the same
|
|
// amount. If the end is reached, the string view will remain empty without moving further.
|
|
void noh_sv_increase_position(Noh_String_View *sv, size_t distance);
|
|
|
|
// Finds the first occurrence of the specified delimiter in a string view and returns the part of the string until
|
|
// that delimiter. The input string view itself is shrunk to start after the delimiter.
|
|
// If the delimiter is not found, the result is the entire string and the input string view is empty.
|
|
Noh_String_View noh_sv_chop_by_delim(Noh_String_View *sv, char delim);
|
|
|
|
// Finds the first occurrence of a line separator in a string view and returns the part of the string until
|
|
// that separator. The string view itself is shrunk to start after the separator.
|
|
// Supports '/r', '/n' and '/r/n'.
|
|
// If no line separator is found, the entire string is returned and the input string is empty.
|
|
Noh_String_View noh_sv_chop_line(Noh_String_View *sv);
|
|
|
|
// Chops a string while a predicate matches.
|
|
Noh_String_View noh_sv_chop_while(Noh_String_View *sv, bool (*do_chop)(char));
|
|
|
|
// Chops a string by the specified distance.
|
|
Noh_String_View noh_sv_chop(Noh_String_View *sv, size_t distance);
|
|
|
|
// Trims the left part of a string view, until the provided function no longer holds on the current character.
|
|
void noh_sv_trim_left(Noh_String_View *sv, bool (*do_trim)(char));
|
|
|
|
// Trims the right part of a string view, until the provided function no longer holds on the current character.
|
|
void noh_sv_trim_right(Noh_String_View *sv, bool (*do_trim)(char));
|
|
|
|
// Trims both sides of a string view, until the provided function no longer holds on the current character.
|
|
void noh_sv_trim(Noh_String_View *sv, bool (*do_trim)(char));
|
|
|
|
// Trims spaces from the left part of a string view.
|
|
inline void noh_sv_trim_space_left(Noh_String_View *sv);
|
|
|
|
// Trims spaces from the right part of a string view.
|
|
inline void noh_sv_trim_space_right(Noh_String_View *sv);
|
|
|
|
// Trims spaces from both sides of a string view.
|
|
inline void noh_sv_trim_space(Noh_String_View *sv);
|
|
|
|
// Creates a string view from a c-string.
|
|
Noh_String_View noh_sv_from_cstr(const char *cstr);
|
|
|
|
// Creates a string view from a string.
|
|
Noh_String_View noh_sv_from_string(const Noh_String string);
|
|
|
|
// Compares two string views.
|
|
// Returns:
|
|
// - 0 if the two strings are equal.
|
|
// - A negative value if a is smaller than b.
|
|
// - A positive value if a is greater than b.
|
|
int noh_sv_compare(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether to string views contain the same string.
|
|
bool noh_sv_eq(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether to string views contain the same string, ignoring the case.
|
|
bool noh_sv_eq_ci(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view starts with the elements from second string view.
|
|
bool noh_sv_starts_with(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view starts with the elements from second string view, ignoring the case.
|
|
bool noh_sv_starts_with_ci(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view ends with the elements from second string view.
|
|
bool noh_sv_ends_with(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view ends with the elements from second string view, ignoring the case.
|
|
bool noh_sv_ends_with_ci(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view contains the elements from the second string view.
|
|
inline bool noh_sv_contains(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Returns the first index of the second string view in the first string view.
|
|
// Returns -1 if it is not found.
|
|
int noh_sv_index_of(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Checks whether the first string view contains the elements from the second string view, ignoring the case.
|
|
inline bool noh_sv_contains_ci(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Returns the first index of the second string view in the first string view, ignoring the case.
|
|
// Returns -1 if it is not found.
|
|
int noh_sv_index_of_ci(Noh_String_View a, Noh_String_View b);
|
|
|
|
// Creates a cstring in an arena from a string view.
|
|
const char *noh_sv_to_arena_cstr(Noh_Arena *arena, Noh_String_View sv);
|
|
|
|
// Creates a substring from a string-view, where the start and end are capped to the bounds of the input string view.
|
|
// If a length of 0 is provided, the entire string after start is returned.
|
|
Noh_String_View noh_sv_substring(Noh_String_View sv, size_t start, size_t length);
|
|
|
|
// printf macros for Noh_String_View or Noh_String.
|
|
#define Nsv_Fmt "%.*s"
|
|
#define Nsv_Arg(sv) (int) (sv).count, (sv).elems
|
|
// USAGE:
|
|
// Noh_String_View name = ...;
|
|
// printf("Name: "Nsv_Fmt"\n", Nsv_Arg(name));
|
|
|
|
///////////////////////// Files and directories /////////////////////////
|
|
|
|
// File paths.
|
|
typedef struct {
|
|
char **elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_File_Paths;
|
|
|
|
// Creates the path at the specified directory if it does not exist.
|
|
// Does not create any missing parent directories.
|
|
bool noh_mkdir_if_needed(const char *path);
|
|
|
|
// Renames a file.
|
|
bool noh_rename(const char *path, const char *new_path);
|
|
|
|
// Removes a file.
|
|
bool noh_remove(const char *path);
|
|
|
|
///////////////////////// Processes /////////////////////////
|
|
|
|
// Process identifiers.
|
|
#ifdef _WIN32
|
|
typedef HANDLE Noh_Pid;
|
|
#define NOH_INVALID_PROC INVALID_HANDLE_VALUE
|
|
#else
|
|
typedef pid_t Noh_Pid;
|
|
#define NOH_INVALID_PROC (-1)
|
|
#endif // _WIN32
|
|
|
|
// A collection of processes.
|
|
typedef struct {
|
|
Noh_Pid *elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_Procs;
|
|
|
|
// Waits for a single process.
|
|
bool noh_proc_wait(Noh_Pid pid);
|
|
|
|
// Waits for a collection of processes.
|
|
bool noh_procs_wait(Noh_Procs procs);
|
|
|
|
// Frees the collection pocesses.
|
|
#define noh_procs_free(procs) noh_da_free(procs);
|
|
|
|
// Resets the collection of processes.
|
|
#define noh_procs_reset(procs) noh_da_reset(procs);
|
|
|
|
///////////////////////// Commands /////////////////////////
|
|
|
|
// Defines a command that can be run.
|
|
typedef struct {
|
|
const char **elems;
|
|
size_t count;
|
|
size_t capacity;
|
|
} Noh_Cmd;
|
|
|
|
// Appends one or more strings to a command.
|
|
#define noh_cmd_append(cmd, ...) \
|
|
noh_da_append_multiple( \
|
|
cmd, \
|
|
((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__}) / sizeof(const char*)))
|
|
|
|
// Frees a command, freeing the memory used for its elements and setting the count and capacity to 0.
|
|
#define noh_cmd_free(cmd) noh_da_free(cmd)
|
|
|
|
// Resets a command, setting the count to 0.
|
|
#define noh_cmd_reset(cmd) noh_da_reset(cmd)
|
|
|
|
// Runs a command asynchronously and returns the process id.
|
|
Noh_Pid noh_cmd_run_async(Noh_Cmd cmd);
|
|
|
|
// Runs a command synchronously.
|
|
bool noh_cmd_run_sync(Noh_Cmd cmd);
|
|
|
|
// Renders a textual representation of the command into the provided string.
|
|
void noh_cmd_render(Noh_Cmd cmd, Noh_String *string);
|
|
|
|
#endif // NOH_H_
|
|
|
|
#ifdef NOH_IMPLEMENTATION
|
|
|
|
///////////////////////// Core stuff /////////////////////////
|
|
|
|
void* noh_realloc_check_(void *target, size_t size) {
|
|
target = realloc(target, size);
|
|
noh_assert(target != NULL && "Could not allocate enough memory");
|
|
return target;
|
|
}
|
|
|
|
char *noh_shift_args(int *argc, char ***argv) {
|
|
noh_assert(*argc > 0 && "No more arguments");
|
|
|
|
char *result = **argv;
|
|
(*argv)++;
|
|
(*argc)--;
|
|
|
|
return result;
|
|
}
|
|
|
|
///////////////////////// Time /////////////////////////
|
|
|
|
long noh_diff_timespec_ms(const struct timespec *time1, const struct timespec *time2) {
|
|
noh_assert(time1);
|
|
noh_assert(time2);
|
|
|
|
long res = 0;
|
|
// Every second adds 1000 milliseconds difference.
|
|
res += (time1->tv_sec - time2->tv_sec) * 1000;
|
|
// Every 1000 * 1000 nanoseconds add 1 millisecond difference.
|
|
res += (time1->tv_nsec - time2->tv_nsec) / 1000 / 1000;
|
|
|
|
return res;
|
|
}
|
|
|
|
struct timespec noh_get_time_in(int seconds, long milliseconds) {
|
|
struct timespec time;
|
|
if (clock_gettime(CLOCK_REALTIME, &time) == -1)
|
|
{
|
|
noh_log(NOH_ERROR, "Unable to get the current time: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
noh_time_add(&time, seconds, milliseconds);
|
|
return time;
|
|
}
|
|
|
|
void noh_time_add(struct timespec *time, int seconds, long milliseconds) {
|
|
static long ns_per_ms = 1000 * 1000;
|
|
time->tv_sec += seconds;
|
|
time->tv_nsec += milliseconds * ns_per_ms;
|
|
|
|
// Fix any overflow.
|
|
if (time->tv_nsec >= 1000 * ns_per_ms) {
|
|
time->tv_sec += time->tv_nsec / (1000 * ns_per_ms);
|
|
time->tv_nsec %= 1000 * ns_per_ms;
|
|
}
|
|
}
|
|
|
|
///////////////////////// Logging /////////////////////////
|
|
|
|
void noh_log(Noh_Log_Level level, const char *fmt, ...)
|
|
{
|
|
switch (level) {
|
|
case NOH_INFO:
|
|
fprintf(stderr, "[INFO] ");
|
|
break;
|
|
case NOH_WARNING:
|
|
fprintf(stderr, "[WARNING] ");
|
|
break;
|
|
case NOH_ERROR:
|
|
fprintf(stderr, "[ERROR] ");
|
|
break;
|
|
default:
|
|
noh_assert(false && "Invalid log level");
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
///////////////////////// Arena /////////////////////////
|
|
|
|
// Alin a size such that it is a multiple of 8, keeping blocks of 64 bits.
|
|
size_t align_size(size_t size) {
|
|
return size + (size % 8);
|
|
}
|
|
|
|
Noh_Arena noh_arena_init(size_t size) {
|
|
Noh_Arena arena = {0};
|
|
|
|
Noh_Arena_Data_Blocks blocks = {0};
|
|
arena.blocks = blocks;
|
|
|
|
Noh_Arena_Checkpoints checkpoints = {0};
|
|
arena.checkpoints = checkpoints;
|
|
arena.active_block = 0;
|
|
|
|
Noh_Arena_Data_Block block = {0};
|
|
block.capacity = align_size(size);
|
|
block.data = noh_realloc_check(block.data, block.capacity);
|
|
block.size = 0;
|
|
noh_da_append(&arena.blocks, block);
|
|
|
|
// Nice to have a checkpoint at the start.
|
|
noh_arena_save(&arena);
|
|
|
|
return arena;
|
|
}
|
|
|
|
void noh_arena_reset(Noh_Arena *arena) {
|
|
// We need to load a block and save it in the checkpoint, so at least one block needs to be allocated.
|
|
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is inintialized.");
|
|
|
|
// Reset checkpoints.
|
|
noh_da_reset(&arena->checkpoints);
|
|
|
|
// Insert a checkpoint at the start so we can rewind to this checkpoint.
|
|
Noh_Arena_Checkpoint start_checkpoint = {0};
|
|
start_checkpoint.block_id = 0;
|
|
start_checkpoint.offset_in_block = 0;
|
|
noh_da_append(&arena->checkpoints, start_checkpoint);
|
|
|
|
// Rewind to the checkpoint, and place the checkpoint back in such that there is again a checkpoint at the start.
|
|
noh_arena_rewind(arena);
|
|
arena->checkpoints.count += 1;
|
|
}
|
|
|
|
void noh_arena_free(Noh_Arena *arena) {
|
|
// Remove checkpoints.
|
|
noh_da_free(&arena->checkpoints);
|
|
|
|
// Free all blocks.
|
|
for (size_t i = 0; i < arena->blocks.count; i++) {
|
|
Noh_Arena_Data_Block *block = &arena->blocks.elems[i];
|
|
free(block->data);
|
|
}
|
|
|
|
// Remove blocks.
|
|
noh_da_free(&arena->blocks);
|
|
|
|
arena->active_block = 0;
|
|
}
|
|
|
|
void *noh_arena_alloc(Noh_Arena *arena, size_t size) {
|
|
// This is technically not needed, but it is nice to be consistent and ensure that there is always a checkpoint
|
|
// at the beginning, either from noh_arena_init, noh_arena_reset or noh_arena_save.
|
|
noh_assert(arena->checkpoints.count > 0 && "Please ensure that there is at least one checkpoint before allocating.");
|
|
|
|
// Reserve will ensure that we have the required space available. Then we just need to find the block where we can
|
|
// allocate the requested space.
|
|
noh_arena_reserve(arena, size);
|
|
|
|
// Find the block that fits the requested size.
|
|
size_t current_block = arena->active_block;
|
|
Noh_Arena_Data_Block *block = &arena->blocks.elems[current_block];
|
|
while (block->capacity - block->size < size && current_block < arena->blocks.count) {
|
|
current_block += 1;
|
|
block = &arena->blocks.elems[current_block];
|
|
}
|
|
|
|
noh_assert(block->capacity - block->size >= size && "Reserve should have provided a large enough block.");
|
|
|
|
arena->active_block = current_block;
|
|
|
|
// Allocate data in the block and return a pointer to the start.
|
|
void *result = &block->data[block->size];
|
|
block->size += size;
|
|
return result;
|
|
}
|
|
|
|
void noh_arena_reserve(Noh_Arena *arena, size_t size) {
|
|
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is initialized.");
|
|
|
|
size_t requested_size = align_size(size);
|
|
|
|
while (arena->active_block < arena->blocks.count) {
|
|
Noh_Arena_Data_Block *block = &arena->blocks.elems[arena->active_block];
|
|
// If the requested size fits into the current block, use it and return.
|
|
if (block->capacity - block->size >= requested_size) {
|
|
return;
|
|
}
|
|
|
|
// If it doesn't, free the block if it was empty. Note that all but the current block will be empty, since
|
|
// rewinding sets the sizes of later blocks to 0. Current block may be empty.
|
|
if (block->size == 0) {
|
|
free(block->data);
|
|
|
|
// This reduces arena->blocks.count, thus ensuring termination of the loop.
|
|
noh_da_remove_at(&arena->blocks, arena->active_block);
|
|
} else {
|
|
// If we're not cleaning this block up, move the pointer.
|
|
arena->active_block += 1;
|
|
}
|
|
}
|
|
|
|
// If no block was found that fits, create a new one that is at least as big as the requested size, and twice the
|
|
// size of the current block.
|
|
// arena->active_block will now point to just beyond the last existing block. We can get the previous capacity
|
|
// only if we didn't just delete the first block.
|
|
size_t prev_cap = 0;
|
|
if (arena->active_block > 1) prev_cap = arena->blocks.elems[arena->active_block - 1].capacity;
|
|
|
|
size_t new_cap = NOH_ARENA_INIT_CAP;
|
|
// If not big enough to double the previous cap, set to double the previous capacity.
|
|
if (prev_cap * 2 > new_cap) new_cap = prev_cap * 2;
|
|
// Keep doubling until the requested size fits.
|
|
while (requested_size > new_cap) new_cap *= 2;
|
|
|
|
Noh_Arena_Data_Block new_block = {0};
|
|
new_block.data = noh_realloc_check(new_block.data, new_cap);
|
|
new_block.capacity = new_cap;
|
|
new_block.size = 0;
|
|
|
|
// After adding this block, arena->active_block will point to this new block.
|
|
noh_da_append(&(arena->blocks), new_block);
|
|
}
|
|
|
|
void noh_arena_save(Noh_Arena *arena) {
|
|
// We need to load a block and save it in the checkpoint, so at least one block needs to be allocated.
|
|
noh_assert(arena->blocks.count > 0 && "Please ensure that the arena is inintialized.");
|
|
|
|
Noh_Arena_Checkpoint checkpoint = {0};
|
|
checkpoint.block_id = arena->active_block;
|
|
|
|
Noh_Arena_Data_Block *block = &arena->blocks.elems[arena->active_block];
|
|
checkpoint.offset_in_block = block->size;
|
|
|
|
noh_da_append(&(arena->checkpoints), checkpoint);
|
|
}
|
|
|
|
void noh_arena_rewind(Noh_Arena *arena) {
|
|
noh_assert(arena->checkpoints.count > 0 && "No history to rewind");
|
|
|
|
// Restore to block from checkpoint.
|
|
Noh_Arena_Checkpoint *checkpoint = &arena->checkpoints.elems[arena->checkpoints.count - 1];
|
|
arena->active_block = checkpoint->block_id;
|
|
|
|
// Rewind all blocks from the active block to the end.
|
|
for (size_t i = arena->active_block; i < arena->blocks.count; i++) {
|
|
Noh_Arena_Data_Block *block = &arena->blocks.elems[i];
|
|
if (i == arena->active_block) block->size = checkpoint->offset_in_block;
|
|
else block->size = 0;
|
|
|
|
}
|
|
|
|
// Remove checkpoint.
|
|
arena->checkpoints.count -= 1;
|
|
}
|
|
|
|
char *noh_arena_strdup(Noh_Arena *arena, const char *cstr) {
|
|
size_t len = strlen(cstr);
|
|
char *result = noh_arena_alloc(arena, len + 1);
|
|
memcpy(result, cstr, len);
|
|
result[len] = '\0';
|
|
return result;
|
|
}
|
|
|
|
char *noh_arena_sprintf(Noh_Arena *arena, const char *format, ...) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
int n = vsnprintf(NULL, 0, format, args);
|
|
va_end(args);
|
|
|
|
noh_assert(n >= 0);
|
|
char *result = noh_arena_alloc(arena, n + 1);
|
|
va_start(args, format);
|
|
vsnprintf(result, n + 1, format, args);
|
|
va_end(args);
|
|
|
|
return result;
|
|
}
|
|
|
|
///////////////////////// Strings /////////////////////////
|
|
|
|
char *noh_arena_consume_string(Noh_Arena *arena, Noh_String *string) {
|
|
char *result = noh_arena_alloc(arena, string->count + 1);
|
|
memcpy(result, string->elems, string->count);
|
|
result[string->count] = '\0';
|
|
|
|
noh_string_free(string);
|
|
return result;
|
|
}
|
|
|
|
Noh_String noh_string_from_cstr(const char *cstr) {
|
|
Noh_String str = {0};
|
|
noh_string_append_cstr(&str, cstr);
|
|
return str;
|
|
}
|
|
|
|
void noh_string_append_cstr(Noh_String *string, const char *cstr) {
|
|
size_t len = strlen(cstr);
|
|
noh_da_append_multiple(string, cstr, len);
|
|
}
|
|
|
|
void noh_string_append_null(Noh_String *string) {
|
|
noh_da_append(string, '\0');
|
|
}
|
|
|
|
bool noh_file_exists(const char *filename) {
|
|
struct stat st;
|
|
return (stat(filename, &st)) == 0;
|
|
}
|
|
|
|
bool noh_string_read_file(Noh_String *string, const char *filename) {
|
|
bool result = true;
|
|
size_t buf_size = 32*1024;
|
|
char *buf = NULL;
|
|
buf = noh_realloc_check(buf, buf_size);
|
|
|
|
FILE *f = fopen(filename, "rb");
|
|
if (f == NULL) {
|
|
noh_log(NOH_ERROR, "Could not open file %s: %s.\n", filename, strerror(errno));
|
|
noh_return_defer(false);
|
|
}
|
|
|
|
size_t n = fread(buf, 1, buf_size, f);
|
|
while (n > 0) {
|
|
noh_da_append_multiple(string, buf, n);
|
|
n = fread(buf, 1, buf_size, f);
|
|
}
|
|
|
|
if (ferror(f)) {
|
|
noh_log(NOH_ERROR, "Could not read file %s: %s.\n", filename, strerror(errno));
|
|
noh_return_defer(false);
|
|
}
|
|
|
|
defer:
|
|
free(buf);
|
|
if (f) fclose(f);
|
|
return result;
|
|
}
|
|
|
|
bool noh_string_write_file(Noh_String *string, const char *filename) {
|
|
bool result = true;
|
|
|
|
FILE *f = fopen(filename, "wb");
|
|
if (f == NULL) {
|
|
noh_log(NOH_ERROR, "Could not open file %s: %s.\n", filename, strerror(errno));
|
|
noh_return_defer(false);
|
|
}
|
|
|
|
int written = fprintf(f, Nsv_Fmt, Nsv_Arg(*string));
|
|
if (written < 0) {
|
|
noh_log(NOH_ERROR, "Could not write to file %s.\n", filename);
|
|
noh_return_defer(false);
|
|
}
|
|
|
|
defer:
|
|
if (f) fclose(f);
|
|
return result;
|
|
}
|
|
|
|
///////////////////////// String view /////////////////////////
|
|
|
|
Noh_String_View noh_sv_copy_cstr(Noh_Arena *arena, const char *cstr) {
|
|
size_t len = strlen(cstr);
|
|
char *buffer = noh_arena_alloc(arena, len);
|
|
memcpy(buffer, cstr, len);
|
|
return (Noh_String_View){ .elems = buffer, .count = len };
|
|
}
|
|
|
|
void noh_string_append_sv(Noh_String *string, Noh_String_View sv) {
|
|
noh_da_append_multiple(string, sv.elems, sv.count);
|
|
}
|
|
|
|
void noh_sv_increase_position(Noh_String_View *sv, size_t distance) {
|
|
if (distance < sv->count) {
|
|
sv->count -= distance;
|
|
sv->elems += distance;
|
|
} else {
|
|
sv->count = 0;
|
|
sv->elems += sv->count;
|
|
}
|
|
}
|
|
|
|
Noh_String_View noh_sv_chop_by_delim(Noh_String_View *sv, char delim) {
|
|
size_t i = 0;
|
|
// Find the character, or the end of the string view.
|
|
while (i < sv->count && sv->elems[i] != delim) i++;
|
|
|
|
// The data until the delimiter is returned.
|
|
Noh_String_View result = { .count = i, .elems = sv->elems };
|
|
|
|
// Update the current string view beyond the delimiter.
|
|
noh_sv_increase_position(sv, i + 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
Noh_String_View noh_sv_chop_while(Noh_String_View *sv, bool (*do_chop)(char)) {
|
|
size_t i = 0;
|
|
// Keep going until the end or the function no longer matches.
|
|
while (i < sv->count && ((*do_chop)(sv->elems[i]))) i++;
|
|
|
|
// The data until this point is returned.
|
|
Noh_String_View result = { .count = i, .elems = sv->elems };
|
|
|
|
// Update the current string view to after this point.
|
|
noh_sv_increase_position(sv, i);
|
|
|
|
return result;
|
|
}
|
|
|
|
Noh_String_View noh_sv_chop(Noh_String_View *sv, size_t distance) {
|
|
Noh_String_View result = noh_sv_substring(*sv, 0, distance);
|
|
noh_sv_increase_position(sv, distance);
|
|
return result;
|
|
}
|
|
|
|
Noh_String_View noh_sv_chop_line(Noh_String_View *sv) {
|
|
size_t i = 0;
|
|
// Find a newline or carriage return character.
|
|
while (i < sv->count && sv->elems[i] != '\r' && sv->elems[i] != '\n') i++;
|
|
|
|
// The data until the line separator(s) is returned.
|
|
Noh_String_View result = { .count = i, .elems = sv->elems };
|
|
|
|
// Update the current string view beyond the line separator(s).
|
|
if (i + 1 < sv->count && sv->elems[i] == '\r' && sv->elems[i + 1] == '\n') {
|
|
// Skip carriage return and newline.
|
|
noh_sv_increase_position(sv, i + 2);
|
|
} else {
|
|
// Skip single newline, carriage return or to the end
|
|
// (noh_sv_increase_position allows too high increases).
|
|
noh_sv_increase_position(sv, i + 1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void noh_sv_trim_left(Noh_String_View *sv, bool (*do_trim)(char)) {
|
|
size_t i = 0;
|
|
while (i < sv->count && (*do_trim)(sv->elems[i])) i++;
|
|
noh_sv_increase_position(sv, i);
|
|
}
|
|
|
|
void noh_sv_trim_right(Noh_String_View *sv, bool (*do_trim)(char)) {
|
|
size_t i = sv->count;
|
|
while (i > 0 && (*do_trim)(sv->elems[i-1])) i--;
|
|
sv->count = i;
|
|
}
|
|
|
|
void noh_sv_trim(Noh_String_View *sv, bool (*do_trim)(char)) {
|
|
noh_sv_trim_left(sv, do_trim);
|
|
noh_sv_trim_right(sv, do_trim);
|
|
}
|
|
|
|
bool is_space(char c) {
|
|
return isspace(c) > 0;
|
|
}
|
|
|
|
inline void noh_sv_trim_space_left(Noh_String_View *sv) {
|
|
noh_sv_trim_left(sv, &is_space);
|
|
}
|
|
|
|
inline void noh_sv_trim_space_right(Noh_String_View *sv) {
|
|
noh_sv_trim_right(sv, &is_space);
|
|
}
|
|
|
|
inline void noh_sv_trim_space(Noh_String_View *sv) {
|
|
noh_sv_trim(sv, &is_space);
|
|
}
|
|
|
|
Noh_String_View noh_sv_from_cstr(const char *cstr) {
|
|
Noh_String_View result = {0};
|
|
result.elems = cstr;
|
|
result.count = strlen(cstr);
|
|
return result;
|
|
}
|
|
|
|
Noh_String_View noh_sv_from_string(const Noh_String string) {
|
|
Noh_String_View result = {0};
|
|
result.elems = string.elems;
|
|
result.count = string.count;
|
|
return result;
|
|
}
|
|
|
|
int noh_sv_compare(Noh_String_View a, Noh_String_View b) {
|
|
size_t count = a.count;
|
|
if (b.count < a.count) count = b.count;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (a.elems[i] == b.elems[i]) continue;
|
|
return a.elems[i] - b.elems[i];
|
|
}
|
|
|
|
return a.count - b.count;
|
|
}
|
|
|
|
bool noh_sv_eq(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count != b.count) return false;
|
|
|
|
for (size_t i = 0; i < a.count; i++) {
|
|
if (a.elems[i] != b.elems[i]) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check two characters case insensitively.
|
|
bool char_eq_ci(char a, char b) {
|
|
// FUTURE: Unicode support?
|
|
// A=65, Z=90 - a=97, z=122
|
|
if (a >= 65 && a <= 90) a +=32;
|
|
if (b >= 65 && b <= 90) b +=32;
|
|
|
|
return a == b;
|
|
}
|
|
|
|
bool noh_sv_eq_ci(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count != b.count) return false;
|
|
|
|
for (size_t i = 0; i < a.count; i++) {
|
|
if (!char_eq_ci(a.elems[i], b.elems[i])) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool noh_sv_starts_with(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count < b.count) return false;
|
|
a.count = b.count;
|
|
return noh_sv_eq(a, b);
|
|
}
|
|
|
|
bool noh_sv_starts_with_ci(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count < b.count) return false;
|
|
a.count = b.count;
|
|
return noh_sv_eq_ci(a, b);
|
|
}
|
|
|
|
bool noh_sv_ends_with(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count < b.count) return false;
|
|
a.elems += (a.count - b.count);
|
|
a.count = b.count;
|
|
return noh_sv_eq(a, b);
|
|
}
|
|
|
|
bool noh_sv_ends_with_ci(Noh_String_View a, Noh_String_View b) {
|
|
if (a.count < b.count) return false;
|
|
a.elems += (a.count - b.count);
|
|
a.count = b.count;
|
|
return noh_sv_eq_ci(a, b);
|
|
}
|
|
|
|
inline bool noh_sv_contains(Noh_String_View a, Noh_String_View b) {
|
|
return noh_sv_index_of(a, b) >= 0;
|
|
}
|
|
|
|
int noh_sv_index_of(Noh_String_View a, Noh_String_View b) {
|
|
int i = 0;
|
|
while (a.count >= b.count) {
|
|
if (noh_sv_starts_with(a, b)) return i;
|
|
noh_sv_increase_position(&a, 1);
|
|
i++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
inline bool noh_sv_contains_ci(Noh_String_View a, Noh_String_View b) {
|
|
return noh_sv_index_of_ci(a, b) >= 0;
|
|
}
|
|
|
|
int noh_sv_index_of_ci(Noh_String_View a, Noh_String_View b) {
|
|
int i = 0;
|
|
while (a.count >= b.count) {
|
|
if (noh_sv_starts_with_ci(a, b)) return i;
|
|
noh_sv_increase_position(&a, 1);
|
|
i++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
const char *noh_sv_to_arena_cstr(Noh_Arena *arena, Noh_String_View sv)
|
|
{
|
|
char *result = noh_arena_alloc(arena, sv.count + 1);
|
|
memcpy(result, sv.elems, sv.count);
|
|
result[sv.count] = '\0';
|
|
return result;
|
|
}
|
|
|
|
Noh_String_View noh_sv_substring(Noh_String_View sv, size_t start, size_t length) {
|
|
Noh_String_View result = (Noh_String_View) { sv.count, sv.elems };
|
|
noh_sv_increase_position(&result, start);
|
|
if (result.count > length && length > 0) result.count = length;
|
|
return result;
|
|
}
|
|
|
|
///////////////////////// Files and directories /////////////////////////
|
|
|
|
bool noh_mkdir_if_needed(const char *path) {
|
|
#ifdef _WIN32
|
|
int result = mkdir(path);
|
|
#else
|
|
int result = mkdir(path, 0755);
|
|
#endif // _WIN32
|
|
|
|
if (result == 0) {
|
|
noh_log(NOH_INFO, "Created directory '%s'.", path);
|
|
return true;
|
|
}
|
|
|
|
if (errno == EEXIST) {
|
|
noh_log(NOH_INFO, "Directory '%s' already exists.", path);
|
|
return true;
|
|
}
|
|
|
|
noh_log(NOH_ERROR, "Could not create directory '%s': %s", path, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
bool noh_rename(const char *path, const char *new_path)
|
|
{
|
|
noh_log(NOH_INFO, "Renaming '%s' to '%s'.", path, new_path);
|
|
if (rename(path, new_path) < 0) {
|
|
noh_log(NOH_ERROR, "Rename failed: %s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool noh_remove(const char *path) {
|
|
noh_log(NOH_INFO, "Removing '%s'.", path);
|
|
if (remove(path) < 0) {
|
|
noh_log(NOH_ERROR, "Remove failed: %s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////// Processes /////////////////////////
|
|
|
|
bool noh_proc_wait(Noh_Pid pid)
|
|
{
|
|
if (pid == NOH_INVALID_PROC) return false;
|
|
|
|
#ifdef _WIN32
|
|
DWORD result = WaitForSingleObject(pid, INFINITE);
|
|
|
|
if (result == WAIT_FAILED) {
|
|
noh_log(NOH_ERROR, "Could not wait for command: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
DWORD exit_status;
|
|
if (!GetExitCodeProcess(pid, &exit_status)) {
|
|
noh_log(NOH_ERROR, "Could not get command exit code: %lu", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
if (exit_status != 0) {
|
|
noh_log(NOH_ERROR, "Command exited with exit code %lu", exit_status);
|
|
return false;
|
|
}
|
|
|
|
CloseHandle(pid);
|
|
#else
|
|
for (;;) {
|
|
int wstatus = 0;
|
|
if (waitpid(pid, &wstatus, 0) < 0) {
|
|
noh_log(NOH_ERROR, "Could not wait for command (pid %d): %s", pid, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (WIFEXITED(wstatus)) {
|
|
int exit_status = WEXITSTATUS(wstatus);
|
|
if (exit_status != 0) {
|
|
noh_log(NOH_ERROR, "Command exited with exit code %d", exit_status);
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (WIFSIGNALED(wstatus)) {
|
|
noh_log(NOH_ERROR, "Command process was terminated by %s", strsignal(WTERMSIG(wstatus)));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endif // _WIN32
|
|
return true;
|
|
}
|
|
|
|
bool noh_procs_wait(Noh_Procs procs) {
|
|
bool success = true;
|
|
for (size_t i = 0; i < procs.count; i++) {
|
|
success = noh_proc_wait(procs.elems[i]) && success;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
///////////////////////// Commands /////////////////////////
|
|
|
|
// Adds a c string to a string, surrounding it with single quotes if it contains any spaces.
|
|
void noh_quote_if_needed(const char *value, Noh_String *string) {
|
|
if (!strchr(value, ' ')) {
|
|
noh_string_append_cstr(string, value);
|
|
} else {
|
|
noh_da_append(string, '\'');
|
|
noh_string_append_cstr(string, value);
|
|
noh_da_append(string, '\'');
|
|
}
|
|
}
|
|
|
|
void noh_cmd_render(Noh_Cmd cmd, Noh_String *string) {
|
|
for (size_t i = 0; i < cmd.count; ++i) {
|
|
const char *arg = cmd.elems[i];
|
|
if (arg == NULL) break;
|
|
if (i > 0) noh_string_append_cstr(string, " ");
|
|
noh_quote_if_needed(arg, string);
|
|
}
|
|
}
|
|
|
|
Noh_Pid noh_cmd_run_async(Noh_Cmd cmd) {
|
|
if (cmd.count < 1) {
|
|
noh_log(NOH_ERROR, "Cannot run an empty command.");
|
|
return NOH_INVALID_PROC;
|
|
}
|
|
|
|
// Log the command.
|
|
Noh_String sb = {0};
|
|
noh_cmd_render(cmd, &sb);
|
|
noh_da_append(&sb, '\0');
|
|
noh_log(NOH_INFO, "CMD: %s", sb.elems);
|
|
|
|
#ifdef _WIN32
|
|
noh_string_reset(&sb);
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
|
|
STARTUPINFO suInfo;
|
|
ZeroMemory(&suInfo, sizeof(STARTUPINFO));
|
|
suInfo.cb = sizeof(STARTUPINFO);
|
|
suInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
suInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
suInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
suInfo.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
|
PROCESS_INFORMATION procInfo;
|
|
ZeroMemory(&procInfo, sizeof(PROCESS_INFORMATION));
|
|
|
|
noh_cmd_render(cmd, &sb);
|
|
noh_string_append_null(&sb);
|
|
bool success = CreateProcessA(NULL, sb.elems, NULL, NULL, true, 0, NULL, NULL, &suInfo, &procInfo);
|
|
noh_string_free(&sb);
|
|
|
|
if (!success) {
|
|
noh_log(NOH_ERROR, "Could not create child process: %lu", GetLastError());
|
|
return NOH_INVALID_PROC;
|
|
}
|
|
|
|
CloseHandle(procInfo.hThread);
|
|
return procInfo.hProcess;
|
|
#else
|
|
noh_string_free(&sb);
|
|
|
|
Noh_Pid cpid = fork();
|
|
if (cpid < 0) {
|
|
noh_log(NOH_ERROR, "Could not fork child process: %s", strerror(errno));
|
|
return NOH_INVALID_PROC;
|
|
}
|
|
|
|
if (cpid == 0) {
|
|
// NOTE: This leaks a bit of memory in the child process.
|
|
// But do we actually care? It's a one off leak anyway...
|
|
// Create a command that is null terminated.
|
|
Noh_Cmd cmd_null = {0};
|
|
noh_da_append_multiple(&cmd_null, cmd.elems, cmd.count);
|
|
noh_cmd_append(&cmd_null, NULL);
|
|
|
|
if (execvp(cmd.elems[0], (char * const*) cmd_null.elems) < 0) {
|
|
noh_log(NOH_ERROR, "Could not execute child process: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
noh_assert(0 && "unreachable");
|
|
}
|
|
|
|
return cpid;
|
|
#endif // _WIN32
|
|
}
|
|
|
|
bool noh_cmd_run_sync(Noh_Cmd cmd) {
|
|
Noh_Pid pid = noh_cmd_run_async(cmd);
|
|
if (pid == NOH_INVALID_PROC) return false;
|
|
|
|
return noh_proc_wait(pid);
|
|
}
|
|
|
|
#endif // NOH_IMPLEMENTATION
|