Compare commits

..

No commits in common. 'master' and 'generic-project-skeleton' have entirely different histories.

@ -7,7 +7,7 @@ AUTHOR=Avril (Flanchan) <flanchan@cumallover.me>
LICENSE=GPL3+
VERSION_MAJ=0
VERSION_MIN=1
VERSION_MIN=0
VERSION_BF=0
VERSION_REV=0
@ -20,18 +20,13 @@ SRC_CXX = $(wildcard src/*.cpp)
INCLUDE=include
# Set compile-time features
# # Available features
# * PARALLEL - Enable multithreaded processing (currently unimplemented.)
FEATURES?=
META_FLAGS:=-D_PROJECT_AUTHOR="\"$(AUTHOR)\"" -D_PROJECT_DESCRIPTION="\"$(DESCRIPTION)\"" -D_PROJECT_NAME="\"$(PROJECT)\"" \
-D_PROJECT_LICENSE="\"$(LICENSE)\"" \
-D_VERSION_MAJOR=$(VERSION_MAJ) -D_VERSION_MINOR=$(VERSION_MIN) \
-D_VERSION_BUGFIX=$(VERSION_BF) -D_VERSION_REVISION=$(VERSION_REV) \
-D_PROJECT_COMPILED=$(COMP_TIME)
COMMON_FLAGS+= -W -Wall -fno-strict-aliasing $(addprefix -I,$(INCLUDE)) $(META_FLAGS) $(addprefix -D_FEATURE_,$(FEATURES))
COMMON_FLAGS+= -W -Wall -fno-strict-aliasing $(addprefix -I,$(INCLUDE)) $(META_FLAGS)
ARCH?=native
OPT_FLAGS+= -fgraphite -fopenmp -floop-parallelize-all -ftree-parallelize-loops=4 \

@ -1,45 +0,0 @@
#ifndef _COMP_H
#define _COMP_H
#include <macros.h>
#include <ints.h>
#include <slice.h>
#include <map.h>
typedef struct comp_aggregate {
bool all_matched;
int idx_first_failure;
usize num_results;
struct comp_single_result {
const map_t* _shared mapping;
bool matched;
usize slice_start;
} aggr[];
} comp_multires_t;
// Create a `slice_t` from a successful match against `needle`.
_mixin bool comp_slice_of(const map_t*pIN needle, const struct comp_single_result *pIN match, slice_t *pOUT slice)
{
ifL(match->matched) {
*slice = SLICE(AS(match->mapping->origin,u8*)+match->slice_start, needle->len);
return true;
} else return false;
}
/// Find `needle` in `haystack`, sets the start of `needle`'s `origin` in `haystack`'s to `pos` and returns `true` if a match is found, otherwise returns false.
bool cmp_find(const map_t *pIN needle, const map_t *pIN haystack, usize *pOUT pos);
/// Check `needle` against `nhaystacks` number of haystack maps, store the offset in `sizes[n]` where `n` is the haystack number.
/// `sizes` must be at least `nhaystacks` long. The haystacks themselves are expected to be of type `const map_t* pIN`
/// Returns `-1` if all haystacks passed match, otherwise returns the index of the haystack that failed.
///
/// # Panics
/// Will `FATAL` if any of the haystack pointers are `NULL`
int cmp_find_many(const map_t *pIN needle, usize nhaystacks, usize sizes[pOUT nhaystacks], ...);
// The return value of this function must be `free()`d after use
comp_multires_t* comp_match_all(map_t *pIN needle, usize nh, map_t hs[pIN nh], bool abort_on_fail);
#endif /* _COMP_H */

@ -1,40 +0,0 @@
#ifndef _DISPLAY_H
#define _DISPLAY_H
#include <stdio.h>
#include "macros.h"
#include "ints.h"
#include "slice.h"
typedef enum display_kind {
DISPK_NORMAL = 0,
DISPK_CSV,
DISPK_BINARY,
DISPK_INI,
} dispkind_t;
#define X(n) AS(1lu << (n), usize)
typedef enum display_flags {
// Show the number (index) of they haystack matched
DISPF_SHOW_NUMBER = X(0),
// Show the slice
DISPF_SHOW_SLICE = X(1),
// Show the length of the slice
DISPF_SHOW_LENGTH = X(2),
// Show failed matches
DISPF_SHOW_FAILURES = X(3),
} dispflags_t;
#define DISPLAY_FLAGS_DEFAULT (DISPF_SHOW_SLICE | DISPF_SHOW_NUMBER | DISPF_SHOW_FAILURES)
#undef X
typedef struct {
bool matched;
slice_t cslice;
const void* base;
usize index;
} dispin_t;
void display_result(FILE* output, dispin_t input, dispkind_t how, dispflags_t flags);
#endif /* _DISPLAY_H */

@ -16,15 +16,10 @@
#define _pure __attribute__((const))
#define _readonly __attribute__((pure))
#define _cold __attribute__((cold))
#define _hot __attribute__((hot))
#define _dead __attribute__((unused))
#ifndef __cplusplus
#define noreturn __attribute__((noreturn))
#define _deprecated __attribute__((deprecated))
#define deprecated __attribute__((deprecated))
#define deprecated_message(str) __attribute__((deprecated(str)))
#define _deprecated_msg(str) deprecated_message(str)
#endif
#define noinline __attribute__((noinline))
@ -44,42 +39,15 @@
#define restrict __restrict__
#endif
// Function conventions
#define _cconv__mixin _mixin
#define _cconv__pure _pure
#define _cconv__readonly _readonly
#define _cconv__cold _cold
#define _cconv__dead _dead
#define _cconv__unused _dead
#define _cconv__hot _hot
#define _cconv__entry __attribute__((constructor))
#define _cconv__exit __attribute__((destructor))
// Allocates
#define _cconv__alloc __attribute__((malloc))
// Infallible alloc
#define _cconv__ialloc __attribute__((malloc)) __attribute__((returns_nonnull))
#define _cconv__nonnull __attribute__((returns_nonnull))
#define _callcv(name) _cconv__ ## name
#define _callconv(name) _callcv(name)
#define _callat_entry _callcv(entry)
#define _callat_exit _callcv(exit)
#define _callat(when) _callat_ ## when
// Argument attribute
// Used like: `int *pOUT output, const int *pIN input`
#define pOUT restrict
#define pIN const restrict
#define pINOUT restrict
// Used like: `int _OUT output, const int _IN input`
#define _IN *pIN
#define _OUT *pOUT
#define _INOUT *pINOUT
// Type macros
@ -98,29 +66,6 @@
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define ifL(x) if(LIKELY((x)))
#define ifU(x) if(UNLIKELY((x)))
#define COLD_EXPR(expr) ({ \
goto _cold_expr; \
UNREACHABLE_UNSAFE(); \
_cold_expr: \
__attribute__((cold)); \
expr; \
})
#define HOT_EXPR(expr) ({ \
goto _hot_expr; \
UNREACHABLE_UNSAFE(); \
_hot_expr: \
__attribute__((hot)); \
expr; \
})
// Use like: COLD_BLOCK { ... }
// XXX: I'm not exactly sure if these hacks work.. Should test.
#define COLD_BLOCK if(UNLIKELY(1))
// Use like: HOT_BLOCK { ... }
#define HOT_BLOCK if(LIKELY(1))
/// Equivalent to GNU C `x ?: y` operator.
#define TERN_OR(x, y) ({ let _x = (x); _x ? _x : (y); })
@ -128,38 +73,21 @@
#define _no_op ((void)0)
#define IGNORE(x) ((void)(x))
_mixin void _drain_val(void* x, ...) { IGNORE(x); } // This compiles to no-op on -O0 and -O2+ due to `gnu_inline` (_mixin)
_mixin void _drain_val(void* x, ...) { IGNORE(x); }
#define _drain(...) _drain_val(NULL, __VA_ARGS__)
#define IGNORE_ALL(...) _drain(__VA_ARGS__)
// Allocation macros
#define box(t) aligned_alloc(_Alignof(t), sizeof(t))
#define box_value(v) ({ let _box = box(var(v)); *_box = (v); _box; })
#define unbox_value(v) ({ let _v = (v); let _res = *_v; free(_v); _res; })
#define stackalloc(t) __builtin_alloca_with_align(sizeof(t), _Alignof(t))
// Function macros
// debug-only statements
#ifdef DEBUG
#define DBG(x) x
#else
#define DBG(x) ((void)0)
#endif
// Assertions
#define assert_msg(expr, ...) ( (expr) ? 1 : FATAL(__VA_ARGS__))
#define UNREACHABLE_UNSAFE() __builtin_unreachable()
#ifdef DEBUG
#define UNREACHABLE() do { COLD_EXPR(FATAL("unreachable code entered")); } while(0)
#else
#define UNREACHABLE() UNREACHABLE_UNSAFE()
#endif
#ifdef DEBUG
#define debug_assert(x) assert((x))
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
@ -206,34 +134,26 @@ static_assert_eq(bswap(128lu), 9223372036854775808lu, "bswap128 (lu) failed (1)"
static_assert_eq(128lu, bswap(9223372036854775808lu), "bswap128 (lu) failed (2)");
static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");
// Transmute
#define TRANSMUTE(val, type) ({ union _trans { var(val) input; type output; }; \
_Static_assert(sizeof(var(val)) == sizeof(type), "Cannot transmute values of different sizes"); \
_Static_assert(sizeof(union _trans) == sizeof(type), "Cannot transmute values of different sizes"); \
/* XXX: Do we need do check this for equality? Can we re-word it or do we even need it at all? _Static_assert(_Alignof(union _trans) == _Alignof(type), "Cannot transmute values of different alignments");*/ \
union _trans _trans__value = { .input = (val), }; \
_trans__value.output; })
// Trace message output
#include "trace.h"
#define TRACEx(L, X, msg, ...) (_t_fprintf(TRACE_LEVEL_ ## L, stderr, "[" X "] " __FILE__ ":%d->%s(): " msg "\n", __LINE__, __func__, ## __VA_ARGS__))
#define TRACEx(X, msg, ...) (fprintf(stderr, "[" X "] " __FILE__ ":%d->%s(): " msg "\n", __LINE__, __func__, ## __VA_ARGS__))
#ifdef DEBUG
#define dprintf(msg, ...) TRACEx(DEBUG, "debug", msg, ## __VA_ARGS__)
#define TRACE(msg, ...) TRACEx("trace", msg, ## __VA_ARGS__)
#define dprintf(msg, ...) TRACEx("debug", msg, ## __VA_ARGS__)
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
#define TRACE(msg, ...) _drain(msg, ## __VA_ARGS__)
#define dprintf(msg, ...) _drain(msg, ## __VA_ARGS__)
#else
#define TRACE(msg, ...) _no_op
#endif
#define TRACE(msg, ...) TRACEx(DEBUG, "trace", msg, ## __VA_ARGS__)
#define INFO(msg, ...) TRACEx(INFO, "info", msg, ## __VA_ARGS__)
#define WARN(msg, ...) TRACEx(WARN, "warning", msg, ## __VA_ARGS__)
#define ERROR(msg, ...) TRACEx(ERROR, "error", msg, ## __VA_ARGS__)
#define INFO(msg, ...) TRACEx("info", msg, ## __VA_ARGS__)
#define WARN(msg, ...) TRACEx("warning", msg, ## __VA_ARGS__)
#define ERROR(msg, ...) TRACEx("error", msg, ## __VA_ARGS__)
#define FATAL(msg, ...) (TRACEx(FATAL, "FATAL", msg, ## __VA_ARGS__), abort())
#define FATAL(msg, ...) (TRACEx("FATAL", msg, ## __VA_ARGS__), abort())
#define TODO(x, ...) FATAL("function %s() is unimplemented: " x, __func__, ## __VA_ARGS__)
#define TODO(x) FATAL("function %s() is unimplemented: " x, __func__)
// Debug testing
@ -272,9 +192,4 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");
#define VERSION_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu)
_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u, "invalid native version spec");
// Misc.
#define PTR_ASSIGN(ptr, val) ( (ptr) ? (*(ptr) = (val), (ptr)) : (ptr) )
#define PTR_ASSIGNv(ptr, val) ({ let _ptr = (ptr); let _val = (val); PTR_ASSIGN(_ptr, _val); })
#endif /* _MACROS_H */

