Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Avril | 02c055f2c8 | 2 years ago |
Avril | 2d5840da8d | 2 years ago |
Avril | 05a7b0f460 | 2 years ago |
Avril | 2d040d5584 | 2 years ago |
Avril | f12c1fffdd | 2 years ago |
Avril | ac7ec87484 | 2 years ago |
Avril | 4589eaa292 | 2 years ago |
Avril | 090474ff17 | 2 years ago |
Avril | fc81dd4b74 | 2 years ago |
Avril | 9d71fa7aee | 2 years ago |
Avril | f2d6ee51fb | 2 years ago |
Avril | c64f3f7499 | 2 years ago |
Avril | 5d1daa98e4 | 2 years ago |
Avril | c03f2e39d3 | 2 years ago |
Avril | b38c91a8ee | 2 years ago |
@ -0,0 +1,52 @@
|
||||
# Argument passing format
|
||||
|
||||
Argument parsing can be done with short `-<chars...>` and/or long `--arg[{=, }<value>]` (with or without value, `=` optional.
|
||||
Short arguments can be chained together: `-abc` is equal to `-a -b -c`. There can be multiple of these: `-ie -O` is equal to `-i -e -O`.
|
||||
|
||||
# **TODO** Long arguments
|
||||
|
||||
* **TODO** User-set sink file path (e.g `--path /path/to/sink`) which overrides the default `/dev/null`.
|
||||
* `--env <file>` - Read environment variables, line-by-line, from `<file>` in the format `KEY=value` and send that as the program's `envp`. Multiple of these can exist, they are concatenated.
|
||||
(example usage: `LD_PRELOAD=malicious.so sink --env <(env | grep ^LD_PRELOAD) setuid-binary`)
|
||||
* `--env=[<key>=<value>]` - Add (or replace) `<key>=<value>` to/in `envp`. Multiple of these can exist.
|
||||
(example usage: `sink --env=LD_PRELOAD=replace.so binary`)
|
||||
|
||||
# Short Arguments
|
||||
* `-i` Replace `stdin` (**default**)
|
||||
* `-o` Replace `stdout` (**default**)
|
||||
* `-e` Replace `stderr` (*default with feature* `REPLACE_STDERR`)
|
||||
* `-I` No replace `stdin`
|
||||
* `-O` No replace `stderr`
|
||||
* `-E` No replace `stderr` (**default** *without feature* `REPLACE_STDERR`)
|
||||
|
||||
* `-p` `envp` passthrough, including optional modifications. (see `--env`.) (**default** *without feature* `NO_ENV`)
|
||||
* `-P` No `envp` passthrough, **except** optional modifications. (see `--env`.) (*default with feature* `NO_ENV`)
|
||||
|
||||
# **TODO** Other options
|
||||
* **TODO** Specific fd passthrough (*probably long arg, could also possibly be short args for std{in,out,err} streams specifically*)
|
||||
* **TOTO** CLOEXEC control (*short arg*)
|
||||
* **TOTO** add specific fds / open specific files (optionally also at specific fds) to passthrough (*probably long arg*)
|
||||
|
||||
# Examples
|
||||
|
||||
Short args:
|
||||
```shell
|
||||
$ sink -ioe cat # sink steam in,out,err
|
||||
$ sink -iOE cat # sink stream in. keep stream out and err
|
||||
$ sink -O cat # sink default streams, but keep out
|
||||
$ sink -E cat # sink default streams and err
|
||||
```
|
||||
|
||||
**TODO** Long args:
|
||||
|
||||
|
||||
## Redundant examples
|
||||
|
||||
Short args:
|
||||
```shell
|
||||
$ sink -iI cat # sink default streams and in (I comes after the redundant i)
|
||||
$ sink -Ii cat # sink default streams except in (i comes after the redundant I)
|
||||
```
|
||||
|
||||
**TODO** Long args:
|
||||
|
@ -0,0 +1,430 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "view.h"
|
||||
|
||||
#include "args.h"
|
||||
|
||||
#include "comp_features.h"
|
||||
|
||||
#define _PASTE_(x, y) x ## y
|
||||
#define _PASTE(x, y) _PASTE_(x, y)
|
||||
|
||||
#define _box_name(nm) _PASTE(_box_, _PASTE(nm, __LINE__))
|
||||
#define $box(expr) ({ __typeof((expr))* _box_name(p) = aligned_alloc(_Alignof(__typeof((expr))), sizeof(__typeof((expr)))); \
|
||||
if(__builtin_expect(_box_name(p) != NULL, true)) *_box_name(p) = (expr); \
|
||||
_box_name(p); })
|
||||
|
||||
#define $box_t(T, expr) ({ T* _box_name(pT) = aligned_alloc(_Alignof(T), sizeof(T)); \
|
||||
if(__builtin_expect(_box_name(pT) != NULL, true)) *_box_name(pT) = (expr); \
|
||||
_box_name(pT); })
|
||||
|
||||
typedef __typeof( *((pargs_t*)0)->fd_pass ) fd_pass_t;
|
||||
typedef __typeof( ((pargs_t*)0)->replace ) replace_t;
|
||||
|
||||
struct input_arguments
|
||||
{
|
||||
int *restrict p_argc;
|
||||
char** *restrict p_argv;
|
||||
char** *restrict p_envp;
|
||||
};
|
||||
|
||||
typedef bool (single_arg_parse_func)(int, const char*, struct input_arguments* restrict, pargs_t*);
|
||||
typedef void (single_arg_error_func)(struct arg_parse_error* restrict);
|
||||
|
||||
enum argument_type {
|
||||
AT_SHORT = 0,
|
||||
AT_LONG,
|
||||
AT_LONG_EQ,
|
||||
|
||||
AT_LONG_OR_EQ = AT_LONG | AT_LONG_EQ,
|
||||
};
|
||||
|
||||
struct argument_parser {
|
||||
union {
|
||||
const char* l;
|
||||
char s;
|
||||
} name;
|
||||
|
||||
enum argument_type kind;
|
||||
|
||||
bool is_default;
|
||||
|
||||
const char* destription;
|
||||
const char* example;
|
||||
|
||||
single_arg_parse_func* parse;
|
||||
single_arg_error_func* error;
|
||||
};
|
||||
|
||||
typedef __typeof( ((struct argument_parser*)0)->name ) av_name_t;
|
||||
|
||||
static bool _si_handle_replace(int idx, const char* arg, struct input_arguments* restrict in, pargs_t* out)
|
||||
{
|
||||
char c;
|
||||
bool* restrict repl;
|
||||
switch(c = *arg) {
|
||||
case 'i':
|
||||
case 'I':
|
||||
repl = &out->replace.in;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
repl = &out->replace.out;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
repl = &out->replace.err;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
*repl = !(c & 0x20);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct argument_parser AV_ARGS[] = {
|
||||
{ { .s= 'i' }, AT_SHORT, true, "Sink stdin", NULL, _si_handle_replace, NULL },
|
||||
{ { .s='o' }, AT_SHORT, true, "Sink stdout", NULL, _si_handle_replace, NULL },
|
||||
{ { .s='e' }, AT_SHORT, false, "Sink stderr", NULL, _si_handle_replace, NULL },
|
||||
|
||||
{ { .s='I' }, AT_SHORT, false, "Keep stdin", NULL, _si_handle_replace, NULL },
|
||||
{ { .s='O' }, AT_SHORT, false, "Keep stdout", NULL, _si_handle_replace, NULL },
|
||||
{ { .s='E' }, AT_SHORT, true, "Keep stderr", NULL, _si_handle_replace, NULL },
|
||||
};
|
||||
#define AV_ARGS_SZ (sizeof(AV_ARGS) / sizeof(struct argument_parser))
|
||||
|
||||
static inline bool _av_long_extract_name(const char* name, enum argument_type ty, view_t* restrict out)
|
||||
{
|
||||
register bool ok = true;
|
||||
switch(ty) {
|
||||
case AT_LONG_EQ:
|
||||
ok = !!sv_split_cstr(name, '=', out, NULL);
|
||||
break;
|
||||
case AT_LONG_OR_EQ:
|
||||
if(!sv_split_cstr(name, '=', out, NULL)) {
|
||||
if(0)
|
||||
case AT_SHORT: name = (const char[2]){*name, 0};
|
||||
*out = sv_from_cstr(name);
|
||||
}
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static inline const struct argument_parser* _av_lookup(av_name_t name, enum argument_type ty)
|
||||
{
|
||||
for(size_t i=0;i<AV_ARGS_SZ;i++) {
|
||||
const struct argument_parser* current = AV_ARGS+i;
|
||||
if(__builtin_constant_p(ty) && ty == AT_SHORT) {
|
||||
if(current->name.s == name.s) return current;
|
||||
} else switch(ty) {
|
||||
case AT_SHORT: if(current->name.s == name.s) return current;
|
||||
default: {
|
||||
view_t real_name;
|
||||
if(!_av_long_extract_name(name.l, ty, &real_name)) continue; // Argument requires `=` but one wasn't fount, continue.
|
||||
if(sv_eq_cstr(real_name, current->name.l)) return current;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const pargs_t* parsed_args = NULL;
|
||||
static const pargs_t default_args[1] = { A_DEFAULT_ARGS }; //A_DEFAULT_ARGS_P;
|
||||
|
||||
__attribute__((__gnu_inline__, __always_inline__))
|
||||
static inline size_t _a_ptr_list_len(void* const* restrict arr)
|
||||
{
|
||||
register size_t n=0;
|
||||
while(*arr++) n+=1;
|
||||
return n;
|
||||
}
|
||||
|
||||
inline static void* _a_clone__string(const void* i) { return __builtin_expect(i!=NULL, true) ? strdup(i) : NULL; }
|
||||
inline static void** _a_clone_ptr_list(void* const arr[static 1], void* (*clone_elem)(const void*), size_t* restrict _n)
|
||||
{
|
||||
size_t len;
|
||||
if(__builtin_constant_p(_n) && _n) *_n = len = _a_ptr_list_len(arr);
|
||||
else {
|
||||
len = _a_ptr_list_len(arr);
|
||||
if(_n) *_n = len;
|
||||
}
|
||||
|
||||
void* *array = aligned_alloc(_Alignof(void*), sizeof(void*)*(len+1));
|
||||
if(__builtin_expect(array!=NULL, true)) {
|
||||
if(__builtin_constant_p(clone_elem) && !clone_elem)
|
||||
memcpy(array, arr, sizeof(void*) * len);
|
||||
else if(clone_elem) {
|
||||
//void* *restrict out = array;
|
||||
while(len --> 0) {
|
||||
// *out++ = clone_elem(*arr++);
|
||||
array[len] = clone_elem(arr[len]);
|
||||
}
|
||||
} else memcpy(array, arr, sizeof(void*) * len);
|
||||
array[len] = NULL; // NULL-terminate ptr list
|
||||
}
|
||||
return array;
|
||||
}
|
||||
static char** _a_clone_string_list(char* const arr[static 1], size_t* restrict n)
|
||||
{
|
||||
return (char**)_a_clone_ptr_list((void* const*)arr, _a_clone__string, n);
|
||||
}
|
||||
|
||||
inline static void _a_free_ptr_list(void* arr[static restrict 1], void (*free_elem)(void*), size_t* restrict _n)
|
||||
{
|
||||
if(__builtin_constant_p(_n) && __builtin_constant_p(free_elem) && !_n && !free_elem) {
|
||||
free(arr);
|
||||
return;
|
||||
} else {
|
||||
size_t n =0;
|
||||
void* restrict _to_free = arr;
|
||||
|
||||
void* cur;
|
||||
if(__builtin_constant_p(free_elem) && free_elem) {
|
||||
if( __builtin_constant_p(_n) && !_n)
|
||||
while( (cur = *arr++) ) free_elem(cur);
|
||||
else
|
||||
while( (cur = *arr++) )
|
||||
{
|
||||
free_elem(cur); n+=1;
|
||||
}
|
||||
} else if(!free_elem)
|
||||
while( (cur = *arr++) ) n+=1;
|
||||
else while( (cur = *arr++) )
|
||||
{
|
||||
free_elem(cur); n+=1;
|
||||
}
|
||||
|
||||
free(_to_free);
|
||||
|
||||
if(_n) *_n = n;
|
||||
}
|
||||
}
|
||||
static void _a_free_string_list(char* *restrict arr/*[static restrict 1]*/, size_t* restrict n)
|
||||
{
|
||||
_a_free_ptr_list((void**restrict)arr, free, n);
|
||||
}
|
||||
|
||||
static fd_pass_t* _a_alloc_fd_pass(size_t n)
|
||||
{
|
||||
fd_pass_t* pass = aligned_alloc(_Alignof(fd_pass_t), sizeof(fd_pass_t) + (sizeof(int)*n));
|
||||
if(__builtin_expect(pass!=NULL,true)) {
|
||||
pass->n = n;
|
||||
//memset(pass->fds, 0, sizeof(int) * n);
|
||||
}
|
||||
return pass;
|
||||
}
|
||||
|
||||
void* a_pass_fds(size_t n, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, n);
|
||||
fd_pass_t* pass;
|
||||
if(!n) {
|
||||
pass = A_FD_PASS_NONE;
|
||||
goto _end;
|
||||
}
|
||||
pass = _a_alloc_fd_pass(n);
|
||||
if(__builtin_expect(pass!=NULL, true)) {
|
||||
int* fds = pass->fds;
|
||||
while ( n --> 0 ) {
|
||||
int fd = va_arg(va, int);
|
||||
*fds++ = fd;
|
||||
}
|
||||
}
|
||||
_end:
|
||||
va_end(va);
|
||||
return pass;
|
||||
}
|
||||
|
||||
inline void* a_pass_fds_a(size_t n, int ar[const restrict n])
|
||||
{
|
||||
if(!n) return A_FD_PASS_NONE;
|
||||
fd_pass_t* pass = _a_alloc_fd_pass(n);
|
||||
if(__builtin_expect(pass!=NULL, true))
|
||||
memcpy(pass->fds, ar, n * sizeof(int));
|
||||
return pass;
|
||||
}
|
||||
|
||||
const pargs_t* a_get_program_args() {
|
||||
return parsed_args ?: default_args;
|
||||
}
|
||||
|
||||
bool a_is_parsed() { return parsed_args != NULL; }
|
||||
|
||||
static bool a_parse__short(int i, const char* arg, struct input_arguments in, pargs_t* restrict out)
|
||||
{
|
||||
const struct argument_parser* p = _av_lookup( (av_name_t){ .s = *arg, }, AT_SHORT );
|
||||
if(!p) return false;
|
||||
else {
|
||||
if(!p->parse(i, arg, &in, out)) {
|
||||
if(p->error) {
|
||||
//TODO: How to handle returning `struct arg_parse_error`? Make `out` union arg_parse_result* restrict` instead?
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool a_parse__long(int i, const char* arg, struct input_arguments in, pargs_t* restrict out)
|
||||
{
|
||||
//TODO: determine if `arg` is `k=v` or not
|
||||
bool is_eq = /* TODO */ false;
|
||||
const struct argument_parser* p = _av_lookup( (av_name_t){ .l = arg, }, is_eq ? AT_LONG_EQ : AT_LONG );
|
||||
if(!p) return false;
|
||||
else {
|
||||
//TODO :if(!p->parse(
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline static bool _a_parse_into(union arg_parse_result *restrict parsed, struct input_arguments input)
|
||||
{
|
||||
bool ok = true;;
|
||||
pargs_t args = A_DEFAULT_ARGS;
|
||||
|
||||
//TODO: Parse arguments
|
||||
|
||||
_end:
|
||||
if(ok)
|
||||
_set_ok: parsed->ok = args;
|
||||
else
|
||||
_set_err: parsed->err = (struct arg_parse_error) { .kind = A_PF_UNKNOWN, ._data = { .none = {} } };
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
inline bool a_parse_into(union arg_parse_result *restrict parsed, int *restrict p_argc, char** *restrict p_argv, char** *restrict p_envp)
|
||||
{
|
||||
#if FEATURE_HAS_FLAG(NO_ARGS)
|
||||
parsed->ok = A_DEFAULT_ARGS;
|
||||
return true;
|
||||
#else
|
||||
struct input_arguments input = {
|
||||
p_argc,
|
||||
p_argv,
|
||||
p_envp,
|
||||
};
|
||||
|
||||
return _a_parse_into(parsed, input);
|
||||
#endif
|
||||
}
|
||||
|
||||
const struct arg_parse_error* a_parse(int *restrict argc, char** *restrict p_argv, char** *restrict p_envp)
|
||||
{
|
||||
/*XXX: Probably not needed... _Thread_local*/
|
||||
static union arg_parse_result glob_parsed_args_r;
|
||||
|
||||
if(a_parse_into(&glob_parsed_args_r, argc, p_argv, p_envp))
|
||||
parsed_args = &glob_parsed_args_r.ok; // A_P_OK
|
||||
else
|
||||
return &glob_parsed_args_r.err;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pargs_t* a_clone_args(const pargs_t* args)
|
||||
{
|
||||
if(__builtin_expect(args!=NULL, true)) {
|
||||
pargs_t clone;
|
||||
|
||||
/* replace */ clone.replace = args->replace;
|
||||
|
||||
/* target */ {
|
||||
const char* target_p = args->target;
|
||||
clone.target = target_p ? strdup(target_p) : NULL;
|
||||
};
|
||||
|
||||
/* fd_pass */ {
|
||||
__auto_type fd_pass_p = args->fd_pass;
|
||||
switch((uintptr_t)fd_pass_p) {
|
||||
case ((uintptr_t)A_FD_PASS_ALL):
|
||||
case ((uintptr_t)A_FD_PASS_NONE):
|
||||
clone.fd_pass = fd_pass_p;
|
||||
if(0)
|
||||
default: {
|
||||
// Clone fd_pass_p.
|
||||
clone.fd_pass = a_pass_fds_a(fd_pass_p->n, fd_pass_p->fds);
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
/* env_pass */ {
|
||||
char** env_pass_p = args->env_pass;
|
||||
switch((uintptr_t)env_pass_p) {
|
||||
case (uintptr_t)A_ENV_PASS_ALL:
|
||||
case (uintptr_t)A_ENV_PASS_NONE:
|
||||
clone.env_pass = env_pass_p;
|
||||
if(0)
|
||||
default: {
|
||||
// Clone envp deeply.
|
||||
clone.env_pass = _a_clone_string_list(env_pass_p, NULL);
|
||||
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
return $box(clone);
|
||||
} else return NULL;
|
||||
}
|
||||
|
||||
inline void a_free_args(const pargs_t* args)
|
||||
{
|
||||
if(__builtin_expect(args==NULL, false)) return;
|
||||
|
||||
/* target */ {
|
||||
char* target_p = args->target;
|
||||
if(target_p) free(target_p);
|
||||
};
|
||||
|
||||
/* fd_pass */ {
|
||||
__auto_type fd_pass_p = args->fd_pass;
|
||||
if(fd_pass_p != A_FD_PASS_NONE
|
||||
&& fd_pass_p != A_FD_PASS_ALL) free(fd_pass_p);
|
||||
};
|
||||
|
||||
/* env_pass */ {
|
||||
char** env_pass_p = args->env_pass;
|
||||
switch((uintptr_t)env_pass_p) {
|
||||
case (uintptr_t)A_ENV_PASS_ALL:
|
||||
case (uintptr_t)A_ENV_PASS_NONE:
|
||||
if(0)
|
||||
default: {
|
||||
// Free each string in `env_pass_p`, then free `env_pass_p` itself
|
||||
_a_free_string_list(env_pass_p, NULL);
|
||||
} break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void a_free(pargs_t* restrict args)
|
||||
{
|
||||
if(__builtin_expect(args!=NULL, true)) {
|
||||
a_free_args(args);
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
|
||||
struct arg_parse_error* a_clone_error(const struct arg_parse_error* error)
|
||||
{
|
||||
/* no-op, for now */
|
||||
if(__builtin_expect(error != NULL, true)) {
|
||||
return $box_t(struct arg_parse_error, *error);
|
||||
}
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
void a_free_error(const struct arg_parse_error* error)
|
||||
{
|
||||
(void)error; // no-op, for now;
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void _a__free_globals()
|
||||
{
|
||||
a_free_args(parsed_args);
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
#ifndef _ARGS_H
|
||||
#define _ARGS_H
|
||||
#define _a_inline_proto __attribute__((__gnu_inline__, __always_inline__)) extern inline
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum arg_parse_error_kind {
|
||||
A_PF_UNKNOWN = -1,
|
||||
A_P_OK = 0,
|
||||
|
||||
// TODO: specific errors...
|
||||
};
|
||||
|
||||
typedef struct arg_parsed {
|
||||
struct {
|
||||
bool in,out,err;
|
||||
} replace; // { 1, 1, 0 }
|
||||
|
||||
char* target; // NULL -> default "/dev/null"
|
||||
|
||||
struct {
|
||||
/*struct _Alignas(uint64_t) {
|
||||
int64_t n : 63;
|
||||
bool all : 1;
|
||||
} __attribute__((packed, aligned(uint64_t)));*/
|
||||
size_t n;
|
||||
|
||||
int fds[];
|
||||
}* restrict fd_pass; // A_FD_PASS_NONE
|
||||
|
||||
char** env_pass; // A_ENV_PASS_ALL
|
||||
|
||||
} pargs_t;
|
||||
|
||||
struct arg_parse_error {
|
||||
enum arg_parse_error_kind kind;
|
||||
union {
|
||||
struct{} none;
|
||||
} _data;
|
||||
};
|
||||
_Static_assert(sizeof( ((struct arg_parse_error*)(0))->_data.none ) == 0, "Bad union ZST");
|
||||
|
||||
union arg_parse_result {
|
||||
struct arg_parsed ok;
|
||||
struct arg_parse_error err;
|
||||
};
|
||||
|
||||
#define A_ENV_PASS_ALL NULL
|
||||
#define A_ENV_PASS_NONE ((void*)(~((uintptr_t)0ul)))
|
||||
|
||||
#define A_FD_PASS_ALL ((void*)(~((uintptr_t)0ul)))
|
||||
#define A_FD_PASS_NONE NULL
|
||||
|
||||
#define A_DEFAULT_ARGS ((pargs_t){ { true, true, false}, NULL, A_FD_PASS_NONE, A_ENV_PASS_ALL })
|
||||
#define A_DEFAULT_ARGS_P ((pargs_t[1]) { A_DEFAULT_ARGS } )
|
||||
|
||||
extern const pargs_t* parsed_args;
|
||||
|
||||
/// Return parsed args or default args if no parsed args have been set.
|
||||
_a_inline_proto const pargs_t* a_get_program_args()
|
||||
{
|
||||
__attribute__((section(".text#"))) // Does this give any actual benefit?
|
||||
static const pargs_t default_args = A_DEFAULT_ARGS;
|
||||
|
||||
return parsed_args ?: &default_args;//A_DEFAULT_ARGS_P;
|
||||
}
|
||||
|
||||
/// Have the program's arguments been parsed?
|
||||
_a_inline_proto bool a_is_parsed()
|
||||
{ return parsed_args != NULL; }
|
||||
|
||||
/// Create an `pargs_t.fd_pass` value for `n` fds.
|
||||
void* a_pass_fds(size_t n, ...);
|
||||
/// Create an `pargs_t.fd_pass` value for `n` fds from the array `ar`.
|
||||
void* a_pass_fds_a(size_t n, int ar[const restrict n]);
|
||||
|
||||
bool a_parse_into(union arg_parse_result *restrict parsed, int *restrict argc, char** *restrict p_argv, char** *restrict p_envp);
|
||||
const struct arg_parse_error* a_parse(int *restrict argc, char** *restrict p_argv, char** *restrict p_envp);
|
||||
|
||||
/// Clone a `pargs_t` struct. The returned struct pointer must be freed with `a_free()`.
|
||||
pargs_t* a_clone_args(const pargs_t* args);
|
||||
|
||||
/// Clone a `struct arg_parse_error`. The returned struct pointer must be freed with `a_free_args()`, then with stdlib `free()` itself.
|
||||
struct arg_parse_error* a_clone_error(const struct arg_parse_error* error);
|
||||
|
||||
/// Free the internals of a `pargs_t` struct.
|
||||
void a_free_args(const pargs_t* args);
|
||||
|
||||
/// Free the internals of an `arg_parse_error` struct.
|
||||
void a_free_error(const struct arg_parse_error* error);
|
||||
|
||||
/// Free a `malloc()` or `a_clone_args()`'d `pargs_t` struct (NOTE: do *not* pass the result of `a_get_program_args()` or `parsed_args` to this, use `a_free_args()` for those instead.
|
||||
void a_free(pargs_t* restrict args);
|
||||
|
||||
#undef _a_inline_proto
|
||||
#endif /* _ARGS_H */
|
@ -0,0 +1,284 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "view.h"
|
||||
|
||||
#define S_PTR_MASK (0xcafebeef00000000ul) /* XXX: Should this frob the high or end parts of the pointer? We'll go with high? */
|
||||
|
||||
#define $mixin __attribute__((__gnu_inline__, __always_inline__, __artificial__)) inline
|
||||
#define $nullchk(ptr, or) ({ __auto_type _$p = (ptr); \
|
||||
__builtin_expect(_$p == NULL, false) ? ({ or; }),NULL : _$p; })
|
||||
|
||||
#define min(x, y) ({ __auto_type _$min_x = (x); \
|
||||
__auto_type _$min_y = (y); \
|
||||
_$min_x < _$min_y ? _$min_x : _$min_y; })
|
||||
|
||||
#define ptrdiff(x, y) ({ \
|
||||
__auto_type _$ptrdiff_x = (x); \
|
||||
__auto_type _$ptrdiff_y = (y); \
|
||||
(ptrdiff_t)((_$ptrdiff_x < _$ptrdiff_y) \
|
||||
? ((uintptr_t)_$ptrdiff_y) - ((uintptr_t)_$ptrdiff_x) \
|
||||
: ((uintptr_t)_$ptrdiff_x) - ((uintptr_t)_$ptrdiff_y)); \
|
||||
})
|
||||
|
||||
|
||||
|
||||
typedef uint8_t byte_t;
|
||||
|
||||
__attribute__((packed))
|
||||
struct string_metadata {
|
||||
bool is_owned : 1; // was the string malloc()'d
|
||||
bool has_nul : 1; // does this string have a nul terminator
|
||||
};
|
||||
|
||||
struct string_data {
|
||||
struct string_metadata meta;
|
||||
|
||||
uintptr_t ptr_frob;
|
||||
|
||||
|
||||
struct /*_Alignas(struct string_view)*/ {
|
||||
size_t len;
|
||||
char ptr[];
|
||||
} __attribute__((aligned(_Alignof(struct string_view)))); //__attribute__((align(struct string_view)));
|
||||
};
|
||||
|
||||
#define _s_frob_pointer_(p, f) ((/*__typeof((p))*/void*) (((uintptr_t)(p)) ^ ((uintptr_t)(f))))
|
||||
__attribute__((const))
|
||||
$mixin static void* _s_frob_pointer(void* p)
|
||||
{
|
||||
return _s_frob_pointer_(p, S_PTR_MASK);
|
||||
}
|
||||
#define _s_frob_pointer(p) _s_frob_pointer_((p), S_PTR_MASK)
|
||||
|
||||
#define $frob(v, w) _s_frob_pointer_((v), (w))
|
||||
|
||||
static inline struct string* _s_frob(struct string_data* restrict d) {
|
||||
return (struct string*) _s_frob_pointer(d->ptr);
|
||||
}
|
||||
static inline struct string_data* _s_unfrob(struct string *restrict ptr) {
|
||||
// static const off64_t O_this = offsetof(struct string_data, ptr);
|
||||
|
||||
struct string_data* restrict base = (void*)((byte_t*)_s_frob_pointer(ptr)) - offsetof(struct string_data, ptr);
|
||||
|
||||
uintptr_t frob = base->ptr_frob;
|
||||
if(frob != ((uintptr_t)_s_frob_pointer(base))) return NULL; //XXX: TODO: Is this valid? Probably, since struct string_data will only ever be heap-allocated.
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
$mixin static char* _s_str(struct string_data* restrict data) { return data->ptr; }
|
||||
|
||||
$mixin extern struct string_data* _s_data(char* str, bool check) {
|
||||
struct string_data* sd = (struct string_data*)( ((byte_t*)str) - offsetof(struct string_data, ptr) );
|
||||
|
||||
if(__builtin_constant_p(check) && !check) return sd;
|
||||
else if(check && sd->ptr_frob != ((uintptr_t)_s_frob_pointer(sd))) return NULL;
|
||||
else return sd;
|
||||
}
|
||||
static struct string_data* _s_data(char* str, bool check) {
|
||||
static const size_t O_base = offsetof(struct string_data, ptr);
|
||||
struct string_data* sd = (struct string_data*)( ((byte_t*)str) - O_base );
|
||||
|
||||
if(__builtin_constant_p(check) && !check) return sd;
|
||||
else if(check && sd->ptr_frob != ((uintptr_t)_s_frob_pointer(sd))) return NULL;
|
||||
else return sd;
|
||||
}
|
||||
|
||||
__attribute__((pure))
|
||||
str_info_t s_get_info(view_t str)
|
||||
{
|
||||
return _s_data($nullchk(str.ptr, return NULL), true);
|
||||
}
|
||||
|
||||
__attribute__((pure))
|
||||
view_t s_get_str(str_info_t data)
|
||||
{
|
||||
return (view_t) {
|
||||
.len = data->len,
|
||||
.ptr = data->ptr,
|
||||
};
|
||||
}
|
||||
|
||||
__attribute__((__always_inline__, __gnu_inline__, artificial))
|
||||
inline static size_t _sv_copy_manual(size_t sz, char buffer[restrict static sz], view_t view)
|
||||
{
|
||||
|
||||
memcpy(buffer, view.ptr, sz = min((sz-1), view.len));
|
||||
/*
|
||||
size_t i=0;
|
||||
for(;i< (sz-1) && i<view.len;i++)
|
||||
{
|
||||
buffer[i] = view.ptr[i];
|
||||
}*/
|
||||
buffer[sz] = 0;
|
||||
return view.len+1;
|
||||
}
|
||||
|
||||
inline size_t sv_copy_to(size_t sz, char buffer[restrict static sz], view_t view)
|
||||
{
|
||||
#ifdef _VIEW_USE_SNPRINTF_INTERNAL
|
||||
if(__builtin_expect(view.len <= INT_MAX, true))
|
||||
return (size_t)snprintf(buffer, sz, "%.*s", (int)view.len, view.ptr);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Manual implementation, for views longer than INT_MAX
|
||||
//XXX: TODO: Ensure return value follows the same construct.
|
||||
return _sv_copy_manual(sz, buffer, view);
|
||||
}
|
||||
}
|
||||
|
||||
// String views //
|
||||
|
||||
inline view_t sv_from_cstr(const char* p)
|
||||
{
|
||||
return (view_t) {
|
||||
.len = strlen(p),
|
||||
.ptr = (char*)p,
|
||||
};
|
||||
}
|
||||
|
||||
inline view_t sv_slice_cstr(const char* p, size_t n)
|
||||
{
|
||||
if(__builtin_constant_p(n) && !n ) return (view_t) { .len = 0, .ptr = (char*)p };
|
||||
else return (view_t) { .len = min(strlen(p), n), .ptr = (char*)p };
|
||||
}
|
||||
|
||||
inline view_t sv_slice(view_t v, size_t s, size_t n)
|
||||
{
|
||||
if(__builtin_expect((s = min(v.len, s)) == v.len,false)) return (view_t) { .len = 0, .ptr = v.ptr+v.len };
|
||||
else {
|
||||
return (view_t) {
|
||||
.len = n
|
||||
? min(v.len - s, n)//min(v.len,n) - s
|
||||
: v.len - s, //XXX: Test this, I think it's correct, but dunno...
|
||||
.ptr = v.ptr + s,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t sv_split(view_t p, char on, view_t* restrict first, view_t* restrict second)
|
||||
{
|
||||
if(__builtin_constant_p(first) && __builtin_constant_p(second) && !first && !second)
|
||||
return (size_t)(((uintptr_t) $nullchk(memchr(p.ptr, (int)on, p.len), return 0)) - (uintptr_t)p.ptr);
|
||||
else {
|
||||
char* start = memchr(p.ptr, (int)on, p.len); // `on` chr here
|
||||
if(__builtin_expect(start==NULL, false)) return 0;
|
||||
|
||||
size_t diff = (size_t)ptrdiff(p.ptr, start);
|
||||
if(first) *first = (view_t) {
|
||||
.len = diff,
|
||||
.ptr = p.ptr,
|
||||
};
|
||||
if(second) *second = (view_t) {
|
||||
.len = (p.len - diff) + 1, //XXX: WARNING: this might be an off-by-1 error...
|
||||
.ptr = start + 1,
|
||||
};
|
||||
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
size_t sv_split_cstr(const char* p, char on, view_t* restrict first, view_t* restrict second)
|
||||
{
|
||||
return sv_split(sv_from_cstr(p), on, first, second);
|
||||
}
|
||||
|
||||
inline char* sv_to_cstr(view_t view, char* restrict buffer, size_t n)
|
||||
{
|
||||
if(__builtin_constant_p(buffer) && !buffer) {
|
||||
if(__builtin_constant_p(n) && !n) return strndup(view.ptr, view.len);
|
||||
else return strndup(view.ptr, n ? min(n, view.len) : view.len);
|
||||
} else {
|
||||
if(__builtin_constant_p(n) && !n) {
|
||||
return buffer;
|
||||
} else {
|
||||
sv_copy_to(n, buffer, view);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((malloc))
|
||||
char* sv_dupto_cstr(view_t view)
|
||||
{
|
||||
return sv_to_cstr(view, NULL, 0);
|
||||
}
|
||||
|
||||
bool sv_eq_cstr(view_t a, const char* b)
|
||||
{
|
||||
return strncmp(a.ptr, b, a.len) == 0 && strlen(b) == a.len;
|
||||
}
|
||||
|
||||
bool sv_eq(view_t a, view_t b)
|
||||
{
|
||||
return a.len == b.len && (__builtin_expect(a.ptr == b.ptr, false) || memcmp(a.ptr, b.ptr, a.len));
|
||||
}
|
||||
|
||||
/* Not useful.
|
||||
int sv_fprintf(FILE* out, view_t fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
char rfmt[fmt.len+1];
|
||||
va_start(va, fmt);
|
||||
sv_copy_to(fmt.len+1, rfmt, fmt);
|
||||
|
||||
int r =vfprintf(out, rfmt, va);
|
||||
|
||||
va_end(va);
|
||||
return r;
|
||||
}*/
|
||||
|
||||
inline static size_t sv_vsnprintf(view_t* restrict out, size_t sz, const char* fmt, va_list args)
|
||||
{
|
||||
if(__builtin_expect(out!=NULL, true))
|
||||
return vsnprintf(out->ptr, min(out->len, sz), fmt, args);
|
||||
else return vsnprintf((char[]){}, 0, fmt, args);
|
||||
}
|
||||
|
||||
size_t sv_vsvnprintf(view_t* restrict out, size_t sz, view_t fmt, va_list args)
|
||||
{
|
||||
char buffer[fmt.len+1];
|
||||
sv_copy_to(fmt.len+1, buffer, fmt);
|
||||
return sv_vsnprintf(out, sz, buffer, args);
|
||||
}
|
||||
|
||||
size_t sv_snprintf(view_t* restrict out, size_t sz, const char* fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
size_t r = sv_vsnprintf(out, sz, fmt, va);
|
||||
va_end(va);
|
||||
return r;
|
||||
}
|
||||
|
||||
size_t sv_sprintf(view_t* restrict out, const char* fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
size_t r = sv_vsnprintf(out, out ? out->len : 0, fmt, va);
|
||||
va_end(va);
|
||||
return r;
|
||||
}
|
||||
size_t sv_svnprintf(view_t* restrict out, size_t sz, view_t fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
size_t r = sv_vsvnprintf(out, sz, fmt, va);
|
||||
va_end(va);
|
||||
return r;
|
||||
}
|
||||
|
||||
size_t sv_svprintf(view_t* restrict out, view_t fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
size_t r = sv_vsvnprintf(out, out ? out->len : fmt.len, fmt, va);
|
||||
va_end(va);
|
||||
return r;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
#ifndef _VIEW_H
|
||||
#define _VIEW_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/// Printf format string for `view_t`
|
||||
#define SV_PRINT_FMT "%.*s"
|
||||
/// Printf format arguments for `view_t` to match the position of `SV_PRINT_FMT`
|
||||
#define SV_PRINT_ARG(view) (int)(view).len, (view).ptr
|
||||
|
||||
typedef struct string_view {
|
||||
size_t len;
|
||||
char* ptr;
|
||||
} view_t;
|
||||
|
||||
struct string_data;
|
||||
typedef struct string_data* str_info_t;
|
||||
|
||||
struct string;
|
||||
|
||||
// Functions //
|
||||
|
||||
view_t s_get_str(str_info_t data) __attribute__((pure));
|
||||
str_info_t s_get_info(view_t str) __attribute__((pure));
|
||||
|
||||
view_t sv_slice(view_t v, size_t s, size_t n);
|
||||
size_t sv_split(view_t p, char on, view_t* restrict first, view_t* restrict second);
|
||||
|
||||
view_t sv_from_cstr(const char* p);
|
||||
view_t sv_slice_cstr(const char* p, size_t n);
|
||||
|
||||
__attribute__((__gnu_inline__))
|
||||
extern inline size_t sv_split_cstr(const char* p, char on, view_t* restrict first, view_t* restrict second)
|
||||
{
|
||||
return sv_split(sv_from_cstr(p), on, first, second);
|
||||
}
|
||||
|
||||
/// Copy `view` into nul-terminated `buffer`, up to `size` bytes. Return the number of bytes that would've been copied regardless of `sz` (incl. nul-terminator)
|
||||
size_t sv_copy_to(size_t sz, char buffer[restrict static sz], view_t view);
|
||||
char* sv_to_cstr(view_t view, char* restrict buffer, size_t n);
|
||||
|
||||
// sprintf-like
|
||||
size_t sv_svnprintf(view_t* restrict out, size_t sz, view_t fmt, ...);
|
||||
size_t sv_svprintf(view_t* restrict out, view_t fmt, ...);
|
||||
size_t sv_sprintf(view_t* restrict out, const char* fmt, ...);
|
||||
size_t sv_snprintf(view_t* restrict out, size_t sz, const char* fmt, ...);
|
||||
|
||||
__attribute__((__gnu_inline__, malloc))
|
||||
extern inline char* sv_dupto_cstr(view_t view)
|
||||
{ return sv_to_cstr(view, (char*)0, 0); }
|
||||
|
||||
bool sv_eq_cstr(view_t a, const char* b);
|
||||
__attribute__((__gnu_inline__))
|
||||
extern inline bool sv_eq(view_t a, view_t b)
|
||||
{
|
||||
return a.len == b.len && (__builtin_expect(a.ptr == b.ptr, false) || (&sv_eq)(a, b));
|
||||
}
|
||||
|
||||
#endif /* _VIEW_H */
|
Loading…
Reference in new issue