user: implement mlibc as the libc, finally.

It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-05-02 03:31:49 -04:00
parent 2fa39ad85a
commit 9a9b91c940
2387 changed files with 152741 additions and 315 deletions
@@ -0,0 +1,64 @@
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
// va_list
void vwarn(const char *fmt, va_list params) {
fprintf(stderr, "%s: ", program_invocation_short_name);
if (fmt) {
vfprintf(stderr, fmt, params);
fwrite(": ", 1, 2, stderr);
}
perror(nullptr);
}
void vwarnx(const char *fmt, va_list params) {
fprintf(stderr, "%s: ", program_invocation_short_name);
if (fmt) {
vfprintf(stderr, fmt, params);
}
putc('\n', stderr);
}
__attribute__((__noreturn__)) void verr(int status, const char *fmt, va_list params) {
vwarn(fmt, params);
exit(status);
}
__attribute__((__noreturn__)) void verrx(int status, const char *fmt, va_list params) {
vwarnx(fmt, params);
exit(status);
}
// variadic
void warn(const char *fmt, ...) {
va_list params;
va_start(params, fmt);
vwarn(fmt, params);
va_end(params);
}
void warnx(const char *fmt, ...) {
va_list params;
va_start(params, fmt);
vwarnx(fmt, params);
va_end(params);
}
__attribute__((__noreturn__)) void err(int status, const char *fmt, ...) {
va_list params;
va_start(params, fmt);
verr(status, fmt, params);
va_end(params);
}
__attribute__((__noreturn__)) void errx(int status, const char *fmt, ...) {
va_list params;
va_start(params, fmt);
verrx(status, fmt, params);
va_end(params);
}
@@ -0,0 +1,65 @@
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <error.h>
unsigned int error_message_count = 0;
int error_one_per_line = 0;
void (*error_print_progname)(void) = nullptr;
void error(int status, int errnum, const char *format, ...) {
va_list args;
va_start(args, format);
error_message_count++;
fflush(stdout);
if(error_print_progname) {
error_print_progname();
} else {
fprintf(stderr, "%s: ", program_invocation_name);
}
vfprintf(stderr, format, args);
va_end(args);
if(errnum) {
fprintf(stderr, ": %s\n", strerror(errnum));
}
if(status) {
exit(status);
}
}
void error_at_line(int status, int errnum, const char *filename, unsigned int linenum, const char *format, ...) {
va_list args;
va_start(args, format);
static bool first_call = true;
static unsigned int last_line = 0;
if(!(last_line == linenum && error_one_per_line && !first_call)) {
first_call = false;
last_line = linenum;
error_message_count++;
fflush(stdout);
if(error_print_progname) {
error_print_progname();
} else {
fprintf(stderr, "%s:", program_invocation_name);
}
fprintf(stderr, "%s:%u: ", filename, linenum);
vfprintf(stderr, format, args);
if(errnum) {
fprintf(stderr, ": %s\n", strerror(errnum));
}
}
va_end(args);
if(status) {
exit(status);
}
}
@@ -0,0 +1,105 @@
#include <dlfcn.h>
#include <execinfo.h>
#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include <unwind.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
namespace {
using UnwindBacktrace = _Unwind_Reason_Code (*)(_Unwind_Trace_Fn, void *);
using UnwindGetIP = _Unwind_Ptr (*)(_Unwind_Context *);
frg::optional<void *> libgccHandle = frg::null_opt;
UnwindBacktrace unwindBacktrace = nullptr;
UnwindGetIP unwindGetIP = nullptr;
struct UnwindState {
void **frames;
int count;
int current_frame = 0;
};
_Unwind_Reason_Code trace(_Unwind_Context *context, void *arg) {
UnwindState *state = static_cast<UnwindState *>(arg);
if (state->current_frame >= state->count)
return _URC_END_OF_STACK;
uintptr_t ip = unwindGetIP(context);
if (ip) {
#if defined(__x86_64__) || defined(__i386__)
ip--;
#elif defined(__aarch64__) || defined(__loongarch64)
ip -= 4;
#elif defined(__riscv) || defined(__m68k__)
ip -= 2;
#else
#warning "Missing support for architecture"
ip--;
#endif
}
state->frames[state->current_frame++] = reinterpret_cast<void *>(ip);
return _URC_NO_REASON;
}
} // namespace
int backtrace(void **buffer, int size) {
if (size <= 0)
return 0;
if (!libgccHandle) {
libgccHandle = dlopen("libgcc_s.so.1", RTLD_LAZY | RTLD_LOCAL);
if (!libgccHandle || libgccHandle.value() == nullptr) {
mlibc::infoLogger() << "Failed to load libgcc_s.so.1: " << (dlerror() ? dlerror() : "") << frg::endlog;
return 0;
}
unwindBacktrace = reinterpret_cast<UnwindBacktrace>(dlsym(libgccHandle.value(), "_Unwind_Backtrace"));
unwindGetIP = reinterpret_cast<UnwindGetIP>(dlsym(libgccHandle.value(), "_Unwind_GetIP"));
if (!unwindBacktrace || !unwindGetIP) {
mlibc::infoLogger() << "Failed to find unwind functions in libgcc_s.so.1: " << dlerror() << frg::endlog;
return 0;
}
}
UnwindState state{buffer, size};
unwindBacktrace(trace, &state);
return state.current_frame;
}
char **backtrace_symbols(void *const *, int) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
void backtrace_symbols_fd(void *const *buffer, int size, int fd) {
if (size <= 0 || fd < 0)
return;
for (int frame_num = 0; frame_num < size; frame_num++) {
Dl_info info;
if (dladdr(buffer[frame_num], &info) != 0) {
if (info.dli_fname != nullptr)
write(fd, info.dli_fname, strlen(info.dli_fname));
if (info.dli_sname != nullptr)
dprintf(fd, "(%s+0x%" PRIxPTR ") ", info.dli_sname,
reinterpret_cast<uintptr_t>(buffer[frame_num]) - reinterpret_cast<uintptr_t>(info.dli_saddr));
else if(info.dli_saddr)
dprintf(fd, "(+%p) ", info.dli_saddr);
else
dprintf(fd, "() ");
}
dprintf(fd, "[%p]\n", buffer[frame_num]);
}
}
@@ -0,0 +1,12 @@
#include <getopt.h>
#include <mlibc/getopt.hpp>
int getopt_long(int argc, char * const argv[], const char *optstring,
const struct option *longopts, int *longindex) {
return getopt_common(argc, argv, optstring, longopts, longindex, mlibc::GetoptMode::Long);
}
int getopt_long_only(int argc, char * const argv[], const char *optstring,
const struct option *longopts, int *longindex) {
return getopt_common(argc, argv, optstring, longopts, longindex, mlibc::GetoptMode::LongOnly);
}
@@ -0,0 +1,14 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bits/ensure.h>
[[gnu::noreturn]] void __assert_fail_perror(int errno, const char *file, unsigned int line,
const char *function) {
char *errormsg = strerror(errno);
fprintf(stderr, "In function %s, file %s:%d: Errno '%s' failed!\n",
function, file, line, errormsg);
abort();
}
@@ -0,0 +1,14 @@
#include <errno.h>
#include <signal.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
#include <mlibc/glibc-sysdeps.hpp>
int tgkill(int tgid, int tid, int sig) {
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_tgkill, -1);
if(int e = mlibc::sys_tgkill(tgid, tid, sig); e) {
errno = e;
return -1;
}
return 0;
}
@@ -0,0 +1,7 @@
#include <gshadow.h>
#include <bits/ensure.h>
int getsgnam_r(const char *, struct sgrp *, char *, size_t, struct sgrp **) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
@@ -0,0 +1,6 @@
#include <bits/glibc/glibc_malloc.h>
#include <mlibc/allocator.hpp>
size_t malloc_usable_size(void *p) {
return getAllocator().get_size(p);
}
@@ -0,0 +1,15 @@
#include <bits/ensure.h>
#include <errno.h>
#include <mlibc/glibc-sysdeps.hpp>
#include <sys/personality.h>
int personality(unsigned long persona) {
int out = 0;
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_personality, -1);
if(int e = sysdep(persona, &out); e) {
errno = e;
return -1;
}
return out;
}
@@ -0,0 +1,7 @@
#include <bits/ensure.h>
#include <printf.h>
size_t parse_printf_format(const char * __restrict, size_t, int * __restrict) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
@@ -0,0 +1,41 @@
#include <resolv.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
int dn_expand(const unsigned char *, const unsigned char *,
const unsigned char *, char *, int) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int res_query(const char *, int, int, unsigned char *, int) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int res_init() {
mlibc::infoLogger() << "mlibc: res_init is a stub!" << frg::endlog;
return 0;
}
int res_ninit(res_state) {
mlibc::infoLogger() << "mlibc: res_ninit is a stub!" << frg::endlog;
return 0;
}
void res_nclose(res_state) {
mlibc::infoLogger() << "mlibc: res_nclose is a stub!" << frg::endlog;
return;
}
int dn_comp(const char *, unsigned char *, int, unsigned char **, unsigned char **) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
/* This is completely unused, and exists purely to satisfy broken apps. */
struct __res_state *__res_state() {
static struct __res_state res;
return &res;
}
@@ -0,0 +1,14 @@
#include <bits/glibc/glibc_search.h>
#include <mlibc/search.hpp>
int hcreate_r(size_t num_entries, hsearch_data *htab) {
return mlibc::hcreate_r(num_entries, htab);
}
void hdestroy_r(hsearch_data *htab) {
mlibc::hdestroy_r(htab);
}
int hsearch_r(ENTRY item, ACTION action, ENTRY **ret, hsearch_data *htab) {
return mlibc::hsearch_r(item, action, ret, htab);
}
@@ -0,0 +1,231 @@
#include <shadow.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
/*
* The code in this file is largely based on or taken from musl.
* This includes:
* - xatol
* - __parsespent
* - cleanup
* - getspnam_r
* - getspnam
*/
#define NUM(n) ((n) == -1 ? 0 : -1), ((n) == -1 ? 0 : (n))
int putspent(const struct spwd *sp, FILE *f) {
auto str = [] (char *s) {
return ((s) ? (s) : "");
};
return fprintf(f, "%s:%s:%.*ld:%.*ld:%.*ld:%.*ld:%.*ld:%.*ld:%.*u\n",
str(sp->sp_namp), str(sp->sp_pwdp), NUM(sp->sp_lstchg),
NUM(sp->sp_min), NUM(sp->sp_max), NUM(sp->sp_warn),
NUM(sp->sp_inact), NUM(sp->sp_expire), NUM((int)sp->sp_flag)) < 0 ? -1 : 0;
}
#undef NUM
static long xatol(char **s) {
long x;
if(**s == ':' || **s == '\n') {
return -1;
}
for(x = 0; (unsigned int)**s - '0' < 10U; ++*s) {
x = 10 * x + (**s - '0');
}
return x;
}
static int __parsespent(char *s, struct spwd *sp) {
sp->sp_namp = s;
if(!(s = strchr(s, ':'))) {
return -1;
}
*s = 0;
sp->sp_pwdp = ++s;
if(!(s = strchr(s, ':'))) {
return -1;
}
*s = 0;
s++;
sp->sp_lstchg = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_min = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_max = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_warn = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_inact = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_expire = xatol(&s);
if(*s != ':') {
return -1;
}
s++;
sp->sp_flag = xatol(&s);
if(*s != '\n') {
return -1;
}
return 0;
}
static void cleanup(void *p) {
fclose((FILE *)p);
}
int getspnam_r(const char *name, struct spwd *sp, char *buf, size_t size, struct spwd **res) {
char path[20 + NAME_MAX];
FILE *f = nullptr;
int rv = 0;
int fd;
size_t k, l = strlen(name);
int skip = 0;
int cs;
int orig_errno = errno;
*res = nullptr;
/* Disallow potentially-malicious user names */
if(*name=='.' || strchr(name, '/') || !l) {
return errno = EINVAL;
}
/* Buffer size must at least be able to hold name, plus some.. */
if(size < l + 100) {
return errno = ERANGE;
}
/* Protect against truncation */
if(snprintf(path, sizeof path, "/etc/tcb/%s/shadow", name) >= (int)sizeof path) {
return errno = EINVAL;
}
fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC);
if(fd >= 0) {
struct stat st = {};
errno = EINVAL;
if(fstat(fd, &st) || !S_ISREG(st.st_mode) || !(f = fdopen(fd, "rb"))) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
close(fd);
pthread_setcancelstate(cs, nullptr);
return errno;
}
} else {
if(errno != ENOENT && errno != ENOTDIR) {
return errno;
}
f = fopen("/etc/shadow", "rbe");
if(!f) {
if(errno != ENOENT && errno != ENOTDIR) {
return errno;
}
return 0;
}
}
pthread_cleanup_push(cleanup, f);
while(fgets(buf, size, f) && (k = strlen(buf)) > 0) {
if(skip || strncmp(name, buf, l) || buf[l] != ':') {
skip = buf[k - 1] != '\n';
continue;
}
if(buf[k - 1] != '\n') {
rv = ERANGE;
break;
}
if(__parsespent(buf, sp) < 0) {
continue;
}
*res = sp;
break;
}
pthread_cleanup_pop(1);
errno = rv ? rv : orig_errno;
return rv;
}
int lckpwdf(void) {
mlibc::infoLogger() << "mlibc: lckpwdf is unimplemented like musl" << frg::endlog;
return 0;
}
int ulckpwdf(void) {
mlibc::infoLogger() << "mlibc: ulckpwdf is unimplemented like musl" << frg::endlog;
return 0;
}
// Musl defines LINE_LIM to 256
#define LINE_LIM 256
struct spwd *getspnam(const char *name) {
static struct spwd sp;
static char *line;
struct spwd *res;
int e;
int orig_errno = errno;
if(!line) {
line = (char *)malloc(LINE_LIM);
}
if(!line) {
return nullptr;
}
e = getspnam_r(name, &sp, line, LINE_LIM, &res);
errno = e ? e : orig_errno;
return res;
}
struct spwd *fgetspent(FILE *f) {
static struct spwd sp;
static char *line;
struct spwd *res = nullptr;
size_t size = 0;
int cs;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
if(getline(&line, &size, f) >= 0 && __parsespent(line, &sp) >= 0) {
res = &sp;
}
pthread_setcancelstate(cs, nullptr);
return res;
}
void endspent(void) {
mlibc::infoLogger() << "mlibc: endspent is a stub" << frg::endlog;
}
struct spwd *sgetspent(const char *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
@@ -0,0 +1,84 @@
#include <mlibc/file-io.hpp>
#include <stdio_ext.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
size_t __fbufsize(FILE *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
size_t __fpending(FILE *file_base) {
__ensure(file_base->__dirty_end >= file_base->__dirty_begin);
return file_base->__dirty_end - file_base->__dirty_begin;
}
int __flbf(FILE *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int __freadable(FILE *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int __fwritable(FILE *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int __freading(FILE *file_base) {
return file_base->__io_mode == 0;
}
int __fwriting(FILE *file_base) {
return file_base->__io_mode == 1;
}
int __fsetlocking(FILE *file_base, int state) {
auto file = static_cast<mlibc::abstract_file *>(file_base);
bool oldstate = file->_lock.uselock;
if (state != FSETLOCKING_QUERY) {
if (state == FSETLOCKING_BYCALLER) {
file->_lock.uselock = false;
} else {
file->_lock.uselock = true;
}
}
if (oldstate) {
return FSETLOCKING_INTERNAL;
} else {
return FSETLOCKING_BYCALLER;
}
}
void _flushlbf(void) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
// The following functions are defined by musl.
size_t __freadahead(FILE *file_base) {
if(file_base->__io_mode != 0) {
mlibc::infoLogger() << "mlibc: __freadahead() called but file is not open for reading" << frg::endlog;
return 0;
}
return file_base->__valid_limit - file_base->__offset;
}
const char *__freadptr(FILE *, size_t *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
void __freadptrinc(FILE *, size_t) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
void __fseterr(FILE *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
@@ -0,0 +1,11 @@
#include <stdlib.h>
int rpmatch(const char *resp) {
if(!resp || resp[0] == '\0')
return -1;
if(resp[0] == 'y' || resp[0] == 'Y')
return 1;
if(resp[0] == 'n' || resp[0] == 'N')
return 0;
return -1;
}
@@ -0,0 +1,32 @@
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <type_traits>
#include <string.h>
/* This is a bit of a weird detail of the GNU implementation and C's lack of
* overloading and strictness: GNU takes const char * and returns a char * so
* that it autocasts to your desired constness, this function never actually
* modifies the string.
*/
char *__mlibc_gnu_basename_c(const char *path) {
char *basename_component = strrchr(path, '/');
if (!basename_component) {
return const_cast<char *>(path);
}
return basename_component + 1;
}
/* GNU exposes these overloads, and as a result, we should probably have them
* checked, to make sure we actually match expectations.
*/
static_assert(
std::is_same_v<decltype(basename((const char *)nullptr)), const char*>,
"C++ overloads broken"
);
static_assert(
std::is_same_v<decltype(basename((char *)nullptr)), char*>,
"C++ overloads broken"
);
@@ -0,0 +1,16 @@
#include <bits/ensure.h>
#include <errno.h>
#include <mlibc/glibc-sysdeps.hpp>
#include <sys/cachectl.h>
#ifdef __riscv
int __riscv_flush_icache(void *start, void *end, unsigned long flags) {
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_riscv_flush_icache, -1);
if(int e = sysdep(start, end, flags); e) {
errno = e;
return -1;
}
return 0;
}
#endif
@@ -0,0 +1,25 @@
#include <errno.h>
#include <sys/io.h>
#include <bits/ensure.h>
#include <mlibc/glibc-sysdeps.hpp>
int ioperm(unsigned long int from, unsigned long int num, int turn_on) {
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_ioperm, -1);
if(int e = sysdep(from, num, turn_on); e) {
errno = e;
return -1;
}
return 0;
}
int iopl(int level) {
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_iopl, -1);
if(int e = sysdep(level); e) {
errno = e;
return -1;
}
return 0;
}
@@ -0,0 +1,21 @@
#include <errno.h>
#include <sys/ioctl.h>
#include <bits/ensure.h>
#include <mlibc/debug.hpp>
#include <mlibc/glibc-sysdeps.hpp>
int ioctl(int fd, unsigned long request, ...) {
va_list args;
va_start(args, request);
int result;
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_ioctl, -1);
void *arg = va_arg(args, void *);
if(int e = mlibc::sys_ioctl(fd, request, arg, &result); e) {
errno = e;
return -1;
}
return result;
}
@@ -0,0 +1,17 @@
#include <bits/ensure.h>
#include <sys/timex.h>
int adjtimex(struct timex *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int clock_adjtime(clockid_t, struct timex *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int ntp_adjtime(struct timex *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}