@ -1,75 +0,0 @@
//! Memory mapping abstraction
#ifndef _MAP_H
#define _MAP_H
#include <stdbool.h>
#include "ints.h"
typedef enum mm_err {
MAP_ERR_UNKNOWN = 0,
MAP_SUCCESS = 1,
MAP_ERR_MMAP,
MAP_ERR_UNMAP,
MAP_ERR_OPEN,
MAP_ERR_CLOSE,
MAP_ERR_STAT,
MAP_ERR_ADVISE,
} map_result_t;
typedef union memory_map {
// Common
struct {
union {
void* origin;
u8* area;
};
usize len;
};
// Specific to anon / file
struct mm_file {
void* origin;
usize len;
off_t fd_offset;
int fd;
} file;
struct mm_anon {
void* origin;
usize len;
} anon;
} map_t;
map_result_t map_fd(int fd, bool write, usize size, off_t offset, map_t *pOUT map);
/// Specify size as 0 to use the file's full size for the map.
map_result_t map_file(const char* file, bool write, usize size, off_t offset, map_t *pOUT map);
map_result_t map_anon(void* ptr, usize len, map_t *pOUT map);
map_result_t map_free(map_t map);
map_result_t map_preload(map_t *pINOUT map, bool random);
const char* map_error_str(map_result_t res);
_mixin int _map_handle_err(const char* stmt, const char* file, int line, const char* func, map_result_t res)
{
if(res == MAP_SUCCESS) return 1;
else COLD_BLOCK {
const char* err = map_error_str(res);
ERROR("mapping failed at %s:%d->%s(): on stmt `%s`", file, line, func, stmt);
switch(res) {
// Non-errno errors
case MAP_ERR_UNKNOWN:
case MAP_SUCCESS:
fprintf(stderr, err);
if(0)
// errno errors
default:
perror(err);
return 0;
}
}
UNREACHABLE();
}
#define map_handle_err(stmt) _map_handle_err(#stmt, __FILE__, __LINE__, __func__, stmt)
#endif /* _MAP_H */

