fixes: improve login environment handling (#114)

* chore(organization): allow nested header and c files

* feat: add shell login "middleware" & etc:
  * lidm now calls `bash` (or other shells, depends on PACKAGE cfg) in
  login mode as a session wrapper to source most env (can be disabled)
  * this fixes a lot of env problems with all `/etc/profile` and more

Extra:
* implemented a musl compatible version of `execvpe` and now lidm should
  search for PATH everywhere it needs to
* `search_path` now also checks if the found binary is properly
  executable
* lidm now uses `confstr` for a decent PATH default if none is found
* logs are unbuffered for cases where debug logs appear empty (exit
  without handlers moment)

* chore: one-time evaluate certain makefile vars

---------

Co-authored-by: grialion <48643945+grialion@users.noreply.github.com>
This commit is contained in:
2026-02-07 17:12:03 +01:00
committed by GitHub
parent c009c6c31c
commit 0b2d0bdf03
34 changed files with 555 additions and 424 deletions

View File

@@ -1,10 +1,11 @@
// TODO: handle `fork() == -1`// TODO: handle `fork() == -1`s
// TODO: handle `fork() == -1`
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <security/pam_misc.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -21,7 +22,8 @@
#include "pam.h"
#include "sessions.h"
#include "ui.h"
#include "util.h"
#include "util/path.h"
#include "util/vec.h"
#define XORG_MESSAGE_LENGTH 16
@@ -80,16 +82,6 @@ static void source_paths(struct Vector* NNULLABLE vec_envlist,
}
}
/*char *buf;*/
/*size_t bsize = snprintf(NULL, 0, "/run/user/%d", pw->pw_uid) + 1;*/
/*buf = malloc(bsize);*/
/*snprintf(buf, bsize, "/run/user/%d", pw->pw_uid);*/
/*setenv("XDG_RUNTIME_DIR", buf, true);*/
/*setenv("XDG_SESSION_CLASS", "user", true);*/
/*setenv("XDG_SESSION_ID", "1", true);*/
/*setenv("XDG_SESSION_DESKTOP", , true);*/
/*setenv("XDG_SEAT", "seat0", true);*/
struct child_msg {
char* msg;
int _errno;
@@ -176,7 +168,8 @@ static void start_xorg_server(struct passwd* pw, char** NNULLABLE envlist,
// TODO: add error msgs
/// returns on completion
static void launch_with_xorg_server(struct session_exec* NNULLABLE exec,
static void launch_with_xorg_server(struct config* config,
struct session_exec* NNULLABLE exec,
struct passwd* pw,
char** NNULLABLE envlist) {
int xorg_pipefd[2];
@@ -212,10 +205,14 @@ static void launch_with_xorg_server(struct session_exec* NNULLABLE exec,
(void)fflush(NULL);
pid_t xorg_session_pid = fork();
if (xorg_session_pid == 0) {
int exit = session_exec_exec(exec, envlist);
perror("exec error");
if (config->behavior.bypass_shell_login)
session_exec_exec(exec, envlist);
else
session_exec_login_through_shell(exec, envlist);
perror("session exec error");
(void)fputs("failure calling session\n", stderr);
_exit(exit);
_exit(EXIT_FAILURE);
}
// looks weird, waiting on -1 should wait on any child and then just check if
@@ -254,8 +251,8 @@ static void launch_with_xorg_server(struct session_exec* NNULLABLE exec,
char _dummy; \
read(pipefd[0], &_dummy, sizeof(_dummy)); \
}
inline static void forked(int pipefd[2], struct passwd* pw,
char* NNULLABLE user,
inline static void forked(struct config* config, int pipefd[2],
struct passwd* pw, char* NNULLABLE user,
struct session* NNULLABLE session,
char** NNULLABLE envlist) {
if (chdir(pw->pw_dir) == -1) SEND_ERR("chdir");
@@ -267,14 +264,26 @@ inline static void forked(int pipefd[2], struct passwd* pw,
DUMMY_READ();
close(pipefd[0]);
if (!getenv("PATH")) {
size_t path_size = confstr(_CS_PATH, NULL, 0);
char* path_env = malloc(path_size);
confstr(_CS_PATH, path_env, path_size);
setenv("PATH", path_env, 1);
free(path_env);
}
log_printf(" [I] using shell login?: %s",
config->behavior.bypass_shell_login ? "true" : "false");
if (session->type == XORG) {
launch_with_xorg_server(&session->exec, pw, envlist);
launch_with_xorg_server(config, &session->exec, pw, envlist);
_exit(EXIT_SUCCESS);
} else {
int exit = session_exec_exec(&session->exec, envlist);
perror("exec error");
(void)fputs("failure calling session\n", stderr);
_exit(exit);
if (config->behavior.bypass_shell_login)
session_exec_exec(&session->exec, envlist);
else
session_exec_login_through_shell(&session->exec, envlist);
perror("session exec error");
_exit(EXIT_FAILURE);
}
}
#undef SEND_MSG
@@ -325,7 +334,7 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
uint pid = fork();
if (pid == 0)
forked(pipefd, pw, user, &session, envlist);
forked(config, pipefd, pw, user, &session, envlist);
else {
struct child_msg msg;
read(pipefd[0], &msg, sizeof(struct child_msg));

View File

@@ -11,14 +11,15 @@
#include "config.h"
#include "desktop.h"
#include "keys.h"
#include "log.h"
#include "macros.h"
#include "util.h"
#include "util/vec.h"
#define UPPER_HALF_BYTE 4
int parse_hex(char* _at, char x1, char x2) {
// make linter happy
u_char* at = (u_char*)_at;
uint8_t* at = (uint8_t*)_at;
*at = 0;

View File

@@ -9,61 +9,39 @@
#include "desktop_exec.h"
#include "macros.h"
#include "util/path.h"
// constants for exec string parsing
#define MAX_ARGS 100
// ARG_LENGTH is the initial length of a parsed argument
#define ARG_LENGTH 64
// returns NULL on any error
// otherwise it returns the absolute path of the program that MUST BE FREED
char* NULLABLE search_path(const char* NNULLABLE for_binary) {
if (strchr(for_binary, '/') != NULL) {
// skip absolute paths
return strdup(for_binary);
char* NULLABLE desktop_as_cmdline(char** args) {
if (args[0] == NULL) return NULL;
size_t fmtd_len = 0;
char** ptr = args;
while (*ptr) {
fmtd_len += strlen(*ptr) + 1;
ptr++;
}
char* path_env = getenv("PATH");
if (!path_env) return NULL;
char* path = strdup(path_env);
if (!path) return NULL;
fmtd_len -= 1;
char* tok = strtok(path, ":");
while (tok) {
char* bin_path;
asprintf(&bin_path, "%s/%s", tok, for_binary);
if (!bin_path) {
free(path);
return NULL;
}
char* fmt_cmdline = malloc(fmtd_len + 1);
if (!fmt_cmdline) return NULL;
struct stat stat_buf;
if (stat(bin_path, &stat_buf) == 0) {
// TODO: check exec bit ig
// if(stat_buf.) {}
free(path);
return bin_path;
}
size_t fmting_len = 0;
ptr = args;
while (*ptr) {
char* nbyte = stpcpy(&fmt_cmdline[fmting_len], *ptr);
*nbyte = ' ';
free(bin_path);
tok = strtok(NULL, ":");
fmting_len += nbyte - &fmt_cmdline[fmting_len] + 1;
ptr++;
}
fmt_cmdline[fmting_len - 1] = '\0';
free(path);
return NULL;
}
// returns -1 on exec failure and -2 on search failure
int execvpe_desktop(char** args, char* NNULLABLE* NNULLABLE envlist) {
char* new_arg = search_path(args[0]);
if (!new_arg) return -2;
free(args[0]);
args[0] = new_arg;
int status = execve(args[0], args, envlist);
free(new_arg);
return status;
return fmt_cmdline;
}
// parse Exec=/bin/prog arg1 arg2\ with\ spaces

View File

@@ -2,7 +2,7 @@
#include "efield.h"
#include "ui.h"
#include "util.h"
#include "util/utf8.h"
struct editable_field efield_new(char* content) {
struct editable_field efield;
@@ -16,14 +16,14 @@ struct editable_field efield_new(char* content) {
return efield;
}
void efield_trim(struct editable_field* self, u_char pos) {
void efield_trim(struct editable_field* self, uint8_t pos) {
self->pos = pos;
self->content[pos + 1] = '\0';
}
#define BACKSPACE_CODE 127
void efield_update(struct editable_field* self, char* update) {
u_char insert_len = strlen(update);
uint8_t insert_len = strlen(update);
if (insert_len == 0) return;
if (self->pos > strlen(self->content))
@@ -61,7 +61,7 @@ bool efield_seek(struct editable_field* self, char seek) {
if (*self->content == '\0') return false;
if (seek == 0) return false;
u_char count = seek < 0 ? -seek : seek;
uint8_t count = seek < 0 ? -seek : seek;
char* ptr = &self->content[self->pos];
char* start = ptr;
@@ -75,6 +75,6 @@ bool efield_seek(struct editable_field* self, char seek) {
}
}
self->pos = (u_char)(ptr - self->content);
self->pos = (uint8_t)(ptr - self->content);
return ptr != start;
}

77
src/keys.c Normal file
View File

@@ -0,0 +1,77 @@
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "keys.h"
#include "macros.h"
#include "ui.h"
static int selret_magic();
int find_keyname(enum Keys* at, const char* name) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
if (strcmp(KEY_NAMES[i], name) == 0) {
*at = (enum Keys)i;
return 0;
}
}
return -1;
}
struct option_keys find_ansi(const char* seq) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
struct key_mapping mapping = KEY_MAPPINGS[i];
for (size_t j = 0; mapping.sequences[j] != NULL; j++) {
if (strcmp(mapping.sequences[j], seq) == 0) {
return (struct option_keys){
.is_some = true,
.key = (enum Keys)i,
};
}
}
}
return (struct option_keys){.is_some = false};
}
void read_press(uint8_t* length, char* out) {
*length = 0;
while (true) {
if (read(STDIN_FILENO, &out[(*length)++], 1) != 1) {
print_errno("read error");
sleep(3);
exit(1);
}
int selret = selret_magic();
if (selret == -1) {
print_errno("selret error");
} else if (selret != 1) {
out[*length] = '\0';
return;
}
}
}
bool read_press_nb(uint8_t* length, char* out, struct timeval* tv) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
errno = 0;
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, tv);
if (errno || ret <= 0) return false;
read_press(length, out);
return true;
}
// https://stackoverflow.com/a/48040042
static int selret_magic() {
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
return select(1, &set, NULL, NULL, &timeout);
}

View File

@@ -8,6 +8,7 @@ static FILE* logger_out = NULL;
void log_init(FILE* fd) {
if (logger_out) (void)fclose(logger_out);
(void)setvbuf(fd, NULL, _IONBF, 0);
logger_out = fd;
}

View File

@@ -15,7 +15,7 @@
#include "signal_handler.h"
#include "ui.h"
#include "users.h"
#include "util.h"
#include "util/vec.h"
#define DATESTR_MAXBUFSIZE 0x20
@@ -31,6 +31,7 @@ int main(int argc, char* argv[]) {
}
log_init(log_fd);
log_puts("Starting log:");
}
if (argc == 2) {

View File

@@ -1,10 +1,9 @@
#include <stddef.h>
#include "config.h"
#include "efield.h"
#include "ofield.h"
#include "ui.h"
#include "util.h"
#include "util/utf8.h"
struct opts_field ofield_new(size_t opts) {
if (opts == 0) {
@@ -55,11 +54,11 @@ bool ofield_seek(struct opts_field* self, char seek) {
return true;
}
u_char ofield_display_cursor_col(struct opts_field* self, u_char maxlen) {
uint8_t ofield_display_cursor_col(struct opts_field* self, uint8_t maxlen) {
if (self->current_opt == 0) {
u_char display_len = utf8len(self->efield.content);
u_char pos = utf8len_until(self->efield.content,
&self->efield.content[self->efield.pos]);
uint8_t display_len = utf8len(self->efield.content);
uint8_t pos = utf8len_until(self->efield.content,
&self->efield.content[self->efield.pos]);
if (display_len > maxlen) {
if (pos < maxlen / 2) {

View File

@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "macros.h"
#include "pam.h"
#include "sessions.h"
@@ -92,6 +93,10 @@ struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle,
char** raw_envlist = pam_getenvlist(handle);
if (!raw_envlist) FAIL(PAMH_ERR_ERRNO, "pam_getenvlist");
char** ptr = raw_envlist;
while (*ptr)
log_printf("[I] got pam env %s\n", *(ptr++));
struct envpair extra_env[] = {
{"TERM", getenv("TERM")},
{"PATH", getenv("PATH")},

View File

@@ -1,3 +1,4 @@
#include <errno.h>
#include <ftw.h>
#include <limits.h>
#include <stdbool.h>
@@ -6,13 +7,16 @@
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "config.h"
#include "desktop.h"
#include "desktop_exec.h"
#include "log.h"
#include "macros.h"
#include "sessions.h"
#include "util.h"
#include "util/path.h"
#include "util/vec.h"
struct source_dir {
enum SessionType type;
@@ -163,3 +167,54 @@ struct Vector get_avaliable_sessions() {
return sessions;
}
int session_exec_exec(struct session_exec* NNULLABLE exec,
char* NULLABLE* NNULLABLE envp) {
switch (exec->typ) {
case EXEC_SHELL: {
char* argv[] = {exec->shell, NULL};
return execvpe(exec->shell, argv, envp);
}
case EXEC_DESKTOP:
return execvpe(exec->desktop.args[0], exec->desktop.args, envp);
default:
__builtin_unreachable();
}
}
/// So the reason this doesn't use the user shell is because that can really be
/// anything, also assuming it were fish for example, it can't execute posix
/// shell files and that leaves it out of most `/etc/profile.d/` scripts.
///
/// I'll just default to bash for now as it's able to source almost everything
/// and takes most flags. Maybe will try to make this more sophisticated in the
/// future.
///
/// This respects errno. Even also works as any exec family function.
#ifndef LOGIN_SHELL
#define LOGIN_SHELL "bash"
#endif
// This triggers login behavior
#define LOGIN_SHELL_ARG0 "-" LOGIN_SHELL
int session_exec_login_through_shell(struct session_exec* NNULLABLE exec,
char* NULLABLE* NNULLABLE envp) {
switch (exec->typ) {
case EXEC_SHELL: {
char* argv[] = {LOGIN_SHELL_ARG0, "-c", exec->shell, NULL};
return execvpe(LOGIN_SHELL, argv, envp);
}
case EXEC_DESKTOP: {
char* name = desktop_as_cmdline(exec->desktop.args);
if (!name) {
errno = EINVAL;
return -1;
}
char* argv[] = {LOGIN_SHELL_ARG0, "-c", name, NULL};
int ret = execvpe(LOGIN_SHELL, argv, envp);
free(name);
return ret;
}
default:
__builtin_unreachable();
}
}

View File

@@ -32,9 +32,9 @@
#include "ui.h"
#include "ui_state.h"
#include "users.h"
#include "util.h"
#include "util/utf8.h"
const u_char INPUTS_N = 3;
const uint8_t INPUTS_N = 3;
struct uint_point {
uint x;
@@ -135,11 +135,11 @@ char* trunc_gethostname(size_t maxlen, const char* const ELLIPSIS) {
}
void ui_update_cursor_focus() {
u_char line = box_start.y;
u_char col = box_start.x + VALUES_COL;
uint8_t line = box_start.y;
uint8_t col = box_start.x + VALUES_COL;
struct opts_field* ofield = get_opts_ffield();
u_char maxlen = VALUE_MAXLEN;
uint8_t maxlen = VALUE_MAXLEN;
if (ofield->opts > 1) {
maxlen -= utf8len(g_config->strings.opts_pre) +
utf8len(g_config->strings.opts_post);
@@ -269,7 +269,7 @@ int load(struct Vector* users, struct Vector* sessions) {
scratch_print_ui();
/// INTERACTIVE
u_char len;
uint8_t len;
char seq[0xff];
uint esc = 0;
while (true) {
@@ -362,11 +362,11 @@ void clean_line(struct uint_point origin, uint line) {
printf("\x1b[%d;%dH%s", origin.y + line, origin.x + 1, line_cleaner);
}
u_char get_render_pos_offset(struct opts_field* self, u_char maxlen) {
uint8_t get_render_pos_offset(struct opts_field* self, uint8_t maxlen) {
if (self->current_opt != 0) return 0;
u_char pos = utf8len_until(self->efield.content,
&self->efield.content[self->efield.pos]);
uint8_t pos = utf8len_until(self->efield.content,
&self->efield.content[self->efield.pos]);
return pos - ofield_display_cursor_col(self, maxlen);
}
@@ -439,8 +439,8 @@ void print_session(struct session session, bool multiple) {
char* toprint = session.name;
if (multiple) {
u_char maxlen = VALUE_MAXLEN - utf8len(g_config->strings.opts_pre) -
utf8len(g_config->strings.opts_post);
uint8_t maxlen = VALUE_MAXLEN - utf8len(g_config->strings.opts_pre) -
utf8len(g_config->strings.opts_post);
toprint += get_render_pos_offset(&of_session, maxlen);
size_t printlen = utf8seekn(toprint, maxlen) - toprint;
@@ -467,8 +467,8 @@ void print_user(struct user user, bool multiple) {
char* toprint = user.display_name;
if (multiple) {
u_char maxlen = VALUE_MAXLEN - utf8len(g_config->strings.opts_pre) -
utf8len(g_config->strings.opts_post);
uint8_t maxlen = VALUE_MAXLEN - utf8len(g_config->strings.opts_pre) -
utf8len(g_config->strings.opts_post);
toprint += get_render_pos_offset(&of_session, maxlen);
size_t printlen = utf8seekn(toprint, maxlen) - toprint;

View File

@@ -7,7 +7,7 @@
#include "log.h"
#include "macros.h"
#include "users.h"
#include "util.h"
#include "util/vec.h"
// NOLINTNEXTLINE(readability-identifier-length)
int build_user(struct user* NNULLABLE user, struct passwd* p) {

View File

@@ -1,238 +0,0 @@
#include <asm-generic/errno-base.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "keys.h"
#include "ui.h"
#include "util.h"
static int selret_magic();
int find_keyname(enum Keys* at, const char* name) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
if (strcmp(KEY_NAMES[i], name) == 0) {
*at = (enum Keys)i;
return 0;
}
}
return -1;
}
struct option_keys find_ansi(const char* seq) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
struct key_mapping mapping = KEY_MAPPINGS[i];
for (size_t j = 0; mapping.sequences[j] != NULL; j++) {
if (strcmp(mapping.sequences[j], seq) == 0) {
return (struct option_keys){
.is_some = true,
.key = (enum Keys)i,
};
}
}
}
return (struct option_keys){.is_some = false};
}
void read_press(u_char* length, char* out) {
*length = 0;
while (true) {
if (read(STDIN_FILENO, &out[(*length)++], 1) != 1) {
print_errno("read error");
sleep(3);
exit(1);
}
int selret = selret_magic();
if (selret == -1) {
print_errno("selret error");
} else if (selret != 1) {
out[*length] = '\0';
return;
}
}
}
bool read_press_nb(u_char* length, char* out, struct timeval* tv) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
errno = 0;
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, tv);
if (errno || ret <= 0) return false;
read_press(length, out);
return true;
}
// https://stackoverflow.com/a/48040042
static int selret_magic() {
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
return select(1, &set, NULL, NULL, &timeout);
}
// UTF-8 shii
#define UTF8_CONT_MSK 0b11000000
#define UTF8_CONT_VAL 0b10000000
bool utf8_iscont(char byte) {
return (byte & UTF8_CONT_MSK) == UTF8_CONT_VAL;
}
size_t utf8len(const char* str) {
size_t len = 0;
while (*str != '\0') {
if (!utf8_iscont(*(str++))) len++;
}
return len;
}
size_t utf8len_until(const char* str, const char* until) {
size_t len = 0;
while (str < until) {
if (!utf8_iscont(*(str++))) len++;
}
return len;
}
size_t utf8trunc(char* str, size_t n) {
size_t bytes = 0;
while (true) {
if (str[bytes] == '\0') break;
if (utf8_iscont(str[bytes])) {
bytes++;
continue;
}
if (n == 0) {
str[bytes] = '\0';
break;
}
bytes++;
n--;
}
return bytes;
}
const char* utf8back(const char* str) {
while (utf8_iscont(*(--str))) {
}
return str;
}
const char* utf8seek(const char* str) {
while (utf8_iscont(*(++str))) {
}
return str;
}
const char* utf8seekn(const char* str, size_t n) {
while (n > 0 && *str != '\0') {
str = utf8seek(str);
n--;
}
return str;
}
// Vector shii
const struct Vector VEC_NEW = {
.length = 0,
.capacity = 0,
.pages = NULL,
};
struct Vector vec_from_raw(void** raw) {
size_t len = 0;
while (raw[len])
len++;
return (struct Vector){
.length = len,
.capacity = len,
.pages = raw,
};
}
void** vec_as_raw(struct Vector self) {
if (vec_push(&self, NULL) != 0) return NULL;
return self.pages;
}
int vec_resize(struct Vector* self, size_t size) {
void** new_location =
(void**)realloc((void*)self->pages, size * sizeof(void*));
if (new_location != NULL) {
if (self->length > size) self->length = size;
self->capacity = size;
self->pages = new_location;
} else {
return -1;
}
return 0;
}
int vec_reserve(struct Vector* self, size_t size) {
uint32_t new_capacity = self->capacity;
while (self->length + size > new_capacity) {
new_capacity = new_capacity + (new_capacity >> 1) +
1; // cap * 1.5 + 1; 0 1 2 4 7 11...
}
return vec_resize(self, new_capacity);
}
int vec_reserve_exact(struct Vector* self, size_t size) {
uint32_t needed_capacity = self->length + size;
if (self->capacity < needed_capacity) {
return vec_resize(self, needed_capacity);
}
return 0;
}
int vec_push(struct Vector* self, void* item) {
int res_ret = vec_reserve(self, 1);
if (res_ret != 0) return res_ret;
self->pages[self->length++] = item;
return 0;
}
void vec_free(struct Vector* self) {
while (self->length > 0)
free(self->pages[--self->length]);
vec_clear(self);
}
void vec_clear(struct Vector* self) {
free((void*)self->pages);
vec_reset(self);
}
void vec_reset(struct Vector* self) {
*self = (struct Vector){
.length = 0,
.capacity = 0,
.pages = NULL,
};
}
void* vec_pop(struct Vector* self) {
if (self->length == 0) return NULL;
return self->pages[--self->length];
}
void* vec_get(struct Vector* self, size_t index) {
if (index >= self->length) return NULL;
return self->pages[index];
}

64
src/util/path.c Normal file
View File

@@ -0,0 +1,64 @@
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "util/path.h"
// returns NULL on any error
// otherwise it returns the absolute path of the program that MUST BE FREED
//
// Should be almost completely posix compliant, except it won't resolve empty
// PATH entries relative to the cwd
char* NULLABLE search_path(const char* NNULLABLE for_binary) {
if (strchr(for_binary, '/') != NULL) {
// skip absolute paths
return strdup(for_binary);
}
char* path_env = getenv("PATH");
if (!path_env) return NULL;
char* path = strdup(path_env);
if (!path) return NULL;
char* tok = strtok(path, ":");
while (tok) {
char* bin_path;
asprintf(&bin_path, "%s/%s", tok, for_binary);
if (!bin_path) {
free(path);
return NULL;
}
struct stat stat_buf;
if (stat(bin_path, &stat_buf) == 0 && access(bin_path, X_OK) == 0) {
free(path);
return bin_path;
}
free(bin_path);
tok = strtok(NULL, ":");
}
free(path);
return NULL;
}
// This is present in glibc ONLY with GNU extensions, this aims to provide a
// musl compatible variant.
//
// Respects errno of exec functions family.
int execvpe(const char* NNULLABLE file, char* NULLABLE const argv[NNULLABLE],
char* NULLABLE const envp[NNULLABLE]) {
char* path = search_path(file);
if (!path) {
errno = ENOENT;
return -1;
}
int ret = execve(path, argv, envp);
free(path);
return ret;
}

62
src/util/utf8.c Normal file
View File

@@ -0,0 +1,62 @@
#include "util/utf8.h"
#define UTF8_CONT_MSK 0b11000000
#define UTF8_CONT_VAL 0b10000000
bool utf8_iscont(char byte) {
return (byte & UTF8_CONT_MSK) == UTF8_CONT_VAL;
}
size_t utf8len(const char* str) {
size_t len = 0;
while (*str != '\0') {
if (!utf8_iscont(*(str++))) len++;
}
return len;
}
size_t utf8len_until(const char* str, const char* until) {
size_t len = 0;
while (str < until) {
if (!utf8_iscont(*(str++))) len++;
}
return len;
}
size_t utf8trunc(char* str, size_t n) {
size_t bytes = 0;
while (true) {
if (str[bytes] == '\0') break;
if (utf8_iscont(str[bytes])) {
bytes++;
continue;
}
if (n == 0) {
str[bytes] = '\0';
break;
}
bytes++;
n--;
}
return bytes;
}
const char* utf8back(const char* str) {
while (utf8_iscont(*(--str))) {
}
return str;
}
const char* utf8seek(const char* str) {
while (utf8_iscont(*(++str))) {
}
return str;
}
const char* utf8seekn(const char* str, size_t n) {
while (n > 0 && *str != '\0') {
str = utf8seek(str);
n--;
}
return str;
}

96
src/util/vec.c Normal file
View File

@@ -0,0 +1,96 @@
#include <stdlib.h>
#include "util/vec.h"
const struct Vector VEC_NEW = {
.length = 0,
.capacity = 0,
.pages = NULL,
};
struct Vector vec_from_raw(void** raw) {
size_t len = 0;
while (raw[len])
len++;
return (struct Vector){
.length = len,
.capacity = len,
.pages = raw,
};
}
void** vec_as_raw(struct Vector self) {
if (vec_push(&self, NULL) != 0) return NULL;
return self.pages;
}
int vec_resize(struct Vector* self, size_t size) {
void** new_location =
(void**)realloc((void*)self->pages, size * sizeof(void*));
if (new_location != NULL) {
if (self->length > size) self->length = size;
self->capacity = size;
self->pages = new_location;
} else {
return -1;
}
return 0;
}
int vec_reserve(struct Vector* self, size_t size) {
uint32_t new_capacity = self->capacity;
while (self->length + size > new_capacity) {
new_capacity = new_capacity + (new_capacity >> 1) +
1; // cap * 1.5 + 1; 0 1 2 4 7 11...
}
return vec_resize(self, new_capacity);
}
int vec_reserve_exact(struct Vector* self, size_t size) {
uint32_t needed_capacity = self->length + size;
if (self->capacity < needed_capacity) {
return vec_resize(self, needed_capacity);
}
return 0;
}
int vec_push(struct Vector* self, void* item) {
int res_ret = vec_reserve(self, 1);
if (res_ret != 0) return res_ret;
self->pages[self->length++] = item;
return 0;
}
void vec_free(struct Vector* self) {
while (self->length > 0)
free(self->pages[--self->length]);
vec_clear(self);
}
void vec_clear(struct Vector* self) {
free((void*)self->pages);
vec_reset(self);
}
void vec_reset(struct Vector* self) {
*self = (struct Vector){
.length = 0,
.capacity = 0,
.pages = NULL,
};
}
void* vec_pop(struct Vector* self) {
if (self->length == 0) return NULL;
return self->pages[--self->length];
}
void* vec_get(struct Vector* self, size_t index) {
if (index >= self->length) return NULL;
return self->pages[index];
}