@ -2,9 +2,6 @@
#ifndef _PROJECT_H
#define _PROJECT_H
#include <time.h>
#include <limits.h>
#include "macros.h"
#include "ints.h"
#include "version.h"
@ -25,29 +22,6 @@
/// Project license
#define PROG_LICENSE _PROJECT_LICENSE
/// Compiled time (UTC unix timestamp)
#define PROG_COMPILED_TIMESTAMP AS(_PROJECT_COMPILED, time_t)
// Returns from the program
//NOTE: -- Reworked `PROG_RET_` to have mapping and internal failures be negative, and the first haystack number to fail returned verbatim (haystack numbers start at 1). So, after consolidating the threadpool's results, we iterate over haystack numbers, find the first non-match, and exit process with its number. Otherwise, we return 0.
// Invalid arguments to program (print usage to stderr)
#define PROG_RET_ARGS -2
// Mapping of argv[1] failed
#define PROG_RET_MAP_NEEDLE_FAILED -3
// `h` is the number in argv[] of the haystack file
#define PROG_RET_MAP_HAYSTACK_N_FAILED(h) -((h) << 2)
// `h` is the number in argv[] of the haystack file
#define PROG_RET_UNMAP_HAYSTACK_N_FAILED(h) -(((h) << 2) | 1)
// Match of haystack number `h` failed.
#define PROG_RET_MATCH_HAYSTACK_N_FAILED(h) AS(h, int)
// Bitwise OR this with the number(s) of the haystack that match failed. It must not exceed INT_MAX-1
// XXX: DEPRECATED
#define PROG_RET_MAP_HAYSTACK_FAILED AS((AS(INT_MAX, u64) >> 1lu) ^ AS(INT_MAX, u64), int)
// Unmapping of argv[1] failed
#define PROG_RET_UNMAP_NEEDLE_FAILED -4
// Internal error
#define PROG_RET_INTERNAL -1
#define PROG_COMPILED_TIMESTAMP AS(_PROJECT_COMPILED, u64)
#endif /* _PROJECT_H */

@ -1,53 +0,0 @@
//! Generic pointer slice
#ifndef _SLICE_H
#define _SLICE_H
#include <limits.h>
#include "ints.h"
#include "macros.h"
typedef struct _slice {
union {
void* ptr;
u8* bytes;
};
usize len;
} slice_t;
#define SLICE_MAX ((struct _slice){ .ptr = AS(UINTPTR_MAX, void*), .len = SIZE_MAX })
#define SLICE(origin, length) ((struct _slice){ .ptr = ((void*)(origin)), .len = ((usize)(length)) })
_mixin usize slice_end(const slice_t*pIN slice)
{
return AS(slice->bytes + slice->len, usize);
}
_mixin usize slice_start(const slice_t*pIN slice)
{
return AS(slice->bytes, usize);
}
_mixin struct { usize start, end; } slice_sted(const slice_t*pIN slice)
{
return (var(slice_sted(NULL))){
.start = slice_start(slice),
.end = slice_end(slice),
};
}
// Format string for slices
#define SLICE_FORMAT "%lu..%lu"
// Expects a pointer to a `slice_t`
#define SLICE_FORMAT_ARGS(slice) slice_start(slice), slice_end(slice)
#define SLICE_STR_MAX_SIZE 42lu
usize slice_strn(const slice_t*pIN slice, usize len, char str[static pOUT len]);
usize slice_str_sz(const slice_t*pIN slice);
// Pointer returned from this function is TLS static and will change on each call.
const char* slice_strs(const slice_t*pIN slice);
// Pointer returned from this function must be free'd
char* slice_str(const slice_t*pIN slice);
#endif /* _SLICE_H */

@ -1,37 +0,0 @@
//! Trace messages
#ifndef _TRACE_H
#define _TRACE_H
#include <stdio.h>
#include <stdarg.h>
enum trace_level {
TRACE_LEVEL_DEBUG = 0,
TRACE_LEVEL_INFO,
TRACE_LEVEL_WARN,
TRACE_LEVEL_ERROR,
TRACE_LEVEL_FATAL,
_TRACE_LEVEL_NUM,
};
#ifdef DEBUG
#define _TRACE_LEVEL_DEFAULT TRACE_LEVEL_DEBUG
#else
#define _TRACE_LEVEL_DEFAULT TRACE_LEVEL_WARN
#endif
int _t_fprintf(enum trace_level l, FILE* output, const char* msg, ...);
enum trace_level trace_max_level();
void trace_init_with(enum trace_level max_level);
// Initialise the tracer with env-var `_TRACE_CONTROL_ENV_NAME`. If the env-var is not found, the max trace level is not modified. If it is found but has an invalid value, this function returns 0. Otherwise it modifies the max trace level value and returns 1.
int trace_init();
const char* trace_name_of(enum trace_level lv);
extern const char* const _TRACE_CONTROL_ENV_NAME;
extern const char* const _trace_level_descriptions[_TRACE_LEVEL_NUM];
#endif /* _TRACE_H */

@ -1,89 +0,0 @@
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <macros.h>
#include <ints.h>
#include <map.h>
#include <comp.h>
bool cmp_find(const map_t *pIN needle, const map_t *pIN haystack, usize *pOUT pos)
{
u8* start;
//TODO: Find sub-substring even if the full match fails
u8* substr = memmem(start = haystack->origin, haystack->len,
needle->origin, needle->len);
if(!substr) return false;
debug_assert(substr >= start);
*pos = (usize) (substr - start);
return true;
}
int cmp_find_many(const map_t *pIN needle, usize nhaystacks, usize sizes[pOUT nhaystacks], ...)
{
va_list v_haystacks;
va_start(v_haystacks, sizes);
const map_t* pINOUT haystack;
register int rval=-1;
for(usize i=0;i<nhaystacks;i++)
{
usize *pOUT size = sizes + i;
haystack = va_arg(v_haystacks, const map_t* pIN);
if(!haystack || !haystack->origin) FATAL("haystack %lu was null or its origin was null", i);
if(!cmp_find(needle, haystack, size)) {
rval = (int)i;
break;
}
}
va_end(v_haystacks);
return rval;
}
comp_multires_t* comp_match_all(map_t *pIN needle, usize nh, map_t hs[pIN nh], bool abort_on_fail)
{
comp_multires_t* result = malloc(sizeof(comp_multires_t) + (sizeof(struct comp_single_result) * nh));
debug_assert(result);
result->all_matched = true;
result->idx_first_failure = -1;
result->num_results = nh;
usize i;
for(i =0;i<nh;i++)
{
usize start=0;
bool matched = cmp_find(needle, hs+i, &start);
struct comp_single_result res = {
.mapping = hs+i,
.matched = matched,
.slice_start = start,
};
TRACE("Result for %lu (%p): Matched: %s, slice start: %lu", i, hs+i, matched ? "yes" : "no", start);
result->aggr[i] = res;
if(!res.matched) {
WARN("Matching haystack %lu failed", i);
result->all_matched = false;
if(result->idx_first_failure < 0) {
INFO("First match failure is %lu", i);
result->idx_first_failure = (int)i;
}
if(abort_on_fail) {
result->num_results = i;
break;
}
}
}
INFO("Aggregate result for %lu / %lu haystack matches: Full passed: %s, first filaure: %d",
i, nh,
result->all_matched ? "yes" : "no",
result->idx_first_failure);
TRACE("Output result aggregate size: %lu (alloc: %p)", sizeof(comp_multires_t) + (sizeof(struct comp_single_result) * nh), result);
return result;
}

@ -1,179 +0,0 @@
//! Displaying match data
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <macros.h>
#include <comp.h>
#include <display.h>
#define FLAGSET(f, flag) (( (f) & (flag) ) == (flag))
#define DFLAGSET(f, flagname) FLAGSET(f, DISPF_SHOW_ ## flagname)
#define CMP(x,cmp,y) ( (x) cmp (y) ? (x) : (y) )
#define CMPv(x,cmp,y) ({ let _x = (x); let _y = (y); CMP(_x, cmp, _y); })
#define MIN(x, y) CMP((x), <, (y))
#define MINv(x, y) CMPv((x), <, (y))
#define MAX(x, y) CMP((x), >, (y))
#define MAXv(x, y) CMPv((x), >, (y))
// Returns number of bytes written to `dest`. Otherwise same as `strncpy()`.
static usize strncpy_n(char*pOUT dest, const char* restrict src, usize n)
{
usize i;
for(i=0;i<n;i++)
if(! (*dest++ = *src++)) return i;
dest[i] = 0;
return i;
}
// Will not write past `dn` bytes of `dest`, will not read past `sn` bytes of `src`. Otherwise, same as `strncpy_n()`.
static usize s_strncpy_n(usize sn, usize dn; char dest[static pOUT dn], usize dn, const char src[static restrict sn], usize sn)
{
usize i;
usize n = MIN(sn,dn);
for(i=0;i<n;i++)
if(! (*dest++ = *src++)) return i;
if(i<dn) dest[i] = 0;
return i;
}
// Will not write past `dn` bytes of `dest`, will not read past `sn` bytes of `src`. Otherwise, same as `strncat_n()`.
static inline usize s_strncat_n(usize sn, usize dn; char dest[static pOUT dn], usize dn, const char src[static restrict sn], usize sn)
{
usize dl = MINv(strlen(dest), dn);
return s_strncpy_n(dest + dl, dn - dl, src, sn);
}
// Returns number of bytes written to (dest+strlen(dest)). Otherwise same as `strncat()`.
static inline usize strncat_n(char*pOUT dest, const char* restrict src, usize n)
{
usize dl = strlen(dest);
return strncpy_n(dest+dl, src, n);
}
//TODO: This isn't working at all...
#ifdef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
_dead
#endif
static usize _display_get_fmt_n(dispflags_t flags, bool matched, usize _n, char str[pOUT _n])
{
register usize n = _n, fw=0;
const char* fmts[] = {
DFLAGSET(flags, FAILURES)
? "failure: "
: "",
"match: ",
"%lu: ",
SLICE_FORMAT,
" (len %lu)",
(const char*)NULL,
};
usize w = s_strncpy_n(str, n, fmts[!!matched], n); // SAFETY: Will never read past `fmts[x]` nul-terminator, so setting n as both dest and src length here is safe.
//TODO: Fix this... It isn't working...
#define ADDSTR(s) do { \
debug_assert((s)); \
w = MINv(s_strncat_n(str, n, (s), n), n); \
/* Remove written from `n` */ \
n -= w; \
/* Check if we have written into the limit (if so, w will be equal to n above, since w is high-bounded to n by `MINv()` above. So if n is 0, we have hit the limit. */ \
ifU(!n) { \
/*XXX: Should this be `str[w-1]?` If this assert fails then yes it should. */ \
debug_assert(w < _n); \
str[w] = 0; \
return fw + w; \
} \
/* Increase `str` pointer by written */ \
str += w; \
/* Increase return value by written. */ \
fw += w; \
} while(0)
if(DFLAGSET(flags, NUMBER)) ADDSTR(fmts[2]);
if(DFLAGSET(flags, SLICE)) ADDSTR(fmts[3]);
if(DFLAGSET(flags, LENGTH)) ADDSTR(fmts[4]);
ADDSTR("\n");
TRACE("Output string written: %lu, (rlen %lu)", fw, n);
#undef ADDSTR
return fw;
}
//XXX: This works, but seems inherantly unsafe. Rewrite to use the above `strn*_n()` functions for better length tracking.
#ifndef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
_dead deprecated_message("this is unsafe, use `_display_get_fmt_n`")
#endif
static usize _display_get_fmt0(dispflags_t flags, bool matched, usize _len, char str[pOUT _len])
{
register usize len = _len;
static const char* fmts[] = { "match: ", "%lu: ", SLICE_FORMAT, " (len %lu)", "failure: ", (const char*)NULL };
#define ADDSTR(s) do { debug_assert((s)); strncat(str, (s), len); w = strlen((s)); w = w > len ? len : w; ifU(len<=w) { str[len-1] = 0; return w; } len -= w; str += w; fw+=w; } while(0)
usize fw;
usize w = strlen(strncpy(str, matched ? fmts[0]
: DFLAGSET(flags, FAILURES) ? fmts[4]
: "", len)); if(len<=w || w == 0) { str[len-1] = 0; return 0; } len -= w; str += w; fw = w;
if(DFLAGSET(flags, NUMBER)) ADDSTR(fmts[1]);
if(DFLAGSET(flags, SLICE)) ADDSTR(fmts[2]);
if(DFLAGSET(flags, LENGTH)) ADDSTR(fmts[3]);
ADDSTR("\n");
TRACE("Output string written: %lu, (rlen %lu)", fw, len);
return fw;
#undef ADDSTR
}
#ifdef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
#define _display_get_fmt(...) _display_get_fmt0(__VA_ARGS__)
#else
#define _display_get_fmt(...) _display_get_fmt_n(__VA_ARGS__)
#endif
static void _display_normal(FILE* output, dispin_t*pIN input, dispflags_t flags)
{
bool matched = input->matched;
slice_t cslice = input->cslice;
const void* base = input->base;
usize i = input->index;
#ifdef _DEBUG_MANCALC_OFFSETS
struct {
usize start, end;
} pos = {
.start = AS(cslice.ptr - base, usize),
};
pos.end = pos.start + cslice.len;
#else
let pos = slice_sted((slice_t[]){ SLICE(AS(cslice.ptr - base, usize), cslice.len)});
#endif
INFO("Mapped region slice for %lu, pre-transform (at %p): " SLICE_FORMAT ", len: %lu", i+1, base, SLICE_FORMAT_ARGS(&cslice), cslice.len);
// --- get format string from `flags` ---
char fmt[200]; //TODO: Find the realistic max for this.
usize fmtw = _display_get_fmt(flags, matched, sizeof(fmt)-1, fmt);
assert(fmtw+1 < sizeof(fmt));
fmt[fmtw+1] = 0;
// ---
TRACE("Format string get: `%s' (%lu)", fmt, fmtw);
fprintf(output, fmt, i+1, pos.start, pos.end, pos.end-pos.start);
}
void display_result(FILE* output, dispin_t input, dispkind_t how, dispflags_t flags)
{
flags = flags ?: DISPLAY_FLAGS_DEFAULT;
output = output ?: stdout;
TRACE("Outputting as %d to %p with flags 0x%08lx", (int)how, output, (usize)flags);
switch(how)
{
case DISPK_NORMAL:
_display_normal(output, &input, flags);
break;
case DISPK_CSV:
case DISPK_BINARY:
case DISPK_INI:
TODO("display kind %d is unimplemented", (int)how);
default: COLD_EXPR(FATAL("unknown display kind: %d", (int)how));
}
}

@ -3,7 +3,6 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <ints.h>
#include <macros.h>
@ -13,73 +12,17 @@
#include <tests.h>
#include <slice.h>
#include <map.h>
#include <comp.h>
#include <display.h>
static const char* _compdate_readable()
{
static const time_t comp = PROG_COMPILED_TIMESTAMP;
#define DATE_MAX 20
static _Thread_local char date[DATE_MAX] = {0};
debug_assert(sizeof(date) == DATE_MAX);
struct tm* time = gmtime(&comp);
if(!date[0]) {
usize w = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", time);
DBG(ifU(!w || w>=DATE_MAX) FATAL("Increase DATE_MAX, %lu is too low (strftime(): %lu)", DATE_MAX, w));
TRACE("size of hr time: %lu / %lu", w, sizeof(date));
}
return date;
#undef DATE_MAX
}
static void pi_print_trace_infos(FILE* out)
{
const char* name;
enum trace_level cmax = trace_max_level();
for(int i=0;i<(int)_TRACE_LEVEL_NUM;i++)
{
enum trace_level lv = (enum trace_level)i;
name = trace_name_of(lv);
if(name) {
debug_assert(AS(lv, int) < AS(_TRACE_LEVEL_NUM, int) && AS(lv, int) >= 0);
const char* desc = lv[_trace_level_descriptions];
assert(desc);
fprintf(out, "-> level %d: \"%s\" - %s%s\n", i, name, desc, cmax==lv ? " (currently set)" : "");
}
}
enum trace_level def = _TRACE_LEVEL_DEFAULT;
name = trace_name_of(def);
debug_assert(name);
fprintf(out, "(default level: %d, \"%s\")\n", def, name);
fprintf(out, "Set `LOG_LEVEL=<name string>` to control logging verbosity level at runtime. The name string is case-sensitive.\n");
}
static void prog_info(FILE* out)
void prog_info(FILE* out)
{
fprintf(out, PROG_NAME " v%s - " PROG_DESCRIPTION
#ifdef DEBUG
" (debug build)"
#endif
"\n written by %s with <3 (compiled at %s UTC."
#ifdef DEBUG
" %lu uts)"
#else
")"
#endif
"\n license %s.\n",
"\n written by %s with <3 (compiled at %lu UTC (unix ts))\n license %s.\n",
v_ctoss(v_rawtoc(PROG_VERSION)),
PROG_AUTHOUR,
_compdate_readable(),
#ifdef DEBUG
PROG_COMPILED_TIMESTAMP,
#endif
PROG_LICENSE);
fprintf(out, "\nLogging levels: (set with env-var `%s')\n", _TRACE_CONTROL_ENV_NAME);
pi_print_trace_infos(out);
}
void usage(FILE* out, int argc, char** argv)
@ -88,116 +31,24 @@ void usage(FILE* out, int argc, char** argv)
prog_info(out);
fprintf(out, "\nUsage: %s <needle> <haystack(s)...>\n", argv[0] ?: PROG_NAME);
fprintf(out, "Usage: %s --help\n", argv[0] ?: PROG_NAME);
}
// err: 0 for normal exit and print to stdout.
// err: nonzero for abnormal exit and print to stderr.
//
// noinline: We don't want this hoisted.
noinline noreturn void usage_then_exit(int err, int argc, char** argv)
noreturn void usage_then_exit(int err, int argc, char** argv)
{
usage( err ? stderr : stdout, argc, argv);
exit(err);
}
static int map_haystacks(const char* const * h, map_t maps[pOUT])
{
const char* path;
usize d =0;
while( (path = *h++) )
{
map_t *pOUT c = maps++;
INFO("Mapping haystack file `%s' (to map addr %p)", path, c);
debug_assert(c);
if(!map_handle_err(map_file(path, false, 0, 0, c))) {
//Handle unmapping previous haystacks before return
// Prep reset maps to before `c` for unwind
maps-=1;
ERROR("Failed to map file `%s', rolling back %lu maps. (erp: %p, prev: %p)", path, d, c, maps);
while( d --> 0 )
ifU(!map_handle_err(map_free(* (--maps)))) WARN("Failed to free map at %p (in %lu unwind)", maps, d);
return 0;
}
d+=1;
}
return 1;
}
int main(int argc, char** argv)
{
if(!trace_init()) {
ERROR("Unknown trace value for %s: `%s'", _TRACE_CONTROL_ENV_NAME, getenv(_TRACE_CONTROL_ENV_NAME) ?: "<null>");
}
TRACE("main start with %d (pn: %s, a1: %s), log trace level (ctrl envar `%s'): %d", argc, argv[0], argv[1] ?: "<null>", _TRACE_CONTROL_ENV_NAME, (int)trace_max_level());
TRACE("main start");
if(!argv[1])
inv_args:
usage_then_exit(PROG_RET_ARGS, argc, argv);
else if(strcmp(argv[1], "--help")==0)
usage_then_exit(0, argc, argv);
else if(!argv[2])
goto inv_args;
usage_then_exit(0, argc, argv);
int m_rval =0;
map_t needle;
INFO("Mapping needle file `%s'", argv[1]);
if(!map_handle_err(map_file(argv[1], false, 0, 0, &needle))) return PROG_RET_MAP_NEEDLE_FAILED;
if(!map_handle_err(map_preload(&needle, false))) WARN("Failed to advise kernel about memory access: needle");
// Remove exe name from argv: now is (needle, haystacks...)
argv+=1;
// Setup haystack maps
usize hsn = AS(argc-2, usize);
map_t haystacks[hsn];
TRACE("Attempting to map %lu haystacks", hsn);
if(!map_haystacks((const char**)(argv+1), haystacks)) {
// Unmap needle before return.
ifU(!map_handle_err(map_free(needle))) WARN("Failed to unmap needle before exiting after failed haystack maps");
return PROG_RET_MAP_HAYSTACK_FAILED;
}
#ifdef _FEATURE_PARALLEL // Multi-threaded
TODO("Multithreaded processing currently unimplemented.");
//TODO: Setup thread-pool.
//TODO: Dispatch haystack maps to threadpool.
//TODO: Use either `cmp_find()` or `cmp_find_many()` to find the `needle` (mapped above) within those haystacks.
//TODO: Within the threadpool: output information regarding each match/nonmatch.
//TODO: Join the threadpool and consolidate results.
//TODO: Iterate through the haystack numbers match results, return the first non-match haystack number through `PROG_RET_MATCH_HAYSTACK_N_FAILED(n)` (haystack numbers are always nonzero). If all were matched, return 0
#else // Single-threaded
INFO("Computing result for %lu haystacks...", hsn);
comp_multires_t* full_result = comp_match_all(&needle, hsn, haystacks, false);
// Number of results actually computed by `comp_match_all()` may differ from `hsn` if `abort_on_fail` is true (currently its hardcoded to false but that may change.)
usize cres = full_result->num_results;
INFO("Computed result: Suc. Matched %lu / %lu hs. Full match: %s (first failure: %d)", cres, hsn, full_result->all_matched ? "yes" : "no", full_result->idx_first_failure);
//TODO: Print out the aggregate (full_result) results (cres) in a reasonable format.
struct comp_single_result* current = full_result->aggr;
for(usize i=0;i<cres;i++)
{
slice_t cslice;
const void* base = current->mapping->origin;
bool matched = current->matched;
if(!comp_slice_of(&needle, current++, &cslice)) {
printf("fail: %lu\n", i+1);
} else {
TRACE("Displaying result %lu (" SLICE_FORMAT ") (base %p)", i, SLICE_FORMAT_ARGS(&cslice), base);
display_result(stdout, (dispin_t){ .base = base, .cslice = cslice, .index = i, .matched = matched }, DISPK_NORMAL, 0);
}
}
free(full_result);
#endif
TRACE("main end");
for(int i=0;i<(int)hsn;i++) {
INFO("Unmapping haystack haystack file `%s' (#%d) (map addr %p)", argv[i+1], i+1, haystacks+i);
ifU(!map_handle_err(map_free(haystacks[i]))) m_rval = !m_rval ? PROG_RET_UNMAP_HAYSTACK_N_FAILED(i+1) : m_rval;
}
TRACE("Unmap haystacks ended with main rval: %d", m_rval);
INFO("Unmapping needle file `%s'", *argv);
ifU(!map_handle_err(map_free(needle))) m_rval = !m_rval ? PROG_RET_UNMAP_NEEDLE_FAILED : m_rval;
TRACE("main end: rval %d", m_rval);
return m_rval;
return 0;
}

@ -1,114 +0,0 @@
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <macros.h>
#include <map.h>
// Set all bytes to 0xff (ints i.e. fd will be -1)
static inline void _map_init(map_t *pOUT map)
{
memset(map, 0xff, sizeof(map_t));
TRACE("blanked %p->%lu to 0xff", map, sizeof(map_t));
}
static inline bool _map_is_file(const map_t* map)
{
TRACE("fd element of union is %x (%d)", map->file.fd, map->file.fd);
return map->file.fd > 0;
}
map_result_t map_fd(int fd, bool write, usize size, off_t offset, map_t *pOUT map)
{
TRACE("mapping %lu bytes of fd %d (%s) from offset %lu to %p", size, fd, write ? "rw" : "ro", offset, map);
void* ptr = mmap(NULL, size, PROT_READ | (write ? PROT_WRITE : 0), MAP_SHARED, fd, offset);
if(ptr == MAP_FAILED) return MAP_ERR_MMAP;
TRACE("mapped %p->%lu", ptr, size);
map->file = (struct mm_file){
.origin = ptr,
.len = size,
.fd = fd,
.fd_offset = offset,
};
return MAP_SUCCESS;
}
map_result_t map_file(const char* file, bool write, usize size, off_t offset, map_t *pOUT map)
{
TRACE("mapping %lu bytes of file `%s' (%s) from %lu to %p", size, file, write ? "rw" : "ro", offset, map);
int fd = open(file, write ? O_RDWR : O_RDONLY);
if(fd < 0) return MAP_ERR_OPEN;
TRACE("opened file %d", fd);
if(!size) {
TRACE("user asked for max-size map (size: 0), stating %d to find size...", fd);
struct stat st;
// Find size of file
if(fstat(fd, &st) != 0) return (close(fd), MAP_ERR_STAT);
TRACE("fstat() reports size is %lu", st.st_size);
size = st.st_size;
}
TRACE("passing fd %d, size %lu to `map_fd()`", fd, size);
return map_fd(fd, write, size, offset, map);
}
map_result_t map_anon(void* ptr, usize len, map_t *pOUT map)
{
TRACE("mapping anon at %p (%s) of size %lu (map %p)", ptr, ptr ? "fixed" : "dynamic", len, map);
ptr = mmap(ptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | (ptr ? MAP_FIXED : 0), -1, 0);
if(ptr==MAP_FAILED) return MAP_ERR_MMAP;
TRACE("mapped ptr %p->%lu", ptr, len);
// Make sure .file.fd is -1, so we can tell this is anon in `map_free()`.
_map_init(map);
map->anon = (struct mm_anon) {
.origin = ptr,
.len = len,
};
return MAP_SUCCESS;
}
map_result_t map_free(map_t map)
{
TRACE("unmapping %p->%lu", map.origin, map.len);
if( munmap(map.anon.origin, map.anon.len) != 0) return MAP_ERR_UNMAP;
if( _map_is_file(&map) ) {
TRACE("map is file, closing fd %d", map.file.fd);
if( close(map.file.fd) != 0 ) return MAP_ERR_CLOSE;
}
return MAP_SUCCESS;
}
map_result_t map_preload(map_t *pINOUT map, bool random)
{
TRACE("preloading map %p (%p->%lu) for immediate %s access", map, map->origin, map->len, random ? "random" : "sequential");
return madvise(map->origin, map->len, MADV_WILLNEED | (random ? MADV_RANDOM : MADV_SEQUENTIAL)) != 0
? MAP_ERR_ADVISE
: MAP_SUCCESS;
}
const char* map_error_str(map_result_t res)
{
switch(res)
{
#define CASE(err, str) case err: return str
CASE(MAP_ERR_MMAP, "mmap() failed (creating a map)");
CASE(MAP_ERR_UNMAP, "munmap() failed (freeing a map)");
CASE(MAP_ERR_OPEN, "open() failed (mapping a filepath)");
CASE(MAP_ERR_CLOSE, "close() failed (freeing a file-map)");
CASE(MAP_ERR_STAT, "fstat() failed: (mapping a full filepath)");
CASE(MAP_ERR_ADVISE, "madvise() failed: (preloading mapped file)");
CASE(MAP_SUCCESS, "success");
#undef CASE
default:
ERROR("unkown or invalid error code %d", (int)res);
return "unknown error";
}
}

@ -1,37 +0,0 @@
#include <stdio.h>
#include <slice.h>
usize slice_strn(const slice_t*pIN slice, usize len, char str[static pOUT len])
{
return snprintf(str, len, SLICE_FORMAT, SLICE_FORMAT_ARGS(slice));
}
usize slice_str_sz(const slice_t*pIN slice)
{
return snprintf(NULL, 0, SLICE_FORMAT, SLICE_FORMAT_ARGS(slice));
}
usize _slice_str_max_sz()
{
const slice_t max = SLICE_MAX;
return snprintf(NULL, 0, SLICE_FORMAT, SLICE_FORMAT_ARGS(&max));
}
const char* slice_strs(const slice_t*pIN slice)
{
static _Thread_local char buf[SLICE_STR_MAX_SIZE+1];
debug_assert(sizeof(buf) == SLICE_STR_MAX_SIZE+1);
buf[snprintf(buf, sizeof(buf), SLICE_FORMAT, SLICE_FORMAT_ARGS(slice))] = 0;
return buf;
}
char* slice_str(const slice_t*pIN slice)
{
usize ln = slice_str_sz(slice)+1;
char *buf = malloc(ln);
usize _out = slice_strn(slice, ln, buf);
debug_assert(_out == ln);
IGNORE(_out);
return buf;
}

@ -1,19 +0,0 @@
#include <string.h>
#include <tests.h>
#include <map.h>
DEFTEST(map_anon)
{
map_t map;
TEST_ASSERT(map_anon(NULL, 1024, &map) == MAP_SUCCESS);
memset(map.origin, 0xab, map.len);
TEST_ASSERT( ((u8*)map.origin)[10] == 0xab );
TEST_ASSERT(map_free(map) == MAP_SUCCESS);
return TEST_OK;
}
RUNTEST_DEBUG(map_anon)

@ -1,19 +0,0 @@
#include <macros.h>
#include <tests.h>
#include <slice.h>
// Internal `slice.c` function prototyped.
usize _slice_str_max_sz();
DEFTEST(slice_static_str_len)
{
const usize func = _slice_str_max_sz();
INFO("Function reports: %lu", func);
INFO("Constant is: %lu", SLICE_STR_MAX_SIZE);
TEST_ASSERT( (func == SLICE_STR_MAX_SIZE) );
return TEST_OK;
}
RUNTEST_DEBUG(slice_static_str_len)

@ -1,23 +0,0 @@
#include <string.h>
#include <ints.h>
#include <macros.h>
#include <tests.h>
DEFTEST(transmute_copy)
{
static const u64 EXPECTED = 18446743474098365025lu;
struct { char i[4]; i32 u; } input = { .i = {'a','b','c','d'}, .u = -140 };
let output = TRANSMUTE(input, u64);
TRACE("Transmute test, expected 0x%lx, got 0x%lx", EXPECTED, output);
//XXX: TODO: This test will fail on Big Endian machines.
TEST_ASSERT(output == EXPECTED);
TRACE("Alignment of output: %lu, alignment of input: %lu", _Alignof(output), _Alignof(input));
let input2 = TRANSMUTE(output, var(input));
TRACE("Transmute output passed, trying to transmute back");
TEST_ASSERT( memcmp(&input2, &input, sizeof(input)) == 0 );
return TEST_OK;
}
RUNTEST_DEBUG(transmute_copy);

@ -1,119 +0,0 @@
// Trace messages
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <trace.h>
#include <macros.h>
#include <ints.h>
static _Atomic enum trace_level _t_level = _TRACE_LEVEL_DEFAULT;
const char* const _TRACE_CONTROL_ENV_NAME = "LOG_LEVEL";
const struct {const char* const name; const enum trace_level level; } _trace_level_names[] = {
#define X(name) { #name, TRACE_LEVEL_ ## name }
#define Y(name, val) { #name, TRACE_LEVEL_ ## val }
X(DEBUG), Y(TRACE, DEBUG),
X(INFO),
X(WARN),
X(ERROR),
X(FATAL),
#undef Y
#undef X
};
#define _trace_level_names_n (sizeof(_trace_level_names) / sizeof(_trace_level_names[0]))
const char* const _trace_level_descriptions[_TRACE_LEVEL_NUM] = {
"All trace messages regarding internals, only useful for debugging",
"Information messages regarding processes, recommended level",
"Only show warnings",
"Only show errors",
"Only show fatal (aborting) errors",
};
static bool _trace_lookup(const char*pIN name, enum trace_level *pOUT olevel)
{
debug_assert(name);
for(usize i=0;i<_trace_level_names_n;i++) {
let level = &_trace_level_names[i];
debug_assert(level && level->name);
if(strcmp(name, level->name) == 0) {
*olevel =level->level;
return true;
}
}
return false;
}
static bool _trace_reverse_lookup(enum trace_level level, const char* *pOUT name)
{
for(usize i=0;i<_trace_level_names_n;i++) {
let lv = &_trace_level_names[i];
debug_assert(lv && lv->name);
if(lv->level == level) {
*name = lv->name;
return true;
}
}
return false;
}
const char* trace_name_of(enum trace_level lv)
{
const char* ret;
if(!_trace_reverse_lookup(lv, &ret)) return NULL;
else return ret;
}
inline enum trace_level trace_max_level()
{
return _t_level;
}
inline static bool _trace_hidden(enum trace_level l)
{
return l < trace_max_level();
}
int _t_fprintf(enum trace_level l, FILE* output, const char* msg, ...)
{
if(_trace_hidden(l)) return 0;
va_list ap;
va_start(ap, msg);
register int r = vfprintf(output, msg, ap);
va_end(ap);
return r;
}
void trace_init_with(enum trace_level max_level)
{
_t_level = max_level;
}
static inline bool _try_get_env(const char* name, const char* *pOUT value)
{
return !!(*value = (const char*)getenv(name));
}
int trace_init()
{
const char* eval;
if(!_try_get_env(_TRACE_CONTROL_ENV_NAME, &eval)) return 1;
else {
assert(eval);
if(*eval == 0) return 1; // Count empty string as default.
enum trace_level level;
if(!_trace_lookup(eval, &level)) return 0;
else {
trace_init_with(level);
}
}
return 1;
}
Loading…
Cancel
Save