diff --git a/Makefile b/Makefile index e1290f72b..9934a6451 100644 --- a/Makefile +++ b/Makefile @@ -849,21 +849,61 @@ rt-darwin-x86_64: embcc embar # EmbLinkOS is a backend, not a second library: same sources, one different # file at the bottom. It needs that OS's ABI headers, so it is opt-in. # make libc-emblinkos EMBLINKOS=$HOME/EmbLinkOs +# make libc-emblinkos EMBLINKOS=... EMBLINKOS_ARCH=aarch64 +# x86_64's goes to build/libc/emblinkos, aarch64's to emblinkos-aarch64. EMBLINKOS ?= $(HOME)/EmbLinkOs +EMBLINKOS_ARCH ?= x86_64 +ELOS_SUFFIX = $(if $(filter aarch64,$(EMBLINKOS_ARCH)),-aarch64,) +ELOS_LIBC = $(BUILD)/libc/emblinkos$(ELOS_SUFFIX) +ELOS_LIBCXX = $(BUILD)/libcxx/emblinkos$(ELOS_SUFFIX) +# --unwind-tables: every function gets its .eh_frame, as GCC gives every +# x86-64 function by default. The unwinder needs its own frames' (without +# them a throw's walk ends where it starts, in _Unwind_RaiseException), and a +# throw from a C++ callback passes through C frames -- qsort's, bsearch's. +ELOS_CC = ./embcc --target=$(EMBLINKOS_ARCH)-elf --unwind-tables libc-emblinkos: embcc embar @[ -f "$(EMBLINKOS)/user/lib/embk.h" ] || { \ echo "libc-emblinkos: no $(EMBLINKOS)/user/lib/embk.h"; \ echo " set EMBLINKOS=/path/to/EmbLinkOs"; exit 1; } - @mkdir -p $(BUILD)/libc/emblinkos - @for f in $(LIBC_SRCS_PORTABLE) lib/libc/os/emblinkos/backend.c; do \ - o=$(BUILD)/libc/emblinkos/$$(echo $$f | tr / _ | sed 's/\.c$$/.o/'); \ - ./embcc -c -O2 $(LIBC_INC) -I$(EMBLINKOS)/user/lib $$f -o $$o || exit 1; \ + @case "$(EMBLINKOS_ARCH)" in x86_64|aarch64) ;; *) \ + echo "libc-emblinkos: EMBLINKOS_ARCH is x86_64 or aarch64"; exit 1;; esac + @mkdir -p $(ELOS_LIBC) + @for f in $(LIBC_SRCS_PORTABLE) lib/libc/os/posix-names.c \ + lib/libc/os/emblinkos/backend.c; do \ + o=$(ELOS_LIBC)/$$(echo $$f | tr / _ | sed 's/\.c$$/.o/'); \ + $(ELOS_CC) -c -O2 $(LIBC_INC) -I$(EMBLINKOS)/user/lib $$f -o $$o || exit 1; \ done - @rm -f $(BUILD)/libc/emblinkos/libc.a - @$${EMBCC_X86_AR:-./embar} rcs $(BUILD)/libc/emblinkos/libc.a \ - $(BUILD)/libc/emblinkos/*.o - @echo "libc: $(BUILD)/libc/emblinkos/libc.a" + @rm -f $(ELOS_LIBC)/libc.a $(ELOS_LIBC)/libm.a + @$${EMBCC_X86_AR:-./embar} rcs $(ELOS_LIBC)/libc.a $(ELOS_LIBC)/*.o + @# libm.a holds nothing: the math is in libc.a, as glibc's is, and an + @# empty one beside it keeps a program's -lm from finding another libc's + @printf '' > $(ELOS_LIBC)/libm-empty.c + @$(ELOS_CC) -c $(ELOS_LIBC)/libm-empty.c -o $(ELOS_LIBC)/libm-empty.o.x + @$${EMBCC_X86_AR:-./embar} rcs $(ELOS_LIBC)/libm.a $(ELOS_LIBC)/libm-empty.o.x + @# librt.a: the compiler runtime, with the unwinder C++'s exceptions + @# need -- its programs have no other (lib/rt/unwind.c says why) + @for f in $(RT_SRCS); do \ + o=$(ELOS_LIBC)/rt_$$(basename $$f .c).o.x; \ + $(ELOS_CC) -c -O2 -D__EMBCC_RT_UNWIND $$f -o $$o || exit 1; \ + done + @rm -f $(ELOS_LIBC)/librt.a + @$${EMBCC_X86_AR:-./embar} rcs $(ELOS_LIBC)/librt.a $(ELOS_LIBC)/rt_*.o.x + @echo "libc: $(ELOS_LIBC)/libc.a (+ an empty libm.a, and librt.a)" + +# C++ on EmbLinkOS: lib/libcxx over this C library, compiled by EmbCC. +# make libcxx-emblinkos EMBLINKOS=$HOME/EmbLinkOs [EMBLINKOS_ARCH=aarch64] +libcxx-emblinkos: embcc embar + @[ -f "$(EMBLINKOS)/user/lib/embk.h" ] || { \ + echo "libcxx-emblinkos: no $(EMBLINKOS)/user/lib/embk.h"; exit 1; } + @mkdir -p $(ELOS_LIBCXX) + @for f in $(LIBCXX_SRCS); do \ + o=$(ELOS_LIBCXX)/$$(basename $$f .cc).o; \ + $(ELOS_CC) -c -O2 -x c++ $(LIBCXX_INC) -I$(EMBLINKOS)/user/lib $$f -o $$o || exit 1; \ + done + @rm -f $(ELOS_LIBCXX)/libcxx.a + @$${EMBCC_X86_AR:-./embar} rcs $(ELOS_LIBCXX)/libcxx.a $(ELOS_LIBCXX)/*.o + @echo "libcxx: $(ELOS_LIBCXX)/libcxx.a" # Linux, which is the first target this library reaches with NO other C # library underneath it: lib/libc/os/linux/backend.c issues syscalls, so @@ -875,7 +915,9 @@ libc-emblinkos: embcc embar # exactly once in an image, and a definition that arrives by archive # member is a definition that arrives by accident. # make libc-linux-x86_64 / make libc-linux-aarch64 -LIBC_SRCS_LINUX := $(LIBC_SRCS_PORTABLE) lib/libc/os/linux/backend.c \ +LIBC_SRCS_LINUX := $(LIBC_SRCS_PORTABLE) lib/libc/os/posix-names.c \ + lib/libc/os/linux/backend.c \ + lib/libc/os/linux/posix.c \ lib/libc/os/linux/thread.c lib/libc/os/linux/tls.c # lib/rt -- the COMPILER runtime, which is a different library from the C @@ -1230,7 +1272,7 @@ distclean: clean rm -f config.mk .PHONY: all check test test-arm64 test-libstdcxx site libc libc-x86_64 libc-aarch64 \ - libc-emblinkos libc-linux libc-linux-x86_64 libc-linux-aarch64 \ + libc-emblinkos libcxx-emblinkos libc-linux libc-linux-x86_64 libc-linux-aarch64 \ libcxx libcxx-x86_64 libcxx-aarch64 \ libcxx-linux-x86_64 libcxx-linux-aarch64 libcxx-embedded \ install install-files uninstall libc-linux-all clean distclean diff --git a/build.ebm b/build.ebm index 15ce0dc60..3588c6d37 100644 --- a/build.ebm +++ b/build.ebm @@ -13,7 +13,7 @@ output: /data/build/out/embcc/platform_platform_common.o name: platform_platform_posix.o kind: compile -inputs: /data/src/embcc/platform/platform_posix.c /data/src/embcc/platform/platform.h /data/apps/embcc/include/stddef.h /system/abi/include/stdio.h /system/abi/include/_ansi.h /system/abi/include/newlib.h /system/abi/include/_newlib_version.h /system/abi/include/sys/config.h /system/abi/include/machine/ieeefp.h /system/abi/include/sys/features.h /system/abi/include/sys/cdefs.h /system/abi/include/machine/_default_types.h /data/apps/embcc/include/stdarg.h /system/abi/include/sys/reent.h /system/abi/include/sys/_types.h /system/abi/include/machine/_types.h /system/abi/include/sys/lock.h /system/abi/include/sys/stdio.h /system/abi/include/stdlib.h /system/abi/include/machine/stdlib.h /system/abi/include/alloca.h /data/apps/embcc/include/string.h /data/src/embcc/driver/util.h /system/abi/include/dirent.h /system/abi/include/sys/dirent.h /system/abi/include/unistd.h /system/abi/include/sys/unistd.h /system/abi/include/sys/types.h /system/abi/include/sys/_stdint.h /system/abi/include/sys/_pthreadtypes.h /system/abi/include/sys/sched.h /system/abi/include/sys/_timespec.h /system/abi/include/machine/types.h /system/abi/include/pthread.h +inputs: /data/src/embcc/platform/platform_posix.c /data/src/embcc/platform/platform.h /data/apps/embcc/include/stddef.h /system/abi/include/stdio.h /system/abi/include/_ansi.h /system/abi/include/newlib.h /system/abi/include/_newlib_version.h /system/abi/include/sys/config.h /system/abi/include/machine/ieeefp.h /system/abi/include/sys/features.h /system/abi/include/sys/cdefs.h /system/abi/include/machine/_default_types.h /data/apps/embcc/include/stdarg.h /system/abi/include/sys/reent.h /system/abi/include/sys/_types.h /system/abi/include/machine/_types.h /system/abi/include/sys/lock.h /system/abi/include/sys/stdio.h /system/abi/include/stdlib.h /system/abi/include/machine/stdlib.h /system/abi/include/alloca.h /data/apps/embcc/include/string.h /data/src/embcc/driver/util.h /system/abi/include/dirent.h /system/abi/include/sys/dirent.h /system/abi/include/unistd.h /system/abi/include/sys/unistd.h /system/abi/include/sys/types.h /system/abi/include/sys/_stdint.h /system/abi/include/sys/_pthreadtypes.h /system/abi/include/sys/sched.h /system/abi/include/sys/_timespec.h /system/abi/include/machine/types.h /system/abi/include/sys/stat.h /system/abi/include/time.h /system/abi/include/machine/time.h /system/abi/include/sys/timespec.h /system/abi/include/sys/_locale.h /system/abi/include/pthread.h args: /data/apps/embcc/embcc.elf -c -I/data/apps/embcc/include -I/system/abi/include /data/src/embcc/platform/platform_posix.c -o /data/build/out/embcc/platform_platform_posix.o output: /data/build/out/embcc/platform_platform_posix.o diff --git a/docs/manual/libraries.md b/docs/manual/libraries.md index 66cb07d8e..36ed883fd 100644 --- a/docs/manual/libraries.md +++ b/docs/manual/libraries.md @@ -15,8 +15,8 @@ program can rely on. Firmware-specific use of the runtime is in | Library | Source | Archive | Built for | Make target | |---|---|---|---|---| | C library | `lib/libc` | `libc.a` (+ `crt1.o` on Linux) | `x86_64-elf`, `aarch64-elf`, `x86_64-linux-gnu`, `aarch64-linux-gnu`, EmbLinkOS | `libc-x86_64`, `libc-aarch64`, `libc-linux-x86_64`, `libc-linux-aarch64`, `libc-emblinkos` | -| C++ runtime and standard library | `lib/libcxx` | `libcxx.a` | `x86_64-elf`, `aarch64-elf`, `x86_64-linux-gnu`, `aarch64-linux-gnu`; the runtime alone (no exceptions) for 32-bit ARM and `riscv32-unknown-elf` | `libcxx-x86_64`, `libcxx-aarch64`, `libcxx-linux-x86_64`, `libcxx-linux-aarch64`, `libcxx-embedded` | -| Compiler runtime | `lib/rt` | `librt.a` | `x86_64-linux-gnu`, `aarch64-linux-gnu`, and the embedded triples | `libc-linux-*` (Linux), `rt-embedded` (embedded) | +| C++ runtime and standard library | `lib/libcxx` | `libcxx.a` | `x86_64-elf`, `aarch64-elf`, `x86_64-linux-gnu`, `aarch64-linux-gnu`, EmbLinkOS; the runtime alone (no exceptions) for 32-bit ARM and `riscv32-unknown-elf` | `libcxx-x86_64`, `libcxx-aarch64`, `libcxx-linux-x86_64`, `libcxx-linux-aarch64`, `libcxx-emblinkos`, `libcxx-embedded` | +| Compiler runtime | `lib/rt` | `librt.a` | `x86_64-linux-gnu`, `aarch64-linux-gnu`, EmbLinkOS, and the embedded triples | `libc-linux-*` (Linux), `libc-emblinkos`, `rt-embedded` (embedded) | Every library is built per target by the `embcc` in the same tree, so it always matches the compiler's ABI. No library exists for the macOS or @@ -154,6 +154,30 @@ system is reached through one small backend file (see `` `` `` `` `` `` `` `` `` +and, of POSIX's and the common Unix ones, `` `` +`` `` `` `` `` `` `` `` `` +`` `` `` `` `` `` +`` `` `` `` `` `` `` +`` `` +`` `` `` `` `` `` and +`` `` `` `` +`` `` `` `` +`` `` `` `` +`` `` `` `` +`` ``. Where a C header also +declares POSIX names (`getline` in ``, `strtok_r` in +``), `` decides: they are declared unless the +program asked for ISO C alone (`-std=c11` with no `_POSIX_C_SOURCE`, +`_XOPEN_SOURCE`, `_DEFAULT_SOURCE`, `_BSD_SOURCE` or `_GNU_SOURCE`), and +the GNU and BSD ones (`strchrnul`, `memrchr`, `clearenv`, `fopencookie`) +are hidden too when the program names a POSIX version and nothing more, +as with glibc. In the default mode `` also brings +``, and `` ``, as glibc's do. + +Every POSIX function is defined weak, so a program -- or an operating +system's own support library linked beside this one -- may define any of +them itself and its definition is the one used. C's names are strong. + ``, ``, ``, `` and `` come from the freestanding directory, and so do C23's `` and ``, which a program with no C library has too. `` defines the library's @@ -162,19 +186,35 @@ freestanding `` for the type widths. | Area | What is provided | |---|---| -| `` | all of C11 §7.24, plus `strdup`, `strndup`, `strnlen`, `stpcpy`, `stpncpy` and `mempcpy` | +| `` | all of C11 §7.24, plus `strdup`, `strndup`, `strnlen`, `stpcpy`, `stpncpy` and `mempcpy`; POSIX's `strtok_r`, `strerror_r` (XSI's, returning an error number), `strsep`, `strlcpy`, `strlcat`, `memmem`; and `strcasestr`, `memrchr`, `strchrnul`, `explicit_bzero`. Each beyond C is weak, as in `` | | `` | `bzero`, `bcopy`, `bcmp`, `index`, `rindex`, `strcasecmp`, `strncasecmp`, `ffs`. Each is defined weak, so a program's own `index` or `bcopy` takes its place | | ``, `` | complete, for the `"C"` locale; the classification functions return 0 or 1 | -| `` | `malloc` family with `aligned_alloc`; `strtol` family, `strtod`/`strtof`/`strtold`, `ato*`; `qsort` (heapsort) and `bsearch`; `rand`/`srand` (`RAND_MAX` is 2147483647); `abs`/`div` families; `atexit`, `exit`, `_Exit`, `abort`, `at_quick_exit`, `quick_exit`; the multibyte conversions without a state (`mblen`, `mbtowc`, `wctomb`, `mbstowcs`, `wcstombs`), in UTF-8; `getenv`; `system` (always reports no command processor) | -| `` | `FILE` streams with buffering, files by name, positioning (`fseek`, `ftell`, `fgetpos`, `fsetpos`, `rewind`), `remove`, `rename`, `tmpfile`, `tmpnam`, C11's exclusive `"x"` open mode, `setvbuf`, `ungetc`, `perror`, and the complete `printf` and `scanf` families (`v`, `f`, `s`, `sn` forms) | +| `` | `malloc` family with `aligned_alloc`; `strtol` family, `strtod`/`strtof`/`strtold`, `ato*`; `qsort` (heapsort) and `bsearch`; `rand`/`srand` (`RAND_MAX` is 2147483647); `abs`/`div` families; `atexit`, `exit`, `_Exit`, `abort`, `at_quick_exit`, `quick_exit`; the multibyte conversions without a state (`mblen`, `mbtowc`, `wctomb`, `mbstowcs`, `wcstombs`), in UTF-8; `getenv`; `system` (always reports no command processor). POSIX's: `setenv`, `unsetenv`, `putenv`, `clearenv`; `mkstemp`, `mkostemp`, `mkstemps`, `mkostemps`, `mkdtemp`, `mktemp`; `realpath`; `posix_memalign`, `memalign`, `reallocarray`; `qsort_r` (POSIX.1-2024's argument order, glibc's); `getsubopt`; `random`, `srandom`, `initstate`, `setstate`, the `*rand48` family and `rand_r`, each giving glibc's sequence for a seed | +| `` | `FILE` streams with buffering, files by name, positioning (`fseek`, `ftell`, `fgetpos`, `fsetpos`, `rewind`), `remove`, `rename`, `tmpfile`, `tmpnam`, C11's exclusive `"x"` open mode, `setvbuf`, `ungetc`, `perror`, and the complete `printf` and `scanf` families (`v`, `f`, `s`, `sn` forms). POSIX's: `fdopen`, `fileno`, `fmemopen`, `open_memstream`, `getline`, `getdelim`, `dprintf`, `asprintf` and their `v` forms, `fseeko`, `ftello`, `flockfile`, `ftrylockfile`, `funlockfile`, the `_unlocked` functions, `ctermid`; and GNU's `fopencookie`, `setbuffer`, `setlinebuf`. More streams may be open at once than `FOPEN_MAX`, which is C's minimum and not a limit here | +| POSIX modules | `basename` and `dirname` (``, the POSIX ones, which may write into their argument); `fnmatch`, with glibc's flags including `FNM_CASEFOLD` and `FNM_LEADING_DIR`; `getopt`, `getopt_long` and `getopt_long_only`, which permute as glibc's and newlib's do (an option string starting with `+`, or `POSIXLY_CORRECT`, stops at the first operand); `` complete, `tsearch` an AVL tree, `hsearch` growing past its `hcreate` size, and GNU's `hsearch_r` family and `tdestroy`; ``'s `err`, `warn` and their `x` and `v` forms, which begin with `program_invocation_short_name` where startup has set it (Linux's does) | | ``, `` | every C11 function at all three widths, `erf`, `erfc`, `lgamma` and `tgamma` FreeBSD's (accurate to an ulp or two; `lgamma` of a negative argument beside a zero of Γ loses digits, ~100 ulp at -2.75, as FreeBSD's does); C23's `sinpi`, `cospi`, `tanpi`, `asinpi`, `acospi`, `atanpi` and `atan2pi`; `signgam`, `lgamma_r`; `float_t`, `double_t`, `FP_ILOGB0` and `FP_ILOGBNAN`, and POSIX's `M_PI` family outside strict ISO mode; `` chooses the width from every generic argument, as C11 7.25 says (`pow(2.0f, 3.0L)` is `powl`) | | `` | complete: every function at all three widths | | `` | exception flags and rounding modes, on x86-64 (`MXCSR`) and AArch64 (`FPSR`/`FPCR`) | -| `` | `time`, `clock`, `difftime`, `mktime`, `timegm`, `gmtime`, `localtime`, the `_r` forms, `asctime`, `ctime`, `strftime`, `timespec_get` | +| `` | `time`, `clock`, `difftime`, `mktime`, `timegm`, `gmtime`, `localtime`, the `_r` forms, `asctime`, `ctime`, `strftime`, `timespec_get`. POSIX's: `tzset` with `tzname`, `timezone` and `daylight`; `strptime`; `tm_gmtoff` and `tm_zone` in `struct tm`; `clock_gettime`, `clock_getres`, `clock_settime`, `clock_nanosleep`, `nanosleep`; ``'s `gettimeofday`, `settimeofday`, `setitimer`, `getitimer`, `utimes`; `times` | | `` | `setjmp`/`longjmp` on every target: x86-64, AArch64, ARM state, Thumb (every Cortex-M, with s16-s31 where there is an FPU), RISC-V (with fs0-fs11 under F or D), MIPS o32 and n64, LoongArch64, PowerPC, SPARC, Xtensa, TriCore, ColdFire, RX, and on AVR in `lib/avr`. `jmp_buf` is 256 bytes everywhere but AVR, where it is 24 | -| `` | `signal` and `raise` | +| `` | `signal` and `raise`; POSIX's `sigaction` (with `SA_SIGINFO`, `SA_RESETHAND`, `SA_NODEFER`, `SA_RESTART`), `sigprocmask`, `pthread_sigmask`, `sigpending`, `sigsuspend`, `sigwait`, `sigwaitinfo`, `sigtimedwait`, `sigaltstack`, `kill`, `killpg`, `pthread_kill`, the `sigset_t` functions and GNU's `sigisemptyset`, `sigorset`, `sigandset`; `strsignal` (glibc's words), `psignal`, `psiginfo`, `siginterrupt`. See [Signals](#behavior-worth-knowing) for who delivers them | +| ``, ``, `` | descriptors: `open`, `openat`, `creat`, `close`, `read`, `write`, `pread`, `pwrite`, `readv`, `writev`, `preadv`, `pwritev`, `lseek`, `dup`, `dup2`, `dup3`, `pipe`, `pipe2`, `fcntl` (descriptor and status flags, record locks), `flock`, `lockf`, `fsync`, `fdatasync`, `ftruncate`, `sync`, `isatty`, `ioctl`, `posix_fadvise`, `getentropy`, `getrandom` | +| ``, ``, `` | files: `stat`, `lstat`, `fstat`, `access`, `unlink`, `rmdir`, `link`, `symlink`, `readlink`, `truncate`, `mkdir`, `chdir`, `fchdir`, `getcwd`, `chmod`, `fchmod`, `umask`, `chown`, `lchown`, `fchown`, `mknod`, `mkfifo`, `futimens`, `utimensat`, `utime`, `utimes`, `lutimes`, `futimes`, `statvfs`, `fstatvfs`, `pathconf`, `fpathconf`, `chroot`, and every `*at` form (`fstatat`, `faccessat`, `unlinkat`, `linkat`, `symlinkat`, `readlinkat`, `renameat`, `mkdirat`, `fchmodat`, `fchownat`, `mknodat`, `mkfifoat`) | +| `` | `opendir`, `fdopendir`, `readdir`, `readdir_r`, `rewinddir`, `telldir`, `seekdir`, `closedir`, `dirfd`, `scandir`, `alphasort`, `versionsort`; `d_type` is filled | +| Processes | `getpid`, `getppid`, the user and group ids and their `set` forms, `getgroups`, process groups and sessions (`getpgid`, `setpgid`, `getsid`, `setsid`, `getpgrp`, `setpgrp`), `getlogin`, `fork`, `vfork`, `_exit`, `execve` and every `exec` form (`execvp`'s search, and `/bin/sh` for a file that is not a program), `waitpid`, `wait`, `wait3`, `wait4`, `system`, `popen`, `pclose`, `sleep`, `usleep`, `pause`, `alarm`, `ualarm`, `daemon`; `` complete, its attributes refused with `ENOTSUP` where the operating system cannot honour them | +| System | `sysconf`, `confstr`, `uname`, `gethostname`, `sethostname`, `getpagesize`, `getdtablesize`, `getrlimit`, `setrlimit`, `getrusage`, `sched_yield` and the priority ranges; ``'s `mmap`, `munmap`, `mprotect`, `msync`, `madvise`, `posix_madvise`, `mlock`, `munlock`, `shm_open`, `shm_unlink`; ``'s `poll`, `ppoll`, and `select`, `pselect` | +| Sockets | `` complete (`socket` to `recvmsg` and `sockatmark`, `accept4`, `socketpair`); ``: `inet_pton`, `inet_ntop`, `inet_aton`, `inet_addr`, `inet_network`, `inet_ntoa` and the byte-order functions; ``: `getaddrinfo`, `getnameinfo`, `freeaddrinfo`, `gai_strerror`, `gethostbyname`, `gethostbyname2`, `gethostbyaddr`, `getservbyname`, `getservbyport`, `getprotobyname`, `getprotobynumber`, `herror`, `hstrerror`. A name is tried as a number, in `/etc/hosts`, by the operating system's own resolver where it has one (EmbLinkOS's), then by DNS to the servers `/etc/resolv.conf` names; services and protocols come from `/etc/services` and `/etc/protocols`, or without them from a table of IANA's well-known ones | +| Terminals | `` complete (`tcgetattr` to `tcsetwinsize`, `cfmakeraw`, `cfsetspeed`), `ttyname`, `ttyname_r`, `getpass`; pseudo-terminals: `posix_openpt`, `grantpt`, `unlockpt`, `ptsname`, `ptsname_r`, and ``'s `openpty`, `login_tty`, `forkpty` | +| Users and groups | `` and `` complete, from `/etc/passwd` and `/etc/group`: `getpwnam`, `getpwuid`, their `_r` forms, `getpwent` and its siblings, the same for groups, `getgrouplist`, `setgroups`, `initgroups` | +| `` | glibc's: `malloc_usable_size`, `memalign`, `valloc`, `pvalloc`, `mallinfo`, `mallinfo2`, `malloc_stats`; `mallopt` and `malloc_trim` are accepted and change nothing | | `` | `setlocale` and `localeconv`, one locale | -| `` | threads, mutexes, condition variables, `call_once` | +| `` | complete: threads, mutexes, condition variables, `call_once`, thread-specific storage | +| `` | threads (`pthread_create`, `join`, `detach`, `exit`, `self`, `equal`, `once`, `atfork`, GNU's `tryjoin_np`, `timedjoin_np`, `setname_np`, `getname_np`), their attributes, the three kinds of mutex with `timedlock` and POSIX.1-2024's `clocklock`, condition variables with a clock attribute and `clockwait`, read-write locks (readers first, as glibc's default), barriers, spin locks, thread-specific data (`pthread_key_create` and the rest, with destructor rounds). Process-shared objects, robust and priority-protocol mutexes are refused with `ENOTSUP`; `pthread_cancel` is `ENOTSUP`, as no call here is a cancellation point; a scheduling policy other than `SCHED_OTHER` is `EPERM` | +| `` | unnamed semaphores: `sem_init`, `sem_wait`, `sem_trywait`, `sem_timedwait`, `sem_clockwait`, `sem_post`, `sem_getvalue`, `sem_destroy`. `sem_open` is `ENOSYS` and a process-shared `sem_init` `ENOTSUP`: both need memory and a futex another process can reach | +| `` | POSIX's basic and extended syntax with glibc's numbers and messages, and GNU's `\+ \? \|` in basic syntax, `\< \> \b \B \w \W \s \S`, back-references in both, `REG_STARTEND`. The match is POSIX's -- leftmost, then longest -- found by backtracking; a pattern that would take more than 20 million steps at one position is `REG_ESPACE`, not a hang | +| ``, `` | `glob` and `globfree` with every flag glibc has (`GLOB_BRACE`, `GLOB_TILDE`, `GLOB_ONLYDIR`, `GLOB_PERIOD`, `GLOB_ALTDIRFUNC` among them), `glob_pattern_p`; `nftw` (with GNU's `FTW_ACTIONRETVAL`) and `ftw`, holding one directory open at a time whatever `fds` allows | +| `` | `openlog`, `syslog`, `vsyslog`, `closelog`, `setlogmask`: RFC 3164 lines to `/dev/log`, as glibc writes them. With nothing listening there a message is not dropped, as glibc drops it: it goes to `/dev/console` under `LOG_CONS` and to standard error otherwise | +| ``, `` | `nl_langinfo` for the `"C"` locale -- its formats, names, `RADIXCHAR` and the yes/no expressions -- with `CODESET` "UTF-8", the encoding its multibyte functions use (glibc's plain "C" says "ANSI_X3.4-1968"); no message catalogs: `catopen` fails with `ENOENT` and `catgets` returns the program's own message | | ``, `` | wide strings, `wcsto*`, restartable UTF-8 conversions (`mbrtowc`, `wcrtomb`, `mbsrtowcs`, `wcsrtombs`), `btowc`, `wctob`, wide streams (`fgetwc`, `fputwc`, `fgetws`, `fputws`, `ungetwc`, `fwide` and the rest), the wide `printf` and `scanf` families, `wcsftime`, `mbrtoc16`/`c16rtomb`/`mbrtoc32`/`c32rtomb` | | `` | the C11 interface over the compiler's atomic builtins; no library code | | `` (C23) | all fourteen operations at the five unsigned types and type-generic; each named function is also a macro over the compiler's bit builtins, so a call is the instruction, and the functions themselves are in `lib/libc` for a pointer to one | @@ -293,30 +333,80 @@ at once: the file lasts as long as the stream. `tmpnam`'s names are under `/tmp` too, each one no file had when it was made. A target with no filesystem has no temporary files: `tmpfile` returns a null pointer. -**Time.** There is no time-zone database: `localtime` is `gmtime`, -`tm_isdst` is 0, and `strftime`'s `%z` is `+0000`. `time` and `clock` -return what the backend provides, which on the freestanding targets is --1. - -**Signals.** The C model only: `raise` calls the handler directly, and -nothing delivers a signal from outside. - -**Threads.** `thrd_create` creates a thread where the backend can (Linux -and EmbLinkOS) and returns `thrd_error` elsewhere. Mutexes, condition -variables and `call_once` work on every target, threaded or not. -`tss_create` always fails and `tss_get` returns a null pointer. -`thread_local` is defined as `_Thread_local`. +**Time.** `localtime` follows `TZ` as POSIX writes it +(`CET-1CEST,M3.5.0,M10.5.0/3`, with quoted names like `<+0530>-5:30`, +`Jn` and `n` rules, and rule times past 24 hours or negative), or names a +compiled zone file (`Europe/Paris`, `:Europe/Paris`) under `TZDIR` or +`/usr/share/zoneinfo`, read whole: TZif versions 1 to 3, with the footer's +rule for times after the last transition. With no `TZ`, `/etc/localtime` +where there is one, UTC where there is not; a `TZ` that is neither is UTC +named `"UTC"`, as no error is possible. `mktime` reads `tm_isdst` as glibc +does at the hours the clocks change: 0 or 1 chooses between the two +meanings of an ambiguous time and moves a missing one by the hour that is +missing. `strptime` is glibc's in the `"C"` locale, down to its readings +of fields it leaves unsaid (`tm_wday` and `tm_yday` once a date is read, +and the date from `%U` or `%W` with a weekday). Two choices differ from +glibc 2.36: a rule applies before 1970 too, and where glibc reads past its +month table (day 366 of a common year, week 0) the calendar is followed. +`time` and `clock` return what the backend provides, which on the +freestanding targets is -1. + +**Signals.** Two models, one interface. Where the kernel delivers signals +(Linux), `sigaction` installs the handler with the kernel, and signals +arrive from outside: `alarm`, `kill` from another process, a child's +`SIGCHLD`. Elsewhere the library keeps the table itself -- the handlers, a +mask, a pending set -- and is the only sender: `raise`, and `kill` of the +program's own id, call the handler; a blocked signal waits in the pending +set and is delivered when it is unblocked; a signal at its default action +ends the program as the default says. `abort` raises `SIGABRT`, and if a +handler returns, resets it and raises again. The same test program passes +under both (`tests/exec/posix-signals.c`). + +**What the operating system cannot do.** A POSIX function whose primitive +the backend has not got fails with `ENOSYS`, never with a made-up answer: +on a board, `fork` is `ENOSYS`, and so is `socket`. `sysconf` answers -1 +for what it cannot know rather than guess. The `*at` functions use the +backend's own where it has them, and otherwise turn the directory +descriptor back into a path. `posix_spawn` runs a program through the +backend's spawn -- `fork` and `exec` on Linux, the kernel's spawn on +EmbLinkOS -- and `posix_spawnattr_setflags` refuses with `ENOTSUP` a flag +that spawn cannot honour, rather than a spawn that would ignore it. + +**Threads.** `pthread_create` and `thrd_create` start a thread where the +backend can (Linux and EmbLinkOS); elsewhere they fail (`EAGAIN`, +`thrd_error`) and everything else still works with the one thread there +is -- locks, `once`, keys. One implementation serves both spellings, so a +C11 thread's `tss_t` is a POSIX key. Each thread's keys live in a record +the library keeps for it, so no thread-local storage is needed beneath +them. `pthread_exit` returns to the start of its thread and runs the +keys' destructors there; frames it leaves are not unwound, so a C++ +destructor between it and the thread's function does not run (as under +musl). A thread's stack is the backend's: 8 MiB on Linux, mapped as it +is used; `pthread_attr_setstacksize` is kept and reported, and +`pthread_attr_setstack` is refused at `pthread_create`. Timed waits take +an absolute time, POSIX's and C11's alike. `thread_local` is defined as +`_Thread_local`. **Environment.** `getenv` reads the vector the startup code publishes in -`environ`. `setenv` and `unsetenv` are declared in `` but not -defined: a program that calls them fails to link. +`environ`. The first change copies that vector into the heap; the strings +`setenv` makes are freed when they are replaced or removed, the startup +vector's and `putenv`'s never are (a `putenv` string IS the entry, and a +later change to it changes the environment). + +**POSIX's choices.** Where POSIX lets an implementation choose, this one +chose glibc's answer, except: `fmemopen` refuses a size of 0 (POSIX lets +it); `open_memstream`'s length moves only when a write passes it, as +POSIX says, so a seek to `SEEK_END` after rewriting the middle goes to +the end and not to where the write stopped (glibc 2.36 differs); and +`dirname("//")` is `"//"`, as glibc's is. ### Thread safety The library's shared state is locked. The heap has one lock; each `FILE` has its own, held for the whole of a call, so the output of one `printf` or `puts` is never interleaved with another thread's; the table -of open streams has a third. The locks are futex-based: uncontended, +of open streams has a third. A stream's lock nests: a thread that holds +it with `flockfile` may still `printf` to the stream. The locks are futex-based: uncontended, locking and unlocking are one atomic instruction each. On a target whose backend has no futex the wait degrades to a yield, which is only reached when a lock is contended, and such a target has one thread. @@ -331,16 +421,17 @@ forms of the time functions. | Missing | Notes | |---|---| -| `setenv`, `unsetenv` | declared, not defined | | C11 Annex K (`*_s` functions) | | -| POSIX extensions such as `fdopen`, `fileno`, `popen`, `strtok_r`, `strerror_r`, `fseeko`, `fmemopen` | | -| a time-zone database, locales other than `"C"`, signal delivery | | +| named and process-shared semaphores, process-shared locks, thread cancellation | see `` and `` above | +| ``, `` | | +| locales other than `"C"`, and the `_l` functions that take one | | +| System V IPC, asynchronous I/O (``), message queues | | ### glibc instead: `--libc=system` For a Linux target, `--libc=system` replaces this library with the -system's own, glibc -- the way to POSIX threads, sockets and the rest of -POSIX, none of which this library has. EmbCC does what GCC does: +system's own, glibc -- the way to POSIX threads, and to whatever else of +glibc's this library has not got. EmbCC does what GCC does: | | With `--libc=system` | |---|---| @@ -367,8 +458,8 @@ runtime is built on its own C library. ### Backends Everything above is portable C. An operating system is reached through -the functions declared in `lib/libc/os/backend.h`, and one file per OS -implements them: +the 140 functions declared in `lib/libc/os/backend.h`, and one file per +OS implements them. The C part is small: | Group | Functions | Without them | |---|---|---| @@ -379,14 +470,28 @@ implements them: C++'s ``, `` and the other synchronization headers are built on the thread group, and `` on the filesystem group. +The rest, the second half of `backend.h`, is POSIX's: descriptors, the +rest of the filesystem and its `*at` forms, directories, processes and +spawn, signals, clocks and timers, mapped memory, the system's +identity and limits, polling, sockets and the resolver, terminals. Each +is shaped like the POSIX call it serves, and where a meaning needs a +number (a flag, an `fcntl` command, a signal, an `ioctl` request, a +socket address's layout) it is Linux's, which a backend whose kernel +numbers it differently translates. All of it is optional: +`os/absent.inc` defines each one as an `ENOSYS` failure, and a backend +includes it after defining `OS_HAS_name` for each it implements itself. +`tests/golden/libc-backends.sh` compiles every backend and fails if any +of the 140 is missing from one, which would otherwise be a link error +in the first program that called it. + Four backends exist: | Backend | Used by | What it does | |---|---|---| -| `os/posixlike/backend.c` | `libc-x86_64` and `libc-aarch64` (`x86_64-elf`, `aarch64-elf`) | forwards to `write`, `read`, `open`, `close`, `lseek`, `sbrk`, `_exit` and `isatty`, which the program or its environment must define. The thread functions (`thread_create`, `futex_wait`, ...) and filesystem functions (`fs_stat`, ...) are weak references: defined, they are used; absent, the group reports `ENOSYS`. `remove`, `rename`, `time`, `clock` and entropy report `ENOSYS`. | -| `os/linux/` | `libc-linux-x86_64`, `libc-linux-aarch64` | issues Linux system calls directly, so a program needs no other C library and no dynamic loader. Provides `crt1.o` (the `_start` that calls `main` and `exit`), threads through `clone`, thread-local storage (per-thread `errno`), and the filesystem group. Static images only. | -| `os/emblinkos/backend.c` | `libc-emblinkos` | EmbLinkOS's system calls, including threads and the filesystem group. `clock` reports failure. | -| `os/baremetal/backend.c` | `libc-embedded` (the embedded targets) | for a part with no operating system, and needs nothing from the program: `write`, `read`, `open`, `close`, `lseek`, `sbrk`, `_exit` and `isatty` are defined WEAKLY with defaults (output discarded, input at end of file, a heap from the end of the image to the stack, `_exit` stops), and a program's own definition of any of them replaces the default. One thread, no clock, no filesystem: those report `ENOSYS`. | +| `os/posixlike/backend.c` | `libc-x86_64` and `libc-aarch64` (`x86_64-elf`, `aarch64-elf`) | forwards to `write`, `read`, `open`, `close`, `lseek`, `sbrk`, `_exit` and `isatty`, which the program or its environment must define. The thread functions (`thread_create`, `futex_wait`, ...) and filesystem functions (`fs_stat`, ...) are weak references: defined, they are used; absent, the group reports `ENOSYS`. `remove`, `rename`, `time`, `clock` and entropy report `ENOSYS`, and so does POSIX's half (`os/absent.inc`); the program's own `kill`, `getpid` or `fork` replace the library's, being weak. | +| `os/linux/` | `libc-linux-x86_64`, `libc-linux-aarch64` | issues Linux system calls directly, so a program needs no other C library and no dynamic loader. Provides `crt1.o` (the `_start` that calls `main` and `exit`), threads through `clone`, thread-local storage (per-thread `errno`), and all of POSIX's half: `fork`, `execve`, kernel-delivered signals, sockets, terminals. Static images only. | +| `os/emblinkos/backend.c` | `libc-emblinkos` | EmbLinkOS's system calls, translated: its absolute-only paths (the working directory is kept here), its open flags and `d_type` numbers, its spawn in place of `fork` and `exec` (with descriptor, directory and environment actions), its IPv4 sockets and resolver, its terminal control. What it has not got -- `fork`, signals from outside, record locks, `mmap` of a file -- is `ENOSYS`. EmbLinkOS builds its whole userland on it with `make LIBC=emblibc`. `clock` reports failure. | +| `os/baremetal/backend.c` | `libc-embedded` (the embedded targets) | for a part with no operating system, and needs nothing from the program: `write`, `read`, `open`, `close`, `lseek`, `sbrk`, `_exit` and `isatty` are defined WEAKLY with defaults (output discarded, input at end of file, a heap from the end of the image to the stack, `_exit` stops), and a program's own definition of any of them replaces the default. One thread, no clock, no filesystem: those report `ENOSYS`, as POSIX's half does (`os/absent.inc`) -- except signals, which the library delivers itself. | A new operating system needs one new backend file and nothing else. @@ -422,8 +527,9 @@ depends on the target: | Target | Unwinder | |---|---| | `x86_64-linux-gnu`, `aarch64-linux-gnu` | EmbCC's own, `lib/rt/unwind.c`, in `librt.a` | +| EmbLinkOS | the same, in the `librt.a` that `make libc-emblinkos` builds beside its `libc.a`; EmbLinkOS's linker script names `__eh_frame_start` and `__eh_frame_end` for it, as EmbLD does | | `x86_64-elf`, `aarch64-elf` | libgcc's; the program links `libgcc.a` (or `libgcc_eh.a`) | -| EmbLinkOS, macOS | the platform's | +| macOS | the platform's | | the embedded targets | none. C++ exceptions are refused on every embedded target, C++ code generation on AVR, and at RV64 a function that needs a landing pad is refused (see [Bare-metal programming](embedded.md#c-on-the-embedded-targets)) | `` in the freestanding directory declares the level-I unwinder @@ -552,7 +658,8 @@ scratch. | `make libc-linux-aarch64` | the same for `aarch64-linux-gnu` | `build/libc/linux-aarch64/{libc.a,crt1.o,librt.a}` | | `make libc-linux` | both Linux C libraries | | | `make libc-embedded` | C library with the bare-metal backend, `--opt=size --sections=functions,data`, for each embedded target it builds on (all but `avr`) | `build/libc/TRIPLE/libc.a` | -| `make libc-emblinkos EMBLINKOS=DIR` | C library with the EmbLinkOS backend, `--opt=speed`; needs `DIR/user/lib/embk.h` (default `DIR` is `$HOME/EmbLinkOs`) | `build/libc/emblinkos/libc.a` | +| `make libc-emblinkos EMBLINKOS=DIR` | C library with the EmbLinkOS backend, `--opt=speed`, an empty `libm.a`, and `librt.a` with the unwinder; needs `DIR/user/lib/embk.h` (default `DIR` is `$HOME/EmbLinkOs`) | `build/libc/emblinkos/{libc.a,libm.a,librt.a}` | +| `make libcxx-emblinkos EMBLINKOS=DIR` | C++ library over the EmbLinkOS C library | `build/libcxx/emblinkos/libcxx.a` | | `make libcxx-x86_64`, `make libcxx-aarch64` | C++ library for `x86_64-elf`, `aarch64-elf`, `--opt=speed` | `build/libcxx/x86_64/libcxx.a`, `build/libcxx/aarch64/libcxx.a` | | `make libcxx` | both of the above | | | `make libcxx-linux-x86_64`, `make libcxx-linux-aarch64` | C++ library for the Linux triples | `build/libcxx/linux-x86_64/libcxx.a`, `build/libcxx/linux-aarch64/libcxx.a` | diff --git a/lib/libc/README.md b/lib/libc/README.md index 3324392a5..484080f41 100644 --- a/lib/libc/README.md +++ b/lib/libc/README.md @@ -11,13 +11,23 @@ the duplication this project removes everywhere else. ## Layout - include/ the hosted C11 headers + include/ the hosted C11 headers, and POSIX's src/ the library, portable C string/ ctype/ stdlib/ stdio/ time/ math/ + posix/ POSIX above C: descriptors, files, directories, + processes and spawn, popen, sockets and the resolver, + terminals and ptys, users, mmap, poll, sysconf; and + libgen, fnmatch, getopt, search, err setjmp/ the one part that cannot be portable C - os// the backend: 11 primitives (os/backend.h) + os/backend.h the contract: 140 primitives, C's few and POSIX's many + os/absent.inc each primitive as an ENOSYS failure, for what a backend + has not got + os// the backends + linux/ Linux system calls; no other libc underneath + emblinkos/ EmbLinkOS's syscall ABI; this is what replaced emlibc, + and with LIBC=emblibc, newlib + baremetal/ a part with no OS posixlike/ write/read/open/... -- the test harness, anything Unix - emblinkos/ EmbLinkOS's syscall ABI; this is what replaced emlibc `src/setjmp/` holds one file per architecture, each wrapped whole in `#ifdef __x86_64__` / `#ifdef __aarch64__` so the build picks one and the @@ -30,12 +40,18 @@ code: no function in this library has two bodies. system. A new OS implements those and gets the entire C library; nothing above the seam knows which OS it is on, and there are no `#ifdef`s there. -EmbLinkOS is the proof. Its C library was `emlibc`; it is now -`os/emblinkos/backend.c`, about 200 lines, and that OS gets the same -printf, malloc and math as every other target. +EmbLinkOS is the proof. Its C library was `emlibc`, then newlib with a +retargeting file; it is now `os/emblinkos/backend.c`, and that OS gets the +same printf, malloc, math, time zones, sockets and processes as every +other target -- its whole userland builds on it (`make LIBC=emblibc` in +EmbLinkOS). + +`tests/golden/libc-backends.sh` holds every backend to the whole contract. ## Status What exists and what does not is in `docs/manual/libraries.md`. The -acceptance test is `tests/golden/libc.sh`: every program in `tests/exec` -built and run against this library with no newlib at all. +acceptance tests are `tests/golden/libc.sh` -- every program in +`tests/exec` built and run against this library with no newlib at all -- +and `tests/golden/linux-libc-exec.sh`, which runs the POSIX programs on a +Linux kernel, where each value was first checked against glibc's. diff --git a/lib/libc/include/alloca.h b/lib/libc/include/alloca.h new file mode 100644 index 000000000..37703e000 --- /dev/null +++ b/lib/libc/include/alloca.h @@ -0,0 +1,9 @@ +/* -- stack allocation, which the compiler does itself */ +#ifndef _ALLOCA_H +#define _ALLOCA_H + +#include + +#define alloca(n) __builtin_alloca(n) + +#endif diff --git a/lib/libc/include/arpa/inet.h b/lib/libc/include/arpa/inet.h new file mode 100644 index 000000000..64e2a77e0 --- /dev/null +++ b/lib/libc/include/arpa/inet.h @@ -0,0 +1,24 @@ +/* -- Internet addresses as text (src/posix/inet.c) */ +#ifndef _ARPA_INET_H +#define _ARPA_INET_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +in_addr_t inet_addr(const char *s); +int inet_aton(const char *s, struct in_addr *out); +char *inet_ntoa(struct in_addr a); +const char *inet_ntop(int family, const void *__restrict src, + char *__restrict dst, socklen_t n); +int inet_pton(int family, const char *__restrict src, + void *__restrict dst); +in_addr_t inet_network(const char *s); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/bits/sigset.h b/lib/libc/include/bits/sigset.h new file mode 100644 index 000000000..888e57169 --- /dev/null +++ b/lib/libc/include/bits/sigset.h @@ -0,0 +1,10 @@ +/* sigset_t, for the headers that use it whatever the program asked of + * -- names it in posix_spawnattr_t, and is POSIX's + * own header, which declares what it declares (see ). + * Bit (sig - 1) is signal sig, Linux's 64. */ +#ifndef _BITS_SIGSET_H +#define _BITS_SIGSET_H + +typedef unsigned long long sigset_t; + +#endif diff --git a/lib/libc/include/dirent.h b/lib/libc/include/dirent.h new file mode 100644 index 000000000..1357535e2 --- /dev/null +++ b/lib/libc/include/dirent.h @@ -0,0 +1,55 @@ +/* -- reading a directory (src/posix/dir.c). `.` and `..` + * are returned where the OS has them, as POSIX says. */ +#ifndef _DIRENT_H +#define _DIRENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 + +struct dirent { + ino_t d_ino; + off_t d_off; /* this entry's place, for telldir */ + unsigned short d_reclen; + unsigned char d_type; /* DT_* */ + char d_name[256]; +}; + +typedef struct __dirstream DIR; + +DIR *opendir(const char *path); +DIR *fdopendir(int fd); +struct dirent *readdir(DIR *d); +int readdir_r(DIR *__restrict d, struct dirent *__restrict e, + struct dirent **__restrict result); +void rewinddir(DIR *d); +long telldir(DIR *d); +void seekdir(DIR *d, long at); +int closedir(DIR *d); +int dirfd(DIR *d); +int alphasort(const struct dirent **a, const struct dirent **b); +int scandir(const char *path, struct dirent ***list, + int (*keep)(const struct dirent *), + int (*cmp)(const struct dirent **, + const struct dirent **)); +#if defined(__EMB_GNU) +int versionsort(const struct dirent **a, const struct dirent **b); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/endian.h b/lib/libc/include/endian.h new file mode 100644 index 000000000..c9be8bc3c --- /dev/null +++ b/lib/libc/include/endian.h @@ -0,0 +1,38 @@ +/* -- the byte order, and conversions to a fixed one + * (POSIX.1-2024 took these from the BSDs), on the compiler's swaps */ +#ifndef _ENDIAN_H +#define _ENDIAN_H + +#include + +#define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ +#define BIG_ENDIAN __ORDER_BIG_ENDIAN__ +#define PDP_ENDIAN __ORDER_PDP_ENDIAN__ +#define BYTE_ORDER __BYTE_ORDER__ +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __BIG_ENDIAN BIG_ENDIAN +#define __BYTE_ORDER BYTE_ORDER + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe16(x) __builtin_bswap16(x) +#define htobe32(x) __builtin_bswap32(x) +#define htobe64(x) __builtin_bswap64(x) +#define htole16(x) ((uint16_t)(x)) +#define htole32(x) ((uint32_t)(x)) +#define htole64(x) ((uint64_t)(x)) +#else +#define htobe16(x) ((uint16_t)(x)) +#define htobe32(x) ((uint32_t)(x)) +#define htobe64(x) ((uint64_t)(x)) +#define htole16(x) __builtin_bswap16(x) +#define htole32(x) __builtin_bswap32(x) +#define htole64(x) __builtin_bswap64(x) +#endif +#define be16toh(x) htobe16(x) +#define be32toh(x) htobe32(x) +#define be64toh(x) htobe64(x) +#define le16toh(x) htole16(x) +#define le32toh(x) htole32(x) +#define le64toh(x) htole64(x) + +#endif diff --git a/lib/libc/include/err.h b/lib/libc/include/err.h new file mode 100644 index 000000000..4006a349b --- /dev/null +++ b/lib/libc/include/err.h @@ -0,0 +1,26 @@ +/* -- the BSD diagnostics every Unix has: the program's name, the + * caller's message, and (err, warn) what errno says, on stderr; err and + * errx then exit with the status given (src/posix/err.c). */ +#ifndef _ERR_H +#define _ERR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void warn(const char *fmt, ...); +void warnx(const char *fmt, ...); +void vwarn(const char *fmt, va_list ap); +void vwarnx(const char *fmt, va_list ap); +__attribute__((__noreturn__)) void err(int status, const char *fmt, ...); +__attribute__((__noreturn__)) void errx(int status, const char *fmt, ...); +__attribute__((__noreturn__)) void verr(int status, const char *fmt, va_list ap); +__attribute__((__noreturn__)) void verrx(int status, const char *fmt, va_list ap); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/errno.h b/lib/libc/include/errno.h index 40e20749a..7ef480a1f 100644 --- a/lib/libc/include/errno.h +++ b/lib/libc/include/errno.h @@ -187,6 +187,12 @@ int *__errno_location(void); #endif +/* the program's name, as startup found it in argv[0], and the part of + * it after the last '/': GNU's, under _GNU_SOURCE as there */ +#if defined(_GNU_SOURCE) +extern char *program_invocation_name, *program_invocation_short_name; +#endif + #ifdef __cplusplus } #endif diff --git a/lib/libc/include/fcntl.h b/lib/libc/include/fcntl.h new file mode 100644 index 000000000..fee56b578 --- /dev/null +++ b/lib/libc/include/fcntl.h @@ -0,0 +1,96 @@ +/* -- open, fcntl and their flags (src/posix/fd.c). The numbers + * are Linux's, which os/backend.h makes the seam's. */ +#ifndef _FCNTL_H +#define _FCNTL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define O_ACCMODE 0003 +#define O_RDONLY 00 +#define O_WRONLY 01 +#define O_RDWR 02 +#define O_CREAT 0100 +#define O_EXCL 0200 +#define O_NOCTTY 0400 +#define O_TRUNC 01000 +#define O_APPEND 02000 +#define O_NONBLOCK 04000 +#define O_NDELAY O_NONBLOCK +#define O_DSYNC 010000 +#define O_ASYNC 020000 +#if __INT_MAX__ > 0x7fff +#define O_DIRECTORY 0200000 +#define O_NOFOLLOW 0400000 +#define O_CLOEXEC 02000000 +#define O_SYNC 04010000 +#else +/* Where an int is 16 bits (AVR) these do not fit, and mean nothing: + * there are no programs to exec, no directories to refuse, no links. */ +#define O_DIRECTORY 0 +#define O_NOFOLLOW 0 +#define O_CLOEXEC 0 +#define O_SYNC O_DSYNC +#endif +#define O_RSYNC O_SYNC +#define O_LARGEFILE 0 +/* POSIX.1-2024's: open for searching or executing only. Linux's O_PATH + * comes closest; this library has no such open, and asks read-only. */ +#define O_SEARCH O_RDONLY +#define O_EXEC O_RDONLY + +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 +#define F_GETLK 5 +#define F_SETLK 6 +#define F_SETLKW 7 +#define F_DUPFD_CLOEXEC 1030 +#define FD_CLOEXEC 1 + +#define F_RDLCK 0 +#define F_WRLCK 1 +#define F_UNLCK 2 + +#define AT_FDCWD (-100) +#define AT_SYMLINK_NOFOLLOW 0x100 +#define AT_REMOVEDIR 0x200 +#define AT_EACCESS 0x200 +#define AT_SYMLINK_FOLLOW 0x400 +#define AT_EMPTY_PATH 0x1000 + +#define POSIX_FADV_NORMAL 0 +#define POSIX_FADV_RANDOM 1 +#define POSIX_FADV_SEQUENTIAL 2 +#define POSIX_FADV_WILLNEED 3 +#define POSIX_FADV_DONTNEED 4 +#define POSIX_FADV_NOREUSE 5 + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +struct flock { + short l_type, l_whence; + off_t l_start, l_len; + pid_t l_pid; +}; + +int open(const char *path, int flags, ...); +int openat(int dirfd, const char *path, int flags, ...); +int creat(const char *path, mode_t mode); +int fcntl(int fd, int cmd, ...); +int posix_fadvise(int fd, off_t off, off_t len, int advice); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/features.h b/lib/libc/include/features.h new file mode 100644 index 000000000..7636add24 --- /dev/null +++ b/lib/libc/include/features.h @@ -0,0 +1,48 @@ +/* -- which names beyond ISO C the C headers declare. + * + * is C's header, and C reserves to the program every name it + * does not itself define there: a strictly conforming program may have + * its own `getline` or `fileno`, and a header that declared POSIX's in + * the same scope would reject that program for nothing it did wrong. + * POSIX's answer is the feature-test macros, and this is where they are + * read, once, for every header that has both kinds of name: + * + * __EMB_POSIX POSIX.1-2024, XSI included. On unless the program + * asked for ISO C alone (-std=c11 and no feature macro); + * any of _POSIX_C_SOURCE, _XOPEN_SOURCE, _DEFAULT_SOURCE, + * _BSD_SOURCE or _GNU_SOURCE turns it back on. + * __EMB_GNU what only GNU and the BSDs have -- strchrnul, memrchr, + * getopt_long, clearenv. On by default as well, and on + * under _GNU_SOURCE, _DEFAULT_SOURCE or _BSD_SOURCE. A + * program that names a POSIX version and nothing more + * (-D_POSIX_C_SOURCE=200809L) gets POSIX without these, + * as with glibc: it was written expecting to, and may + * define a `strchrnul` of its own. + * + * A header that is POSIX's own (, , ...) declares + * what it declares without asking: including it is the request. + * + * Asking for an OLDER POSIX (_POSIX_C_SOURCE=200112L) does not hide what + * came later. glibc does that, and it buys a program nothing but a name + * it then has to declare itself. + */ +#ifndef _FEATURES_H +#define _FEATURES_H + +#if defined(_GNU_SOURCE) || defined(_DEFAULT_SOURCE) || \ + defined(_BSD_SOURCE) || \ + (!defined(__STRICT_ANSI__) && !defined(_POSIX_C_SOURCE) && \ + !defined(_POSIX_SOURCE) && !defined(_XOPEN_SOURCE)) +#define __EMB_GNU 1 +#endif + +#if defined(__EMB_GNU) || defined(_POSIX_C_SOURCE) || \ + defined(_POSIX_SOURCE) || defined(_XOPEN_SOURCE) +#define __EMB_POSIX 1 +#endif + +/* The version this library implements, for a program that asks with + * #if _POSIX_VERSION >= ... (from , where POSIX puts it). */ +#define __EMB_POSIX_VERSION 202405L + +#endif diff --git a/lib/libc/include/fnmatch.h b/lib/libc/include/fnmatch.h new file mode 100644 index 000000000..89bf47ee5 --- /dev/null +++ b/lib/libc/include/fnmatch.h @@ -0,0 +1,28 @@ +/* -- shell wildcard matching (src/posix/fnmatch.c). The flag + * values are glibc's, as is FNM_NOMATCH, so a program's numbers agree + * with the system it was written on. */ +#ifndef _FNMATCH_H +#define _FNMATCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define FNM_PATHNAME 0x01 /* a '/' is matched only by a '/' */ +#define FNM_NOESCAPE 0x02 /* '\' is an ordinary character */ +#define FNM_PERIOD 0x04 /* a leading '.' only by a '.' */ +#define FNM_LEADING_DIR 0x08 /* the pattern may match a leading dir/ */ +#define FNM_CASEFOLD 0x10 /* letters match either case */ +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +#define FNM_NOMATCH 1 +#define FNM_NOSYS (-1) + +int fnmatch(const char *pattern, const char *string, int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/ftw.h b/lib/libc/include/ftw.h new file mode 100644 index 000000000..0f56da8a5 --- /dev/null +++ b/lib/libc/include/ftw.h @@ -0,0 +1,52 @@ +/* -- walking a file tree: POSIX's nftw and the older ftw, with + * glibc's numbers and GNU's FTW_ACTIONRETVAL results. */ +#ifndef _FTW_H +#define _FTW_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* what the callback is told it was given */ +#define FTW_F 0 /* a file */ +#define FTW_D 1 /* a directory, before its entries */ +#define FTW_DNR 2 /* a directory that could not be read */ +#define FTW_NS 3 /* stat failed on it */ +#define FTW_SL 4 /* a symbolic link (with FTW_PHYS) */ +#define FTW_DP 5 /* a directory, after its entries (FTW_DEPTH) */ +#define FTW_SLN 6 /* a link to nothing */ + +/* nftw's flags */ +#define FTW_PHYS 1 /* do not follow links */ +#define FTW_MOUNT 2 /* stay on one filesystem */ +#define FTW_CHDIR 4 /* chdir into each directory first */ +#define FTW_DEPTH 8 /* a directory after its entries */ +#if defined(__EMB_GNU) +#define FTW_ACTIONRETVAL 16 /* the callback's result steers the walk */ +#define FTW_CONTINUE 0 +#define FTW_STOP 1 +#define FTW_SKIP_SUBTREE 2 +#define FTW_SKIP_SIBLINGS 3 +#endif + +struct FTW { + int base; /* where the name starts in the path */ + int level; /* depth below the starting point */ +}; + +int ftw(const char *path, + int (*fn)(const char *path, const struct stat *st, int type), + int fds); +int nftw(const char *path, + int (*fn)(const char *path, const struct stat *st, int type, + struct FTW *ftw), + int fds, int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/getopt.h b/lib/libc/include/getopt.h new file mode 100644 index 000000000..b933af15d --- /dev/null +++ b/lib/libc/include/getopt.h @@ -0,0 +1,38 @@ +/* -- getopt, and GNU's getopt_long and getopt_long_only + * (src/posix/getopt.c). declares getopt and its variables + * too; this header adds the long forms. */ +#ifndef _GETOPT_H +#define _GETOPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; +extern int optind, opterr, optopt; +/* BSD's way to start over; setting optind to 0 does the same, as glibc */ +extern int optreset; + +int getopt(int argc, char *const argv[], const char *opts); + +struct option { + const char *name; + int has_arg; /* no_argument, required_argument, optional_ */ + int *flag; /* non-null: *flag = val and return 0 */ + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int getopt_long(int argc, char *const argv[], const char *opts, + const struct option *longopts, int *index); +int getopt_long_only(int argc, char *const argv[], const char *opts, + const struct option *longopts, int *index); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/glob.h b/lib/libc/include/glob.h new file mode 100644 index 000000000..f1b4701bb --- /dev/null +++ b/lib/libc/include/glob.h @@ -0,0 +1,63 @@ +/* -- pathname patterns, as the shell expands them: POSIX's + * glob and globfree, with glibc's numbers and GNU's extensions (braces, + * tildes, GLOB_ONLYDIR, GLOB_PERIOD, the alternative directory functions). */ +#ifndef _GLOB_H +#define _GLOB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct stat; +struct dirent; + +typedef struct { + size_t gl_pathc; /* how many matched */ + char **gl_pathv; /* gl_offs nulls, the matches, a null */ + size_t gl_offs; /* with GLOB_DOOFFS, how many nulls first */ + int gl_flags; /* the flags given, and GLOB_MAGCHAR */ + /* GLOB_ALTDIRFUNC: these instead of opendir, readdir, closedir, + * lstat and stat */ + void (*gl_closedir)(void *); + struct dirent *(*gl_readdir)(void *); + void *(*gl_opendir)(const char *); + int (*gl_lstat)(const char *__restrict, struct stat *__restrict); + int (*gl_stat)(const char *__restrict, struct stat *__restrict); +} glob_t; + +#define GLOB_ERR (1 << 0) +#define GLOB_MARK (1 << 1) +#define GLOB_NOSORT (1 << 2) +#define GLOB_DOOFFS (1 << 3) +#define GLOB_NOCHECK (1 << 4) +#define GLOB_APPEND (1 << 5) +#define GLOB_NOESCAPE (1 << 6) +#define GLOB_PERIOD (1 << 7) +#define GLOB_MAGCHAR (1 << 8) +#define GLOB_ALTDIRFUNC (1 << 9) +#define GLOB_BRACE (1 << 10) +#define GLOB_NOMAGIC (1 << 11) +#define GLOB_TILDE (1 << 12) +#define GLOB_ONLYDIR (1 << 13) +#define GLOB_TILDE_CHECK (1 << 14) + +#define GLOB_NOSPACE 1 +#define GLOB_ABORTED 2 +#define GLOB_NOMATCH 3 +#define GLOB_NOSYS 4 + +int glob(const char *__restrict pattern, int flags, + int (*errfunc)(const char *path, int err), glob_t *__restrict g); +void globfree(glob_t *g); +#if defined(__EMB_GNU) +int glob_pattern_p(const char *pattern, int quote); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/grp.h b/lib/libc/include/grp.h new file mode 100644 index 000000000..d317de243 --- /dev/null +++ b/lib/libc/include/grp.h @@ -0,0 +1,34 @@ +/* -- the group database: /etc/group (src/posix/pwd.c) */ +#ifndef _GRP_H +#define _GRP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct group { + char *gr_name, *gr_passwd; + gid_t gr_gid; + char **gr_mem; +}; + +struct group *getgrnam(const char *name); +struct group *getgrgid(gid_t gid); +int getgrnam_r(const char *name, struct group *g, char *buf, size_t n, + struct group **result); +int getgrgid_r(gid_t gid, struct group *g, char *buf, size_t n, + struct group **result); +struct group *getgrent(void); +void setgrent(void); +void endgrent(void); +int getgrouplist(const char *user, gid_t group, gid_t *groups, int *n); +int initgroups(const char *user, gid_t group); +int setgroups(size_t n, const gid_t *groups); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/langinfo.h b/lib/libc/include/langinfo.h new file mode 100644 index 000000000..a4ca90c64 --- /dev/null +++ b/lib/libc/include/langinfo.h @@ -0,0 +1,37 @@ +/* -- what the locale says, item by item, for the one locale + * this library has: "C", whose multibyte encoding is UTF-8 (CODESET is + * "UTF-8", as glibc's C.UTF-8 says). The item numbers are this + * library's; a program reaches them only through these names. */ +#ifndef _LANGINFO_H +#define _LANGINFO_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + CODESET, + D_T_FMT, D_FMT, T_FMT, T_FMT_AMPM, AM_STR, PM_STR, + DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7, + ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, + MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, + MON_10, MON_11, MON_12, + ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, + ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12, + ERA, ERA_D_FMT, ALT_DIGITS, ERA_D_T_FMT, ERA_T_FMT, + RADIXCHAR, THOUSEP, YESEXPR, NOEXPR, CRNCYSTR, + __NL_ITEMS +}; +#define CODESET CODESET +#define RADIXCHAR RADIXCHAR + +char *nl_langinfo(nl_item item); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/libgen.h b/lib/libc/include/libgen.h new file mode 100644 index 000000000..48762435d --- /dev/null +++ b/lib/libc/include/libgen.h @@ -0,0 +1,16 @@ +/* -- POSIX's basename and dirname (src/posix/libgen.c). */ +#ifndef _LIBGEN_H +#define _LIBGEN_H + +#ifdef __cplusplus +extern "C" { +#endif + +char *basename(char *path); +char *dirname(char *path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/limits.h b/lib/libc/include/limits.h index 993067209..e502d9548 100644 --- a/lib/libc/include/limits.h +++ b/lib/libc/include/limits.h @@ -5,16 +5,36 @@ * compiler's with #include_next for the ones that belong to the target's * type widths. Neither is complete alone, and neither duplicates the other. */ +#ifndef _EMBLIBC_LIMITS_H_ +#define _EMBLIBC_LIMITS_H_ + +/* _LIBC_LIMITS_H_ is the protocol's word for "the C library's half is + * here", which the compiler's half reads -- and newlib's limits.h uses it + * as its own guard. So it is defined, not tested at the top: where + * another C library's limits.h came first (a build putting newlib's + * headers ahead), that library's limits stand, and this file only passes + * the chain on to the compiler's half, which holds INT_MAX. */ #ifndef _LIBC_LIMITS_H_ -#define _LIBC_LIMITS_H_ +#define _LIBC_LIMITS_H_ 1 +#define _EMBLIBC_LIMITS_OWN 1 +#endif + +#ifdef _EMBLIBC_LIMITS_OWN /* POSIX-ish limits a hosted program expects to find here. Modest, and * honest about what this library actually supports. */ #define PATH_MAX 4096 #define NAME_MAX 255 +#ifndef ARG_MAX /* a build may have its own, as glibc has none */ #define ARG_MAX 32768 +#endif #define OPEN_MAX 32 #define NGROUPS_MAX 32 +#define PTHREAD_KEYS_MAX 128 +#define PTHREAD_DESTRUCTOR_ITERATIONS 4 +#define PTHREAD_STACK_MIN 16384 +#define SEM_VALUE_MAX 0x7FFFFFFF +#define SEM_NSEMS_MAX 256 #define SSIZE_MAX __LONG_MAX__ /* wchar_t's range. It belongs here rather than being assumed, because its @@ -34,6 +54,8 @@ * wctomb was overrun by the first character past U+007F. */ #define MB_LEN_MAX 4 +#endif /* _EMBLIBC_LIMITS_OWN */ + /* the compiler's half: CHAR_BIT, INT_MAX and the rest, from the target */ #include_next diff --git a/lib/libc/include/malloc.h b/lib/libc/include/malloc.h new file mode 100644 index 000000000..83f5c1964 --- /dev/null +++ b/lib/libc/include/malloc.h @@ -0,0 +1,57 @@ +/* -- glibc's: the allocator's own questions and knobs, beside + * 's malloc family. mallinfo and malloc_stats walk the heap; + * mallopt and malloc_trim are accepted and change nothing, as the heap + * has no thresholds to set and gives no memory back. */ +#ifndef _MALLOC_H +#define _MALLOC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct mallinfo { + int arena; /* bytes the heap has taken from the system */ + int ordblks; /* free blocks */ + int smblks, hblks, hblkhd, usmblks, fsmblks; /* always 0 here */ + int uordblks; /* bytes in use */ + int fordblks; /* bytes free */ + int keepcost; /* the free bytes at the top of the heap */ +}; + +struct mallinfo2 { + size_t arena, ordblks, smblks, hblks, hblkhd, usmblks, fsmblks; + size_t uordblks, fordblks, keepcost; +}; + +/* mallopt's parameters, glibc's numbers */ +#define M_MXFAST 1 +#define M_NLBLKS 2 +#define M_GRAIN 3 +#define M_KEEP 4 +#define M_TRIM_THRESHOLD (-1) +#define M_TOP_PAD (-2) +#define M_MMAP_THRESHOLD (-3) +#define M_MMAP_MAX (-4) +#define M_CHECK_ACTION (-5) +#define M_PERTURB (-6) +#define M_ARENA_TEST (-7) +#define M_ARENA_MAX (-8) + +size_t malloc_usable_size(void *p); +void *memalign(size_t align, size_t n); +void *valloc(size_t n); +void *pvalloc(size_t n); +struct mallinfo mallinfo(void); +struct mallinfo2 mallinfo2(void); +void malloc_stats(void); +int malloc_trim(size_t pad); +int mallopt(int param, int value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/netdb.h b/lib/libc/include/netdb.h new file mode 100644 index 000000000..d99744c4e --- /dev/null +++ b/lib/libc/include/netdb.h @@ -0,0 +1,105 @@ +/* -- names to addresses (src/posix/netdb.c, resolv.c). A + * name is looked up in /etc/hosts, then by the OS's own resolver where + * it has one (EmbLinkOS), else by asking the DNS servers /etc/resolv.conf + * names. */ +#ifndef _NETDB_H +#define _NETDB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype, h_length; + char **h_addr_list; +}; +#define h_addr h_addr_list[0] + +struct servent { + char *s_name; + char **s_aliases; + int s_port; /* network order */ + char *s_proto; +}; + +struct protoent { + char *p_name; + char **p_aliases; + int p_proto; +}; + +struct addrinfo { + int ai_flags, ai_family, ai_socktype, ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +#define AI_PASSIVE 0x0001 +#define AI_CANONNAME 0x0002 +#define AI_NUMERICHOST 0x0004 +#define AI_V4MAPPED 0x0008 +#define AI_ALL 0x0010 +#define AI_ADDRCONFIG 0x0020 +#define AI_NUMERICSERV 0x0400 + +#define NI_NUMERICHOST 1 +#define NI_NUMERICSERV 2 +#define NI_NOFQDN 4 +#define NI_NAMEREQD 8 +#define NI_DGRAM 16 +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 + +#define EAI_BADFLAGS (-1) +#define EAI_NONAME (-2) +#define EAI_AGAIN (-3) +#define EAI_FAIL (-4) +#define EAI_FAMILY (-6) +#define EAI_SOCKTYPE (-7) +#define EAI_SERVICE (-8) +#define EAI_MEMORY (-10) +#define EAI_SYSTEM (-11) +#define EAI_OVERFLOW (-12) + +#define HOST_NOT_FOUND 1 +#define TRY_AGAIN 2 +#define NO_RECOVERY 3 +#define NO_DATA 4 +#define NO_ADDRESS NO_DATA + +extern int h_errno; + +int getaddrinfo(const char *__restrict node, + const char *__restrict service, + const struct addrinfo *__restrict hints, + struct addrinfo **__restrict res); +void freeaddrinfo(struct addrinfo *ai); +const char *gai_strerror(int code); +int getnameinfo(const struct sockaddr *__restrict sa, + socklen_t salen, char *__restrict host, + socklen_t hostlen, char *__restrict serv, + socklen_t servlen, int flags); +struct hostent *gethostbyname(const char *name); +struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type); +struct servent *getservbyname(const char *name, const char *proto); +struct servent *getservbyport(int port, const char *proto); +struct protoent *getprotobyname(const char *name); +struct protoent *getprotobynumber(int proto); +#if defined(__EMB_GNU) +struct hostent *gethostbyname2(const char *name, int family); +const char *hstrerror(int code); +void herror(const char *msg); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/netinet/in.h b/lib/libc/include/netinet/in.h new file mode 100644 index 000000000..646060cd4 --- /dev/null +++ b/lib/libc/include/netinet/in.h @@ -0,0 +1,121 @@ +/* -- Internet addresses (src/posix/inet.c) */ +#ifndef _NETINET_IN_H +#define _NETINET_IN_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint16_t in_port_t; +typedef uint32_t in_addr_t; + +struct in_addr { in_addr_t s_addr; }; + +struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + unsigned char sin_zero[8]; +}; + +struct in6_addr { + union { + uint8_t __u8[16]; + uint16_t __u16[8]; + uint32_t __u32[4]; + } __in6_u; +}; +#define s6_addr __in6_u.__u8 +#define s6_addr16 __in6_u.__u16 +#define s6_addr32 __in6_u.__u32 + +struct sockaddr_in6 { + sa_family_t sin6_family; + in_port_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; + +struct ip_mreq { struct in_addr imr_multiaddr, imr_interface; }; +struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; unsigned ipv6mr_interface; }; + +extern const struct in6_addr in6addr_any, in6addr_loopback; +#define IN6ADDR_ANY_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } } +#define IN6ADDR_LOOPBACK_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } } + +#define IPPROTO_IP 0 +#define IPPROTO_ICMP 1 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define IPPROTO_IPV6 41 +#define IPPROTO_ICMPV6 58 +#define IPPROTO_RAW 255 + +#define INADDR_ANY ((in_addr_t)0x00000000) +#define INADDR_BROADCAST ((in_addr_t)0xffffffff) +#define INADDR_NONE ((in_addr_t)0xffffffff) +#define INADDR_LOOPBACK ((in_addr_t)0x7f000001) + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +#define IP_TOS 1 +#define IP_TTL 2 +#define IP_MULTICAST_IF 32 +#define IP_MULTICAST_TTL 33 +#define IP_MULTICAST_LOOP 34 +#define IP_ADD_MEMBERSHIP 35 +#define IP_DROP_MEMBERSHIP 36 +#define IPV6_UNICAST_HOPS 16 +#define IPV6_MULTICAST_IF 17 +#define IPV6_MULTICAST_HOPS 18 +#define IPV6_MULTICAST_LOOP 19 +#define IPV6_JOIN_GROUP 20 +#define IPV6_LEAVE_GROUP 21 +#define IPV6_V6ONLY 26 + +#define IN6_IS_ADDR_UNSPECIFIED(a) \ + (!(a)->s6_addr32[0] && !(a)->s6_addr32[1] && !(a)->s6_addr32[2] && \ + !(a)->s6_addr32[3]) +#define IN6_IS_ADDR_LOOPBACK(a) \ + (!(a)->s6_addr32[0] && !(a)->s6_addr32[1] && !(a)->s6_addr32[2] && \ + (a)->s6_addr[12] == 0 && (a)->s6_addr[13] == 0 && \ + (a)->s6_addr[14] == 0 && (a)->s6_addr[15] == 1) +#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xff) +#define IN6_IS_ADDR_LINKLOCAL(a) \ + ((a)->s6_addr[0] == 0xfe && ((a)->s6_addr[1] & 0xc0) == 0x80) +#define IN6_IS_ADDR_SITELOCAL(a) \ + ((a)->s6_addr[0] == 0xfe && ((a)->s6_addr[1] & 0xc0) == 0xc0) +#define IN6_IS_ADDR_V4MAPPED(a) \ + (!(a)->s6_addr32[0] && !(a)->s6_addr32[1] && \ + (a)->s6_addr[8] == 0 && (a)->s6_addr[9] == 0 && \ + (a)->s6_addr[10] == 0xff && (a)->s6_addr[11] == 0xff) +#define IN6_IS_ADDR_V4COMPAT(a) \ + (!(a)->s6_addr32[0] && !(a)->s6_addr32[1] && !(a)->s6_addr32[2] && \ + !IN6_IS_ADDR_UNSPECIFIED(a) && !IN6_IS_ADDR_LOOPBACK(a)) + +uint32_t htonl(uint32_t v); +uint16_t htons(uint16_t v); +uint32_t ntohl(uint32_t v); +uint16_t ntohs(uint16_t v); + +/* as the compiler's byte swaps, so a constant converts at compile time */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htonl(v) __builtin_bswap32(v) +#define htons(v) __builtin_bswap16(v) +#else +#define htonl(v) ((uint32_t)(v)) +#define htons(v) ((uint16_t)(v)) +#endif +#define ntohl(v) htonl(v) +#define ntohs(v) htons(v) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/netinet/tcp.h b/lib/libc/include/netinet/tcp.h new file mode 100644 index 000000000..ab3cd3012 --- /dev/null +++ b/lib/libc/include/netinet/tcp.h @@ -0,0 +1,12 @@ +/* -- TCP's socket options */ +#ifndef _NETINET_TCP_H +#define _NETINET_TCP_H + +#define TCP_NODELAY 1 +#define TCP_MAXSEG 2 +#define TCP_CORK 3 +#define TCP_KEEPIDLE 4 +#define TCP_KEEPINTVL 5 +#define TCP_KEEPCNT 6 + +#endif diff --git a/lib/libc/include/nl_types.h b/lib/libc/include/nl_types.h new file mode 100644 index 000000000..4c1792bdb --- /dev/null +++ b/lib/libc/include/nl_types.h @@ -0,0 +1,26 @@ +/* -- message catalogs. There are none to open: catopen + * fails with ENOENT (no catalog), and catgets hands back the message the + * program gave as its default, which is what a program with no + * translation shows. */ +#ifndef _NL_TYPES_H +#define _NL_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int nl_item; +typedef void *nl_catd; + +#define NL_SETD 1 +#define NL_CAT_LOCALE 1 + +nl_catd catopen(const char *name, int flag); +char *catgets(nl_catd cat, int set, int number, const char *message); +int catclose(nl_catd cat); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/paths.h b/lib/libc/include/paths.h new file mode 100644 index 000000000..3ebc31ca1 --- /dev/null +++ b/lib/libc/include/paths.h @@ -0,0 +1,15 @@ +/* -- the system's well-known paths */ +#ifndef _PATHS_H +#define _PATHS_H + +#define _PATH_BSHELL "/bin/sh" +#define _PATH_CONSOLE "/dev/console" +#define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin" +#define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin" +#define _PATH_DEVNULL "/dev/null" +#define _PATH_TTY "/dev/tty" +#define _PATH_TMP "/tmp/" +#define _PATH_VARTMP "/var/tmp/" +#define _PATH_DEV "/dev/" + +#endif diff --git a/lib/libc/include/poll.h b/lib/libc/include/poll.h new file mode 100644 index 000000000..e1a5971a2 --- /dev/null +++ b/lib/libc/include/poll.h @@ -0,0 +1,37 @@ +/* -- waiting for any of several descriptors */ +#ifndef _POLL_H +#define _POLL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define POLLIN 0x001 +#define POLLPRI 0x002 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 +#define POLLRDNORM 0x040 +#define POLLRDBAND 0x080 +#define POLLWRNORM 0x100 +#define POLLWRBAND 0x200 + +typedef unsigned long nfds_t; + +struct pollfd { int fd; short events, revents; }; + +int poll(struct pollfd *fds, nfds_t n, int timeout_ms); +#if defined(__EMB_GNU) +struct timespec; +int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *timeout, + const unsigned long long *sigmask); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/pthread.h b/lib/libc/include/pthread.h new file mode 100644 index 000000000..214494da7 --- /dev/null +++ b/lib/libc/include/pthread.h @@ -0,0 +1,283 @@ +/* -- POSIX threads, over the same seam C11's uses + * (lib/libc/os/backend.h: thread_create/join/detach/self and the futex). + * + * The constants are glibc's, so a program's numbers mean what it meant. + * The types are this library's own: every object here is reached only + * through these functions, and a static program has nobody else's to + * agree with. Every lock is the three-state futex word; a target whose + * backend has no threads runs the same code with one thread, where a + * lock never waits. + */ +#ifndef _PTHREAD_H +#define _PTHREAD_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long pthread_t; + +typedef struct { + int __detach, __inherit, __policy, __scope; + struct sched_param __param; + size_t __stacksize, __guardsize; + void *__stackaddr; +} pthread_attr_t; + +typedef struct { + int __state; /* 0 free, 1 held, 2 held with waiters */ + int __type; + unsigned long __owner; + unsigned __count; + int __flags; +} pthread_mutex_t; +typedef struct { int __type, __pshared, __protocol, __robust; } pthread_mutexattr_t; + +typedef struct { int __seq; int __clock; } pthread_cond_t; +typedef struct { int __clock, __pshared; } pthread_condattr_t; + +typedef struct { + int __lock, __seq; + unsigned __readers; + int __writer; + unsigned long __owner; +} pthread_rwlock_t; +typedef struct { int __pshared, __kind; } pthread_rwlockattr_t; + +typedef struct { + int __lock, __seq; + unsigned __count, __waiting; +} pthread_barrier_t; +typedef struct { int __pshared; } pthread_barrierattr_t; + +typedef volatile int pthread_spinlock_t; +typedef int pthread_once_t; +typedef unsigned pthread_key_t; + +#define PTHREAD_CREATE_JOINABLE 0 +#define PTHREAD_CREATE_DETACHED 1 +#define PTHREAD_INHERIT_SCHED 0 +#define PTHREAD_EXPLICIT_SCHED 1 +#define PTHREAD_SCOPE_SYSTEM 0 +#define PTHREAD_SCOPE_PROCESS 1 +#define PTHREAD_PROCESS_PRIVATE 0 +#define PTHREAD_PROCESS_SHARED 1 + +#define PTHREAD_MUTEX_NORMAL 0 +#define PTHREAD_MUTEX_RECURSIVE 1 +#define PTHREAD_MUTEX_ERRORCHECK 2 +#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL +#define PTHREAD_PRIO_NONE 0 +#define PTHREAD_PRIO_INHERIT 1 +#define PTHREAD_PRIO_PROTECT 2 +#define PTHREAD_MUTEX_STALLED 0 +#define PTHREAD_MUTEX_ROBUST 1 +#if defined(__EMB_GNU) +#define PTHREAD_MUTEX_TIMED_NP PTHREAD_MUTEX_NORMAL +#define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE +#define PTHREAD_MUTEX_ERRORCHECK_NP PTHREAD_MUTEX_ERRORCHECK +#define PTHREAD_MUTEX_ADAPTIVE_NP PTHREAD_MUTEX_NORMAL +#define PTHREAD_RWLOCK_PREFER_READER_NP 0 +#define PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 2 +#endif + +#define PTHREAD_CANCEL_ENABLE 0 +#define PTHREAD_CANCEL_DISABLE 1 +#define PTHREAD_CANCEL_DEFERRED 0 +#define PTHREAD_CANCEL_ASYNCHRONOUS 1 +#define PTHREAD_CANCELED ((void *)-1) + +#define PTHREAD_ONCE_INIT 0 +#define PTHREAD_BARRIER_SERIAL_THREAD (-1) + +#define PTHREAD_MUTEX_INITIALIZER { 0, PTHREAD_MUTEX_NORMAL, 0, 0, 0 } +#if defined(__EMB_GNU) +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP \ + { 0, PTHREAD_MUTEX_RECURSIVE, 0, 0, 0 } +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP \ + { 0, PTHREAD_MUTEX_ERRORCHECK, 0, 0, 0 } +#endif +#define PTHREAD_COND_INITIALIZER { 0, CLOCK_REALTIME } +#define PTHREAD_RWLOCK_INITIALIZER { 0, 0, 0, 0, 0 } + +/* threads */ +int pthread_create(pthread_t *__restrict t, const pthread_attr_t *__restrict a, + void *(*fn)(void *), void *__restrict arg); +int pthread_join(pthread_t t, void **result); +int pthread_detach(pthread_t t); +#ifdef __cplusplus +[[noreturn]] void pthread_exit(void *result); +#else +_Noreturn void pthread_exit(void *result); +#endif +pthread_t pthread_self(void); +int pthread_equal(pthread_t a, pthread_t b); +int pthread_once(pthread_once_t *once, void (*fn)(void)); +int pthread_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)); +int pthread_getconcurrency(void); +int pthread_setconcurrency(int level); +int pthread_getschedparam(pthread_t t, int *__restrict policy, + struct sched_param *__restrict param); +int pthread_setschedparam(pthread_t t, int policy, + const struct sched_param *param); +int pthread_setschedprio(pthread_t t, int prio); +int pthread_getcpuclockid(pthread_t t, clockid_t *clock); + +/* cancellation: there are no cancellation points, so a cancel request is + * refused (ENOTSUP) and the state calls only keep their state */ +int pthread_cancel(pthread_t t); +int pthread_setcancelstate(int state, int *old); +int pthread_setcanceltype(int type, int *old); +void pthread_testcancel(void); + +/* attributes */ +int pthread_attr_init(pthread_attr_t *a); +int pthread_attr_destroy(pthread_attr_t *a); +int pthread_attr_getdetachstate(const pthread_attr_t *a, int *s); +int pthread_attr_setdetachstate(pthread_attr_t *a, int s); +int pthread_attr_getstacksize(const pthread_attr_t *__restrict a, + size_t *__restrict n); +int pthread_attr_setstacksize(pthread_attr_t *a, size_t n); +int pthread_attr_getguardsize(const pthread_attr_t *__restrict a, + size_t *__restrict n); +int pthread_attr_setguardsize(pthread_attr_t *a, size_t n); +int pthread_attr_getstack(const pthread_attr_t *__restrict a, + void **__restrict addr, size_t *__restrict n); +int pthread_attr_setstack(pthread_attr_t *a, void *addr, size_t n); +int pthread_attr_getinheritsched(const pthread_attr_t *__restrict a, + int *__restrict s); +int pthread_attr_setinheritsched(pthread_attr_t *a, int s); +int pthread_attr_getschedpolicy(const pthread_attr_t *__restrict a, + int *__restrict p); +int pthread_attr_setschedpolicy(pthread_attr_t *a, int p); +int pthread_attr_getschedparam(const pthread_attr_t *__restrict a, + struct sched_param *__restrict p); +int pthread_attr_setschedparam(pthread_attr_t *__restrict a, + const struct sched_param *__restrict p); +int pthread_attr_getscope(const pthread_attr_t *__restrict a, + int *__restrict s); +int pthread_attr_setscope(pthread_attr_t *a, int s); + +/* mutexes */ +int pthread_mutex_init(pthread_mutex_t *__restrict m, + const pthread_mutexattr_t *__restrict a); +int pthread_mutex_destroy(pthread_mutex_t *m); +int pthread_mutex_lock(pthread_mutex_t *m); +int pthread_mutex_trylock(pthread_mutex_t *m); +int pthread_mutex_timedlock(pthread_mutex_t *__restrict m, + const struct timespec *__restrict abstime); +int pthread_mutex_clocklock(pthread_mutex_t *__restrict m, clockid_t clock, + const struct timespec *__restrict abstime); +int pthread_mutex_unlock(pthread_mutex_t *m); +int pthread_mutex_consistent(pthread_mutex_t *m); +int pthread_mutex_getprioceiling(const pthread_mutex_t *__restrict m, + int *__restrict ceiling); +int pthread_mutex_setprioceiling(pthread_mutex_t *__restrict m, int ceiling, + int *__restrict old); +int pthread_mutexattr_init(pthread_mutexattr_t *a); +int pthread_mutexattr_destroy(pthread_mutexattr_t *a); +int pthread_mutexattr_gettype(const pthread_mutexattr_t *__restrict a, + int *__restrict t); +int pthread_mutexattr_settype(pthread_mutexattr_t *a, int t); +int pthread_mutexattr_getpshared(const pthread_mutexattr_t *__restrict a, + int *__restrict s); +int pthread_mutexattr_setpshared(pthread_mutexattr_t *a, int s); +int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *__restrict a, + int *__restrict p); +int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int p); +int pthread_mutexattr_getrobust(const pthread_mutexattr_t *__restrict a, + int *__restrict r); +int pthread_mutexattr_setrobust(pthread_mutexattr_t *a, int r); + +/* condition variables */ +int pthread_cond_init(pthread_cond_t *__restrict c, + const pthread_condattr_t *__restrict a); +int pthread_cond_destroy(pthread_cond_t *c); +int pthread_cond_signal(pthread_cond_t *c); +int pthread_cond_broadcast(pthread_cond_t *c); +int pthread_cond_wait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m); +int pthread_cond_timedwait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m, + const struct timespec *__restrict abstime); +int pthread_cond_clockwait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m, clockid_t clock, + const struct timespec *__restrict abstime); +int pthread_condattr_init(pthread_condattr_t *a); +int pthread_condattr_destroy(pthread_condattr_t *a); +int pthread_condattr_getclock(const pthread_condattr_t *__restrict a, + clockid_t *__restrict c); +int pthread_condattr_setclock(pthread_condattr_t *a, clockid_t c); +int pthread_condattr_getpshared(const pthread_condattr_t *__restrict a, + int *__restrict s); +int pthread_condattr_setpshared(pthread_condattr_t *a, int s); + +/* read-write locks */ +int pthread_rwlock_init(pthread_rwlock_t *__restrict l, + const pthread_rwlockattr_t *__restrict a); +int pthread_rwlock_destroy(pthread_rwlock_t *l); +int pthread_rwlock_rdlock(pthread_rwlock_t *l); +int pthread_rwlock_tryrdlock(pthread_rwlock_t *l); +int pthread_rwlock_timedrdlock(pthread_rwlock_t *__restrict l, + const struct timespec *__restrict abstime); +int pthread_rwlock_clockrdlock(pthread_rwlock_t *__restrict l, clockid_t clock, + const struct timespec *__restrict abstime); +int pthread_rwlock_wrlock(pthread_rwlock_t *l); +int pthread_rwlock_trywrlock(pthread_rwlock_t *l); +int pthread_rwlock_timedwrlock(pthread_rwlock_t *__restrict l, + const struct timespec *__restrict abstime); +int pthread_rwlock_clockwrlock(pthread_rwlock_t *__restrict l, clockid_t clock, + const struct timespec *__restrict abstime); +int pthread_rwlock_unlock(pthread_rwlock_t *l); +int pthread_rwlockattr_init(pthread_rwlockattr_t *a); +int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a); +int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *__restrict a, + int *__restrict s); +int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s); + +/* barriers and spin locks */ +int pthread_barrier_init(pthread_barrier_t *__restrict b, + const pthread_barrierattr_t *__restrict a, + unsigned count); +int pthread_barrier_destroy(pthread_barrier_t *b); +int pthread_barrier_wait(pthread_barrier_t *b); +int pthread_barrierattr_init(pthread_barrierattr_t *a); +int pthread_barrierattr_destroy(pthread_barrierattr_t *a); +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict a, + int *__restrict s); +int pthread_barrierattr_setpshared(pthread_barrierattr_t *a, int s); +int pthread_spin_init(pthread_spinlock_t *l, int pshared); +int pthread_spin_destroy(pthread_spinlock_t *l); +int pthread_spin_lock(pthread_spinlock_t *l); +int pthread_spin_trylock(pthread_spinlock_t *l); +int pthread_spin_unlock(pthread_spinlock_t *l); + +/* thread-specific data */ +int pthread_key_create(pthread_key_t *key, void (*dtor)(void *)); +int pthread_key_delete(pthread_key_t key); +void *pthread_getspecific(pthread_key_t key); +int pthread_setspecific(pthread_key_t key, const void *value); + +#if defined(__EMB_GNU) +int pthread_setname_np(pthread_t t, const char *name); +int pthread_getname_np(pthread_t t, char *name, size_t n); +int pthread_tryjoin_np(pthread_t t, void **result); +int pthread_timedjoin_np(pthread_t t, void **result, + const struct timespec *abstime); +int pthread_yield(void); +#endif + +/* 's, declared here too, as POSIX lists them in both */ +int pthread_kill(pthread_t t, int sig); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/pty.h b/lib/libc/include/pty.h new file mode 100644 index 000000000..38ce76774 --- /dev/null +++ b/lib/libc/include/pty.h @@ -0,0 +1,22 @@ +/* -- the BSD pseudo-terminal pair (src/posix/pty.c) */ +#ifndef _PTY_H +#define _PTY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int openpty(int *master, int *slave, char *name, const struct termios *t, + const struct winsize *ws); +pid_t forkpty(int *master, char *name, const struct termios *t, + const struct winsize *ws); +int login_tty(int fd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/pwd.h b/lib/libc/include/pwd.h new file mode 100644 index 000000000..6eada428e --- /dev/null +++ b/lib/libc/include/pwd.h @@ -0,0 +1,32 @@ +/* -- the user database: /etc/passwd (src/posix/pwd.c) */ +#ifndef _PWD_H +#define _PWD_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct passwd { + char *pw_name, *pw_passwd; + uid_t pw_uid; + gid_t pw_gid; + char *pw_gecos, *pw_dir, *pw_shell; +}; + +struct passwd *getpwnam(const char *name); +struct passwd *getpwuid(uid_t uid); +int getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t n, + struct passwd **result); +int getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t n, + struct passwd **result); +struct passwd *getpwent(void); +void setpwent(void); +void endpwent(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/regex.h b/lib/libc/include/regex.h new file mode 100644 index 000000000..39c7072e5 --- /dev/null +++ b/lib/libc/include/regex.h @@ -0,0 +1,68 @@ +/* -- POSIX regular expressions, basic and extended, with + * glibc's numbers and GNU's common extensions: \+ \? \| in basic syntax, + * \< \> \b \B \w \W \s \S \` \', back-references in both syntaxes, and + * REG_STARTEND. Matching is POSIX's: the leftmost match, and of those + * the longest. */ +#ifndef _REGEX_H +#define _REGEX_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int regoff_t; + +typedef struct { + size_t re_nsub; /* how many parenthesized subexpressions */ + void *__prog; + int __cflags; +} regex_t; + +typedef struct { + regoff_t rm_so; /* where the match starts, -1 for none */ + regoff_t rm_eo; /* one past where it ends */ +} regmatch_t; + +#define REG_EXTENDED 1 +#define REG_ICASE 2 +#define REG_NEWLINE 4 +#define REG_NOSUB 8 + +#define REG_NOTBOL 1 +#define REG_NOTEOL 2 +#define REG_STARTEND 4 + +#define REG_NOERROR 0 +#define REG_NOMATCH 1 +#define REG_BADPAT 2 +#define REG_ECOLLATE 3 +#define REG_ECTYPE 4 +#define REG_EESCAPE 5 +#define REG_ESUBREG 6 +#define REG_EBRACK 7 +#define REG_EPAREN 8 +#define REG_EBRACE 9 +#define REG_BADBR 10 +#define REG_ERANGE 11 +#define REG_ESPACE 12 +#define REG_BADRPT 13 +#define REG_EEND 14 +#define REG_ESIZE 15 +#define REG_ERPAREN 16 + +int regcomp(regex_t *__restrict re, const char *__restrict pattern, int cflags); +int regexec(const regex_t *__restrict re, const char *__restrict s, + size_t nmatch, regmatch_t pmatch[__restrict], int eflags); +size_t regerror(int err, const regex_t *__restrict re, char *__restrict buf, + size_t n); +void regfree(regex_t *re); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sched.h b/lib/libc/include/sched.h new file mode 100644 index 000000000..4ff3fb10e --- /dev/null +++ b/lib/libc/include/sched.h @@ -0,0 +1,27 @@ +/* -- giving up the processor, and the scheduling policies' + * names. The priority ranges are the policies' own definitions (Linux's), + * not a question for the kernel. */ +#ifndef _SCHED_H +#define _SCHED_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCHED_OTHER 0 +#define SCHED_FIFO 1 +#define SCHED_RR 2 + +struct sched_param { int sched_priority; }; + +int sched_yield(void); +int sched_get_priority_max(int policy); +int sched_get_priority_min(int policy); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/search.h b/lib/libc/include/search.h new file mode 100644 index 000000000..cf6aecb68 --- /dev/null +++ b/lib/libc/include/search.h @@ -0,0 +1,59 @@ +/* -- POSIX's search tables: binary trees (tsearch), one hash + * table (hsearch, and GNU's reentrant _r forms), linear search and + * doubly linked queues (src/posix/search.c). */ +#ifndef _SEARCH_H +#define _SEARCH_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { FIND, ENTER } ACTION; +typedef struct entry { char *key; void *data; } ENTRY; + +int hcreate(size_t nel); +void hdestroy(void); +ENTRY *hsearch(ENTRY item, ACTION action); + +#if defined(__EMB_GNU) +struct hsearch_data { + struct __htab *__tab; + size_t __unused1, __unused2; +}; +int hcreate_r(size_t nel, struct hsearch_data *htab); +void hdestroy_r(struct hsearch_data *htab); +int hsearch_r(ENTRY item, ACTION action, ENTRY **ret, + struct hsearch_data *htab); +#endif + +/* twalk's visits: before a node's children, between them, after them, + * and the one visit of a node with none */ +typedef enum { preorder, postorder, endorder, leaf } VISIT; + +void *tsearch(const void *key, void **root, + int (*cmp)(const void *, const void *)); +void *tfind(const void *key, void *const *root, + int (*cmp)(const void *, const void *)); +void *tdelete(const void *__restrict key, void **__restrict root, + int (*cmp)(const void *, const void *)); +void twalk(const void *root, void (*action)(const void *, VISIT, int)); +#if defined(__EMB_GNU) +void tdestroy(void *root, void (*free_key)(void *)); +#endif + +void *lsearch(const void *key, void *base, size_t *nel, size_t width, + int (*cmp)(const void *, const void *)); +void *lfind(const void *key, const void *base, size_t *nel, size_t width, + int (*cmp)(const void *, const void *)); + +void insque(void *elem, void *pred); +void remque(void *elem); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/semaphore.h b/lib/libc/include/semaphore.h new file mode 100644 index 000000000..a69f7872b --- /dev/null +++ b/lib/libc/include/semaphore.h @@ -0,0 +1,38 @@ +/* -- POSIX's counting semaphores, on the futex the locks + * use. Unnamed ones only: a named semaphore (sem_open) and a process- + * shared one (sem_init's pshared) need memory another process maps and + * a futex it can wake, which this library does not have -- sem_open + * fails with ENOSYS and sem_init with pshared != 0 with ENOTSUP. */ +#ifndef _SEMAPHORE_H +#define _SEMAPHORE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { int __value, __waiters; } sem_t; + +#define SEM_FAILED ((sem_t *)0) + +int sem_init(sem_t *s, int pshared, unsigned value); +int sem_destroy(sem_t *s); +int sem_wait(sem_t *s); +int sem_trywait(sem_t *s); +int sem_timedwait(sem_t *__restrict s, const struct timespec *__restrict abstime); +int sem_clockwait(sem_t *__restrict s, clockid_t clock, + const struct timespec *__restrict abstime); +int sem_post(sem_t *s); +int sem_getvalue(sem_t *__restrict s, int *__restrict value); +sem_t *sem_open(const char *name, int oflag, ...); +int sem_close(sem_t *s); +int sem_unlink(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/signal.h b/lib/libc/include/signal.h index fff163b26..ab28ba935 100644 --- a/lib/libc/include/signal.h +++ b/lib/libc/include/signal.h @@ -2,8 +2,8 @@ * * The C standard's signal handling, which is a much smaller thing than * POSIX's: six signal numbers, one handler per signal, and a very short - * list of what a handler may legally do. Everything else -- sigaction, - * masks, siginfo, waiting -- is POSIX and is not here. + * list of what a handler may legally do. POSIX's -- sigaction, masks, + * siginfo, waiting -- follows it, at the end of this header. * * What a handler may do is the part worth knowing, because nearly every * handler in the wild breaks it. C11 §7.14.1.1p5 allows a handler to @@ -21,6 +21,8 @@ #ifndef _SIGNAL_H #define _SIGNAL_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -51,6 +53,178 @@ void (*signal(int sig, void (*handler)(int)))(int); /* Send a signal to this program. Returns zero on success. */ int raise(int sig); +/* ---- POSIX's signals -------------------------------------------------------- + * + * Where the OS delivers signals (Linux), they are delivered: sigaction + * installs a handler with the kernel, and `signal` and `raise` above go + * through it. Where it does not, sigaction fails with ENOSYS and C's + * model above is what there is. The numbers are Linux's. */ +#if defined(__EMB_POSIX) +#include + +#define SIGHUP 1 +#define SIGQUIT 3 +#define SIGTRAP 5 +#define SIGIOT SIGABRT +#define SIGBUS 7 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPOLL SIGIO +#define SIGPWR 30 +#define SIGSYS 31 +/* glibc keeps 32 and 33 for its threads; this library's threads need + * none, but a program that counts from SIGRTMIN expects glibc's 34 */ +#define SIGRTMIN 34 +#define SIGRTMAX 64 +#define NSIG 65 +#define _NSIG NSIG + +#define SIG_HOLD ((void (*)(int))2) + +#include + +union sigval { int sival_int; void *sival_ptr; }; + +/* Linux's siginfo_t: 128 bytes, the three ints first, then per-signal + * fields at offset 16 on both 64-bit ABIs */ +typedef struct { + int si_signo, si_errno, si_code; + int __pad0; + union { + struct { pid_t si_pid; uid_t si_uid; int si_status; } __child; + struct { pid_t si_pid; uid_t si_uid; union sigval si_value; } __rt; + struct { void *si_addr; } __fault; + struct { long si_band; int si_fd; } __poll; + char __pad[112]; + } __u; +} siginfo_t; +#define si_pid __u.__child.si_pid +#define si_uid __u.__child.si_uid +#define si_status __u.__child.si_status +#define si_value __u.__rt.si_value +#define si_addr __u.__fault.si_addr +#define si_band __u.__poll.si_band +#define si_fd __u.__poll.si_fd + +/* si_code */ +#define SI_USER 0 +#define SI_KERNEL 0x80 +#define SI_QUEUE (-1) +#define SI_TIMER (-2) +#define SI_TKILL (-6) +#define CLD_EXITED 1 +#define CLD_KILLED 2 +#define CLD_DUMPED 3 +#define CLD_TRAPPED 4 +#define CLD_STOPPED 5 +#define CLD_CONTINUED 6 +#define SEGV_MAPERR 1 +#define SEGV_ACCERR 2 +#define BUS_ADRALN 1 +#define BUS_ADRERR 2 +#define FPE_INTDIV 1 +#define FPE_INTOVF 2 +#define FPE_FLTDIV 3 +#define ILL_ILLOPC 1 + +struct sigaction { + union { + void (*sa_handler)(int); + void (*sa_sigaction)(int, siginfo_t *, void *); + } __sa; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(void); +}; +#define sa_handler __sa.sa_handler +#define sa_sigaction __sa.sa_sigaction + +#define SA_NOCLDSTOP 0x00000001 +#define SA_NOCLDWAIT 0x00000002 +#define SA_SIGINFO 0x00000004 +#define SA_ONSTACK 0x08000000 +#define SA_RESTART 0x10000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 +#define SA_NOMASK SA_NODEFER +#define SA_ONESHOT SA_RESETHAND + +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 1 +#define SIG_SETMASK 2 + +typedef struct { + void *ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; +#define SS_ONSTACK 1 +#define SS_DISABLE 2 +#if defined(__aarch64__) +#define MINSIGSTKSZ 5120 +#define SIGSTKSZ 16384 +#else +#define MINSIGSTKSZ 2048 +#define SIGSTKSZ 8192 +#endif + +#ifndef _STRUCT_TIMESPEC_DECLARED +#define _STRUCT_TIMESPEC_DECLARED +struct timespec { time_t tv_sec; long tv_nsec; }; +#endif + +int kill(pid_t pid, int sig); +int killpg(pid_t pgrp, int sig); +int sigaction(int sig, const struct sigaction *__restrict act, + struct sigaction *__restrict old); +int sigprocmask(int how, const sigset_t *__restrict set, + sigset_t *__restrict old); +int sigpending(sigset_t *set); +int sigsuspend(const sigset_t *mask); +int sigwait(const sigset_t *__restrict set, int *__restrict sig); +int sigwaitinfo(const sigset_t *__restrict set, siginfo_t *__restrict info); +int sigtimedwait(const sigset_t *__restrict set, siginfo_t *__restrict info, + const struct timespec *__restrict timeout); +int sigaltstack(const stack_t *__restrict ss, stack_t *__restrict old); +int sigemptyset(sigset_t *set); +int sigfillset(sigset_t *set); +int sigaddset(sigset_t *set, int sig); +int sigdelset(sigset_t *set, int sig); +int sigismember(const sigset_t *set, int sig); +int siginterrupt(int sig, int flag); +void psignal(int sig, const char *msg); +void psiginfo(const siginfo_t *info, const char *msg); +int pthread_kill(unsigned long thread, int sig); +int pthread_sigmask(int how, const sigset_t *__restrict set, + sigset_t *__restrict old); +#endif + +#if defined(__EMB_GNU) +typedef void (*sighandler_t)(int); +typedef void (*sig_t)(int); +int sigisemptyset(const sigset_t *set); +int sigorset(sigset_t *dst, const sigset_t *a, const sigset_t *b); +int sigandset(sigset_t *dst, const sigset_t *a, const sigset_t *b); +extern const char *const sys_siglist[NSIG]; +#endif + #ifdef __cplusplus } #endif diff --git a/lib/libc/include/spawn.h b/lib/libc/include/spawn.h new file mode 100644 index 000000000..b870fbda1 --- /dev/null +++ b/lib/libc/include/spawn.h @@ -0,0 +1,85 @@ +/* -- posix_spawn: a new process running a program, set up by + * a list of file actions and attributes (src/posix/spawn.c), which is how + * a program starts another on a kernel that cannot fork. */ +#ifndef _SPAWN_H +#define _SPAWN_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define POSIX_SPAWN_RESETIDS 0x01 +#define POSIX_SPAWN_SETPGROUP 0x02 +#define POSIX_SPAWN_SETSIGDEF 0x04 +#define POSIX_SPAWN_SETSIGMASK 0x08 +#define POSIX_SPAWN_SETSCHEDPARAM 0x10 +#define POSIX_SPAWN_SETSCHEDULER 0x20 +#define POSIX_SPAWN_USEVFORK 0x40 +#define POSIX_SPAWN_SETSID 0x80 + +/* Opaque handles, which init allocates and destroy frees and sets back + * to NULL: POSIX lets them be anything, and a handle is what EmbLinkOS + * programs were written against (`posix_spawnattr_t a = NULL;`). A + * program that only uses the functions below cannot tell the difference. */ +typedef struct __spawn_actions *posix_spawn_file_actions_t; +typedef struct __spawn_attr *posix_spawnattr_t; + +int posix_spawn(pid_t *__restrict pid, const char *__restrict path, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *__restrict attr, + char *const argv[__restrict], char *const envp[__restrict]); +int posix_spawnp(pid_t *__restrict pid, const char *__restrict file, + const posix_spawn_file_actions_t *actions, + const posix_spawnattr_t *__restrict attr, + char *const argv[__restrict], char *const envp[__restrict]); + +int posix_spawn_file_actions_init(posix_spawn_file_actions_t *fa); +int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *fa); +int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *__restrict fa, + int fd, const char *__restrict path, + int oflag, mode_t mode); +int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *fa, int fd); +int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *fa, int fd, + int newfd); +/* POSIX.1-2024's, and their glibc names before that */ +int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t *__restrict fa, + const char *__restrict path); +int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t *fa, int fd); +int posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t *__restrict fa, + const char *__restrict path); +int posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *fa, + int fd); + +int posix_spawnattr_init(posix_spawnattr_t *a); +int posix_spawnattr_destroy(posix_spawnattr_t *a); +int posix_spawnattr_getflags(const posix_spawnattr_t *__restrict a, + short *__restrict flags); +int posix_spawnattr_setflags(posix_spawnattr_t *a, short flags); +int posix_spawnattr_getpgroup(const posix_spawnattr_t *__restrict a, + pid_t *__restrict pgroup); +int posix_spawnattr_setpgroup(posix_spawnattr_t *a, pid_t pgroup); +int posix_spawnattr_getsigdefault(const posix_spawnattr_t *__restrict a, + sigset_t *__restrict set); +int posix_spawnattr_setsigdefault(posix_spawnattr_t *__restrict a, + const sigset_t *__restrict set); +int posix_spawnattr_getsigmask(const posix_spawnattr_t *__restrict a, + sigset_t *__restrict set); +int posix_spawnattr_setsigmask(posix_spawnattr_t *__restrict a, + const sigset_t *__restrict set); +int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *__restrict a, + int *__restrict policy); +int posix_spawnattr_setschedpolicy(posix_spawnattr_t *a, int policy); +int posix_spawnattr_getschedparam(const posix_spawnattr_t *__restrict a, + struct sched_param *__restrict p); +int posix_spawnattr_setschedparam(posix_spawnattr_t *__restrict a, + const struct sched_param *__restrict p); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/stdio.h b/lib/libc/include/stdio.h index 5d8bf78ac..7ce35aa87 100644 --- a/lib/libc/include/stdio.h +++ b/lib/libc/include/stdio.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -138,6 +139,88 @@ int vfscanf(FILE *__restrict f, const char *__restrict fmt, va_list ap) int vsscanf(const char *__restrict s, const char *__restrict fmt, va_list ap) __fmt(scanf, 2, 0); +/* POSIX's, which ISO C leaves to the program (see ) */ +#if defined(__EMB_POSIX) +#ifndef _SSIZE_T_DECLARED +#define _SSIZE_T_DECLARED +typedef __PTRDIFF_TYPE__ ssize_t; +#endif +#ifndef _OFF_T_DECLARED +#define _OFF_T_DECLARED +typedef long off_t; +#endif +#define L_ctermid 9 +#define P_tmpdir "/tmp" + +FILE *fdopen(int fd, const char *mode); +int fileno(FILE *f); +FILE *fmemopen(void *__restrict buf, size_t size, const char *__restrict mode); +FILE *open_memstream(char **bufp, size_t *sizep); +ssize_t getline(char **__restrict line, size_t *__restrict n, + FILE *__restrict f); +ssize_t getdelim(char **__restrict line, size_t *__restrict n, int delim, + FILE *__restrict f); +int dprintf(int fd, const char *__restrict fmt, ...) __fmt(printf, 2, 3); +int vdprintf(int fd, const char *__restrict fmt, va_list ap) + __fmt(printf, 2, 0); +int asprintf(char **__restrict s, const char *__restrict fmt, ...) + __fmt(printf, 2, 3); +int vasprintf(char **__restrict s, const char *__restrict fmt, va_list ap) + __fmt(printf, 2, 0); +int fseeko(FILE *f, off_t off, int whence); +off_t ftello(FILE *f); +char *ctermid(char *s); +/* a command's output or input as a stream (src/posix/popen.c) */ +FILE *popen(const char *cmd, const char *mode); +int pclose(FILE *f); +int renameat(int fromfd, const char *from, int tofd, const char *to); + +/* One lock around many calls: the stream's lock nests, so a thread that + * holds it may still call printf on the stream. */ +void flockfile(FILE *f); +int ftrylockfile(FILE *f); +void funlockfile(FILE *f); +int getc_unlocked(FILE *f); +int getchar_unlocked(void); +int putc_unlocked(int c, FILE *f); +int putchar_unlocked(int c); +#endif + +#if defined(__EMB_GNU) +#ifndef _OFF64_T_DECLARED +#define _OFF64_T_DECLARED +typedef long long off64_t; +#endif +/* A stream on the caller's four functions: glibc's fopencookie. */ +typedef ssize_t cookie_read_function_t(void *cookie, char *buf, size_t n); +typedef ssize_t cookie_write_function_t(void *cookie, const char *buf, + size_t n); +typedef int cookie_seek_function_t(void *cookie, off64_t *off, int whence); +typedef int cookie_close_function_t(void *cookie); +typedef struct { + cookie_read_function_t *read; + cookie_write_function_t *write; + cookie_seek_function_t *seek; + cookie_close_function_t *close; +} cookie_io_functions_t; +FILE *fopencookie(void *cookie, const char *mode, cookie_io_functions_t io); + +int fgetc_unlocked(FILE *f); +int fputc_unlocked(int c, FILE *f); +int fflush_unlocked(FILE *f); +int feof_unlocked(FILE *f); +int ferror_unlocked(FILE *f); +void clearerr_unlocked(FILE *f); +int fileno_unlocked(FILE *f); +size_t fread_unlocked(void *__restrict p, size_t size, size_t n, + FILE *__restrict f); +size_t fwrite_unlocked(const void *__restrict p, size_t size, size_t n, + FILE *__restrict f); +int fputs_unlocked(const char *__restrict s, FILE *__restrict f); +void setbuffer(FILE *f, char *buf, size_t n); +void setlinebuf(FILE *f); +#endif + #ifdef __cplusplus } #endif diff --git a/lib/libc/include/stdlib.h b/lib/libc/include/stdlib.h index 60ac91fbe..0862942d0 100644 --- a/lib/libc/include/stdlib.h +++ b/lib/libc/include/stdlib.h @@ -3,6 +3,7 @@ #define _STDLIB_H #include +#include #ifdef __cplusplus extern "C" { @@ -105,8 +106,61 @@ div_t div(int num, int den); ldiv_t ldiv(long num, long den); lldiv_t lldiv(long long num, long long den); +/* POSIX's, which ISO C leaves to the program (see ) */ +#if defined(__EMB_POSIX) +int putenv(char *s); +int mkstemp(char *tmpl); +int mkostemp(char *tmpl, int flags); +char *mkdtemp(char *tmpl); +char *mktemp(char *tmpl); +char *realpath(const char *__restrict path, char *__restrict resolved); +int posix_memalign(void **p, size_t align, size_t n); +void *reallocarray(void *p, size_t n, size_t size); +void qsort_r(void *base, size_t n, size_t size, + int (*cmp)(const void *, const void *, void *), void *arg); +int getsubopt(char **opt, char *const *tokens, char **value); + +/* pseudo-terminals (src/posix/pty.c) */ +int posix_openpt(int flags); +int grantpt(int fd); +int unlockpt(int fd); +char *ptsname(int fd); + +/* the historical generators, each giving glibc's sequence for a seed */ +long random(void); +void srandom(unsigned seed); +char *initstate(unsigned seed, char *state, size_t n); +char *setstate(char *state); +int rand_r(unsigned *seed); +double drand48(void); +double erand48(unsigned short x[3]); +long lrand48(void); +long nrand48(unsigned short x[3]); +long mrand48(void); +long jrand48(unsigned short x[3]); +void srand48(long seed); +unsigned short *seed48(unsigned short s[3]); +void lcong48(unsigned short p[7]); +#endif + +#if defined(__EMB_GNU) +int clearenv(void); +int ptsname_r(int fd, char *buf, size_t n); +int mkstemps(char *tmpl, int suffix); +int mkostemps(char *tmpl, int suffix, int flags); +void *memalign(size_t align, size_t n); +void *valloc(size_t n); +const char *getprogname(void); +void setprogname(const char *name); +#endif + #ifdef __cplusplus } #endif +/* alloca, which glibc's declares by default */ +#if defined(__EMB_GNU) +#include +#endif + #endif diff --git a/lib/libc/include/string.h b/lib/libc/include/string.h index 191e515b6..eb2feafa0 100644 --- a/lib/libc/include/string.h +++ b/lib/libc/include/string.h @@ -8,6 +8,7 @@ #define _STRING_H #include +#include #ifdef __cplusplus extern "C" { @@ -49,8 +50,35 @@ char *stpcpy(char *__restrict d, const char *__restrict s); char *stpncpy(char *__restrict d, const char *__restrict s, size_t n); void *mempcpy(void *__restrict d, const void *__restrict s, size_t n); +/* POSIX's, which ISO C leaves to the program (see ) */ +#if defined(__EMB_POSIX) +char *strtok_r(char *__restrict s, const char *__restrict sep, + char **__restrict save); +/* XSI's form, returning 0 or an errno -- not GNU's, which returns the + * message and may ignore the buffer */ +int strerror_r(int errnum, char *buf, size_t n); +char *strsep(char **sp, const char *sep); +size_t strlcpy(char *__restrict d, const char *__restrict s, size_t n); +size_t strlcat(char *__restrict d, const char *__restrict s, size_t n); +void *memmem(const void *hay, size_t hn, const void *needle, size_t nn); +char *strsignal(int sig); +#endif +#if defined(__EMB_GNU) +char *strcasestr(const char *h, const char *n); +void *memrchr(const void *s, int c, size_t n); +char *strchrnul(const char *s, int c); +void explicit_bzero(void *s, size_t n); +int strverscmp(const char *a, const char *b); +#endif + #ifdef __cplusplus } #endif +/* strcasecmp and the other BSD names, which glibc's and newlib's + * both bring along by default -- and code relies on it */ +#if defined(__EMB_GNU) +#include +#endif + #endif diff --git a/lib/libc/include/sys/cdefs.h b/lib/libc/include/sys/cdefs.h new file mode 100644 index 000000000..5e5b2f249 --- /dev/null +++ b/lib/libc/include/sys/cdefs.h @@ -0,0 +1,30 @@ +/* -- the BSD and glibc header macros ports include for */ +#ifndef _SYS_CDEFS_H +#define _SYS_CDEFS_H + +#ifdef __cplusplus +#define __BEGIN_DECLS extern "C" { +#define __END_DECLS } +#else +#define __BEGIN_DECLS +#define __END_DECLS +#endif +#define __P(args) args +#define __PMT(args) args +#define __THROW +#define __CONCAT(a, b) a##b +#define __STRING(x) #x +#define __unused __attribute__((__unused__)) +#define __dead2 __attribute__((__noreturn__)) +#define __pure2 __attribute__((__const__)) +#define __packed __attribute__((__packed__)) +#define __aligned(n) __attribute__((__aligned__(n))) +#define __printflike(f, a) __attribute__((__format__(__printf__, f, a))) +#define __nonnull(p) __attribute__((__nonnull__ p)) +#define __wur __attribute__((__warn_unused_result__)) +#define __attribute_pure__ __attribute__((__pure__)) +#define __attribute_const__ __attribute__((__const__)) +#define __glibc_unlikely(c) __builtin_expect((c), 0) +#define __glibc_likely(c) __builtin_expect((c), 1) + +#endif diff --git a/lib/libc/include/sys/file.h b/lib/libc/include/sys/file.h new file mode 100644 index 000000000..a94602fbe --- /dev/null +++ b/lib/libc/include/sys/file.h @@ -0,0 +1,22 @@ +/* -- flock(2)'s whole-file advisory locks */ +#ifndef _SYS_FILE_H +#define _SYS_FILE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOCK_SH 1 +#define LOCK_EX 2 +#define LOCK_NB 4 +#define LOCK_UN 8 + +int flock(int fd, int op); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/ioctl.h b/lib/libc/include/sys/ioctl.h new file mode 100644 index 000000000..281d000f5 --- /dev/null +++ b/lib/libc/include/sys/ioctl.h @@ -0,0 +1,45 @@ +/* -- device control. The requests are Linux's, and a + * backend whose kernel has others translates those it supports. */ +#ifndef _SYS_IOCTL_H +#define _SYS_IOCTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct winsize { + unsigned short ws_row, ws_col, ws_xpixel, ws_ypixel; +}; + +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 +#define TCSBRK 0x5409 +#define TCXONC 0x540A +#define TCFLSH 0x540B +#define TIOCEXCL 0x540C +#define TIOCNXCL 0x540D +#define TIOCSCTTY 0x540E +#define TIOCGPGRP 0x540F +#define TIOCSPGRP 0x5410 +#define TIOCOUTQ 0x5411 +#define TIOCSTI 0x5412 +#define TIOCGWINSZ 0x5413 +#define TIOCSWINSZ 0x5414 +#define FIONREAD 0x541B +#define TIOCINQ FIONREAD +#define TIOCNOTTY 0x5422 +#define FIONBIO 0x5421 +#define TIOCGSID 0x5429 +#define FIONCLEX 0x5450 +#define FIOCLEX 0x5451 +#define FIOASYNC 0x5452 + +int ioctl(int fd, unsigned long request, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/mman.h b/lib/libc/include/sys/mman.h new file mode 100644 index 000000000..aec5a86bd --- /dev/null +++ b/lib/libc/include/sys/mman.h @@ -0,0 +1,57 @@ +/* -- mapping files and memory (src/posix/mman.c) */ +#ifndef _SYS_MMAN_H +#define _SYS_MMAN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_SHARED 0x01 +#define MAP_PRIVATE 0x02 +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS +#define MAP_NORESERVE 0x4000 +#define MAP_FAILED ((void *)-1) + +#define MS_ASYNC 1 +#define MS_INVALIDATE 2 +#define MS_SYNC 4 + +#define MADV_NORMAL 0 +#define MADV_RANDOM 1 +#define MADV_SEQUENTIAL 2 +#define MADV_WILLNEED 3 +#define MADV_DONTNEED 4 +#define POSIX_MADV_NORMAL 0 +#define POSIX_MADV_RANDOM 1 +#define POSIX_MADV_SEQUENTIAL 2 +#define POSIX_MADV_WILLNEED 3 +#define POSIX_MADV_DONTNEED 4 + +#define MCL_CURRENT 1 +#define MCL_FUTURE 2 + +void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off); +int munmap(void *addr, size_t len); +int mprotect(void *addr, size_t len, int prot); +int msync(void *addr, size_t len, int flags); +int madvise(void *addr, size_t len, int advice); +int posix_madvise(void *addr, size_t len, int advice); +int mlock(const void *addr, size_t len); +int munlock(const void *addr, size_t len); +int shm_open(const char *name, int flags, mode_t mode); +int shm_unlink(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/param.h b/lib/libc/include/sys/param.h new file mode 100644 index 000000000..19e668439 --- /dev/null +++ b/lib/libc/include/sys/param.h @@ -0,0 +1,31 @@ +/* -- BSD's system constants and a few macros */ +#ifndef _SYS_PARAM_H +#define _SYS_PARAM_H + +#include +#include + +#define MAXPATHLEN PATH_MAX +#define MAXHOSTNAMELEN 64 +#define MAXSYMLINKS 40 +#define NOFILE 256 +#define NGROUPS NGROUPS_MAX +#define NBBY 8 +#define DEV_BSIZE 512 + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#define howmany(x, y) (((x) + ((y) - 1)) / (y)) +#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) +#define powerof2(x) ((((x) - 1) & (x)) == 0) +#define nitems(a) (sizeof(a) / sizeof((a)[0])) +#define setbit(a, i) ((a)[(i) / NBBY] |= 1 << ((i) % NBBY)) +#define clrbit(a, i) ((a)[(i) / NBBY] &= ~(1 << ((i) % NBBY))) +#define isset(a, i) ((a)[(i) / NBBY] & (1 << ((i) % NBBY))) +#define isclr(a, i) (((a)[(i) / NBBY] & (1 << ((i) % NBBY))) == 0) + +#endif diff --git a/lib/libc/include/sys/random.h b/lib/libc/include/sys/random.h new file mode 100644 index 000000000..960359cea --- /dev/null +++ b/lib/libc/include/sys/random.h @@ -0,0 +1,21 @@ +/* -- bytes from the OS's entropy */ +#ifndef _SYS_RANDOM_H +#define _SYS_RANDOM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define GRND_NONBLOCK 1 +#define GRND_RANDOM 2 + +ssize_t getrandom(void *buf, size_t n, unsigned flags); +int getentropy(void *buf, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/resource.h b/lib/libc/include/sys/resource.h new file mode 100644 index 000000000..9e8288c79 --- /dev/null +++ b/lib/libc/include/sys/resource.h @@ -0,0 +1,49 @@ +/* -- limits and usage (src/posix/sysconf.c) */ +#ifndef _SYS_RESOURCE_H +#define _SYS_RESOURCE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long long rlim_t; +#define RLIM_INFINITY (~(rlim_t)0) +#define RLIM_SAVED_MAX RLIM_INFINITY +#define RLIM_SAVED_CUR RLIM_INFINITY + +#define RLIMIT_CPU 0 +#define RLIMIT_FSIZE 1 +#define RLIMIT_DATA 2 +#define RLIMIT_STACK 3 +#define RLIMIT_CORE 4 +#define RLIMIT_RSS 5 +#define RLIMIT_NPROC 6 +#define RLIMIT_NOFILE 7 +#define RLIMIT_MEMLOCK 8 +#define RLIMIT_AS 9 +#define RLIM_NLIMITS 16 + +struct rlimit { rlim_t rlim_cur, rlim_max; }; + +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN (-1) +#define RUSAGE_THREAD 1 + +struct rusage { + struct timeval ru_utime, ru_stime; + long ru_maxrss, ru_ixrss, ru_idrss, ru_isrss, ru_minflt, ru_majflt; + long ru_nswap, ru_inblock, ru_oublock, ru_msgsnd, ru_msgrcv; + long ru_nsignals, ru_nvcsw, ru_nivcsw; +}; + +int getrlimit(int resource, struct rlimit *lim); +int setrlimit(int resource, const struct rlimit *lim); +int getrusage(int who, struct rusage *ru); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/select.h b/lib/libc/include/sys/select.h new file mode 100644 index 000000000..a235770ee --- /dev/null +++ b/lib/libc/include/sys/select.h @@ -0,0 +1,47 @@ +/* -- select and its descriptor sets, over poll + * (src/posix/poll.c) */ +#ifndef _SYS_SELECT_H +#define _SYS_SELECT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _STRUCT_TIMEVAL_DECLARED +#define _STRUCT_TIMEVAL_DECLARED +struct timeval { time_t tv_sec; suseconds_t tv_usec; }; +#endif +#ifndef _STRUCT_TIMESPEC_DECLARED +#define _STRUCT_TIMESPEC_DECLARED +struct timespec { time_t tv_sec; long tv_nsec; }; +#endif + +#define FD_SETSIZE 1024 + +typedef struct { + unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))]; +} fd_set; + +#define __FD_WORD(fd) ((unsigned)(fd) / (8 * sizeof(unsigned long))) +#define __FD_BIT(fd) (1UL << ((unsigned)(fd) % (8 * sizeof(unsigned long)))) +#define FD_ZERO(s) do { \ + for (unsigned __i = 0; __i < sizeof((s)->fds_bits) / sizeof(unsigned long); __i++) \ + (s)->fds_bits[__i] = 0; \ +} while (0) +#define FD_SET(fd, s) ((void)((s)->fds_bits[__FD_WORD(fd)] |= __FD_BIT(fd))) +#define FD_CLR(fd, s) ((void)((s)->fds_bits[__FD_WORD(fd)] &= ~__FD_BIT(fd))) +#define FD_ISSET(fd, s) (((s)->fds_bits[__FD_WORD(fd)] & __FD_BIT(fd)) != 0) + +int select(int n, fd_set *__restrict r, fd_set *__restrict w, + fd_set *__restrict e, struct timeval *__restrict timeout); +int pselect(int n, fd_set *__restrict r, fd_set *__restrict w, + fd_set *__restrict e, const struct timespec *__restrict timeout, + const unsigned long long *__restrict sigmask); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/socket.h b/lib/libc/include/sys/socket.h new file mode 100644 index 000000000..39a2c768c --- /dev/null +++ b/lib/libc/include/sys/socket.h @@ -0,0 +1,159 @@ +/* -- sockets (src/posix/socket.c). Linux's layouts and + * numbers, which os/backend.h makes the seam's. */ +#ifndef _SYS_SOCKET_H +#define _SYS_SOCKET_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int socklen_t; +typedef unsigned short sa_family_t; + +#ifndef _STRUCT_IOVEC_DECLARED +#define _STRUCT_IOVEC_DECLARED +struct iovec { void *iov_base; size_t iov_len; }; +#endif + +struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; +}; + +struct sockaddr_storage { + sa_family_t ss_family; + char __ss_pad[118]; + unsigned long __ss_align; +}; + +struct msghdr { + void *msg_name; + socklen_t msg_namelen; + struct iovec *msg_iov; + size_t msg_iovlen; + void *msg_control; + size_t msg_controllen; + int msg_flags; +}; + +struct cmsghdr { + size_t cmsg_len; + int cmsg_level, cmsg_type; +}; + +#define __CMSG_ALIGN(n) (((n) + sizeof(size_t) - 1) & ~(sizeof(size_t) - 1)) +#define CMSG_DATA(c) ((unsigned char *)((struct cmsghdr *)(c) + 1)) +#define CMSG_SPACE(n) (__CMSG_ALIGN(n) + __CMSG_ALIGN(sizeof(struct cmsghdr))) +#define CMSG_LEN(n) (__CMSG_ALIGN(sizeof(struct cmsghdr)) + (n)) +#define CMSG_FIRSTHDR(m) ((size_t)(m)->msg_controllen >= sizeof(struct cmsghdr) \ + ? (struct cmsghdr *)(m)->msg_control : (struct cmsghdr *)0) +#define CMSG_NXTHDR(m, c) \ + ((c)->cmsg_len < sizeof(struct cmsghdr) || \ + __CMSG_ALIGN((c)->cmsg_len) + sizeof(struct cmsghdr) >= \ + (size_t)((unsigned char *)(m)->msg_control + (m)->msg_controllen - \ + (unsigned char *)(c)) \ + ? (struct cmsghdr *)0 \ + : (struct cmsghdr *)((unsigned char *)(c) + __CMSG_ALIGN((c)->cmsg_len))) + +struct linger { int l_onoff, l_linger; }; + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define SOCK_RAW 3 +#define SOCK_RDM 4 +#define SOCK_SEQPACKET 5 +#define SOCK_NONBLOCK 04000 +#if __INT_MAX__ > 0x7fff +#define SOCK_CLOEXEC 02000000 +#else +#define SOCK_CLOEXEC 0 /* no exec where an int is 16 bits */ +#endif + +#define AF_UNSPEC 0 +#define AF_UNIX 1 +#define AF_LOCAL AF_UNIX +#define AF_INET 2 +#define AF_INET6 10 +#define AF_PACKET 17 +#define PF_UNSPEC AF_UNSPEC +#define PF_UNIX AF_UNIX +#define PF_LOCAL AF_LOCAL +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 + +#define SOL_SOCKET 1 +#define SO_DEBUG 1 +#define SO_REUSEADDR 2 +#define SO_TYPE 3 +#define SO_ERROR 4 +#define SO_DONTROUTE 5 +#define SO_BROADCAST 6 +#define SO_SNDBUF 7 +#define SO_RCVBUF 8 +#define SO_KEEPALIVE 9 +#define SO_OOBINLINE 10 +#define SO_LINGER 13 +#define SO_REUSEPORT 15 +#define SO_RCVLOWAT 18 +#define SO_SNDLOWAT 19 +#define SO_RCVTIMEO 20 +#define SO_SNDTIMEO 21 +#define SO_ACCEPTCONN 30 +#define SO_DOMAIN 39 +#define SO_PROTOCOL 38 + +#define SCM_RIGHTS 1 + +#define MSG_OOB 0x0001 +#define MSG_PEEK 0x0002 +#define MSG_DONTROUTE 0x0004 +#define MSG_CTRUNC 0x0008 +#define MSG_TRUNC 0x0020 +#define MSG_DONTWAIT 0x0040 +#define MSG_EOR 0x0080 +#define MSG_WAITALL 0x0100 +#define MSG_NOSIGNAL 0x4000 +#define MSG_CMSG_CLOEXEC 0x40000000 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +#define SOMAXCONN 4096 + +int accept(int fd, struct sockaddr *__restrict addr, + socklen_t *__restrict len); +int accept4(int fd, struct sockaddr *__restrict addr, + socklen_t *__restrict len, int flags); +int bind(int fd, const struct sockaddr *addr, socklen_t len); +int connect(int fd, const struct sockaddr *addr, socklen_t len); +int getpeername(int fd, struct sockaddr *__restrict addr, + socklen_t *__restrict len); +int getsockname(int fd, struct sockaddr *__restrict addr, + socklen_t *__restrict len); +int getsockopt(int fd, int level, int name, void *__restrict val, + socklen_t *__restrict len); +int listen(int fd, int backlog); +ssize_t recv(int fd, void *buf, size_t n, int flags); +ssize_t recvfrom(int fd, void *__restrict buf, size_t n, int flags, + struct sockaddr *__restrict addr, socklen_t *__restrict len); +ssize_t recvmsg(int fd, struct msghdr *msg, int flags); +ssize_t send(int fd, const void *buf, size_t n, int flags); +ssize_t sendmsg(int fd, const struct msghdr *msg, int flags); +ssize_t sendto(int fd, const void *buf, size_t n, int flags, + const struct sockaddr *addr, socklen_t len); +int setsockopt(int fd, int level, int name, const void *val, + socklen_t len); +int shutdown(int fd, int how); +int sockatmark(int fd); +int socket(int domain, int type, int protocol); +int socketpair(int domain, int type, int protocol, int sv[2]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/stat.h b/lib/libc/include/sys/stat.h new file mode 100644 index 000000000..86459edb1 --- /dev/null +++ b/lib/libc/include/sys/stat.h @@ -0,0 +1,100 @@ +/* -- what a file is, and the calls that change its mode + * (src/posix/file.c). The mode bits are POSIX's own, which every Unix + * numbers alike. */ +#ifndef _SYS_STAT_H +#define _SYS_STAT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _STRUCT_TIMESPEC_DECLARED +#define _STRUCT_TIMESPEC_DECLARED +struct timespec { time_t tv_sec; long tv_nsec; }; +#endif + +struct stat { + dev_t st_dev; + ino_t st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + off_t st_size; + blksize_t st_blksize; + blkcnt_t st_blocks; + struct timespec st_atim, st_mtim, st_ctim; +}; +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec + +#define S_IFMT 0170000 +#define S_IFSOCK 0140000 +#define S_IFLNK 0120000 +#define S_IFREG 0100000 +#define S_IFBLK 0060000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFIFO 0010000 + +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) + +#define S_ISUID 04000 +#define S_ISGID 02000 +#define S_ISVTX 01000 +#define S_IRWXU 0700 +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +#define S_IRWXG 070 +#define S_IRGRP 040 +#define S_IWGRP 020 +#define S_IXGRP 010 +#define S_IRWXO 07 +#define S_IROTH 04 +#define S_IWOTH 02 +#define S_IXOTH 01 +#define S_IREAD S_IRUSR +#define S_IWRITE S_IWUSR +#define S_IEXEC S_IXUSR +#define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO) +#define ALLPERMS (S_ISUID | S_ISGID | S_ISVTX | ACCESSPERMS) +#define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +#define UTIME_NOW 0x3fffffffL +#define UTIME_OMIT 0x3ffffffeL + +int stat(const char *__restrict path, struct stat *__restrict st); +int lstat(const char *__restrict path, struct stat *__restrict st); +int fstat(int fd, struct stat *st); +int fstatat(int dirfd, const char *__restrict path, + struct stat *__restrict st, int flags); +int chmod(const char *path, mode_t mode); +int fchmod(int fd, mode_t mode); +int fchmodat(int dirfd, const char *path, mode_t mode, int flags); +int mkdir(const char *path, mode_t mode); +int mkdirat(int dirfd, const char *path, mode_t mode); +int mkfifo(const char *path, mode_t mode); +int mkfifoat(int dirfd, const char *path, mode_t mode); +int mknod(const char *path, mode_t mode, dev_t dev); +int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev); +mode_t umask(mode_t mask); +int futimens(int fd, const struct timespec times[2]); +int utimensat(int dirfd, const char *path, const struct timespec times[2], + int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/statvfs.h b/lib/libc/include/sys/statvfs.h new file mode 100644 index 000000000..397439c61 --- /dev/null +++ b/lib/libc/include/sys/statvfs.h @@ -0,0 +1,28 @@ +/* -- what a filesystem says about itself */ +#ifndef _SYS_STATVFS_H +#define _SYS_STATVFS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct statvfs { + unsigned long f_bsize, f_frsize; + fsblkcnt_t f_blocks, f_bfree, f_bavail; + fsfilcnt_t f_files, f_ffree, f_favail; + unsigned long f_fsid, f_flag, f_namemax; +}; + +#define ST_RDONLY 1 +#define ST_NOSUID 2 + +int statvfs(const char *__restrict path, struct statvfs *__restrict out); +int fstatvfs(int fd, struct statvfs *out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/sysmacros.h b/lib/libc/include/sys/sysmacros.h new file mode 100644 index 000000000..285195875 --- /dev/null +++ b/lib/libc/include/sys/sysmacros.h @@ -0,0 +1,14 @@ +/* -- a device number's major and minor, as glibc + * encodes them */ +#ifndef _SYS_SYSMACROS_H +#define _SYS_SYSMACROS_H + +#include + +#define major(d) ((unsigned)((((d) >> 32) & 0xfffff000u) | (((d) >> 8) & 0xfffu))) +#define minor(d) ((unsigned)((((d) >> 12) & 0xffffff00u) | ((d) & 0xffu))) +#define makedev(ma, mi) \ + ((((dev_t)(ma) & 0xfffff000u) << 32) | (((dev_t)(ma) & 0xfffu) << 8) | \ + (((dev_t)(mi) & 0xffffff00u) << 12) | ((dev_t)(mi) & 0xffu)) + +#endif diff --git a/lib/libc/include/sys/time.h b/lib/libc/include/sys/time.h new file mode 100644 index 000000000..10271aaa8 --- /dev/null +++ b/lib/libc/include/sys/time.h @@ -0,0 +1,49 @@ +/* -- microsecond time, interval timers, and select's + * timeout (src/posix/time.c) */ +#ifndef _SYS_TIME_H +#define _SYS_TIME_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct timezone { int tz_minuteswest, tz_dsttime; }; + +#define ITIMER_REAL 0 +#define ITIMER_VIRTUAL 1 +#define ITIMER_PROF 2 + +struct itimerval { struct timeval it_interval, it_value; }; + +int gettimeofday(struct timeval *__restrict tv, void *__restrict tz); +int settimeofday(const struct timeval *tv, const struct timezone *tz); +int getitimer(int which, struct itimerval *cur); +int setitimer(int which, const struct itimerval *__restrict val, + struct itimerval *__restrict old); +int utimes(const char *path, const struct timeval times[2]); +int futimes(int fd, const struct timeval times[2]); +int lutimes(const char *path, const struct timeval times[2]); + +#define timerisset(t) ((t)->tv_sec || (t)->tv_usec) +#define timerclear(t) ((t)->tv_sec = (t)->tv_usec = 0) +#define timercmp(a, b, op) ((a)->tv_sec == (b)->tv_sec ? \ + (a)->tv_usec op (b)->tv_usec : (a)->tv_sec op (b)->tv_sec) +#define timeradd(a, b, r) do { \ + (r)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (r)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((r)->tv_usec >= 1000000) { (r)->tv_sec++; (r)->tv_usec -= 1000000; } \ +} while (0) +#define timersub(a, b, r) do { \ + (r)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (r)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((r)->tv_usec < 0) { (r)->tv_sec--; (r)->tv_usec += 1000000; } \ +} while (0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/times.h b/lib/libc/include/sys/times.h new file mode 100644 index 000000000..b5a97e778 --- /dev/null +++ b/lib/libc/include/sys/times.h @@ -0,0 +1,19 @@ +/* -- processor time, in clock ticks */ +#ifndef _SYS_TIMES_H +#define _SYS_TIMES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct tms { clock_t tms_utime, tms_stime, tms_cutime, tms_cstime; }; + +clock_t times(struct tms *t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/types.h b/lib/libc/include/sys/types.h index 0ab452ad3..c0026dbb0 100644 --- a/lib/libc/include/sys/types.h +++ b/lib/libc/include/sys/types.h @@ -1,11 +1,16 @@ -/* -- POSIX's type names, as embedded code asks for them: - * Microchip Harmony's toolchain_specifics.h includes it, and so does code - * written against newlib. EmbCC's C library has no processes or files of - * its own; these are the names, at newlib's widths, so that such code - * compiles and its structures lay out as they did there. +/* -- POSIX's type names. + * + * The widths are a hosted system's, on every target: a file's inode + * number and its device are 64 bits, its owner 32, so that struct stat + * says on EmbLinkOS what it says on Linux and a program's + * `ino_t` does not wrap at 65536 files. (They were newlib's widths once, + * 16 bits for most of them, from before this library had a + * to fill them in; embedded code that names the types does not depend + * on their size.) off_t is a long: 64 bits on every target with a disk + * big enough to need it. * * time_t and clock_t are 's, declared once whichever header comes - * first; ssize_t is the signed type of size_t's width. + * first; so are ssize_t and off_t, 's too. */ #ifndef _SYS_TYPES_H #define _SYS_TYPES_H @@ -22,21 +27,48 @@ typedef long time_t; typedef long clock_t; #endif +/* declared once, whichever of this, and is first */ +#ifndef _SSIZE_T_DECLARED +#define _SSIZE_T_DECLARED typedef __PTRDIFF_TYPE__ ssize_t; +#endif +#ifndef _OFF_T_DECLARED +#define _OFF_T_DECLARED typedef long off_t; +#endif +#ifndef _OFF64_T_DECLARED +#define _OFF64_T_DECLARED +typedef long long off64_t; +#endif +#ifndef _PID_T_DECLARED +#define _PID_T_DECLARED typedef int pid_t; +#endif +#ifndef _MODE_T_DECLARED +#define _MODE_T_DECLARED typedef unsigned int mode_t; -typedef unsigned short uid_t; -typedef unsigned short gid_t; +#endif +#ifndef _UID_T_DECLARED +#define _UID_T_DECLARED +typedef unsigned int uid_t; +typedef unsigned int gid_t; +#endif typedef unsigned int id_t; -typedef short dev_t; -typedef unsigned short ino_t; -typedef unsigned short nlink_t; -typedef long blkcnt_t; +typedef unsigned long long dev_t; +typedef unsigned long long ino_t; +typedef unsigned long nlink_t; +typedef long long blkcnt_t; typedef long blksize_t; -typedef unsigned long useconds_t; +typedef unsigned long long fsblkcnt_t; +typedef unsigned long long fsfilcnt_t; +typedef unsigned int useconds_t; typedef long suseconds_t; -typedef long key_t; +typedef int key_t; +#ifndef _CLOCKID_T_DECLARED +#define _CLOCKID_T_DECLARED +typedef int clockid_t; +#endif +typedef void *timer_t; typedef long daddr_t; typedef char *caddr_t; @@ -52,5 +84,7 @@ typedef uint8_t u_int8_t; typedef uint16_t u_int16_t; typedef uint32_t u_int32_t; typedef uint64_t u_int64_t; +typedef int64_t quad_t; +typedef uint64_t u_quad_t; #endif diff --git a/lib/libc/include/sys/uio.h b/lib/libc/include/sys/uio.h new file mode 100644 index 000000000..e8214093a --- /dev/null +++ b/lib/libc/include/sys/uio.h @@ -0,0 +1,27 @@ +/* -- reads and writes of several buffers in one call */ +#ifndef _SYS_UIO_H +#define _SYS_UIO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define IOV_MAX 1024 + +#ifndef _STRUCT_IOVEC_DECLARED +#define _STRUCT_IOVEC_DECLARED +struct iovec { void *iov_base; size_t iov_len; }; +#endif + +ssize_t readv(int fd, const struct iovec *iov, int n); +ssize_t writev(int fd, const struct iovec *iov, int n); +ssize_t preadv(int fd, const struct iovec *iov, int n, off_t off); +ssize_t pwritev(int fd, const struct iovec *iov, int n, off_t off); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/un.h b/lib/libc/include/sys/un.h new file mode 100644 index 000000000..bdadf5bad --- /dev/null +++ b/lib/libc/include/sys/un.h @@ -0,0 +1,12 @@ +/* -- a local socket's address: a path */ +#ifndef _SYS_UN_H +#define _SYS_UN_H + +#include + +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; + +#endif diff --git a/lib/libc/include/sys/utsname.h b/lib/libc/include/sys/utsname.h new file mode 100644 index 000000000..f220da764 --- /dev/null +++ b/lib/libc/include/sys/utsname.h @@ -0,0 +1,20 @@ +/* -- the running system's names */ +#ifndef _SYS_UTSNAME_H +#define _SYS_UTSNAME_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct utsname { + char sysname[65], nodename[65], release[65], version[65], machine[65]; + char domainname[65]; +}; + +int uname(struct utsname *u); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/sys/wait.h b/lib/libc/include/sys/wait.h new file mode 100644 index 000000000..2e6e12bac --- /dev/null +++ b/lib/libc/include/sys/wait.h @@ -0,0 +1,38 @@ +/* -- waiting for a child (src/posix/proc.c). The status + * is encoded as Linux encodes it: an exit is (code << 8), a signal its + * number, a stop 0x7f with the signal above it. */ +#ifndef _SYS_WAIT_H +#define _SYS_WAIT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WNOHANG 1 +#define WUNTRACED 2 +#define WSTOPPED WUNTRACED +#define WCONTINUED 8 + +#define WEXITSTATUS(s) (((s) & 0xff00) >> 8) +#define WTERMSIG(s) ((s) & 0x7f) +#define WSTOPSIG(s) WEXITSTATUS(s) +#define WIFEXITED(s) (WTERMSIG(s) == 0) +#define WIFSIGNALED(s) (((signed char)(((s) & 0x7f) + 1) >> 1) > 0) +#define WIFSTOPPED(s) (((s) & 0xff) == 0x7f) +#define WIFCONTINUED(s) ((s) == 0xffff) +#define WCOREDUMP(s) ((s) & 0x80) + +struct rusage; + +pid_t wait(int *status); +pid_t waitpid(pid_t pid, int *status, int options); +pid_t wait3(int *status, int options, struct rusage *ru); +pid_t wait4(pid_t pid, int *status, int options, struct rusage *ru); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/syslog.h b/lib/libc/include/syslog.h new file mode 100644 index 000000000..54af9a0af --- /dev/null +++ b/lib/libc/include/syslog.h @@ -0,0 +1,72 @@ +/* -- the system log, POSIX's interface with glibc's numbers. + * A message goes to the logger listening on /dev/log; where none is, + * it is not dropped: it goes to /dev/console with LOG_CONS, and to + * standard error otherwise (glibc drops it), so that a system with no + * syslog daemon still shows what a program logged. */ +#ifndef _SYSLOG_H +#define _SYSLOG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 +#define LOG_PRIMASK 0x07 +#define LOG_PRI(p) ((p) & LOG_PRIMASK) +#define LOG_MAKEPRI(fac, pri) ((fac) | (pri)) + +#define LOG_KERN (0 << 3) +#define LOG_USER (1 << 3) +#define LOG_MAIL (2 << 3) +#define LOG_DAEMON (3 << 3) +#define LOG_AUTH (4 << 3) +#define LOG_SYSLOG (5 << 3) +#define LOG_LPR (6 << 3) +#define LOG_NEWS (7 << 3) +#define LOG_UUCP (8 << 3) +#define LOG_CRON (9 << 3) +#define LOG_AUTHPRIV (10 << 3) +#define LOG_FTP (11 << 3) +#define LOG_LOCAL0 (16 << 3) +#define LOG_LOCAL1 (17 << 3) +#define LOG_LOCAL2 (18 << 3) +#define LOG_LOCAL3 (19 << 3) +#define LOG_LOCAL4 (20 << 3) +#define LOG_LOCAL5 (21 << 3) +#define LOG_LOCAL6 (22 << 3) +#define LOG_LOCAL7 (23 << 3) +#define LOG_NFACILITIES 24 +#define LOG_FACMASK 0x03f8 +#define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) + +#define LOG_MASK(pri) (1 << (pri)) +#define LOG_UPTO(pri) ((1 << ((pri) + 1)) - 1) + +#define LOG_PID 0x01 +#define LOG_CONS 0x02 +#define LOG_ODELAY 0x04 +#define LOG_NDELAY 0x08 +#define LOG_NOWAIT 0x10 +#define LOG_PERROR 0x20 + +void openlog(const char *ident, int option, int facility); +void syslog(int priority, const char *format, ...); +void vsyslog(int priority, const char *format, va_list ap); +void closelog(void); +int setlogmask(int mask); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/termios.h b/lib/libc/include/termios.h new file mode 100644 index 000000000..a1e088e3b --- /dev/null +++ b/lib/libc/include/termios.h @@ -0,0 +1,191 @@ +/* -- terminal modes (src/posix/termios.c). glibc's layout + * and Linux's flag bits and speed codes. */ +#ifndef _TERMIOS_H +#define _TERMIOS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned char cc_t; +typedef unsigned int speed_t; +typedef unsigned int tcflag_t; + +#define NCCS 32 + +struct termios { + tcflag_t c_iflag, c_oflag, c_cflag, c_lflag; + cc_t c_line; + cc_t c_cc[NCCS]; + speed_t c_ispeed, c_ospeed; +}; + +#define VINTR 0 +#define VQUIT 1 +#define VERASE 2 +#define VKILL 3 +#define VEOF 4 +#define VTIME 5 +#define VMIN 6 +#define VSWTC 7 +#define VSTART 8 +#define VSTOP 9 +#define VSUSP 10 +#define VEOL 11 +#define VREPRINT 12 +#define VDISCARD 13 +#define VWERASE 14 +#define VLNEXT 15 +#define VEOL2 16 + +#define IGNBRK 0000001 +#define BRKINT 0000002 +#define IGNPAR 0000004 +#define PARMRK 0000010 +#define INPCK 0000020 +#define ISTRIP 0000040 +#define INLCR 0000100 +#define IGNCR 0000200 +#define ICRNL 0000400 +#define IUCLC 0001000 +#define IXON 0002000 +#define IXANY 0004000 +#define IXOFF 0010000 +#define IMAXBEL 0020000 +#define IUTF8 0040000 + +#define OPOST 0000001 +#define OLCUC 0000002 +#define ONLCR 0000004 +#define OCRNL 0000010 +#define ONOCR 0000020 +#define ONLRET 0000040 +#define OFILL 0000100 +#define OFDEL 0000200 +#define NLDLY 0000400 +#define NL0 0000000 +#define NL1 0000400 +#define CRDLY 0003000 +#define CR0 0000000 +#define CR1 0001000 +#define CR2 0002000 +#define CR3 0003000 +#define TABDLY 0014000 +#define TAB0 0000000 +#define TAB1 0004000 +#define TAB2 0010000 +#define TAB3 0014000 +#define XTABS TAB3 +#define BSDLY 0020000 +#define BS0 0000000 +#define BS1 0020000 +#define VTDLY 0040000 +#define VT0 0000000 +#define VT1 0040000 +#define FFDLY 0100000 +#define FF0 0000000 +#define FF1 0100000 + +#define CBAUD 0010017 +#define B0 0000000 +#define B50 0000001 +#define B75 0000002 +#define B110 0000003 +#define B134 0000004 +#define B150 0000005 +#define B200 0000006 +#define B300 0000007 +#define B600 0000010 +#define B1200 0000011 +#define B1800 0000012 +#define B2400 0000013 +#define B4800 0000014 +#define B9600 0000015 +#define B19200 0000016 +#define B38400 0000017 +#define CBAUDEX 0010000 +#define B57600 0010001 +#define B115200 0010002 +#define B230400 0010003 +#define B460800 0010004 +#define B500000 0010005 +#define B576000 0010006 +#define B921600 0010007 +#define B1000000 0010010 +#define B1152000 0010011 +#define B1500000 0010012 +#define B2000000 0010013 +#define B2500000 0010014 +#define B3000000 0010015 +#define B3500000 0010016 +#define B4000000 0010017 +#define CSIZE 0000060 +#define CS5 0000000 +#define CS6 0000020 +#define CS7 0000040 +#define CS8 0000060 +#define CSTOPB 0000100 +#define CREAD 0000200 +#define PARENB 0000400 +#define PARODD 0001000 +#define HUPCL 0002000 +#define CLOCAL 0004000 +#define CRTSCTS 020000000000 + +#define ISIG 0000001 +#define ICANON 0000002 +#define XCASE 0000004 +#define ECHO 0000010 +#define ECHOE 0000020 +#define ECHOK 0000040 +#define ECHONL 0000100 +#define NOFLSH 0000200 +#define TOSTOP 0000400 +#define ECHOCTL 0001000 +#define ECHOPRT 0002000 +#define ECHOKE 0004000 +#define FLUSHO 0010000 +#define PENDIN 0040000 +#define IEXTEN 0100000 +#define EXTPROC 0200000 + +#define TCSANOW 0 +#define TCSADRAIN 1 +#define TCSAFLUSH 2 +#define TCIFLUSH 0 +#define TCOFLUSH 1 +#define TCIOFLUSH 2 +#define TCOOFF 0 +#define TCOON 1 +#define TCIOFF 2 +#define TCION 3 + +struct winsize; + +speed_t cfgetispeed(const struct termios *t); +speed_t cfgetospeed(const struct termios *t); +int cfsetispeed(struct termios *t, speed_t s); +int cfsetospeed(struct termios *t, speed_t s); +int tcdrain(int fd); +int tcflow(int fd, int action); +int tcflush(int fd, int queue); +int tcgetattr(int fd, struct termios *t); +pid_t tcgetsid(int fd); +int tcsendbreak(int fd, int duration); +int tcsetattr(int fd, int action, const struct termios *t); +/* POSIX.1-2024's */ +int tcgetwinsize(int fd, struct winsize *ws); +int tcsetwinsize(int fd, const struct winsize *ws); +#if defined(__EMB_GNU) +void cfmakeraw(struct termios *t); +int cfsetspeed(struct termios *t, speed_t s); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/threads.h b/lib/libc/include/threads.h index be2b873b0..352ea6011 100644 --- a/lib/libc/include/threads.h +++ b/lib/libc/include/threads.h @@ -78,9 +78,8 @@ typedef struct { int __state; } once_flag; #define ONCE_FLAG_INIT { 0 } #endif -/* Thread-specific storage needs thread-local storage underneath, which - * this compiler does not have; tss_create reports failure rather than - * handing back a key that every thread shares. */ +/* Thread-specific storage: POSIX's keys (), a table in each + * thread's record, so it needs no thread-local storage underneath. */ typedef int tss_t; typedef void (*tss_dtor_t)(void *); #define TSS_DTOR_ITERATIONS 4 diff --git a/lib/libc/include/time.h b/lib/libc/include/time.h index 2386efa0e..cf559b0c5 100644 --- a/lib/libc/include/time.h +++ b/lib/libc/include/time.h @@ -12,6 +12,8 @@ #ifndef _TIME_H #define _TIME_H +#include + #include #ifdef __cplusplus @@ -38,17 +40,18 @@ struct tm { int tm_year; /* years since 1900 */ int tm_wday; /* 0..6, Sunday is 0 */ int tm_yday; /* 0..365 */ - int tm_isdst; /* always 0: no timezone database */ -#if defined(__APPLE__) - /* Apple's C library has two more, and its mktime, localtime_r and - * gmtime_r fill them in: a 36-byte struct tm on the caller's stack - * was overrun by 20 bytes. */ + int tm_isdst; /* > 0 in daylight saving time */ + /* POSIX.1-2024's (the BSDs' and glibc's long before): seconds east of + * UTC, and the zone's abbreviation. Apple's C library fills them too, + * and a struct tm without them was overrun by 20 bytes there. */ long tm_gmtoff; - char *tm_zone; -#endif + const char *tm_zone; }; +#ifndef _STRUCT_TIMESPEC_DECLARED /* (and in , ) */ +#define _STRUCT_TIMESPEC_DECLARED struct timespec { time_t tv_sec; long tv_nsec; }; +#endif #define TIME_UTC 1 @@ -75,6 +78,40 @@ char *ctime_r(const time_t *__restrict t, char *__restrict buf); size_t strftime(char *__restrict s, size_t max, const char *__restrict fmt, const struct tm *__restrict tm); +/* POSIX's clocks and sleeps, and the time zone (src/posix/time.c) */ +#if defined(__EMB_POSIX) +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 +#define CLOCK_PROCESS_CPUTIME_ID 2 +#define CLOCK_THREAD_CPUTIME_ID 3 +#define CLOCK_MONOTONIC_RAW 4 +#define CLOCK_REALTIME_COARSE 5 +#define CLOCK_MONOTONIC_COARSE 6 +#define CLOCK_BOOTTIME 7 +#define TIMER_ABSTIME 1 + +#ifndef _CLOCKID_T_DECLARED +#define _CLOCKID_T_DECLARED +typedef int clockid_t; +#endif + +int clock_gettime(clockid_t clock, struct timespec *t); +int clock_getres(clockid_t clock, struct timespec *t); +int clock_settime(clockid_t clock, const struct timespec *t); +int clock_nanosleep(clockid_t clock, int flags, const struct timespec *req, + struct timespec *rem); +int nanosleep(const struct timespec *req, struct timespec *rem); + +/* TZ, as POSIX spells it ("CET-1CEST,M3.5.0,M10.5.0/3"), or a zone + * file's name where the system has /usr/share/zoneinfo */ +extern char *tzname[2]; +extern long timezone; +extern int daylight; +void tzset(void); +char *strptime(const char *__restrict s, const char *__restrict fmt, + struct tm *__restrict tm); +#endif + #ifdef __cplusplus } #endif diff --git a/lib/libc/include/unistd.h b/lib/libc/include/unistd.h new file mode 100644 index 000000000..1600a1b9c --- /dev/null +++ b/lib/libc/include/unistd.h @@ -0,0 +1,224 @@ +/* -- POSIX's operating system calls (src/posix/fd.c, file.c, + * proc.c, sysconf.c), on the primitives os/backend.h names. What the + * target's kernel cannot do fails with ENOSYS: fork on EmbLinkOS, which + * starts new processes rather than copying them (posix_spawn works), and + * nearly everything on bare metal. */ +#ifndef _UNISTD_H +#define _UNISTD_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define _POSIX_VERSION 200809L +#define _POSIX2_VERSION 200809L +#define _XOPEN_VERSION 700 +#define _POSIX_FSYNC 200809L +#define _POSIX_MAPPED_FILES 200809L +#define _POSIX_MEMORY_PROTECTION 200809L +#define _POSIX_MONOTONIC_CLOCK 200809L +#define _POSIX_CLOCK_SELECTION 200809L +#define _POSIX_SAVED_IDS 1 +#define _POSIX_JOB_CONTROL 1 +#define _POSIX_SPAWN 200809L +#define _POSIX_REGEXP 1 +#define _POSIX_SHELL 1 +#define _POSIX_VDISABLE '\0' +#define _POSIX_NO_TRUNC 1 +#define _POSIX_CHOWN_RESTRICTED 1 + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif + +/* lockf */ +#define F_ULOCK 0 +#define F_LOCK 1 +#define F_TLOCK 2 +#define F_TEST 3 + +/* sysconf's names, glibc's numbers */ +#define _SC_ARG_MAX 0 +#define _SC_CHILD_MAX 1 +#define _SC_CLK_TCK 2 +#define _SC_NGROUPS_MAX 3 +#define _SC_OPEN_MAX 4 +#define _SC_STREAM_MAX 5 +#define _SC_TZNAME_MAX 6 +#define _SC_JOB_CONTROL 7 +#define _SC_SAVED_IDS 8 +#define _SC_REALTIME_SIGNALS 9 +#define _SC_TIMERS 11 +#define _SC_FSYNC 15 +#define _SC_MAPPED_FILES 16 +#define _SC_SEMAPHORES 21 +#define _SC_VERSION 29 +#define _SC_PAGESIZE 30 +#define _SC_PAGE_SIZE _SC_PAGESIZE +#define _SC_RTSIG_MAX 31 +#define _SC_SEM_NSEMS_MAX 32 +#define _SC_SIGQUEUE_MAX 34 +#define _SC_BC_BASE_MAX 36 +#define _SC_BC_DIM_MAX 37 +#define _SC_BC_SCALE_MAX 38 +#define _SC_BC_STRING_MAX 39 +#define _SC_COLL_WEIGHTS_MAX 40 +#define _SC_EXPR_NEST_MAX 42 +#define _SC_LINE_MAX 43 +#define _SC_RE_DUP_MAX 44 +#define _SC_2_VERSION 46 +#define _SC_IOV_MAX 60 +#define _SC_THREADS 67 +#define _SC_THREAD_SAFE_FUNCTIONS 68 +#define _SC_GETGR_R_SIZE_MAX 69 +#define _SC_GETPW_R_SIZE_MAX 70 +#define _SC_LOGIN_NAME_MAX 71 +#define _SC_TTY_NAME_MAX 72 +#define _SC_THREAD_KEYS_MAX 74 +#define _SC_THREAD_STACK_MIN 75 +#define _SC_THREAD_THREADS_MAX 76 +#define _SC_NPROCESSORS_CONF 83 +#define _SC_NPROCESSORS_ONLN 84 +#define _SC_PHYS_PAGES 85 +#define _SC_AVPHYS_PAGES 86 +#define _SC_ATEXIT_MAX 87 +#define _SC_XOPEN_VERSION 89 +#define _SC_MONOTONIC_CLOCK 149 +#define _SC_SPAWN 159 +#define _SC_SYMLOOP_MAX 173 +#define _SC_HOST_NAME_MAX 180 + +#define _PC_LINK_MAX 0 +#define _PC_MAX_CANON 1 +#define _PC_MAX_INPUT 2 +#define _PC_NAME_MAX 3 +#define _PC_PATH_MAX 4 +#define _PC_PIPE_BUF 5 +#define _PC_CHOWN_RESTRICTED 6 +#define _PC_NO_TRUNC 7 +#define _PC_VDISABLE 8 +#define _PC_SYNC_IO 9 +#define _PC_FILESIZEBITS 13 +#define _PC_SYMLINK_MAX 19 + +#define _CS_PATH 0 + +extern char *optarg; +extern int optind, opterr, optopt; + +int access(const char *path, int mode); +int faccessat(int dirfd, const char *path, int mode, int flags); +unsigned alarm(unsigned seconds); +int chdir(const char *path); +int chown(const char *path, uid_t uid, gid_t gid); +int close(int fd); +size_t confstr(int name, char *buf, size_t n); +int dup(int fd); +int dup2(int fd, int newfd); +int execl(const char *path, const char *arg, ...); +int execle(const char *path, const char *arg, ...); +int execlp(const char *file, const char *arg, ...); +int execv(const char *path, char *const argv[]); +int execve(const char *path, char *const argv[], char *const envp[]); +int execvp(const char *file, char *const argv[]); +__attribute__((__noreturn__)) void _exit(int status); +int fchdir(int fd); +int fchown(int fd, uid_t uid, gid_t gid); +int fchownat(int dirfd, const char *path, uid_t uid, gid_t gid, int flags); +int fdatasync(int fd); +pid_t fork(void); +long fpathconf(int fd, int name); +int fsync(int fd); +int ftruncate(int fd, off_t size); +char *getcwd(char *buf, size_t n); +gid_t getegid(void); +uid_t geteuid(void); +gid_t getgid(void); +int getgroups(int n, gid_t list[]); +int gethostname(char *name, size_t n); +char *getlogin(void); +int getlogin_r(char *name, size_t n); +int getopt(int argc, char *const argv[], const char *opts); +pid_t getpgid(pid_t pid); +pid_t getpgrp(void); +pid_t getpid(void); +pid_t getppid(void); +pid_t getsid(pid_t pid); +uid_t getuid(void); +int isatty(int fd); +int lchown(const char *path, uid_t uid, gid_t gid); +int link(const char *target, const char *path); +int linkat(int fromfd, const char *from, int tofd, const char *to, + int flags); +int lockf(int fd, int cmd, off_t len); +off_t lseek(int fd, off_t off, int whence); +long pathconf(const char *path, int name); +int pause(void); +int pipe(int fds[2]); +ssize_t pread(int fd, void *buf, size_t n, off_t off); +ssize_t pwrite(int fd, const void *buf, size_t n, off_t off); +ssize_t read(int fd, void *buf, size_t n); +ssize_t readlink(const char *__restrict path, char *__restrict buf, size_t n); +ssize_t readlinkat(int dirfd, const char *__restrict path, + char *__restrict buf, size_t n); +int rmdir(const char *path); +int setegid(gid_t gid); +int seteuid(uid_t uid); +int setgid(gid_t gid); +int setpgid(pid_t pid, pid_t pgid); +pid_t setsid(void); +int setuid(uid_t uid); +unsigned sleep(unsigned seconds); +int symlink(const char *target, const char *path); +int symlinkat(const char *target, int dirfd, const char *path); +void sync(void); +long sysconf(int name); +pid_t tcgetpgrp(int fd); +int tcsetpgrp(int fd, pid_t pgrp); +int truncate(const char *path, off_t size); +char *ttyname(int fd); +int ttyname_r(int fd, char *buf, size_t n); +int unlink(const char *path); +int unlinkat(int dirfd, const char *path, int flags); +ssize_t write(int fd, const void *buf, size_t n); +/* POSIX.1-2024's */ +int dup3(int fd, int newfd, int flags); +int pipe2(int fds[2], int flags); +int getentropy(void *buf, size_t n); + +#if defined(__EMB_GNU) +extern char **environ; +int execvpe(const char *file, char *const argv[], char *const envp[]); +int getpagesize(void); +int getdtablesize(void); +int sethostname(const char *name, size_t n); +int setreuid(uid_t ruid, uid_t euid); +int setregid(gid_t rgid, gid_t egid); +int setpgrp(void); +int usleep(useconds_t us); +pid_t vfork(void); +int chroot(const char *path); +void *sbrk(intptr_t increment); +int daemon(int nochdir, int noclose); +char *getpass(const char *prompt); /* gone from POSIX in 2001; still called */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/include/utime.h b/lib/libc/include/utime.h new file mode 100644 index 000000000..9c37a2457 --- /dev/null +++ b/lib/libc/include/utime.h @@ -0,0 +1,19 @@ +/* -- a file's access and modification times, in seconds */ +#ifndef _UTIME_H +#define _UTIME_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct utimbuf { time_t actime, modtime; }; + +int utime(const char *path, const struct utimbuf *t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libc/os/absent.inc b/lib/libc/os/absent.inc new file mode 100644 index 000000000..9da048327 --- /dev/null +++ b/lib/libc/os/absent.inc @@ -0,0 +1,739 @@ +/* Every primitive of os/backend.h's "POSIX's operating system" half, + * defined as a kernel that has not got it: -1 with ENOSYS (or the + * contract's answer where it promises one -- umask's 022, getid's pid 1 + * and ids 0). A backend includes this after its own definitions, + * having #defined OS_HAS_ for each it implements; the bare-metal + * ones include it whole. + * + * Generated from os/backend.h; tests/golden/libc-backends.sh checks + * that every backend defines every primitive that header names, which is + * what keeps this list and that one the same. */ +#include + +#define ABSENT_RETURN(v) do { errno = ENOSYS; return v; } while (0) + +#ifndef OS_HAS_dup +int __os_dup(int fd, int minfd, int cloexec) +{ + (void)fd; (void)minfd; (void)cloexec; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_dup2 +int __os_dup2(int fd, int newfd, int cloexec) +{ + (void)fd; (void)newfd; (void)cloexec; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_pipe +int __os_pipe(int fds[2], int flags) +{ + (void)fds; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fcntl +int __os_fcntl(int fd, int cmd, long arg) +{ + (void)fd; (void)cmd; (void)arg; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fstat +int __os_fstat(int fd, struct __os_fileinfo *out) +{ + (void)fd; (void)out; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_pread +long __os_pread(int fd, void *buf, size_t n, long long off) +{ + (void)fd; (void)buf; (void)n; (void)off; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_pwrite +long __os_pwrite(int fd, const void *buf, size_t n, long long off) +{ + (void)fd; (void)buf; (void)n; (void)off; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_readv +long __os_readv(int fd, const struct __os_iovec *iov, int n) +{ + (void)fd; (void)iov; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_writev +long __os_writev(int fd, const struct __os_iovec *iov, int n) +{ + (void)fd; (void)iov; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fsync +int __os_fsync(int fd, int data_only) +{ + (void)fd; (void)data_only; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_ftruncate +int __os_ftruncate(int fd, long long size) +{ + (void)fd; (void)size; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fchmod +int __os_fchmod(int fd, unsigned mode) +{ + (void)fd; (void)mode; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fchdir +int __os_fchdir(int fd) +{ + (void)fd; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_ioctl +int __os_ioctl(int fd, unsigned long request, void *arg) +{ + (void)fd; (void)request; (void)arg; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_flock_file +int __os_flock_file(int fd, int op) +{ + (void)fd; (void)op; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fdpath +int __os_fdpath(int fd, char *buf, size_t n) +{ + (void)fd; (void)buf; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_openat +int __os_openat(int dirfd, const char *path, int flags, int mode) +{ + (void)dirfd; (void)path; (void)flags; (void)mode; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fstatat +int __os_fstatat(int dirfd, const char *path, struct __os_fileinfo *out, + int flags) +{ + (void)dirfd; (void)path; (void)out; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mkdirat +int __os_mkdirat(int dirfd, const char *path, unsigned mode) +{ + (void)dirfd; (void)path; (void)mode; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_unlinkat +int __os_unlinkat(int dirfd, const char *path, int flags) +{ + (void)dirfd; (void)path; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_renameat +int __os_renameat(int fromfd, const char *from, int tofd, const char *to) +{ + (void)fromfd; (void)from; (void)tofd; (void)to; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_linkat +int __os_linkat(int fromfd, const char *from, int tofd, const char *to, + int flags) +{ + (void)fromfd; (void)from; (void)tofd; (void)to; (void)flags; + ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_symlinkat +int __os_symlinkat(const char *target, int dirfd, const char *path) +{ + (void)target; (void)dirfd; (void)path; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_readlinkat +long __os_readlinkat(int dirfd, const char *path, char *buf, size_t n) +{ + (void)dirfd; (void)path; (void)buf; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fchmodat +int __os_fchmodat(int dirfd, const char *path, unsigned mode, int flags) +{ + (void)dirfd; (void)path; (void)mode; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_faccessat +int __os_faccessat(int dirfd, const char *path, int mode, int flags) +{ + (void)dirfd; (void)path; (void)mode; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mknodat +int __os_mknodat(int dirfd, const char *path, unsigned mode, + unsigned long long dev) +{ + (void)dirfd; (void)path; (void)mode; (void)dev; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_access +int __os_access(const char *path, int mode) +{ + (void)path; (void)mode; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_umask +unsigned __os_umask(unsigned mask) +{ + (void)mask; return 022; +} +#endif + +#ifndef OS_HAS_chown +int __os_chown(const char *path, int fd, unsigned uid, unsigned gid, int nofollow) +{ + (void)path; (void)fd; (void)uid; (void)gid; (void)nofollow; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_utimens +int __os_utimens(const char *path, int fd, const struct __os_timespec times[2], int nofollow) +{ + (void)path; (void)fd; (void)times; (void)nofollow; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mknod +int __os_mknod(const char *path, unsigned mode, unsigned long long dev) +{ + (void)path; (void)mode; (void)dev; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_chroot +int __os_chroot(const char *path) +{ + (void)path; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sync +void __os_sync(void) +{ + +} +#endif + +#ifndef OS_HAS_statvfs +int __os_statvfs(const char *path, int fd, struct __os_fsinfo *out) +{ + (void)path; (void)fd; (void)out; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_readdir_entry +int __os_readdir_entry(void *dir, struct __os_dirent *e) +{ + (void)dir; (void)e; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_rewinddir +void __os_rewinddir(void *dir) +{ + (void)dir; +} +#endif + +#ifndef OS_HAS_dirfd +int __os_dirfd(void *dir) +{ + (void)dir; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fdopendir +void *__os_fdopendir(int fd) +{ + (void)fd; ABSENT_RETURN((void *)0); +} +#endif + +#ifndef OS_HAS_getid +long __os_getid(int which) +{ + return which == __OS_ID_PID ? 1 : 0; +} +#endif + +#ifndef OS_HAS_setid +int __os_setid(int which, unsigned id) +{ + (void)which; (void)id; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getgroups +int __os_getgroups(int n, unsigned *list) +{ + (void)n; (void)list; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setgroups +int __os_setgroups(int n, const unsigned *list) +{ + (void)n; (void)list; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getpgid +int __os_getpgid(int pid) +{ + (void)pid; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setpgid +int __os_setpgid(int pid, int pgid) +{ + (void)pid; (void)pgid; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getsid +int __os_getsid(int pid) +{ + (void)pid; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setsid +int __os_setsid(void) +{ + ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_fork +int __os_fork(void) +{ + ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_execve +int __os_execve(const char *path, char *const argv[], char *const envp[]) +{ + (void)path; (void)argv; (void)envp; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_spawn_flags +int __os_spawn_flags(void) +{ + return 0; +} +#endif + +#ifndef OS_HAS_spawn_check +int __os_spawn_check(const struct __os_spawn_action *action, int count) +{ + (void)action; (void)count; + return 0; +} +#endif + +#ifndef OS_HAS_spawn +int __os_spawn(int *pid, const char *path, const struct __os_spawn_action *actions, int nactions, const struct __os_spawn_attr *attr, char *const argv[], char *const envp[]) +{ + (void)pid; (void)path; (void)actions; (void)nactions; (void)attr; (void)argv; (void)envp; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_waitpid +int __os_waitpid(int pid, int *status, int options) +{ + (void)pid; (void)status; (void)options; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_kill +int __os_kill(int pid, int sig) +{ + (void)pid; (void)sig; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_thread_kill +int __os_thread_kill(unsigned long thread, int sig) +{ + (void)thread; (void)sig; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigaction +int __os_sigaction(int sig, const struct __os_sigaction *act, struct __os_sigaction *old) +{ + (void)sig; (void)act; (void)old; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigprocmask +int __os_sigprocmask(int how, const unsigned long long *set, unsigned long long *old) +{ + (void)how; (void)set; (void)old; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigpending +int __os_sigpending(unsigned long long *set) +{ + (void)set; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigsuspend +int __os_sigsuspend(const unsigned long long *mask) +{ + (void)mask; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigtimedwait +int __os_sigtimedwait(const unsigned long long *set, void *info, const struct __os_timespec *timeout) +{ + (void)set; (void)info; (void)timeout; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sigaltstack +int __os_sigaltstack(const void *ss, void *old) +{ + (void)ss; (void)old; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setitimer +int __os_setitimer(int which, const struct __os_itimer *val, struct __os_itimer *old) +{ + (void)which; (void)val; (void)old; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getitimer +int __os_getitimer(int which, struct __os_itimer *cur) +{ + (void)which; (void)cur; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_clock_gettime +int __os_clock_gettime(int clock, struct __os_timespec *t) +{ + (void)clock; (void)t; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_clock_getres +int __os_clock_getres(int clock, struct __os_timespec *t) +{ + (void)clock; (void)t; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_clock_settime +int __os_clock_settime(int clock, const struct __os_timespec *t) +{ + (void)clock; (void)t; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_clock_nanosleep +int __os_clock_nanosleep(int clock, int abs, const struct __os_timespec *req, struct __os_timespec *rem) +{ + (void)clock; (void)abs; (void)req; (void)rem; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_times +long long __os_times(long long out[4]) +{ + (void)out; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getrusage +int __os_getrusage(int who, struct __os_rusage *out) +{ + (void)who; (void)out; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getrlimit +int __os_getrlimit(int resource, unsigned long long lim[2]) +{ + (void)resource; (void)lim; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setrlimit +int __os_setrlimit(int resource, const unsigned long long lim[2]) +{ + (void)resource; (void)lim; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mmap +void *__os_mmap(void *addr, size_t len, int prot, int flags, int fd, long long off) +{ + (void)addr; (void)len; (void)prot; (void)flags; (void)fd; (void)off; ABSENT_RETURN((void *)-1); +} +#endif + +#ifndef OS_HAS_munmap +int __os_munmap(void *addr, size_t len) +{ + (void)addr; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mprotect +int __os_mprotect(void *addr, size_t len, int prot) +{ + (void)addr; (void)len; (void)prot; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_msync +int __os_msync(void *addr, size_t len, int flags) +{ + (void)addr; (void)len; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_madvise +int __os_madvise(void *addr, size_t len, int advice) +{ + (void)addr; (void)len; (void)advice; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_mlock +int __os_mlock(const void *addr, size_t len, int lock) +{ + (void)addr; (void)len; (void)lock; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_uname +int __os_uname(struct __os_utsname *out) +{ + (void)out; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sethostname +int __os_sethostname(const char *name, size_t len) +{ + (void)name; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sysinfo +long __os_sysinfo(int what) +{ + (void)what; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_poll +int __os_poll(struct __os_pollfd *fds, unsigned long n, const struct __os_timespec *timeout, const unsigned long long *sigmask) +{ + (void)fds; (void)n; (void)timeout; (void)sigmask; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_socket +int __os_socket(int domain, int type, int protocol) +{ + (void)domain; (void)type; (void)protocol; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_socketpair +int __os_socketpair(int domain, int type, int protocol, int sv[2]) +{ + (void)domain; (void)type; (void)protocol; (void)sv; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_bind +int __os_bind(int fd, const void *addr, unsigned len) +{ + (void)fd; (void)addr; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_connect +int __os_connect(int fd, const void *addr, unsigned len) +{ + (void)fd; (void)addr; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_listen +int __os_listen(int fd, int backlog) +{ + (void)fd; (void)backlog; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_accept +int __os_accept(int fd, void *addr, unsigned *len, int flags) +{ + (void)fd; (void)addr; (void)len; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sendto +long __os_sendto(int fd, const void *buf, size_t n, int flags, const void *addr, unsigned alen) +{ + (void)fd; (void)buf; (void)n; (void)flags; (void)addr; (void)alen; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_recvfrom +long __os_recvfrom(int fd, void *buf, size_t n, int flags, void *addr, unsigned *alen) +{ + (void)fd; (void)buf; (void)n; (void)flags; (void)addr; (void)alen; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_sendmsg +long __os_sendmsg(int fd, const void *msg, int flags) +{ + (void)fd; (void)msg; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_recvmsg +long __os_recvmsg(int fd, void *msg, int flags) +{ + (void)fd; (void)msg; (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_shutdown +int __os_shutdown(int fd, int how) +{ + (void)fd; (void)how; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getsockopt +int __os_getsockopt(int fd, int level, int name, void *val, unsigned *len) +{ + (void)fd; (void)level; (void)name; (void)val; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_setsockopt +int __os_setsockopt(int fd, int level, int name, const void *val, unsigned len) +{ + (void)fd; (void)level; (void)name; (void)val; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getsockname +int __os_getsockname(int fd, void *addr, unsigned *len) +{ + (void)fd; (void)addr; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_getpeername +int __os_getpeername(int fd, void *addr, unsigned *len) +{ + (void)fd; (void)addr; (void)len; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_resolve +int __os_resolve(const char *name, int family, unsigned char *addrs, int max) +{ + (void)name; (void)family; (void)addrs; (void)max; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_tcgetattr +int __os_tcgetattr(int fd, struct __os_termios *t) +{ + (void)fd; (void)t; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_tcsetattr +int __os_tcsetattr(int fd, int action, const struct __os_termios *t) +{ + (void)fd; (void)action; (void)t; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_ttyname +int __os_ttyname(int fd, char *buf, size_t n) +{ + (void)fd; (void)buf; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_openpt +int __os_openpt(int flags) +{ + (void)flags; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_ptsname +int __os_ptsname(int fd, char *buf, size_t n) +{ + (void)fd; (void)buf; (void)n; ABSENT_RETURN(-1); +} +#endif + +#ifndef OS_HAS_unlockpt +int __os_unlockpt(int fd) +{ + (void)fd; ABSENT_RETURN(-1); +} +#endif diff --git a/lib/libc/os/backend.h b/lib/libc/os/backend.h index 861a84b4b..d163ae15e 100644 --- a/lib/libc/os/backend.h +++ b/lib/libc/os/backend.h @@ -203,6 +203,15 @@ struct __os_fileinfo { * answer -- two different paths may be one file. */ unsigned long long dev, ino; unsigned long long nlink; + /* What POSIX's struct stat adds. A backend that does not know one + * leaves it zero -- every filler clears the structure first -- and + * a backend with no owners reports uid and gid 0. */ + unsigned int uid, gid; + unsigned long long rdev; + long long atime, ctime; /* seconds since the epoch */ + long atime_nsec, mtime_nsec, ctime_nsec; + long long blocks; /* 512-byte units */ + long blksize; /* preferred I/O size */ }; int __os_stat(const char *path, struct __os_fileinfo *out); @@ -234,6 +243,408 @@ void *__os_opendir(const char *path); int __os_readdir(void *dir, char *name, size_t n, int *type); void __os_closedir(void *dir); +/* ========================================================================= + * POSIX's operating system + * + * Everything from here on is what POSIX asks of a kernel beyond C's few + * needs -- descriptors that can be duplicated and polled, processes and + * their ids, signals, clocks, mapped memory, sockets, terminals. All of + * it is OPTIONAL in the sense the threads and the filesystem are: a + * backend that cannot do one of these defines it anyway and fails with + * ENOSYS (os/absent.inc is the list, and the bare-metal backends include + * it whole). The POSIX functions in src/posix sit on these and nothing + * else. + * + * Each is shaped like the POSIX call it serves -- the same arguments, + * the same meaning -- because there is nothing to gain by inventing a + * second vocabulary for "duplicate this descriptor". Where the meaning + * needs a number (a flag, a command, a signal, an ioctl request, a + * socket address's layout) the number is the one Linux uses, which is + * what nearly every kernel since copied; a backend whose kernel numbers + * something differently translates, and EmbLinkOS's has the most to + * translate. Every call returns -1 with errno set on failure unless it + * says otherwise. + * ========================================================================= */ + +/* ---- descriptors ---------------------------------------------------------- */ + +/* the rest of open's flags; O_DIRECTORY and O_NOFOLLOW are x86's numbers, + * which aarch64's kernel swaps with others and its backend translates */ +#define __OS_O_NOCTTY 0x00000100 +#define __OS_O_NONBLOCK 0x00000800 +#define __OS_O_DSYNC 0x00001000 +#if __INT_MAX__ > 0x7fff +#define __OS_O_DIRECTORY 0x00010000 +#define __OS_O_NOFOLLOW 0x00020000 +#define __OS_O_CLOEXEC 0x00080000 +#define __OS_O_SYNC 0x00101000 +#else /* a 16-bit int (AVR): as says, 0 */ +#define __OS_O_DIRECTORY 0 +#define __OS_O_NOFOLLOW 0 +#define __OS_O_CLOEXEC 0 +#define __OS_O_SYNC __OS_O_DSYNC +#endif + +/* fcntl's commands, and its one descriptor flag */ +#define __OS_F_DUPFD 0 +#define __OS_F_GETFD 1 +#define __OS_F_SETFD 2 +#define __OS_F_GETFL 3 +#define __OS_F_SETFL 4 +#define __OS_F_GETLK 5 +#define __OS_F_SETLK 6 +#define __OS_F_SETLKW 7 +#define __OS_F_DUPFD_CLOEXEC 1030 +#define __OS_FD_CLOEXEC 1 + +/* fcntl's lock description: struct flock, as both 64-bit Linux ABIs lay + * it out (the C library's declares this very structure) */ +struct __os_flock { + short l_type, l_whence; + long long l_start, l_len; + int l_pid; +}; + +/* dup, as F_DUPFD: the lowest free descriptor at or above minfd */ +int __os_dup(int fd, int minfd, int cloexec); +/* dup3: newfd becomes a copy of fd. fd == newfd is dup2's no-op, which + * succeeds when fd is open -- dup3 alone refuses it. */ +int __os_dup2(int fd, int newfd, int cloexec); +/* pipe2: fds[0] reads, fds[1] writes; flags are O_NONBLOCK and O_CLOEXEC */ +int __os_pipe(int fds[2], int flags); +/* every fcntl command but the dups; arg is an int or a struct __os_flock * */ +int __os_fcntl(int fd, int cmd, long arg); +int __os_fstat(int fd, struct __os_fileinfo *out); +long __os_pread(int fd, void *buf, size_t n, long long off); +long __os_pwrite(int fd, const void *buf, size_t n, long long off); +/* readv and writev, one call each: a pipe or socket sees one write */ +struct __os_iovec { void *base; size_t len; }; +long __os_readv(int fd, const struct __os_iovec *iov, int n); +long __os_writev(int fd, const struct __os_iovec *iov, int n); +int __os_fsync(int fd, int data_only); +int __os_ftruncate(int fd, long long size); +int __os_fchmod(int fd, unsigned mode); +int __os_fchdir(int fd); +/* the requests are Linux's (TCGETS, TIOCGWINSZ, FIONREAD, ...) */ +int __os_ioctl(int fd, unsigned long request, void *arg); +/* flock(2)'s whole-file advisory lock: LOCK_SH 1, LOCK_EX 2, LOCK_NB 4, + * LOCK_UN 8 */ +int __os_flock_file(int fd, int op); +/* The absolute path the descriptor is open on, for the *at functions, + * which resolve (dirfd, relative path) through it: openat, unlinkat and + * the rest exist precisely so a program can name a file relative to a + * directory it holds open. ENOSYS where the OS cannot say. */ +int __os_fdpath(int fd, char *buf, size_t n); + +/* ---- the filesystem, the rest -------------------------------------------- */ + +/* access(2)'s question, for the real ids: F_OK 0, X_OK 1, W_OK 2, R_OK 4 */ +int __os_access(const char *path, int mode); +/* The *at forms, relative to a directory descriptor (dirfd AT_FDCWD, + * -100, is the working directory); flags are Linux's AT_SYMLINK_NOFOLLOW + * 0x100, AT_REMOVEDIR 0x200, AT_SYMLINK_FOLLOW 0x400, AT_EMPTY_PATH + * 0x1000. A backend without them answers ENOSYS, and src/posix resolves + * the directory to a path itself (__os_fdpath) and calls the plain + * form -- which a kernel with real *at calls should not be made to do: + * it needs /proc on Linux, and races with a rename of the directory. */ +int __os_openat(int dirfd, const char *path, int flags, int mode); +int __os_fstatat(int dirfd, const char *path, struct __os_fileinfo *out, + int flags); +int __os_mkdirat(int dirfd, const char *path, unsigned mode); +int __os_unlinkat(int dirfd, const char *path, int flags); +int __os_renameat(int fromfd, const char *from, int tofd, const char *to); +int __os_linkat(int fromfd, const char *from, int tofd, const char *to, + int flags); +int __os_symlinkat(const char *target, int dirfd, const char *path); +long __os_readlinkat(int dirfd, const char *path, char *buf, size_t n); +int __os_fchmodat(int dirfd, const char *path, unsigned mode, int flags); +int __os_faccessat(int dirfd, const char *path, int mode, int flags); +int __os_mknodat(int dirfd, const char *path, unsigned mode, + unsigned long long dev); +/* the old mask; a backend with none answers 022 and changes nothing */ +unsigned __os_umask(unsigned mask); +/* path NULL: the descriptor's file; else path relative to the directory + * fd (AT_FDCWD for the working directory). nofollow: a symlink itself. */ +int __os_chown(const char *path, int fd, unsigned uid, unsigned gid, + int nofollow); +/* times[0] access, times[1] modification; a nsec of UTIME_NOW + * (0x3fffffff) or UTIME_OMIT (0x3ffffffe), as Linux's. path and fd as + * for __os_chown. */ +struct __os_timespec { long long sec; long nsec; }; +int __os_utimens(const char *path, int fd, + const struct __os_timespec times[2], int nofollow); +int __os_mknod(const char *path, unsigned mode, unsigned long long dev); +int __os_chroot(const char *path); +void __os_sync(void); +/* statvfs: everything a filesystem's superblock says, in its own units */ +struct __os_fsinfo { + unsigned long bsize, frsize; + unsigned long long blocks, bfree, bavail, files, ffree, favail; + unsigned long fsid, flag, namemax; +}; +/* path NULL: the descriptor's filesystem */ +int __os_statvfs(const char *path, int fd, struct __os_fsinfo *out); + +/* ---- directories, as POSIX reads them ------------------------------------ + * + * __os_readdir above serves , which wants neither `.` nor + * `..` nor an inode. POSIX's readdir wants all three, and a position to + * come back to. These read the same handles __os_opendir makes. */ +struct __os_dirent { + unsigned long long ino; + int type; /* __OS_FT_*; UNKNOWN when the OS says not */ + char name[256]; +}; +/* 1 with *e filled, 0 at the end, -1 on error -- `.` and `..` included + * when the OS reports them */ +int __os_readdir_entry(void *dir, struct __os_dirent *e); +void __os_rewinddir(void *dir); +/* the descriptor the handle reads, or -1 with ENOTSUP when it has none */ +int __os_dirfd(void *dir); +/* a handle on an open directory descriptor, which it then owns */ +void *__os_fdopendir(int fd); + +/* ---- processes ------------------------------------------------------------ */ + +#define __OS_ID_PID 0 +#define __OS_ID_PPID 1 +#define __OS_ID_UID 2 +#define __OS_ID_EUID 3 +#define __OS_ID_GID 4 +#define __OS_ID_EGID 5 +/* this process's id of the kind asked; no backend fails it -- one with no + * users answers 0 for the four user and group ids */ +long __os_getid(int which); +/* setuid, seteuid, setgid, setegid: which is UID, EUID, GID or EGID */ +int __os_setid(int which, unsigned id); +int __os_getgroups(int n, unsigned *list); +int __os_setgroups(int n, const unsigned *list); +int __os_getpgid(int pid); +int __os_setpgid(int pid, int pgid); +int __os_getsid(int pid); +int __os_setsid(void); + +/* fork: the child's pid in the parent, 0 in the child. A kernel that + * cannot copy a process -- EmbLinkOS starts new ones -- fails it with + * ENOSYS, and a program has posix_spawn. */ +int __os_fork(void); +/* returns only on failure */ +int __os_execve(const char *path, char *const argv[], char *const envp[]); + +/* posix_spawn, whole: a NEW process running path, with the file actions + * applied in order in the child and the attributes set. The kinds and + * the attribute flags are POSIX's, numbered as glibc numbers them. */ +#define __OS_SPAWN_OPEN 1 +#define __OS_SPAWN_CLOSE 2 +#define __OS_SPAWN_DUP2 3 +#define __OS_SPAWN_CHDIR 4 +#define __OS_SPAWN_FCHDIR 5 +struct __os_spawn_action { + int kind; + int fd, newfd; /* OPEN: fd is the target; DUP2: fd -> newfd */ + int oflag; + unsigned mode; + const char *path; /* OPEN, CHDIR */ +}; +#define __OS_SPAWN_RESETIDS 0x01 +#define __OS_SPAWN_SETPGROUP 0x02 +#define __OS_SPAWN_SETSIGDEF 0x04 +#define __OS_SPAWN_SETSIGMASK 0x08 +#define __OS_SPAWN_SETSID 0x80 +struct __os_spawn_attr { + int flags; + int pgroup; + unsigned long long sigdefault, sigmask; +}; +/* The attribute flags this backend's spawn honours. posix_spawnattr_setflags + * refuses the others with ENOTSUP at the moment they are set -- a flag + * that would be silently ignored is a promise nobody keeps. */ +int __os_spawn_flags(void); +/* A file action as it is added, before it joins the list: 0, or the error + * number posix_spawn_file_actions_add* is to return for an action this + * backend's spawn could never carry out -- one too many, a path too long + * for it, a kind it has not got. `count` is how many the list holds + * already. The error then belongs to the call that asked for the + * impossible, not to a posix_spawn several calls later. */ +int __os_spawn_check(const struct __os_spawn_action *action, int count); +/* 0 with *pid set, or -1 with errno -- including an error in the child + * before it ran path (a file action that failed, a path not found), + * which the parent learns of here rather than as an exit status */ +int __os_spawn(int *pid, const char *path, + const struct __os_spawn_action *actions, int nactions, + const struct __os_spawn_attr *attr, + char *const argv[], char *const envp[]); + +/* waitpid, with POSIX's status encoding as Linux makes it -- an exit is + * (code & 0xff) << 8, a signal its number -- and its options WNOHANG 1, + * WUNTRACED 2, WCONTINUED 8. 0 for WNOHANG with nothing ready. */ +int __os_waitpid(int pid, int *status, int options); + +/* ---- signals --------------------------------------------------------------- + * + * Linux's numbers (SIGHUP 1 ... SIGSYS 31, and the real-time ones to 64) + * and a 64-bit set, bit sig-1. A backend that delivers no signals fails + * sigaction and the rest with ENOSYS; kill may still work, as EmbLinkOS's + * does, ending the process it names. */ +#define __OS_SA_NOCLDSTOP 0x00000001 +#define __OS_SA_NOCLDWAIT 0x00000002 +#define __OS_SA_SIGINFO 0x00000004 +#define __OS_SA_ONSTACK 0x08000000 +#define __OS_SA_RESTART 0x10000000 +#define __OS_SA_NODEFER 0x40000000 +#define __OS_SA_RESETHAND 0x80000000 +struct __os_sigaction { + void *handler; /* 1-arg, or 3-arg with SA_SIGINFO; + * (void *)0 SIG_DFL, (void *)1 SIG_IGN */ + unsigned long long mask; + int flags; +}; +int __os_kill(int pid, int sig); +int __os_thread_kill(unsigned long thread, int sig); +int __os_sigaction(int sig, const struct __os_sigaction *act, + struct __os_sigaction *old); +/* how: SIG_BLOCK 0, SIG_UNBLOCK 1, SIG_SETMASK 2; either set may be NULL */ +int __os_sigprocmask(int how, const unsigned long long *set, + unsigned long long *old); +int __os_sigpending(unsigned long long *set); +/* always -1, EINTR once a signal has been handled */ +int __os_sigsuspend(const unsigned long long *mask); +/* the signal taken from set, info (a siginfo_t, 128 bytes, Linux's) filled + * if not NULL; timeout NULL waits for ever */ +int __os_sigtimedwait(const unsigned long long *set, void *info, + const struct __os_timespec *timeout); +/* sigaltstack: {sp, flags, size}, Linux's stack_t */ +int __os_sigaltstack(const void *ss, void *old); +/* setitimer: which REAL 0, VIRTUAL 1, PROF 2; val {interval, value} as + * seconds and microseconds */ +struct __os_itimer { long long isec, iusec, vsec, vusec; }; +int __os_setitimer(int which, const struct __os_itimer *val, + struct __os_itimer *old); +int __os_getitimer(int which, struct __os_itimer *cur); + +/* ---- time ------------------------------------------------------------------ + * + * Linux's clock ids: REALTIME 0, MONOTONIC 1, PROCESS_CPUTIME_ID 2, + * THREAD_CPUTIME_ID 3, MONOTONIC_RAW 4, REALTIME_COARSE 5, + * MONOTONIC_COARSE 6, BOOTTIME 7. A clock the target has not got fails + * with EINVAL. */ +int __os_clock_gettime(int clock, struct __os_timespec *t); +int __os_clock_getres(int clock, struct __os_timespec *t); +int __os_clock_settime(int clock, const struct __os_timespec *t); +/* abs: req is a time on the clock, not an interval. On EINTR *rem, if + * not NULL, is what was left. */ +int __os_clock_nanosleep(int clock, int abs, const struct __os_timespec *req, + struct __os_timespec *rem); +/* times(2): the four in clock ticks (sysconf's _SC_CLK_TCK), and the + * ticks since an arbitrary point */ +long long __os_times(long long out[4]); +/* getrusage's who: SELF 0, CHILDREN -1, THREAD 1. Only what POSIX names: + * user and system time in microseconds, and the largest resident size. */ +struct __os_rusage { long long utime_us, stime_us; long maxrss_kb; }; +int __os_getrusage(int who, struct __os_rusage *out); +/* getrlimit and setrlimit: Linux's resources (CPU 0 ... AS 9 ...), + * RLIM_INFINITY ~0 */ +int __os_getrlimit(int resource, unsigned long long lim[2]); +int __os_setrlimit(int resource, const unsigned long long lim[2]); + +/* ---- memory ---------------------------------------------------------------- + * + * mmap's: PROT_READ 1, WRITE 2, EXEC 4; MAP_SHARED 1, PRIVATE 2, + * FIXED 0x10, ANONYMOUS 0x20. (void *)-1 on failure. */ +void *__os_mmap(void *addr, size_t len, int prot, int flags, int fd, + long long off); +int __os_munmap(void *addr, size_t len); +int __os_mprotect(void *addr, size_t len, int prot); +/* MS_ASYNC 1, MS_INVALIDATE 2, MS_SYNC 4 */ +int __os_msync(void *addr, size_t len, int flags); +int __os_madvise(void *addr, size_t len, int advice); +/* lock nonzero: mlock, else munlock */ +int __os_mlock(const void *addr, size_t len, int lock); + +/* ---- the system ------------------------------------------------------------ */ + +struct __os_utsname { + char sysname[65], nodename[65], release[65], version[65], machine[65]; +}; +int __os_uname(struct __os_utsname *out); +int __os_sethostname(const char *name, size_t len); +/* What only the running system knows, for sysconf: NPROCESSORS_CONF 0, + * NPROCESSORS_ONLN 1, PHYS_PAGES 2, AVPHYS_PAGES 3, PAGESIZE 4. -1 with + * EINVAL for what it cannot say. */ +#define __OS_INFO_NPROCESSORS_CONF 0 +#define __OS_INFO_NPROCESSORS_ONLN 1 +#define __OS_INFO_PHYS_PAGES 2 +#define __OS_INFO_AVPHYS_PAGES 3 +#define __OS_INFO_PAGESIZE 4 +long __os_sysinfo(int what); + +/* ---- waiting on descriptors --------------------------------------------- + * + * ppoll: struct pollfd's layout and POLLIN 1, PRI 2, OUT 4, ERR 8, + * HUP 16, NVAL 32. timeout NULL waits for ever; sigmask NULL keeps the + * mask. The number ready, 0 at the timeout. select is built on it. */ +struct __os_pollfd { int fd; short events, revents; }; +int __os_poll(struct __os_pollfd *fds, unsigned long n, + const struct __os_timespec *timeout, + const unsigned long long *sigmask); + +/* ---- sockets --------------------------------------------------------------- + * + * Addresses are struct sockaddr as Linux lays them out -- a 16-bit + * family first, then the family's own fields in network order -- which + * is the layout declares; families AF_UNIX 1, AF_INET 2, + * AF_INET6 10; types SOCK_STREAM 1, SOCK_DGRAM 2, SOCK_RAW 3, with + * SOCK_NONBLOCK 0x800 and SOCK_CLOEXEC 0x80000 or'd in. The levels and + * option names are Linux's too (SOL_SOCKET 1, SO_REUSEADDR 2, ...), as + * is struct msghdr. */ +int __os_socket(int domain, int type, int protocol); +int __os_socketpair(int domain, int type, int protocol, int sv[2]); +int __os_bind(int fd, const void *addr, unsigned len); +int __os_connect(int fd, const void *addr, unsigned len); +int __os_listen(int fd, int backlog); +/* accept4: flags SOCK_NONBLOCK, SOCK_CLOEXEC */ +int __os_accept(int fd, void *addr, unsigned *len, int flags); +long __os_sendto(int fd, const void *buf, size_t n, int flags, + const void *addr, unsigned alen); +long __os_recvfrom(int fd, void *buf, size_t n, int flags, + void *addr, unsigned *alen); +long __os_sendmsg(int fd, const void *msg, int flags); +long __os_recvmsg(int fd, void *msg, int flags); +int __os_shutdown(int fd, int how); +int __os_getsockopt(int fd, int level, int name, void *val, unsigned *len); +int __os_setsockopt(int fd, int level, int name, const void *val, + unsigned len); +int __os_getsockname(int fd, void *addr, unsigned *len); +int __os_getpeername(int fd, void *addr, unsigned *len); +/* A name looked up by the OS itself, where it has a resolver of its own + * (EmbLinkOS does): up to max addresses of the family (AF_INET: 4 bytes + * each, AF_INET6: 16), in network order, into addrs; the count, 0 for a + * name that is not there. ENOSYS where the C library must ask DNS + * itself, which src/posix/resolv.c then does. */ +int __os_resolve(const char *name, int family, unsigned char *addrs, int max); + +/* ---- terminals ------------------------------------------------------------ + * + * struct termios as glibc lays it out -- four 32-bit flag words, a line + * discipline byte, 32 control characters, two speeds -- with Linux's + * flag bits and c_cc indices. Linux's kernel has a shorter one, which + * its backend converts. action TCSANOW 0, TCSADRAIN 1, TCSAFLUSH 2. */ +struct __os_termios { + unsigned iflag, oflag, cflag, lflag; + unsigned char line; + unsigned char cc[32]; + unsigned ispeed, ospeed; +}; +int __os_tcgetattr(int fd, struct __os_termios *t); +int __os_tcsetattr(int fd, int action, const struct __os_termios *t); +/* the terminal device fd is, as a path */ +int __os_ttyname(int fd, char *buf, size_t n); +/* a pseudo-terminal's controlling side, its other side's name, and + * permission to open that other side */ +int __os_openpt(int flags); +int __os_ptsname(int fd, char *buf, size_t n); +int __os_unlockpt(int fd); + #ifdef __cplusplus } #endif diff --git a/lib/libc/os/baremetal/backend.c b/lib/libc/os/baremetal/backend.c index 10dcede24..8d09949fb 100644 --- a/lib/libc/os/baremetal/backend.c +++ b/lib/libc/os/baremetal/backend.c @@ -238,3 +238,7 @@ int __os_readdir(void *d, char *name, size_t n, int *type) return nosys(); } void __os_closedir(void *d) { (void)d; } + +/* A bare-metal target has none of POSIX's operating system: every primitive + * of that half of the contract reports ENOSYS (os/absent.inc). */ +#include "../absent.inc" diff --git a/lib/libc/os/emblinkos/backend.c b/lib/libc/os/emblinkos/backend.c index 89004323e..c2705a71f 100644 --- a/lib/libc/os/emblinkos/backend.c +++ b/lib/libc/os/emblinkos/backend.c @@ -1,18 +1,39 @@ /* The backend for EmbLinkOS. * - * This file is what replaces `emlibc`. Not a port of it -- a REPLACEMENT of - * everything except its bottom edge: EmbLinkOS keeps its syscall ABI and - * its path conventions, and gets the same printf, strtod, malloc and math - * as every other target. That was the whole point of the seam. emlibc's - * `rim/syscalls.c` was 184 lines of OS-specific code under ~7000 lines of - * portable C that duplicated newlib's; only the 184 lines were ever really - * EmbLinkOS's, and they are what survives here. - * - * The ABI comes from the OS's own / rather than numbers - * copied into this repository. The syscall numbers belong to the kernel and - * are hand-synchronised with it (that header says so in capitals); a second - * copy here would be a second thing to forget. Build with + * Every primitive of os/backend.h on EmbLinkOS's own system calls, read + * from that OS's and rather than copied here -- + * the numbers belong to the kernel and are hand-synchronised with it, so a + * second copy would be a second thing to forget. Build with * `make libc-emblinkos EMBLINKOS=/path/to/EmbLinkOs`. + * + * ---- where the meaning comes from ----------------------------------------- + * + * EmbLinkOS's newlib adapter (user/lib/syscalls.c and spawn_compat.inc) + * already worked out, call by call, what POSIX means on this kernel, and + * its test suite holds it to that. This file carries those decisions + * over, under that adapter's three honest categories: + * + * REAL the kernel can do it: done truthfully + * VACUOUS the promise holds here whatever is done -- FD_CLOEXEC on a + * system with no exec, a mask on a system with no signals -- + * so saying yes is a true statement + * ABSENT the capability does not exist: ENOSYS (os/absent.inc for + * everything not named below), never a pretended success + * + * ---- what this OS is --------------------------------------------------------- + * + * paths the kernel resolves absolute paths only, up to 256 bytes; + * the working directory is this library's, per process, and + * a child's is named in its spawn, never inherited by accident + * processes spawn, not fork: a new program, set up by explicit actions, + * waited for through the child handles its parent owns + * signals none delivered: lib/libc's own model (stdlib/signal.c) is + * what a program has, and kill reaches only itself + * network IPv4, through the kernel's own sockets and resolver + * users one, with the whole machine's authority: every id is 0 + * + * errno: the kernel numbers its errors as Linux does, and so does + * lib/libc's , so a kernel error passes through as itself. */ #include "../backend.h" @@ -20,218 +41,437 @@ #include #include -/* embk.h is header-only and usable from a freestanding program, so this - * backend needs nothing linked from the OS side -- only the ABI. - * - * stdbool first because embk.h's mutex helpers pass `false` to - * __atomic_compare_exchange_n without including it; under newlib something - * else always had. Including it here is a one-line courtesy rather than an - * edit to that repository's header. */ +/* stdbool first because embk.h's mutex helpers pass `false` to + * __atomic_compare_exchange_n without including it. */ #include #include +#include +#include -/* The kernel returns -errno on failure, using the SAME numbers this - * library's defines (EPERM 1, ENOENT 2, EBADF 9, EINVAL 22 ...): - * kernel/include/errno.h and lib/libc/include/errno.h agree value for - * value. So the mapping is the identity, and saying that here is better - * than a translation table that would silently rot if either side moved. */ static long fail(long ret) { errno = (int)-ret; return -1; } -/* ---- paths --------------------------------------------------------------- - * The kernel resolves absolute paths only, so the working directory is a - * userspace fact. It lives HERE, in the backend, rather than in the - * portable library: "what does this name refer to" is part of how an - * operating system names files, which is exactly what a backend is for. - * A target with no filesystem never compiles this. - */ -#define EM_PATH_MAX 1024 +static int ret0(int64_t r) { return embk_is_err(r) ? (int)fail((long)r) : 0; } +static long retn(int64_t r) { return embk_is_err(r) ? fail((long)r) : (long)r; } + +/* ---- paths ---------------------------------------------------------------- */ + +/* the kernel's SYSCALL_PATH_MAX: a longer path is refused there anyway */ +#define EM_PATH_MAX 256 static char g_cwd[EM_PATH_MAX] = "/"; -static const char *path_abs(const char *in, char *buf, size_t cap) +/* in, made absolute against cwd and NORMALIZED -- "." dropped, ".." + * popping a component, "/.." staying "/" -- into out: the kernel has no + * rule for ".." and an absolute path with one in it would fail there. + * 0, or -1 with errno (an empty path is ENOENT, not the root). */ +static int path_from(const char *cwd, const char *in, char *out, size_t cap) { - if (in && in[0] == '/') - return in; /* already absolute */ - size_t cl = strlen(g_cwd), il = in ? strlen(in) : 0; - if (cl + 1 + il + 1 > cap) - return in; /* too long: let the kernel judge */ - memcpy(buf, g_cwd, cl); - size_t p = cl; - if (p == 0 || buf[p - 1] != '/') - buf[p++] = '/'; - if (in) { memcpy(buf + p, in, il); p += il; } - buf[p] = 0; - return buf; + if (!in) { + errno = EFAULT; + return -1; + } + if (!*in) { + errno = ENOENT; + return -1; + } + char raw[EM_PATH_MAX * 2]; + size_t il = strlen(in), cl = strlen(cwd); + if (in[0] == '/') { + if (il >= sizeof raw) + goto toolong; + memcpy(raw, in, il + 1); + } else { + if (cl + 1 + il >= sizeof raw) + goto toolong; + memcpy(raw, cwd, cl); + size_t at = cl; + if (!cl || raw[cl - 1] != '/') + raw[at++] = '/'; + memcpy(raw + at, in, il + 1); + } + size_t o = 0; + out[o++] = '/'; + for (const char *p = raw; *p; ) { + while (*p == '/') + p++; + if (!*p) + break; + const char *seg = p; + size_t n = 0; + while (p[n] && p[n] != '/') + n++; + p += n; + if (n == 1 && seg[0] == '.') + continue; + if (n == 2 && seg[0] == '.' && seg[1] == '.') { + while (o > 1 && out[o - 1] != '/') + o--; + if (o > 1) + o--; + continue; + } + if (o > 1) { + if (o + 1 >= cap) + goto toolong; + out[o++] = '/'; + } + if (o + n >= cap) + goto toolong; + memcpy(out + o, seg, n); + o += n; + } + out[o] = 0; + return 0; +toolong: + errno = ENAMETOOLONG; + return -1; } -/* Not C11, and so not declared in any header this library installs -- but - * EmbLinkOS programs use them and they have nowhere else to live. */ -char *getcwd(char *buf, size_t size) +#define ABS(path, buf) \ + char buf[EM_PATH_MAX]; \ + if (path_from(g_cwd, (path), buf, sizeof buf) < 0) \ + return -1 + +/* The environment a child gets when its parent names none -- the + * EmbLinkOS default: nothing is inherited unless named. crt0 points + * environ here then, rather than at NULL, so getenv can walk it. */ +char *embk_empty_env[1] = { 0 }; + +/* crt0 calls this once environ is installed: the child's starting + * directory as its spawn named it, else PWD (how a native parent hands + * one over), else the root. A PWD naming somewhere gone must not stop + * the program, so it is checked and otherwise ignored. */ +void embk_cwd_init_from_env(void) { - size_t n = strlen(g_cwd); - if (!buf || size <= n) { errno = ERANGE; return 0; } - memcpy(buf, g_cwd, n + 1); - return buf; + char initial[EM_PATH_MAX]; + int64_t n = embk_syscall2(EMBK_SYS_spawn_cwd, (int64_t)(uintptr_t)initial, + sizeof initial); + if (n > 0 && n < (int64_t)sizeof g_cwd) { + memcpy(g_cwd, initial, (size_t)n); + g_cwd[n] = 0; + return; + } + extern char *getenv(const char *name); + const char *pwd = getenv("PWD"); + if (pwd && pwd[0] == '/') + __os_chdir(pwd); } -int chdir(const char *path) +/* ---- the kernel's file metadata --------------------------------------------- */ + +static int type_of(uint8_t t, uint32_t mode) { - char tmp[EM_PATH_MAX]; - const char *ap = path_abs(path, tmp, sizeof tmp); - struct embk_stat st; - long r = (long)embk_syscall2(EMBK_SYS_stat, (int64_t)(intptr_t)ap, - (int64_t)(intptr_t)&st); - if (embk_is_err(r)) - return (int)fail(r); - size_t n = strlen(ap); - if (n >= sizeof g_cwd) { errno = ENAMETOOLONG; return -1; } - memcpy(g_cwd, ap, n + 1); - return 0; + switch (t) { + case EMBK_DT_REG: return __OS_FT_REGULAR; + case EMBK_DT_DIR: return __OS_FT_DIRECTORY; + case EMBK_DT_LNK: return __OS_FT_SYMLINK; + case 5: return __OS_FT_CHARDEV; /* VFS_DT_CHAR: the console */ + case 6: return __OS_FT_FIFO; /* VFS_DT_FIFO: a pipe end */ + default: break; + } + switch (mode & 0170000u) { + case 0100000u: return __OS_FT_REGULAR; + case 0040000u: return __OS_FT_DIRECTORY; + case 0120000u: return __OS_FT_SYMLINK; + case 0020000u: return __OS_FT_CHARDEV; + case 0060000u: return __OS_FT_BLOCKDEV; + case 0010000u: return __OS_FT_FIFO; + case 0140000u: return __OS_FT_SOCKET; + default: return __OS_FT_UNKNOWN; + } } -/* crt0 calls this after publishing environ: a parent names the child's - * start directory with PWD, and nothing is inherited unless it does. */ -extern char *getenv(const char *name); - -void embk_cwd_init_from_env(void) +/* The kernel reports no owner (there is one user), no device number and + * no inode through stat -- an inode only in a directory entry -- so those + * stay zero; equivalent() then compares resolved paths, which on one + * filesystem is right. */ +static int fill_info(int64_t r, const struct embk_stat *st, + struct __os_fileinfo *out) { - const char *pwd = getenv("PWD"); - if (pwd && pwd[0] == '/' && strlen(pwd) < sizeof g_cwd) - memcpy(g_cwd, pwd, strlen(pwd) + 1); + if (embk_is_err(r)) + return (int)fail((long)r); + memset(out, 0, sizeof *out); + out->size = st->size; + out->mtime = (long)st->mtime; + out->mode = st->mode & 07777u; + out->type = type_of(st->type, st->mode); + out->nlink = st->nlink ? st->nlink : 1; + out->blksize = out->type == __OS_FT_CHARDEV ? 1024 : 4096; + return 0; } -/* ---- the eleven primitives ---------------------------------------------- */ +/* ---- bytes ------------------------------------------------------------------- */ long __os_write(int fd, const void *buf, size_t n) { - long r = (long)embk_syscall3(EMBK_SYS_write, fd, (int64_t)(intptr_t)buf, - (int64_t)n); - return embk_is_err(r) ? fail(r) : r; + return retn(embk_syscall3(EMBK_SYS_write, fd, (int64_t)(intptr_t)buf, + (int64_t)n)); } long __os_read(int fd, void *buf, size_t n) { - long r = (long)embk_syscall3(EMBK_SYS_read, fd, (int64_t)(intptr_t)buf, - (int64_t)n); - return embk_is_err(r) ? fail(r) : r; + return retn(embk_syscall3(EMBK_SYS_read, fd, (int64_t)(intptr_t)buf, + (int64_t)n)); } -int __os_open(const char *path, int flags, int mode) +/* ---- files ------------------------------------------------------------------- */ + +/* The seam's flags are Linux's; the kernel's agree on all but + * close-on-exec, which it numbers 0x100000. It has no O_DIRECTORY and no + * O_NOFOLLOW, and both are honoured here rather than dropped: the first + * by looking at what was opened, the second by looking before. */ +#define EM_O_CLOEXEC 0x100000 + +static int open_flags(int f) { - char tmp[EM_PATH_MAX]; - const char *ap = path_abs(path, tmp, sizeof tmp); - long r = (long)embk_syscall3(EMBK_SYS_open, (int64_t)(intptr_t)ap, - (int64_t)flags, (int64_t)mode); - return embk_is_err(r) ? (int)fail(r) : (int)r; + int out = f & (__OS_O_RDONLY | __OS_O_WRONLY | __OS_O_RDWR | + __OS_O_CREAT | __OS_O_EXCL | __OS_O_TRUNC | + __OS_O_APPEND | __OS_O_NONBLOCK); + if (f & __OS_O_CLOEXEC) + out |= EM_O_CLOEXEC; + return out; } -int __os_close(int fd) +int __os_open(const char *path, int flags, int mode) { - long r = (long)embk_syscall1(EMBK_SYS_close, fd); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(path, ap); + if (flags & __OS_O_NOFOLLOW) { + struct embk_stat st; + int64_t r = embk_syscall2(EMBK_SYS_lstat, (int64_t)(intptr_t)ap, + (int64_t)(intptr_t)&st); + if (!embk_is_err(r) && type_of(st.type, st.mode) == __OS_FT_SYMLINK) { + errno = ELOOP; + return -1; + } + } + int64_t fd = embk_syscall3(EMBK_SYS_open, (int64_t)(intptr_t)ap, + open_flags(flags), mode); + if (embk_is_err(fd)) + return (int)fail((long)fd); + if (flags & __OS_O_DIRECTORY) { + struct embk_stat st; + if (embk_is_err(embk_syscall2(EMBK_SYS_fstat, fd, + (int64_t)(intptr_t)&st)) || + type_of(st.type, st.mode) != __OS_FT_DIRECTORY) { + embk_syscall1(EMBK_SYS_close, fd); + errno = ENOTDIR; + return -1; + } + } + return (int)fd; } +int __os_close(int fd) { return ret0(embk_syscall1(EMBK_SYS_close, fd)); } + long __os_lseek(int fd, long off, int whence) { - long r = (long)embk_syscall3(EMBK_SYS_lseek, fd, (int64_t)off, - (int64_t)whence); - return embk_is_err(r) ? fail(r) : r; + return retn(embk_syscall3(EMBK_SYS_lseek, fd, (int64_t)off, whence)); } -/* Robust against whether the kernel returns the old break or the new one: - * callers use sbrk(0) to read it and sbrk(+n) only to grow, so exactly one - * of the two conventions has to hold and it does not matter which. */ -void *__os_sbrk(long increment) +/* C's remove takes a file or an empty directory */ +int __os_remove(const char *path) { - long r = (long)embk_syscall1(EMBK_SYS_sbrk, (int64_t)increment); - if (embk_is_err(r)) { fail(r); return (void *)-1; } - return (void *)(uintptr_t)r; + ABS(path, ap); + int64_t r = embk_syscall1(EMBK_SYS_unlink, (int64_t)(intptr_t)ap); + if (embk_is_err(r) && (r == -EISDIR || r == -EPERM)) { + struct embk_stat st; + if (!embk_is_err(embk_syscall2(EMBK_SYS_lstat, (int64_t)(intptr_t)ap, + (int64_t)(intptr_t)&st)) && + type_of(st.type, st.mode) == __OS_FT_DIRECTORY) + r = embk_syscall1(EMBK_SYS_rmdir, (int64_t)(intptr_t)ap); + } + return ret0(r); } -int __os_remove(const char *path) +/* The kernel replaces an existing destination in the same commit that + * moves the name -- atomically, which is what git's lockfiles and every + * write-then-rename save rely on. So this is the one call, not an unlink + * followed by a rename that a crash between the two would make lose the + * destination. */ +int __os_rename(const char *from, const char *to) { - char tmp[EM_PATH_MAX]; - const char *ap = path_abs(path, tmp, sizeof tmp); - long r = (long)embk_syscall1(EMBK_SYS_unlink, (int64_t)(intptr_t)ap); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(from, fa); + ABS(to, ta); + return ret0(embk_syscall2(EMBK_SYS_rename, (int64_t)(intptr_t)fa, + (int64_t)(intptr_t)ta)); } -/* The kernel's rename is STRICT: it fails -EEXIST when the destination - * exists, where C's rename replaces it. Replacing is the libc's job, so - * the destination is removed first -- and only after the source is known - * to exist, or a failed rename would have destroyed the target. */ -int __os_rename(const char *from, const char *to) +void *__os_sbrk(long increment) { - char fb[EM_PATH_MAX], tb[EM_PATH_MAX]; - const char *fa = path_abs(from, fb, sizeof fb); - struct embk_stat st; - long r = (long)embk_syscall2(EMBK_SYS_stat, (int64_t)(intptr_t)fa, - (int64_t)(intptr_t)&st); - if (embk_is_err(r)) - return (int)fail(r); - const char *ta = path_abs(to, tb, sizeof tb); - embk_syscall1(EMBK_SYS_unlink, (int64_t)(intptr_t)ta); /* may fail */ - r = (long)embk_syscall2(EMBK_SYS_rename, (int64_t)(intptr_t)fa, - (int64_t)(intptr_t)ta); - return embk_is_err(r) ? (int)fail(r) : 0; + int64_t r = embk_syscall1(EMBK_SYS_sbrk, (int64_t)increment); + if (embk_is_err(r)) { + fail((long)r); + return (void *)-1; + } + return (void *)(uintptr_t)r; } +/* ---- time --------------------------------------------------------------------- */ + long __os_time(void) { uint64_t out[2]; /* [0] seconds, [1] microseconds */ - long r = (long)embk_syscall1(EMBK_SYS_gettimeofday, - (int64_t)(intptr_t)out); - if (embk_is_err(r)) - return fail(r); - return (long)out[0]; + int64_t r = embk_syscall1(EMBK_SYS_gettimeofday, (int64_t)(intptr_t)out); + return embk_is_err(r) ? fail((long)r) : (long)out[0]; } -/* clock() is CPU time, and this kernel has no per-process CPU accounting - * (user/lib/syscalls.c refuses the CPU-time clock_gettime clocks for the - * same reason). SYS_uptime_ms is available and would produce a plausible - * number, which is precisely why it is not used here: under any load it - * would be wrong in a way the caller cannot detect. Failing is the answer - * the OS's own rule demands -- what the kernel does not provide is absent, - * not stubbed to lie. Give it CPU accounting and this becomes three lines. */ +/* there is no per-process CPU accounting to report */ long __os_clock_ns(void) { errno = ENOSYS; return -1; } +/* The clocks this OS has, at the precision they have: + * REALTIME the RTC, whole seconds -- sub-second digits from elsewhere + * would be precision the wall clock does not have + * MONOTONIC the HPET's uptime in milliseconds (RAW and BOOTTIME are + * the same counter: nothing slews it, and it spans suspend) + * THREAD_CPUTIME_ID the calling thread's CPU time + * The process's CPU time has no counter, and asking for it is EINVAL. */ +int __os_clock_gettime(int clock, struct __os_timespec *t) +{ + switch (clock) { + case 0: { + uint64_t out[2]; + int64_t r = embk_syscall1(EMBK_SYS_gettimeofday, + (int64_t)(intptr_t)out); + if (embk_is_err(r)) + return (int)fail((long)r); + t->sec = (long long)out[0]; + t->nsec = (long)out[1] * 1000L; + return 0; + } + case 1: case 4: case 7: { + uint64_t ms = embk_uptime_ms(); + t->sec = (long long)(ms / 1000); + t->nsec = (long)(ms % 1000) * 1000000L; + return 0; + } + case 3: { + uint64_t ns = embk_thread_cpu_ns(); + t->sec = (long long)(ns / 1000000000ull); + t->nsec = (long)(ns % 1000000000ull); + return 0; + } + } + errno = EINVAL; + return -1; +} + +/* The one limit this kernel has and can say: 64 descriptors a process + * (kernel/fs/fd.h's FD_MAX_OPEN), soft and hard alike. The others it + * does not keep, and an answer of "unlimited" would be a guess. */ +int __os_getrlimit(int resource, unsigned long long lim[2]) +{ + if (resource != 7) { /* RLIMIT_NOFILE */ + errno = ENOSYS; + return -1; + } + lim[0] = lim[1] = 64; + return 0; +} + +int __os_clock_getres(int clock, struct __os_timespec *t) +{ + long ns; + switch (clock) { + case 0: ns = 1000000000L; break; + case 1: case 4: case 7: ns = 1000000L; break; + case 3: ns = 1; break; + default: + errno = EINVAL; + return -1; + } + if (t) { + t->sec = ns / 1000000000L; + t->nsec = ns % 1000000000L; + } + return 0; +} + +/* The kernel sleeps in milliseconds; a request rounds UP, since waking + * early breaks a pacing loop and a fraction of a millisecond more is + * harmless. Nothing interrupts a sleep here, so nothing is left over. */ +static int sleep_ms_ns(long long ns) +{ + if (ns <= 0) + return 0; + uint64_t ms = (uint64_t)((ns + 999999) / 1000000); + int r = embk_sleep_ms(ms); + return embk_is_err(r) ? (int)fail(r) : 0; +} + +int __os_clock_nanosleep(int clock, int abs, const struct __os_timespec *req, + struct __os_timespec *rem) +{ + long long ns = req->sec * 1000000000LL + req->nsec; + if (abs) { + struct __os_timespec now; + if (__os_clock_gettime(clock, &now) < 0) + return -1; + ns -= now.sec * 1000000000LL + now.nsec; + } else if (clock != 0 && clock != 1 && clock != 4 && clock != 7) { + errno = EINVAL; + return -1; + } + if (rem) + rem->sec = rem->nsec = 0; + return sleep_ms_ns(ns); +} + +/* VACUOUS: with no CPU accounting, the times that have passed are zero, + * which clock() and times() then report consistently */ +long long __os_times(long long out[4]) +{ + out[0] = out[1] = out[2] = out[3] = 0; + return 0; +} + +/* ---- the rest of C's ---------------------------------------------------------- */ + void __os_exit(int status) { embk_syscall1(EMBK_SYS_exit, status); - for (;;) { } /* the kernel never returns; defensive */ + for (;;) { } } -/* Ask what actually backs the descriptor rather than assuming 0/1/2 are the - * console. A stdio slot the shell redirected into a pipeline correctly - * reports not-a-tty, which is what makes line buffering come out right. */ +/* a terminal is what fstat says is a character device: the console; a + * pipe-backed standard descriptor is not one */ int __os_isatty(int fd) { struct embk_stat st; - long r = (long)embk_syscall2(EMBK_SYS_fstat, fd, - (int64_t)(intptr_t)&st); - if (embk_is_err(r)) - return 0; - return (st.mode & 0170000) == 0020000; /* S_IFCHR */ + int64_t r = embk_syscall2(EMBK_SYS_fstat, fd, (int64_t)(intptr_t)&st); + return !embk_is_err(r) && type_of(st.type, st.mode) == __OS_FT_CHARDEV; } +/* The kernel's generator, which is seeded from every clock the machine + * has and reads RNDR at EL1 on an aarch64 part that has it. On x86, the + * processor's RDRAND where the kernel cannot answer. A security + * primitive fails rather than return bytes anyone could predict. */ int __os_getentropy(void *buf, size_t n) { + if (n > 256) { + errno = EIO; + return -1; + } + if (embk_getrandom(buf, n) == (int64_t)n) + return 0; #if defined(__x86_64__) unsigned char *p = buf; while (n) { unsigned long long v; unsigned char ok = 0; - for (int tries = 0; tries < 32 && !ok; tries++) + for (int tries = 0; tries < 10 && !ok; tries++) __asm__ volatile ("rdrand %0; setc %1" : "=r"(v), "=qm"(ok)); - if (!ok) { errno = EIO; return -1; } /* honest failure, not a byte */ + if (!ok) { + errno = EIO; + return -1; + } size_t take = n < sizeof v ? n : sizeof v; memcpy(p, &v, take); p += take; @@ -239,39 +479,15 @@ int __os_getentropy(void *buf, size_t n) } return 0; #else - /* aarch64's equivalent is FEAT_RNG's RNDR, which EL0 may execute -- but - * only where it exists, and the feature bit saying so is in - * ID_AA64ISAR0_EL1, which EL0 may not read: the MRS traps to EL1 and - * this kernel does not emulate it. There is no way to ask before - * executing, and executing it on a part without it is UNDEFINED. So - * fail, exactly as the x86 path fails when RDRAND is absent: a caller - * handed -1 can decide, a caller handed predictable bytes cannot. */ - (void)buf; (void)n; errno = ENOSYS; return -1; #endif } -/* ---- threads --------------------------------------------------------------- - * - * EmbLinkOS HAS them, and a futex, so both halves of the seam's - * optional threading group are real here: the C++ , - * and , and C's , all light up on this target - * without a line of either library knowing which OS it is. - * - * Two shapes have to be bridged. The kernel's thread entry takes a - * `long` and the seam's takes a `void *`, so the pointer travels as the - * long -- which is exact on LP64 and is the only reason this is a cast - * rather than a table. And the kernel's futex word is a `uint32_t` - * while the seam's is an `int`; they are the same four bytes and the - * comparison the kernel does is bitwise, so the cast is safe and is - * where the difference is written down. - */ +/* ---- threads, and blocking ---------------------------------------------------- */ + struct __os_start { void (*fn)(void *); void *arg; }; -/* The kernel's entry takes a long; the seam's takes a void *. The - * block is freed by the thread that runs it, so a thread that is - * never joined still leaks nothing. */ static void __os_thread_trampoline(long a) { struct __os_start *st = (struct __os_start *)(intptr_t)a; @@ -284,10 +500,6 @@ static void __os_thread_trampoline(long a) int __os_thread_create(unsigned long *id, void (*fn)(void *), void *arg) { - /* The entry signature differs, so the seam's function pointer and - * its argument are packed into one allocation and the trampoline - * unpacks them. A static table would bound the thread count for no - * reason; the block is freed by the thread that runs it. */ struct __os_start *st = malloc(sizeof *st); if (!st) { errno = ENOMEM; @@ -298,7 +510,7 @@ int __os_thread_create(unsigned long *id, void (*fn)(void *), void *arg) int64_t r = embk_thread_create(__os_thread_trampoline, (long)(intptr_t)st); if (embk_is_err(r)) { free(st); - return (int)fail(r); + return (int)fail((long)r); } *id = (unsigned long)r; return 0; @@ -306,52 +518,30 @@ int __os_thread_create(unsigned long *id, void (*fn)(void *), void *arg) int __os_thread_join(unsigned long id) { - int64_t r = embk_thread_join((int)id); - return embk_is_err(r) ? (int)fail(r) : 0; + return ret0(embk_thread_join((int)id)); } -/* The kernel reclaims a thread when it ends, so there is nothing to - * release: detaching is giving up the right to join, which is a - * decision the caller has already made by calling this. */ +/* the kernel reaps a thread that ends whether or not it is joined */ int __os_thread_detach(unsigned long id) { (void)id; return 0; } -unsigned long __os_thread_self(void) -{ - return (unsigned long)embk_thread_self(); -} - +unsigned long __os_thread_self(void) { return (unsigned long)embk_thread_self(); } void __os_thread_yield(void) { embk_yield(); } +int __os_sleep_ns(long ns) { return sleep_ms_ns(ns); } -int __os_sleep_ns(long ns) -{ - /* The kernel sleeps in milliseconds. A sub-millisecond request - * rounds UP to one rather than to zero: a caller asking to sleep - * has asked to yield the processor, and returning immediately is - * the one answer that is never what was meant. */ - uint64_t ms = (uint64_t)((ns + 999999L) / 1000000L); - if (ns > 0 && ms == 0) - ms = 1; - int r = embk_sleep_ms(ms); - return embk_is_err(r) ? (int)fail(r) : 0; -} - +/* The kernel's futex has no timeout, so a bounded wait is refused rather + * than turned into an unbounded one. */ int __os_futex_wait(const volatile int *addr, int expected, long timeout_ns) { - /* The kernel's futex has no timeout, so a bounded wait cannot be - * expressed and is refused rather than turned into an unbounded - * one -- which would hang where the caller asked not to. The C++ - * library handles the refusal (see lib/libcxx/include/mutex). */ if (timeout_ns >= 0) { errno = ENOSYS; return -1; } - int64_t r = embk_futex((volatile uint32_t *)(uintptr_t)addr, - EMBK_FUTEX_WAIT, (uint32_t)expected); - return embk_is_err(r) ? (int)fail(r) : 0; + return ret0(embk_futex((volatile uint32_t *)(uintptr_t)addr, + EMBK_FUTEX_WAIT, (uint32_t)expected)); } int __os_futex_wake(const volatile int *addr, int count) @@ -359,138 +549,74 @@ int __os_futex_wake(const volatile int *addr, int count) int64_t r = embk_futex((volatile uint32_t *)(uintptr_t)addr, EMBK_FUTEX_WAKE, count < 0 ? 0x7FFFFFFFu : (uint32_t)count); - return embk_is_err(r) ? (int)fail(r) : (int)r; + return (int)retn(r); } -/* ---- the filesystem --------------------------------------------------------- - * - * EmbLinkOS has the whole set, so works here rather than - * reporting ENOSYS. Two mappings are worth stating because they are not - * one-to-one: - * - * readdir reads the WHOLE directory in one call -- the kernel's is - * not resumable -- so opendir snapshots it and readdir walks the - * snapshot. A directory that changes while it is being walked is - * therefore read as it was at the open, which is what the standard - * permits and what every implementation does for the same reason. - * - * the kernel numbers its file types itself (EMBK_DT_*), so they are - * translated to the seam's rather than passed through. Passing them - * through would work until either side renumbered. - */ -static int type_of(uint8_t t, uint32_t mode) -{ - switch (t) { - case EMBK_DT_REG: return __OS_FT_REGULAR; - case EMBK_DT_DIR: return __OS_FT_DIRECTORY; - case EMBK_DT_LNK: return __OS_FT_SYMLINK; - default: break; - } - /* No type from the entry: fall back to the mode bits, which are - * POSIX-shaped. */ - switch (mode & 0170000u) { - case 0100000u: return __OS_FT_REGULAR; - case 0040000u: return __OS_FT_DIRECTORY; - case 0120000u: return __OS_FT_SYMLINK; - case 0020000u: return __OS_FT_CHARDEV; - case 0060000u: return __OS_FT_BLOCKDEV; - case 0010000u: return __OS_FT_FIFO; - case 0140000u: return __OS_FT_SOCKET; - default: return __OS_FT_UNKNOWN; - } -} +/* ---- the filesystem ------------------------------------------------------------ */ -static int fill_info(const struct embk_stat *st, struct __os_fileinfo *out) +int __os_stat(const char *path, struct __os_fileinfo *out) { - out->size = st->size; - out->mtime = (long)st->mtime; - out->mode = st->mode & 07777u; - out->type = type_of(st->type, st->mode); - out->nlink = st->nlink; - /* The kernel has no device number, and an inode only in a dirent. - * equivalent() therefore compares the resolved PATHS here, which is - * right for a single filesystem and is what this target has. */ - out->dev = 0; - out->ino = 0; - return 0; + ABS(path, ap); + struct embk_stat st; + return fill_info(embk_syscall2(EMBK_SYS_stat, (int64_t)(intptr_t)ap, + (int64_t)(intptr_t)&st), &st, out); } -int __os_stat(const char *path, struct __os_fileinfo *out) +int __os_lstat(const char *path, struct __os_fileinfo *out) { - char pb[EM_PATH_MAX]; - const char *ap = path_abs(path, pb, sizeof pb); + ABS(path, ap); struct embk_stat st; - long r = (long)embk_syscall2(EMBK_SYS_stat, (int64_t)(intptr_t)ap, - (int64_t)(intptr_t)&st); - if (embk_is_err(r)) - return (int)fail(r); - return fill_info(&st, out); + return fill_info(embk_syscall2(EMBK_SYS_lstat, (int64_t)(intptr_t)ap, + (int64_t)(intptr_t)&st), &st, out); } -int __os_lstat(const char *path, struct __os_fileinfo *out) +int __os_fstat(int fd, struct __os_fileinfo *out) { - char pb[EM_PATH_MAX]; - const char *ap = path_abs(path, pb, sizeof pb); struct embk_stat st; - long r = (long)embk_syscall2(EMBK_SYS_lstat, (int64_t)(intptr_t)ap, - (int64_t)(intptr_t)&st); - if (embk_is_err(r)) - return (int)fail(r); - return fill_info(&st, out); + return fill_info(embk_syscall2(EMBK_SYS_fstat, fd, (int64_t)(intptr_t)&st), + &st, out); } +/* EMBKFS has no permission bits for a mode to set */ int __os_mkdir(const char *path, unsigned mode) { - char pb[EM_PATH_MAX]; - (void)mode; /* the kernel's mkdir takes no mode */ - int r = embk_mkdir(path_abs(path, pb, sizeof pb)); - return embk_is_err(r) ? (int)fail(r) : 0; + (void)mode; + ABS(path, ap); + return ret0(embk_mkdir(ap)); } int __os_rmdir(const char *path) { - char pb[EM_PATH_MAX]; - int r = embk_rmdir(path_abs(path, pb, sizeof pb)); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(path, ap); + return ret0(embk_rmdir(ap)); } int __os_unlink(const char *path) { - char pb[EM_PATH_MAX]; - long r = (long)embk_syscall1(EMBK_SYS_unlink, - (int64_t)(intptr_t)path_abs(path, pb, - sizeof pb)); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(path, ap); + return ret0(embk_syscall1(EMBK_SYS_unlink, (int64_t)(intptr_t)ap)); } +/* the kernel keeps the type bits and takes the rest */ int __os_chmod(const char *path, unsigned mode) { - char pb[EM_PATH_MAX]; - long r = (long)embk_syscall2(EMBK_SYS_chmod, - (int64_t)(intptr_t)path_abs(path, pb, - sizeof pb), - (int64_t)mode); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(path, ap); + return ret0(embk_syscall2(EMBK_SYS_chmod, (int64_t)(intptr_t)ap, mode)); } -/* The kernel truncates by DESCRIPTOR, so the file is opened for the - * one call. Opening write-only is deliberate: resize_file must not - * need read permission. */ int __os_truncate(const char *path, long long size) { - char pb[EM_PATH_MAX]; - int fd = __os_open(path_abs(path, pb, sizeof pb), 1 /* O_WRONLY */, 0); + int fd = __os_open(path, __OS_O_WRONLY, 0); if (fd < 0) return -1; - long r = (long)embk_syscall2(EMBK_SYS_ftruncate, fd, (int64_t)size); + int r = __os_ftruncate(fd, size); + int e = errno; __os_close(fd); - return embk_is_err(r) ? (int)fail(r) : 0; + errno = e; + return r; } -/* No syscall sets a modification time: the kernel tracks it and does - * not let userland write it. Saying so is better than succeeding and - * changing nothing, which would make last_write_time's setter a silent - * no-op. */ +/* the kernel tracks a modification time but has no call that sets one */ int __os_utime(const char *path, long mtime) { (void)path; (void)mtime; @@ -498,95 +624,144 @@ int __os_utime(const char *path, long mtime) return -1; } +/* The target is a string stored in the link and is not resolved: a + * relative target stays relative, which is what makes a tree of links + * survive being moved. Only the link's own name is made absolute. */ int __os_symlink(const char *target, const char *linkpath) { - char lb[EM_PATH_MAX]; - /* The TARGET is a string stored in the link and is not resolved -- - * a relative target stays relative, which is what makes a symlink - * survive its directory being moved. Only the link's own path is - * made absolute. */ - int r = embk_symlink(target, path_abs(linkpath, lb, sizeof lb)); - return embk_is_err(r) ? (int)fail(r) : 0; + if (!target) { + errno = EINVAL; + return -1; + } + ABS(linkpath, lp); + return ret0(embk_symlink(target, lp)); } +/* The kernel reports the link's FULL length; POSIX's readlink reports + * the bytes placed, so it is clamped to the buffer. */ long __os_readlink(const char *path, char *buf, size_t n) { - char pb[EM_PATH_MAX]; - int64_t r = embk_readlink(path_abs(path, pb, sizeof pb), buf, n); - return embk_is_err(r) ? fail((long)r) : (long)r; + ABS(path, ap); + int64_t r = embk_readlink(ap, buf, n); + if (embk_is_err(r)) + return fail((long)r); + return (size_t)r < n ? (long)r : (long)n; } +/* a hard link names the OBJECT, so both sides resolve */ int __os_link(const char *target, const char *linkpath) { - char tb[EM_PATH_MAX], lb[EM_PATH_MAX]; - /* A hard link is a reference to the OBJECT, so both sides resolve. */ - int r = embk_link(path_abs(target, tb, sizeof tb), - path_abs(linkpath, lb, sizeof lb)); - return embk_is_err(r) ? (int)fail(r) : 0; + ABS(target, tp); + ABS(linkpath, lp); + return ret0(embk_link(tp, lp)); } int __os_getcwd(char *buf, size_t n) { - return getcwd(buf, n) ? 0 : -1; + size_t k = strlen(g_cwd) + 1; + if (k > n) { + errno = ERANGE; + return -1; + } + memcpy(buf, g_cwd, k); + return 0; } -int __os_chdir(const char *path) { return chdir(path); } +/* verified before it moves: a cwd that is not there would turn one bad + * chdir into ENOENTs far from the cause */ +int __os_chdir(const char *path) +{ + ABS(path, ap); + struct __os_fileinfo fi; + if (__os_stat(ap, &fi) < 0) + return -1; + if (fi.type != __OS_FT_DIRECTORY) { + errno = ENOTDIR; + return -1; + } + memcpy(g_cwd, ap, strlen(ap) + 1); + return 0; +} +/* meminfo is memory, not disk: there is no call that says how full a + * filesystem is, and memory's numbers would be plausible and wrong */ int __os_statfs(const char *path, unsigned long long *capacity, unsigned long long *freespace, unsigned long long *available) { - /* meminfo reports MEMORY, not disk, and reporting it as disk space - * would be a plausible number that is simply about something else. - * There is no disk-usage syscall, so this fails. */ (void)path; (void)capacity; (void)freespace; (void)available; errno = ENOSYS; return -1; } -/* One snapshot per open directory, because the kernel's readdir is not - * resumable: it walks the whole directory in one call. The count is - * bounded, and a directory with more entries than this reports the - * first MAXENT -- which is a limitation and is stated rather than - * silently truncating to zero. */ -#define EM_MAXENT 512 - +/* ---- directories --------------------------------------------------------------- + * + * The kernel's readdir is path-based and not resumable -- one call walks + * the whole directory into a buffer, and it TRUNCATES SILENTLY when the + * buffer fills, returning exactly the count it was given room for. So a + * directory is a snapshot taken at opendir, and a full count means the + * snapshot may be short: the buffer is doubled and the directory read + * again until a count comes back with room to spare. */ struct em_dir { - struct embk_dirent ents[EM_MAXENT]; - int n; - int at; + struct embk_dirent *ents; + int n, at; }; void *__os_opendir(const char *path) { - char pb[EM_PATH_MAX]; - struct em_dir *d = malloc(sizeof *d); - if (!d) { - errno = ENOMEM; - return 0; + char ap[EM_PATH_MAX]; + if (path_from(g_cwd, path, ap, sizeof ap) < 0) + return NULL; + struct __os_fileinfo fi; + if (__os_stat(ap, &fi) < 0) + return NULL; + if (fi.type != __OS_FT_DIRECTORY) { + errno = ENOTDIR; + return NULL; } - int64_t r = embk_readdir(path_abs(path, pb, sizeof pb), d->ents, - EM_MAXENT); - if (embk_is_err(r)) { - fail((long)r); - free(d); - return 0; + for (uint32_t cap = 64;; cap *= 2) { + struct embk_dirent *ents = calloc(cap, sizeof *ents); + if (!ents) { + errno = ENOMEM; + return NULL; + } + int64_t n = embk_readdir(ap, ents, cap); + if (embk_is_err(n)) { + free(ents); + fail((long)n); + return NULL; + } + if ((uint32_t)n < cap) { + struct em_dir *d = malloc(sizeof *d); + if (!d) { + free(ents); + errno = ENOMEM; + return NULL; + } + d->ents = ents; + d->n = (int)n; + d->at = 0; + return d; + } + free(ents); + if (cap >= (1u << 20)) { + errno = ENOMEM; + return NULL; + } } - d->n = (int)r; - d->at = 0; - return d; } +static int is_dot(const char *s) +{ + return s[0] == '.' && (!s[1] || (s[1] == '.' && !s[2])); +} + +/* `.` and `..` are filtered here for (os/backend.h says why) */ int __os_readdir(void *dir, char *name, size_t n, int *type) { struct em_dir *d = dir; - if (!d) - return -1; while (d->at < d->n) { struct embk_dirent *e = &d->ents[d->at++]; - /* `.` and `..` are filtered here, once, rather than in every - * caller -- see os/backend.h. */ - if (e->name[0] == '.' && - (e->name[1] == 0 || (e->name[1] == '.' && e->name[2] == 0))) + if (is_dot(e->name)) continue; size_t len = strlen(e->name); if (len + 1 > n) { @@ -601,4 +776,872 @@ int __os_readdir(void *dir, char *name, size_t n, int *type) return 0; } -void __os_closedir(void *dir) { free(dir); } +/* POSIX's readdir has every entry the kernel reports, with its inode */ +int __os_readdir_entry(void *dir, struct __os_dirent *e) +{ + struct em_dir *d = dir; + if (d->at >= d->n) + return 0; + struct embk_dirent *k = &d->ents[d->at++]; + e->ino = k->ino; + e->type = type_of(k->type, 0); + size_t len = strnlen(k->name, sizeof k->name); + memcpy(e->name, k->name, len); + e->name[len] = 0; + return 1; +} + +/* the snapshot again from the start; a directory changed since is seen + * by the next opendir, which POSIX leaves unspecified */ +void __os_rewinddir(void *dir) { ((struct em_dir *)dir)->at = 0; } + +/* a snapshot has no descriptor behind it, and inventing one would let a + * caller fstat the wrong object */ +int __os_dirfd(void *dir) +{ + (void)dir; + errno = ENOTSUP; + return -1; +} + +void __os_closedir(void *dir) +{ + struct em_dir *d = dir; + if (d) { + free(d->ents); + free(d); + } +} + +/* ---- descriptors ------------------------------------------------------------------ + * + * The kernel's fcntl ops: 3 get status (access mode, 4 nonblocking, + * 8 append), 4 get close-on-exec, 5 set it, 6 set status. */ +#define EM_FD_NONBLOCK 4 +#define EM_FD_APPEND 8 + +static int set_cloexec(int fd, int on) +{ + return ret0(embk_syscall3(EMBK_SYS_fcntl, fd, 5, on != 0)); +} + +/* the lowest free descriptor at or above minfd, sharing fd's open file + * description and cursor, as dup promises */ +int __os_dup(int fd, int minfd, int cloexec) +{ + int r = embk_dup(fd, -1, minfd); + if (r < 0) + return (int)fail(r); + if (cloexec) + set_cloexec(r, 1); + return r; +} + +int __os_dup2(int fd, int newfd, int cloexec) +{ + int r = embk_dup(fd, newfd, 0); + if (r < 0) + return (int)fail(r); + if (fd != newfd) + set_cloexec(newfd, cloexec); + return r; +} + +int __os_pipe(int fds[2], int flags) +{ + int r = embk_pipe_fds(fds); + if (r < 0) + return (int)fail(r); + for (int i = 0; i < 2; i++) { + if (flags & __OS_O_CLOEXEC) + set_cloexec(fds[i], 1); + if (flags & __OS_O_NONBLOCK) + embk_syscall3(EMBK_SYS_fcntl, fds[i], 6, EM_FD_NONBLOCK); + } + return 0; +} + +/* Close-on-exec and the status flags are real per-descriptor state. Record + * locks do not exist: F_GETLK, F_SETLK and F_SETLKW are ENOSYS, which a + * program that needs locking can see, where a lock that locked nothing + * would let two writers corrupt a file without either knowing. */ +int __os_fcntl(int fd, int cmd, long arg) +{ + struct embk_stat st; + if (embk_is_err(embk_syscall2(EMBK_SYS_fstat, fd, (int64_t)(intptr_t)&st))) { + errno = EBADF; + return -1; + } + switch (cmd) { + case __OS_F_GETFD: { + int64_t r = embk_syscall3(EMBK_SYS_fcntl, fd, 4, 0); + return embk_is_err(r) ? (int)fail((long)r) : (r ? __OS_FD_CLOEXEC : 0); + } + case __OS_F_SETFD: + if (arg & ~(long)__OS_FD_CLOEXEC) { + errno = EINVAL; + return -1; + } + return set_cloexec(fd, (int)(arg & __OS_FD_CLOEXEC)); + case __OS_F_GETFL: { + int64_t r = embk_syscall3(EMBK_SYS_fcntl, fd, 3, 0); + if (embk_is_err(r)) + return (int)fail((long)r); + return (int)(r & 3) | (r & EM_FD_NONBLOCK ? __OS_O_NONBLOCK : 0) | + (r & EM_FD_APPEND ? __OS_O_APPEND : 0); + } + case __OS_F_SETFL: { + /* The access mode cannot change after open, and an attempt is + * ignored, as Linux ignores it. A flag that cannot be set at all + * here (O_TRUNC, O_SYNC, O_ASYNC ...) is EINVAL -- this OS's + * rule, where Linux drops it -- so a program asking for one + * learns it did not happen. */ + if (arg & ~(long)(3 | __OS_O_NONBLOCK | __OS_O_APPEND)) { /* 3: O_ACCMODE */ + errno = EINVAL; + return -1; + } + int st_bits = (arg & __OS_O_NONBLOCK ? EM_FD_NONBLOCK : 0) | + (arg & __OS_O_APPEND ? EM_FD_APPEND : 0); + return ret0(embk_syscall3(EMBK_SYS_fcntl, fd, 6, st_bits)); + } + } + errno = cmd == __OS_F_GETLK || cmd == __OS_F_SETLK || + cmd == __OS_F_SETLKW ? ENOSYS : EINVAL; + return -1; +} + +/* There is no positional read: seek, read, seek back. Not atomic against + * another thread on the same descriptor, which POSIX's pread is; the + * callers are readers of a file they opened themselves, which is what + * pread is nearly always for. */ +long __os_pread(int fd, void *buf, size_t n, long long off) +{ + long here = __os_lseek(fd, 0, 1); + if (here < 0 || __os_lseek(fd, (long)off, 0) < 0) + return -1; + long r = __os_read(fd, buf, n); + int e = errno; + __os_lseek(fd, here, 0); + errno = e; + return r; +} + +long __os_pwrite(int fd, const void *buf, size_t n, long long off) +{ + long here = __os_lseek(fd, 0, 1); + if (here < 0 || __os_lseek(fd, (long)off, 0) < 0) + return -1; + long r = __os_write(fd, buf, n); + int e = errno; + __os_lseek(fd, here, 0); + errno = e; + return r; +} + +/* One write of the buffers joined, so a pipe or socket sees them as one + * -- the atomicity writev promises a pipe for what fits in its buffer. + * Past what a copy is worth, one write each. */ +long __os_writev(int fd, const struct __os_iovec *iov, int n) +{ + size_t total = 0; + for (int i = 0; i < n; i++) + total += iov[i].len; + if (total <= 4096) { + unsigned char tmp[4096], *p = tmp; + for (int i = 0; i < n; i++) { + memcpy(p, iov[i].base, iov[i].len); + p += iov[i].len; + } + return __os_write(fd, tmp, total); + } + long done = 0; + for (int i = 0; i < n; i++) { + long r = __os_write(fd, iov[i].base, iov[i].len); + if (r < 0) + return done ? done : -1; + done += r; + if ((size_t)r < iov[i].len) + break; + } + return done; +} + +long __os_readv(int fd, const struct __os_iovec *iov, int n) +{ + long done = 0; + for (int i = 0; i < n; i++) { + long r = __os_read(fd, iov[i].base, iov[i].len); + if (r < 0) + return done ? done : -1; + done += r; + if ((size_t)r < iov[i].len) + break; + } + return done; +} + +/* A real flush: write() returns once the bytes are in the page cache, and + * this does not return until the file's dirty pages are on the device. + * EMBKFS commits data and inode in one copy-on-write transaction, so + * fdatasync has no cheaper flush to be. */ +int __os_fsync(int fd, int data_only) +{ + (void)data_only; + return ret0(embk_fsync(fd)); +} + +int __os_ftruncate(int fd, long long size) +{ + if (size < 0) { + errno = EINVAL; + return -1; + } + return ret0(embk_syscall2(EMBK_SYS_ftruncate, fd, (int64_t)size)); +} + +/* ---- the filesystem, the rest -------------------------------------------------- */ + +/* With no ids and no permission bits, nothing can deny access to what + * exists: the answer is whether it exists. If permissions ever land, + * this must start consulting them. */ +int __os_access(const char *path, int mode) +{ + (void)mode; + struct __os_fileinfo fi; + return __os_stat(path, &fi); +} + +/* nothing for a mask to filter: the mask is 0 and stays so */ +unsigned __os_umask(unsigned mask) +{ + (void)mask; + return 0; +} + +/* ---- processes ------------------------------------------------------------------ */ + +/* One user, with the whole machine's authority. The kernel tracks a + * parent but no call names it, and authority over a child is a handle, + * not pid arithmetic: 0 is "not visible". */ +long __os_getid(int which) +{ + return which == __OS_ID_PID ? (long)embk_getpid() : 0; +} + +/* becoming the only user there is: vacuous; anyone else: there is no one */ +int __os_setid(int which, unsigned id) +{ + (void)which; + if (id == 0) + return 0; + errno = EPERM; + return -1; +} + +int __os_getgroups(int n, unsigned *list) +{ + (void)n; (void)list; + return 0; +} + +/* posix_spawn on the kernel's explicit actions: a snapshot of the + * caller's descriptors, its working directory, and a request that the + * child's status be kept until it is waited for -- then the caller's own + * actions in order, their paths made absolute against the directory the + * actions so far have reached. Attributes are refused: there are no + * groups, identities or signals for them to set, and accepting one would + * be a promise nothing keeps. */ +#define EM_ACTIONS (EMBK_SPAWN_ACTIONS_MAX - 3) + +/* What the kernel's spawn cannot take, refused as each action is added: + * more than EM_ACTIONS (three of its 64 slots are this library's), a path + * longer than it copies, a directory by descriptor (there is no way from + * one to its path), and an open flag the action has no bit for. */ +int __os_spawn_check(const struct __os_spawn_action *a, int count) +{ + if (count >= EM_ACTIONS) + return E2BIG; + if (a->kind == __OS_SPAWN_FCHDIR) + return ENOTSUP; + if (a->path && strlen(a->path) >= EM_PATH_MAX) + return ENAMETOOLONG; + if (a->kind == __OS_SPAWN_OPEN && + (a->oflag & ~(3 | __OS_O_CREAT | __OS_O_EXCL | __OS_O_TRUNC | + __OS_O_APPEND | __OS_O_NONBLOCK | __OS_O_CLOEXEC))) + return ENOTSUP; + return 0; +} + +int __os_spawn(int *pid, const char *path, const struct __os_spawn_action *a, + int na, const struct __os_spawn_attr *attr, + char *const argv[], char *const envp[]) +{ + if (attr && attr->flags) { + errno = ENOTSUP; + return -1; + } + if (na > EM_ACTIONS) { + errno = E2BIG; + return -1; + } + if (!argv) { + errno = EINVAL; + return -1; + } + int argc = 0; + while (argv[argc]) + if (++argc >= 128) { + errno = E2BIG; + return -1; + } + struct embk_spawn_file_action *k = calloc(EMBK_SPAWN_ACTIONS_MAX, sizeof *k); + if (!k) { + errno = ENOMEM; + return -1; + } + char cwd[EM_PATH_MAX], exe[EM_PATH_MAX]; + memcpy(cwd, g_cwd, sizeof cwd); + k[0].kind = EMBK_SPAWN_ACTION_FD_SNAPSHOT; + k[1].kind = EMBK_SPAWN_ACTION_CWD; + memcpy(k[1].path, cwd, sizeof k[1].path); + k[2].kind = EMBK_SPAWN_ACTION_KEEP_STATUS; + int n = 3, err = 0; + for (int i = 0; i < na && !err; i++, n++) { + const struct __os_spawn_action *x = &a[i]; + struct embk_spawn_file_action *y = &k[n]; + if (x->fd < 0 || x->fd >= 64 || + (x->kind == __OS_SPAWN_DUP2 && (x->newfd < 0 || x->newfd >= 64))) { + err = EBADF; + break; + } + switch (x->kind) { + case __OS_SPAWN_OPEN: + y->kind = EMBK_SPAWN_ACTION_OPEN; + y->target_fd = x->fd; + y->flags = open_flags(x->oflag); + y->mode = x->mode; + if (path_from(cwd, x->path, y->path, sizeof y->path) < 0) + err = errno; + break; + case __OS_SPAWN_CLOSE: + y->kind = EMBK_SPAWN_ACTION_CLOSE; + y->target_fd = x->fd; + break; + case __OS_SPAWN_DUP2: + y->kind = EMBK_SPAWN_ACTION_DUP2; + y->target_fd = x->newfd; + y->src_obj_handle = x->fd; + break; + case __OS_SPAWN_CHDIR: + y->kind = EMBK_SPAWN_ACTION_CWD; + if (path_from(cwd, x->path, y->path, sizeof y->path) < 0) + err = errno; + else + memcpy(cwd, y->path, sizeof cwd); + break; + default: /* FCHDIR: a descriptor has no path here */ + err = ENOTSUP; + } + } + if (!err && path_from(cwd, path, exe, sizeof exe) < 0) + err = errno; + if (err) { + free(k); + errno = err; + return -1; + } + int64_t h = embk_syscall6(EMBK_SYS_spawn_pid, (int64_t)(uintptr_t)exe, + (int64_t)(uintptr_t)argv, argc, + (int64_t)(uintptr_t)k, n, + (int64_t)(uintptr_t)envp); + free(k); + if (embk_is_err(h)) + return (int)fail((long)h); + *pid = (int)h; + return 0; +} + +/* The children this process owns, by pid or any (-1): the status as + * POSIX encodes it, which the kernel copies out itself. A process group + * (0, or below -1) names nothing here. A cancelled wait is EINTR. */ +#define EM_ECANCELED 125 + +int __os_waitpid(int pid, int *status, int options) +{ + if (options & ~1) { /* only WNOHANG means anything */ + errno = EINVAL; + return -1; + } + if (pid == 0 || pid < -1) { + errno = ENOSYS; + return -1; + } + int64_t r = embk_syscall5(EMBK_SYS_child, pid, EMBK_CHILD_WAIT_STATUS, + (int64_t)(intptr_t)status, + status ? sizeof *status : 0, + options & 1 ? EMBK_CHILD_NOHANG : 0); + if (embk_is_err(r)) { + errno = r == -EM_ECANCELED ? EINTR : (int)-r; + return -1; + } + return (int)r; +} + +/* ---- memory ------------------------------------------------------------------------ + * + * What the kernel cannot honour is refused at the call, not approximated: + * MAP_FIXED, or an address asked for ENOTSUP -- the kernel picks it + * an anonymous MAP_SHARED ENOTSUP -- shared with nobody, + * there being no fork to share with + * an offset not page-aligned EINVAL + * PROT_WRITE with PROT_EXEC EINVAL, from the kernel: write, + * then mprotect to executable */ +void *__os_mmap(void *addr, size_t len, int prot, int flags, int fd, + long long off) +{ + if ((flags & 0x10) || addr) { + errno = ENOTSUP; + return (void *)-1; + } + if (!len || off < 0 || (off & 0xfff)) { + errno = EINVAL; + return (void *)-1; + } + if (fd < 0 || (flags & 0x20)) { + if (off) { + errno = EINVAL; + return (void *)-1; + } + if (flags & 0x01) { + errno = ENOTSUP; + return (void *)-1; + } + fd = -1; + } else if (!(flags & 0x03)) { + errno = EINVAL; /* a file mapping must say which */ + return (void *)-1; + } + int64_t r = embk_mmap_fd(len, prot, flags, fd, (uint64_t)off); + if (embk_is_err(r)) { + fail((long)r); + return (void *)-1; + } + return (void *)(intptr_t)r; +} + +int __os_munmap(void *addr, size_t len) { return ret0(embk_munmap(addr, len)); } + +int __os_mprotect(void *addr, size_t len, int prot) +{ + return ret0(embk_mprotect(addr, len, prot)); +} + +/* ---- the system ---------------------------------------------------------------------- */ + +/* what this OS is, nothing guessed: one machine, no network name */ +int __os_uname(struct __os_utsname *u) +{ + memset(u, 0, sizeof *u); + strcpy(u->sysname, "EmbLink"); + strcpy(u->nodename, "emblink"); + strcpy(u->release, "1.0"); + strcpy(u->version, "EmbLinkOS"); +#if defined(__aarch64__) + strcpy(u->machine, "aarch64"); +#else + strcpy(u->machine, "x86_64"); +#endif + return 0; +} + +/* The page size is the VMM's. No call reports the processor count or the + * memory size in pages, and a plausible guess would be a fabricated answer + * a caller could not tell from a measured one. */ +long __os_sysinfo(int what) +{ + if (what == __OS_INFO_PAGESIZE) + return 4096; + errno = EINVAL; + return -1; +} + +/* ---- waiting on descriptors ------------------------------------------------------- + * + * The kernel answers "is this descriptor ready" one descriptor at a time + * (fd_poll), with no way to sleep on several. So the set is asked, and if + * nothing is ready the thread sleeps 10 ms -- which is also what lets the + * network's receive thread move a connecting socket on -- and asks again, + * until the timeout. Not a scalable event loop; exactly what a blocking + * call with a timeout needs. */ +int __os_poll(struct __os_pollfd *fds, unsigned long n, + const struct __os_timespec *timeout, + const unsigned long long *sigmask) +{ + (void)sigmask; /* no signals to mask */ + long long left = timeout ? timeout->sec * 1000 + timeout->nsec / 1000000 + : -1; + for (;;) { + int ready = 0; + for (unsigned long i = 0; i < n; i++) { + fds[i].revents = 0; + if (fds[i].fd < 0) + continue; + int want = (fds[i].events & (EMBK_POLLIN | EMBK_POLLOUT)) | + EMBK_POLLERR; + int re = embk_fd_poll(fds[i].fd, want); + if (re < 0) + fds[i].revents = 0x20; /* POLLNVAL */ + else + fds[i].revents = (short)(re & (fds[i].events | EMBK_POLLERR | + EMBK_POLLHUP)); + if (fds[i].revents) + ready++; + } + if (ready || left == 0) + return ready; + long step = left > 0 && left < 10 ? (long)left : 10; + embk_sleep_ms((uint64_t)step); + if (left > 0) + left -= step; + } +} + +/* ---- sockets ------------------------------------------------------------------------ + * + * IPv4, over the kernel's own network layer: a socket is a real + * descriptor, so read, write and close need nothing here. Addresses are + * network order in a sockaddr_in and host order to the kernel. */ +#define EM_AF_INET 2 +#define EM_SOCK_STREAM 1 +#define EM_SOCK_DGRAM 2 + +struct em_sin { + unsigned short family, port; + unsigned int addr; + unsigned char zero[8]; +}; + +static unsigned int be32(unsigned int v) { return __builtin_bswap32(v); } +static unsigned short be16(unsigned short v) { return __builtin_bswap16(v); } + +static void sin_fill(void *addr, unsigned *len, unsigned ip, unsigned port) +{ + if (!addr || !len || *len < sizeof(struct em_sin)) + return; + struct em_sin s = { EM_AF_INET, be16((unsigned short)port), be32(ip), { 0 } }; + memcpy(addr, &s, sizeof s); + *len = sizeof s; +} + +static void fd_flags(int fd, int type) +{ + if (type & 0x800) /* SOCK_NONBLOCK */ + embk_syscall3(EMBK_SYS_fcntl, fd, 6, EM_FD_NONBLOCK); + if (type & 0x80000) /* SOCK_CLOEXEC */ + set_cloexec(fd, 1); +} + +int __os_socket(int domain, int type, int protocol) +{ + (void)protocol; + if (domain != EM_AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + int base = type & ~(0x800 | 0x80000); + if (base != EM_SOCK_STREAM && base != EM_SOCK_DGRAM) { + errno = EPROTONOSUPPORT; + return -1; + } + int fd = embk_net_socket(base); + if (fd < 0) + return (int)fail(fd); + fd_flags(fd, type); + return fd; +} + +int __os_connect(int fd, const void *addr, unsigned len) +{ + struct em_sin s; + if (!addr || len < sizeof s) { + errno = EINVAL; + return -1; + } + memcpy(&s, addr, sizeof s); + if (s.family != EM_AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + return ret0(embk_net_connect(fd, be32(s.addr), be16(s.port))); +} + +/* the kernel binds a port; the address is every interface there is */ +int __os_bind(int fd, const void *addr, unsigned len) +{ + struct em_sin s; + if (!addr || len < sizeof s) { + errno = EINVAL; + return -1; + } + memcpy(&s, addr, sizeof s); + return ret0(embk_net_bind(fd, be16(s.port))); +} + +int __os_listen(int fd, int backlog) { return ret0(embk_net_listen(fd, backlog)); } + +/* the peer's address is not tracked: AF_INET with zeros */ +int __os_accept(int fd, void *addr, unsigned *len, int flags) +{ + int nfd = embk_net_accept(fd); + if (nfd < 0) + return (int)fail(nfd); + sin_fill(addr, len, 0, 0); + fd_flags(nfd, flags); + return nfd; +} + +long __os_sendto(int fd, const void *buf, size_t n, int flags, + const void *addr, unsigned alen) +{ + (void)flags; + if (!addr) + return __os_write(fd, buf, n); + struct em_sin s; + if (alen < sizeof s) { + errno = EINVAL; + return -1; + } + memcpy(&s, addr, sizeof s); + int r = embk_net_sendto(fd, be32(s.addr), be16(s.port), buf, n); + return r < 0 ? fail(r) : (long)n; +} + +long __os_recvfrom(int fd, void *buf, size_t n, int flags, void *addr, + unsigned *alen) +{ + (void)flags; + if (!addr) + return __os_read(fd, buf, n); + unsigned int ip = 0; + unsigned short port = 0; + int r = embk_net_recvfrom(fd, buf, n, &ip, &port); + if (r < 0) + return fail(r); + sin_fill(addr, alen, ip, port); + return r; +} + +/* No half-close in the kernel: the connection ends at close. Succeeding + * lets the libraries that shut down before closing go on to close. */ +int __os_shutdown(int fd, int how) +{ + (void)fd; (void)how; + return 0; +} + +/* The options are advisory here (SO_REUSEADDR, TCP_NODELAY, ...) and + * accepted. SO_ERROR is how a caller reaps a non-blocking connect: an + * error on the descriptor reads as ECONNREFUSED, else none. */ +int __os_setsockopt(int fd, int level, int name, const void *val, + unsigned len) +{ + (void)fd; (void)level; (void)name; (void)val; (void)len; + return 0; +} + +int __os_getsockopt(int fd, int level, int name, void *val, unsigned *len) +{ + (void)level; + if (val && len && *len >= sizeof(int)) { + int v = 0; + if (name == 4 && (embk_fd_poll(fd, EMBK_POLLERR) & EMBK_POLLERR)) + v = ECONNREFUSED; /* SO_ERROR */ + memcpy(val, &v, sizeof v); + *len = sizeof v; + } + return 0; +} + +int __os_getsockname(int fd, void *addr, unsigned *len) +{ + (void)fd; + sin_fill(addr, len, 0, 0); + return 0; +} + +int __os_getpeername(int fd, void *addr, unsigned *len) +{ + return __os_getsockname(fd, addr, len); +} + +/* The kernel's resolver, IPv4: one address, or none for a name it cannot + * find. Asked for IPv6, it has none to give. */ +int __os_resolve(const char *name, int family, unsigned char *addrs, int max) +{ + if (family != EM_AF_INET || max < 1) + return 0; + unsigned int ip; + if (embk_net_resolve(name, &ip) != 0) + return 0; + unsigned int be = be32(ip); + memcpy(addrs, &be, 4); + return 1; +} + +/* ---- terminals ------------------------------------------------------------------------- + * + * The kernel's terminal attributes are Linux's flag bits and control + * character indices, the first EMBK_TCC_COUNT of them. A mode asking for + * one past those cannot be set and is refused, as is TCSADRAIN, which + * the kernel has no way to wait for. */ +int __os_tcgetattr(int fd, struct __os_termios *t) +{ + struct embk_terminal_attr a; + int r = embk_terminal_control(fd, EMBK_TERMINAL_GET_ATTR, &a, sizeof a); + if (r < 0) + return (int)fail(r); + memset(t, 0, sizeof *t); + t->iflag = a.iflag; + t->oflag = a.oflag; + t->cflag = a.cflag; + t->lflag = a.lflag; + memcpy(t->cc, a.cc, sizeof a.cc); + t->ispeed = t->ospeed = a.cflag & 0010017; + return 0; +} + +int __os_tcsetattr(int fd, int action, const struct __os_termios *t) +{ + if (action != 0 && action != 2) { + errno = action == 1 ? ENOTSUP : EINVAL; + return -1; + } + for (unsigned i = EMBK_TCC_COUNT; i < sizeof t->cc; i++) + if (t->cc[i]) { + errno = ENOTSUP; + return -1; + } + struct embk_terminal_attr a; + memset(&a, 0, sizeof a); + a.iflag = t->iflag; + a.oflag = t->oflag; + a.cflag = t->cflag; + a.lflag = t->lflag; + memcpy(a.cc, t->cc, sizeof a.cc); + int r = embk_terminal_control(fd, action == 2 ? EMBK_TERMINAL_SET_ATTR_FLUSH + : EMBK_TERMINAL_SET_ATTR, + &a, sizeof a); + return r < 0 ? (int)fail(r) : 0; +} + +/* The requests a program asks of a terminal: its size, its modes, and + * whether a descriptor blocks. Any other is not one this device knows. */ +#define L_TCGETS 0x5401 +#define L_TCSETS 0x5402 +#define L_TCSETSF 0x5404 +#define L_TIOCGWINSZ 0x5413 +#define L_TIOCSWINSZ 0x5414 +#define L_FIONBIO 0x5421 + +int __os_ioctl(int fd, unsigned long request, void *arg) +{ + switch (request) { + case L_TIOCGWINSZ: + case L_TIOCSWINSZ: { + unsigned short *w = arg; + struct embk_terminal_size s; + if (!w) { + errno = EFAULT; + return -1; + } + if (request == L_TIOCSWINSZ) { + s.rows = w[0]; + s.cols = w[1]; + s.xpixel = w[2]; + s.ypixel = w[3]; + } + int r = embk_terminal_control(fd, request == L_TIOCGWINSZ + ? EMBK_TERMINAL_GET_SIZE + : EMBK_TERMINAL_SET_SIZE, + &s, sizeof s); + if (r < 0) + return (int)fail(r); + if (request == L_TIOCGWINSZ) { + w[0] = s.rows; + w[1] = s.cols; + w[2] = s.xpixel; + w[3] = s.ypixel; + } + return 0; + } + case L_TCGETS: + return __os_tcgetattr(fd, arg); + case L_TCSETS: + case L_TCSETSF: + return __os_tcsetattr(fd, request == L_TCSETS ? 0 : 2, arg); + case L_FIONBIO: { + int on = arg ? *(int *)arg : 0; + int64_t fl = embk_syscall3(EMBK_SYS_fcntl, fd, 3, 0); + if (embk_is_err(fl)) + return (int)fail((long)fl); + int bits = (int)fl & (EM_FD_NONBLOCK | EM_FD_APPEND); + bits = on ? bits | EM_FD_NONBLOCK : bits & ~EM_FD_NONBLOCK; + return ret0(embk_syscall3(EMBK_SYS_fcntl, fd, 6, bits)); + } + } + errno = ENOTTY; + return -1; +} + +/* ---- the rest of POSIX's operating system: what this OS has not got ---------- */ + +#define OS_HAS_fstat +#define OS_HAS_dup +#define OS_HAS_dup2 +#define OS_HAS_pipe +#define OS_HAS_fcntl +#define OS_HAS_pread +#define OS_HAS_pwrite +#define OS_HAS_readv +#define OS_HAS_writev +#define OS_HAS_fsync +#define OS_HAS_ftruncate +#define OS_HAS_ioctl +#define OS_HAS_access +#define OS_HAS_umask +#define OS_HAS_readdir_entry +#define OS_HAS_rewinddir +#define OS_HAS_dirfd +#define OS_HAS_getid +#define OS_HAS_setid +#define OS_HAS_getgroups +#define OS_HAS_spawn +#define OS_HAS_spawn_check +#define OS_HAS_waitpid +#define OS_HAS_clock_gettime +#define OS_HAS_clock_getres +#define OS_HAS_clock_nanosleep +#define OS_HAS_times +#define OS_HAS_mmap +#define OS_HAS_munmap +#define OS_HAS_mprotect +#define OS_HAS_uname +#define OS_HAS_sysinfo +#define OS_HAS_getrlimit +#define OS_HAS_poll +#define OS_HAS_socket +#define OS_HAS_bind +#define OS_HAS_connect +#define OS_HAS_listen +#define OS_HAS_accept +#define OS_HAS_sendto +#define OS_HAS_recvfrom +#define OS_HAS_shutdown +#define OS_HAS_getsockopt +#define OS_HAS_setsockopt +#define OS_HAS_getsockname +#define OS_HAS_getpeername +#define OS_HAS_resolve +#define OS_HAS_tcgetattr +#define OS_HAS_tcsetattr +#include "../absent.inc" diff --git a/lib/libc/os/linux/backend.c b/lib/libc/os/linux/backend.c index b613cab47..cfc13afdc 100644 --- a/lib/libc/os/linux/backend.c +++ b/lib/libc/os/linux/backend.c @@ -82,9 +82,14 @@ long __os_read(int fd, void *buf, size_t n) * test asserts that against rather than leaving it as a * comment. */ +/* The seam's open flags as this kernel numbers them (syscall.h says why + * O_DIRECTORY and O_NOFOLLOW move on aarch64). */ +int __linux_oflags(int seam); + int __os_open(const char *path, int flags, int mode) { - return (int)ret(lsys4(LSYS_openat, LAT_FDCWD, (long)path, flags, mode)); + return (int)ret(lsys4(LSYS_openat, LAT_FDCWD, (long)path, + __linux_oflags(flags), mode)); } int __os_close(int fd) @@ -328,11 +333,23 @@ static unsigned long long makedev(unsigned maj, unsigned min) | (unsigned long long)(min & 0x000000ffu); } +/* statx of (dirfd, path, flags) into the seam's structure: shared with + * __os_fstat in posix.c, so stat and fstat cannot report one file two + * ways. */ +int __linux_statx(int dirfd, const char *path, int flags, + struct __os_fileinfo *out); + static int do_stat(const char *path, struct __os_fileinfo *out, int flags) +{ + return __linux_statx(LAT_FDCWD, path, flags, out); +} + +int __linux_statx(int dirfd, const char *path, int flags, + struct __os_fileinfo *out) { struct lstatx sx; memset(&sx, 0, sizeof sx); - long r = lsys5(LSYS_statx, LAT_FDCWD, (long)path, + long r = lsys5(LSYS_statx, dirfd, (long)path, flags | LAT_STATX_SYNC_AS_STAT, LSTATX_BASIC_STATS, (long)&sx); if (r < 0) { @@ -348,8 +365,19 @@ static int do_stat(const char *path, struct __os_fileinfo *out, int flags) } return (int)fail(r); } + memset(out, 0, sizeof *out); out->size = sx.stx_size; out->mtime = (long)sx.stx_mtime.tv_sec; + out->mtime_nsec = (long)sx.stx_mtime.tv_nsec; + out->atime = sx.stx_atime.tv_sec; + out->atime_nsec = (long)sx.stx_atime.tv_nsec; + out->ctime = sx.stx_ctime.tv_sec; + out->ctime_nsec = (long)sx.stx_ctime.tv_nsec; + out->uid = sx.stx_uid; + out->gid = sx.stx_gid; + out->rdev = makedev(sx.stx_rdev_major, sx.stx_rdev_minor); + out->blocks = (long long)sx.stx_blocks; + out->blksize = (long)sx.stx_blksize; out->mode = sx.stx_mode & 07777u; out->type = type_of_mode(sx.stx_mode); out->dev = makedev(sx.stx_dev_major, sx.stx_dev_minor); diff --git a/lib/libc/os/linux/posix.c b/lib/libc/os/linux/posix.c new file mode 100644 index 000000000..4abf6c853 --- /dev/null +++ b/lib/libc/os/linux/posix.c @@ -0,0 +1,1148 @@ +/* The Linux backend's half of POSIX's operating system: the primitives + * os/backend.h lists after "POSIX's operating system", each a syscall or + * a few. + * + * The seam's numbers are Linux's by design -- its flags, commands, signal + * numbers, socket layouts and ioctl requests -- so most of this file is + * argument passing. What is not is said where it happens: the open flags + * aarch64 numbers differently, the structures whose kernel layout is not + * the C library's (termios, utsname, rusage), the signal trampoline, and + * posix_spawn, which Linux has no syscall for. + */ +#include "../backend.h" +#include "syscall.h" + +#include +#include + +static long fail(long r) +{ + errno = (int)-r; + return -1; +} + +static long ret(long r) { return r < 0 ? fail(r) : r; } +static int ret0(long r) { return r < 0 ? (int)fail(r) : 0; } + +int __linux_statx(int dirfd, const char *path, int flags, + struct __os_fileinfo *out); + +/* ---- open's flags --------------------------------------------------------- + * + * The seam numbers O_DIRECTORY and O_NOFOLLOW as x86 does. aarch64's + * table, like every later port's, moved them -- and gave 0200000, x86's + * O_DIRECTORY, to O_DIRECT. So there they are translated both ways, and + * every other flag passes through. */ +#define X86_O_DIRECTORY 0200000 +#define X86_O_NOFOLLOW 0400000 +#define X86_O_DIRECT 0040000 +#define A64_O_DIRECTORY 0040000 +#define A64_O_NOFOLLOW 0100000 +#define A64_O_DIRECT 0200000 + +int __linux_oflags(int f) +{ +#if defined(__aarch64__) + int out = f & ~(X86_O_DIRECTORY | X86_O_NOFOLLOW | X86_O_DIRECT); + if (f & X86_O_DIRECTORY) out |= A64_O_DIRECTORY; + if (f & X86_O_NOFOLLOW) out |= A64_O_NOFOLLOW; + if (f & X86_O_DIRECT) out |= A64_O_DIRECT; + return out; +#else + return f; +#endif +} + +static int seam_oflags(int f) +{ +#if defined(__aarch64__) + int out = f & ~(A64_O_DIRECTORY | A64_O_NOFOLLOW | A64_O_DIRECT); + if (f & A64_O_DIRECTORY) out |= X86_O_DIRECTORY; + if (f & A64_O_NOFOLLOW) out |= X86_O_NOFOLLOW; + if (f & A64_O_DIRECT) out |= X86_O_DIRECT; + return out; +#else + return f; +#endif +} + +/* ---- descriptors ---------------------------------------------------------- */ + +int __os_dup(int fd, int minfd, int cloexec) +{ + return (int)ret(lsys3(LSYS_fcntl, fd, + cloexec ? __OS_F_DUPFD_CLOEXEC : __OS_F_DUPFD, + minfd)); +} + +/* dup3 refuses fd == newfd; dup2 succeeds when fd is open, so that case + * asks whether it is and changes nothing */ +int __os_dup2(int fd, int newfd, int cloexec) +{ + if (fd == newfd) { + long r = lsys3(LSYS_fcntl, fd, __OS_F_GETFD, 0); + return r < 0 ? (int)fail(r) : newfd; + } + return (int)ret(lsys3(LSYS_dup3, fd, newfd, + cloexec ? __OS_O_CLOEXEC : 0)); +} + +int __os_pipe(int fds[2], int flags) +{ + return ret0(lsys2(LSYS_pipe2, (long)fds, flags)); +} + +int __os_fcntl(int fd, int cmd, long arg) +{ + if (cmd == __OS_F_SETFL) + arg = __linux_oflags((int)arg); + long r = lsys3(LSYS_fcntl, fd, cmd, arg); + if (r >= 0 && cmd == __OS_F_GETFL) + r = seam_oflags((int)r); + return (int)ret(r); +} + +/* statx of the descriptor itself: an empty path and AT_EMPTY_PATH */ +int __os_fstat(int fd, struct __os_fileinfo *out) +{ + return __linux_statx(fd, "", LAT_EMPTY_PATH, out); +} + +long __os_pread(int fd, void *buf, size_t n, long long off) +{ + return ret(lsys4(LSYS_pread64, fd, (long)buf, (long)n, (long)off)); +} + +long __os_pwrite(int fd, const void *buf, size_t n, long long off) +{ + return ret(lsys4(LSYS_pwrite64, fd, (long)buf, (long)n, (long)off)); +} + +/* struct __os_iovec is the kernel's struct iovec: a pointer and a size */ +long __os_readv(int fd, const struct __os_iovec *iov, int n) +{ + return ret(lsys3(LSYS_readv, fd, (long)iov, n)); +} + +long __os_writev(int fd, const struct __os_iovec *iov, int n) +{ + return ret(lsys3(LSYS_writev, fd, (long)iov, n)); +} + +int __os_fsync(int fd, int data_only) +{ + return ret0(lsys1(data_only ? LSYS_fdatasync : LSYS_fsync, fd)); +} + +int __os_ftruncate(int fd, long long size) +{ + return ret0(lsys2(LSYS_ftruncate, fd, (long)size)); +} + +int __os_fchmod(int fd, unsigned mode) +{ + return ret0(lsys2(LSYS_fchmod, fd, mode)); +} + +int __os_fchdir(int fd) { return ret0(lsys1(LSYS_fchdir, fd)); } + +int __os_ioctl(int fd, unsigned long request, void *arg) +{ + return (int)ret(lsys3(LSYS_ioctl, fd, (long)request, (long)arg)); +} + +int __os_flock_file(int fd, int op) { return ret0(lsys2(LSYS_flock, fd, op)); } + +static void fd_link(int fd, char out[32]) +{ + char num[12]; + int k = 0; + unsigned u = (unsigned)fd; + memcpy(out, "/proc/self/fd/", 15); + do + num[k++] = (char)('0' + u % 10); + while (u /= 10); + size_t at = 14; + while (k) + out[at++] = num[--k]; + out[at] = 0; +} + +/* /proc/self/fd/N is a link to what N is open on. A directory removed + * while open reads as "path (deleted)", which no path resolves -- the + * *at call then fails with ENOENT, as it would have anyway. */ +int __os_fdpath(int fd, char *buf, size_t n) +{ + char link[32]; + fd_link(fd, link); + long r = lsys4(LSYS_readlinkat, LAT_FDCWD, (long)link, (long)buf, (long)n); + if (r < 0) + return (int)fail(r == -ENOENT ? -EBADF : r); + if ((size_t)r >= n) { + errno = ENAMETOOLONG; + return -1; + } + buf[r] = 0; + return 0; +} + +/* ---- the filesystem, the rest -------------------------------------------- */ + +/* The *at calls are the kernel's own; the plain forms in backend.c are + * these with AT_FDCWD. AT_* flag values are the uapi's, the same on + * both architectures. */ +int __os_openat(int dirfd, const char *path, int flags, int mode) +{ + return (int)ret(lsys4(LSYS_openat, dirfd, (long)path, + __linux_oflags(flags), mode)); +} + +int __os_fstatat(int dirfd, const char *path, struct __os_fileinfo *out, + int flags) +{ + return __linux_statx(dirfd, path, flags & (LAT_SYMLINK_NOFOLLOW | + LAT_EMPTY_PATH), out); +} + +int __os_mkdirat(int dirfd, const char *path, unsigned mode) +{ + return ret0(lsys3(LSYS_mkdirat, dirfd, (long)path, mode)); +} + +int __os_unlinkat(int dirfd, const char *path, int flags) +{ + return ret0(lsys3(LSYS_unlinkat, dirfd, (long)path, flags)); +} + +int __os_renameat(int fromfd, const char *from, int tofd, const char *to) +{ + return ret0(lsys4(LSYS_renameat, fromfd, (long)from, tofd, (long)to)); +} + +int __os_linkat(int fromfd, const char *from, int tofd, const char *to, + int flags) +{ + return ret0(lsys5(LSYS_linkat, fromfd, (long)from, tofd, (long)to, + flags)); +} + +int __os_symlinkat(const char *target, int dirfd, const char *path) +{ + return ret0(lsys3(LSYS_symlinkat, (long)target, dirfd, (long)path)); +} + +long __os_readlinkat(int dirfd, const char *path, char *buf, size_t n) +{ + return ret(lsys4(LSYS_readlinkat, dirfd, (long)path, (long)buf, + (long)n)); +} + +/* the kernel's fchmodat has no flags; a nofollow request is refused, as + * glibc refuses it, since a link's own mode means nothing on Linux */ +int __os_fchmodat(int dirfd, const char *path, unsigned mode, int flags) +{ + if (flags & LAT_SYMLINK_NOFOLLOW) { + errno = EOPNOTSUPP; + return -1; + } + return ret0(lsys4(LSYS_fchmodat, dirfd, (long)path, mode, 0)); +} + +int __os_faccessat(int dirfd, const char *path, int mode, int flags) +{ + (void)flags; /* AT_EACCESS: the effective ids, which a + * process that never changed them shares */ + return ret0(lsys4(LSYS_faccessat, dirfd, (long)path, mode, 0)); +} + +int __os_mknodat(int dirfd, const char *path, unsigned mode, + unsigned long long dev) +{ + return ret0(lsys4(LSYS_mknodat, dirfd, (long)path, mode, (long)dev)); +} + +int __os_access(const char *path, int mode) +{ + return ret0(lsys4(LSYS_faccessat, LAT_FDCWD, (long)path, mode, 0)); +} + +unsigned __os_umask(unsigned mask) +{ + return (unsigned)lsys1(LSYS_umask, mask & 0777); +} + +int __os_chown(const char *path, int fd, unsigned uid, unsigned gid, + int nofollow) +{ + if (!path) + return ret0(lsys5(LSYS_fchownat, fd, (long)"", uid, gid, + LAT_EMPTY_PATH)); + return ret0(lsys5(LSYS_fchownat, fd, (long)path, uid, gid, + nofollow ? LAT_SYMLINK_NOFOLLOW : 0)); +} + +int __os_utimens(const char *path, int fd, + const struct __os_timespec times[2], int nofollow) +{ + struct ltimespec k[2], *kp = 0; + if (times) { + for (int i = 0; i < 2; i++) { + k[i].tv_sec = (long)times[i].sec; + k[i].tv_nsec = times[i].nsec; + } + kp = k; + } + /* a NULL path with a descriptor is futimens, which utimensat is */ + return ret0(lsys4(LSYS_utimensat, fd, (long)path, + (long)kp, nofollow ? LAT_SYMLINK_NOFOLLOW : 0)); +} + +int __os_mknod(const char *path, unsigned mode, unsigned long long dev) +{ + return ret0(lsys4(LSYS_mknodat, LAT_FDCWD, (long)path, mode, (long)dev)); +} + +int __os_chroot(const char *path) { return ret0(lsys1(LSYS_chroot, (long)path)); } + +void __os_sync(void) { lsys0(LSYS_sync); } + +#define LST_VALID 0x0020 /* statfs's "f_flags means something" */ + +int __os_statvfs(const char *path, int fd, struct __os_fsinfo *out) +{ + struct lstatfs sf; + memset(&sf, 0, sizeof sf); + long r = path ? lsys2(LSYS_statfs, (long)path, (long)&sf) + : lsys2(LSYS_fstatfs, fd, (long)&sf); + if (r < 0) + return (int)fail(r); + out->bsize = (unsigned long)sf.f_bsize; + out->frsize = (unsigned long)(sf.f_frsize ? sf.f_frsize : sf.f_bsize); + out->blocks = sf.f_blocks; + out->bfree = sf.f_bfree; + out->bavail = sf.f_bavail; + out->files = sf.f_files; + out->ffree = sf.f_ffree; + out->favail = sf.f_ffree; + out->fsid = (unsigned long)sf.f_fsid; + out->flag = (unsigned long)(sf.f_flags & ~(unsigned long long)LST_VALID); + out->namemax = (unsigned long)sf.f_namelen; + return 0; +} + +/* ---- directories, as POSIX reads them ------------------------------------ + * + * The same handle as backend.c's __os_opendir -- its layout is repeated + * here, and the two must agree. */ +struct ldir { + int fd; + int pos, len; + char buf[4096]; +}; + +void *__os_opendir(const char *path); +void *__os_fdopendir(int fd); + +static int type_of_dtype(unsigned char t) +{ + switch (t) { + case LDT_REG: return __OS_FT_REGULAR; + case LDT_DIR: return __OS_FT_DIRECTORY; + case LDT_LNK: return __OS_FT_SYMLINK; + case LDT_CHR: return __OS_FT_CHARDEV; + case LDT_BLK: return __OS_FT_BLOCKDEV; + case LDT_FIFO: return __OS_FT_FIFO; + case LDT_SOCK: return __OS_FT_SOCKET; + default: return __OS_FT_UNKNOWN; + } +} + +int __os_readdir_entry(void *dir, struct __os_dirent *e) +{ + struct ldir *d = dir; + if (d->pos >= d->len) { + long r = lsys3(LSYS_getdents64, d->fd, (long)d->buf, + (long)sizeof d->buf); + if (r < 0) + return (int)fail(r); + if (r == 0) + return 0; + d->len = (int)r; + d->pos = 0; + } + struct ldirent64 *k = (struct ldirent64 *)(d->buf + d->pos); + d->pos += k->d_reclen; + e->ino = k->d_ino; + e->type = type_of_dtype(k->d_type); + size_t n = strlen(k->d_name); + if (n >= sizeof e->name) + n = sizeof e->name - 1; /* NAME_MAX is 255: never, in fact */ + memcpy(e->name, k->d_name, n); + e->name[n] = 0; + return 1; +} + +void __os_rewinddir(void *dir) +{ + struct ldir *d = dir; + lsys3(LSYS_lseek, d->fd, 0, 0); + d->pos = d->len = 0; +} + +int __os_dirfd(void *dir) { return ((struct ldir *)dir)->fd; } + +void *__os_fdopendir(int fd) +{ + struct __os_fileinfo fi; + if (__os_fstat(fd, &fi) < 0) + return 0; + if (fi.type != __OS_FT_DIRECTORY) { + errno = ENOTDIR; + return 0; + } + extern void *malloc(size_t); + struct ldir *d = malloc(sizeof *d); + if (!d) { + errno = ENOMEM; + return 0; + } + d->fd = fd; + d->pos = d->len = 0; + return d; +} + +/* ---- processes ------------------------------------------------------------ */ + +long __os_getid(int which) +{ + static const short nr[] = { LSYS_getpid, LSYS_getppid, LSYS_getuid, + LSYS_geteuid, LSYS_getgid, LSYS_getegid }; + if (which < 0 || which > __OS_ID_EGID) + return 0; + return lsys0(nr[which]); +} + +/* seteuid and setegid are setreuid(-1, id) and setregid(-1, id) */ +int __os_setid(int which, unsigned id) +{ + switch (which) { + case __OS_ID_UID: return ret0(lsys1(LSYS_setuid, id)); + case __OS_ID_GID: return ret0(lsys1(LSYS_setgid, id)); + case __OS_ID_EUID: return ret0(lsys2(LSYS_setreuid, -1, id)); + case __OS_ID_EGID: return ret0(lsys2(LSYS_setregid, -1, id)); + } + errno = EINVAL; + return -1; +} + +int __os_getgroups(int n, unsigned *list) +{ + return (int)ret(lsys2(LSYS_getgroups, n, (long)list)); +} + +int __os_setgroups(int n, const unsigned *list) +{ + return ret0(lsys2(LSYS_setgroups, n, (long)list)); +} + +int __os_getpgid(int pid) { return (int)ret(lsys1(LSYS_getpgid, pid)); } +int __os_setpgid(int pid, int pgid) +{ + return ret0(lsys2(LSYS_setpgid, pid, pgid)); +} +int __os_getsid(int pid) { return (int)ret(lsys1(LSYS_getsid, pid)); } +int __os_setsid(void) { return (int)ret(lsys0(LSYS_setsid)); } + +/* fork is clone with only the signal the parent is told by: SIGCHLD. + * Both architectures have clone; only x86-64 has fork itself. */ +#define LSIGCHLD 17 + +int __os_fork(void) +{ + return (int)ret(lsys5(LSYS_clone, LSIGCHLD, 0, 0, 0, 0)); +} + +int __os_execve(const char *path, char *const argv[], char *const envp[]) +{ + return ret0(lsys3(LSYS_execve, (long)path, (long)argv, (long)envp)); +} + +/* posix_spawn, as Linux has no syscall for it: fork, then in the child + * the attributes and the file actions, then execve. What went wrong in + * the child comes back to the parent over a pipe whose ends close on + * exec -- an exec that succeeds closes it with nothing written, and the + * parent reads end-of-file; one that fails writes its errno first. So + * the parent learns of a missing program as an error from posix_spawn, + * which POSIX allows and every caller wants, rather than as a child + * that exits 127. + * + * fork rather than vfork: a vfork child shares the parent's memory, + * and every library call it made before exec would be made on the + * parent's stack. That is a speed difference for a large parent and + * nothing else. */ +static void child_fail(int fd, int err) +{ + lsys3(LSYS_write, fd, (long)&err, sizeof err); + lsys1(LSYS_exit_group, 127); + for (;;) + ; +} + +int __os_spawn_flags(void) +{ + return __OS_SPAWN_RESETIDS | __OS_SPAWN_SETPGROUP | __OS_SPAWN_SETSIGDEF | + __OS_SPAWN_SETSIGMASK | __OS_SPAWN_SETSID; +} + +/* fork carries out any action there is: nothing to refuse in advance */ +int __os_spawn_check(const struct __os_spawn_action *action, int count) +{ + (void)action; + (void)count; + return 0; +} + +int __os_spawn(int *pid, const char *path, + const struct __os_spawn_action *a, int na, + const struct __os_spawn_attr *attr, + char *const argv[], char *const envp[]) +{ + int p[2]; + long r = lsys2(LSYS_pipe2, (long)p, __OS_O_CLOEXEC); + if (r < 0) + return (int)fail(r); + long child = lsys5(LSYS_clone, LSIGCHLD, 0, 0, 0, 0); + if (child < 0) { + lsys1(LSYS_close, p[0]); + lsys1(LSYS_close, p[1]); + return (int)fail(child); + } + if (child == 0) { + lsys1(LSYS_close, p[0]); + int f = attr ? attr->flags : 0; + if ((f & __OS_SPAWN_SETSID) && (r = lsys0(LSYS_setsid)) < 0) + child_fail(p[1], (int)-r); + if ((f & __OS_SPAWN_SETPGROUP) && + (r = lsys2(LSYS_setpgid, 0, attr->pgroup)) < 0) + child_fail(p[1], (int)-r); + if (f & __OS_SPAWN_RESETIDS) { + if ((r = lsys1(LSYS_setgid, lsys0(LSYS_getgid))) < 0 || + (r = lsys1(LSYS_setuid, lsys0(LSYS_getuid))) < 0) + child_fail(p[1], (int)-r); + } + if (f & __OS_SPAWN_SETSIGDEF) + for (int sig = 1; sig <= 64; sig++) + if (attr->sigdefault >> (sig - 1) & 1) { + struct __os_sigaction dfl = { 0, 0, 0 }; + __os_sigaction(sig, &dfl, 0); + } + if (f & __OS_SPAWN_SETSIGMASK) + lsys4(LSYS_rt_sigprocmask, 2, (long)&attr->sigmask, 0, 8); + for (int i = 0; i < na; i++) { + const struct __os_spawn_action *x = &a[i]; + switch (x->kind) { + case __OS_SPAWN_OPEN: { + r = lsys4(LSYS_openat, LAT_FDCWD, (long)x->path, + __linux_oflags(x->oflag), x->mode); + if (r >= 0 && r != x->fd) { + long d = lsys3(LSYS_dup3, r, x->fd, 0); + lsys1(LSYS_close, r); + r = d; + } + break; + } + case __OS_SPAWN_CLOSE: + r = lsys1(LSYS_close, x->fd); + break; + case __OS_SPAWN_DUP2: + /* POSIX: dup2 onto itself clears close-on-exec */ + if (x->fd == x->newfd) { + r = lsys3(LSYS_fcntl, x->fd, __OS_F_GETFD, 0); + if (r >= 0) + r = lsys3(LSYS_fcntl, x->fd, __OS_F_SETFD, + r & ~__OS_FD_CLOEXEC); + } else + r = lsys3(LSYS_dup3, x->fd, x->newfd, 0); + break; + case __OS_SPAWN_CHDIR: + r = lsys1(LSYS_chdir, (long)x->path); + break; + case __OS_SPAWN_FCHDIR: + r = lsys1(LSYS_fchdir, x->fd); + break; + default: + r = -EINVAL; + } + if (r < 0) + child_fail(p[1], (int)-r); + } + r = lsys3(LSYS_execve, (long)path, (long)argv, (long)envp); + child_fail(p[1], (int)-r); + } + lsys1(LSYS_close, p[1]); + int err = 0; + long got; + do + got = lsys3(LSYS_read, p[0], (long)&err, sizeof err); + while (got == -EINTR); + lsys1(LSYS_close, p[0]); + if (got == (long)sizeof err) { + /* reaped here, since the caller never learns this pid */ + int st; + lsys4(LSYS_wait4, child, (long)&st, 0, 0); + errno = err; + return -1; + } + *pid = (int)child; + return 0; +} + +/* EINTR goes to the caller, as POSIX's waitpid has it: a shell waiting + * for its job must see the SIGINT that interrupted the wait */ +int __os_waitpid(int pid, int *status, int options) +{ + return (int)ret(lsys4(LSYS_wait4, pid, (long)status, options, 0)); +} + +/* ---- signals --------------------------------------------------------------- + * + * The kernel's struct sigaction is the handler, the flags as a long, the + * restorer, and the 64-bit mask -- the same order on both architectures. + * + * The RESTORER is where a handler returns to: code that calls + * rt_sigreturn with the stack exactly as the kernel left it. No C + * function can be that code (its prologue moves the stack), so it is + * two instructions placed as bytes, the encodings beside them -- + * tests/golden/libc.sh assembles the comments and compares. x86-64 + * requires it (SA_RESTORER); aarch64 would use the vDSO's without one, + * and is given its own anyway, so a static image needs no vDSO. */ +#define LSA_RESTORER 0x04000000 + +struct ksigaction { + void *handler; + unsigned long flags; + void (*restorer)(void); + unsigned long long mask; +}; + +void __libc_restore_rt(void); + +#if defined(__x86_64__) +__asm__( +".text\n" +".globl __libc_restore_rt\n" +"__libc_restore_rt:\n" +" .byte 0xb8,0x0f,0x00,0x00,0x00 # mov $0xf, %eax\n" +" .byte 0x0f,0x05 # syscall\n" +); +#elif defined(__aarch64__) +__asm__( +".text\n" +".globl __libc_restore_rt\n" +".p2align 2\n" +"__libc_restore_rt:\n" +" .long 0xd2801168 // mov x8, #0x8b\n" +" .long 0xd4000001 // svc #0x0\n" +); +#endif + +int __os_sigaction(int sig, const struct __os_sigaction *act, + struct __os_sigaction *old) +{ + struct ksigaction k, ko; + if (act) { + k.handler = act->handler; + k.flags = (unsigned long)(unsigned)act->flags | LSA_RESTORER; + k.restorer = __libc_restore_rt; + k.mask = act->mask; + } + long r = lsys4(LSYS_rt_sigaction, sig, act ? (long)&k : 0, + old ? (long)&ko : 0, 8); + if (r < 0) + return (int)fail(r); + if (old) { + old->handler = ko.handler; + old->flags = (int)(ko.flags & ~(unsigned long)LSA_RESTORER); + old->mask = ko.mask; + } + return 0; +} + +int __os_kill(int pid, int sig) { return ret0(lsys2(LSYS_kill, pid, sig)); } + +/* a thread's handle is its kernel tid (thread.c) */ +int __os_thread_kill(unsigned long thread, int sig) +{ + long tid = thread ? (long)thread : lsys0(LSYS_gettid); + return ret0(lsys3(LSYS_tgkill, lsys0(LSYS_getpid), tid, sig)); +} + +int __os_sigprocmask(int how, const unsigned long long *set, + unsigned long long *old) +{ + return ret0(lsys4(LSYS_rt_sigprocmask, how, (long)set, (long)old, 8)); +} + +int __os_sigpending(unsigned long long *set) +{ + return ret0(lsys2(LSYS_rt_sigpending, (long)set, 8)); +} + +int __os_sigsuspend(const unsigned long long *mask) +{ + return ret0(lsys2(LSYS_rt_sigsuspend, (long)mask, 8)); +} + +int __os_sigtimedwait(const unsigned long long *set, void *info, + const struct __os_timespec *timeout) +{ + struct ltimespec k, *kp = 0; + if (timeout) { + k.tv_sec = (long)timeout->sec; + k.tv_nsec = timeout->nsec; + kp = &k; + } + return (int)ret(lsys4(LSYS_rt_sigtimedwait, (long)set, (long)info, + (long)kp, 8)); +} + +int __os_sigaltstack(const void *ss, void *old) +{ + return ret0(lsys2(LSYS_sigaltstack, (long)ss, (long)old)); +} + +struct kitimerval { long isec, iusec, vsec, vusec; }; + +int __os_setitimer(int which, const struct __os_itimer *val, + struct __os_itimer *old) +{ + struct kitimerval k = { (long)val->isec, (long)val->iusec, + (long)val->vsec, (long)val->vusec }, ko; + long r = lsys3(LSYS_setitimer, which, (long)&k, old ? (long)&ko : 0); + if (r < 0) + return (int)fail(r); + if (old) + *old = (struct __os_itimer){ ko.isec, ko.iusec, ko.vsec, ko.vusec }; + return 0; +} + +int __os_getitimer(int which, struct __os_itimer *cur) +{ + struct kitimerval k; + long r = lsys2(LSYS_getitimer, which, (long)&k); + if (r < 0) + return (int)fail(r); + *cur = (struct __os_itimer){ k.isec, k.iusec, k.vsec, k.vusec }; + return 0; +} + +/* ---- time ------------------------------------------------------------------ */ + +int __os_clock_gettime(int clock, struct __os_timespec *t) +{ + struct ltimespec k; + long r = lsys2(LSYS_clock_gettime, clock, (long)&k); + if (r < 0) + return (int)fail(r); + t->sec = k.tv_sec; + t->nsec = k.tv_nsec; + return 0; +} + +int __os_clock_getres(int clock, struct __os_timespec *t) +{ + struct ltimespec k; + long r = lsys2(LSYS_clock_getres, clock, (long)&k); + if (r < 0) + return (int)fail(r); + if (t) { + t->sec = k.tv_sec; + t->nsec = k.tv_nsec; + } + return 0; +} + +int __os_clock_settime(int clock, const struct __os_timespec *t) +{ + struct ltimespec k = { (long)t->sec, t->nsec }; + return ret0(lsys2(LSYS_clock_settime, clock, (long)&k)); +} + +#define LTIMER_ABSTIME 1 + +/* clock_nanosleep returns its error instead of setting errno; the seam + * sets errno as everywhere else */ +int __os_clock_nanosleep(int clock, int abs, const struct __os_timespec *req, + struct __os_timespec *rem) +{ + struct ltimespec k = { (long)req->sec, req->nsec }, kr = { 0, 0 }; + long r = lsys4(LSYS_clock_nanosleep, clock, abs ? LTIMER_ABSTIME : 0, + (long)&k, rem ? (long)&kr : 0); + if (r < 0 && rem) { + rem->sec = kr.tv_sec; + rem->nsec = kr.tv_nsec; + } + return ret0(r); +} + +long long __os_times(long long out[4]) +{ + long k[4]; + long r = lsys1(LSYS_times, (long)k); + if (r < 0 && r > -4096) + return fail(r); + for (int i = 0; i < 4; i++) + out[i] = k[i]; + return r; +} + +struct krusage { + long utime_sec, utime_usec, stime_sec, stime_usec; + long maxrss, rest[13]; +}; + +int __os_getrusage(int who, struct __os_rusage *out) +{ + struct krusage k; + long r = lsys2(LSYS_getrusage, who, (long)&k); + if (r < 0) + return (int)fail(r); + out->utime_us = (long long)k.utime_sec * 1000000 + k.utime_usec; + out->stime_us = (long long)k.stime_sec * 1000000 + k.stime_usec; + out->maxrss_kb = k.maxrss; + return 0; +} + +int __os_getrlimit(int resource, unsigned long long lim[2]) +{ + return ret0(lsys4(LSYS_prlimit64, 0, resource, 0, (long)lim)); +} + +int __os_setrlimit(int resource, const unsigned long long lim[2]) +{ + return ret0(lsys4(LSYS_prlimit64, 0, resource, (long)lim, 0)); +} + +/* ---- memory ---------------------------------------------------------------- */ + +void *__os_mmap(void *addr, size_t len, int prot, int flags, int fd, + long long off) +{ + long r = lsys6(LSYS_mmap, (long)addr, (long)len, prot, flags, fd, + (long)off); + if (r < 0 && r > -4096) { + fail(r); + return (void *)-1; + } + return (void *)r; +} + +int __os_munmap(void *addr, size_t len) +{ + return ret0(lsys2(LSYS_munmap, (long)addr, (long)len)); +} + +int __os_mprotect(void *addr, size_t len, int prot) +{ + return ret0(lsys3(LSYS_mprotect, (long)addr, (long)len, prot)); +} + +int __os_msync(void *addr, size_t len, int flags) +{ + return ret0(lsys3(LSYS_msync, (long)addr, (long)len, flags)); +} + +int __os_madvise(void *addr, size_t len, int advice) +{ + return ret0(lsys3(LSYS_madvise, (long)addr, (long)len, advice)); +} + +int __os_mlock(const void *addr, size_t len, int lock) +{ + return ret0(lsys2(lock ? LSYS_mlock : LSYS_munlock, (long)addr, + (long)len)); +} + +/* ---- the system ------------------------------------------------------------ */ + +/* the kernel's has a sixth field, the NIS domain name, POSIX's has not */ +struct kutsname { char f[6][65]; }; + +int __os_uname(struct __os_utsname *out) +{ + struct kutsname k; + long r = lsys1(LSYS_uname, (long)&k); + if (r < 0) + return (int)fail(r); + memcpy(out->sysname, k.f[0], 65); + memcpy(out->nodename, k.f[1], 65); + memcpy(out->release, k.f[2], 65); + memcpy(out->version, k.f[3], 65); + memcpy(out->machine, k.f[4], 65); + return 0; +} + +int __os_sethostname(const char *name, size_t len) +{ + return ret0(lsys2(LSYS_sethostname, (long)name, (long)len)); +} + +struct ksysinfo { + long uptime; + unsigned long loads[3]; + unsigned long totalram, freeram, sharedram, bufferram; + unsigned long totalswap, freeswap; + unsigned short procs, pad; + unsigned long totalhigh, freehigh; + unsigned mem_unit; + char reserved[256]; +}; + +extern unsigned long __libc_pagesize; + +static long pagesize(void) +{ + return __libc_pagesize ? (long)__libc_pagesize : 4096; +} + +/* The processors this process may run on, which is what a program + * sizing a thread pool means by "how many"; the kernel counts them in a + * mask as wide as it was built for, so the mask here is generous. */ +static long processors(void) +{ + unsigned long mask[128]; + long r = lsys3(LSYS_sched_getaffinity, 0, sizeof mask, (long)mask); + if (r < 0) + return fail(r); + long n = 0; + for (long i = 0; i < r / (long)sizeof mask[0]; i++) + n += __builtin_popcountl(mask[i]); + return n ? n : 1; +} + +long __os_sysinfo(int what) +{ + struct ksysinfo k; + switch (what) { + case __OS_INFO_NPROCESSORS_CONF: + case __OS_INFO_NPROCESSORS_ONLN: + return processors(); + case __OS_INFO_PAGESIZE: + return pagesize(); + case __OS_INFO_PHYS_PAGES: + case __OS_INFO_AVPHYS_PAGES: { + long r = lsys1(LSYS_sysinfo, (long)&k); + if (r < 0) + return fail(r); + unsigned long long bytes = + (unsigned long long)(what == __OS_INFO_PHYS_PAGES ? k.totalram + : k.freeram) * + (k.mem_unit ? k.mem_unit : 1); + return (long)(bytes / (unsigned long long)pagesize()); + } + } + errno = EINVAL; + return -1; +} + +/* ---- waiting on descriptors ---------------------------------------------- */ + +int __os_poll(struct __os_pollfd *fds, unsigned long n, + const struct __os_timespec *timeout, + const unsigned long long *sigmask) +{ + struct ltimespec k, *kp = 0; + if (timeout) { + k.tv_sec = (long)timeout->sec; + k.tv_nsec = timeout->nsec; + kp = &k; + } + return (int)ret(lsys5(LSYS_ppoll, (long)fds, (long)n, (long)kp, + (long)sigmask, 8)); +} + +/* ---- sockets --------------------------------------------------------------- */ + +int __os_socket(int domain, int type, int protocol) +{ + return (int)ret(lsys3(LSYS_socket, domain, type, protocol)); +} + +int __os_socketpair(int domain, int type, int protocol, int sv[2]) +{ + return ret0(lsys4(LSYS_socketpair, domain, type, protocol, (long)sv)); +} + +int __os_bind(int fd, const void *addr, unsigned len) +{ + return ret0(lsys3(LSYS_bind, fd, (long)addr, len)); +} + +int __os_connect(int fd, const void *addr, unsigned len) +{ + return ret0(lsys3(LSYS_connect, fd, (long)addr, len)); +} + +int __os_listen(int fd, int backlog) { return ret0(lsys2(LSYS_listen, fd, backlog)); } + +int __os_accept(int fd, void *addr, unsigned *len, int flags) +{ + return (int)ret(lsys4(LSYS_accept4, fd, (long)addr, (long)len, flags)); +} + +long __os_sendto(int fd, const void *buf, size_t n, int flags, + const void *addr, unsigned alen) +{ + return ret(lsys6(LSYS_sendto, fd, (long)buf, (long)n, flags, (long)addr, + alen)); +} + +long __os_recvfrom(int fd, void *buf, size_t n, int flags, void *addr, + unsigned *alen) +{ + return ret(lsys6(LSYS_recvfrom, fd, (long)buf, (long)n, flags, + (long)addr, (long)alen)); +} + +long __os_sendmsg(int fd, const void *msg, int flags) +{ + return ret(lsys3(LSYS_sendmsg, fd, (long)msg, flags)); +} + +long __os_recvmsg(int fd, void *msg, int flags) +{ + return ret(lsys3(LSYS_recvmsg, fd, (long)msg, flags)); +} + +int __os_shutdown(int fd, int how) { return ret0(lsys2(LSYS_shutdown, fd, how)); } + +int __os_getsockopt(int fd, int level, int name, void *val, unsigned *len) +{ + return ret0(lsys5(LSYS_getsockopt, fd, level, name, (long)val, (long)len)); +} + +int __os_setsockopt(int fd, int level, int name, const void *val, + unsigned len) +{ + return ret0(lsys5(LSYS_setsockopt, fd, level, name, (long)val, len)); +} + +int __os_getsockname(int fd, void *addr, unsigned *len) +{ + return ret0(lsys3(LSYS_getsockname, fd, (long)addr, (long)len)); +} + +int __os_getpeername(int fd, void *addr, unsigned *len) +{ + return ret0(lsys3(LSYS_getpeername, fd, (long)addr, (long)len)); +} + +/* Linux has no resolver of its own: the C library asks DNS itself */ +int __os_resolve(const char *name, int family, unsigned char *addrs, int max) +{ + (void)name; (void)family; (void)addrs; (void)max; + errno = ENOSYS; + return -1; +} + +/* ---- terminals -------------------------------------------------------------- + * + * TCGETS fills the kernel's struct termios, which stops after 19 control + * characters and keeps the speed in c_cflag; glibc's -- the seam's -- + * has 32 and the speeds as fields too. The speeds here are the CBAUD + * bits, which is what cfgetospeed reports. */ +#define LTCSETS 0x5402 +#define LCBAUD 0010017 +#define LTIOCGPTN 0x80045430 +#define LTIOCSPTLCK 0x40045431 + +struct ktermios { + unsigned iflag, oflag, cflag, lflag; + unsigned char line; + unsigned char cc[19]; +}; + +int __os_tcgetattr(int fd, struct __os_termios *t) +{ + struct ktermios k; + long r = lsys3(LSYS_ioctl, fd, LTCGETS, (long)&k); + if (r < 0) + return (int)fail(r); + memset(t, 0, sizeof *t); + t->iflag = k.iflag; + t->oflag = k.oflag; + t->cflag = k.cflag; + t->lflag = k.lflag; + t->line = k.line; + memcpy(t->cc, k.cc, sizeof k.cc); + t->ispeed = t->ospeed = k.cflag & LCBAUD; + return 0; +} + +/* TCSETS, TCSETSW, TCSETSF: TCSANOW, TCSADRAIN, TCSAFLUSH in order */ +int __os_tcsetattr(int fd, int action, const struct __os_termios *t) +{ + if (action < 0 || action > 2) { + errno = EINVAL; + return -1; + } + struct ktermios k; + k.iflag = t->iflag; + k.oflag = t->oflag; + k.cflag = t->cflag; + k.lflag = t->lflag; + k.line = t->line; + memcpy(k.cc, t->cc, sizeof k.cc); + return ret0(lsys3(LSYS_ioctl, fd, LTCSETS + action, (long)&k)); +} + +/* a terminal's name is the path its descriptor is open on */ +int __os_ttyname(int fd, char *buf, size_t n) +{ + struct ktermios t; + long r = lsys3(LSYS_ioctl, fd, LTCGETS, (long)&t); + if (r < 0) + return (int)fail(r); + if (__os_fdpath(fd, buf, n) < 0) { + if (errno == ENAMETOOLONG) + errno = ERANGE; + return -1; + } + return 0; +} + +int __os_openpt(int flags) +{ + return (int)ret(lsys4(LSYS_openat, LAT_FDCWD, (long)"/dev/ptmx", + __linux_oflags(flags), 0)); +} + +int __os_ptsname(int fd, char *buf, size_t n) +{ + unsigned num; + long r = lsys3(LSYS_ioctl, fd, LTIOCGPTN, (long)&num); + if (r < 0) + return (int)fail(r); + char tmp[32] = "/dev/pts/"; + char d[12]; + int k = 0; + do + d[k++] = (char)('0' + num % 10); + while (num /= 10); + size_t at = strlen(tmp); + while (k) + tmp[at++] = d[--k]; + tmp[at] = 0; + if (at + 1 > n) { + errno = ERANGE; + return -1; + } + memcpy(buf, tmp, at + 1); + return 0; +} + +int __os_unlockpt(int fd) +{ + int zero = 0; + return ret0(lsys3(LSYS_ioctl, fd, LTIOCSPTLCK, (long)&zero)); +} diff --git a/lib/libc/os/linux/start.c b/lib/libc/os/linux/start.c index d8285ee16..b7c1629c5 100644 --- a/lib/libc/os/linux/start.c +++ b/lib/libc/os/linux/start.c @@ -48,6 +48,7 @@ #include "../backend.h" #include +#include void __libc_start(long *sp); int main(int argc, char **argv); @@ -96,6 +97,7 @@ __asm__( * loop below runs zero times instead of failing to link. */ typedef void (*initfn)(void); +extern char *program_invocation_name, *program_invocation_short_name; extern initfn __init_array_start[] __attribute__((weak)); extern initfn __init_array_end[] __attribute__((weak)); extern initfn __fini_array_start[] __attribute__((weak)); @@ -132,6 +134,14 @@ void __libc_start(long *sp) * stack block the kernel built and they outlive the program. */ environ = argv + argc + 1; + /* The name err(3) and warn(3) begin with (src/posix/err.c): argv[0], + * and the part of it after the last '/'. */ + if (argc > 0 && argv[0]) { + program_invocation_name = argv[0]; + char *slash = strrchr(argv[0], '/'); + program_invocation_short_name = slash ? slash + 1 : argv[0]; + } + if (__fini_array_start != __fini_array_end) atexit(run_fini); for (initfn *f = __init_array_start; f < __init_array_end; f++) diff --git a/lib/libc/os/linux/syscall.h b/lib/libc/os/linux/syscall.h index a6c915811..db6aa5d65 100644 --- a/lib/libc/os/linux/syscall.h +++ b/lib/libc/os/linux/syscall.h @@ -79,6 +79,87 @@ #define LSYS_utimensat 280 #define LSYS_getrandom 318 #define LSYS_statx 332 +/* POSIX's: read out of the kernel's asm/unistd_64.h (Debian 12's uapi + * headers), not typed in; linux.sh checks them against + * again where it runs on Linux */ +#define LSYS_dup 32 +#define LSYS_dup3 292 +#define LSYS_pipe2 293 +#define LSYS_fcntl 72 +#define LSYS_pread64 17 +#define LSYS_pwrite64 18 +#define LSYS_readv 19 +#define LSYS_fsync 74 +#define LSYS_fdatasync 75 +#define LSYS_faccessat 269 +#define LSYS_umask 95 +#define LSYS_fchmod 91 +#define LSYS_fchdir 81 +#define LSYS_fchownat 260 +#define LSYS_mknodat 259 +#define LSYS_chroot 161 +#define LSYS_getpid 39 +#define LSYS_getppid 110 +#define LSYS_getuid 102 +#define LSYS_geteuid 107 +#define LSYS_getgid 104 +#define LSYS_getegid 108 +#define LSYS_setuid 105 +#define LSYS_setgid 106 +#define LSYS_setreuid 113 +#define LSYS_setregid 114 +#define LSYS_setpgid 109 +#define LSYS_getpgid 121 +#define LSYS_setsid 112 +#define LSYS_getsid 124 +#define LSYS_getgroups 115 +#define LSYS_setgroups 116 +#define LSYS_execve 59 +#define LSYS_wait4 61 +#define LSYS_kill 62 +#define LSYS_tgkill 234 +#define LSYS_rt_sigaction 13 +#define LSYS_rt_sigprocmask 14 +#define LSYS_rt_sigpending 127 +#define LSYS_rt_sigsuspend 130 +#define LSYS_rt_sigreturn 15 +#define LSYS_rt_sigtimedwait 128 +#define LSYS_sigaltstack 131 +#define LSYS_setitimer 38 +#define LSYS_getitimer 36 +#define LSYS_uname 63 +#define LSYS_sethostname 170 +#define LSYS_clock_getres 229 +#define LSYS_clock_nanosleep 230 +#define LSYS_clock_settime 227 +#define LSYS_times 100 +#define LSYS_getrusage 98 +#define LSYS_prlimit64 302 +#define LSYS_msync 26 +#define LSYS_madvise 28 +#define LSYS_mlock 149 +#define LSYS_munlock 150 +#define LSYS_ppoll 271 +#define LSYS_socket 41 +#define LSYS_socketpair 53 +#define LSYS_bind 49 +#define LSYS_listen 50 +#define LSYS_accept4 288 +#define LSYS_connect 42 +#define LSYS_getsockname 51 +#define LSYS_getpeername 52 +#define LSYS_sendto 44 +#define LSYS_recvfrom 45 +#define LSYS_setsockopt 54 +#define LSYS_getsockopt 55 +#define LSYS_shutdown 48 +#define LSYS_sendmsg 46 +#define LSYS_recvmsg 47 +#define LSYS_sched_getaffinity 204 +#define LSYS_sysinfo 99 +#define LSYS_flock 73 +#define LSYS_fstatfs 138 +#define LSYS_sync 162 /* O_DIRECTORY. Almost every open flag has the same value everywhere -- * the seam's own __OS_O_* are passed straight through for exactly that @@ -130,6 +211,85 @@ #define LSYS_mprotect 226 #define LSYS_getrandom 278 #define LSYS_statx 291 +/* POSIX's, from asm-generic/unistd.h, like the rest */ +#define LSYS_dup 23 +#define LSYS_dup3 24 +#define LSYS_pipe2 59 +#define LSYS_fcntl 25 +#define LSYS_pread64 67 +#define LSYS_pwrite64 68 +#define LSYS_readv 65 +#define LSYS_fsync 82 +#define LSYS_fdatasync 83 +#define LSYS_faccessat 48 +#define LSYS_umask 166 +#define LSYS_fchmod 52 +#define LSYS_fchdir 50 +#define LSYS_fchownat 54 +#define LSYS_mknodat 33 +#define LSYS_chroot 51 +#define LSYS_getpid 172 +#define LSYS_getppid 173 +#define LSYS_getuid 174 +#define LSYS_geteuid 175 +#define LSYS_getgid 176 +#define LSYS_getegid 177 +#define LSYS_setuid 146 +#define LSYS_setgid 144 +#define LSYS_setreuid 145 +#define LSYS_setregid 143 +#define LSYS_setpgid 154 +#define LSYS_getpgid 155 +#define LSYS_setsid 157 +#define LSYS_getsid 156 +#define LSYS_getgroups 158 +#define LSYS_setgroups 159 +#define LSYS_execve 221 +#define LSYS_wait4 260 +#define LSYS_kill 129 +#define LSYS_tgkill 131 +#define LSYS_rt_sigaction 134 +#define LSYS_rt_sigprocmask 135 +#define LSYS_rt_sigpending 136 +#define LSYS_rt_sigsuspend 133 +#define LSYS_rt_sigreturn 139 +#define LSYS_rt_sigtimedwait 137 +#define LSYS_sigaltstack 132 +#define LSYS_setitimer 103 +#define LSYS_getitimer 102 +#define LSYS_uname 160 +#define LSYS_sethostname 161 +#define LSYS_clock_getres 114 +#define LSYS_clock_nanosleep 115 +#define LSYS_clock_settime 112 +#define LSYS_times 153 +#define LSYS_getrusage 165 +#define LSYS_prlimit64 261 +#define LSYS_msync 227 +#define LSYS_madvise 233 +#define LSYS_mlock 228 +#define LSYS_munlock 229 +#define LSYS_ppoll 73 +#define LSYS_socket 198 +#define LSYS_socketpair 199 +#define LSYS_bind 200 +#define LSYS_listen 201 +#define LSYS_accept4 242 +#define LSYS_connect 203 +#define LSYS_getsockname 204 +#define LSYS_getpeername 205 +#define LSYS_sendto 206 +#define LSYS_recvfrom 207 +#define LSYS_setsockopt 208 +#define LSYS_getsockopt 209 +#define LSYS_shutdown 210 +#define LSYS_sendmsg 211 +#define LSYS_recvmsg 212 +#define LSYS_sched_getaffinity 123 +#define LSYS_sysinfo 179 +#define LSYS_flock 32 +#define LSYS_fstatfs 44 +#define LSYS_sync 81 /* The other half of the swap described above: here 0200000 is O_DIRECT * and this is O_DIRECTORY. */ diff --git a/lib/libc/os/linux/thread.c b/lib/libc/os/linux/thread.c index d2ae773c1..6af2dc81c 100644 --- a/lib/libc/os/linux/thread.c +++ b/lib/libc/os/linux/thread.c @@ -174,7 +174,7 @@ void __libc_tls_free(void *tp); * actually touches are ever backed by memory, and the lowest page is * unmapped so running off the end faults instead of quietly writing * into whatever mmap put below it. */ -#define LSTACK_SIZE (1024 * 1024) +#define LSTACK_SIZE (8 * 1024 * 1024) /* glibc's default; mapped on demand */ #define LGUARD_SIZE 4096 struct lthread { diff --git a/lib/libc/os/linux/tls.c b/lib/libc/os/linux/tls.c index ef9e05d24..687b74ce9 100644 --- a/lib/libc/os/linux/tls.c +++ b/lib/libc/os/linux/tls.c @@ -49,6 +49,12 @@ #define LAT_PHDR 3 #define LAT_PHENT 4 #define LAT_PHNUM 5 +#define LAT_PAGESZ 6 + +/* The page size the kernel reported, for sysconf (os/linux/posix.c): + * aarch64 kernels are built with 4, 16 or 64 KB pages, so it is asked, + * not assumed. 0 until startup has read the auxv. */ +unsigned long __libc_pagesize; #define LPT_TLS 7 /* Just enough of a program header to find PT_TLS and read it. The @@ -177,6 +183,7 @@ void __libc_tls_init(long *sp) if (a[0] == LAT_PHDR) phdr = (const struct lphdr *)a[1]; else if (a[0] == LAT_PHNUM) phnum = (unsigned long)a[1]; else if (a[0] == LAT_PHENT) phent = (unsigned long)a[1]; + else if (a[0] == LAT_PAGESZ) __libc_pagesize = (unsigned long)a[1]; } if (!phdr || !phnum) return; /* nothing to find it with */ diff --git a/lib/libc/os/posix-names.c b/lib/libc/os/posix-names.c new file mode 100644 index 000000000..706b21328 --- /dev/null +++ b/lib/libc/os/posix-names.c @@ -0,0 +1,77 @@ +/* The POSIX names that a backend's primitives are themselves built on, + * where a backend is built on names at all. + * + * The bare-metal and posix-like backends implement __os_write by calling + * write, __os_sbrk by calling sbrk, and so on: those names are the + * program's, or the board's (a weak default the program replaces), as + * newlib's retargeting names are. Defined here too, they would be two + * weak definitions of one name, and whichever an image happened to link + * first would win -- this file's write calling __os_write calling write, + * round and round until the stack ran out (posix-threads did that on + * Xtensa once proc.o came first). + * + * So this file is built only for backends whose primitives are the + * kernel's system calls -- Linux and EmbLinkOS (the Makefile names it for + * those) -- where these names have nobody else to come from. */ +#include +#include +#include +#include +#include + +#include "backend.h" + +#define WEAK __attribute__((weak)) + +/* the mode argument is there only when the flags create */ +WEAK int open(const char *path, int flags, ...) +{ + unsigned mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, unsigned); + va_end(ap); + } + return __os_open(path, flags, (int)mode); +} + +WEAK int close(int fd) { return __os_close(fd); } + +WEAK ssize_t read(int fd, void *buf, size_t n) +{ + return __os_read(fd, buf, n); +} + +WEAK ssize_t write(int fd, const void *buf, size_t n) +{ + return __os_write(fd, buf, n); +} + +WEAK off_t lseek(int fd, off_t off, int whence) +{ + return __os_lseek(fd, off, whence); +} + +/* 0 with EBADF for a descriptor that is not open, ENOTTY for one that + * is open on something else -- the backend's isatty says only yes or no */ +WEAK int isatty(int fd) +{ + if (__os_isatty(fd)) + return 1; + struct __os_fileinfo fi; + if (__os_fstat(fd, &fi) < 0 && errno == EBADF) + return 0; + errno = ENOTTY; + return 0; +} + +WEAK _Noreturn void _exit(int status) +{ + __os_exit(status); + for (;;) + ; +} + +/* the heap's edge, for programs from before malloc */ +WEAK void *sbrk(intptr_t increment) { return __os_sbrk((long)increment); } diff --git a/lib/libc/os/posixlike/backend.c b/lib/libc/os/posixlike/backend.c index 383f797af..c3aa9952f 100644 --- a/lib/libc/os/posixlike/backend.c +++ b/lib/libc/os/posixlike/backend.c @@ -8,6 +8,7 @@ #include "../backend.h" #include +#include #include /* Provided by the target: the raw calls, with the classic signatures. A @@ -217,6 +218,9 @@ int __os_stat(const char *path, struct __os_fileinfo *out) errno = ENOSYS; return -1; } + /* cleared, so the fields a harness's fs_stat does not know of -- + * the owners and the other times -- read as zero, never as stack */ + memset(out, 0, sizeof *out); return fs_stat(path, out); } @@ -226,8 +230,10 @@ int __os_stat(const char *path, struct __os_fileinfo *out) * no lstat would be lying whichever we chose. */ int __os_lstat(const char *path, struct __os_fileinfo *out) { - if (fs_lstat) + if (fs_lstat) { + memset(out, 0, sizeof *out); return fs_lstat(path, out); + } return __os_stat(path, out); } @@ -363,3 +369,8 @@ void __os_closedir(void *dir) if (fs_closedir) fs_closedir(dir); } + +/* POSIX's operating system, the rest of the contract: this backend forwards + * nothing more, so every primitive of that half reports ENOSYS + * (os/absent.inc). The harnesses it serves have no processes or sockets. */ +#include "../absent.inc" diff --git a/lib/libc/src/internal/lock.c b/lib/libc/src/internal/lock.c index eea0c8a88..60291cab1 100644 --- a/lib/libc/src/internal/lock.c +++ b/lib/libc/src/internal/lock.c @@ -40,3 +40,10 @@ void __unlock(__lock_t *l) __os_futex_wake(&l->v, 1); } } + +int __trylock(__lock_t *l) +{ + int c = 0; + return __atomic_compare_exchange_n(&l->v, &c, 1, 0, __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED) ? 0 : -1; +} diff --git a/lib/libc/src/internal/lock.h b/lib/libc/src/internal/lock.h index dff1522c4..5cc8719c4 100644 --- a/lib/libc/src/internal/lock.h +++ b/lib/libc/src/internal/lock.h @@ -38,5 +38,7 @@ typedef struct { volatile int v; } __lock_t; void __lock(__lock_t *l); void __unlock(__lock_t *l); +/* 0 with the lock taken, -1 when it is held: no waiting. */ +int __trylock(__lock_t *l); #endif diff --git a/lib/libc/src/internal/tmpname.h b/lib/libc/src/internal/tmpname.h new file mode 100644 index 000000000..e22e08079 --- /dev/null +++ b/lib/libc/src/internal/tmpname.h @@ -0,0 +1,15 @@ +/* The randomness behind every name this library makes up for a temporary + * file: tmpnam's and tmpfile's (stdio/stdio.c) and mkstemp's family + * (stdlib/tempfile.c). + * + * A name another program can predict is one it can create first -- as a + * link to somewhere else -- so the bits come from the OS's entropy. A + * target with none mixes the clock in instead, and a count keeps one + * program's names apart either way. errno is left as it was: a caller + * asking for a name has not failed because the entropy source did. */ +#ifndef EMBLIBC_TMPNAME_H +#define EMBLIBC_TMPNAME_H + +unsigned long long __tmp_bits(void); + +#endif diff --git a/lib/libc/src/posix/dir.c b/lib/libc/src/posix/dir.c new file mode 100644 index 000000000..3e8c3bde7 --- /dev/null +++ b/lib/libc/src/posix/dir.c @@ -0,0 +1,179 @@ +/* : a DIR is the backend's handle (__os_opendir) and the one + * entry readdir last returned. + * + * A position is the count of entries read since the start, which every + * backend can honour: seekdir rewinds and reads that many again. A + * kernel offset (Linux's d_off) would be cheaper, and is not something + * every OS has to give. */ +#include +#include +#include +#include + +#include "posix.h" + +struct __dirstream { + void *h; + long pos; + struct dirent ent; +}; + +static const unsigned char dt[] = { + [__OS_FT_UNKNOWN] = DT_UNKNOWN, [__OS_FT_REGULAR] = DT_REG, + [__OS_FT_DIRECTORY] = DT_DIR, [__OS_FT_SYMLINK] = DT_LNK, + [__OS_FT_CHARDEV] = DT_CHR, [__OS_FT_BLOCKDEV] = DT_BLK, + [__OS_FT_FIFO] = DT_FIFO, [__OS_FT_SOCKET] = DT_SOCK, +}; + +static DIR *wrap(void *h) +{ + if (!h) + return NULL; + DIR *d = calloc(1, sizeof *d); + if (!d) { + __os_closedir(h); + errno = ENOMEM; + return NULL; + } + d->h = h; + return d; +} + +WEAK DIR *opendir(const char *path) { return wrap(__os_opendir(path)); } +WEAK DIR *fdopendir(int fd) { return wrap(__os_fdopendir(fd)); } + +/* errno is left as it was at the end of the directory: that is how a + * caller tells the end from an error, and POSIX requires it */ +WEAK struct dirent *readdir(DIR *d) +{ + struct __os_dirent e; + int r = __os_readdir_entry(d->h, &e); + if (r <= 0) + return NULL; + d->ent.d_ino = (ino_t)e.ino; + d->ent.d_off = ++d->pos; + d->ent.d_reclen = sizeof d->ent; + d->ent.d_type = e.type >= 0 && e.type < (int)sizeof dt ? dt[e.type] + : DT_UNKNOWN; + memcpy(d->ent.d_name, e.name, sizeof d->ent.d_name); + return &d->ent; +} + +WEAK int readdir_r(DIR *restrict d, struct dirent *restrict out, + struct dirent **restrict result) +{ + int saved = errno; + errno = 0; + struct dirent *e = readdir(d); + int err = errno; + errno = saved; + if (!e) { + *result = NULL; + return err; + } + *out = *e; + *result = out; + return 0; +} + +WEAK void rewinddir(DIR *d) +{ + __os_rewinddir(d->h); + d->pos = 0; +} + +WEAK long telldir(DIR *d) { return d->pos; } + +WEAK void seekdir(DIR *d, long at) +{ + rewinddir(d); + while (d->pos < at && readdir(d)) + ; +} + +WEAK int closedir(DIR *d) +{ + if (!d) { + errno = EBADF; + return -1; + } + __os_closedir(d->h); + free(d); + return 0; +} + +WEAK int dirfd(DIR *d) { return __os_dirfd(d->h); } + +WEAK int alphasort(const struct dirent **a, const struct dirent **b) +{ + return strcoll((*a)->d_name, (*b)->d_name); +} + +WEAK int versionsort(const struct dirent **a, const struct dirent **b) +{ + return strverscmp((*a)->d_name, (*b)->d_name); +} + +/* the caller's comparison, which takes struct dirent ** -- called with + * its own type, not through a cast of its pointer */ +struct by { int (*cmp)(const struct dirent **, const struct dirent **); }; + +static int by_cmp(const void *a, const void *b, void *c) +{ + return ((struct by *)c)->cmp((const struct dirent **)a, + (const struct dirent **)b); +} + +/* The entries keep() accepts, each malloc'd, in a malloc'd array sorted + * by cmp; the count, or -1 with errno and nothing left allocated. */ +WEAK int scandir(const char *path, struct dirent ***list, + int (*keep)(const struct dirent *), + int (*cmp)(const struct dirent **, const struct dirent **)) +{ + DIR *d = opendir(path); + if (!d) + return -1; + struct dirent **v = NULL, *e; + size_t n = 0, cap = 0; + int saved = errno; + errno = 0; + while ((e = readdir(d))) { + if (keep && !keep(e)) + continue; + if (n == cap) { + size_t nc = cap ? 2 * cap : 16; + struct dirent **nv = realloc(v, nc * sizeof *nv); + if (!nv) + goto oom; + v = nv; + cap = nc; + } + if (!(v[n] = malloc(sizeof *e))) + goto oom; + memcpy(v[n++], e, sizeof *e); + } + if (errno) { + int err = errno; + while (n) + free(v[--n]); + free(v); + closedir(d); + errno = err; + return -1; + } + closedir(d); + errno = saved; + if (cmp && n > 1) { + struct by c = { cmp }; + qsort_r(v, n, sizeof *v, by_cmp, &c); + } + *list = v; + return (int)n; +oom: + while (n) + free(v[--n]); + free(v); + closedir(d); + errno = ENOMEM; + return -1; +} diff --git a/lib/libc/src/posix/err.c b/lib/libc/src/posix/err.c new file mode 100644 index 000000000..c22ff406b --- /dev/null +++ b/lib/libc/src/posix/err.c @@ -0,0 +1,87 @@ +/* 's diagnostics, and the program's name they begin with. + * + * The name is what startup code records from argv[0]: the whole of it in + * program_invocation_name, and the part after its last '/' in + * program_invocation_short_name, which is what a message starts with -- + * "ls: cannot access", not "/bin/ls: cannot access". Before startup has + * set them, or on a target whose startup has no argv, they are NULL and + * a message goes without the prefix rather than with a made-up one. */ +#include +#include +#include +#include +#include + +__attribute__((weak)) char *program_invocation_name, *program_invocation_short_name; + +__attribute__((weak)) const char *getprogname(void) { return program_invocation_short_name; } + +__attribute__((weak)) void setprogname(const char *name) +{ + const char *slash = strrchr(name, '/'); + program_invocation_short_name = (char *)(slash ? slash + 1 : name); +} + +/* One lock for the whole line: a diagnostic split across threads reads + * as two different messages. */ +static void say(const char *fmt, va_list ap, int with_errno) +{ + int e = errno; + flockfile(stderr); + if (program_invocation_short_name) + fprintf(stderr, "%s: ", program_invocation_short_name); + if (fmt) { + vfprintf(stderr, fmt, ap); + if (with_errno) + fputs(": ", stderr); + } + if (with_errno) + fputs(strerror(e), stderr); + fputc('\n', stderr); + funlockfile(stderr); +} + +__attribute__((weak)) void vwarn(const char *fmt, va_list ap) { say(fmt, ap, 1); } +__attribute__((weak)) void vwarnx(const char *fmt, va_list ap) { say(fmt, ap, 0); } + +__attribute__((weak)) void verr(int status, const char *fmt, va_list ap) +{ + say(fmt, ap, 1); + exit(status); +} + +__attribute__((weak)) void verrx(int status, const char *fmt, va_list ap) +{ + say(fmt, ap, 0); + exit(status); +} + +__attribute__((weak)) void warn(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); +} + +__attribute__((weak)) void warnx(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vwarnx(fmt, ap); + va_end(ap); +} + +__attribute__((weak)) void err(int status, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + verr(status, fmt, ap); +} + +__attribute__((weak)) void errx(int status, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + verrx(status, fmt, ap); +} diff --git a/lib/libc/src/posix/fd.c b/lib/libc/src/posix/fd.c new file mode 100644 index 000000000..d18a3fce0 --- /dev/null +++ b/lib/libc/src/posix/fd.c @@ -0,0 +1,220 @@ +/* and 's calls on descriptors: open and close, read + * and write in their forms, dup and pipe, fcntl, sync, locks. Each is + * its primitive (os/backend.h); what is here is argument shuffling and + * what POSIX adds above the kernel -- lockf over fcntl, preadv over + * pread. */ +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +WEAK int openat(int dirfd, const char *path, int flags, ...) +{ + unsigned mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, unsigned); + va_end(ap); + } + int saved = errno; + int r = __os_openat(dirfd, path, flags, (int)mode); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_open(p, flags, (int)mode) : -1; +} + +WEAK int creat(const char *path, mode_t mode) +{ + return __os_open(path, O_WRONLY | O_CREAT | O_TRUNC, (int)mode); +} + + +WEAK ssize_t pread(int fd, void *buf, size_t n, off_t off) +{ + return __os_pread(fd, buf, n, off); +} + +WEAK ssize_t pwrite(int fd, const void *buf, size_t n, off_t off) +{ + return __os_pwrite(fd, buf, n, off); +} + +/* struct iovec and the seam's __os_iovec are one layout: a pointer and + * a size_t */ +_Static_assert(sizeof(struct iovec) == sizeof(struct __os_iovec), + "struct iovec is not the seam's"); + +WEAK ssize_t readv(int fd, const struct iovec *iov, int n) +{ + return __os_readv(fd, (const struct __os_iovec *)(const void *)iov, n); +} + +WEAK ssize_t writev(int fd, const struct iovec *iov, int n) +{ + return __os_writev(fd, (const struct __os_iovec *)(const void *)iov, n); +} + +/* buffer by buffer at advancing offsets: positioned I/O has no file + * position to race with, so the pieces need not be one call */ +WEAK ssize_t preadv(int fd, const struct iovec *iov, int n, off_t off) +{ + ssize_t total = 0; + for (int i = 0; i < n; i++) { + long r = __os_pread(fd, iov[i].iov_base, iov[i].iov_len, off + total); + if (r < 0) + return total ? total : -1; + total += r; + if ((size_t)r < iov[i].iov_len) + break; + } + return total; +} + +WEAK ssize_t pwritev(int fd, const struct iovec *iov, int n, off_t off) +{ + ssize_t total = 0; + for (int i = 0; i < n; i++) { + long r = __os_pwrite(fd, iov[i].iov_base, iov[i].iov_len, off + total); + if (r < 0) + return total ? total : -1; + total += r; + if ((size_t)r < iov[i].iov_len) + break; + } + return total; +} + + +WEAK int dup(int fd) { return __os_dup(fd, 0, 0); } +WEAK int dup2(int fd, int newfd) { return __os_dup2(fd, newfd, 0); } + +/* dup3 refuses what dup2 allows: the same descriptor twice */ +WEAK int dup3(int fd, int newfd, int flags) +{ + if (fd == newfd || (flags & ~O_CLOEXEC)) { + errno = EINVAL; + return -1; + } + return __os_dup2(fd, newfd, flags & O_CLOEXEC); +} + +WEAK int pipe(int fds[2]) { return __os_pipe(fds, 0); } +WEAK int pipe2(int fds[2], int flags) { return __os_pipe(fds, flags); } + +/* the third argument is an int, or a struct flock * for the locks */ +WEAK int fcntl(int fd, int cmd, ...) +{ + va_list ap; + va_start(ap, cmd); + long arg = 0; + if (cmd == F_GETLK || cmd == F_SETLK || cmd == F_SETLKW) + arg = (long)va_arg(ap, struct flock *); + else if (cmd != F_GETFD && cmd != F_GETFL) + arg = va_arg(ap, int); + va_end(ap); + if (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC) + return __os_dup(fd, (int)arg, cmd == F_DUPFD_CLOEXEC); + if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW) + return __os_fcntl(fd, cmd, arg); + /* struct flock has off_t's fields, which are 32 bits where a long + * is; the seam's are always 64 */ + struct flock *fl = (struct flock *)arg; + struct __os_flock k = { fl->l_type, fl->l_whence, fl->l_start, + fl->l_len, fl->l_pid }; + int r = __os_fcntl(fd, cmd, (long)&k); + if (r == 0 && cmd == F_GETLK) { + fl->l_type = k.l_type; + fl->l_whence = k.l_whence; + fl->l_start = (off_t)k.l_start; + fl->l_len = (off_t)k.l_len; + fl->l_pid = k.l_pid; + } + return r; +} + +WEAK int fsync(int fd) { return __os_fsync(fd, 0); } +WEAK int fdatasync(int fd) { return __os_fsync(fd, 1); } +WEAK int ftruncate(int fd, off_t size) { return __os_ftruncate(fd, size); } +WEAK void sync(void) { __os_sync(); } + +WEAK int ioctl(int fd, unsigned long request, ...) +{ + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + return __os_ioctl(fd, request, arg); +} + +WEAK int flock(int fd, int op) { return __os_flock_file(fd, op); } + +/* lockf: a write lock from the current position, as fcntl's */ +WEAK int lockf(int fd, int cmd, off_t len) +{ + struct flock fl = { F_WRLCK, SEEK_CUR, 0, len, 0 }; + switch (cmd) { + case F_ULOCK: + fl.l_type = F_UNLCK; + return fcntl(fd, F_SETLK, &fl); + case F_LOCK: + return fcntl(fd, F_SETLKW, &fl); + case F_TLOCK: + return fcntl(fd, F_SETLK, &fl); + case F_TEST: + fl.l_type = F_RDLCK; + if (fcntl(fd, F_GETLK, &fl) < 0) + return -1; + if (fl.l_type == F_UNLCK || fl.l_pid == getpid()) + return 0; + errno = EACCES; + return -1; + } + errno = EINVAL; + return -1; +} + +/* advice, which a kernel may take or leave: taking none is a correct + * answer, and the call checks only what POSIX says it must */ +WEAK int posix_fadvise(int fd, off_t off, off_t len, int advice) +{ + (void)off; + struct __os_fileinfo fi; + if (advice < 0 || advice > POSIX_FADV_NOREUSE || len < 0) + return EINVAL; + if (__os_fstat(fd, &fi) < 0 && errno == EBADF) + return EBADF; + return 0; +} + +WEAK int getentropy(void *buf, size_t n) +{ + if (n > 256) { + errno = EIO; + return -1; + } + return __os_getentropy(buf, n); +} + +/* getrandom over getentropy's 256-byte pieces */ +WEAK ssize_t getrandom(void *buf, size_t n, unsigned flags) +{ + (void)flags; + unsigned char *p = buf; + size_t left = n; + while (left) { + size_t k = left > 256 ? 256 : left; + if (__os_getentropy(p, k) < 0) + return n - left ? (ssize_t)(n - left) : -1; + p += k; + left -= k; + } + return (ssize_t)n; +} diff --git a/lib/libc/src/posix/file.c b/lib/libc/src/posix/file.c new file mode 100644 index 000000000..41f862c8a --- /dev/null +++ b/lib/libc/src/posix/file.c @@ -0,0 +1,455 @@ +/* The filesystem by name: stat and its forms, the links, the + * directories, modes, owners and times, and their *at forms -- which + * resolve (dirfd, path) to a path (posix.h) and call the plain one. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +/* ---- stat ------------------------------------------------------------------ */ + +static const mode_t ifmt[] = { + [__OS_FT_REGULAR] = S_IFREG, [__OS_FT_DIRECTORY] = S_IFDIR, + [__OS_FT_SYMLINK] = S_IFLNK, [__OS_FT_CHARDEV] = S_IFCHR, + [__OS_FT_BLOCKDEV] = S_IFBLK, [__OS_FT_FIFO] = S_IFIFO, + [__OS_FT_SOCKET] = S_IFSOCK, +}; + +static int to_stat(int r, const struct __os_fileinfo *fi, struct stat *st) +{ + if (r < 0) + return -1; + memset(st, 0, sizeof *st); + st->st_dev = fi->dev; + st->st_ino = fi->ino; + st->st_mode = (fi->mode & 07777) | + (fi->type >= 0 && fi->type < (int)(sizeof ifmt / sizeof *ifmt) + ? ifmt[fi->type] : 0); + st->st_nlink = (nlink_t)fi->nlink; + st->st_uid = fi->uid; + st->st_gid = fi->gid; + st->st_rdev = fi->rdev; + st->st_size = (off_t)fi->size; + st->st_blksize = fi->blksize ? fi->blksize : 4096; + /* a backend that does not know the blocks: the size, rounded up */ + st->st_blocks = fi->blocks ? fi->blocks + : (blkcnt_t)((fi->size + 511) / 512); + st->st_atim.tv_sec = fi->atime ? fi->atime : fi->mtime; + st->st_atim.tv_nsec = fi->atime_nsec; + st->st_mtim.tv_sec = fi->mtime; + st->st_mtim.tv_nsec = fi->mtime_nsec; + st->st_ctim.tv_sec = fi->ctime ? fi->ctime : fi->mtime; + st->st_ctim.tv_nsec = fi->ctime_nsec; + return 0; +} + +WEAK int stat(const char *restrict path, struct stat *restrict st) +{ + struct __os_fileinfo fi; + return to_stat(__os_stat(path, &fi), &fi, st); +} + +WEAK int lstat(const char *restrict path, struct stat *restrict st) +{ + struct __os_fileinfo fi; + return to_stat(__os_lstat(path, &fi), &fi, st); +} + +WEAK int fstat(int fd, struct stat *st) +{ + struct __os_fileinfo fi; + return to_stat(__os_fstat(fd, &fi), &fi, st); +} + +WEAK int fstatat(int dirfd, const char *restrict path, + struct stat *restrict st, int flags) +{ + if ((flags & AT_EMPTY_PATH) && path && !*path) + return fstat(dirfd, st); + struct __os_fileinfo fi; + int saved = errno; + int r = __os_fstatat(dirfd, path, &fi, flags); + if (!__at_absent(r, saved)) + return to_stat(r, &fi, st); + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + if (!p) + return -1; + return flags & AT_SYMLINK_NOFOLLOW ? lstat(p, st) : stat(p, st); +} + +/* ---- names ----------------------------------------------------------------- */ + +WEAK int access(const char *path, int mode) { return __os_access(path, mode); } + +WEAK int faccessat(int dirfd, const char *path, int mode, int flags) +{ + int saved = errno; + int r = __os_faccessat(dirfd, path, mode, flags); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_access(p, mode) : -1; +} + +WEAK int unlink(const char *path) { return __os_unlink(path); } +WEAK int rmdir(const char *path) { return __os_rmdir(path); } + +WEAK int unlinkat(int dirfd, const char *path, int flags) +{ + int saved = errno; + int r = __os_unlinkat(dirfd, path, flags); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + if (!p) + return -1; + return flags & AT_REMOVEDIR ? __os_rmdir(p) : __os_unlink(p); +} + +WEAK int link(const char *target, const char *path) +{ + return __os_link(target, path); +} + +WEAK int linkat(int fromfd, const char *from, int tofd, const char *to, + int flags) +{ + int saved = errno; + int r = __os_linkat(fromfd, from, tofd, to, flags); + if (!__at_absent(r, saved)) + return r; + char b1[PATH_MAX], b2[PATH_MAX]; + const char *f = __at_path(fromfd, from, b1), *t = __at_path(tofd, to, b2); + return f && t ? __os_link(f, t) : -1; +} + +WEAK int symlink(const char *target, const char *path) +{ + return __os_symlink(target, path); +} + +/* the target is a string stored in the link, never resolved */ +WEAK int symlinkat(const char *target, int dirfd, const char *path) +{ + int saved = errno; + int r = __os_symlinkat(target, dirfd, path); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_symlink(target, p) : -1; +} + +WEAK ssize_t readlink(const char *restrict path, char *restrict buf, size_t n) +{ + return __os_readlink(path, buf, n); +} + +WEAK ssize_t readlinkat(int dirfd, const char *restrict path, + char *restrict buf, size_t n) +{ + int saved = errno; + long r = __os_readlinkat(dirfd, path, buf, n); + if (!__at_absent((int)(r < 0 ? -1 : 0), saved)) + return r; + char tmp[PATH_MAX]; + const char *p = __at_path(dirfd, path, tmp); + return p ? __os_readlink(p, buf, n) : -1; +} + +WEAK int renameat(int fromfd, const char *from, int tofd, const char *to) +{ + int saved = errno; + int r = __os_renameat(fromfd, from, tofd, to); + if (!__at_absent(r, saved)) + return r; + char b1[PATH_MAX], b2[PATH_MAX]; + const char *f = __at_path(fromfd, from, b1), *t = __at_path(tofd, to, b2); + return f && t ? __os_rename(f, t) : -1; +} + +WEAK int truncate(const char *path, off_t size) +{ + return __os_truncate(path, size); +} + +/* ---- directories ----------------------------------------------------------- */ + +WEAK int mkdir(const char *path, mode_t mode) { return __os_mkdir(path, mode); } + +WEAK int mkdirat(int dirfd, const char *path, mode_t mode) +{ + int saved = errno; + int r = __os_mkdirat(dirfd, path, mode); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_mkdir(p, mode) : -1; +} + +WEAK int chdir(const char *path) { return __os_chdir(path); } +WEAK int fchdir(int fd) { return __os_fchdir(fd); } +WEAK int chroot(const char *path) { return __os_chroot(path); } + +/* NULL asks for a buffer of the size needed, as glibc and the BSDs + * allocate it; POSIX leaves that case to the implementation */ +WEAK char *getcwd(char *buf, size_t n) +{ + if (buf) { + if (n == 0) { + errno = EINVAL; + return NULL; + } + return __os_getcwd(buf, n) < 0 ? NULL : buf; + } + char tmp[PATH_MAX]; + if (__os_getcwd(tmp, sizeof tmp) < 0) + return NULL; + size_t len = strlen(tmp) + 1; + if (n && n < len) { + errno = ERANGE; + return NULL; + } + char *r = malloc(n > len ? n : len); + return r ? memcpy(r, tmp, len) : NULL; +} + +/* ---- modes and owners ------------------------------------------------------- */ + +WEAK int chmod(const char *path, mode_t mode) { return __os_chmod(path, mode); } +WEAK int fchmod(int fd, mode_t mode) { return __os_fchmod(fd, mode); } + +WEAK int fchmodat(int dirfd, const char *path, mode_t mode, int flags) +{ + int saved = errno; + int r = __os_fchmodat(dirfd, path, mode, flags); + if (!__at_absent(r, saved)) + return r; + if (flags & AT_SYMLINK_NOFOLLOW) { + errno = EOPNOTSUPP; /* a link's own mode means nothing here */ + return -1; + } + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_chmod(p, mode) : -1; +} + +WEAK mode_t umask(mode_t mask) { return __os_umask(mask); } + +WEAK int chown(const char *path, uid_t uid, gid_t gid) +{ + return __os_chown(path, AT_FDCWD, uid, gid, 0); +} + +WEAK int lchown(const char *path, uid_t uid, gid_t gid) +{ + return __os_chown(path, AT_FDCWD, uid, gid, 1); +} + +WEAK int fchown(int fd, uid_t uid, gid_t gid) +{ + return __os_chown(NULL, fd, uid, gid, 0); +} + +WEAK int fchownat(int dirfd, const char *path, uid_t uid, gid_t gid, + int flags) +{ + if ((flags & AT_EMPTY_PATH) && path && !*path) + return fchown(dirfd, uid, gid); + int saved = errno; + int r = __os_chown(path, dirfd, uid, gid, flags & AT_SYMLINK_NOFOLLOW); + if (!__at_absent(r, saved) || dirfd == AT_FDCWD) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_chown(p, AT_FDCWD, uid, gid, flags & AT_SYMLINK_NOFOLLOW) + : -1; +} + +WEAK int mknod(const char *path, mode_t mode, dev_t dev) +{ + return __os_mknod(path, mode, dev); +} + +WEAK int mknodat(int dirfd, const char *path, mode_t mode, dev_t dev) +{ + int saved = errno; + int r = __os_mknodat(dirfd, path, mode, dev); + if (!__at_absent(r, saved)) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + return p ? __os_mknod(p, mode, dev) : -1; +} + +WEAK int mkfifo(const char *path, mode_t mode) +{ + return __os_mknod(path, (mode & 07777) | S_IFIFO, 0); +} + +WEAK int mkfifoat(int dirfd, const char *path, mode_t mode) +{ + return mknodat(dirfd, path, (mode & 07777) | S_IFIFO, 0); +} + +/* ---- times --------------------------------------------------------------- */ + +static const struct __os_timespec *ts_in(const struct timespec *t, + struct __os_timespec out[2]) +{ + if (!t) + return NULL; + for (int i = 0; i < 2; i++) { + out[i].sec = t[i].tv_sec; + out[i].nsec = t[i].tv_nsec; + } + return out; +} + +WEAK int futimens(int fd, const struct timespec times[2]) +{ + struct __os_timespec k[2]; + return __os_utimens(NULL, fd, ts_in(times, k), 0); +} + +WEAK int utimensat(int dirfd, const char *path, const struct timespec times[2], + int flags) +{ + struct __os_timespec k[2]; + int saved = errno; + int r = __os_utimens(path, dirfd, ts_in(times, k), + flags & AT_SYMLINK_NOFOLLOW); + if (!__at_absent(r, saved) || dirfd == AT_FDCWD || !path) + return r; + char buf[PATH_MAX]; + const char *p = __at_path(dirfd, path, buf); + if (!p) + return -1; + return __os_utimens(p, AT_FDCWD, ts_in(times, k), + flags & AT_SYMLINK_NOFOLLOW); +} + +/* A kernel with no utimens may still set a modification time + * (__os_utime): asked for a time and nothing more, that is the answer. */ +WEAK int utime(const char *path, const struct utimbuf *t) +{ + struct __os_timespec k[2]; + if (t) { + k[0].sec = t->actime; + k[1].sec = t->modtime; + k[0].nsec = k[1].nsec = 0; + } + int r = __os_utimens(path, AT_FDCWD, t ? k : NULL, 0); + if (r < 0 && errno == ENOSYS && t) + return __os_utime(path, t->modtime); + return r; +} + +static int tv_utimens(const char *path, int fd, const struct timeval tv[2], + int nofollow) +{ + struct __os_timespec k[2]; + if (tv) + for (int i = 0; i < 2; i++) { + k[i].sec = tv[i].tv_sec; + k[i].nsec = tv[i].tv_usec * 1000; + } + return __os_utimens(path, fd, tv ? k : NULL, nofollow); +} + +WEAK int utimes(const char *path, const struct timeval times[2]) +{ + return tv_utimens(path, AT_FDCWD, times, 0); +} + +WEAK int lutimes(const char *path, const struct timeval times[2]) +{ + return tv_utimens(path, AT_FDCWD, times, 1); +} + +WEAK int futimes(int fd, const struct timeval times[2]) +{ + return tv_utimens(NULL, fd, times, 0); +} + +/* ---- filesystems ------------------------------------------------------------ */ + +static int to_statvfs(int r, const struct __os_fsinfo *k, struct statvfs *out) +{ + if (r < 0) + return -1; + memset(out, 0, sizeof *out); + out->f_bsize = k->bsize; + out->f_frsize = k->frsize; + out->f_blocks = k->blocks; + out->f_bfree = k->bfree; + out->f_bavail = k->bavail; + out->f_files = k->files; + out->f_ffree = k->ffree; + out->f_favail = k->favail; + out->f_fsid = k->fsid; + out->f_flag = k->flag; + out->f_namemax = k->namemax; + return 0; +} + +WEAK int statvfs(const char *restrict path, struct statvfs *restrict out) +{ + struct __os_fsinfo k; + return to_statvfs(__os_statvfs(path, -1, &k), &k, out); +} + +WEAK int fstatvfs(int fd, struct statvfs *out) +{ + struct __os_fsinfo k; + return to_statvfs(__os_statvfs(NULL, fd, &k), &k, out); +} + +/* What POSIX lets a file's filesystem say. This library's answers are + * the same for every filesystem: the limits it was built with, which + * are what it will refuse past anyway. */ +static long path_limit(int name) +{ + switch (name) { + case _PC_LINK_MAX: return 127; + case _PC_MAX_CANON: return 255; + case _PC_MAX_INPUT: return 255; + case _PC_NAME_MAX: return NAME_MAX; + case _PC_PATH_MAX: return PATH_MAX; + case _PC_PIPE_BUF: return 4096; + case _PC_CHOWN_RESTRICTED: return 1; + case _PC_NO_TRUNC: return 1; + case _PC_VDISABLE: return 0; + case _PC_SYNC_IO: return 1; + case _PC_FILESIZEBITS: return 64; + case _PC_SYMLINK_MAX: return -1; /* no limit */ + } + errno = EINVAL; + return -1; +} + +WEAK long pathconf(const char *path, int name) +{ + struct __os_fileinfo fi; + if (__os_stat(path, &fi) < 0) + return -1; + return path_limit(name); +} + +WEAK long fpathconf(int fd, int name) +{ + struct __os_fileinfo fi; + if (__os_fstat(fd, &fi) < 0 && errno == EBADF) + return -1; + return path_limit(name); +} diff --git a/lib/libc/src/posix/fnmatch.c b/lib/libc/src/posix/fnmatch.c new file mode 100644 index 000000000..f10e479d3 --- /dev/null +++ b/lib/libc/src/posix/fnmatch.c @@ -0,0 +1,174 @@ +/* fnmatch: whether a string matches a shell wildcard pattern. + * + * One pass with ONE backtracking point, the last '*' seen -- which is + * enough, and is what keeps a pattern like "*a*a*a*a*b" against a long + * run of a's linear rather than exponential. The argument: when the + * text after the last star fails, only that star stretching can help; + * an earlier star stretching would have to re-match everything between + * it and the last one, and the last star can already absorb that. + * + * FNM_PATHNAME changes one thing about it: no wildcard matches '/', so + * a star may not stretch over one, and when the last star would have + * to, nothing can help and the match fails outright. + */ +#include +#include +#include + +static int fold(int c, int flags) +{ + return flags & FNM_CASEFOLD ? tolower(c) : c; +} + +static int in_class(const char *name, size_t n, int c) +{ + static const struct { const char *name; int (*is)(int); } cls[] = { + { "alnum", isalnum }, { "alpha", isalpha }, { "blank", isblank }, + { "cntrl", iscntrl }, { "digit", isdigit }, { "graph", isgraph }, + { "lower", islower }, { "print", isprint }, { "punct", ispunct }, + { "space", isspace }, { "upper", isupper }, { "xdigit", isxdigit }, + }; + for (size_t i = 0; i < sizeof cls / sizeof cls[0]; i++) + if (strlen(cls[i].name) == n && memcmp(cls[i].name, name, n) == 0) + return cls[i].is(c) != 0; + return -1; +} + +/* One character of a bracket expression, at *pp: escaped, a collating + * element [.c.] or an equivalence class [=c=] of a single character + * (this library's only locale has no others), or itself. -1 when it is + * malformed. */ +static int bracket_char(const char **pp, int flags) +{ + const char *p = *pp; + if (*p == '\\' && !(flags & FNM_NOESCAPE) && p[1]) { + *pp = p + 2; + return (unsigned char)p[1]; + } + if (*p == '[' && (p[1] == '.' || p[1] == '=')) { + if (!p[2] || p[3] != p[1] || p[4] != ']') + return -1; + *pp = p + 5; + return (unsigned char)p[2]; + } + *pp = p + 1; + return (unsigned char)*p; +} + +/* The bracket expression after '[' at p, against c: past its ']', with + * *ok set, or NULL when it is not one -- unterminated or malformed -- + * and the '[' is then an ordinary character, as the shell has it. */ +static const char *bracket(const char *p, int c, int flags, int *ok) +{ + int neg = 0, hit = 0; + if (*p == '!' || *p == '^') { + neg = 1; + p++; + } + const char *first = p; + for (;;) { + if (!*p) + return NULL; + if (*p == ']' && p > first) + break; + if (p[0] == '[' && p[1] == ':') { + const char *name = p + 2, *e = strstr(name, ":]"); + if (!e) + return NULL; + /* a class is not folded: [[:upper:]] is the upper case + * letters under FNM_CASEFOLD too, as glibc has it */ + int r = in_class(name, (size_t)(e - name), c); + if (r < 0) + return NULL; + hit |= r; + p = e + 2; + continue; + } + int lo = bracket_char(&p, flags), hi = lo; + if (lo < 0) + return NULL; + if (p[0] == '-' && p[1] && p[1] != ']') { + p++; + hi = bracket_char(&p, flags); + if (hi < 0) + return NULL; + } + if (lo <= c && c <= hi) + hit = 1; + else if (flags & FNM_CASEFOLD) { + int l = tolower(c), u = toupper(c); + if ((lo <= l && l <= hi) || (lo <= u && u <= hi)) + hit = 1; + } + } + *ok = hit != neg; + return p + 1; +} + +__attribute__((weak)) int fnmatch(const char *pat, const char *str, int flags) +{ + const char *p = pat, *s = str; + const char *star_p = NULL, *star_s = NULL; + int path = flags & FNM_PATHNAME; + + for (;;) { + int c = (unsigned char)*s; + /* A '.' that only a '.' may match: at the start, or of a path + * component when '/' separates them. */ + int hidden = (flags & FNM_PERIOD) && c == '.' && + (s == str || (path && s[-1] == '/')); + switch (*p) { + case 0: + if (!c || ((flags & FNM_LEADING_DIR) && c == '/')) + return 0; + goto fail; + case '?': + if (!c || hidden || (path && c == '/')) + goto fail; + p++, s++; + continue; + case '*': + if (hidden) + goto fail; + while (*p == '*') + p++; + star_p = p; + star_s = s; + continue; + case '[': { + int ok; + const char *q = bracket(p + 1, c, flags, &ok); + if (!q) { + if (c != '[') + goto fail; + p++, s++; + continue; + } + if (!c || hidden || (path && c == '/') || !ok) + goto fail; + p = q, s++; + continue; + } + case '\\': + /* The escaped character, compared as any other. A '\' that + * ends the pattern escapes nothing and matches nothing: + * glibc's "trailing \ loses". */ + if (!(flags & FNM_NOESCAPE)) { + if (!p[1]) + return FNM_NOMATCH; + p++; + } + /* fall through */ + default: + if (!c || fold((unsigned char)*p, flags) != fold(c, flags)) + goto fail; + p++, s++; + continue; + } + fail: + if (!star_p || !*star_s || (path && *star_s == '/')) + return FNM_NOMATCH; + p = star_p; + s = ++star_s; + } +} diff --git a/lib/libc/src/posix/ftw.c b/lib/libc/src/posix/ftw.c new file mode 100644 index 000000000..5e8b28d92 --- /dev/null +++ b/lib/libc/src/posix/ftw.c @@ -0,0 +1,242 @@ +/* : depth first, one directory open at a time per level -- the + * descriptor budget (`fds`) is a limit POSIX lets an implementation keep + * by closing and reopening, and this one reads each directory whole + * before descending, so it never holds more than one open. The order of + * a directory's entries is readdir's, as glibc's is. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +typedef int (*nftw_fn)(const char *, const struct stat *, int, struct FTW *); + +struct walk { + nftw_fn fn; + int (*old)(const char *, const struct stat *, int); /* ftw's */ + int flags; + dev_t dev; + char path[PATH_MAX]; +}; + +#define STOPPED(r) ((w->flags & FTW_ACTIONRETVAL) ? (r) == FTW_STOP : (r) != 0) + +static int visit(struct walk *w, size_t len, int base, int level); + +static int call(struct walk *w, const struct stat *st, int type, + struct FTW *f) +{ + return w->fn ? w->fn(w->path, st, type, f) : w->old(w->path, st, type); +} + +/* the entries of the directory in w->path, read and closed first */ +static char **list(const char *dir, int *err) +{ + DIR *d = opendir(dir); + if (!d) { + *err = errno; + return NULL; + } + size_t n = 0, cap = 16; + char **v = malloc(cap * sizeof *v); + while (v) { + struct dirent *e = readdir(d); + if (!e) + break; + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) + continue; + if (n + 1 == cap) { + char **nv = realloc(v, (cap *= 2) * sizeof *v); + if (!nv) { + for (size_t i = 0; i < n; i++) + free(v[i]); + free(v); + v = NULL; + break; + } + v = nv; + } + if (!(v[n] = strdup(e->d_name))) { + for (size_t i = 0; i < n; i++) + free(v[i]); + free(v); + v = NULL; + break; + } + n++; + } + closedir(d); + if (!v) { + *err = ENOMEM; + return NULL; + } + v[n] = NULL; + return v; +} + +static int visit(struct walk *w, size_t len, int base, int level) +{ + struct stat st; + struct FTW f = { base, level }; + int type, r; + int (*st_fn)(const char *, struct stat *) = + (w->flags & FTW_PHYS) ? lstat : stat; + if (st_fn(w->path, &st) != 0) { + if (level == 0) + return -1; /* the start itself: nftw's error, errno set */ + /* a link whose target is gone, when links are followed */ + if (!(w->flags & FTW_PHYS) && lstat(w->path, &st) == 0 && + S_ISLNK(st.st_mode)) + type = FTW_SLN; + else if (errno == EACCES || errno == ENOENT || errno == ENOTDIR || + errno == ELOOP) + type = FTW_NS; + else + return -1; + } else if (S_ISDIR(st.st_mode)) { + type = (w->flags & FTW_DEPTH) ? FTW_DP : FTW_D; + } else if (S_ISLNK(st.st_mode)) { + type = FTW_SL; + } else { + type = FTW_F; + } + if (type == FTW_NS) + memset(&st, 0, sizeof st); + if (level == 0) + w->dev = st.st_dev; + else if ((w->flags & FTW_MOUNT) && type != FTW_NS && st.st_dev != w->dev) + return 0; + + int is_dir = type == FTW_D || type == FTW_DP; + char **names = NULL; + if (is_dir) { + int err = 0; + names = list(w->path, &err); + if (!names) { + if (err == ENOMEM) + return -1; + type = FTW_DNR; + is_dir = 0; + } + } + if (type != FTW_DP) { + r = call(w, &st, type, &f); + if (STOPPED(r) || ((w->flags & FTW_ACTIONRETVAL) && + r == FTW_SKIP_SIBLINGS)) { + if (names) { + for (char **p = names; *p; p++) + free(*p); + free(names); + } + return r; + } + if ((w->flags & FTW_ACTIONRETVAL) && r == FTW_SKIP_SUBTREE) + is_dir = 0; + } + if (names) { + r = 0; + int cwd = -1; + if (is_dir && (w->flags & FTW_CHDIR)) { + cwd = open(".", O_RDONLY | O_DIRECTORY); + /* below the start, cwd is the parent: in by the name */ + if (chdir(level ? w->path + base : w->path) != 0) { + if (cwd >= 0) + close(cwd); + cwd = -2; + } + } + for (char **p = names; is_dir && *p && cwd != -2; p++) { + size_t nl = strlen(*p); + size_t add = (len && w->path[len - 1] != '/') ? 1 : 0; + if (len + add + nl >= sizeof w->path) { + errno = ENAMETOOLONG; + r = -1; + break; + } + if (add) + w->path[len] = '/'; + memcpy(w->path + len + add, *p, nl + 1); + r = visit(w, len + add + nl, (int)(len + add), level + 1); + w->path[len] = 0; + if ((w->flags & FTW_ACTIONRETVAL) && r == FTW_SKIP_SIBLINGS) { + r = 0; + break; + } + if (STOPPED(r) || r < 0) + break; + } + if (cwd >= 0) { + (void)!fchdir(cwd); + close(cwd); + } + for (char **p = names; *p; p++) + free(*p); + free(names); + if (STOPPED(r) || r < 0) + return r; + if (type == FTW_DP) { + r = call(w, &st, FTW_DP, &f); + if ((w->flags & FTW_ACTIONRETVAL) && r != FTW_STOP) + r = 0; + return r; + } + } + return 0; +} + +WEAK int nftw(const char *path, nftw_fn fn, int fds, int flags) +{ + if (fds < 1) { + errno = EINVAL; + return -1; + } + struct walk w; + w.fn = fn; + w.old = NULL; + w.flags = flags; + size_t len = strlen(path); + if (len >= sizeof w.path) { + errno = ENAMETOOLONG; + return -1; + } + memcpy(w.path, path, len + 1); + /* where the last component starts, past any trailing slashes */ + size_t e = len; + while (e > 1 && w.path[e - 1] == '/') + e--; + size_t b = e; + while (b > 0 && w.path[b - 1] != '/') + b--; + int r = visit(&w, len, (int)b, 0); + if ((flags & FTW_ACTIONRETVAL) && r > 0 && r != FTW_STOP) + r = 0; + return r; +} + +/* ftw: nftw following links, with a callback that takes no struct FTW + * -- the walk carries either kind, so neither needs the other's shape */ +WEAK int ftw(const char *path, + int (*fn)(const char *, const struct stat *, int), int fds) +{ + if (fds < 1) { + errno = EINVAL; + return -1; + } + struct walk w; + w.fn = NULL; + w.old = fn; + w.flags = 0; + size_t len = strlen(path); + if (len >= sizeof w.path) { + errno = ENAMETOOLONG; + return -1; + } + memcpy(w.path, path, len + 1); + return visit(&w, len, 0, 0); +} diff --git a/lib/libc/src/posix/getopt.c b/lib/libc/src/posix/getopt.c new file mode 100644 index 000000000..c7268e68d --- /dev/null +++ b/lib/libc/src/posix/getopt.c @@ -0,0 +1,252 @@ +/* getopt, getopt_long and getopt_long_only. + * + * All three PERMUTE, as glibc's and newlib's getopt do: "prog file -v" + * finds the -v, moving each option in front of the operands it skipped, + * so a caller's operands end up in argv[optind..] either way. POSIX's + * getopt stops at the first operand instead, and a program gets that by + * starting its option string with '+', or its user by setting + * POSIXLY_CORRECT. Programs were written against the permuting ones -- + * EmbLinkOS's against newlib's -- and expect it. An option string that + * starts with '-' asks for each operand in order, as an option whose + * code is 1 and whose argument is the operand. + * + * The diagnostics are glibc's words, so a test that compares a program's + * stderr sees the same line here as there. + */ +#include +#include +#include +#include + +__attribute__((weak)) char *optarg; +__attribute__((weak)) int optind = 1, opterr = 1, optopt, optreset; + +static int optpos; /* where in argv[optind] the next short option is */ + +static void say_short(const char *prog, const char *what, int c) +{ + fprintf(stderr, "%s: %s -- '%c'\n", prog, what, c); +} + +static void say_long(const char *prog, const char *what, const char *dashes, + const char *name, size_t n) +{ + fprintf(stderr, "%s: %s '%s%.*s'\n", prog, what, dashes, (int)n, name); +} + +/* optind = 0 is a full reset: glibc's convention, and the only way a + * program that parses two argument vectors can ask for one */ +static void restart(void) +{ + if (optind == 0 || optreset) { + optreset = 0; + optpos = 0; + optind = 1; + } +} + +/* The option string without its '+' or '-' mode character. */ +static const char *spec_of(const char *opts) +{ + return opts[0] == '+' || opts[0] == '-' ? opts + 1 : opts; +} + +/* One short option, POSIX's way: no permuting, which the callers do. */ +static int short_opt(int argc, char *const argv[], const char *opts) +{ + restart(); + optarg = NULL; + if (optind >= argc || !argv[optind]) + return -1; + char *arg = argv[optind]; + if (optpos == 0) { + if (arg[0] != '-' || !arg[1]) { + if (opts[0] != '-') + return -1; + optarg = arg; /* '-' in opts: operands in order */ + optind++; + return 1; + } + if (arg[1] == '-' && !arg[2]) { + optind++; /* "--" ends the options */ + return -1; + } + optpos = 1; + } + const char *spec = spec_of(opts); + int quiet = spec[0] == ':'; /* report ':' rather than print */ + int c = (unsigned char)arg[optpos++]; + const char *at = c == ':' ? NULL : strchr(spec, c); + char *rest = arg + optpos; /* what follows c in this argument */ + + if (!at || !at[1] || at[1] != ':') { + if (!*rest) { + optind++; + optpos = 0; + } + if (at) + return c; + optopt = c; + if (opterr && !quiet) + say_short(argv[0], "invalid option", c); + return '?'; + } + + /* c takes an argument: the rest of this one, or the next one -- or, + * for "c::", GNU's optional argument, only the rest of this one */ + optind++; + optpos = 0; + if (*rest) + optarg = rest; + else if (at[2] == ':') + optarg = NULL; + else if (optind < argc && argv[optind]) + optarg = argv[optind++]; + else { + optopt = c; + if (quiet) + return ':'; + if (opterr) + say_short(argv[0], "option requires an argument", c); + return '?'; + } + return c; +} + +/* Move argv[src] to argv[dest], shifting the ones between up by one. The + * strings are not touched, only the vector; POSIX's prototype says + * `char *const argv[]`, and every getopt that permutes casts that away. */ +static void move_down(char *const *argv, int dest, int src) +{ + char **av = (char **)argv; + char *tmp = av[src]; + for (int i = src; i > dest; i--) + av[i] = av[i - 1]; + av[dest] = tmp; +} + +static int long_core(int argc, char *const argv[], const char *opts, + const struct option *lo, int *index, int only) +{ + char *arg = argv[optind]; + int dashes = arg[1] == '-' ? 2 : 1; + if (!lo || optpos != 0 || arg[0] != '-' || + !(dashes == 2 ? arg[2] != 0 : only && arg[1] != 0)) + return short_opt(argc, argv, opts); + + const char *spec = spec_of(opts); + int quiet = spec[0] == ':'; + const char *name = arg + dashes; + size_t n = strcspn(name, "="); + int match = -1, count = 0; + for (int i = 0; lo[i].name; i++) { + if (strncmp(lo[i].name, name, n) != 0) + continue; + if (lo[i].name[n] == 0) { /* exact: no other can compete */ + match = i; + count = 1; + break; + } + /* GNU's rule: prefixes of several names are one match when the + * options they name are the same option */ + if (match < 0 || lo[i].has_arg != lo[match].has_arg || + lo[i].flag != lo[match].flag || lo[i].val != lo[match].val) + count++; + if (match < 0) + match = i; + } + /* -abc under getopt_long_only that names no long option, but whose + * first letter is a short one: the short options it spells */ + if (count == 0 && dashes == 1 && strchr(spec, arg[1])) + return short_opt(argc, argv, opts); + + const char *dd = dashes == 2 ? "--" : "-"; + optind++; + if (count != 1) { + optopt = 0; + if (opterr && !quiet) + say_long(argv[0], count ? "option is ambiguous;" : + "unrecognized option", dd, name, strlen(name)); + return '?'; + } + const struct option *o = &lo[match]; + optarg = NULL; + if (name[n] == '=') { + if (o->has_arg == no_argument) { + optopt = o->val; + if (opterr && !quiet) + say_long(argv[0], "option doesn't allow an argument", dd, + o->name, strlen(o->name)); + return '?'; + } + optarg = (char *)name + n + 1; + } else if (o->has_arg == required_argument) { + if (optind >= argc || !argv[optind]) { + optopt = o->val; + if (quiet) + return ':'; + if (opterr) + say_long(argv[0], "option requires an argument", dd, + o->name, strlen(o->name)); + return '?'; + } + optarg = argv[optind++]; + } + if (index) + *index = match; + if (o->flag) { + *o->flag = o->val; + return 0; + } + return o->val; +} + +static int long_permuting(int argc, char *const argv[], const char *opts, + const struct option *lo, int *index, int only) +{ + restart(); + if (optind >= argc || !argv[optind]) + return -1; + int skipped = optind; + /* Mid-cluster too: the search finds the same "-abc" again, since it + * starts with '-', and optpos still says where in it to go on. */ + if (opts[0] != '+' && opts[0] != '-' && !getenv("POSIXLY_CORRECT")) { + int i = optind; + for (;; i++) { + if (i >= argc || !argv[i]) + return -1; /* operands only, all left in place */ + if (argv[i][0] == '-' && argv[i][1]) + break; + } + optind = i; + } + int resumed = optind; + int r = long_core(argc, argv, opts, lo, index, only); + /* The arguments consumed come in front of the operands passed over. + * In the middle of "-abc" none has been, and optind goes back to the + * first operand -- which the next call's search passes over again. */ + if (resumed > skipped) { + int taken = optind - resumed; + for (int i = 0; i < taken; i++) + move_down(argv, skipped, optind - 1); + optind = skipped + taken; + } + return r; +} + +__attribute__((weak)) int getopt(int argc, char *const argv[], const char *opts) +{ + return long_permuting(argc, argv, opts, NULL, NULL, 0); +} + +__attribute__((weak)) int getopt_long(int argc, char *const argv[], const char *opts, + const struct option *lo, int *index) +{ + return long_permuting(argc, argv, opts, lo, index, 0); +} + +__attribute__((weak)) int getopt_long_only(int argc, char *const argv[], const char *opts, + const struct option *lo, int *index) +{ + return long_permuting(argc, argv, opts, lo, index, 1); +} diff --git a/lib/libc/src/posix/glob.c b/lib/libc/src/posix/glob.c new file mode 100644 index 000000000..cb5c35041 --- /dev/null +++ b/lib/libc/src/posix/glob.c @@ -0,0 +1,419 @@ +/* : a pattern a component at a time. A component with no magic + * is taken as it is (its backslashes removed); one with magic is matched + * by fnmatch against the names in the directory so far, which is the + * only place glob reads a directory. Braces and a leading tilde are + * GNU's and are expanded first, into whole patterns. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +struct ctx { + int flags; + int (*errfunc)(const char *, int); + glob_t *g; + char **v; /* the matches so far, this call's */ + size_t n, cap; + int aborted, nospace; +}; + +/* magic: an unescaped * or ?, or a [ that a ] closes, as glibc decides */ +static int magic(const char *s, size_t n, int noescape) +{ + for (size_t i = 0; i < n; i++) { + if (s[i] == '\\' && !noescape && i + 1 < n) { + i++; + continue; + } + if (s[i] == '*' || s[i] == '?') + return 1; + if (s[i] == '[') + for (size_t j = i + 1; j < n; j++) + if (s[j] == ']' && j > i + 1) + return 1; + } + return 0; +} + +static int add(struct ctx *c, char *path) +{ + if (!path) { + c->nospace = 1; + return -1; + } + if (c->n == c->cap) { + size_t cap = c->cap ? c->cap * 2 : 16; + char **v = realloc(c->v, cap * sizeof *v); + if (!v) { + free(path); + c->nospace = 1; + return -1; + } + c->v = v; + c->cap = cap; + } + c->v[c->n++] = path; + return 0; +} + +static int stat_of(struct ctx *c, const char *p, struct stat *st, int follow) +{ + if (c->flags & GLOB_ALTDIRFUNC) + return follow ? c->g->gl_stat(p, st) : c->g->gl_lstat(p, st); + return follow ? stat(p, st) : lstat(p, st); +} + +static int is_dir(struct ctx *c, const char *p) +{ + struct stat st; + return stat_of(c, p, &st, 1) == 0 && S_ISDIR(st.st_mode); +} + +/* base followed by n bytes of name, with GLOB_MARK's slash if asked */ +static char *join(const char *base, const char *name, size_t n, int slash) +{ + size_t b = strlen(base); + char *p = malloc(b + n + 2); + if (!p) + return NULL; + memcpy(p, base, b); + memcpy(p + b, name, n); + p[b + n] = 0; + if (slash && (b + n == 0 || p[b + n - 1] != '/')) { + p[b + n] = '/'; + p[b + n + 1] = 0; + } + return p; +} + +static void unescape(char *s, int noescape) +{ + if (noescape) + return; + char *o = s; + for (; *s; s++) { + if (*s == '\\' && s[1]) + s++; + *o++ = *s; + } + *o = 0; +} + +static int found(struct ctx *c, char *path) +{ + int dir = (c->flags & (GLOB_MARK | GLOB_ONLYDIR)) && is_dir(c, path); + if ((c->flags & GLOB_ONLYDIR) && !dir) { + free(path); + return 0; + } + if ((c->flags & GLOB_MARK) && dir) { + char *m = join(path, "", 0, 1); + free(path); + path = m; + } + return add(c, path); +} + +static void expand(struct ctx *c, const char *base, const char *rest) +{ + if (c->aborted || c->nospace) + return; + /* slashes belong to the base: "/a//b" keeps them as written */ + size_t sl = strspn(rest, "/"); + char *b = join(base, rest, sl, 0); + if (!b) { + c->nospace = 1; + return; + } + rest += sl; + if (!*rest) { + struct stat st; + if (*b && stat_of(c, b, &st, 0) == 0) + found(c, b); + else + free(b); + return; + } + const char *slash = strchr(rest, '/'); + size_t clen = slash ? (size_t)(slash - rest) : strlen(rest); + int noesc = c->flags & GLOB_NOESCAPE; + + if (!magic(rest, clen, noesc)) { + char *comp = join("", rest, clen, 0); + if (!comp) { + free(b); + c->nospace = 1; + return; + } + unescape(comp, noesc); + char *nb = join(b, comp, strlen(comp), 0); + free(comp); + free(b); + if (!nb) { + c->nospace = 1; + return; + } + expand(c, nb, rest + clen); + free(nb); + return; + } + + char *pat = join("", rest, clen, 0); + if (!pat) { + free(b); + c->nospace = 1; + return; + } + const char *dir = *b ? b : "."; + void *d = (c->flags & GLOB_ALTDIRFUNC) ? c->g->gl_opendir(dir) + : (void *)opendir(dir); + if (!d) { + int e = errno; + if (e != ENOENT && e != ENOTDIR && + ((c->errfunc && c->errfunc(dir, e)) || (c->flags & GLOB_ERR))) + c->aborted = 1; + free(pat); + free(b); + return; + } + int fl = (noesc ? FNM_NOESCAPE : 0) | + ((c->flags & GLOB_PERIOD) ? 0 : FNM_PERIOD); + struct dirent *e; + for (;;) { + e = (c->flags & GLOB_ALTDIRFUNC) ? c->g->gl_readdir(d) + : readdir((DIR *)d); + if (!e || c->aborted || c->nospace) + break; + const char *nm = e->d_name; + /* GLOB_PERIOD lets a wildcard match a leading dot, but "." and + * ".." still only by a pattern that starts with one */ + if ((c->flags & GLOB_PERIOD) && pat[0] != '.' && + (!strcmp(nm, ".") || !strcmp(nm, ".."))) + continue; + if (fnmatch(pat, nm, fl) != 0) + continue; + char *p = join(b, nm, strlen(nm), 0); + if (!p) { + c->nospace = 1; + break; + } + if (slash) { + if (is_dir(c, p)) { + char *sub = join(p, "/", 1, 0); + if (sub) + expand(c, sub, slash + 1); + else + c->nospace = 1; + free(sub); + } + free(p); + } else { + found(c, p); + } + } + if (c->flags & GLOB_ALTDIRFUNC) + c->g->gl_closedir(d); + else + closedir((DIR *)d); + free(pat); + free(b); +} + +static int by_name(const void *a, const void *b) +{ + return strcmp(*(char *const *)a, *(char *const *)b); +} + +/* one pattern, after braces: 1 if it matched or was kept, 0 if not */ +static int glob_one(struct ctx *c, const char *pattern) +{ + size_t start = c->n; + char *owned = NULL; + const char *pat = pattern; + if ((c->flags & (GLOB_TILDE | GLOB_TILDE_CHECK)) && pat[0] == '~') { + const char *end = strchr(pat, '/'); + size_t ul = end ? (size_t)(end - pat - 1) : strlen(pat + 1); + const char *home = NULL; + if (ul == 0) { + home = getenv("HOME"); + if (!home) { + struct passwd *pw = getpwuid(getuid()); + home = pw ? pw->pw_dir : NULL; + } + } else { + char user[256]; + if (ul < sizeof user) { + memcpy(user, pat + 1, ul); + user[ul] = 0; + struct passwd *pw = getpwnam(user); + home = pw ? pw->pw_dir : NULL; + } + } + if (home) { + owned = join(home, end ? end : "", end ? strlen(end) : 0, 0); + if (!owned) { + c->nospace = 1; + return 0; + } + pat = owned; + } else if (c->flags & GLOB_TILDE_CHECK) { + return 0; + } + } + int m = magic(pat, strlen(pat), c->flags & GLOB_NOESCAPE); + if (m) + c->g->gl_flags |= GLOB_MAGCHAR; + expand(c, "", pat); + free(owned); + if (c->aborted || c->nospace) + return 0; + if (c->n == start) { + if ((c->flags & GLOB_NOCHECK) || ((c->flags & GLOB_NOMAGIC) && !m)) { + char *p = malloc(strlen(pattern) + 1); + if (p) + strcpy(p, pattern); + add(c, p); + return !c->nospace; + } + return 0; + } + if (!(c->flags & GLOB_NOSORT)) + qsort(c->v + start, c->n - start, sizeof *c->v, by_name); + return 1; +} + +/* GNU's braces: the first {a,b} at this level, each alternative expanded + * in turn and in order, nested ones by the recursion */ +static int braces(struct ctx *c, const char *p) +{ + int noesc = c->flags & GLOB_NOESCAPE; + const char *open = NULL; + for (const char *s = p; *s; s++) { + if (*s == '\\' && !noesc && s[1]) { + s++; + continue; + } + if (*s == '{') { + open = s; + break; + } + } + if (!open) + return glob_one(c, p); + /* its close, at depth 0, and the commas at depth 1 */ + int depth = 0; + const char *close = NULL; + for (const char *s = open; *s; s++) { + if (*s == '\\' && !noesc && s[1]) { + s++; + continue; + } + if (*s == '{') + depth++; + else if (*s == '}' && --depth == 0) { + close = s; + break; + } + } + if (!close) + return glob_one(c, p); /* an unclosed brace is literal */ + size_t pre = (size_t)(open - p), post = strlen(close + 1); + int any = 0; + const char *alt = open + 1; + depth = 0; + for (const char *s = open + 1; s <= close; s++) { + if (*s == '\\' && !noesc && s[1] && s < close) { + s++; + continue; + } + if (*s == '{') + depth++; + else if (*s == '}' && depth) + depth--; + else if ((*s == ',' && depth == 0) || s == close) { + size_t al = (size_t)(s - alt); + char *q = malloc(pre + al + post + 1); + if (!q) { + c->nospace = 1; + return any; + } + memcpy(q, p, pre); + memcpy(q + pre, alt, al); + memcpy(q + pre + al, close + 1, post + 1); + any |= braces(c, q); + free(q); + if (c->aborted || c->nospace) + return any; + alt = s + 1; + } + } + return any; +} + +WEAK int glob(const char *__restrict pattern, int flags, + int (*errfunc)(const char *, int), glob_t *__restrict g) +{ + if (!(flags & GLOB_APPEND)) { + g->gl_pathc = 0; + g->gl_pathv = NULL; + if (!(flags & GLOB_DOOFFS)) + g->gl_offs = 0; + } + g->gl_flags = flags & ~GLOB_MAGCHAR; + struct ctx c = { flags, errfunc, g, NULL, 0, 0, 0, 0 }; + int any = (flags & GLOB_BRACE) ? braces(&c, pattern) + : glob_one(&c, pattern); + if (c.nospace || c.aborted) { + for (size_t i = 0; i < c.n; i++) + free(c.v[i]); + free(c.v); + return c.nospace ? GLOB_NOSPACE : GLOB_ABORTED; + } + if (!any && !c.n) { + free(c.v); + return GLOB_NOMATCH; + } + /* gl_offs nulls, what was there (GLOB_APPEND), these, a null */ + size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0; + size_t old = g->gl_pathv ? g->gl_pathc : 0; + char **v = realloc(g->gl_pathv, (offs + old + c.n + 1) * sizeof *v); + if (!v) { + for (size_t i = 0; i < c.n; i++) + free(c.v[i]); + free(c.v); + return GLOB_NOSPACE; + } + if (!g->gl_pathv) + for (size_t i = 0; i < offs; i++) + v[i] = NULL; + memcpy(v + offs + old, c.v, c.n * sizeof *v); + v[offs + old + c.n] = NULL; + free(c.v); + g->gl_pathv = v; + g->gl_pathc = old + c.n; + return 0; +} + +WEAK void globfree(glob_t *g) +{ + if (!g->gl_pathv) + return; + for (size_t i = 0; i < g->gl_pathc; i++) + free(g->gl_pathv[g->gl_offs + i]); + free(g->gl_pathv); + g->gl_pathv = NULL; + g->gl_pathc = 0; +} + +WEAK int glob_pattern_p(const char *pattern, int quote) +{ + return magic(pattern, strlen(pattern), !quote); +} diff --git a/lib/libc/src/posix/inet.c b/lib/libc/src/posix/inet.c new file mode 100644 index 000000000..e7c0effaf --- /dev/null +++ b/lib/libc/src/posix/inet.c @@ -0,0 +1,260 @@ +/* Internet addresses as text, both ways, and the byte-order functions. + * + * inet_pton is strict (POSIX's: four decimal parts for IPv4, no leading + * zeros, as glibc has it); inet_aton is the old lenient one, which takes + * "127.1" and parts in octal and hex because a whole generation of + * configuration files was written for it. inet_ntop writes IPv6 as RFC + * 5952 says -- lower case, the longest run of zero groups as "::" -- and + * an IPv4-mapped address as ::ffff:a.b.c.d, as glibc does. */ +#include +#include +#include +#include + +#include "posix.h" + +#undef htonl +#undef htons +#undef ntohl +#undef ntohs + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +WEAK uint32_t htonl(uint32_t v) { return v; } +WEAK uint16_t htons(uint16_t v) { return v; } +#else +WEAK uint32_t htonl(uint32_t v) { return __builtin_bswap32(v); } +WEAK uint16_t htons(uint16_t v) { return __builtin_bswap16(v); } +#endif +WEAK uint32_t ntohl(uint32_t v) { return htonl(v); } +WEAK uint16_t ntohs(uint16_t v) { return htons(v); } + +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; + +/* "a", "a.b", "a.b.c", "a.b.c.d", each part decimal, 0-octal or 0x-hex; + * the last part fills the bytes that are left */ +WEAK int inet_aton(const char *s, struct in_addr *out) +{ + unsigned long part[4]; + int n = 0; + const char *p = s; + for (;;) { + if (!isdigit((unsigned char)*p)) + return 0; + char *end; + part[n] = strtoul(p, &end, 0); + if (end == p) + return 0; + p = end; + n++; + if (*p != '.') + break; + if (n == 4) + return 0; + p++; + } + if (*p && !isspace((unsigned char)*p)) + return 0; + unsigned long v; + switch (n) { + case 1: + if (part[0] > 0xffffffffUL) return 0; + v = part[0]; + break; + case 2: + if (part[0] > 255 || part[1] > 0xffffff) return 0; + v = part[0] << 24 | part[1]; + break; + case 3: + if (part[0] > 255 || part[1] > 255 || part[2] > 0xffff) return 0; + v = part[0] << 24 | part[1] << 16 | part[2]; + break; + default: + if (part[0] > 255 || part[1] > 255 || part[2] > 255 || part[3] > 255) + return 0; + v = part[0] << 24 | part[1] << 16 | part[2] << 8 | part[3]; + break; + } + if (out) + out->s_addr = htonl((uint32_t)v); + return 1; +} + +WEAK in_addr_t inet_addr(const char *s) +{ + struct in_addr a; + return inet_aton(s, &a) ? a.s_addr : INADDR_NONE; +} + +WEAK in_addr_t inet_network(const char *s) +{ + struct in_addr a; + return inet_aton(s, &a) ? ntohl(a.s_addr) : INADDR_NONE; +} + +WEAK char *inet_ntoa(struct in_addr a) +{ + static char buf[INET_ADDRSTRLEN]; + const unsigned char *b = (const unsigned char *)&a.s_addr; + snprintf(buf, sizeof buf, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]); + return buf; +} + +static int pton4(const char *s, unsigned char out[4]) +{ + int n = 0; + while (n < 4) { + if (!isdigit((unsigned char)*s)) + return 0; + unsigned v = 0; + int digits = 0; + while (isdigit((unsigned char)*s)) { + if (digits && v == 0) + return 0; /* a leading zero */ + v = v * 10 + (unsigned)(*s++ - '0'); + if (v > 255) + return 0; + digits++; + } + out[n++] = (unsigned char)v; + if (n < 4 && *s++ != '.') + return 0; + } + return *s == 0; +} + +static int hexval(int c) +{ + return isdigit(c) ? c - '0' : (c | 32) >= 'a' && (c | 32) <= 'f' + ? (c | 32) - 'a' + 10 : -1; +} + +static int pton6(const char *s, unsigned char out[16]) +{ + unsigned char tmp[16] = { 0 }; + int at = 0, gap = -1; + if (s[0] == ':') { + if (s[1] != ':') + return 0; + s++; + } + while (*s) { + if (*s == ':') { + if (gap >= 0) + return 0; /* a second "::" */ + gap = at; + s++; + continue; + } + /* an embedded IPv4 address, in the last four bytes */ + const char *q = s; + while (isxdigit((unsigned char)*q)) + q++; + if (*q == '.') { + if (at > 12 || !pton4(s, tmp + at)) + return 0; + at += 4; + break; + } + unsigned v = 0; + int digits = 0; + while (hexval((unsigned char)*s) >= 0) { + v = v << 4 | (unsigned)hexval((unsigned char)*s++); + if (++digits > 4) + return 0; + } + if (!digits || at > 14) + return 0; + tmp[at++] = (unsigned char)(v >> 8); + tmp[at++] = (unsigned char)v; + if (*s == ':') { + s++; + if (!*s) + return 0; /* a trailing single ':' */ + } else if (*s) + return 0; + } + if (gap >= 0) { + int move = at - gap; + if (at == 16) + return 0; /* "::" standing for nothing */ + memmove(tmp + 16 - move, tmp + gap, (size_t)move); + memset(tmp + gap, 0, (size_t)(16 - move - gap)); + } else if (at != 16) + return 0; + memcpy(out, tmp, 16); + return 1; +} + +WEAK int inet_pton(int family, const char *restrict src, void *restrict dst) +{ + if (family == AF_INET) + return pton4(src, dst); + if (family == AF_INET6) + return pton6(src, dst); + errno = EAFNOSUPPORT; + return -1; +} + +static const char *ntop6(const unsigned char *a, char *dst, socklen_t n) +{ + unsigned w[8]; + for (int i = 0; i < 8; i++) + w[i] = (unsigned)a[2 * i] << 8 | a[2 * i + 1]; + /* the longest run of zero groups, two or more, the first if tied */ + int best = -1, blen = 0; + for (int i = 0; i < 8; ) { + if (w[i]) { i++; continue; } + int j = i; + while (j < 8 && !w[j]) + j++; + if (j - i > blen) { best = i; blen = j - i; } + i = j; + } + if (blen < 2) + best = -1; + char buf[INET6_ADDRSTRLEN], *p = buf; + for (int i = 0; i < 8; i++) { + if (i == best) { + *p++ = ':'; + if (i == 0) + *p++ = ':'; + i += blen - 1; + continue; + } + /* glibc's IPv4 tails: ::ffff:a.b.c.d and ::a.b.c.d */ + if (i == 6 && best == 0 && + (blen == 6 || (blen == 5 && w[5] == 0xffff))) { + p += sprintf(p, "%u.%u.%u.%u", a[12], a[13], a[14], a[15]); + break; + } + p += sprintf(p, "%x", w[i]); + if (i < 7) + *p++ = ':'; + } + *p = 0; + if ((size_t)(p - buf) + 1 > n) { + errno = ENOSPC; + return NULL; + } + return memcpy(dst, buf, (size_t)(p - buf) + 1); +} + +WEAK const char *inet_ntop(int family, const void *restrict src, + char *restrict dst, socklen_t n) +{ + if (family == AF_INET) { + const unsigned char *b = src; + char buf[INET_ADDRSTRLEN]; + int k = snprintf(buf, sizeof buf, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]); + if ((socklen_t)k + 1 > n) { + errno = ENOSPC; + return NULL; + } + return memcpy(dst, buf, (size_t)k + 1); + } + if (family == AF_INET6) + return ntop6(src, dst, n); + errno = EAFNOSUPPORT; + return NULL; +} diff --git a/lib/libc/src/posix/langinfo.c b/lib/libc/src/posix/langinfo.c new file mode 100644 index 000000000..dcfb01fd7 --- /dev/null +++ b/lib/libc/src/posix/langinfo.c @@ -0,0 +1,63 @@ +/* and : the "C" locale's answers, which are + * strftime's and localeconv's, and no message catalogs. */ +#include +#include +#include + +#include "posix.h" + +static const char *const g_items[__NL_ITEMS] = { + [CODESET] = "UTF-8", + [D_T_FMT] = "%a %b %e %H:%M:%S %Y", [D_FMT] = "%m/%d/%y", + [T_FMT] = "%H:%M:%S", [T_FMT_AMPM] = "%I:%M:%S %p", + [AM_STR] = "AM", [PM_STR] = "PM", + [DAY_1] = "Sunday", [DAY_2] = "Monday", [DAY_3] = "Tuesday", + [DAY_4] = "Wednesday", [DAY_5] = "Thursday", [DAY_6] = "Friday", + [DAY_7] = "Saturday", + [ABDAY_1] = "Sun", [ABDAY_2] = "Mon", [ABDAY_3] = "Tue", [ABDAY_4] = "Wed", + [ABDAY_5] = "Thu", [ABDAY_6] = "Fri", [ABDAY_7] = "Sat", + [MON_1] = "January", [MON_2] = "February", [MON_3] = "March", + [MON_4] = "April", [MON_5] = "May", [MON_6] = "June", [MON_7] = "July", + [MON_8] = "August", [MON_9] = "September", [MON_10] = "October", + [MON_11] = "November", [MON_12] = "December", + [ABMON_1] = "Jan", [ABMON_2] = "Feb", [ABMON_3] = "Mar", [ABMON_4] = "Apr", + [ABMON_5] = "May", [ABMON_6] = "Jun", [ABMON_7] = "Jul", [ABMON_8] = "Aug", + [ABMON_9] = "Sep", [ABMON_10] = "Oct", [ABMON_11] = "Nov", + [ABMON_12] = "Dec", + [ERA] = "", [ERA_D_FMT] = "", [ALT_DIGITS] = "", [ERA_D_T_FMT] = "", + [ERA_T_FMT] = "", + [RADIXCHAR] = ".", [THOUSEP] = "", [YESEXPR] = "^[yY]", + [NOEXPR] = "^[nN]", [CRNCYSTR] = "-", +}; + +/* an item nobody knows is "", as POSIX asks */ +WEAK char *nl_langinfo(nl_item item) +{ + if (item < 0 || item >= __NL_ITEMS || !g_items[item]) + return (char *)""; + return (char *)g_items[item]; +} + +WEAK nl_catd catopen(const char *name, int flag) +{ + (void)name; + (void)flag; + errno = ENOENT; + return (nl_catd)-1; +} + +WEAK char *catgets(nl_catd cat, int set, int number, const char *message) +{ + (void)cat; + (void)set; + (void)number; + errno = EBADF; + return (char *)message; +} + +WEAK int catclose(nl_catd cat) +{ + (void)cat; + errno = EBADF; + return -1; +} diff --git a/lib/libc/src/posix/libgen.c b/lib/libc/src/posix/libgen.c new file mode 100644 index 000000000..7e69d2f70 --- /dev/null +++ b/lib/libc/src/posix/libgen.c @@ -0,0 +1,44 @@ +/* : basename and dirname, POSIX's -- which may write into the + * string they are given, and do: a trailing slash is cut off in place. + * That is why they take a char * and not a const char *, and why the + * GNU basename of , which never writes, is a different + * function under the same name. This library has only this one. */ +#include +#include + +/* "usr/lib/" -> "lib", "/" -> "/", "" and NULL -> "." */ +__attribute__((weak)) char *basename(char *s) +{ + if (!s || !*s) + return (char *)"."; + size_t n = strlen(s); + while (n > 1 && s[n - 1] == '/') + s[--n] = 0; + if (n == 1 && s[0] == '/') + return s; + char *slash = strrchr(s, '/'); + return slash ? slash + 1 : s; +} + +/* "usr/lib/" -> "usr", "usr" -> ".", "/usr" -> "/". A path that begins + * with EXACTLY two slashes keeps both when only the root is left -- "//" + * and "//a" give "//" -- because POSIX lets "//" name something other + * than "/" (XBD 4.13), and glibc answers so. */ +__attribute__((weak)) char *dirname(char *s) +{ + if (!s || !*s) + return (char *)"."; + size_t n = strlen(s); + while (n > 1 && s[n - 1] == '/') + n--; + while (n > 0 && s[n - 1] != '/') + n--; + if (n == 0) + return (char *)"."; + while (n > 1 && s[n - 1] == '/') + n--; + if (n == 1 && s[0] == '/' && s[1] == '/' && s[2] != '/') + n = 2; + s[n] = 0; + return s; +} diff --git a/lib/libc/src/posix/mman.c b/lib/libc/src/posix/mman.c new file mode 100644 index 000000000..8554f1b2b --- /dev/null +++ b/lib/libc/src/posix/mman.c @@ -0,0 +1,70 @@ +/* : mapped memory, and POSIX shared memory objects -- which + * are files under /dev/shm, as Linux and glibc have them. */ +#include +#include +#include + +#include "posix.h" + +WEAK void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off) +{ + return __os_mmap(addr, len, prot, flags, fd, off); +} + +WEAK int munmap(void *addr, size_t len) { return __os_munmap(addr, len); } + +WEAK int mprotect(void *addr, size_t len, int prot) +{ + return __os_mprotect(addr, len, prot); +} + +WEAK int msync(void *addr, size_t len, int flags) +{ + return __os_msync(addr, len, flags); +} + +WEAK int madvise(void *addr, size_t len, int advice) +{ + return __os_madvise(addr, len, advice); +} + +/* advice again, returning its error */ +WEAK int posix_madvise(void *addr, size_t len, int advice) +{ + int saved = errno; + int r = __os_madvise(addr, len, advice) < 0 ? errno : 0; + errno = saved; + return r == ENOSYS ? 0 : r; +} + +WEAK int mlock(const void *addr, size_t len) { return __os_mlock(addr, len, 1); } +WEAK int munlock(const void *addr, size_t len) { return __os_mlock(addr, len, 0); } + +/* "/name": one component, which becomes /dev/shm/name */ +static const char *shm_path(const char *name, char *buf) +{ + while (*name == '/') + name++; + size_t n = strlen(name); + if (!n || strchr(name, '/') || n > NAME_MAX) { + errno = EINVAL; + return NULL; + } + memcpy(buf, "/dev/shm/", 9); + memcpy(buf + 9, name, n + 1); + return buf; +} + +WEAK int shm_open(const char *name, int flags, mode_t mode) +{ + char buf[NAME_MAX + 10]; + const char *p = shm_path(name, buf); + return p ? __os_open(p, flags | O_NOFOLLOW | O_CLOEXEC, (int)mode) : -1; +} + +WEAK int shm_unlink(const char *name) +{ + char buf[NAME_MAX + 10]; + const char *p = shm_path(name, buf); + return p ? __os_unlink(p) : -1; +} diff --git a/lib/libc/src/posix/netdb.c b/lib/libc/src/posix/netdb.c new file mode 100644 index 000000000..401124d73 --- /dev/null +++ b/lib/libc/src/posix/netdb.c @@ -0,0 +1,721 @@ +/* : names to addresses and back, services, protocols. + * + * A host name is looked for in this order: a numeric address, the + * hosts file, the OS's own resolver where it has one (__os_resolve -- + * EmbLinkOS's), and DNS (resolv.c). "localhost" answers the loopback + * address even with no hosts file, as every resolver since RFC 6761. + * + * The services and protocols databases are /etc/services and + * /etc/protocols; a system without them still has the well-known ones + * IANA assigned, which are a table here, not a guess. + */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" +#include "resolv.h" + +__attribute__((weak)) int h_errno; + +/* ---- addresses found ------------------------------------------------------- */ + +#define MAXADDR 32 + +struct found { + int n; + int family[MAXADDR]; + unsigned char addr[MAXADDR][16]; + char canon[256]; +}; + +static void found_add(struct found *f, int family, const void *a) +{ + int len = family == AF_INET ? 4 : 16; + for (int i = 0; i < f->n; i++) + if (f->family[i] == family && !memcmp(f->addr[i], a, (size_t)len)) + return; + if (f->n < MAXADDR) { + f->family[f->n] = family; + memcpy(f->addr[f->n++], a, (size_t)len); + } +} + +/* ---- the hosts file ---------------------------------------------------------- */ + +static int want(int asked, int family) +{ + return asked == AF_UNSPEC || asked == family; +} + +static void hosts_by_name(const char *name, int family, struct found *f) +{ + FILE *h = fopen("/etc/hosts", "r"); + char line[512]; + while (h && fgets(line, sizeof line, h)) { + char *hash = strchr(line, '#'); + if (hash) + *hash = 0; + char *save, *a = strtok_r(line, " \t\r\n", &save); + if (!a) + continue; + unsigned char bin[16]; + int fam = inet_pton(AF_INET, a, bin) == 1 ? AF_INET + : inet_pton(AF_INET6, a, bin) == 1 ? AF_INET6 : 0; + if (!fam || !want(family, fam)) + continue; + /* the line's first name is the official one, whichever matched */ + char *official = NULL; + for (char *n; (n = strtok_r(NULL, " \t\r\n", &save)); ) { + if (!official) + official = n; + if (!strcasecmp(n, name)) { + if (!f->canon[0]) + snprintf(f->canon, sizeof f->canon, "%s", official); + found_add(f, fam, bin); + break; + } + } + } + if (h) + fclose(h); +} + +static int hosts_by_addr(int family, const void *a, char *out, size_t n) +{ + FILE *h = fopen("/etc/hosts", "r"); + char line[512]; + int ok = 0; + while (!ok && h && fgets(line, sizeof line, h)) { + char *hash = strchr(line, '#'); + if (hash) + *hash = 0; + char *save, *s = strtok_r(line, " \t\r\n", &save); + unsigned char bin[16]; + if (!s || inet_pton(family, s, bin) != 1 || + memcmp(bin, a, family == AF_INET ? 4 : 16)) + continue; + char *name = strtok_r(NULL, " \t\r\n", &save); + if (name && strlen(name) < n) { + strcpy(out, name); + ok = 1; + } + } + if (h) + fclose(h); + return ok; +} + +/* ---- by DNS ---------------------------------------------------------------- */ + +struct dnsctx { struct found *f; int family; }; + +static void take_addr(void *c, const unsigned char *rd, int n, + const unsigned char *m, int len) +{ + (void)m; (void)len; + struct dnsctx *d = c; + if ((d->family == AF_INET && n == 4) || (d->family == AF_INET6 && n == 16)) + found_add(d->f, d->family, rd); +} + +/* 0, or an EAI_ code */ +static int dns_by_name(const char *name, int family, struct found *f) +{ + static unsigned char ans[4096]; + int err = 0, any = 0; + int fams[2] = { AF_INET, AF_INET6 }; + for (int i = 0; i < 2; i++) { + if (!want(family, fams[i])) + continue; + int len = __dns_lookup(name, fams[i] == AF_INET ? DNS_A : DNS_AAAA, + ans, sizeof ans); + if (len < 0) { + if (errno != ENOENT) + err = errno == EAGAIN ? EAI_AGAIN : EAI_FAIL; + continue; + } + struct dnsctx d = { f, fams[i] }; + char canon[256] = ""; + if (__dns_records(ans, len, fams[i] == AF_INET ? DNS_A : DNS_AAAA, + canon, take_addr, &d) > 0) + any = 1; + if (canon[0] && !f->canon[0]) + snprintf(f->canon, sizeof f->canon, "%s", canon); + } + return any ? 0 : err ? err : EAI_NONAME; +} + +struct ptrctx { char *out; size_t n; int ok; }; + +static void take_ptr(void *c, const unsigned char *rd, int n, + const unsigned char *m, int len) +{ + (void)n; + struct ptrctx *p = c; + char name[256]; + if (!p->ok && __dns_name(m, len, (int)(rd - m), name, sizeof name) > 0 && + strlen(name) < p->n) { + strcpy(p->out, name); + p->ok = 1; + } +} + +static int dns_by_addr(int family, const unsigned char *a, char *out, + size_t n) +{ + char q[80]; + char *p = q; + if (family == AF_INET) + p += sprintf(p, "%u.%u.%u.%u.in-addr.arpa", a[3], a[2], a[1], a[0]); + else { + static const char hex[] = "0123456789abcdef"; + for (int i = 15; i >= 0; i--) { + *p++ = hex[a[i] & 15]; + *p++ = '.'; + *p++ = hex[a[i] >> 4]; + *p++ = '.'; + } + strcpy(p, "ip6.arpa."); + } + static unsigned char ans[4096]; + int len = __dns_lookup(q, DNS_PTR, ans, sizeof ans); + if (len < 0) + return 0; + struct ptrctx c = { out, n, 0 }; + __dns_records(ans, len, DNS_PTR, NULL, take_ptr, &c); + return c.ok; +} + +/* ---- all of it, in order ----------------------------------------------------- */ + +static int by_name(const char *name, int family, struct found *f) +{ + unsigned char bin[16]; + if (want(family, AF_INET) && inet_pton(AF_INET, name, bin) == 1) { + found_add(f, AF_INET, bin); + return 0; + } + if (want(family, AF_INET6) && inet_pton(AF_INET6, name, bin) == 1) { + found_add(f, AF_INET6, bin); + return 0; + } + hosts_by_name(name, family, f); + if (f->n) + return 0; + if (!strcasecmp(name, "localhost") || + (strlen(name) > 10 && + !strcasecmp(name + strlen(name) - 10, ".localhost"))) { + if (want(family, AF_INET)) + found_add(f, AF_INET, (unsigned char[4]){ 127, 0, 0, 1 }); + if (want(family, AF_INET6)) + found_add(f, AF_INET6, in6addr_loopback.s6_addr); + return 0; + } + /* the OS's resolver, where it has one */ + int saved = errno, asked = 0, fams[2] = { AF_INET, AF_INET6 }; + for (int i = 0; i < 2; i++) { + if (!want(family, fams[i])) + continue; + unsigned char got[8][16]; + int k = __os_resolve(name, fams[i], &got[0][0], 8); + if (k < 0 && errno == ENOSYS) + break; + asked = 1; + for (int j = 0; j < k; j++) + found_add(f, fams[i], fams[i] == AF_INET + ? &got[0][0] + 4 * j : got[j]); + } + errno = saved; + if (asked) + return f->n ? 0 : EAI_NONAME; + return dns_by_name(name, family, f); +} + +/* ---- services and protocols ------------------------------------------------ */ + +static const struct { const char *name; int port; const char *proto; } +well_known[] = { + { "echo", 7, NULL }, { "discard", 9, NULL }, { "daytime", 13, NULL }, + { "ftp-data", 20, "tcp" }, { "ftp", 21, "tcp" }, { "ssh", 22, NULL }, + { "telnet", 23, "tcp" }, { "smtp", 25, "tcp" }, { "time", 37, NULL }, + { "domain", 53, NULL }, { "bootps", 67, "udp" }, { "bootpc", 68, "udp" }, + { "tftp", 69, "udp" }, { "http", 80, NULL }, { "www", 80, NULL }, + { "pop3", 110, "tcp" }, { "sunrpc", 111, NULL }, { "ntp", 123, "udp" }, + { "imap", 143, "tcp" }, { "snmp", 161, "udp" }, { "ldap", 389, NULL }, + { "https", 443, NULL }, { "submission", 587, "tcp" }, + { "ldaps", 636, "tcp" }, { "imaps", 993, "tcp" }, { "pop3s", 995, "tcp" }, + { "mysql", 3306, "tcp" }, { "postgresql", 5432, "tcp" }, + { "git", 9418, "tcp" }, +}; + +/* the port, host order, or -1 */ +static int service_port(const char *name, const char *proto) +{ + FILE *f = fopen("/etc/services", "r"); + char line[512]; + int port = -1; + while (port < 0 && f && fgets(line, sizeof line, f)) { + char *hash = strchr(line, '#'); + if (hash) + *hash = 0; + char *save, *n = strtok_r(line, " \t\r\n", &save); + char *pp = strtok_r(NULL, " \t\r\n", &save); + if (!n || !pp) + continue; + char *slash = strchr(pp, '/'); + if (!slash || (proto && strcmp(slash + 1, proto))) + continue; + int match = !strcmp(n, name); + char *a = strtok_r(NULL, " \t\r\n", &save); + while (!match && a) { + match = !strcmp(a, name); + a = strtok_r(NULL, " \t\r\n", &save); + } + if (match) + port = atoi(pp); + } + if (f) + fclose(f); + for (size_t i = 0; port < 0 && i < sizeof well_known / sizeof *well_known; + i++) + if (!strcmp(well_known[i].name, name) && + (!proto || !well_known[i].proto || + !strcmp(well_known[i].proto, proto))) + port = well_known[i].port; + return port; +} + +static const char *service_name(int port, const char *proto) +{ + static char buf[64]; + FILE *f = fopen("/etc/services", "r"); + char line[512]; + const char *r = NULL; + while (!r && f && fgets(line, sizeof line, f)) { + char *save, *n = strtok_r(line, " \t\r\n", &save); + char *pp = strtok_r(NULL, " \t\r\n", &save); + if (!n || *n == '#' || !pp || atoi(pp) != port) + continue; + char *slash = strchr(pp, '/'); + if (slash && (!proto || !strcmp(slash + 1, proto))) { + snprintf(buf, sizeof buf, "%s", n); + r = buf; + } + } + if (f) + fclose(f); + for (size_t i = 0; !r && i < sizeof well_known / sizeof *well_known; i++) + if (well_known[i].port == port && + (!proto || !well_known[i].proto || + !strcmp(well_known[i].proto, proto))) + r = well_known[i].name; + return r; +} + +WEAK struct servent *getservbyname(const char *name, const char *proto) +{ + static struct servent s; + static char nbuf[64], pbuf[8]; + static char *aliases[1]; + int port = service_port(name, proto); + if (port < 0) + return NULL; + snprintf(nbuf, sizeof nbuf, "%s", name); + snprintf(pbuf, sizeof pbuf, "%s", proto ? proto : "tcp"); + s.s_name = nbuf; + s.s_aliases = aliases; + s.s_port = htons((uint16_t)port); + s.s_proto = pbuf; + return &s; +} + +WEAK struct servent *getservbyport(int port, const char *proto) +{ + static struct servent s; + static char nbuf[64], pbuf[8]; + static char *aliases[1]; + const char *n = service_name(ntohs((uint16_t)port), proto); + if (!n) + return NULL; + snprintf(nbuf, sizeof nbuf, "%s", n); + snprintf(pbuf, sizeof pbuf, "%s", proto ? proto : "tcp"); + s.s_name = nbuf; + s.s_aliases = aliases; + s.s_port = port; + s.s_proto = pbuf; + return &s; +} + +static const struct { const char *name; int num; } protocols[] = { + { "ip", 0 }, { "icmp", 1 }, { "igmp", 2 }, { "tcp", 6 }, { "udp", 17 }, + { "ipv6", 41 }, { "gre", 47 }, { "esp", 50 }, { "ah", 51 }, + { "ipv6-icmp", 58 }, { "sctp", 132 }, { "raw", 255 }, +}; + +static struct protoent *proto_ent(int i) +{ + static struct protoent p; + static char *aliases[1]; + p.p_name = (char *)protocols[i].name; + p.p_aliases = aliases; + p.p_proto = protocols[i].num; + return &p; +} + +WEAK struct protoent *getprotobyname(const char *name) +{ + for (size_t i = 0; i < sizeof protocols / sizeof *protocols; i++) + if (!strcmp(protocols[i].name, name)) + return proto_ent((int)i); + return NULL; +} + +WEAK struct protoent *getprotobynumber(int num) +{ + for (size_t i = 0; i < sizeof protocols / sizeof *protocols; i++) + if (protocols[i].num == num) + return proto_ent((int)i); + return NULL; +} + +/* ---- getaddrinfo ------------------------------------------------------------ */ + +static struct addrinfo *entry(int family, const unsigned char *a, int port, + int socktype, int protocol) +{ + struct addrinfo *ai = calloc(1, sizeof *ai + sizeof(struct sockaddr_in6)); + if (!ai) + return NULL; + ai->ai_family = family; + ai->ai_socktype = socktype; + ai->ai_protocol = protocol; + ai->ai_addr = (struct sockaddr *)(ai + 1); + if (family == AF_INET) { + struct sockaddr_in *s = (struct sockaddr_in *)ai->ai_addr; + s->sin_family = AF_INET; + s->sin_port = htons((uint16_t)port); + memcpy(&s->sin_addr, a, 4); + ai->ai_addrlen = sizeof *s; + } else { + struct sockaddr_in6 *s = (struct sockaddr_in6 *)ai->ai_addr; + s->sin6_family = AF_INET6; + s->sin6_port = htons((uint16_t)port); + memcpy(&s->sin6_addr, a, 16); + ai->ai_addrlen = sizeof *s; + } + return ai; +} + +WEAK void freeaddrinfo(struct addrinfo *ai) +{ + while (ai) { + struct addrinfo *next = ai->ai_next; + free(ai->ai_canonname); + free(ai); + ai = next; + } +} + +#define FLAGS (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_V4MAPPED | \ + AI_ALL | AI_ADDRCONFIG | AI_NUMERICSERV) + +WEAK int getaddrinfo(const char *restrict node, const char *restrict service, + const struct addrinfo *restrict hints, + struct addrinfo **restrict res) +{ + int flags = hints ? hints->ai_flags : AI_V4MAPPED | AI_ADDRCONFIG; + int family = hints ? hints->ai_family : AF_UNSPEC; + int socktype = hints ? hints->ai_socktype : 0; + int protocol = hints ? hints->ai_protocol : 0; + if (flags & ~FLAGS) + return EAI_BADFLAGS; + if (family != AF_UNSPEC && family != AF_INET && family != AF_INET6) + return EAI_FAMILY; + if (socktype && socktype != SOCK_STREAM && socktype != SOCK_DGRAM && + socktype != SOCK_RAW) + return EAI_SOCKTYPE; + if (!node && !service) + return EAI_NONAME; + if ((flags & AI_CANONNAME) && !node) + return EAI_BADFLAGS; + + /* the port, for each socket type asked */ + int types[3], ntypes = 0; + if (socktype) + types[ntypes++] = socktype; + else { + /* every type, as glibc answers -- raw too, unless a service NAME + * was asked, which only TCP and UDP have */ + char *end = NULL; + int numeric = service && *service && + (strtol(service, &end, 10), *end == 0); + types[ntypes++] = SOCK_STREAM; + types[ntypes++] = SOCK_DGRAM; + if (!service || !*service || numeric) + types[ntypes++] = SOCK_RAW; + } + int port[3] = { 0, 0, 0 }; + if (service && *service) { + char *end; + long p = strtol(service, &end, 10); + if (*end == 0 && p >= 0 && p <= 65535) { + for (int i = 0; i < ntypes; i++) + port[i] = (int)p; + } else if (flags & AI_NUMERICSERV) { + return EAI_NONAME; + } else { + int any = 0; + for (int i = 0; i < ntypes; i++) { + if (types[i] == SOCK_RAW) + return EAI_SERVICE; + port[i] = service_port(service, types[i] == SOCK_STREAM + ? "tcp" : "udp"); + any |= port[i] >= 0; + } + if (!any) + return EAI_SERVICE; + } + } + + struct found f; + memset(&f, 0, sizeof f); + if (!node) { + int passive = flags & AI_PASSIVE; + if (want(family, AF_INET)) + found_add(&f, AF_INET, passive ? (unsigned char[4]){ 0, 0, 0, 0 } + : (unsigned char[4]){ 127, 0, 0, 1 }); + if (want(family, AF_INET6)) + found_add(&f, AF_INET6, passive ? in6addr_any.s6_addr + : in6addr_loopback.s6_addr); + } else if (flags & AI_NUMERICHOST) { + unsigned char bin[16]; + if (want(family, AF_INET) && inet_pton(AF_INET, node, bin) == 1) + found_add(&f, AF_INET, bin); + else if (want(family, AF_INET6) && inet_pton(AF_INET6, node, bin) == 1) + found_add(&f, AF_INET6, bin); + else + return EAI_NONAME; + } else { + int search = family == AF_INET6 && (flags & AI_V4MAPPED) ? AF_UNSPEC + : family; + int e = by_name(node, search, &f); + if (e) + return e; + } + + /* AI_V4MAPPED: an IPv6 caller with only IPv4 answers gets them as + * ::ffff:a.b.c.d (all of them as well, with AI_ALL) */ + if (family == AF_INET6 && (flags & AI_V4MAPPED)) { + int has6 = 0; + for (int i = 0; i < f.n; i++) + has6 |= f.family[i] == AF_INET6; + struct found g; + memset(&g, 0, sizeof g); + memcpy(g.canon, f.canon, sizeof g.canon); + for (int i = 0; i < f.n; i++) { + if (f.family[i] == AF_INET6) + found_add(&g, AF_INET6, f.addr[i]); + else if (!has6 || (flags & AI_ALL)) { + unsigned char m[16] = { 0,0,0,0,0,0,0,0,0,0,0xff,0xff }; + memcpy(m + 12, f.addr[i], 4); + found_add(&g, AF_INET6, m); + } + } + f = g; + } + if (!f.n) + return EAI_NONAME; + + struct addrinfo *head = NULL, **tail = &head; + for (int i = 0; i < f.n; i++) + for (int t = 0; t < ntypes; t++) { + if (port[t] < 0) + continue; + int proto = protocol ? protocol + : types[t] == SOCK_STREAM ? IPPROTO_TCP + : types[t] == SOCK_DGRAM ? IPPROTO_UDP : 0; + struct addrinfo *ai = entry(f.family[i], f.addr[i], port[t], + types[t], proto); + if (!ai) { + freeaddrinfo(head); + return EAI_MEMORY; + } + *tail = ai; + tail = &ai->ai_next; + } + if (head && (flags & AI_CANONNAME)) { + head->ai_canonname = strdup(f.canon[0] ? f.canon : node); + if (!head->ai_canonname) { + freeaddrinfo(head); + return EAI_MEMORY; + } + } + *res = head; + return 0; +} + +/* glibc's words */ +WEAK const char *gai_strerror(int code) +{ + switch (code) { + case 0: return "Success"; + case EAI_BADFLAGS: return "Bad value for ai_flags"; + case EAI_NONAME: return "Name or service not known"; + case EAI_AGAIN: return "Temporary failure in name resolution"; + case EAI_FAIL: return "Non-recoverable failure in name resolution"; + case EAI_FAMILY: return "ai_family not supported"; + case EAI_SOCKTYPE: return "ai_socktype not supported"; + case EAI_SERVICE: return "Servname not supported for ai_socktype"; + case EAI_MEMORY: return "Memory allocation failure"; + case EAI_SYSTEM: return "System error"; + case EAI_OVERFLOW: return "Argument buffer overflow"; + } + return "Unknown error"; +} + +/* ---- getnameinfo ---------------------------------------------------------------- */ + +WEAK int getnameinfo(const struct sockaddr *restrict sa, socklen_t salen, + char *restrict host, socklen_t hostlen, + char *restrict serv, socklen_t servlen, int flags) +{ + const unsigned char *a; + int family = sa->sa_family, port; + if (family == AF_INET && salen >= sizeof(struct sockaddr_in)) { + const struct sockaddr_in *s = (const struct sockaddr_in *)sa; + a = (const unsigned char *)&s->sin_addr; + port = ntohs(s->sin_port); + } else if (family == AF_INET6 && salen >= sizeof(struct sockaddr_in6)) { + const struct sockaddr_in6 *s = (const struct sockaddr_in6 *)sa; + a = s->sin6_addr.s6_addr; + port = ntohs(s->sin6_port); + } else + return EAI_FAMILY; + + if (host && hostlen) { + int named = 0; + if (!(flags & NI_NUMERICHOST)) + named = hosts_by_addr(family, a, host, hostlen) || + dns_by_addr(family, a, host, hostlen); + if (!named) { + if (flags & NI_NAMEREQD) + return EAI_NONAME; + if (!inet_ntop(family, a, host, hostlen)) + return EAI_OVERFLOW; + } else if (flags & NI_NOFQDN) { + char *dot = strchr(host, '.'); + if (dot) + *dot = 0; + } + } + if (serv && servlen) { + const char *n = flags & NI_NUMERICSERV + ? NULL + : service_name(port, flags & NI_DGRAM ? "udp" + : "tcp"); + char num[8]; + if (!n) { + snprintf(num, sizeof num, "%d", port); + n = num; + } + if (strlen(n) + 1 > servlen) + return EAI_OVERFLOW; + strcpy(serv, n); + } + return 0; +} + +/* ---- the old interface ---------------------------------------------------------- */ + +static struct hostent *fill_hostent(const struct found *f, int family, + const char *name) +{ + static struct hostent h; + static char hname[256]; + static char *aliases[1]; + static char *list[MAXADDR + 1]; + static unsigned char store[MAXADDR][16]; + int k = 0; + for (int i = 0; i < f->n; i++) + if (f->family[i] == family) { + memcpy(store[k], f->addr[i], 16); + list[k] = (char *)store[k]; + k++; + } + if (!k) { + h_errno = HOST_NOT_FOUND; + return NULL; + } + list[k] = NULL; + snprintf(hname, sizeof hname, "%s", f->canon[0] ? f->canon : name); + h.h_name = hname; + h.h_aliases = aliases; + h.h_addrtype = family; + h.h_length = family == AF_INET ? 4 : 16; + h.h_addr_list = list; + return &h; +} + +WEAK struct hostent *gethostbyname2(const char *name, int family) +{ + struct found f; + memset(&f, 0, sizeof f); + int e = by_name(name, family, &f); + if (e) { + h_errno = e == EAI_AGAIN ? TRY_AGAIN : e == EAI_FAIL ? NO_RECOVERY + : HOST_NOT_FOUND; + return NULL; + } + return fill_hostent(&f, family, name); +} + +WEAK struct hostent *gethostbyname(const char *name) +{ + return gethostbyname2(name, AF_INET); +} + +WEAK struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type) +{ + char name[NI_MAXHOST]; + if ((type == AF_INET && len != 4) || (type == AF_INET6 && len != 16) || + (type != AF_INET && type != AF_INET6)) { + h_errno = NO_RECOVERY; + return NULL; + } + if (!hosts_by_addr(type, addr, name, sizeof name) && + !dns_by_addr(type, addr, name, sizeof name)) { + h_errno = HOST_NOT_FOUND; + return NULL; + } + struct found f; + memset(&f, 0, sizeof f); + found_add(&f, type, addr); + snprintf(f.canon, sizeof f.canon, "%s", name); + return fill_hostent(&f, type, name); +} + +WEAK const char *hstrerror(int code) +{ + switch (code) { + case 0: return "Resolver Error 0 (no error)"; + case HOST_NOT_FOUND: return "Unknown host"; + case TRY_AGAIN: return "Host name lookup failure"; + case NO_RECOVERY: return "Unknown server error"; + case NO_DATA: return "No address associated with name"; + } + return "Unknown resolver error"; +} + +WEAK void herror(const char *msg) +{ + if (msg && *msg) + fprintf(stderr, "%s: %s\n", msg, hstrerror(h_errno)); + else + fprintf(stderr, "%s\n", hstrerror(h_errno)); +} diff --git a/lib/libc/src/posix/poll.c b/lib/libc/src/posix/poll.c new file mode 100644 index 000000000..df5c8de08 --- /dev/null +++ b/lib/libc/src/posix/poll.c @@ -0,0 +1,120 @@ +/* poll, ppoll, select and pselect, all on the one primitive: ppoll. + * select's descriptor sets become a pollfd list and back. */ +#define _GNU_SOURCE 1 +#include +#include +#include + +#include "posix.h" + +_Static_assert(sizeof(struct pollfd) == sizeof(struct __os_pollfd), + "struct pollfd is not the seam's"); + +/* a negative timeout waits for ever */ +WEAK int poll(struct pollfd *fds, nfds_t n, int timeout_ms) +{ + struct __os_timespec t = { timeout_ms / 1000, + (long)(timeout_ms % 1000) * 1000000L }; + return __os_poll((struct __os_pollfd *)(void *)fds, n, + timeout_ms < 0 ? NULL : &t, NULL); +} + +WEAK int ppoll(struct pollfd *fds, nfds_t n, const struct timespec *timeout, + const unsigned long long *sigmask) +{ + struct __os_timespec t; + if (timeout) { + t.sec = timeout->tv_sec; + t.nsec = timeout->tv_nsec; + } + return __os_poll((struct __os_pollfd *)(void *)fds, n, + timeout ? &t : NULL, sigmask); +} + +#define RD (POLLIN | POLLHUP | POLLERR) +#define WR (POLLOUT | POLLERR) +#define EX POLLPRI + +static int do_select(int n, fd_set *r, fd_set *w, fd_set *e, + const struct __os_timespec *timeout, + const unsigned long long *sigmask) +{ + if (n < 0 || n > FD_SETSIZE) { + errno = EINVAL; + return -1; + } + struct __os_pollfd small[64], *p = small; + int count = 0; + for (int fd = 0; fd < n; fd++) + if ((r && FD_ISSET(fd, r)) || (w && FD_ISSET(fd, w)) || + (e && FD_ISSET(fd, e))) + count++; + if (count > 64 && !(p = malloc((size_t)count * sizeof *p))) { + errno = ENOMEM; + return -1; + } + int k = 0; + for (int fd = 0; fd < n; fd++) { + short ev = 0; + if (r && FD_ISSET(fd, r)) ev |= POLLIN; + if (w && FD_ISSET(fd, w)) ev |= POLLOUT; + if (e && FD_ISSET(fd, e)) ev |= POLLPRI; + if (ev) { + p[k].fd = fd; + p[k].events = ev; + p[k++].revents = 0; + } + } + int got = __os_poll(p, (unsigned long)k, timeout, sigmask); + if (got >= 0) { + got = 0; + for (int i = 0; i < k; i++) + if (p[i].revents & POLLNVAL) { + got = -1; + errno = EBADF; + break; + } + if (got == 0) { + if (r) FD_ZERO(r); + if (w) FD_ZERO(w); + if (e) FD_ZERO(e); + for (int i = 0; i < k; i++) { + int fd = p[i].fd; + short re = p[i].revents, ev = p[i].events; + if ((ev & POLLIN) && (re & RD)) { FD_SET(fd, r); got++; } + if ((ev & POLLOUT) && (re & WR)) { FD_SET(fd, w); got++; } + if ((ev & POLLPRI) && (re & EX)) { FD_SET(fd, e); got++; } + } + } + } + if (p != small) + free(p); + return got; +} + +WEAK int select(int n, fd_set *restrict r, fd_set *restrict w, + fd_set *restrict e, struct timeval *restrict timeout) +{ + struct __os_timespec t; + if (timeout) { + if (timeout->tv_sec < 0 || timeout->tv_usec < 0) { + errno = EINVAL; + return -1; + } + t.sec = timeout->tv_sec + timeout->tv_usec / 1000000; + t.nsec = (timeout->tv_usec % 1000000) * 1000L; + } + return do_select(n, r, w, e, timeout ? &t : NULL, NULL); +} + +WEAK int pselect(int n, fd_set *restrict r, fd_set *restrict w, + fd_set *restrict e, const struct timespec *restrict timeout, + const unsigned long long *restrict sigmask) +{ + struct __os_timespec t; + if (timeout) { + t.sec = timeout->tv_sec; + t.nsec = timeout->tv_nsec; + } + return do_select(n, r, w, e, timeout ? &t : NULL, sigmask); +} diff --git a/lib/libc/src/posix/popen.c b/lib/libc/src/posix/popen.c new file mode 100644 index 000000000..f3f94ceff --- /dev/null +++ b/lib/libc/src/posix/popen.c @@ -0,0 +1,108 @@ +/* popen and pclose: a command's standard output or input as a stream, + * on a pipe and posix_spawn -- so they work where fork does not. + * + * The child must not inherit the pipes of the OTHER popen streams still + * open, or a reader waiting for one command's end of file waits for + * every later command too; POSIX says so, and the list of open ones is + * kept for exactly that. */ +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" +#include "../internal/lock.h" + +struct child { FILE *f; int fd; pid_t pid; struct child *next; }; + +static struct child *open_children; +static __lock_t children_lock = LOCK_INIT; + +/* "r" or "w", and 'e' for close-on-exec as glibc takes it */ +WEAK FILE *popen(const char *cmd, const char *mode) +{ + int reading = mode[0] == 'r'; + if ((mode[0] != 'r' && mode[0] != 'w') || + (mode[1] && mode[1] != 'e')) { + errno = EINVAL; + return NULL; + } + int p[2]; + if (pipe2(p, O_CLOEXEC) < 0) + return NULL; + int mine = reading ? p[0] : p[1], theirs = reading ? p[1] : p[0]; + struct child *c = malloc(sizeof *c); + if (!c) + goto fail; + + posix_spawn_file_actions_t fa; + posix_spawn_file_actions_init(&fa); + int e = posix_spawn_file_actions_adddup2(&fa, theirs, reading ? 1 : 0); + __lock(&children_lock); + for (struct child *o = open_children; o && !e; o = o->next) + if (o->fd != (reading ? 1 : 0)) + e = posix_spawn_file_actions_addclose(&fa, o->fd); + char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL }; + extern char **environ; + pid_t pid = -1; + if (!e) + e = posix_spawn(&pid, _PATH_BSHELL, &fa, NULL, argv, environ); + __unlock(&children_lock); + posix_spawn_file_actions_destroy(&fa); + if (e) { + free(c); + errno = e; + goto fail; + } + close(theirs); + if (!(mode[1] == 'e')) + fcntl(mine, F_SETFD, 0); + FILE *f = fdopen(mine, reading ? "r" : "w"); + if (!f) { + close(mine); + waitpid(pid, NULL, 0); + free(c); + return NULL; + } + c->f = f; + c->fd = mine; + c->pid = pid; + __lock(&children_lock); + c->next = open_children; + open_children = c; + __unlock(&children_lock); + return f; +fail: + close(p[0]); + close(p[1]); + return NULL; +} + +/* the command's status, as waitpid reports it; -1 for a stream popen + * did not open */ +WEAK int pclose(FILE *f) +{ + __lock(&children_lock); + struct child **pp = &open_children, *c = NULL; + while (*pp && (*pp)->f != f) + pp = &(*pp)->next; + if (*pp) { + c = *pp; + *pp = c->next; + } + __unlock(&children_lock); + if (!c) { + errno = ECHILD; + return -1; + } + fclose(f); + int st; + pid_t r; + while ((r = waitpid(c->pid, &st, 0)) < 0 && errno == EINTR) + ; + free(c); + return r < 0 ? -1 : st; +} diff --git a/lib/libc/src/posix/posix.h b/lib/libc/src/posix/posix.h new file mode 100644 index 000000000..f027d5c2c --- /dev/null +++ b/lib/libc/src/posix/posix.h @@ -0,0 +1,59 @@ +/* What the POSIX layer's sources share. + * + * Every public function in src/posix is WEAK. None of POSIX's names is + * reserved to the implementation the way C's are -- a C program may have + * its own `read`, `link` or `index` -- and these files hold many + * functions each, so a program that calls `open` pulls in fd.c's `read` + * too. Weak, the program's own definition wins rather than colliding. + */ +#ifndef EMBLIBC_POSIX_H +#define EMBLIBC_POSIX_H + +#include +#include +#include + +#include "../../os/backend.h" + +#define WEAK __attribute__((weak)) + +#ifndef AT_FDCWD +#define AT_FDCWD (-100) +#endif + +/* The path an *at function means by (dirfd, path): path itself when it + * is absolute or dirfd is AT_FDCWD, else the directory dirfd is open on + * with path after it, in buf (PATH_MAX bytes). NULL with errno when the + * OS cannot say where dirfd is (ENOSYS) or the result is too long. */ +static inline const char *__at_path(int dirfd, const char *path, char *buf) +{ + if (!path || path[0] == '/' || dirfd == AT_FDCWD) + return path; + if (__os_fdpath(dirfd, buf, PATH_MAX) < 0) + return 0; + size_t d = strlen(buf), p = strlen(path); + if (!*path) + return buf; + if (d + 1 + p + 1 > PATH_MAX) { + errno = ENAMETOOLONG; + return 0; + } + if (d == 0 || buf[d - 1] != '/') + buf[d++] = '/'; + memcpy(buf + d, path, p + 1); + return buf; +} + +/* Whether a backend's *at primitive answered ENOSYS -- the cue to + * resolve (dirfd, path) here and call the plain form instead. errno is + * restored, so the fallback starts clean. */ +static inline int __at_absent(int r, int saved) +{ + if (r < 0 && errno == ENOSYS) { + errno = saved; + return 1; + } + return 0; +} + +#endif diff --git a/lib/libc/src/posix/proc.c b/lib/libc/src/posix/proc.c new file mode 100644 index 000000000..7d1219d26 --- /dev/null +++ b/lib/libc/src/posix/proc.c @@ -0,0 +1,459 @@ +/* Processes: their ids, fork and the exec family, waiting, system, the + * sleeps and alarm. On a kernel that starts processes rather than + * copying them (EmbLinkOS) fork fails with ENOSYS and system, popen and + * posix_spawn still work: they are built on spawn, not on fork. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +/* ---- ids ------------------------------------------------------------------- */ + +WEAK pid_t getpid(void) { return (pid_t)__os_getid(__OS_ID_PID); } +WEAK pid_t getppid(void) { return (pid_t)__os_getid(__OS_ID_PPID); } +WEAK uid_t getuid(void) { return (uid_t)__os_getid(__OS_ID_UID); } +WEAK uid_t geteuid(void) { return (uid_t)__os_getid(__OS_ID_EUID); } +WEAK gid_t getgid(void) { return (gid_t)__os_getid(__OS_ID_GID); } +WEAK gid_t getegid(void) { return (gid_t)__os_getid(__OS_ID_EGID); } + +WEAK int setuid(uid_t id) { return __os_setid(__OS_ID_UID, id); } +WEAK int seteuid(uid_t id) { return __os_setid(__OS_ID_EUID, id); } +WEAK int setgid(gid_t id) { return __os_setid(__OS_ID_GID, id); } +WEAK int setegid(gid_t id) { return __os_setid(__OS_ID_EGID, id); } + +/* setreuid(r, e): either may be -1, for "leave it" */ +WEAK int setreuid(uid_t r, uid_t e) +{ + if (r != (uid_t)-1 && __os_setid(__OS_ID_UID, r) < 0) + return -1; + return e != (uid_t)-1 ? __os_setid(__OS_ID_EUID, e) : 0; +} + +WEAK int setregid(gid_t r, gid_t e) +{ + if (r != (gid_t)-1 && __os_setid(__OS_ID_GID, r) < 0) + return -1; + return e != (gid_t)-1 ? __os_setid(__OS_ID_EGID, e) : 0; +} + +WEAK int getgroups(int n, gid_t list[]) +{ + return __os_getgroups(n, (unsigned *)list); +} + +WEAK pid_t getpgid(pid_t pid) { return __os_getpgid(pid); } +WEAK pid_t getpgrp(void) { return __os_getpgid(0); } +WEAK int setpgid(pid_t pid, pid_t g) { return __os_setpgid(pid, g); } +WEAK int setpgrp(void) { return __os_setpgid(0, 0); } +WEAK pid_t getsid(pid_t pid) { return __os_getsid(pid); } +WEAK pid_t setsid(void) { return __os_setsid(); } + +/* The login name: LOGNAME as the session set it, which is what POSIX's + * getlogin answers on a system with no utmp to ask. NULL with ENXIO when + * there is none. */ +WEAK char *getlogin(void) +{ + char *s = getenv("LOGNAME"); + if (!s || !*s) + s = getenv("USER"); + if (!s || !*s) + errno = ENXIO; + return s && *s ? s : NULL; +} + +WEAK int getlogin_r(char *name, size_t n) +{ + char *s = getlogin(); + if (!s) + return errno; + size_t k = strlen(s); + if (k + 1 > n) + return ERANGE; + memcpy(name, s, k + 1); + return 0; +} + +/* ---- fork and exec --------------------------------------------------------- */ + +/* pthread_atfork's handlers, when anything registered one. A pointer + * pthread_atfork sets, so that a program which never names a pthread + * function does not link the thread library to fork -- and not a weak + * reference, which left undefined has no PC-relative address in a + * position-independent program (EmbLinkOS links its apps PIE). */ +void (*__atfork_hook)(int phase); + +WEAK pid_t fork(void) +{ + void (*hook)(int) = __atfork_hook; + if (hook) + hook(0); + pid_t p = __os_fork(); + if (hook && p >= 0) + hook(p == 0 ? 2 : 1); + return p; +} + +/* vfork's one promise is that the child only execs or _exits, which a + * fork keeps too */ +WEAK pid_t vfork(void) { return __os_fork(); } + +WEAK int execve(const char *path, char *const argv[], char *const envp[]) +{ + return __os_execve(path, argv, envp); +} + +WEAK int execv(const char *path, char *const argv[]) +{ + extern char **environ; + return __os_execve(path, argv, environ); +} + +/* A file that is not a program the kernel recognises (ENOEXEC) is a + * shell script, as POSIX's exec*p have it: run by /bin/sh with the file + * as its first argument. */ +static int exec_script(const char *path, char *const argv[], + char *const envp[]) +{ + int n = 0; + while (argv[n]) + n++; + char **v = malloc((size_t)(n + 2) * sizeof *v); + if (!v) + return -1; + v[0] = (char *)"sh"; + v[1] = (char *)path; + for (int i = 1; i <= n; i++) + v[i + 1] = argv[i]; + if (n == 0) + v[2] = NULL; + __os_execve(_PATH_BSHELL, v, envp); + free(v); + return -1; +} + +/* The search POSIX describes: each PATH element in turn, an empty one + * meaning the current directory; an EACCES is remembered and reported + * if nothing later is found, so a directory the user cannot read does + * not hide a program further along. */ +WEAK int execvpe(const char *file, char *const argv[], char *const envp[]) +{ + if (!file || !*file) { + errno = ENOENT; + return -1; + } + if (strchr(file, '/')) { + __os_execve(file, argv, envp); + if (errno == ENOEXEC) + exec_script(file, argv, envp); + return -1; + } + const char *path = getenv("PATH"); + if (!path) + path = "/usr/local/bin:/bin:/usr/bin"; + size_t fl = strlen(file); + if (fl > NAME_MAX) { + errno = ENAMETOOLONG; + return -1; + } + int seen_eacces = 0; + char buf[PATH_MAX]; + for (const char *p = path;; p++) { + const char *end = strchr(p, ':'); + size_t dl = end ? (size_t)(end - p) : strlen(p); + if (dl + 1 + fl + 1 <= sizeof buf) { + memcpy(buf, p, dl); + size_t at = dl; + if (dl) + buf[at++] = '/'; + memcpy(buf + at, file, fl + 1); + __os_execve(buf, argv, envp); + if (errno == ENOEXEC) { + exec_script(buf, argv, envp); + return -1; + } + if (errno == EACCES) + seen_eacces = 1; + else if (errno != ENOENT && errno != ENOTDIR && errno != ENOSYS) + return -1; + else if (errno == ENOSYS) + return -1; + } + if (!end) + break; + p = end; + } + errno = seen_eacces ? EACCES : ENOENT; + return -1; +} + +WEAK int execvp(const char *file, char *const argv[]) +{ + extern char **environ; + return execvpe(file, argv, environ); +} + +/* the l forms: their arguments, counted, into a vector on the stack */ +static int count_args(const char *first, va_list ap) +{ + (void)first; + int n = 1; + va_list c; + va_copy(c, ap); + while (va_arg(c, char *)) + n++; + va_end(c); + return n; +} + +WEAK int execl(const char *path, const char *arg, ...) +{ + va_list ap; + va_start(ap, arg); + int n = count_args(arg, ap); + char *v[n + 1]; + v[0] = (char *)arg; + for (int i = 1; i <= n; i++) + v[i] = va_arg(ap, char *); + va_end(ap); + return execv(path, v); +} + +WEAK int execlp(const char *file, const char *arg, ...) +{ + va_list ap; + va_start(ap, arg); + int n = count_args(arg, ap); + char *v[n + 1]; + v[0] = (char *)arg; + for (int i = 1; i <= n; i++) + v[i] = va_arg(ap, char *); + va_end(ap); + return execvp(file, v); +} + +/* the environment comes after the terminating NULL */ +WEAK int execle(const char *path, const char *arg, ...) +{ + va_list ap; + va_start(ap, arg); + int n = count_args(arg, ap); + char *v[n + 1]; + v[0] = (char *)arg; + for (int i = 1; i <= n; i++) + v[i] = va_arg(ap, char *); + char **envp = va_arg(ap, char **); + va_end(ap); + return execve(path, v, envp); +} + +/* ---- waiting ------------------------------------------------------------- */ + +WEAK pid_t waitpid(pid_t pid, int *status, int options) +{ + return __os_waitpid(pid, status, options); +} + +WEAK pid_t wait(int *status) { return __os_waitpid(-1, status, 0); } + +/* wait3 and wait4 report usage the seam has no per-child form of: what + * there is, getrusage's CHILDREN, which a caller reaping one child at a + * time reads as that child's -- and zeros where even that is not had */ +WEAK pid_t wait4(pid_t pid, int *status, int options, struct rusage *ru) +{ + pid_t r = __os_waitpid(pid, status, options); + if (r > 0 && ru) { + struct __os_rusage k; + memset(ru, 0, sizeof *ru); + int saved = errno; + if (__os_getrusage(-1, &k) == 0) { + ru->ru_utime.tv_sec = (time_t)(k.utime_us / 1000000); + ru->ru_utime.tv_usec = (suseconds_t)(k.utime_us % 1000000); + ru->ru_stime.tv_sec = (time_t)(k.stime_us / 1000000); + ru->ru_stime.tv_usec = (suseconds_t)(k.stime_us % 1000000); + ru->ru_maxrss = k.maxrss_kb; + } + errno = saved; + } + return r; +} + +WEAK pid_t wait3(int *status, int options, struct rusage *ru) +{ + return wait4(-1, status, options, ru); +} + +/* ---- system ----------------------------------------------------------------- + * + * POSIX's, on posix_spawn: /bin/sh -c cmd, with SIGINT and SIGQUIT + * ignored and SIGCHLD blocked in the caller while the command runs, so a + * ^C goes to the command and the wait is not stolen by a handler. Where + * there are no signals those steps fail quietly and the rest stands. + * system(NULL) asks whether there is a shell to run commands with. */ +WEAK int system(const char *cmd) +{ + if (!cmd) + return __os_access(_PATH_BSHELL, X_OK) == 0; + extern char **environ; + struct sigaction ign, oint, oquit; + sigset_t chld, omask; + memset(&ign, 0, sizeof ign); + ign.sa_handler = SIG_IGN; + /* Only where the kernel delivers signals: elsewhere nothing can + * interrupt the wait, a child inherits no dispositions, and a spawn + * asked to set them is refused (EmbLinkOS makes no promises about + * signals it does not have). */ + int saved = errno; + struct __os_sigaction probe; + int kernel = __os_sigaction(SIGINT, NULL, &probe) == 0; + int have_sig = kernel && sigaction(SIGINT, &ign, &oint) == 0; + if (have_sig) + sigaction(SIGQUIT, &ign, &oquit); + sigemptyset(&chld); + sigaddset(&chld, SIGCHLD); + int have_mask = kernel && sigprocmask(SIG_BLOCK, &chld, &omask) == 0; + errno = saved; + + posix_spawnattr_t attr; + posix_spawnattr_init(&attr); + short flags = 0; + if (have_mask) { + posix_spawnattr_setsigmask(&attr, &omask); + flags |= POSIX_SPAWN_SETSIGMASK; + } + if (have_sig) { + sigset_t def; + sigemptyset(&def); + if (oint.sa_handler != SIG_IGN) + sigaddset(&def, SIGINT); + if (oquit.sa_handler != SIG_IGN) + sigaddset(&def, SIGQUIT); + posix_spawnattr_setsigdefault(&attr, &def); + flags |= POSIX_SPAWN_SETSIGDEF; + } + posix_spawnattr_setflags(&attr, flags); + char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL }; + pid_t pid; + int st = -1; + int err = posix_spawn(&pid, _PATH_BSHELL, NULL, &attr, argv, environ); + posix_spawnattr_destroy(&attr); + if (err == 0) { + while (__os_waitpid(pid, &st, 0) < 0) { + if (errno != EINTR) { + st = -1; + break; + } + } + } else if (err == ENOENT || err == EACCES || err == ENOEXEC) { + st = 127 << 8; /* as if the shell had run and failed */ + } else { + errno = err; + } + if (have_sig) { + sigaction(SIGINT, &oint, NULL); + sigaction(SIGQUIT, &oquit, NULL); + } + if (have_mask) + sigprocmask(SIG_SETMASK, &omask, NULL); + return st; +} + +/* ---- sleeping ---------------------------------------------------------------- */ + +WEAK int nanosleep(const struct timespec *req, struct timespec *rem) +{ + if (req->tv_nsec < 0 || req->tv_nsec >= 1000000000L || req->tv_sec < 0) { + errno = EINVAL; + return -1; + } + struct __os_timespec k = { req->tv_sec, req->tv_nsec }, kr; + int r = __os_clock_nanosleep(CLOCK_MONOTONIC, 0, &k, rem ? &kr : NULL); + if (r < 0 && errno == ENOSYS) { + /* no clock_nanosleep: the plain sleep every backend has */ + long long ns = (long long)req->tv_sec * 1000000000LL + req->tv_nsec; + return __os_sleep_ns((long)ns); + } + if (r < 0 && rem && errno == EINTR) { + rem->tv_sec = (time_t)kr.sec; + rem->tv_nsec = kr.nsec; + } + return r; +} + +/* the seconds left when a signal cut it short */ +WEAK unsigned sleep(unsigned seconds) +{ + struct timespec t = { (time_t)seconds, 0 }, rem = { 0, 0 }; + if (nanosleep(&t, &rem) < 0 && errno == EINTR) + return (unsigned)rem.tv_sec + (rem.tv_nsec > 0); + return 0; +} + +WEAK int usleep(useconds_t us) +{ + struct timespec t = { (time_t)(us / 1000000), (long)(us % 1000000) * 1000 }; + return nanosleep(&t, NULL); +} + +/* until a signal is handled: sigsuspend with the mask as it is */ +WEAK int pause(void) +{ + sigset_t cur; + if (__os_sigprocmask(SIG_BLOCK, NULL, &cur) < 0) + return -1; + return __os_sigsuspend(&cur); +} + +/* the real-time interval timer; the seconds that were left on it */ +WEAK unsigned alarm(unsigned seconds) +{ + struct __os_itimer v = { 0, 0, seconds, 0 }, old = { 0, 0, 0, 0 }; + if (__os_setitimer(ITIMER_REAL, &v, &old) < 0) + return 0; + return (unsigned)old.vsec + (old.vusec >= 500000); +} + +WEAK useconds_t ualarm(useconds_t value, useconds_t interval) +{ + struct __os_itimer v = { interval / 1000000, interval % 1000000, + value / 1000000, value % 1000000 }, old; + if (__os_setitimer(ITIMER_REAL, &v, &old) < 0) + return 0; + return (useconds_t)(old.vsec * 1000000 + old.vusec); +} + +/* ---- detaching from the terminal ------------------------------------------ + * + * daemon is fork, setsid, and the standard descriptors to /dev/null -- + * so on a kernel with no fork it fails with ENOSYS before changing + * anything. */ +WEAK int daemon(int nochdir, int noclose) +{ + pid_t p = __os_fork(); + if (p < 0) + return -1; + if (p > 0) + _exit(0); + if (__os_setsid() < 0) + return -1; + if (!nochdir) + __os_chdir("/"); + if (!noclose) { + int fd = __os_open(_PATH_DEVNULL, __OS_O_RDWR, 0); + if (fd >= 0) { + for (int i = 0; i < 3; i++) + if (fd != i) + __os_dup2(fd, i, 0); + if (fd > 2) + __os_close(fd); + } + } + return 0; +} diff --git a/lib/libc/src/posix/pty.c b/lib/libc/src/posix/pty.c new file mode 100644 index 000000000..7ecc4f17c --- /dev/null +++ b/lib/libc/src/posix/pty.c @@ -0,0 +1,114 @@ +/* Pseudo-terminals: POSIX's posix_openpt, grantpt, unlockpt and ptsname, + * and BSD's openpty, forkpty and login_tty over them. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include + +#include "posix.h" + +WEAK int posix_openpt(int flags) { return __os_openpt(flags); } + +/* Where the kernel makes the other side with the right owner and mode -- + * Linux's devpts does -- there is nothing to grant; the call still + * checks that fd is a controlling side. */ +WEAK int grantpt(int fd) +{ + char name[64]; + if (__os_ptsname(fd, name, sizeof name) < 0) { + if (errno == ENOTTY) + errno = EINVAL; + return -1; + } + return 0; +} + +WEAK int unlockpt(int fd) { return __os_unlockpt(fd); } + +WEAK int ptsname_r(int fd, char *buf, size_t n) +{ + int saved = errno; + if (__os_ptsname(fd, buf, n) == 0) + return 0; + int e = errno; + errno = saved; + return e; +} + +WEAK char *ptsname(int fd) +{ + static char buf[64]; + int e = ptsname_r(fd, buf, sizeof buf); + if (e) { + errno = e; + return NULL; + } + return buf; +} + +WEAK int openpty(int *master, int *slave, char *name, const struct termios *t, + const struct winsize *ws) +{ + char buf[64]; + int m = posix_openpt(O_RDWR | O_NOCTTY); + if (m < 0) + return -1; + if (grantpt(m) < 0 || unlockpt(m) < 0 || ptsname_r(m, buf, sizeof buf)) { + close(m); + return -1; + } + int s = open(buf, O_RDWR | O_NOCTTY); + if (s < 0) { + close(m); + return -1; + } + if (t) + tcsetattr(s, TCSANOW, t); + if (ws) + ioctl(s, TIOCSWINSZ, ws); + if (name) + strcpy(name, buf); + *master = m; + *slave = s; + return 0; +} + +/* the terminal fd as this process's controlling one, and its standard + * input, output and error */ +WEAK int login_tty(int fd) +{ + setsid(); + if (ioctl(fd, TIOCSCTTY, 0) < 0) + return -1; + for (int i = 0; i < 3; i++) + if (dup2(fd, i) < 0) + return -1; + if (fd > 2) + close(fd); + return 0; +} + +WEAK pid_t forkpty(int *master, char *name, const struct termios *t, + const struct winsize *ws) +{ + int m, s; + if (openpty(&m, &s, name, t, ws) < 0) + return -1; + pid_t pid = fork(); + if (pid < 0) { + close(m); + close(s); + return -1; + } + if (pid == 0) { + close(m); + if (login_tty(s) < 0) + _exit(1); + return 0; + } + close(s); + *master = m; + return pid; +} diff --git a/lib/libc/src/posix/pwd.c b/lib/libc/src/posix/pwd.c new file mode 100644 index 000000000..4edc77d0f --- /dev/null +++ b/lib/libc/src/posix/pwd.c @@ -0,0 +1,321 @@ +/* and : the user and group databases, /etc/passwd and + * /etc/group. A system with neither file -- EmbLinkOS has no users -- + * finds nobody, which is the answer the databases give there. + * + * The _r forms parse into the caller's buffer; the others into one + * static buffer each, as POSIX permits. */ +#include +#include +#include +#include +#include + +#include "posix.h" + +/* ---- users ------------------------------------------------------------------ */ + +/* "name:passwd:uid:gid:gecos:dir:shell" copied into buf and split there */ +static int parse_pw(const char *line, struct passwd *pw, char *buf, size_t n) +{ + size_t len = strlen(line); + while (len && (line[len - 1] == '\n' || line[len - 1] == '\r')) + len--; + if (len + 1 > n) + return ERANGE; + memcpy(buf, line, len); + buf[len] = 0; + char *f[7]; + char *p = buf; + for (int i = 0; i < 7; i++) { + f[i] = p; + char *c = i < 6 ? strchr(p, ':') : NULL; + if (i < 6 && !c) + return -1; /* not a passwd line */ + if (c) { + *c = 0; + p = c + 1; + } + } + pw->pw_name = f[0]; + pw->pw_passwd = f[1]; + pw->pw_uid = (uid_t)strtoul(f[2], NULL, 10); + pw->pw_gid = (gid_t)strtoul(f[3], NULL, 10); + pw->pw_gecos = f[4]; + pw->pw_dir = f[5]; + pw->pw_shell = f[6]; + return 0; +} + +/* name or uid, whichever is asked */ +static int find_pw(const char *name, uid_t uid, struct passwd *pw, char *buf, + size_t n, struct passwd **result) +{ + *result = NULL; + int saved = errno; + FILE *f = fopen("/etc/passwd", "r"); + if (!f) { + errno = saved; + return 0; /* no database: nobody */ + } + char line[1024]; + int r = 0; + while (fgets(line, sizeof line, f)) { + if (line[0] == '#' || line[0] == '\n') + continue; + int e = parse_pw(line, pw, buf, n); + if (e == ERANGE) { + r = ERANGE; + break; + } + if (e) + continue; + if (name ? !strcmp(pw->pw_name, name) : pw->pw_uid == uid) { + *result = pw; + break; + } + } + fclose(f); + errno = saved; + return r; +} + +WEAK int getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t n, + struct passwd **result) +{ + return find_pw(name, 0, pw, buf, n, result); +} + +WEAK int getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t n, + struct passwd **result) +{ + return find_pw(NULL, uid, pw, buf, n, result); +} + +static struct passwd g_pw; +static char g_pwbuf[1024]; + +WEAK struct passwd *getpwnam(const char *name) +{ + struct passwd *r; + int e = getpwnam_r(name, &g_pw, g_pwbuf, sizeof g_pwbuf, &r); + if (e) + errno = e; + return r; +} + +WEAK struct passwd *getpwuid(uid_t uid) +{ + struct passwd *r; + int e = getpwuid_r(uid, &g_pw, g_pwbuf, sizeof g_pwbuf, &r); + if (e) + errno = e; + return r; +} + +static FILE *g_pwent; + +WEAK void setpwent(void) +{ + if (g_pwent) + rewind(g_pwent); +} + +WEAK void endpwent(void) +{ + if (g_pwent) + fclose(g_pwent); + g_pwent = NULL; +} + +WEAK struct passwd *getpwent(void) +{ + if (!g_pwent && !(g_pwent = fopen("/etc/passwd", "r"))) + return NULL; + char line[1024]; + while (fgets(line, sizeof line, g_pwent)) + if (line[0] != '#' && + parse_pw(line, &g_pw, g_pwbuf, sizeof g_pwbuf) == 0) + return &g_pw; + return NULL; +} + +/* ---- groups ------------------------------------------------------------------- */ + +/* "name:passwd:gid:mem1,mem2" into buf: the strings, then the aligned + * member vector after them */ +static int parse_gr(const char *line, struct group *g, char *buf, size_t n) +{ + size_t len = strlen(line); + while (len && (line[len - 1] == '\n' || line[len - 1] == '\r')) + len--; + size_t members = 1; + for (size_t i = 0; i < len; i++) + members += line[i] == ','; + size_t strs = (len + 1 + sizeof(char *) - 1) & ~(sizeof(char *) - 1); + if (strs + (members + 1) * sizeof(char *) > n) + return ERANGE; + memcpy(buf, line, len); + buf[len] = 0; + char *f[4], *p = buf; + for (int i = 0; i < 4; i++) { + f[i] = p; + char *c = i < 3 ? strchr(p, ':') : NULL; + if (i < 3 && !c) + return -1; + if (c) { + *c = 0; + p = c + 1; + } + } + char **mem = (char **)(void *)(buf + strs); + size_t k = 0; + for (char *m = f[3]; *m; ) { + mem[k++] = m; + char *c = strchr(m, ','); + if (!c) + break; + *c = 0; + m = c + 1; + } + mem[k] = NULL; + g->gr_name = f[0]; + g->gr_passwd = f[1]; + g->gr_gid = (gid_t)strtoul(f[2], NULL, 10); + g->gr_mem = mem; + return 0; +} + +static int find_gr(const char *name, gid_t gid, struct group *g, char *buf, + size_t n, struct group **result) +{ + *result = NULL; + int saved = errno; + FILE *f = fopen("/etc/group", "r"); + if (!f) { + errno = saved; + return 0; + } + char line[1024]; + int r = 0; + while (fgets(line, sizeof line, f)) { + if (line[0] == '#' || line[0] == '\n') + continue; + int e = parse_gr(line, g, buf, n); + if (e == ERANGE) { + r = ERANGE; + break; + } + if (e) + continue; + if (name ? !strcmp(g->gr_name, name) : g->gr_gid == gid) { + *result = g; + break; + } + } + fclose(f); + errno = saved; + return r; +} + +WEAK int getgrnam_r(const char *name, struct group *g, char *buf, size_t n, + struct group **result) +{ + return find_gr(name, 0, g, buf, n, result); +} + +WEAK int getgrgid_r(gid_t gid, struct group *g, char *buf, size_t n, + struct group **result) +{ + return find_gr(NULL, gid, g, buf, n, result); +} + +static struct group g_gr; +static char g_grbuf[2048]; + +WEAK struct group *getgrnam(const char *name) +{ + struct group *r; + int e = getgrnam_r(name, &g_gr, g_grbuf, sizeof g_grbuf, &r); + if (e) + errno = e; + return r; +} + +WEAK struct group *getgrgid(gid_t gid) +{ + struct group *r; + int e = getgrgid_r(gid, &g_gr, g_grbuf, sizeof g_grbuf, &r); + if (e) + errno = e; + return r; +} + +static FILE *g_grent; + +WEAK void setgrent(void) +{ + if (g_grent) + rewind(g_grent); +} + +WEAK void endgrent(void) +{ + if (g_grent) + fclose(g_grent); + g_grent = NULL; +} + +WEAK struct group *getgrent(void) +{ + if (!g_grent && !(g_grent = fopen("/etc/group", "r"))) + return NULL; + char line[1024]; + while (fgets(line, sizeof line, g_grent)) + if (line[0] != '#' && + parse_gr(line, &g_gr, g_grbuf, sizeof g_grbuf) == 0) + return &g_gr; + return NULL; +} + +/* The groups user is in: group first, then each group that lists the + * user, at most *n of them; the count, or -1 with *n the count needed. */ +WEAK int getgrouplist(const char *user, gid_t group, gid_t *groups, int *n) +{ + int k = 0, cap = *n; + if (k < cap) + groups[k] = group; + k++; + FILE *f = fopen("/etc/group", "r"); + char line[1024], buf[2048]; + struct group g; + while (f && fgets(line, sizeof line, f)) { + if (parse_gr(line, &g, buf, sizeof buf) || g.gr_gid == group) + continue; + for (char **m = g.gr_mem; *m; m++) + if (!strcmp(*m, user)) { + if (k < cap) + groups[k] = g.gr_gid; + k++; + break; + } + } + if (f) + fclose(f); + int fit = k <= cap; + *n = k; + return fit ? k : -1; +} + +WEAK int setgroups(size_t n, const gid_t *groups) +{ + return __os_setgroups((int)n, (const unsigned *)groups); +} + +WEAK int initgroups(const char *user, gid_t group) +{ + gid_t list[NGROUPS_MAX]; + int n = NGROUPS_MAX; + if (getgrouplist(user, group, list, &n) < 0) + n = NGROUPS_MAX; + return setgroups((size_t)n, list); +} diff --git a/lib/libc/src/posix/regex.c b/lib/libc/src/posix/regex.c new file mode 100644 index 000000000..312655446 --- /dev/null +++ b/lib/libc/src/posix/regex.c @@ -0,0 +1,847 @@ +/* : a parser to a tree, and a backtracking matcher that finds + * POSIX's match -- the leftmost, and of those the longest. + * + * ---- matching ---------------------------------------------------------------- + * + * The matcher walks the tree in continuation-passing style: matching a + * node is told what must follow it (a chain of `struct k` on the C + * stack), so alternation and repetition backtrack by returning. At each + * start position every way of matching is tried and the longest end is + * kept, with the captures of the first way to reach it in greedy order; + * a match that reaches the end of the string cannot be bettered and + * stops the search at once. With REG_NOSUB and no back-reference the + * first match is enough. + * + * A repetition of a single character, set or `.` -- by far the commonest + * -- is a loop, not a recursion per character, so the stack does not + * grow with the length of the line. Everything else costs a few frames + * per node matched. A pattern that would take more than STEP_LIMIT steps + * at one position is REG_ESPACE rather than a hang: backtracking can be + * exponential, and an answer that never comes is worse than an error. + */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include + +#include "posix.h" + +#define STEP_LIMIT 20000000L +#define DUP_MAX 255 /* RE_DUP_MAX */ + +enum { + N_CHAR, N_ANY, N_SET, N_BOL, N_EOL, N_WORDB, N_NWORDB, N_WBEG, N_WEND, + N_BUFBEG, N_BUFEND, N_GROUP, N_BREF, N_CAT, N_ALT, N_REP, N_EMPTY +}; + +struct node { + int type; + int c; /* N_CHAR; N_GROUP/N_BREF: the number */ + unsigned char *set; /* N_SET: 256 bits */ + int min, max; /* N_REP; max -1 for no limit */ + struct node **kid; /* N_CAT, N_ALT; N_GROUP/N_REP: kid[0] */ + int nkid; +}; + +struct prog { + struct node *root; + int nsub, cflags, has_bref; +}; + +/* ---- the parser -------------------------------------------------------------- */ + +struct parse { + const char *p, *end; + int ere, icase, err, nsub, has_bref; + int open[10]; /* \N refers to a closed group only */ +}; + +static struct node *mk(struct parse *ps, int type) +{ + struct node *n = calloc(1, sizeof *n); + if (!n) + ps->err = REG_ESPACE; + else + n->type = type; + return n; +} + +static void free_node(struct node *n) +{ + if (!n) + return; + for (int i = 0; i < n->nkid; i++) + free_node(n->kid[i]); + free(n->kid); + free(n->set); + free(n); +} + +static int push(struct parse *ps, struct node *n, struct node *kid) +{ + struct node **k = realloc(n->kid, (size_t)(n->nkid + 1) * sizeof *k); + if (!k) { + ps->err = REG_ESPACE; + free_node(kid); + return -1; + } + n->kid = k; + n->kid[n->nkid++] = kid; + return 0; +} + +static void set_bit(unsigned char *s, int c) { s[(unsigned char)c >> 3] |= 1u << (c & 7); } +static int has_bit(const unsigned char *s, int c) { return s[(unsigned char)c >> 3] >> (c & 7) & 1; } + +static int class_match(const char *name, size_t n, int c) +{ +#define CL(s, f) if (n == sizeof s - 1 && !memcmp(name, s, n)) return f(c) != 0; + CL("alpha", isalpha) CL("digit", isdigit) CL("alnum", isalnum) + CL("upper", isupper) CL("lower", islower) CL("space", isspace) + CL("blank", isblank) CL("punct", ispunct) CL("print", isprint) + CL("graph", isgraph) CL("cntrl", iscntrl) CL("xdigit", isxdigit) +#undef CL + return -1; +} + +/* a bracket expression, after its '[' */ +static struct node *bracket(struct parse *ps) +{ + struct node *n = mk(ps, N_SET); + if (!n) + return NULL; + n->set = calloc(32, 1); + if (!n->set) { + ps->err = REG_ESPACE; + return n; + } + int neg = 0; + if (ps->p < ps->end && *ps->p == '^') { + neg = 1; + ps->p++; + } + int first = 1, prev = -1; + for (;;) { + if (ps->p >= ps->end) { + ps->err = REG_EBRACK; + return n; + } + int c = (unsigned char)*ps->p++; + if (c == ']' && !first) + break; + first = 0; + if (c == '[' && ps->p < ps->end && + (*ps->p == ':' || *ps->p == '=' || *ps->p == '.')) { + char kind = *ps->p++; + const char *s = ps->p; + while (ps->p + 1 < ps->end && !(ps->p[0] == kind && ps->p[1] == ']')) + ps->p++; + if (ps->p + 1 >= ps->end) { + ps->err = REG_EBRACK; + return n; + } + size_t len = (size_t)(ps->p - s); + ps->p += 2; + if (kind == ':') { + if (class_match(s, len, 'a') < 0) { + ps->err = REG_ECTYPE; + return n; + } + for (int ch = 1; ch < 256; ch++) + if (class_match(s, len, ch) > 0) + set_bit(n->set, ch); + prev = -1; + continue; + } + /* [=c=] and [.c.]: one character in the "C" locale */ + if (len != 1) { + ps->err = REG_ECOLLATE; + return n; + } + c = (unsigned char)s[0]; + if (kind == '=') { + set_bit(n->set, c); + prev = -1; + continue; + } + } + /* a range: the end may itself be [.c.] */ + if (ps->p + 1 < ps->end && *ps->p == '-' && ps->p[1] != ']') { + ps->p++; + int hi = (unsigned char)*ps->p++; + if (hi == '[' && ps->p + 2 < ps->end && *ps->p == '.' && + ps->p[2] == '.') { + hi = (unsigned char)ps->p[1]; + ps->p += 4; + } + if (hi < c) { + ps->err = REG_ERANGE; + return n; + } + for (int ch = c; ch <= hi; ch++) + set_bit(n->set, ch); + prev = -1; + continue; + } + set_bit(n->set, c); + prev = c; + } + (void)prev; + if (ps->icase) + for (int ch = 0; ch < 256; ch++) + if (has_bit(n->set, ch)) { + set_bit(n->set, tolower(ch)); + set_bit(n->set, toupper(ch)); + } + if (neg) { + for (int i = 0; i < 32; i++) + n->set[i] = (unsigned char)~n->set[i]; + n->set[0] &= (unsigned char)~1u; /* never NUL */ + n->c = 1; /* REG_NEWLINE: not \n */ + } + return n; +} + +static struct node *alternation(struct parse *ps, int depth); + +static struct node *char_node(struct parse *ps, int c) +{ + if (ps->icase && isalpha(c)) { + struct node *n = mk(ps, N_SET); + if (!n) + return NULL; + n->set = calloc(32, 1); + if (!n->set) { + ps->err = REG_ESPACE; + return n; + } + set_bit(n->set, tolower(c)); + set_bit(n->set, toupper(c)); + return n; + } + struct node *n = mk(ps, N_CHAR); + if (n) + n->c = c; + return n; +} + +static struct node *class_node(struct parse *ps, int which) +{ + struct node *n = mk(ps, N_SET); + if (!n) + return NULL; + n->set = calloc(32, 1); + if (!n->set) { + ps->err = REG_ESPACE; + return n; + } + for (int ch = 1; ch < 256; ch++) { + int in = (which == 'w' || which == 'W') ? (isalnum(ch) || ch == '_') + : isspace(ch) != 0; + if (in != (which == 'W' || which == 'S')) + set_bit(n->set, ch); + } + return n; +} + +/* one atom, or NULL at the end of a branch (ps->err says whether that + * was an error) */ +static struct node *atom(struct parse *ps, int depth, int at_start) +{ + if (ps->p >= ps->end) + return NULL; + int c = (unsigned char)*ps->p; + if (ps->ere) { + if (c == '|' || (c == ')' && depth)) + return NULL; + if ((c == '*' || c == '+' || c == '?') && at_start) { + ps->err = REG_BADRPT; /* nothing to repeat */ + return NULL; + } + if (c == '{' && at_start && ps->p + 1 < ps->end && + isdigit((unsigned char)ps->p[1])) { + ps->err = REG_BADRPT; + return NULL; + } + } + ps->p++; + switch (c) { + case '.': + return mk(ps, N_ANY); + case '[': + return bracket(ps); + case '^': + if (ps->ere || at_start) + return mk(ps, N_BOL); + return char_node(ps, c); + case '$': + if (ps->ere || ps->p >= ps->end || + (ps->p + 1 < ps->end && ps->p[0] == '\\' && + (ps->p[1] == ')' || ps->p[1] == '|'))) + return mk(ps, N_EOL); + return char_node(ps, c); + case '*': + if (!ps->ere && at_start) + return char_node(ps, c); + break; + case '(': + if (ps->ere) + goto group; + break; + case ')': + if (ps->ere) { + if (!depth) + return char_node(ps, c); /* glibc: a lone ) is itself */ + ps->err = REG_EPAREN; + return NULL; + } + break; + case '\\': { + if (ps->p >= ps->end) { + ps->err = REG_EESCAPE; + return NULL; + } + int e = (unsigned char)*ps->p++; + if (!ps->ere && e == '(') + goto group; + if (!ps->ere && (e == ')' || e == '|')) { + ps->p -= 2; /* the caller's to see */ + return NULL; + } + if (e >= '1' && e <= '9') { + if (e - '0' > ps->nsub || ps->open[e - '0']) { + ps->err = REG_ESUBREG; + return NULL; + } + struct node *n = mk(ps, N_BREF); + if (n) + n->c = e - '0'; + ps->has_bref = 1; + return n; + } + switch (e) { + case '<': return mk(ps, N_WBEG); + case '>': return mk(ps, N_WEND); + case 'b': return mk(ps, N_WORDB); + case 'B': return mk(ps, N_NWORDB); + case '`': return mk(ps, N_BUFBEG); + case '\'': return mk(ps, N_BUFEND); + case 'w': case 'W': case 's': case 'S': return class_node(ps, e); + } + if (!ps->ere && (e == '{' || e == '+' || e == '?') && at_start) + return char_node(ps, e); + return char_node(ps, e); + } + } + return char_node(ps, c); + +group: { + int num = ++ps->nsub; + if (num < 10) + ps->open[num] = 1; + struct node *g = mk(ps, N_GROUP); + if (!g) + return NULL; + g->c = num; + struct node *in = alternation(ps, depth + 1); + if (ps->err) { + free_node(in); + free_node(g); + return NULL; + } + if (ps->ere) { + if (ps->p >= ps->end || *ps->p != ')') { + ps->err = REG_EPAREN; + free_node(in); + free_node(g); + return NULL; + } + ps->p++; + } else { + if (ps->p + 1 >= ps->end || ps->p[0] != '\\' || ps->p[1] != ')') { + ps->err = REG_EPAREN; + free_node(in); + free_node(g); + return NULL; + } + ps->p += 2; + } + if (num < 10) + ps->open[num] = 0; + if (!in) + in = mk(ps, N_EMPTY); + push(ps, g, in); + return g; + } +} + +/* after an atom: *, +, ?, {m,n} -- with \ in front where basic syntax + * wants it; several apply in turn ("a**") */ +static struct node *repeats(struct parse *ps, struct node *a) +{ + for (;;) { + if (ps->p >= ps->end) + return a; + int min, max; + const char *p = ps->p; + int bs = !ps->ere && *p == '\\' && p + 1 < ps->end; + int c = (unsigned char)(bs ? p[1] : p[0]); + if (c == '*' && !bs) { + min = 0; + max = -1; + ps->p++; + } else if ((c == '+' || c == '?') && (ps->ere ? !bs : bs)) { + min = c == '+'; + max = c == '+' ? -1 : 1; + ps->p += bs ? 2 : 1; + } else if (c == '{' && (ps->ere ? !bs : bs)) { + const char *q = p + (bs ? 2 : 1); + /* after an atom a brace always opens an interval, as glibc + * reads it: "a{x" is REG_EBRACE, "a{,2}" is a{0,2} */ + min = 0; + int digits = 0; + while (q < ps->end && isdigit((unsigned char)*q)) { + if (min <= DUP_MAX) + min = min * 10 + (*q - '0'); + q++; + digits++; + } + max = min; + if (q < ps->end && *q == ',') { + q++; + max = -1; + if (q < ps->end && isdigit((unsigned char)*q)) { + max = 0; + while (q < ps->end && isdigit((unsigned char)*q)) { + if (max <= DUP_MAX) + max = max * 10 + (*q - '0'); + q++; + } + } + } + if (!digits && max == min) { /* "{}" or "{x" */ + ps->err = q >= ps->end || *q != '}' ? REG_EBRACE : REG_BADBR; + return a; + } + if (bs) { + if (q + 1 >= ps->end || q[0] != '\\' || q[1] != '}') { + ps->err = q >= ps->end ? REG_EBRACE : REG_BADBR; + return a; + } + q += 2; + } else { + if (q >= ps->end) { + ps->err = REG_EBRACE; + return a; + } + if (*q != '}') { + ps->err = REG_BADBR; + return a; + } + q++; + } + if (min > DUP_MAX || max > DUP_MAX || (max >= 0 && max < min)) { + ps->err = REG_BADBR; + return a; + } + ps->p = q; + } else { + return a; + } + if (a->type == N_BOL || a->type == N_EOL) { + if (ps->ere) { + ps->err = REG_BADRPT; + return a; + } + } + if (a->type == N_REP && !ps->ere) { + /* basic "a**" glibc refuses; extended "a+?" it nests */ + ps->err = REG_BADRPT; + return a; + } + struct node *r = mk(ps, N_REP); + if (!r) + return a; + r->min = min; + r->max = max; + if (push(ps, r, a)) + return NULL; + a = r; + } +} + +static struct node *branch(struct parse *ps, int depth) +{ + struct node *cat = mk(ps, N_CAT); + if (!cat) + return NULL; + int at_start = 1; + for (;;) { + struct node *a = atom(ps, depth, at_start); + if (!a || ps->err) + break; + /* basic syntax: a * right after ^ is literal, as at the start */ + int after_bol = a->type == N_BOL; + if (!(after_bol && !ps->ere)) + a = repeats(ps, a); + if (ps->err) { + free_node(a); + break; + } + if (push(ps, cat, a)) + break; + at_start = after_bol && !ps->ere; + } + return cat; +} + +static struct node *alternation(struct parse *ps, int depth) +{ + struct node *alt = mk(ps, N_ALT); + if (!alt) + return NULL; + for (;;) { + struct node *b = branch(ps, depth); + if (!b || push(ps, alt, b) || ps->err) + return alt; + if (ps->p >= ps->end) + return alt; + if (ps->ere && *ps->p == '|') { + ps->p++; + continue; + } + if (!ps->ere && ps->p + 1 < ps->end && ps->p[0] == '\\' && + ps->p[1] == '|') { + ps->p += 2; + continue; + } + if (!depth) + ps->err = REG_EPAREN; /* a basic \) with nothing open */ + return alt; + } +} + +WEAK int regcomp(regex_t *__restrict re, const char *__restrict pattern, + int cflags) +{ + struct parse ps; + memset(&ps, 0, sizeof ps); + ps.p = pattern; + ps.end = pattern + strlen(pattern); + ps.ere = cflags & REG_EXTENDED; + ps.icase = cflags & REG_ICASE; + struct node *root = alternation(&ps, 0); + if (!ps.err && ps.p < ps.end) + ps.err = REG_EPAREN; + if (ps.err || !root) { + free_node(root); + return ps.err ? ps.err : REG_ESPACE; + } + struct prog *pg = malloc(sizeof *pg); + if (!pg) { + free_node(root); + return REG_ESPACE; + } + pg->root = root; + pg->nsub = ps.nsub; + pg->cflags = cflags; + pg->has_bref = ps.has_bref; + re->re_nsub = (size_t)ps.nsub; + re->__prog = pg; + re->__cflags = cflags; + return 0; +} + +WEAK void regfree(regex_t *re) +{ + struct prog *pg = re->__prog; + if (pg) { + free_node(pg->root); + free(pg); + } + re->__prog = NULL; +} + +/* ---- the matcher --------------------------------------------------------------- */ + +enum { K_SEQ, K_REP, K_GROUP }; + +struct k { + int type; + const struct node *n; + int idx; /* K_SEQ: next kid; K_REP: iterations done */ + long start; /* K_REP: where this iteration began */ + long gstart; /* K_GROUP: where the group began */ + const struct k *next; +}; + +struct m { + const char *s; + long lo, hi; /* the string is s[lo, hi) */ + int eflags, cflags, first_only; + regmatch_t *cap, *best; + int ncap; + long best_end; + int found, spent; + long steps; +}; + +static int is_word(int c) { return isalnum(c) || c == '_'; } + +static int match(struct m *m, const struct node *n, long i, const struct k *k); + +static int done(struct m *m, long i) +{ + if (!m->found || i > m->best_end) { + m->found = 1; + m->best_end = i; + memcpy(m->best, m->cap, (size_t)m->ncap * sizeof *m->cap); + } + /* the end of the string is as long as a match gets */ + return m->first_only || i == m->hi; +} + +static int next(struct m *m, const struct k *k, long i) +{ + if (!k) + return done(m, i); + switch (k->type) { + case K_SEQ: + if (k->idx < k->n->nkid) { + struct k nk = { K_SEQ, k->n, k->idx + 1, 0, 0, k->next }; + return match(m, k->n->kid[k->idx], i, &nk); + } + return next(m, k->next, i); + case K_GROUP: { + int g = k->n->c; + regmatch_t old = m->cap[g]; + m->cap[g].rm_so = (regoff_t)k->gstart; + m->cap[g].rm_eo = (regoff_t)i; + int r = next(m, k->next, i); + m->cap[g] = old; + return r; + } + default: { /* K_REP: one more iteration done */ + const struct node *rep = k->n; + int count = k->idx + 1; + /* an iteration that matched nothing would match nothing forever */ + if (i == k->start && count > rep->min) + return 0; + if (rep->max < 0 || count < rep->max) { + struct k nk = { K_REP, rep, count, i, 0, k->next }; + if (match(m, rep->kid[0], i, &nk)) + return 1; + } + if (count >= rep->min) + return next(m, k->next, i); + return 0; + } + } +} + +/* does one character at i match n, which is a single-character node */ +static int one(const struct m *m, const struct node *n, long i) +{ + if (i >= m->hi) + return 0; + int c = (unsigned char)m->s[i]; + switch (n->type) { + case N_CHAR: + return c == n->c; + case N_ANY: + return c != 0 && !(c == '\n' && (m->cflags & REG_NEWLINE)); + default: /* N_SET */ + if (c == '\n' && n->c && (m->cflags & REG_NEWLINE)) + return 0; + return has_bit(n->set, c); + } +} + +static int match(struct m *m, const struct node *n, long i, const struct k *k) +{ + if (++m->steps > STEP_LIMIT) { + m->spent = 1; + return 1; + } + switch (n->type) { + case N_EMPTY: + return next(m, k, i); + case N_CHAR: + case N_ANY: + case N_SET: + return one(m, n, i) ? next(m, k, i + 1) : 0; + case N_BOL: { + /* the start of the STRING, as glibc has it: REG_STARTEND's + * rm_so is where the search starts, not a line's start */ + int ok = (i == 0 && !(m->eflags & REG_NOTBOL)) || + (i > 0 && m->s[i - 1] == '\n' && + (m->cflags & REG_NEWLINE)); + return ok ? next(m, k, i) : 0; + } + case N_EOL: { + int ok = (i == m->hi && !(m->eflags & REG_NOTEOL)) || + (i < m->hi && m->s[i] == '\n' && (m->cflags & REG_NEWLINE)); + return ok ? next(m, k, i) : 0; + } + case N_BUFBEG: + return i == m->lo ? next(m, k, i) : 0; + case N_BUFEND: + return i == m->hi ? next(m, k, i) : 0; + case N_WORDB: case N_NWORDB: case N_WBEG: case N_WEND: { + int before = i > m->lo && is_word((unsigned char)m->s[i - 1]); + int after = i < m->hi && is_word((unsigned char)m->s[i]); + int ok = n->type == N_WORDB ? before != after : + n->type == N_NWORDB ? before == after : + n->type == N_WBEG ? !before && after : before && !after; + return ok ? next(m, k, i) : 0; + } + case N_GROUP: { + struct k nk = { K_GROUP, n, 0, 0, i, k }; + return match(m, n->kid[0], i, &nk); + } + case N_BREF: { + regmatch_t g = m->cap[n->c]; + if (g.rm_so < 0) + return 0; + long len = g.rm_eo - g.rm_so; + if (i + len > m->hi) + return 0; + for (long j = 0; j < len; j++) { + int a = (unsigned char)m->s[g.rm_so + j]; + int b = (unsigned char)m->s[i + j]; + if (a != b && !((m->cflags & REG_ICASE) && tolower(a) == tolower(b))) + return 0; + } + return next(m, k, i + len); + } + case N_CAT: { + if (!n->nkid) + return next(m, k, i); + struct k nk = { K_SEQ, n, 1, 0, 0, k }; + return match(m, n->kid[0], i, &nk); + } + case N_ALT: + for (int a = 0; a < n->nkid; a++) + if (match(m, n->kid[a], i, k)) + return 1; + return 0; + default: { /* N_REP */ + const struct node *kid = n->kid[0]; + if (kid->type == N_CHAR || kid->type == N_ANY || kid->type == N_SET) { + /* one character at a time: count the run, then give back + * from the longest */ + long run = 0, cap = n->max < 0 ? LONG_MAX : n->max; + while (run < cap && one(m, kid, i + run)) + run++; + for (long j = run; j >= n->min; j--) { + if (next(m, k, i + j)) + return 1; + if (++m->steps > STEP_LIMIT) { + m->spent = 1; + return 1; + } + } + return 0; + } + if (n->max != 0) { + struct k nk = { K_REP, n, 0, i, 0, k }; + if (match(m, kid, i, &nk)) + return 1; + } + return n->min == 0 ? next(m, k, i) : 0; + } + } +} + +WEAK int regexec(const regex_t *__restrict re, const char *__restrict s, + size_t nmatch, regmatch_t pmatch[__restrict], int eflags) +{ + const struct prog *pg = re->__prog; + if (!pg) + return REG_BADPAT; + if (pg->cflags & REG_NOSUB) + nmatch = 0; + long lo = 0, hi; + if ((eflags & REG_STARTEND) && pmatch) { + lo = pmatch[0].rm_so; + hi = pmatch[0].rm_eo; + } else { + hi = (long)strlen(s); + } + int ncap = pg->nsub + 1; + regmatch_t stack_cap[20]; + regmatch_t *cap = ncap * 2 <= 20 ? stack_cap : malloc((size_t)ncap * 2 * sizeof *cap); + if (!cap) + return REG_ESPACE; + struct m m; + m.s = s; + m.lo = lo; + m.hi = hi; + m.eflags = eflags; + m.cflags = pg->cflags; + m.first_only = !nmatch && !pg->has_bref; + m.cap = cap; + m.best = cap + ncap; + m.ncap = ncap; + int result = REG_NOMATCH; + for (long start = lo; start <= hi; start++) { + for (int g = 0; g < ncap; g++) + cap[g].rm_so = cap[g].rm_eo = -1; + m.found = 0; + m.spent = 0; + m.steps = 0; + match(&m, pg->root, start, NULL); + if (m.spent) { + result = REG_ESPACE; + break; + } + if (m.found) { + m.best[0].rm_so = (regoff_t)start; + m.best[0].rm_eo = (regoff_t)m.best_end; + for (size_t g = 0; g < nmatch; g++) + pmatch[g] = (int)g < ncap ? m.best[g] + : (regmatch_t){ -1, -1 }; + result = 0; + break; + } + /* a pattern anchored at the start of the text cannot match later, + * unless a newline makes a new line there */ + if (pg->root->nkid == 1 && pg->root->kid[0]->nkid) { + int t = pg->root->kid[0]->kid[0]->type; + if (t == N_BUFBEG || + (t == N_BOL && !(pg->cflags & REG_NEWLINE))) + break; + } + } + if (cap != stack_cap) + free(cap); + return result; +} + +static const char *const g_msgs[] = { + "Success", "No match", "Invalid regular expression", + "Invalid collation character", "Invalid character class name", + "Trailing backslash", "Invalid back reference", + "Unmatched [, [^, [:, [., or [=", "Unmatched ( or \\(", + "Unmatched \\{", "Invalid content of \\{\\}", "Invalid range end", + "Memory exhausted", "Invalid preceding regular expression", + "Premature end of regular expression", "Regular expression too big", + "Unmatched ) or \\)", +}; + +WEAK size_t regerror(int err, const regex_t *__restrict re, + char *__restrict buf, size_t n) +{ + (void)re; + const char *msg = err >= 0 && err < (int)(sizeof g_msgs / sizeof *g_msgs) + ? g_msgs[err] : "Unknown error"; + size_t len = strlen(msg) + 1; + if (buf && n) { + size_t c = len < n ? len : n; + memcpy(buf, msg, c - 1); + buf[c - 1] = 0; + } + return len; +} diff --git a/lib/libc/src/posix/resolv.c b/lib/libc/src/posix/resolv.c new file mode 100644 index 000000000..08ec2e72d --- /dev/null +++ b/lib/libc/src/posix/resolv.c @@ -0,0 +1,366 @@ +/* A DNS client: the part of name lookup that asks the network, for a + * system whose kernel has no resolver of its own (Linux; EmbLinkOS has + * one, and netdb.c asks it first). + * + * /etc/resolv.conf as glibc reads it -- nameserver (three at most), + * search and domain, options ndots: and timeout: and attempts: -- and + * the queries as RFC 1035 has them: UDP to port 53, recursion desired, + * an id from the OS's entropy, and the answer accepted only from the + * server asked, with that id and that question. A truncated answer is + * asked again over TCP. With no resolv.conf the server is 127.0.0.1, + * as glibc's default is. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" +#include "resolv.h" + +struct conf { + struct sockaddr_storage ns[3]; + socklen_t nslen[3]; + int nns; + char search[6][256]; + int nsearch; + int ndots, timeout, attempts; +}; + +static void add_ns(struct conf *c, const char *s) +{ + if (c->nns == 3) + return; + struct sockaddr_in *a4 = (struct sockaddr_in *)&c->ns[c->nns]; + struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)&c->ns[c->nns]; + memset(&c->ns[c->nns], 0, sizeof c->ns[0]); + if (inet_pton(AF_INET, s, &a4->sin_addr) == 1) { + a4->sin_family = AF_INET; + a4->sin_port = htons(53); + c->nslen[c->nns++] = sizeof *a4; + } else if (inet_pton(AF_INET6, s, &a6->sin6_addr) == 1) { + a6->sin6_family = AF_INET6; + a6->sin6_port = htons(53); + c->nslen[c->nns++] = sizeof *a6; + } +} + +static void read_conf(struct conf *c) +{ + memset(c, 0, sizeof *c); + c->ndots = 1; + c->timeout = 5; + c->attempts = 2; + FILE *f = fopen("/etc/resolv.conf", "r"); + char line[512]; + while (f && fgets(line, sizeof line, f)) { + char *save, *key = strtok_r(line, " \t\r\n", &save); + if (!key || *key == '#' || *key == ';') + continue; + if (!strcmp(key, "nameserver")) { + char *v = strtok_r(NULL, " \t\r\n", &save); + if (v) + add_ns(c, v); + } else if (!strcmp(key, "search") || !strcmp(key, "domain")) { + c->nsearch = 0; + for (char *v; (v = strtok_r(NULL, " \t\r\n", &save)) && + c->nsearch < 6; ) + snprintf(c->search[c->nsearch++], 256, "%s", v); + } else if (!strcmp(key, "options")) { + for (char *v; (v = strtok_r(NULL, " \t\r\n", &save)); ) { + if (!strncmp(v, "ndots:", 6)) + c->ndots = atoi(v + 6); + else if (!strncmp(v, "timeout:", 8)) + c->timeout = atoi(v + 8) > 0 ? atoi(v + 8) : 1; + else if (!strncmp(v, "attempts:", 9)) + c->attempts = atoi(v + 9) > 0 ? atoi(v + 9) : 1; + } + } + } + if (f) + fclose(f); + if (!c->nns) + add_ns(c, "127.0.0.1"); +} + +/* ---- the wire format ----------------------------------------------------- */ + +/* name as labels, at q; its length, or -1 if a label or the name is too + * long */ +static int put_name(unsigned char *q, size_t cap, const char *name) +{ + size_t at = 0; + while (*name) { + const char *dot = strchr(name, '.'); + size_t n = dot ? (size_t)(dot - name) : strlen(name); + if (n == 0 || n > 63 || at + n + 2 > cap) + return -1; + q[at++] = (unsigned char)n; + memcpy(q + at, name, n); + at += n; + name += n; + if (*name == '.') + name++; + } + if (at + 1 > cap || at > 254) + return -1; + q[at++] = 0; + return (int)at; +} + +static int build_query(unsigned char *q, size_t cap, const char *name, + int type, unsigned id) +{ + if (cap < 12) + return -1; + memset(q, 0, 12); + q[0] = (unsigned char)(id >> 8); + q[1] = (unsigned char)id; + q[2] = 0x01; /* RD */ + q[5] = 1; /* one question */ + int n = put_name(q + 12, cap - 12 - 4, name); + if (n < 0) + return -1; + unsigned char *t = q + 12 + n; + t[0] = (unsigned char)(type >> 8); + t[1] = (unsigned char)type; + t[2] = 0; + t[3] = 1; /* class IN */ + return 12 + n + 4; +} + +/* The name at off, decompressed into out; the offset after it in the + * message, or -1 for a malformed one -- pointers that loop or point + * past the end included. */ +int __dns_name(const unsigned char *m, int len, int off, char *out, int cap) +{ + int at = 0, jumps = 0, next = -1; + while (off < len) { + unsigned c = m[off]; + if (c == 0) { + if (next < 0) + next = off + 1; + if (at == 0 && cap > 1) + out[at++] = '.'; + if (at < cap) + out[at] = 0; + return at < cap ? next : -1; + } + if ((c & 0xc0) == 0xc0) { + if (off + 1 >= len || ++jumps > 32) + return -1; + if (next < 0) + next = off + 2; + off = (int)((c & 0x3f) << 8 | m[off + 1]); + continue; + } + if (c & 0xc0 || off + 1 + (int)c > len) + return -1; + if (at && at < cap) + out[at++] = '.'; + if (at + (int)c >= cap) + return -1; + memcpy(out + at, m + off + 1, c); + at += (int)c; + off += 1 + (int)c; + } + return -1; +} + +static unsigned id16(void) +{ + unsigned short r; + if (__os_getentropy(&r, sizeof r) == 0) + return r; + return (unsigned)(__os_clock_ns() ^ (long)__os_time()) & 0xffff; +} + +/* ---- asking ----------------------------------------------------------------- */ + +static int ask_tcp(const struct sockaddr *ns, socklen_t nslen, + const unsigned char *q, int qlen, unsigned char *ans, + int cap, int timeout) +{ + int fd = socket(ns->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + int r = -1; + unsigned char pre[2] = { (unsigned char)(qlen >> 8), (unsigned char)qlen }; + if (connect(fd, ns, nslen) == 0 && write(fd, pre, 2) == 2 && + write(fd, q, (size_t)qlen) == qlen) { + int got = 0, want = -1; + unsigned char lenb[2]; + int lg = 0; + struct pollfd p = { fd, POLLIN, 0 }; + while (poll(&p, 1, timeout * 1000) > 0) { + if (lg < 2) { + long k = read(fd, lenb + lg, (size_t)(2 - lg)); + if (k <= 0) + break; + lg += (int)k; + if (lg == 2) + want = lenb[0] << 8 | lenb[1]; + continue; + } + if (want > cap) + break; + long k = read(fd, ans + got, (size_t)(want - got)); + if (k <= 0) + break; + got += (int)k; + if (got == want) { + r = got; + break; + } + } + } + close(fd); + return r; +} + +/* One question to the servers in turn, attempts times round. The + * answer's length, or -1: EAGAIN when nobody answered, ENOENT for a + * name that does not exist (NXDOMAIN), EIO for a server failure. */ +static int ask(const struct conf *c, const char *name, int type, + unsigned char *ans, int cap) +{ + unsigned char q[300]; + unsigned id = id16(); + int qlen = build_query(q, sizeof q, name, type, id); + if (qlen < 0) { + errno = EINVAL; + return -1; + } + int failed = EAGAIN; + for (int round = 0; round < c->attempts; round++) + for (int i = 0; i < c->nns; i++) { + const struct sockaddr *ns = (const struct sockaddr *)&c->ns[i]; + int fd = socket(ns->sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + if (connect(fd, ns, c->nslen[i]) < 0 || + send(fd, q, (size_t)qlen, 0) != qlen) { + close(fd); + continue; + } + struct pollfd p = { fd, POLLIN, 0 }; + long got = -1; + while (poll(&p, 1, c->timeout * 1000) > 0) { + got = recv(fd, ans, (size_t)cap, 0); + /* the reply to THIS question: id, QR set, the same name */ + if (got >= 12 && ans[0] == q[0] && ans[1] == q[1] && + (ans[2] & 0x80) && got >= qlen && + memcmp(ans + 12, q + 12, (size_t)(qlen - 12)) == 0) + break; + got = -1; + } + close(fd); + if (got < 0) + continue; + if (ans[2] & 0x02) { /* TC: the whole answer, over TCP */ + int t = ask_tcp(ns, c->nslen[i], q, qlen, ans, cap, + c->timeout); + if (t > 0) + got = t; + } + int rcode = ans[3] & 0x0f; + if (rcode == 0) + return (int)got; + if (rcode == 3) { + errno = ENOENT; + return -1; + } + failed = EIO; + } + errno = failed; + return -1; +} + +/* ---- reading an answer ---------------------------------------------------- */ + +static int skip_question(const unsigned char *m, int len) +{ + char tmp[256]; + int qd = m[4] << 8 | m[5], off = 12; + for (int i = 0; i < qd; i++) { + off = __dns_name(m, len, off, tmp, sizeof tmp); + if (off < 0 || off + 4 > len) + return -1; + off += 4; + } + return off; +} + +/* The records of `type` in the answer section, through cb; the canonical + * name (the last CNAME's target, else the name asked) in canon. */ +int __dns_records(const unsigned char *m, int len, int type, char *canon, + void (*cb)(void *, const unsigned char *, int, const + unsigned char *, int), + void *ctx) +{ + int an = m[6] << 8 | m[7], n = 0; + int off = skip_question(m, len); + if (off < 0) + return -1; + for (int i = 0; i < an; i++) { + char owner[256]; + off = __dns_name(m, len, off, owner, sizeof owner); + if (off < 0 || off + 10 > len) + return -1; + int t = m[off] << 8 | m[off + 1]; + int rdlen = m[off + 8] << 8 | m[off + 9]; + off += 10; + if (off + rdlen > len) + return -1; + if (t == 5 && canon) /* CNAME */ + __dns_name(m, len, off, canon, 256); + else if (t == type) { + cb(ctx, m + off, rdlen, m, len); + n++; + } + off += rdlen; + } + return n; +} + +/* A question about name, with resolv.conf's search list applied: a name + * with fewer dots than ndots is tried with each search domain first, + * then as it is. The answer as for ask(). */ +int __dns_lookup(const char *name, int type, unsigned char *ans, int cap) +{ + struct conf c; + read_conf(&c); + int dots = 0; + for (const char *p = name; *p; p++) + dots += *p == '.'; + size_t n = strlen(name); + if (n && name[n - 1] == '.') { /* absolute: exactly this */ + char tmp[256]; + if (n > sizeof tmp) { + errno = EINVAL; + return -1; + } + memcpy(tmp, name, n - 1); + tmp[n - 1] = 0; + return ask(&c, tmp, type, ans, cap); + } + int last = ENOENT; + if (dots < c.ndots) + for (int i = 0; i < c.nsearch; i++) { + char full[512]; + snprintf(full, sizeof full, "%s.%s", name, c.search[i]); + int r = ask(&c, full, type, ans, cap); + if (r >= 0) + return r; + if (errno != ENOENT) + last = errno; + } + int r = ask(&c, name, type, ans, cap); + if (r < 0 && errno == ENOENT && last != ENOENT) + errno = last; + return r; +} diff --git a/lib/libc/src/posix/resolv.h b/lib/libc/src/posix/resolv.h new file mode 100644 index 000000000..89dfc1873 --- /dev/null +++ b/lib/libc/src/posix/resolv.h @@ -0,0 +1,17 @@ +/* The DNS client netdb.c uses (resolv.c). */ +#ifndef EMBLIBC_RESOLV_H +#define EMBLIBC_RESOLV_H + +#define DNS_A 1 +#define DNS_CNAME 5 +#define DNS_PTR 12 +#define DNS_AAAA 28 + +int __dns_lookup(const char *name, int type, unsigned char *ans, int cap); +int __dns_records(const unsigned char *m, int len, int type, char *canon, + void (*cb)(void *, const unsigned char *, int, + const unsigned char *, int), + void *ctx); +int __dns_name(const unsigned char *m, int len, int off, char *out, int cap); + +#endif diff --git a/lib/libc/src/posix/search.c b/lib/libc/src/posix/search.c new file mode 100644 index 000000000..77c80bf37 --- /dev/null +++ b/lib/libc/src/posix/search.c @@ -0,0 +1,359 @@ +/* . + * + * tsearch's tree is AVL: POSIX requires nothing of the shape, but a + * program that inserts keys in sorted order -- the common case, reading + * a sorted file -- turns an unbalanced tree into a list and every + * lookup into a scan. A node's first member is the key pointer, which is + * what POSIX promises of the pointer tsearch returns: *(void **)node is + * the key. + * + * hsearch's table is open addressing over string keys, and grows: POSIX + * lets ENTER fail once the table is full, and a program sized with + * hcreate(100) that enters a hundred and first key is better served by + * a bigger table than by an ENOMEM. + */ +#include +#include +#include +#include + +/* ---- trees ------------------------------------------------------------ */ + +struct node { + const void *key; /* first: see above */ + struct node *a[2]; /* less, greater */ + int h; /* height: a leaf is 1 */ +}; + +typedef int (*cmp_fn)(const void *, const void *); + +static int height(const struct node *n) { return n ? n->h : 0; } + +static void fix(struct node *n) +{ + int l = height(n->a[0]), r = height(n->a[1]); + n->h = (l > r ? l : r) + 1; +} + +/* n's child on side !dir becomes the root of this subtree */ +static struct node *rotate(struct node *n, int dir) +{ + struct node *r = n->a[!dir]; + n->a[!dir] = r->a[dir]; + r->a[dir] = n; + fix(n); + fix(r); + return r; +} + +static struct node *rebalance(struct node *n) +{ + fix(n); + int d = height(n->a[0]) - height(n->a[1]); + if (d > 1) { + if (height(n->a[0]->a[0]) < height(n->a[0]->a[1])) + n->a[0] = rotate(n->a[0], 0); + return rotate(n, 1); + } + if (d < -1) { + if (height(n->a[1]->a[1]) < height(n->a[1]->a[0])) + n->a[1] = rotate(n->a[1], 1); + return rotate(n, 0); + } + return n; +} + +/* *found is the node with key, new or old; NULL when there was no memory + * for a new one, and the tree is then unchanged */ +static struct node *insert(struct node *n, const void *key, cmp_fn cmp, + struct node **found) +{ + if (!n) { + n = malloc(sizeof *n); + if (n) { + n->key = key; + n->a[0] = n->a[1] = NULL; + n->h = 1; + } + *found = n; + return n; + } + int c = cmp(key, n->key); + if (c == 0) { + *found = n; + return n; + } + n->a[c > 0] = insert(n->a[c > 0], key, cmp, found); + return rebalance(n); +} + +__attribute__((weak)) void *tsearch(const void *key, void **root, cmp_fn cmp) +{ + if (!root) + return NULL; + struct node *found; + *root = insert(*root, key, cmp, &found); + return found; +} + +__attribute__((weak)) void *tfind(const void *key, void *const *root, cmp_fn cmp) +{ + if (!root) + return NULL; + for (struct node *n = *root; n; ) { + int c = cmp(key, n->key); + if (c == 0) + return n; + n = n->a[c > 0]; + } + return NULL; +} + +/* the subtree without its least node, which is handed back in *min */ +static struct node *pop_min(struct node *n, struct node **min) +{ + if (!n->a[0]) { + *min = n; + return n->a[1]; + } + n->a[0] = pop_min(n->a[0], min); + return rebalance(n); +} + +/* A deleted node with two children is replaced by its successor NODE, + * moved, rather than by the successor's key copied in: a pointer a + * caller holds from tsearch must go on naming the key it named. */ +static struct node *del(struct node *n, const void *key, cmp_fn cmp, + struct node *up, struct node **parent, int *done) +{ + if (!n) + return NULL; + int c = cmp(key, n->key); + if (c) { + n->a[c > 0] = del(n->a[c > 0], key, cmp, n, parent, done); + return *done ? rebalance(n) : n; + } + *done = 1; + *parent = up; + struct node *r; + if (!n->a[0] || !n->a[1]) + r = n->a[0] ? n->a[0] : n->a[1]; + else { + struct node *m; + struct node *right = pop_min(n->a[1], &m); + m->a[0] = n->a[0]; + m->a[1] = right; + r = rebalance(m); + } + free(n); + return r; +} + +/* The deleted node's parent; for the root, which has none, any pointer + * that is not null (POSIX's words) -- the root's address here. */ +__attribute__((weak)) void *tdelete(const void *restrict key, void **restrict root, cmp_fn cmp) +{ + if (!root || !*root) + return NULL; + struct node *parent = NULL; + int done = 0; + *root = del(*root, key, cmp, NULL, &parent, &done); + if (!done) + return NULL; + return parent ? (void *)parent : (void *)root; +} + +static void walk(const struct node *n, void (*act)(const void *, VISIT, int), + int depth) +{ + if (!n->a[0] && !n->a[1]) { + act(n, leaf, depth); + return; + } + act(n, preorder, depth); + if (n->a[0]) + walk(n->a[0], act, depth + 1); + act(n, postorder, depth); + if (n->a[1]) + walk(n->a[1], act, depth + 1); + act(n, endorder, depth); +} + +__attribute__((weak)) void twalk(const void *root, void (*act)(const void *, VISIT, int)) +{ + if (root && act) + walk(root, act, 0); +} + +__attribute__((weak)) void tdestroy(void *root, void (*free_key)(void *)) +{ + struct node *n = root; + if (!n) + return; + tdestroy(n->a[0], free_key); + tdestroy(n->a[1], free_key); + if (free_key) + free_key((void *)n->key); + free(n); +} + +/* ---- the hash table ---------------------------------------------------- */ + +struct __htab { + ENTRY *e; /* key NULL: an empty slot */ + size_t mask, used; +}; + +/* FNV-1a, in 32 bits whatever size_t is: on AVR it is 16, and the + * offset basis does not fit */ +static size_t hash(const char *s) +{ + unsigned long h = 2166136261ul; + while (*s) + h = ((h ^ (unsigned char)*s++) * 16777619ul) & 0xFFFFFFFFul; + return (size_t)(h ^ h >> 16); +} + +static ENTRY *slot(struct __htab *t, const char *key) +{ + for (size_t i = hash(key) & t->mask;; i = (i + 1) & t->mask) + if (!t->e[i].key || strcmp(t->e[i].key, key) == 0) + return &t->e[i]; +} + +static int resize(struct __htab *t, size_t want) +{ + size_t n = 8; + while (n < want + want / 3 + 1) /* at most three quarters full */ + n *= 2; + ENTRY *e = calloc(n, sizeof *e); + if (!e) + return -1; + ENTRY *old = t->e; + size_t oldn = old ? t->mask + 1 : 0; + t->e = e; + t->mask = n - 1; + for (size_t i = 0; i < oldn; i++) + if (old[i].key) + *slot(t, old[i].key) = old[i]; + free(old); + return 0; +} + +__attribute__((weak)) int hcreate_r(size_t nel, struct hsearch_data *h) +{ + struct __htab *t = calloc(1, sizeof *t); + if (!t || resize(t, nel) < 0) { + free(t); + errno = ENOMEM; + return 0; + } + h->__tab = t; + return 1; +} + +__attribute__((weak)) void hdestroy_r(struct hsearch_data *h) +{ + if (h && h->__tab) { + free(h->__tab->e); + free(h->__tab); + h->__tab = NULL; + } +} + +/* 1 with *ret at the entry, or 0 with errno: ESRCH for a FIND that found + * nothing, ENOMEM for an ENTER there was no room for. */ +__attribute__((weak)) int hsearch_r(ENTRY item, ACTION action, ENTRY **ret, struct hsearch_data *h) +{ + struct __htab *t = h->__tab; + ENTRY *e = slot(t, item.key); + if (!e->key) { + if (action == FIND) { + *ret = NULL; + errno = ESRCH; + return 0; + } + if ((t->used + 1) * 4 > (t->mask + 1) * 3) { + if (resize(t, t->used + 1) < 0) { + *ret = NULL; + errno = ENOMEM; + return 0; + } + e = slot(t, item.key); + } + *e = item; + t->used++; + } + *ret = e; + return 1; +} + +static struct hsearch_data global; + +__attribute__((weak)) int hcreate(size_t nel) +{ + if (global.__tab) + return 0; /* one table at a time, as POSIX has it */ + return hcreate_r(nel, &global); +} + +__attribute__((weak)) void hdestroy(void) { hdestroy_r(&global); } + +__attribute__((weak)) ENTRY *hsearch(ENTRY item, ACTION action) +{ + ENTRY *r; + if (!global.__tab) + return NULL; + return hsearch_r(item, action, &r, &global) ? r : NULL; +} + +/* ---- linear search, and queues ---------------------------------------- */ + +__attribute__((weak)) void *lfind(const void *key, const void *base, size_t *nel, size_t width, + cmp_fn cmp) +{ + const char *p = base; + for (size_t i = 0; i < *nel; i++, p += width) + if (cmp(key, p) == 0) + return (void *)p; + return NULL; +} + +__attribute__((weak)) void *lsearch(const void *key, void *base, size_t *nel, size_t width, + cmp_fn cmp) +{ + void *p = lfind(key, base, nel, width, cmp); + if (p) + return p; + p = (char *)base + *nel * width; + memcpy(p, key, width); + ++*nel; + return p; +} + +/* An element of a queue starts with its forward and backward links. */ +struct qelem { struct qelem *next, *prev; }; + +/* After pred, or as a one-element queue when pred is NULL. */ +__attribute__((weak)) void insque(void *elem, void *pred) +{ + struct qelem *e = elem, *p = pred; + if (!p) { + e->next = e->prev = NULL; + return; + } + e->next = p->next; + e->prev = p; + p->next = e; + if (e->next) + e->next->prev = e; +} + +__attribute__((weak)) void remque(void *elem) +{ + struct qelem *e = elem; + if (e->next) + e->next->prev = e->prev; + if (e->prev) + e->prev->next = e->next; +} diff --git a/lib/libc/src/posix/socket.c b/lib/libc/src/posix/socket.c new file mode 100644 index 000000000..811a3c273 --- /dev/null +++ b/lib/libc/src/posix/socket.c @@ -0,0 +1,105 @@ +/* : each call its primitive. The layouts are the seam's + * own (Linux's), so nothing is converted. */ +#include +#include + +#include "posix.h" + +WEAK int socket(int domain, int type, int protocol) +{ + return __os_socket(domain, type, protocol); +} + +WEAK int socketpair(int domain, int type, int protocol, int sv[2]) +{ + return __os_socketpair(domain, type, protocol, sv); +} + +WEAK int bind(int fd, const struct sockaddr *addr, socklen_t len) +{ + return __os_bind(fd, addr, len); +} + +WEAK int connect(int fd, const struct sockaddr *addr, socklen_t len) +{ + return __os_connect(fd, addr, len); +} + +WEAK int listen(int fd, int backlog) { return __os_listen(fd, backlog); } + +WEAK int accept(int fd, struct sockaddr *restrict addr, + socklen_t *restrict len) +{ + return __os_accept(fd, addr, len, 0); +} + +WEAK int accept4(int fd, struct sockaddr *restrict addr, + socklen_t *restrict len, int flags) +{ + return __os_accept(fd, addr, len, flags); +} + +WEAK ssize_t send(int fd, const void *buf, size_t n, int flags) +{ + return __os_sendto(fd, buf, n, flags, NULL, 0); +} + +WEAK ssize_t sendto(int fd, const void *buf, size_t n, int flags, + const struct sockaddr *addr, socklen_t len) +{ + return __os_sendto(fd, buf, n, flags, addr, len); +} + +WEAK ssize_t recv(int fd, void *buf, size_t n, int flags) +{ + return __os_recvfrom(fd, buf, n, flags, NULL, NULL); +} + +WEAK ssize_t recvfrom(int fd, void *restrict buf, size_t n, int flags, + struct sockaddr *restrict addr, socklen_t *restrict len) +{ + return __os_recvfrom(fd, buf, n, flags, addr, len); +} + +WEAK ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) +{ + return __os_sendmsg(fd, msg, flags); +} + +WEAK ssize_t recvmsg(int fd, struct msghdr *msg, int flags) +{ + return __os_recvmsg(fd, msg, flags); +} + +WEAK int shutdown(int fd, int how) { return __os_shutdown(fd, how); } + +WEAK int getsockopt(int fd, int level, int name, void *restrict val, + socklen_t *restrict len) +{ + return __os_getsockopt(fd, level, name, val, len); +} + +WEAK int setsockopt(int fd, int level, int name, const void *val, + socklen_t len) +{ + return __os_setsockopt(fd, level, name, val, len); +} + +WEAK int getsockname(int fd, struct sockaddr *restrict addr, + socklen_t *restrict len) +{ + return __os_getsockname(fd, addr, len); +} + +WEAK int getpeername(int fd, struct sockaddr *restrict addr, + socklen_t *restrict len) +{ + return __os_getpeername(fd, addr, len); +} + +/* SIOCATMARK: whether the next byte is the urgent one */ +WEAK int sockatmark(int fd) +{ + int at = 0; + return __os_ioctl(fd, 0x8905, &at) < 0 ? -1 : at; +} diff --git a/lib/libc/src/posix/spawn.c b/lib/libc/src/posix/spawn.c new file mode 100644 index 000000000..26c1cbe47 --- /dev/null +++ b/lib/libc/src/posix/spawn.c @@ -0,0 +1,323 @@ +/* : a list of file actions and a set of attributes, handed whole + * to __os_spawn, which makes the process -- by fork and exec on Linux, + * natively on EmbLinkOS. These functions return an error number, as + * POSIX's spawn functions do, rather than setting errno. + * + * The two types are handles: init allocates, destroy frees and sets the + * handle back to NULL, and a NULL handle -- or a pointer to one -- is + * EINVAL rather than a crash. */ +#include +#include +#include +#include +#include + +#include "posix.h" + +struct __spawn_actions { + int n, cap; + struct __os_spawn_action *a; +}; + +struct __spawn_attr { + short flags; + pid_t pgroup; + sigset_t sigdefault, sigmask; + int policy; + struct sched_param param; +}; + +WEAK int posix_spawn_file_actions_init(posix_spawn_file_actions_t *fa) +{ + if (!fa) + return EINVAL; + *fa = calloc(1, sizeof **fa); + return *fa ? 0 : ENOMEM; +} + +WEAK int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *fa) +{ + if (!fa || !*fa) + return EINVAL; + for (int i = 0; i < (*fa)->n; i++) + free((char *)(*fa)->a[i].path); + free((*fa)->a); + free(*fa); + *fa = NULL; + return 0; +} + +static int add(posix_spawn_file_actions_t *fa, int kind, int fd, int newfd, + const char *path, int oflag, mode_t mode) +{ + if (!fa || !*fa) + return EINVAL; + /* POSIX's EBADF: negative, or at or past OPEN_MAX */ + long max = sysconf(_SC_OPEN_MAX); + if (fd < 0 || (max > 0 && fd >= max) || + (kind == __OS_SPAWN_DUP2 && (newfd < 0 || (max > 0 && newfd >= max)))) + return EBADF; + struct __spawn_actions *s = *fa; + struct __os_spawn_action probe = { kind, fd, newfd, oflag, mode, path }; + int no = __os_spawn_check(&probe, s->n); + if (no) + return no; + if (s->n == s->cap) { + int cap = s->cap ? 2 * s->cap : 8; + struct __os_spawn_action *a = realloc(s->a, (size_t)cap * sizeof *a); + if (!a) + return ENOMEM; + s->a = a; + s->cap = cap; + } + char *copy = NULL; + if (path && !(copy = strdup(path))) + return ENOMEM; + struct __os_spawn_action *a = &s->a[s->n++]; + a->kind = kind; + a->fd = fd; + a->newfd = newfd; + a->oflag = oflag; + a->mode = mode; + a->path = copy; + return 0; +} + +WEAK int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *restrict fa, + int fd, const char *restrict path, + int oflag, mode_t mode) +{ + if (!path || (oflag & O_ACCMODE) == O_ACCMODE) + return EINVAL; + return add(fa, __OS_SPAWN_OPEN, fd, 0, path, oflag, mode); +} + +WEAK int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *fa, + int fd) +{ + return add(fa, __OS_SPAWN_CLOSE, fd, 0, NULL, 0, 0); +} + +WEAK int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *fa, + int fd, int newfd) +{ + return add(fa, __OS_SPAWN_DUP2, fd, newfd, NULL, 0, 0); +} + +WEAK int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t *restrict fa, + const char *restrict path) +{ + if (!path) + return EINVAL; + return add(fa, __OS_SPAWN_CHDIR, 0, 0, path, 0, 0); +} + +WEAK int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t *fa, + int fd) +{ + return add(fa, __OS_SPAWN_FCHDIR, fd, 0, NULL, 0, 0); +} + +WEAK int posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t *restrict fa, + const char *restrict path) +{ + return posix_spawn_file_actions_addchdir(fa, path); +} + +WEAK int posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *fa, + int fd) +{ + return posix_spawn_file_actions_addfchdir(fa, fd); +} + +WEAK int posix_spawnattr_init(posix_spawnattr_t *a) +{ + if (!a) + return EINVAL; + *a = calloc(1, sizeof **a); + return *a ? 0 : ENOMEM; +} + +WEAK int posix_spawnattr_destroy(posix_spawnattr_t *a) +{ + if (!a || !*a) + return EINVAL; + free(*a); + *a = NULL; + return 0; +} + +#define ALL_FLAGS (POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | \ + POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | \ + POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | \ + POSIX_SPAWN_USEVFORK | POSIX_SPAWN_SETSID) + +/* An unknown flag is EINVAL. A known one this OS's spawn cannot honour is + * ENOTSUP, now, rather than a spawn that later ignores it -- including + * the scheduling ones, which no backend applies. USEVFORK is only a hint + * about how, and is always accepted. */ +WEAK int posix_spawnattr_setflags(posix_spawnattr_t *a, short flags) +{ + if (!a || !*a || (flags & ~ALL_FLAGS)) + return EINVAL; + int honoured = __os_spawn_flags() | POSIX_SPAWN_USEVFORK; + if (flags & ~honoured) + return ENOTSUP; + (*a)->flags = flags; + return 0; +} + +#define ATTR_GET(name, type, field) \ + WEAK int posix_spawnattr_get##name(const posix_spawnattr_t *restrict a, \ + type *restrict out) \ + { \ + if (!a || !*a || !out) \ + return EINVAL; \ + *out = (*a)->field; \ + return 0; \ + } + +ATTR_GET(flags, short, flags) +ATTR_GET(pgroup, pid_t, pgroup) +ATTR_GET(sigdefault, sigset_t, sigdefault) +ATTR_GET(sigmask, sigset_t, sigmask) +ATTR_GET(schedpolicy, int, policy) +ATTR_GET(schedparam, struct sched_param, param) + +WEAK int posix_spawnattr_setpgroup(posix_spawnattr_t *a, pid_t g) +{ + if (!a || !*a) + return EINVAL; + (*a)->pgroup = g; + return 0; +} + +WEAK int posix_spawnattr_setschedpolicy(posix_spawnattr_t *a, int p) +{ + if (!a || !*a) + return EINVAL; + (*a)->policy = p; + return 0; +} + +WEAK int posix_spawnattr_setsigdefault(posix_spawnattr_t *restrict a, + const sigset_t *restrict s) +{ + if (!a || !*a || !s) + return EINVAL; + (*a)->sigdefault = *s; + return 0; +} + +WEAK int posix_spawnattr_setsigmask(posix_spawnattr_t *restrict a, + const sigset_t *restrict s) +{ + if (!a || !*a || !s) + return EINVAL; + (*a)->sigmask = *s; + return 0; +} + +WEAK int posix_spawnattr_setschedparam(posix_spawnattr_t *restrict a, + const struct sched_param *restrict p) +{ + if (!a || !*a || !p) + return EINVAL; + (*a)->param = *p; + return 0; +} + +/* The environment is passed as given -- NULL included, which the child + * receives as no environment at all (EmbLinkOS's rule, and Linux's execve + * reads it the same way). The caller's errno is left as it was. */ +WEAK int posix_spawn(pid_t *restrict pid, const char *restrict path, + const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *restrict attr, + char *const argv[restrict], char *const envp[restrict]) +{ + if (!path || (fa && !*fa) || (attr && !*attr)) + return EINVAL; + if (!*path) + return ENOENT; + struct __os_spawn_attr k = { 0, 0, 0, 0 }; + if (attr) { + k.flags = (*attr)->flags & (POSIX_SPAWN_RESETIDS | + POSIX_SPAWN_SETPGROUP | + POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_SETSIGMASK | + POSIX_SPAWN_SETSID); + k.pgroup = (*attr)->pgroup; + k.sigdefault = (*attr)->sigdefault; + k.sigmask = (*attr)->sigmask; + } + int saved = errno, child; + if (__os_spawn(&child, path, fa ? (*fa)->a : NULL, fa ? (*fa)->n : 0, + attr ? &k : NULL, argv, envp) < 0) { + int err = errno; + errno = saved; + return err; + } + errno = saved; + if (pid) + *pid = child; + return 0; +} + +/* exec*p's search, and its rule for a file the kernel cannot run + * (ENOEXEC): /bin/sh runs it as a script. With no PATH, the system's + * program directories -- EmbLinkOS's /system/bin among them. */ +WEAK int posix_spawnp(pid_t *restrict pid, const char *restrict file, + const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *restrict attr, + char *const argv[restrict], char *const envp[restrict]) +{ + if (!file) + return EINVAL; + if (strchr(file, '/')) + return posix_spawn(pid, file, fa, attr, argv, envp); + const char *path = getenv("PATH"); + if (!path) + path = "/system/bin:/usr/local/bin:/bin:/usr/bin"; + size_t fl = strlen(file); + int seen_eacces = 0; + char buf[PATH_MAX]; + for (const char *p = path;; p++) { + const char *end = strchr(p, ':'); + size_t dl = end ? (size_t)(end - p) : strlen(p); + if (dl + 1 + fl + 1 <= sizeof buf) { + memcpy(buf, p, dl); + size_t at = dl; + if (dl) + buf[at++] = '/'; + memcpy(buf + at, file, fl + 1); + int e = posix_spawn(pid, buf, fa, attr, argv, envp); + if (e == ENOEXEC) { + int n = 0; + while (argv[n]) + n++; + char **v = malloc((size_t)(n + 2) * sizeof *v); + if (!v) + return ENOMEM; + v[0] = (char *)"sh"; + v[1] = buf; + for (int i = 1; i <= n; i++) + v[i + 1] = argv[i]; + if (n == 0) + v[2] = NULL; + e = posix_spawn(pid, _PATH_BSHELL, fa, attr, v, envp); + free(v); + return e; + } + if (e == 0) + return 0; + if (e == EACCES) + seen_eacces = 1; + else if (e != ENOENT && e != ENOTDIR) + return e; + } + if (!end) + break; + p = end; + } + return seen_eacces ? EACCES : ENOENT; +} diff --git a/lib/libc/src/posix/sysconf.c b/lib/libc/src/posix/sysconf.c new file mode 100644 index 000000000..d245a5015 --- /dev/null +++ b/lib/libc/src/posix/sysconf.c @@ -0,0 +1,220 @@ +/* What the system is: sysconf and confstr, uname and the host name, + * limits and usage, the scheduling queries. + * + * sysconf's answers are of two kinds. Most are this library's own + * limits -- a line it reads, a name it stores -- and are the same on + * every target. A few only the running system knows (processors, + * memory, the page size), and those are asked of the backend. */ +#include +#include +#include +#include +#include +#include + +#include "posix.h" + +/* The page size is not like the processor count: a program needs one to + * do arithmetic with, and where there is no MMU any power of two is + * right -- so 4096 stands in where the backend has no answer. */ +static long ask(int what, long otherwise) +{ + int saved = errno; + long r = __os_sysinfo(what); + errno = saved; + return r > 0 ? r : otherwise; +} + +WEAK long sysconf(int name) +{ + switch (name) { + case _SC_ARG_MAX: return ARG_MAX; + case _SC_CHILD_MAX: return 32; + case _SC_CLK_TCK: return 100; + case _SC_NGROUPS_MAX: return NGROUPS_MAX; + case _SC_OPEN_MAX: { + unsigned long long lim[2]; + int saved = errno; + if (__os_getrlimit(7 /* RLIMIT_NOFILE */, lim) == 0 && + lim[0] < (unsigned long long)LONG_MAX) + return (long)lim[0]; + errno = saved; + return OPEN_MAX; + } + case _SC_STREAM_MAX: return FOPEN_MAX; + case _SC_TZNAME_MAX: return 6; + case _SC_JOB_CONTROL: return 1; + case _SC_SAVED_IDS: return 1; + case _SC_VERSION: return _POSIX_VERSION; + case _SC_2_VERSION: return _POSIX2_VERSION; + case _SC_XOPEN_VERSION: return _XOPEN_VERSION; + case _SC_PAGESIZE: return ask(__OS_INFO_PAGESIZE, 4096); + case _SC_RTSIG_MAX: return 64 - 34 + 1; /* SIGRTMAX - SIGRTMIN + 1 */ + case _SC_SIGQUEUE_MAX: return 32; + case _SC_BC_BASE_MAX: return 99; + case _SC_BC_DIM_MAX: return 2048; + case _SC_BC_SCALE_MAX: return 99; + case _SC_BC_STRING_MAX: return 1000; + case _SC_COLL_WEIGHTS_MAX: return 2; + case _SC_EXPR_NEST_MAX: return 32; + case _SC_LINE_MAX: return 2048; + case _SC_RE_DUP_MAX: return 255; + case _SC_IOV_MAX: return 1024; + case _SC_GETGR_R_SIZE_MAX: return 1024; + case _SC_GETPW_R_SIZE_MAX: return 1024; + case _SC_LOGIN_NAME_MAX: return 256; + case _SC_TTY_NAME_MAX: return 32; + case _SC_HOST_NAME_MAX: return 64; + case _SC_SYMLOOP_MAX: return 40; + case _SC_ATEXIT_MAX: return 32; + case _SC_THREAD_STACK_MIN: return 16384; + case _SC_FSYNC: + case _SC_MAPPED_FILES: + case _SC_MONOTONIC_CLOCK: + case _SC_SPAWN: return _POSIX_VERSION; + /* A count the system cannot give is -1, not a plausible 1 that a + * caller could not tell from a measured one (os.cpu_count() then + * says None, which is the truth). */ + case _SC_NPROCESSORS_CONF: return __os_sysinfo(__OS_INFO_NPROCESSORS_CONF); + case _SC_NPROCESSORS_ONLN: return __os_sysinfo(__OS_INFO_NPROCESSORS_ONLN); + case _SC_PHYS_PAGES: + case _SC_AVPHYS_PAGES: { + long r = __os_sysinfo(name == _SC_PHYS_PAGES + ? __OS_INFO_PHYS_PAGES + : __OS_INFO_AVPHYS_PAGES); + return r; /* -1 with errno where it cannot say */ + } + /* options this library does not offer: -1 with errno unchanged, + * which is POSIX's "no such option" */ + case _SC_REALTIME_SIGNALS: + case _SC_TIMERS: + case _SC_SEMAPHORES: + case _SC_THREADS: + case _SC_THREAD_SAFE_FUNCTIONS: + case _SC_SEM_NSEMS_MAX: + case _SC_THREAD_KEYS_MAX: + case _SC_THREAD_THREADS_MAX: + return -1; + } + errno = EINVAL; + return -1; +} + +/* the value's length with its terminator; 0 with EINVAL for no such name */ +WEAK size_t confstr(int name, char *buf, size_t n) +{ + const char *v; + switch (name) { + case _CS_PATH: v = "/bin:/usr/bin"; break; + default: errno = EINVAL; return 0; + } + size_t len = strlen(v) + 1; + if (buf && n) { + size_t k = len < n ? len : n; + memcpy(buf, v, k - 1); + buf[k - 1] = 0; + } + return len; +} + +WEAK int getpagesize(void) { return (int)sysconf(_SC_PAGESIZE); } +WEAK int getdtablesize(void) { return (int)sysconf(_SC_OPEN_MAX); } + +WEAK int uname(struct utsname *u) +{ + struct __os_utsname k; + if (__os_uname(&k) < 0) + return -1; + memset(u, 0, sizeof *u); + memcpy(u->sysname, k.sysname, 65); + memcpy(u->nodename, k.nodename, 65); + memcpy(u->release, k.release, 65); + memcpy(u->version, k.version, 65); + memcpy(u->machine, k.machine, 65); + return 0; +} + +/* POSIX leaves a name that does not fit unterminated; this one is + * terminated and reported, as glibc reports it, with ENAMETOOLONG */ +WEAK int gethostname(char *name, size_t n) +{ + struct utsname u; + if (uname(&u) < 0) + return -1; + size_t k = strlen(u.nodename); + if (k + 1 > n) { + if (n) { + memcpy(name, u.nodename, n - 1); + name[n - 1] = 0; + } + errno = ENAMETOOLONG; + return -1; + } + memcpy(name, u.nodename, k + 1); + return 0; +} + +WEAK int sethostname(const char *name, size_t n) +{ + return __os_sethostname(name, n); +} + +WEAK int getrlimit(int resource, struct rlimit *lim) +{ + unsigned long long k[2]; + if (__os_getrlimit(resource, k) < 0) + return -1; + lim->rlim_cur = k[0]; + lim->rlim_max = k[1]; + return 0; +} + +WEAK int setrlimit(int resource, const struct rlimit *lim) +{ + unsigned long long k[2] = { lim->rlim_cur, lim->rlim_max }; + return __os_setrlimit(resource, k); +} + +WEAK int getrusage(int who, struct rusage *ru) +{ + struct __os_rusage k; + if (__os_getrusage(who, &k) < 0) + return -1; + memset(ru, 0, sizeof *ru); + ru->ru_utime.tv_sec = (time_t)(k.utime_us / 1000000); + ru->ru_utime.tv_usec = (suseconds_t)(k.utime_us % 1000000); + ru->ru_stime.tv_sec = (time_t)(k.stime_us / 1000000); + ru->ru_stime.tv_usec = (suseconds_t)(k.stime_us % 1000000); + ru->ru_maxrss = k.maxrss_kb; + return 0; +} + +/* ---- scheduling ---------------------------------------------------------- */ + +WEAK int sched_yield(void) +{ + __os_thread_yield(); + return 0; +} + +/* the policies' own ranges, Linux's: they define the policy, so they + * are not a question for the kernel */ +WEAK int sched_get_priority_max(int policy) +{ + if (policy == SCHED_FIFO || policy == SCHED_RR) + return 99; + if (policy == SCHED_OTHER) + return 0; + errno = EINVAL; + return -1; +} + +WEAK int sched_get_priority_min(int policy) +{ + if (policy == SCHED_FIFO || policy == SCHED_RR) + return 1; + if (policy == SCHED_OTHER) + return 0; + errno = EINVAL; + return -1; +} diff --git a/lib/libc/src/posix/syslog.c b/lib/libc/src/posix/syslog.c new file mode 100644 index 000000000..1391f8302 --- /dev/null +++ b/lib/libc/src/posix/syslog.c @@ -0,0 +1,167 @@ +/* : RFC 3164's line, "Mmm dd hh:mm:ss ident[pid]: text", + * to the datagram socket /dev/log, as glibc writes it; the connection is + * made at the first message (or at openlog with LOG_NDELAY) and made + * again if the logger went away. %m is strerror(errno), taken before + * anything here can change errno. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "posix.h" +#include "../internal/lock.h" + +static __lock_t g_log_lock = LOCK_INIT; +static const char *g_ident; +static int g_option, g_facility = LOG_USER, g_mask = 0xff, g_fd = -1; + +static void connect_log(void) +{ + if (g_fd >= 0) + return; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strcpy(a.sun_path, "/dev/log"); + int types[2] = { SOCK_DGRAM, SOCK_STREAM }; + for (int i = 0; i < 2; i++) { + int fd = socket(AF_UNIX, types[i] | SOCK_CLOEXEC, 0); + if (fd < 0) + continue; + if (connect(fd, (struct sockaddr *)&a, sizeof a) == 0) { + g_fd = fd; + return; + } + close(fd); + } +} + +WEAK void openlog(const char *ident, int option, int facility) +{ + __lock(&g_log_lock); + g_ident = ident; + g_option = option; + if (facility && !(facility & ~LOG_FACMASK)) + g_facility = facility; + if (option & LOG_NDELAY) + connect_log(); + __unlock(&g_log_lock); +} + +WEAK void closelog(void) +{ + __lock(&g_log_lock); + if (g_fd >= 0) + close(g_fd); + g_fd = -1; + g_ident = NULL; + __unlock(&g_log_lock); +} + +WEAK int setlogmask(int mask) +{ + __lock(&g_log_lock); + int old = g_mask; + if (mask) + g_mask = mask; + __unlock(&g_log_lock); + return old; +} + +static void write_all(int fd, const char *s, size_t n) +{ + while (n) { + ssize_t w = write(fd, s, n); + if (w <= 0) + return; + s += w; + n -= (size_t)w; + } +} + +WEAK void vsyslog(int priority, const char *format, va_list ap) +{ + int saved = errno; + if (!(g_mask & LOG_MASK(LOG_PRI(priority))) || + (priority & ~(LOG_PRIMASK | LOG_FACMASK))) + return; + /* %m first: it is errno as the caller left it */ + char fmt[512]; + size_t o = 0; + for (const char *p = format; *p && o + 1 < sizeof fmt; p++) { + if (p[0] == '%' && p[1] == 'm') { + const char *e = strerror(saved); + for (; *e && o + 1 < sizeof fmt; e++) + fmt[o++] = *e == '%' ? '?' : *e; + p++; + } else if (p[0] == '%' && p[1] == '%' && o + 2 < sizeof fmt) { + fmt[o++] = *p++; + fmt[o++] = *p; + } else { + fmt[o++] = *p; + } + } + fmt[o] = 0; + + __lock(&g_log_lock); + if (!(priority & LOG_FACMASK)) + priority |= g_facility; + char line[1024], stamp[32]; + time_t now = time(NULL); + struct tm tm; + if (!localtime_r(&now, &tm) || !strftime(stamp, sizeof stamp, "%b %e %T", &tm)) + strcpy(stamp, "Jan 1 00:00:00"); + const char *ident = g_ident ? g_ident : program_invocation_short_name; + int head = snprintf(line, sizeof line, "<%d>%s ", priority, stamp); + int msg = head; + msg += snprintf(line + head, sizeof line - (size_t)head, + (g_option & LOG_PID) ? "%s[%d]: " : "%s: ", + ident ? ident : "", (int)getpid()); + if (msg >= (int)sizeof line) + msg = (int)sizeof line - 1; + vsnprintf(line + msg, sizeof line - (size_t)msg, fmt, ap); + size_t len = strlen(line); + + if (g_option & LOG_PERROR) { + write_all(2, line + head, len - (size_t)head); + if (!len || line[len - 1] != '\n') + write_all(2, "\n", 1); + } + connect_log(); + int sent = g_fd >= 0 && send(g_fd, line, len, MSG_NOSIGNAL) >= 0; + if (!sent && g_fd >= 0) { /* the logger restarted: once more */ + close(g_fd); + g_fd = -1; + connect_log(); + sent = g_fd >= 0 && send(g_fd, line, len, MSG_NOSIGNAL) >= 0; + } + if (!sent) { + int fd = (g_option & LOG_CONS) ? open("/dev/console", + O_WRONLY | O_NOCTTY | O_CLOEXEC) + : -1; + if (fd >= 0 || !(g_option & LOG_PERROR)) { + int to = fd >= 0 ? fd : 2; + write_all(to, line + head, len - (size_t)head); + if (!len || line[len - 1] != '\n') + write_all(to, "\n", 1); + } + if (fd >= 0) + close(fd); + } + __unlock(&g_log_lock); + errno = saved; +} + +WEAK void syslog(int priority, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vsyslog(priority, format, ap); + va_end(ap); +} diff --git a/lib/libc/src/posix/termios.c b/lib/libc/src/posix/termios.c new file mode 100644 index 000000000..a21d759d8 --- /dev/null +++ b/lib/libc/src/posix/termios.c @@ -0,0 +1,196 @@ +/* : terminal modes through the seam's struct, which is + * glibc's layout -- and the line controls (drain, flush, flow, break) + * and job control through ioctls, whose requests are Linux's. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include + +#include "posix.h" + +_Static_assert(sizeof(struct termios) == sizeof(struct __os_termios), + "struct termios is not the seam's"); + +WEAK int tcgetattr(int fd, struct termios *t) +{ + return __os_tcgetattr(fd, (struct __os_termios *)(void *)t); +} + +WEAK int tcsetattr(int fd, int action, const struct termios *t) +{ + return __os_tcsetattr(fd, action, + (const struct __os_termios *)(const void *)t); +} + +/* The speed is the CBAUD bits of c_cflag, as glibc keeps it; the + * c_ispeed and c_ospeed fields follow along. */ +WEAK speed_t cfgetospeed(const struct termios *t) { return t->c_cflag & CBAUD; } +WEAK speed_t cfgetispeed(const struct termios *t) { return cfgetospeed(t); } + +WEAK int cfsetospeed(struct termios *t, speed_t s) +{ + if (s & ~CBAUD) { + errno = EINVAL; + return -1; + } + t->c_cflag = (t->c_cflag & ~CBAUD) | s; + t->c_ospeed = s; + return 0; +} + +/* 0 means "the same as the output speed", as POSIX says */ +WEAK int cfsetispeed(struct termios *t, speed_t s) +{ + if (s & ~CBAUD) { + errno = EINVAL; + return -1; + } + if (s) + t->c_cflag = (t->c_cflag & ~CBAUD) | s; + t->c_ispeed = s ? s : t->c_ospeed; + return 0; +} + +WEAK int cfsetspeed(struct termios *t, speed_t s) +{ + if (cfsetospeed(t, s) < 0) + return -1; + return cfsetispeed(t, s); +} + +/* the raw mode BSD's cfmakeraw makes */ +WEAK void cfmakeraw(struct termios *t) +{ + t->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL | IXON); + t->c_oflag &= ~OPOST; + t->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + t->c_cflag &= ~(CSIZE | PARENB); + t->c_cflag |= CS8; + t->c_cc[VMIN] = 1; + t->c_cc[VTIME] = 0; +} + +WEAK int tcdrain(int fd) { return __os_ioctl(fd, TCSBRK, (void *)1); } + +WEAK int tcflush(int fd, int queue) +{ + return __os_ioctl(fd, TCFLSH, (void *)(long)queue); +} + +WEAK int tcflow(int fd, int action) +{ + return __os_ioctl(fd, TCXONC, (void *)(long)action); +} + +WEAK int tcsendbreak(int fd, int duration) +{ + (void)duration; + return __os_ioctl(fd, TCSBRK, (void *)0); +} + +WEAK pid_t tcgetpgrp(int fd) +{ + int pgrp; + return __os_ioctl(fd, TIOCGPGRP, &pgrp) < 0 ? -1 : pgrp; +} + +WEAK int tcsetpgrp(int fd, pid_t pgrp) +{ + int p = pgrp; + return __os_ioctl(fd, TIOCSPGRP, &p); +} + +WEAK pid_t tcgetsid(int fd) +{ + int sid; + return __os_ioctl(fd, TIOCGSID, &sid) < 0 ? -1 : sid; +} + +WEAK int tcgetwinsize(int fd, struct winsize *ws) +{ + return __os_ioctl(fd, TIOCGWINSZ, ws); +} + +WEAK int tcsetwinsize(int fd, const struct winsize *ws) +{ + return __os_ioctl(fd, TIOCSWINSZ, (void *)ws); +} + +WEAK int ttyname_r(int fd, char *buf, size_t n) +{ + int saved = errno; + if (__os_ttyname(fd, buf, n) == 0) + return 0; + int e = errno; + errno = saved; + return e; +} + +WEAK char *ttyname(int fd) +{ + static char buf[64]; + int e = ttyname_r(fd, buf, sizeof buf); + if (e) { + errno = e; + return NULL; + } + return buf; +} + +/* getpass, as glibc does it: the controlling terminal if /dev/tty opens, + * else standard input with the prompt on standard error; echo and the + * signal characters off while the line is typed, and the newline the + * user's Enter did not echo written afterwards. The line, without its + * newline, in a static buffer; longer lines are cut, never overflowed. */ +WEAK char *getpass(const char *prompt) +{ + static char buf[128]; + int in = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC), out = in; + int own = in >= 0; + if (!own) { + in = STDIN_FILENO; + out = STDERR_FILENO; + } + struct termios old, t; + int quiet = 0; + if (tcgetattr(in, &old) == 0) { + t = old; + t.c_lflag &= ~(tcflag_t)(ECHO | ISIG); + quiet = tcsetattr(in, TCSAFLUSH, &t) == 0; + } + if (prompt && *prompt) { + size_t n = strlen(prompt); + for (size_t w = 0; w < n;) { + ssize_t r = write(out, prompt + w, n - w); + if (r <= 0) + break; + w += (size_t)r; + } + } + size_t n = 0; + int any = 0; + for (;;) { + char c; + ssize_t r = read(in, &c, 1); + if (r < 0 && errno == EINTR) + continue; + if (r <= 0) + break; + any = 1; + if (c == '\n') + break; + if (n + 1 < sizeof buf) + buf[n++] = c; + } + buf[n] = 0; + if (quiet) { + tcsetattr(in, TCSAFLUSH, &old); + (void)!write(out, "\n", 1); + } + if (own) + close(in); + return any ? buf : NULL; +} diff --git a/lib/libc/src/posix/time.c b/lib/libc/src/posix/time.c new file mode 100644 index 000000000..7b6bb60bc --- /dev/null +++ b/lib/libc/src/posix/time.c @@ -0,0 +1,139 @@ +/* POSIX's clocks: clock_gettime and its family, gettimeofday, the + * interval timers and times(). A backend without clock_gettime still has + * __os_time for the real-time clock's seconds, and that is used rather + * than failing a program that asked only for the date. */ +#include +#include +#include +#include +#include + +#include "posix.h" + +WEAK int clock_gettime(clockid_t clock, struct timespec *t) +{ + struct __os_timespec k; + if (__os_clock_gettime(clock, &k) == 0) { + t->tv_sec = (time_t)k.sec; + t->tv_nsec = k.nsec; + return 0; + } + if (errno == ENOSYS && clock == CLOCK_REALTIME) { + long s = __os_time(); + if (s < 0) + return -1; + t->tv_sec = s; + t->tv_nsec = 0; + return 0; + } + return -1; +} + +WEAK int clock_getres(clockid_t clock, struct timespec *t) +{ + struct __os_timespec k; + if (__os_clock_getres(clock, &k) < 0) { + if (errno != ENOSYS || clock != CLOCK_REALTIME) + return -1; + k.sec = 1; /* __os_time's resolution: a second */ + k.nsec = 0; + } + if (t) { + t->tv_sec = (time_t)k.sec; + t->tv_nsec = k.nsec; + } + return 0; +} + +WEAK int clock_settime(clockid_t clock, const struct timespec *t) +{ + struct __os_timespec k = { t->tv_sec, t->tv_nsec }; + return __os_clock_settime(clock, &k); +} + +/* returns the error, as POSIX's clock_nanosleep does, not -1 */ +WEAK int clock_nanosleep(clockid_t clock, int flags, + const struct timespec *req, struct timespec *rem) +{ + struct __os_timespec k = { req->tv_sec, req->tv_nsec }, kr; + int saved = errno; + if (__os_clock_nanosleep(clock, flags & TIMER_ABSTIME, &k, + rem ? &kr : NULL) == 0) + return 0; + int err = errno; + errno = saved; + if (err == EINTR && rem && !(flags & TIMER_ABSTIME)) { + rem->tv_sec = (time_t)kr.sec; + rem->tv_nsec = kr.nsec; + } + return err; +} + +/* tz is obsolete, and filled with zeros where it is asked for */ +WEAK int gettimeofday(struct timeval *restrict tv, void *restrict tz) +{ + struct timespec t; + if (tz) + memset(tz, 0, sizeof(struct timezone)); + if (!tv) + return 0; + if (clock_gettime(CLOCK_REALTIME, &t) < 0) + return -1; + tv->tv_sec = t.tv_sec; + tv->tv_usec = t.tv_nsec / 1000; + return 0; +} + +WEAK int settimeofday(const struct timeval *tv, const struct timezone *tz) +{ + (void)tz; + if (!tv) + return 0; + struct timespec t = { tv->tv_sec, tv->tv_usec * 1000 }; + return clock_settime(CLOCK_REALTIME, &t); +} + +static void itimer_out(const struct __os_itimer *k, struct itimerval *v) +{ + v->it_interval.tv_sec = (time_t)k->isec; + v->it_interval.tv_usec = (suseconds_t)k->iusec; + v->it_value.tv_sec = (time_t)k->vsec; + v->it_value.tv_usec = (suseconds_t)k->vusec; +} + +WEAK int setitimer(int which, const struct itimerval *restrict val, + struct itimerval *restrict old) +{ + struct __os_itimer k = { val->it_interval.tv_sec, val->it_interval.tv_usec, + val->it_value.tv_sec, val->it_value.tv_usec }; + struct __os_itimer ko; + if (__os_setitimer(which, &k, old ? &ko : NULL) < 0) + return -1; + if (old) + itimer_out(&ko, old); + return 0; +} + +WEAK int getitimer(int which, struct itimerval *cur) +{ + struct __os_itimer k; + if (__os_getitimer(which, &k) < 0) + return -1; + itimer_out(&k, cur); + return 0; +} + +WEAK clock_t times(struct tms *t) +{ + long long k[4]; + long long r = __os_times(k); + if (r == -1) + return (clock_t)-1; + if (t) { + t->tms_utime = (clock_t)k[0]; + t->tms_stime = (clock_t)k[1]; + t->tms_cutime = (clock_t)k[2]; + t->tms_cstime = (clock_t)k[3]; + } + return (clock_t)r; +} diff --git a/lib/libc/src/stdio/file.h b/lib/libc/src/stdio/file.h index e30241f45..16748f886 100644 --- a/lib/libc/src/stdio/file.h +++ b/lib/libc/src/stdio/file.h @@ -11,6 +11,18 @@ #include "../internal/lock.h" +/* The four operations of a stream that is NOT a descriptor -- fmemopen's, + * open_memstream's, fopencookie's. Laid out as glibc's + * cookie_io_functions_t, which fopencookie copies in whole. A stream + * carries them only with F_COOKIE set; every other stream reads and + * writes its descriptor through the backend, as it always has. */ +struct __stream_io { + __PTRDIFF_TYPE__ (*read)(void *cookie, char *buf, size_t n); + __PTRDIFF_TYPE__ (*write)(void *cookie, const char *buf, size_t n); + int (*seek)(void *cookie, long long *off, int whence); + int (*close)(void *cookie); +}; + struct _FILE { int fd; int flags; @@ -27,6 +39,15 @@ struct _FILE { * files have nothing to say to each other. Last, so the positional * initialisers of the three standard streams stay readable. */ __lock_t lock; + /* The lock NESTS, for POSIX's flockfile: a thread that holds a + * stream may call printf on it, which takes the lock again. owner is + * meaningful only while depth > 0 -- the thread holding it, by + * __os_thread_self, which is 0 for every caller where there are no + * threads, and there the depth alone does the counting. */ + unsigned long owner; + int depth; + struct __stream_io io; /* F_COOKIE streams only */ + void *cookie; }; #define F_READ 0x01 @@ -43,12 +64,25 @@ struct _FILE { * `ungot` holds a wide character: there are no byte reads to see it. */ #define F_WIDE 0x80 #define F_BYTE 0x100 +/* The stream is its io and cookie, not a descriptor (fd is -1). */ +#define F_COOKIE 0x200 #define __byte_orient(f) \ ((f)->flags & (F_WIDE | F_BYTE) ? (void)0 : (void)((f)->flags |= F_BYTE)) void __stdio_flush_all(void); int __stdio_flush(FILE *f); +/* Opening, in three steps that every way of opening shares (fopen, + * fdopen, fopencookie, the memory streams): a free FILE, taken under the + * table's lock and marked F_USED, or NULL with EMFILE; the mode string's + * open flags and F_READ/F_WRITE/F_APPEND bits, or -1 with EINVAL; and the + * buffer, once the stream has its fd or io. A FILE claimed and then not + * wanted is given back with __stdio_unclaim. */ +FILE *__stdio_claim(void); +void __stdio_unclaim(FILE *f); +int __stdio_mode(const char *mode, int *oflags, int *acc); +void __stdio_setup(FILE *f, int acc); + /* The unlocked halves, for callers that ALREADY hold f's lock. * * They exist because the lock has to be taken at the outermost public diff --git a/lib/libc/src/stdio/memstream.c b/lib/libc/src/stdio/memstream.c new file mode 100644 index 000000000..122f269ed --- /dev/null +++ b/lib/libc/src/stdio/memstream.c @@ -0,0 +1,238 @@ +/* The memory streams: fmemopen, a stream over a buffer of a fixed size, + * and open_memstream, a write stream into memory that grows. + * + * Both are ordinary buffered streams whose io is here (file.h's + * __stream_io): what a program wrote is in its memory once the stream is + * flushed or closed, as POSIX says, and not necessarily before -- so a + * program must fflush before reading the buffer, as it must with glibc. + */ +#include "file.h" + +#include +#include +#include + +static FILE *open_io(void *cookie, const struct __stream_io *io, int acc) +{ + FILE *f = __stdio_claim(); + if (!f) + return NULL; + f->flags |= F_COOKIE; + f->cookie = cookie; + f->io = *io; + __stdio_setup(f, acc); + return f; +} + +/* ---- fmemopen ---------------------------------------------------------- + * + * The rules are POSIX.1-2008's as glibc implements them since 2.22: + * + * size the buffer's; nothing is ever written past it + * end how much of it holds data: all of it for "r", none for "w" + * (which also writes a NUL at the start), up to the first NUL + * for "a" -- and every append goes there + * NUL a write that moves `end` puts a NUL after the data when there + * is room, so the buffer is a string; a write that fills the + * buffer puts one in its last byte instead, unless the data + * itself ended with one or the stream appends + * + * A seek may go anywhere from 0 to size. Reading stops at `end`. */ +struct mem { + char *buf; + size_t size, pos, end; + int append, own; +}; + +static ssize_t mem_read(void *c, char *b, size_t n) +{ + struct mem *m = c; + size_t have = m->pos < m->end ? m->end - m->pos : 0; + if (n > have) + n = have; + memcpy(b, m->buf + m->pos, n); + m->pos += n; + return (ssize_t)n; +} + +static ssize_t mem_write(void *c, const char *b, size_t n) +{ + struct mem *m = c; + int nul = n == 0 || b[n - 1] != 0; + if (m->append) + m->pos = m->end; + if (m->pos + n > m->size) { + if (m->pos + nul >= m->size) { + errno = ENOSPC; + return 0; + } + n = m->size - m->pos; + } + memcpy(m->buf + m->pos, b, n); + m->pos += n; + if (m->pos > m->end) { + m->end = m->pos; + if (m->end < m->size && nul) + m->buf[m->end] = 0; + else if (!m->append && nul) + m->buf[m->size - 1] = 0; + } + return (ssize_t)n; +} + +static int mem_seek(void *c, long long *off, int whence) +{ + struct mem *m = c; + long long base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? (long long)m->pos + : whence == SEEK_END ? (long long)m->end : -1; + if (base < 0 || *off < -base || base + *off > (long long)m->size) { + errno = EINVAL; + return -1; + } + m->pos = (size_t)(base + *off); + *off = (long long)m->pos; + return 0; +} + +static int mem_close(void *c) +{ + struct mem *m = c; + if (m->own) + free(m->buf); + free(m); + return 0; +} + +static const struct __stream_io mem_io = { + mem_read, mem_write, mem_seek, mem_close +}; + +/* A NULL buf asks for one of size bytes, freed at close: useful only + * with "+", since nothing else could read what was written there. */ +__attribute__((weak)) FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode) +{ + int fl, acc; + if (size == 0) { + errno = EINVAL; + return NULL; + } + if (__stdio_mode(mode, &fl, &acc) < 0) + return NULL; + struct mem *m = calloc(1, sizeof *m); + if (!m) + return NULL; + m->buf = buf; + if (!buf) { + m->buf = calloc(1, size); + m->own = 1; + if (!m->buf) { + free(m); + return NULL; + } + } + m->size = size; + switch (mode[0]) { + case 'r': m->end = buf ? size : 0; break; + case 'w': m->end = 0; m->buf[0] = 0; break; + default: m->append = 1; + m->pos = m->end = strnlen(m->buf, size); break; + } + FILE *f = open_io(m, &mem_io, acc); + if (!f) + mem_close(m); + return f; +} + +/* ---- open_memstream ---------------------------------------------------- + * + * Write-only, into memory that grows. After every flush *bufp is the + * memory and *sizep the position, with a NUL there; a seek past the end + * and a write make a gap, which reads as zeros. The memory is the + * caller's to free, after fclose. */ +struct dyn { + char **bufp; + size_t *sizep; + char *buf; + size_t cap, pos, len; +}; + +static void publish(struct dyn *d) +{ + *d->bufp = d->buf; + *d->sizep = d->pos < d->len ? d->pos : d->len; +} + +static ssize_t dyn_write(void *c, const char *b, size_t n) +{ + struct dyn *d = c; + if (d->pos + n + 1 > d->cap) { + size_t cap = d->cap * 2; + if (cap < d->pos + n + 1) + cap = d->pos + n + 1; + char *p = realloc(d->buf, cap); + if (!p) { + errno = ENOMEM; + return 0; + } + memset(p + d->cap, 0, cap - d->cap); + d->buf = p; + d->cap = cap; + } + memcpy(d->buf + d->pos, b, n); + d->pos += n; + if (d->pos > d->len) + d->len = d->pos; + d->buf[d->len] = 0; + publish(d); + return (ssize_t)n; +} + +static int dyn_seek(void *c, long long *off, int whence) +{ + struct dyn *d = c; + long long base = whence == SEEK_SET ? 0 + : whence == SEEK_CUR ? (long long)d->pos + : whence == SEEK_END ? (long long)d->len : -1; + if (base < 0 || *off < -base) { + errno = EINVAL; + return -1; + } + d->pos = (size_t)(base + *off); + *off = (long long)d->pos; + publish(d); + return 0; +} + +static int dyn_close(void *c) +{ + publish(c); + free(c); + return 0; +} + +static const struct __stream_io dyn_io = { NULL, dyn_write, dyn_seek, + dyn_close }; + +__attribute__((weak)) FILE *open_memstream(char **bufp, size_t *sizep) +{ + if (!bufp || !sizep) { + errno = EINVAL; + return NULL; + } + struct dyn *d = calloc(1, sizeof *d); + char *buf = calloc(1, 64); + if (!d || !buf) { + free(d); + free(buf); + return NULL; + } + *d = (struct dyn){ bufp, sizep, buf, 64, 0, 0 }; + publish(d); + FILE *f = open_io(d, &dyn_io, F_WRITE); + if (!f) { + free(buf); + free(d); + } + return f; +} diff --git a/lib/libc/src/stdio/posix.c b/lib/libc/src/stdio/posix.c new file mode 100644 index 000000000..331391d02 --- /dev/null +++ b/lib/libc/src/stdio/posix.c @@ -0,0 +1,267 @@ +/* 's POSIX functions beyond C's, and the GNU ones Unix programs + * reach for: streams on a descriptor (fdopen, fileno) or on a caller's + * functions (fopencookie), line reading of any length (getline, + * getdelim), printf to a descriptor or to new memory (dprintf, asprintf), + * the 64-bit offsets' names, and the unlocked forms. + * + * The memory streams, fmemopen and open_memstream, are memstream.c. */ +#include "file.h" + +#include +#include +#include +#include + +#include "../../os/backend.h" + +/* A stream on a descriptor already open. The mode must not ask for more + * than the descriptor allows, but nothing here can ask the OS what that + * is, so a mismatch surfaces at the first read or write, as EBADF -- + * where glibc would have refused at fdopen. "w" does NOT truncate: the + * file is already open and fdopen does not reopen it. */ +__attribute__((weak)) FILE *fdopen(int fd, const char *mode) +{ + int fl, acc; + if (fd < 0) { + errno = EBADF; + return NULL; + } + if (__stdio_mode(mode, &fl, &acc) < 0) + return NULL; + FILE *f = __stdio_claim(); + if (!f) + return NULL; + f->fd = fd; + __stdio_setup(f, acc); + return f; +} + +__attribute__((weak)) int fileno(FILE *f) +{ + if (!f || !(f->flags & F_USED) || (f->flags & F_COOKIE)) { + errno = EBADF; + return -1; + } + return f->fd; +} + +/* A stream whose reads, writes, seeks and close are the caller's. */ +__attribute__((weak)) FILE *fopencookie(void *cookie, const char *mode, cookie_io_functions_t io) +{ + int fl, acc; + if (__stdio_mode(mode, &fl, &acc) < 0) + return NULL; + FILE *f = __stdio_claim(); + if (!f) + return NULL; + f->flags |= F_COOKIE; + f->cookie = cookie; + f->io.read = io.read; + f->io.write = io.write; + f->io.seek = io.seek; + f->io.close = io.close; + __stdio_setup(f, acc); + return f; +} + +/* ---- lines of any length --------------------------------------------- */ + +/* Up to and including delim, NUL-terminated, in *line -- which is grown + * with realloc as the line needs, and allocated when it is NULL. The + * length, which may count NULs in the line; -1 at end of file with + * nothing read, or on error. The buffer exists afterwards even then, as + * glibc leaves it, so a caller's free(line) is always right. */ +__attribute__((weak)) ssize_t getdelim(char **restrict line, size_t *restrict n, int delim, + FILE *restrict f) +{ + if (!line || !n || !f) { + errno = EINVAL; + return -1; + } + if (!*line || *n == 0) { + char *p = realloc(*line, 128); + if (!p) { + errno = ENOMEM; + return -1; + } + *line = p; + *n = 128; + } + size_t len = 0; + __flockfile(f); + for (;;) { + int c = __getc_unlocked(f); + if (c == EOF) + break; + if (len + 2 > *n) { + size_t want = *n * 2; + if (want < *n || want > (size_t)PTRDIFF_MAX) { + __funlockfile(f); + errno = EOVERFLOW; + return -1; + } + char *p = realloc(*line, want); + if (!p) { + f->flags |= F_ERR; + __funlockfile(f); + errno = ENOMEM; + return -1; + } + *line = p; + *n = want; + } + (*line)[len++] = (char)c; + if (c == (unsigned char)delim) + break; + } + __funlockfile(f); + (*line)[len] = 0; + return len ? (ssize_t)len : -1; +} + +__attribute__((weak)) ssize_t getline(char **restrict line, size_t *restrict n, FILE *restrict f) +{ + return getdelim(line, n, '\n', f); +} + +/* ---- printf somewhere else ------------------------------------------- */ + +/* To a descriptor, through a small buffer: one write per 256 bytes, + * not one per conversion, and no FILE. */ +struct fd_sink { + int fd, err; + size_t n; + char buf[256]; +}; + +static void fd_flush(struct fd_sink *s) +{ + for (size_t off = 0; off < s->n && !s->err; ) { + long w = __os_write(s->fd, s->buf + off, s->n - off); + if (w <= 0) + s->err = 1; + else + off += (size_t)w; + } + s->n = 0; +} + +static void sink_fd(void *ctx, const char *p, size_t n) +{ + struct fd_sink *s = ctx; + while (n) { + if (s->n == sizeof s->buf) + fd_flush(s); + size_t k = sizeof s->buf - s->n; + if (k > n) + k = n; + memcpy(s->buf + s->n, p, k); + s->n += k; + p += k; + n -= k; + } +} + +__attribute__((weak)) int vdprintf(int fd, const char *restrict fmt, va_list ap) +{ + struct fd_sink s = { fd, 0, 0, { 0 } }; + int r = __vformat(sink_fd, &s, fmt, ap); + fd_flush(&s); + return s.err ? -1 : r; +} + +__attribute__((weak)) int dprintf(int fd, const char *restrict fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = vdprintf(fd, fmt, ap); + va_end(ap); + return r; +} + +/* To memory it allocates: vsnprintf twice, once to learn the length. On + * failure *s is NULL, which POSIX.1-2024 made the rule. */ +__attribute__((weak)) int vasprintf(char **restrict s, const char *restrict fmt, va_list ap) +{ + va_list again; + va_copy(again, ap); + int n = vsnprintf(NULL, 0, fmt, ap); + char *p = n < 0 ? NULL : malloc((size_t)n + 1); + if (p) + vsnprintf(p, (size_t)n + 1, fmt, again); + va_end(again); + *s = p; + return p ? n : -1; +} + +__attribute__((weak)) int asprintf(char **restrict s, const char *restrict fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int r = vasprintf(s, fmt, ap); + va_end(ap); + return r; +} + +/* ---- offsets, buffering, the terminal --------------------------------- */ + +__attribute__((weak)) int fseeko(FILE *f, off_t off, int whence) { return fseek(f, off, whence); } +__attribute__((weak)) off_t ftello(FILE *f) { return ftell(f); } + +__attribute__((weak)) void setbuffer(FILE *f, char *buf, size_t n) +{ + setvbuf(f, buf, buf ? _IOFBF : _IONBF, n); +} + +__attribute__((weak)) void setlinebuf(FILE *f) { setvbuf(f, NULL, _IOLBF, 0); } + +/* The name of the controlling terminal, which on every Unix is this. */ +__attribute__((weak)) char *ctermid(char *s) +{ + static const char tty[] = "/dev/tty"; + return s ? strcpy(s, tty) : (char *)tty; +} + +/* ---- without the lock ------------------------------------------------ + * + * For a caller that has taken it with flockfile: one lock around a loop + * of getc_unlocked is the point of having both. */ +__attribute__((weak)) int getc_unlocked(FILE *f) { return __getc_unlocked(f); } +__attribute__((weak)) int getchar_unlocked(void) { return __getc_unlocked(stdin); } +__attribute__((weak)) int putc_unlocked(int c, FILE *f) { return __putc_unlocked(c, f); } +__attribute__((weak)) int putchar_unlocked(int c) { return __putc_unlocked(c, stdout); } +__attribute__((weak)) int fgetc_unlocked(FILE *f) { return __getc_unlocked(f); } +__attribute__((weak)) int fputc_unlocked(int c, FILE *f) { return __putc_unlocked(c, f); } +__attribute__((weak)) int fflush_unlocked(FILE *f) { return f ? __stdio_flush(f) : fflush(NULL); } +__attribute__((weak)) int feof_unlocked(FILE *f) { return (f->flags & F_EOF) != 0; } +__attribute__((weak)) int ferror_unlocked(FILE *f) { return (f->flags & F_ERR) != 0; } +__attribute__((weak)) void clearerr_unlocked(FILE *f) { f->flags &= ~(F_EOF | F_ERR); } +__attribute__((weak)) int fileno_unlocked(FILE *f) { return fileno(f); } + +__attribute__((weak)) size_t fwrite_unlocked(const void *restrict p, size_t size, size_t n, + FILE *restrict f) +{ + return __fwrite_unlocked(p, size, n, f); +} + +__attribute__((weak)) size_t fread_unlocked(void *restrict p, size_t size, size_t n, + FILE *restrict f) +{ + if (size == 0 || n == 0) + return 0; + unsigned char *q = p; + size_t total = size * n, got = 0; + while (got < total) { + int c = __getc_unlocked(f); + if (c == EOF) + break; + q[got++] = (unsigned char)c; + } + return got / size; +} + +__attribute__((weak)) int fputs_unlocked(const char *restrict s, FILE *restrict f) +{ + size_t n = strlen(s); + return __fwrite_unlocked(s, 1, n, f) == n ? 0 : EOF; +} diff --git a/lib/libc/src/stdio/stdio.c b/lib/libc/src/stdio/stdio.c index 6b5f4c7e3..556ae0f6c 100644 --- a/lib/libc/src/stdio/stdio.c +++ b/lib/libc/src/stdio/stdio.c @@ -12,24 +12,116 @@ #include #include "../../os/backend.h" +#include "../internal/tmpname.h" static FILE g_files[FOPEN_MAX]; + +/* More than FOPEN_MAX streams at once: FOPEN_MAX is C's promise of how + * many a program may count on, not a limit a hosted program should meet + * -- a server has a stream per connection. Beyond the table they come in + * chunks from the heap, and a chunk is never freed: a FILE that has + * been handed out stays valid memory for the life of the program, so + * the flush at exit, walking every stream, can never land in a closed + * one's freed storage. A closed FILE in a chunk is reused like a slot. */ +#define CHUNK 16 +struct chunk { + struct chunk *next; + FILE f[CHUNK]; +}; +static struct chunk *g_chunks; static unsigned char g_inbuf[BUFSIZ], g_outbuf[BUFSIZ]; static FILE g_stdin = { 0, F_READ | F_USED, g_inbuf, BUFSIZ, 0, 0, - _IOFBF, -1, 0, LOCK_INIT }; + _IOFBF, -1, 0, LOCK_INIT, 0, 0, { 0 }, 0 }; static FILE g_stdout = { 1, F_WRITE | F_USED, g_outbuf, BUFSIZ, 0, 0, - _IOFBF, -1, 0, LOCK_INIT }; + _IOFBF, -1, 0, LOCK_INIT, 0, 0, { 0 }, 0 }; static FILE g_stderr = { 2, F_WRITE | F_USED, NULL, 0, 0, 0, _IONBF, -1, 0, - LOCK_INIT }; + LOCK_INIT, 0, 0, { 0 }, 0 }; /* The g_files TABLE has its own lock, which is a different question from * a stream's: it guards which slots are taken, so two threads in fopen * cannot pick the same one. A stream's lock guards its buffer. */ static __lock_t files_lock = LOCK_INIT; -void __flockfile(FILE *f) { if (f) __lock(&f->lock); } -void __funlockfile(FILE *f) { if (f) __unlock(&f->lock); } +void __flockfile(FILE *f) +{ + if (!f) + return; + unsigned long me = __os_thread_self(); + if (__atomic_load_n(&f->depth, __ATOMIC_RELAXED) > 0 && + __atomic_load_n(&f->owner, __ATOMIC_RELAXED) == me) { + f->depth++; /* this thread's already: nest */ + return; + } + __lock(&f->lock); + __atomic_store_n(&f->owner, me, __ATOMIC_RELAXED); + __atomic_store_n(&f->depth, 1, __ATOMIC_RELAXED); +} + +void __funlockfile(FILE *f) +{ + if (!f || f->depth <= 0) + return; + if (--f->depth == 0) + __unlock(&f->lock); +} + +__attribute__((weak)) void flockfile(FILE *f) { __flockfile(f); } +__attribute__((weak)) void funlockfile(FILE *f) { __funlockfile(f); } + +/* 0 with the lock taken, nonzero when another thread has it. */ +__attribute__((weak)) int ftrylockfile(FILE *f) +{ + unsigned long me = __os_thread_self(); + if (__atomic_load_n(&f->depth, __ATOMIC_RELAXED) > 0 && + __atomic_load_n(&f->owner, __ATOMIC_RELAXED) == me) { + f->depth++; + return 0; + } + if (__trylock(&f->lock) != 0) + return -1; + __atomic_store_n(&f->owner, me, __ATOMIC_RELAXED); + __atomic_store_n(&f->depth, 1, __ATOMIC_RELAXED); + return 0; +} + +/* ---- the four operations, for a descriptor or for a cookie ---- + * + * A cookie stream with no read function reads end-of-file, and one with + * no write function swallows what is written: glibc's rules for + * fopencookie, which is where a caller learned to leave one out. */ +static long io_read(FILE *f, void *b, size_t n) +{ + if (!(f->flags & F_COOKIE)) + return __os_read(f->fd, b, n); + return f->io.read ? (long)f->io.read(f->cookie, b, n) : 0; +} + +static long io_write(FILE *f, const void *b, size_t n) +{ + if (!(f->flags & F_COOKIE)) + return __os_write(f->fd, b, n); + return f->io.write ? (long)f->io.write(f->cookie, b, n) : (long)n; +} + +static long io_seek(FILE *f, long off, int whence) +{ + if (!(f->flags & F_COOKIE)) + return __os_lseek(f->fd, off, whence); + long long at = off; + if (!f->io.seek) { + errno = ESPIPE; + return -1; + } + return f->io.seek(f->cookie, &at, whence) < 0 ? -1 : (long)at; +} + +static int io_close(FILE *f) +{ + if (!(f->flags & F_COOKIE)) + return __os_close(f->fd); + return f->io.close ? f->io.close(f->cookie) : 0; +} FILE *stdin = &g_stdin; FILE *stdout = &g_stdout; @@ -51,7 +143,7 @@ int __stdio_flush(FILE *f) return 0; size_t off = 0; while (off < f->pos) { - long w = __os_write(f->fd, f->buf + off, f->pos - off); + long w = io_write(f, f->buf + off, f->pos - off); if (w <= 0) { f->flags |= F_ERR; f->pos = 0; return EOF; } off += (size_t)w; } @@ -60,6 +152,18 @@ int __stdio_flush(FILE *f) return 0; } +static void flush_if_used(FILE *f) +{ + __lock(&files_lock); + int used = (f->flags & F_USED) != 0; + __unlock(&files_lock); + if (used) { + __flockfile(f); + __stdio_flush(f); + __funlockfile(f); + } +} + void __stdio_flush_all(void) { /* One stream at a time, each under its own lock. Taking them all at @@ -67,16 +171,14 @@ void __stdio_flush_all(void) * per stream and they do not interact. */ __flockfile(&g_stdout); __stdio_flush(&g_stdout); __funlockfile(&g_stdout); __flockfile(&g_stderr); __stdio_flush(&g_stderr); __funlockfile(&g_stderr); - for (int i = 0; i < FOPEN_MAX; i++) { - __lock(&files_lock); - int used = (g_files[i].flags & F_USED) != 0; - __unlock(&files_lock); - if (used) { - __flockfile(&g_files[i]); - __stdio_flush(&g_files[i]); - __funlockfile(&g_files[i]); - } - } + for (int i = 0; i < FOPEN_MAX; i++) + flush_if_used(&g_files[i]); + __lock(&files_lock); + struct chunk *c = g_chunks; + __unlock(&files_lock); + for (; c; c = c->next) + for (int i = 0; i < CHUNK; i++) + flush_if_used(&c->f[i]); } int fflush(FILE *f) @@ -95,7 +197,7 @@ int __putc_unlocked(int c, FILE *f) settle_mode(f); unsigned char ch = (unsigned char)c; if (f->mode == _IONBF || !f->buf) { - if (__os_write(f->fd, &ch, 1) != 1) { f->flags |= F_ERR; return EOF; } + if (io_write(f, &ch, 1) != 1) { f->flags |= F_ERR; return EOF; } return (int)ch; } f->flags |= F_WRITING; @@ -159,7 +261,7 @@ size_t __fwrite_unlocked(const void *p, size_t size, size_t n, FILE *f) return 0; size_t off = 0; while (off < total) { - long w = __os_write(f->fd, q + off, total - off); + long w = io_write(f, q + off, total - off); if (w <= 0) { f->flags |= F_ERR; return off / size; } off += (size_t)w; } @@ -179,7 +281,7 @@ size_t fwrite(const void *restrict p, size_t size, size_t n, static int refill(FILE *f) { if (!f->buf || !(f->flags & F_READ)) return EOF; - long r = __os_read(f->fd, f->buf, f->bufsz); + long r = io_read(f, f->buf, f->bufsz); if (r <= 0) { f->flags |= (r == 0) ? F_EOF : F_ERR; return EOF; } f->pos = 0; f->len = (size_t)r; @@ -307,90 +409,159 @@ void perror(const char *s) #define O_APPEND __OS_O_APPEND #define O_EXCL __OS_O_EXCL -static FILE *open_as(const char *path, int fl, int acc, int perm); - -FILE *fopen(const char *restrict path, const char *restrict mode) +/* The mode string: its open flags and its F_* access bits. 'b' is + * accepted and means nothing, as on every POSIX system; 'e' is glibc's + * close-on-exec and 'x' C11's exclusive creation. */ +int __stdio_mode(const char *mode, int *oflags, int *acc) { - int fl = 0, acc = 0; + int fl = 0, a = 0; switch (mode[0]) { - case 'r': acc = F_READ; fl = O_RDONLY; break; - case 'w': acc = F_WRITE; fl = O_WRONLY | O_CREAT | O_TRUNC; break; - case 'a': acc = F_WRITE | F_APPEND; fl = O_WRONLY | O_CREAT | O_APPEND; + case 'r': a = F_READ; fl = O_RDONLY; break; + case 'w': a = F_WRITE; fl = O_WRONLY | O_CREAT | O_TRUNC; break; + case 'a': a = F_WRITE | F_APPEND; fl = O_WRONLY | O_CREAT | O_APPEND; break; - default: errno = EINVAL; return NULL; + default: errno = EINVAL; return -1; } - if (strchr(mode, '+')) { acc = F_READ | F_WRITE; + if (strchr(mode, '+')) { a |= F_READ | F_WRITE; fl = (fl & ~(O_RDONLY | O_WRONLY)) | O_RDWR; } /* C11's exclusive mode, "wx" and its forms: the open fails if the * file exists, and that test and the creation are one step. */ if (mode[0] == 'w' && strchr(mode, 'x')) fl |= O_EXCL; - return open_as(path, fl, acc, 0666); + *oflags = fl; + *acc = a; + return 0; } -static FILE *open_as(const char *path, int fl, int acc, int perm) +/* Claiming a FILE and opening the file are two steps, and the claim has + * to happen under the table's lock or two threads take the same one. + * F_USED is set while still holding it, so the FILE is unavailable to + * anyone else before the open is even attempted; __stdio_unclaim gives + * it back if the open fails. */ +FILE *__stdio_claim(void) { - /* Claiming the slot and opening the file are two steps, and the - * claim has to happen under the table's lock or two threads take - * the same slot. F_USED is set while still holding it, so the slot - * is unavailable to anyone else before the open is even attempted; - * it is given back if the open fails. */ FILE *f = NULL; __lock(&files_lock); - for (int i = 0; i < FOPEN_MAX; i++) - if (!(g_files[i].flags & F_USED)) { + for (int i = 0; i < FOPEN_MAX && !f; i++) + if (!(g_files[i].flags & F_USED)) f = &g_files[i]; - memset(f, 0, sizeof *f); - f->flags = F_USED; - break; + for (struct chunk *c = g_chunks; c && !f; c = c->next) + for (int i = 0; i < CHUNK && !f; i++) + if (!(c->f[i].flags & F_USED)) + f = &c->f[i]; + if (!f) { + struct chunk *c = calloc(1, sizeof *c); + if (c) { + c->next = g_chunks; + g_chunks = c; + f = &c->f[0]; } + } + if (f) { + memset(f, 0, sizeof *f); + f->fd = -1; + f->ungot = -1; + f->flags = F_USED; + } __unlock(&files_lock); - if (!f) { errno = EMFILE; return NULL; } + if (!f) + errno = EMFILE; + return f; +} - int fd = __os_open(path, fl, perm); - if (fd < 0) { /* the backend set errno */ - __lock(&files_lock); - f->flags = 0; /* the slot goes back */ - __unlock(&files_lock); - return NULL; - } +void __stdio_unclaim(FILE *f) +{ + __lock(&files_lock); + f->flags = 0; + __unlock(&files_lock); +} - f->fd = fd; - f->flags = acc | F_USED; +/* Fully buffered, with a buffer of its own -- or unbuffered when there + * is no memory for one, which is slower but still correct. */ +void __stdio_setup(FILE *f, int acc) +{ + f->flags = (f->flags & F_COOKIE) | acc | F_USED; f->buf = malloc(BUFSIZ); f->bufsz = f->buf ? BUFSIZ : 0; f->own_buf = f->buf != NULL; f->mode = f->buf ? _IOFBF : _IONBF; + f->pos = f->len = 0; f->ungot = -1; +} + +FILE *fopen(const char *restrict path, const char *restrict mode) +{ + int fl, acc; + if (__stdio_mode(mode, &fl, &acc) < 0) + return NULL; + FILE *f = __stdio_claim(); + if (!f) + return NULL; + int fd = __os_open(path, fl, 0666); + if (fd < 0) { /* the backend set errno */ + __stdio_unclaim(f); + return NULL; + } + f->fd = fd; + __stdio_setup(f, acc); return f; } -int fclose(FILE *f) +/* Everything fclose does but give the FILE back: what freopen needs. */ +static int close_unlocked(FILE *f) { - if (!f) return EOF; - __flockfile(f); - if (!(f->flags & F_USED)) { __funlockfile(f); return EOF; } int r = __stdio_flush(f); - if (__os_close(f->fd) < 0) r = EOF; + if (io_close(f) < 0) r = EOF; if (f->own_buf) free(f->buf); f->buf = NULL; f->own_buf = 0; + return r; +} + +int fclose(FILE *f) +{ + if (!f) return EOF; + __flockfile(f); + if (!(f->flags & F_USED)) { __funlockfile(f); return EOF; } + int r = close_unlocked(f); f->flags = 0; /* the slot is free from here */ - __funlockfile(f); + f->depth = 0; + __unlock(&f->lock); return r; } +/* The SAME stream, reopened: freopen("log", "w", stdout) must leave + * stdout itself writing to log, so the FILE is kept and only what it is + * a stream of changes. A NULL path asks to change the mode of the file + * already open, which needs an fcntl this library's OS contract does not + * have; it fails, as POSIX allows. */ FILE *freopen(const char *restrict path, const char *restrict mode, FILE *restrict f) { - if (f) { - __flockfile(f); - __stdio_flush(f); - __os_close(f->fd); - f->flags = 0; - __funlockfile(f); + int fl, acc; + if (!f) return NULL; + __flockfile(f); + if (f->flags & F_USED) + close_unlocked(f); + f->flags = F_USED; /* closed, but not given back */ + if (!path || __stdio_mode(mode, &fl, &acc) < 0) { + if (!path) errno = EBADF; + goto fail; } - return fopen(path, mode); + int fd = __os_open(path, fl, 0666); + if (fd < 0) + goto fail; + f->fd = fd; + __stdio_setup(f, acc); + f->io = (struct __stream_io){ 0 }; + f->cookie = NULL; + __funlockfile(f); + return f; +fail: + f->flags = 0; + f->depth = 0; + __unlock(&f->lock); + return NULL; } int setvbuf(FILE *restrict f, char *restrict buf, int mode, size_t size) @@ -421,7 +592,7 @@ static int seek_unlocked(FILE *f, long off, int whence) f->pos = f->len = 0; f->ungot = -1; f->flags &= ~F_EOF; - return __os_lseek(f->fd, off, whence) < 0 ? -1 : 0; + return io_seek(f, off, whence) < 0 ? -1 : 0; } int fseek(FILE *f, long off, int whence) @@ -435,7 +606,7 @@ int fseek(FILE *f, long off, int whence) static long tell_unlocked(FILE *f) { - long at = __os_lseek(f->fd, 0, SEEK_CUR); + long at = io_seek(f, 0, SEEK_CUR); if (at < 0) return -1; /* What the OS thinks minus what is still sitting in our buffer. */ if (f->flags & F_WRITING) return at + (long)f->pos; @@ -489,10 +660,7 @@ int rename(const char *from, const char *to) * * Their names are under /tmp, the directory the rest of this library * calls temporary (lib/libcxx's temp_directory_path), and end in twelve - * letters from the OS's entropy -- a name another program can predict - * is one it can create first, a link to somewhere else. A target with no - * entropy mixes the clock in instead, and a count keeps one program's - * names apart either way. + * letters from __tmp_bits (internal/tmpname.h says why those). * * tmpnam's name is one no file had when it was made. Between that and * the caller's open is a race C cannot close, so tmpfile does not use @@ -505,14 +673,7 @@ int rename(const char *from, const char *to) static void tmp_name(char *buf) { static const char abc[] = "abcdefghijklmnopqrstuvwxyz012345"; - static unsigned long count; - unsigned long long r; - int saved = errno; - if (__os_getentropy(&r, sizeof r) != 0) - r = (unsigned long long)__os_clock_ns() ^ - (unsigned long long)__os_time() << 32; - errno = saved; - r += (unsigned long long)++count * 0x9E3779B97F4A7C15ull; + unsigned long long r = __tmp_bits(); memcpy(buf, "/tmp/", 5); for (int i = 0; i < 12; i++, r >>= 5) buf[5 + i] = abc[r & 31]; @@ -543,14 +704,19 @@ FILE *tmpfile(void) char name[L_tmpnam]; for (int tries = 0; tries < 100; tries++) { tmp_name(name); - FILE *f = open_as(name, O_RDWR | O_CREAT | O_EXCL, F_READ | F_WRITE, - 0600); - if (f) { + FILE *f = __stdio_claim(); + if (!f) + return NULL; + int fd = __os_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + f->fd = fd; + __stdio_setup(f, F_READ | F_WRITE); int saved = errno; __os_remove(name); errno = saved; return f; } + __stdio_unclaim(f); if (errno != EEXIST) return NULL; } diff --git a/lib/libc/src/stdlib/env.c b/lib/libc/src/stdlib/env.c index 758d6a4ed..e17e7a24d 100644 --- a/lib/libc/src/stdlib/env.c +++ b/lib/libc/src/stdlib/env.c @@ -1,4 +1,5 @@ -/* getenv, and the environment it reads. +/* getenv, setenv, unsetenv, putenv and clearenv, and the environment + * they share. * * `environ` lives here rather than in the backend because the environment * is not an OS primitive: it is a NUL-terminated "NAME=value" vector that @@ -6,16 +7,49 @@ * on passes it the same way. A backend that has no environment leaves the * pointer null and getenv correctly finds nothing. * - * setenv/putenv are absent on purpose. They are POSIX, not C11, and doing - * them properly means owning the vector's storage -- which of the strings - * were malloc'd, which came from the startup vector -- for a feature no - * program in this tree uses. An honest absence beats a leak. + * ---- who owns what --------------------------------------------------------- + * + * Changing the environment means owning storage, and three kinds of + * string end up in the one vector: + * + * the startup vector's on the initial stack; never freed + * setenv's malloc'd here; freed when replaced or removed + * putenv's the CALLER's, by POSIX's definition -- a later + * change to that string changes the environment + * -- so never copied and never freed + * + * Only the second kind is freed, so this file keeps the list of the + * strings it made. The vector itself is copied into malloc'd storage the + * first time anything changes, and again if the program has since + * pointed `environ` somewhere else -- an assignment to environ is + * legal, and writing into a vector the program owns would be writing + * into memory this file was never given. + * + * One lock for all of it. POSIX does not make setenv thread-safe against + * getenv (the pointer getenv returned can be freed under a caller's feet + * and no lock fixes that), but two setenvs at once must not lose an entry. */ #include #include +#include + +#include "../internal/lock.h" char **environ = 0; +static __lock_t env_lock = LOCK_INIT; +static char **own_vec; /* the vector this file allocated, if any */ +static size_t own_cap; /* its capacity, in entries */ +static char **made; /* the strings setenv malloc'd */ +static size_t nmade, made_cap; + +/* "NAME=" matches entry e, for a name of n bytes. The '=' test is not + * optional: without it "PATHX=..." answers a lookup of "PATH". */ +static int names(const char *e, const char *name, size_t n) +{ + return strncmp(e, name, n) == 0 && e[n] == '='; +} + char *getenv(const char *name) { if (!environ || !name) @@ -23,20 +57,179 @@ char *getenv(const char *name) size_t n = strlen(name); if (n == 0) return 0; - for (char **p = environ; *p; p++) { - /* The '=' test is not optional: without it "PATHX=..." answers a - * lookup of "PATH" with "X=...". */ - if (strncmp(*p, name, n) == 0 && (*p)[n] == '=') + for (char **p = environ; *p; p++) + if (names(*p, name, n)) return *p + n + 1; - } return 0; } -/* §7.22.4.8: system(NULL) reports whether a command processor exists. - * There is none here, so it reports 0 -- and a real command string gets - * -1 rather than a silent success that ran nothing. */ -int system(const char *cmd) +/* A name POSIX lets setenv and unsetenv take: not null, not empty, and + * without the '=' that would make "A=B" set A to "B=value". */ +static int bad_name(const char *name) +{ + return !name || !*name || strchr(name, '='); +} + +static void release(char *s) +{ + for (size_t i = 0; i < nmade; i++) + if (made[i] == s) { + free(s); + made[i] = made[--nmade]; + return; + } +} + +static size_t count(void) +{ + size_t n = 0; + if (environ) + while (environ[n]) + n++; + return n; +} + +/* A vector this file may write into, with room for one more entry and + * its terminator. -1 when there is no memory for one. */ +static int writable(void) { - (void)cmd; - return cmd ? -1 : 0; + size_t n = count(); + if (environ && environ == own_vec && n + 2 <= own_cap) + return 0; + size_t cap = n + 2 > 16 ? 2 * (n + 2) : 16; + char **v = malloc(cap * sizeof *v); + if (!v) + return -1; + if (n) + memcpy(v, environ, n * sizeof *v); + v[n] = 0; + if (own_vec && environ == own_vec) + free(own_vec); + own_vec = environ = v; + own_cap = cap; + return 0; } + +/* Put entry s, named by its first n bytes, in place of any entry of that + * name or at the end. */ +static int put(char *s, size_t n) +{ + if (writable() < 0) + return -1; + size_t i = 0; + for (; environ[i]; i++) + if (names(environ[i], s, n)) { + char *old = environ[i]; + environ[i] = s; + if (old != s) + release(old); + return 0; + } + environ[i] = s; + environ[i + 1] = 0; + return 0; +} + +__attribute__((weak)) int setenv(const char *name, const char *value, int overwrite) +{ + if (bad_name(name)) { + errno = EINVAL; + return -1; + } + __lock(&env_lock); + int r = 0; + if (!overwrite && getenv(name)) + goto out; + size_t n = strlen(name), v = strlen(value); + char *s = malloc(n + v + 2); + if (!s) { + r = -1; + goto out; + } + if (nmade == made_cap) { + size_t cap = made_cap ? 2 * made_cap : 16; + char **m = realloc(made, cap * sizeof *m); + if (!m) { + free(s); + r = -1; + goto out; + } + made = m; + made_cap = cap; + } + memcpy(s, name, n); + s[n] = '='; + memcpy(s + n + 1, value, v + 1); + made[nmade++] = s; + if (put(s, n) < 0) { + release(s); + r = -1; + } +out: + __unlock(&env_lock); + return r; +} + +/* Every entry of the name goes, not the first: a startup vector may hold + * two, and leaving the second would make the variable come back. */ +__attribute__((weak)) int unsetenv(const char *name) +{ + if (bad_name(name)) { + errno = EINVAL; + return -1; + } + __lock(&env_lock); + size_t n = strlen(name); + int r = 0; + for (size_t i = 0; environ && environ[i]; ) { + if (!names(environ[i], name, n)) { + i++; + continue; + } + if (writable() < 0) { + r = -1; + break; + } + char *old = environ[i]; + size_t k = i; + do + environ[k] = environ[k + 1]; + while (environ[k++]); + release(old); + } + __unlock(&env_lock); + return r; +} + +/* The caller's string itself becomes the entry. One without an '=' is + * glibc's way of asking for an unsetenv, and is taken that way. */ +__attribute__((weak)) int putenv(char *s) +{ + char *eq = s ? strchr(s, '=') : 0; + if (!eq) + return s && *s ? unsetenv(s) : (errno = EINVAL, -1); + if (eq == s) { + errno = EINVAL; + return -1; + } + __lock(&env_lock); + int r = put(s, (size_t)(eq - s)); + __unlock(&env_lock); + return r; +} + +__attribute__((weak)) int clearenv(void) +{ + __lock(&env_lock); + while (nmade) + free(made[--nmade]); + if (own_vec && environ == own_vec) + free(own_vec); + own_vec = 0; + own_cap = 0; + environ = 0; + __unlock(&env_lock); + return 0; +} + +/* system() is src/posix/proc.c's: /bin/sh, through posix_spawn. */ diff --git a/lib/libc/src/stdlib/errno.c b/lib/libc/src/stdlib/errno.c index 7864ea28c..b9d5b6ddb 100644 --- a/lib/libc/src/stdlib/errno.c +++ b/lib/libc/src/stdlib/errno.c @@ -15,9 +15,12 @@ * that is Linux: lib/libc/os/linux/tls.c finds PT_TLS through the * auxiliary vector, gives the first thread its copy before anything * else runs, and gives every clone()d thread its own. The freestanding - * harnesses and EmbLinkOS have no such step, so there errno stays one - * variable -- which is correct for them, because they are - * single-threaded. + * harnesses have no such step and no threads, so there errno is one + * variable. EmbLinkOS has threads but only half the step: its crt0 + * builds a block for the first thread of a statically linked program, + * and none for its dynamically linked apps or for a new thread -- so + * there too errno is one variable, as newlib's was on that OS, until + * the backend sets up each thread's block itself. * * The consequence of getting this wrong is worth stating plainly: a * shared errno under real threads is not a slow program, it is two diff --git a/lib/libc/src/stdlib/exit.c b/lib/libc/src/stdlib/exit.c index a032742b3..170a5a204 100644 --- a/lib/libc/src/stdlib/exit.c +++ b/lib/libc/src/stdlib/exit.c @@ -132,14 +132,7 @@ void quick_exit(int status) _Exit(status); } -void abort(void) -{ - /* SIGABRT would be raised here if this library had signals. Until then, - * a distinctive status is better than a silent stop. */ - __stdio_flush_all(); - __os_exit(134); /* 128 + SIGABRT, what a shell reports */ - for (;;) {} -} +/* abort is stdlib/signal.c's: it raises SIGABRT first. */ int abs(int x) { return x < 0 ? -x : x; } long labs(long x) { return x < 0 ? -x : x; } diff --git a/lib/libc/src/stdlib/malloc-gnu.c b/lib/libc/src/stdlib/malloc-gnu.c new file mode 100644 index 000000000..4831747c0 --- /dev/null +++ b/lib/libc/src/stdlib/malloc-gnu.c @@ -0,0 +1,38 @@ +/* 's functions that are not the allocator's own business: page- + * aligned blocks, and the report. Apart from malloc.c, so that a program + * that only allocates does not bring printf along with it. */ +#include +#include +#include + +static size_t page(void) +{ + long n = sysconf(_SC_PAGESIZE); + return n > 0 ? (size_t)n : 4096; +} + +__attribute__((weak)) void *valloc(size_t n) +{ + return memalign(page(), n); +} + +/* valloc of a size rounded up to whole pages; 0 is a page, as glibc's */ +__attribute__((weak)) void *pvalloc(size_t n) +{ + size_t p = page(); + if (n > (size_t)-1 - p) + return NULL; + return memalign(p, n ? (n + p - 1) & ~(p - 1) : p); +} + +/* glibc's report, for the one arena there is */ +__attribute__((weak)) void malloc_stats(void) +{ + struct mallinfo2 m = mallinfo2(); + fprintf(stderr, "Arena 0:\nsystem bytes = %10zu\n" + "in use bytes = %10zu\n" + "Total (incl. mmap):\nsystem bytes = %10zu\n" + "in use bytes = %10zu\nmax mmap regions = %10u\n" + "max mmap bytes = %10lu\n", + m.arena, m.uordblks, m.arena, m.uordblks, 0u, 0ul); +} diff --git a/lib/libc/src/stdlib/malloc.c b/lib/libc/src/stdlib/malloc.c index b1d8deaf9..9245e7ac9 100644 --- a/lib/libc/src/stdlib/malloc.c +++ b/lib/libc/src/stdlib/malloc.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include "../../os/backend.h" #include "../internal/lock.h" @@ -127,8 +129,10 @@ static int abuts(struct blk *b, struct blk *n) static struct blk *grow(size_t need) { size_t chunk = ALIGN_UP(need + HDR); - if (chunk < 64 * 1024) - chunk = 64 * 1024; +#if SIZE_MAX > 0xFFFF /* with a 16-bit size_t, 64 KB is everything */ + if (chunk < 64 * 1024UL) + chunk = 64 * 1024UL; +#endif void *p = __os_sbrk((long)chunk); if (p == (void *)-1 && chunk > ALIGN_UP(need + HDR)) { chunk = ALIGN_UP(need + HDR); @@ -414,3 +418,63 @@ void *aligned_alloc(size_t align, size_t n) __unlock(&heap_lock); return p; } + +/* ---- ---------------------------------------------------------- */ + +/* The block's payload, which may be more than was asked: sizes are kept + * rounded up, and a block too small to split is handed out whole. */ +__attribute__((weak)) size_t malloc_usable_size(void *p) +{ + if (!p) + return 0; + return BSIZE((struct blk *)((unsigned char *)p - HDR)); +} + +__attribute__((weak)) struct mallinfo2 mallinfo2(void) +{ + struct mallinfo2 m; + memset(&m, 0, sizeof m); + __lock(&heap_lock); + for (struct blk *b = g_head; b; b = b->next) { + m.arena += HDR + BSIZE(b); + if (BFREE(b)) { + m.ordblks++; + m.fordblks += BSIZE(b); + } else { + m.uordblks += BSIZE(b); + } + } + if (g_tail && BFREE(g_tail)) + m.keepcost = BSIZE(g_tail); + __unlock(&heap_lock); + return m; +} + +/* The old form, whose ints saturate rather than wrap. */ +__attribute__((weak)) struct mallinfo mallinfo(void) +{ + struct mallinfo2 w = mallinfo2(); + struct mallinfo m; + memset(&m, 0, sizeof m); +#define CLAMP(v) ((v) > (size_t)INT_MAX ? INT_MAX : (int)(v)) + m.arena = CLAMP(w.arena); + m.ordblks = CLAMP(w.ordblks); + m.uordblks = CLAMP(w.uordblks); + m.fordblks = CLAMP(w.fordblks); + m.keepcost = CLAMP(w.keepcost); +#undef CLAMP + return m; +} + +__attribute__((weak)) int malloc_trim(size_t pad) +{ + (void)pad; + return 0; /* 0: no memory was given back */ +} + +__attribute__((weak)) int mallopt(int param, int value) +{ + (void)param; + (void)value; + return 0; /* 0: not applied */ +} diff --git a/lib/libc/src/stdlib/posix.c b/lib/libc/src/stdlib/posix.c new file mode 100644 index 000000000..89c48487b --- /dev/null +++ b/lib/libc/src/stdlib/posix.c @@ -0,0 +1,63 @@ +/* 's POSIX functions that are a few lines over C's: the + * allocation forms and getsubopt. The larger ones have files of their + * own -- env.c, random.c, tempfile.c, realpath.c. */ +#include +#include +#include +#include + +/* aligned_alloc with POSIX's rules and POSIX's way of failing: an + * alignment must be a power of two AND a multiple of sizeof(void *), + * and the error is RETURNED, with errno left alone. */ +__attribute__((weak)) int posix_memalign(void **p, size_t align, size_t n) +{ + if (align < sizeof(void *) || (align & (align - 1)) || + align % sizeof(void *)) + return EINVAL; + int saved = errno; + void *q = aligned_alloc(align, n); + int e = errno; + errno = saved; + if (!q) + return e == EINVAL ? EINVAL : ENOMEM; + *p = q; + return 0; +} + +/* the old name, which code from before C11 still calls */ +__attribute__((weak)) void *memalign(size_t align, size_t n) { return aligned_alloc(align, n); } + +/* realloc of n things of `size` bytes, refusing a count whose product + * does not fit rather than allocating its wrapped-around remainder */ +__attribute__((weak)) void *reallocarray(void *p, size_t n, size_t size) +{ + if (size && n > SIZE_MAX / size) { + errno = ENOMEM; + return NULL; + } + return realloc(p, n * size); +} + +/* One "name" or "name=value" from a comma-separated list, as mount(8) + * options are written: the index of name in tokens, or -1 when it is not + * there, with *value at what followed '=' (or NULL), and *opt moved past + * the comma. An unknown suboption is still consumed and *value is then + * the whole of it, which is what a caller reports. */ +__attribute__((weak)) int getsubopt(char **opt, char *const *tokens, char **value) +{ + char *s = *opt, *end = strchr(s, ','); + if (end) { + *end = 0; + *opt = end + 1; + } else + *opt = s + strlen(s); + char *eq = strchr(s, '='); + size_t n = eq ? (size_t)(eq - s) : strlen(s); + for (int i = 0; tokens[i]; i++) + if (strncmp(tokens[i], s, n) == 0 && tokens[i][n] == 0) { + *value = eq ? eq + 1 : NULL; + return i; + } + *value = s; + return -1; +} diff --git a/lib/libc/src/stdlib/qsort.c b/lib/libc/src/stdlib/qsort.c index 1c9db4766..7f4bfef58 100644 --- a/lib/libc/src/stdlib/qsort.c +++ b/lib/libc/src/stdlib/qsort.c @@ -15,12 +15,12 @@ static void swap(char *a, char *b, size_t size) } static void sift(char *base, size_t size, size_t root, size_t n, - int (*cmp)(const void *, const void *)) + int (*cmp)(const void *, const void *, void *), void *arg) { for (;;) { size_t big = root, l = 2 * root + 1, r = l + 1; - if (l < n && cmp(base + l * size, base + big * size) > 0) big = l; - if (r < n && cmp(base + r * size, base + big * size) > 0) big = r; + if (l < n && cmp(base + l * size, base + big * size, arg) > 0) big = l; + if (r < n && cmp(base + r * size, base + big * size, arg) > 0) big = r; if (big == root) return; swap(base + root * size, base + big * size, size); @@ -28,20 +28,39 @@ static void sift(char *base, size_t size, size_t root, size_t n, } } -void qsort(void *base, size_t n, size_t size, - int (*cmp)(const void *, const void *)) +/* POSIX.1-2024's qsort_r, the comparison told the caller's `arg` -- in + * glibc's argument order, which POSIX adopted, and not the BSDs' older + * one with arg first. */ +__attribute__((weak)) void qsort_r(void *base, size_t n, size_t size, + int (*cmp)(const void *, const void *, void *), void *arg) { if (n < 2 || size == 0) return; char *b = base; for (size_t i = n / 2; i-- > 0; ) - sift(b, size, i, n, cmp); + sift(b, size, i, n, cmp, arg); for (size_t i = n; i-- > 1; ) { swap(b, b + i * size, size); - sift(b, size, 0, i, cmp); + sift(b, size, 0, i, cmp, arg); } } +/* C's two-argument comparison, carried as qsort_r's arg. In a struct, + * because ISO C has no conversion between a function pointer and void *. */ +struct cmp2 { int (*f)(const void *, const void *); }; + +static int call2(const void *a, const void *b, void *c) +{ + return ((struct cmp2 *)c)->f(a, b); +} + +void qsort(void *base, size_t n, size_t size, + int (*cmp)(const void *, const void *)) +{ + struct cmp2 c = { cmp }; + qsort_r(base, n, size, call2, &c); +} + void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *, const void *)) { diff --git a/lib/libc/src/stdlib/random.c b/lib/libc/src/stdlib/random.c new file mode 100644 index 000000000..b8ca1db4c --- /dev/null +++ b/lib/libc/src/stdlib/random.c @@ -0,0 +1,213 @@ +/* POSIX's pseudo-random generators: random and its state functions, the + * *rand48 family, and rand_r. + * + * Each produces the SAME sequence as glibc's for the same seed. These + * generators are not good -- none of them is fit for anything an attacker + * may see -- but programs print their output, and tests compare what was + * printed, and a port whose random(3) disagrees with the system it came + * from looks broken when it is not. The algorithms are the historical + * BSD and System V ones, which is what glibc implements. + * + * Not thread-safe, as POSIX permits for all but rand_r; the *_r-free + * interfaces share one state by definition. + */ +#include +#include +#include + +/* ---- random: BSD's additive feedback generator ----------------------------- + * + * r[i] = r[i - deg] + r[i - deg + sep], 32 bits wrapping, of which the top + * 31 are returned. The state is a caller-supplied int32_t array whose first + * word records the generator type and the read position, so setstate can + * hand a state back and resume it exactly: word 0 is + * TYPES * (rear index) + type, as BSD lays it out. Type 0, an 8-byte state, + * is a plain linear congruential generator instead. */ +#define TYPES 5 +static const int degrees[TYPES] = { 0, 7, 15, 31, 63 }; +static const int seps[TYPES] = { 0, 3, 1, 3, 1 }; + +static int32_t deftab[32]; /* the default state, as initstate(1, ., 128) */ +static int32_t *state = deftab + 1; +static int type = 3, deg = 31, sep = 3; +static int32_t *fptr, *rptr; /* front and rear, into state */ +static int inited; + +static int32_t next_raw(void) +{ + if (type == 0) { + state[0] = (int32_t)(((uint32_t)state[0] * 1103515245u + 12345u) & + 0x7fffffff); + return state[0]; + } + int32_t *end = state + deg; + uint32_t v = (uint32_t)*fptr + (uint32_t)*rptr; + *fptr = (int32_t)v; + if (++fptr >= end) { + fptr = state; + ++rptr; + } else if (++rptr >= end) + rptr = state; + return (int32_t)(v >> 1); +} + +__attribute__((weak)) void srandom(unsigned seed) +{ + inited = 1; + if (seed == 0) + seed = 1; + state[0] = (int32_t)seed; + if (type == 0) + return; + /* 16807 * word mod 2^31 - 1 without overflow, by Schrage's method -- + * and exactly glibc's arithmetic, signed, so a seed above INT_MAX + * starts the same sequence there and here. */ + int32_t word = (int32_t)seed; + for (int i = 1; i < deg; i++) { + int32_t hi = word / 127773, lo = word % 127773; + word = 16807 * lo - 2836 * hi; + if (word < 0) + word += 2147483647; + state[i] = word; + } + fptr = state + sep; + rptr = state; + for (int i = 10 * deg; i > 0; i--) + next_raw(); +} + +__attribute__((weak)) long random(void) +{ + if (!inited) + srandom(1); + return next_raw(); +} + +static void save_position(void) +{ + state[-1] = type == 0 ? 0 : (int32_t)(TYPES * (rptr - state) + type); +} + +/* A state of n bytes: the larger the state, the longer the period. Less + * than 8 bytes holds nothing to generate from. */ +__attribute__((weak)) char *initstate(unsigned seed, char *buf, size_t n) +{ + if (n < 8) { + errno = EINVAL; + return NULL; + } + if (!inited) + srandom(1); + char *old = (char *)(state - 1); + save_position(); + type = n < 32 ? 0 : n < 64 ? 1 : n < 128 ? 2 : n < 256 ? 3 : 4; + deg = degrees[type]; + sep = seps[type]; + state = (int32_t *)(void *)buf + 1; + srandom(seed); + save_position(); + return old; +} + +__attribute__((weak)) char *setstate(char *buf) +{ + int32_t *ns = (int32_t *)(void *)buf + 1; + int t = ns[-1] % TYPES; + if (t < 0 || t >= TYPES) { + errno = EINVAL; + return NULL; + } + if (!inited) + srandom(1); + char *old = (char *)(state - 1); + save_position(); + type = t; + deg = degrees[t]; + sep = seps[t]; + state = ns; + if (t != 0) { + int rear = ns[-1] / TYPES; + rptr = ns + rear; + fptr = ns + (rear + sep) % deg; + } + return old; +} + +/* ---- the *rand48 family: System V's 48-bit generator ----------------------- + * + * x' = a * x + c mod 2^48, with a and c changeable by lcong48. Each + * function returns a different slice of the new x. The ones taking an + * xsubi array keep the 48 bits in the caller's three shorts rather than + * in the shared state, but share a and c, as POSIX specifies. */ +static unsigned short x48[3]; +static uint64_t a48 = 0x5DEECE66Dull; +static unsigned short c48 = 0xB; + +static uint64_t step48(unsigned short xs[3]) +{ + uint64_t x = (uint64_t)xs[0] | (uint64_t)xs[1] << 16 | + (uint64_t)xs[2] << 32; + x = (a48 * x + c48) & 0xFFFFFFFFFFFFull; + xs[0] = (unsigned short)x; + xs[1] = (unsigned short)(x >> 16); + xs[2] = (unsigned short)(x >> 32); + return x; +} + +/* all 48 bits, as a fraction: exact, since a double holds 53 */ +__attribute__((weak)) double erand48(unsigned short xs[3]) { return (double)step48(xs) * 0x1p-48; } +__attribute__((weak)) double drand48(void) { return erand48(x48); } +__attribute__((weak)) long nrand48(unsigned short xs[3]) { return (long)(step48(xs) >> 17); } +__attribute__((weak)) long lrand48(void) { return nrand48(x48); } +/* the top 32 bits, signed */ +__attribute__((weak)) long jrand48(unsigned short xs[3]) +{ + return (long)(int32_t)(uint32_t)(step48(xs) >> 16); +} +__attribute__((weak)) long mrand48(void) { return jrand48(x48); } + +__attribute__((weak)) void srand48(long seed) +{ + x48[0] = 0x330E; + x48[1] = (unsigned short)seed; + x48[2] = (unsigned short)((unsigned long)seed >> 16); + a48 = 0x5DEECE66Dull; + c48 = 0xB; +} + +__attribute__((weak)) unsigned short *seed48(unsigned short s[3]) +{ + static unsigned short old[3]; + for (int i = 0; i < 3; i++) { + old[i] = x48[i]; + x48[i] = s[i]; + } + a48 = 0x5DEECE66Dull; + c48 = 0xB; + return old; +} + +__attribute__((weak)) void lcong48(unsigned short p[7]) +{ + for (int i = 0; i < 3; i++) + x48[i] = p[i]; + a48 = (uint64_t)p[3] | (uint64_t)p[4] << 16 | (uint64_t)p[5] << 32; + c48 = p[6]; +} + +/* ---- rand_r: the one with its state in the caller's hands ------------------ + * + * Three steps of C's example generator, 11 + 10 + 10 bits of them: what + * glibc returns, so the same seed gives the same numbers. */ +__attribute__((weak)) int rand_r(unsigned *seed) +{ + unsigned next = *seed; + next = next * 1103515245u + 12345u; + unsigned r = (next / 65536) % 2048; + next = next * 1103515245u + 12345u; + r = r << 10 ^ (next / 65536) % 1024; + next = next * 1103515245u + 12345u; + r = r << 10 ^ (next / 65536) % 1024; + *seed = next; + return (int)r; +} diff --git a/lib/libc/src/stdlib/realpath.c b/lib/libc/src/stdlib/realpath.c new file mode 100644 index 000000000..48d38990e --- /dev/null +++ b/lib/libc/src/stdlib/realpath.c @@ -0,0 +1,141 @@ +/* realpath: the absolute name of a file with every ".", ".." and symbolic + * link taken out of it. + * + * One component at a time, left to right, with each looked at (lstat) + * before the next is added. The order is the point: "a/../b" is NOT "b" + * when a is a symlink -- ".." leaves whatever a points at -- so ".." may + * only be applied to a prefix that has already been resolved, and that + * prefix is then free of links and ".." can simply drop its last + * component. A link's target is put in front of what remains of the + * path and resolved like the rest of it; forty of them in one walk is + * Linux's ELOOP, and the answer here too. + */ +#include +#include +#include +#include + +#include "../../os/backend.h" + +#define MAX_LINKS 40 + +static int too_long(void) +{ + errno = ENAMETOOLONG; + return -1; +} + +/* out, of olen bytes, without its last component. "/" stays "/". */ +static size_t parent(const char *out, size_t olen) +{ + while (olen > 1 && out[olen - 1] != '/') + olen--; + return olen > 1 ? olen - 1 : 1; +} + +/* rest and link are the caller's, PATH_MAX bytes each: on the heap, since + * three path buffers are 12 KB, which is a whole stack on a small target */ +static int resolve(const char *path, char *out, char *rest, char *link) +{ + size_t olen, links = 0; + + if (strlen(path) >= PATH_MAX) + return too_long(); + strcpy(rest, path); + if (rest[0] == '/') { + out[0] = '/'; + olen = 1; + } else { + if (__os_getcwd(out, PATH_MAX) < 0) + return -1; + olen = strlen(out); + } + + char *p = rest; + for (;;) { + while (*p == '/') + p++; + if (!*p) + break; + char *end = p; + while (*end && *end != '/') + end++; + size_t n = (size_t)(end - p); + if (n == 1 && p[0] == '.') { + p = end; + continue; + } + if (n == 2 && p[0] == '.' && p[1] == '.') { + olen = parent(out, olen); + p = end; + continue; + } + size_t before = olen; + if (olen + (olen > 1) + n >= PATH_MAX) + return too_long(); + if (olen > 1) + out[olen++] = '/'; + memcpy(out + olen, p, n); + olen += n; + out[olen] = 0; + p = end; + + struct __os_fileinfo fi; + if (__os_lstat(out, &fi) < 0) + return -1; + if (fi.type == __OS_FT_SYMLINK) { + if (++links > MAX_LINKS) { + errno = ELOOP; + return -1; + } + long k = __os_readlink(out, link, PATH_MAX - 1); + if (k < 0) + return -1; + /* the target, then what was left after the link's name */ + size_t left = strlen(p); + if ((size_t)k + left + 1 >= PATH_MAX) + return too_long(); + memmove(rest + k, p, left + 1); + memcpy(rest, link, (size_t)k); + p = rest; + if (link[0] == '/') + olen = 1; + else + olen = before; + out[olen] = 0; + continue; + } + /* Anything after this must be inside it -- including a lone + * trailing slash, which names "the directory a" and is an error + * when a is a file. */ + if (*p && fi.type != __OS_FT_DIRECTORY) { + errno = ENOTDIR; + return -1; + } + } + out[olen] = 0; + return 0; +} + +__attribute__((weak)) char *realpath(const char *restrict path, char *restrict resolved) +{ + if (!path) { + errno = EINVAL; + return NULL; + } + if (!*path) { + errno = ENOENT; + return NULL; + } + char *work = malloc(3 * PATH_MAX); + if (!work) + return NULL; + char *out = work, *r = NULL; + if (resolve(path, out, work + PATH_MAX, work + 2 * PATH_MAX) == 0) { + r = resolved ? resolved : malloc(strlen(out) + 1); + if (r) + strcpy(r, out); + } + free(work); + return r; +} diff --git a/lib/libc/src/stdlib/signal.c b/lib/libc/src/stdlib/signal.c index dea8c1f63..ea5b7682a 100644 --- a/lib/libc/src/stdlib/signal.c +++ b/lib/libc/src/stdlib/signal.c @@ -1,60 +1,470 @@ -/* signal, raise — C11 §7.14. +/* Signals: C's signal and raise, and POSIX's sigaction and the rest. * - * A table of handlers and a dispatcher, and nothing else: the C - * standard's signal model does not include a way for the OS to DELIVER - * one, because C does not know what an OS is. `raise` is therefore the - * only path a signal takes here, and it is a direct call. + * Two models, and the backend decides which. Where the OS delivers + * signals (Linux), sigaction installs a handler with the kernel, raise + * sends a real signal to the calling thread, and signal is sigaction with + * BSD's semantics -- the handler stays, interrupted calls restart -- as + * glibc has it. * - * That is not a simplification of the real thing -- it IS the real - * thing for a freestanding target. A hosted OS delivers signals by - * interrupting a thread and calling the handler itself; when a backend - * gains that ability, it calls __raise_signal below and everything - * above this line is already correct. + * Where it does not (bare metal, EmbLinkOS today), the kernel's half of + * every call answers ENOSYS and this file is the whole of it: a table + * of handlers, a mask and a pending set. raise is then the only way a + * signal arrives, and it is a direct call -- which is not a + * simplification of the real thing, it IS the real thing for a target + * that cannot interrupt a program. A signal raised while blocked waits + * in the pending set and is delivered when sigprocmask unblocks it, so a + * program written for POSIX behaves the same here, short of what only + * an OS can do (a SIGINT from a keyboard, a SIGALRM from a timer). + * + * What a handler may do is the part worth knowing, because nearly every + * handler in the wild breaks it: C11 §7.14.1.1p5 allows a handler to call + * only abort, _Exit, quick_exit and signal for its own signal, and to + * assign to a volatile sig_atomic_t. POSIX widens that to its list of + * async-signal-safe functions -- write, _exit, sigaction, ... -- which + * printf and malloc are not on. */ +#define _GNU_SOURCE 1 #include +#include #include +#include +#include +#include -#define NSIG 32 +#include "../../os/backend.h" +#include "../stdio/file.h" -static void (*handlers[NSIG])(int); +/* ---- which model ------------------------------------------------------------ + * + * Asked once, by reading SIGINT's disposition: a backend that delivers + * answers, one that does not says ENOSYS. */ +static int g_kernel = -1; -void (*signal(int sig, void (*handler)(int)))(int) +static int kernel(void) { - if (sig <= 0 || sig >= NSIG) - return SIG_ERR; - void (*prev)(int) = handlers[sig]; - handlers[sig] = handler; - return prev ? prev : SIG_DFL; + if (g_kernel < 0) { + int saved = errno; + struct __os_sigaction old; + g_kernel = __os_sigaction(SIGINT, NULL, &old) == 0; + errno = saved; + } + return g_kernel; } -/* The default action for the signals C defines: all six are abnormal - * termination. A real OS distinguishes core-dumping from not; from here - * the distinction is invisible and inventing it would be a lie. */ +static int valid(int sig) { return sig > 0 && sig < NSIG; } + +#define BIT(sig) (1ULL << ((sig) - 1)) + +/* ---- the model with no kernel -------------------------------------------- */ + +static struct sigaction g_act[NSIG]; +static sigset_t g_mask, g_pending; + +/* The default action, where there is nobody else to take it: the + * signals POSIX says are ignored by default are, and every other one + * ends the program with the status a shell reports for it, 128 + sig -- + * after stdio is flushed, since nothing else will flush it. */ static void default_action(int sig) { - (void)sig; - abort(); + switch (sig) { + case SIGCHLD: case SIGCONT: case SIGURG: case SIGWINCH: + return; + } + __stdio_flush_all(); + __os_exit(128 + sig); +} + +static void deliver(int sig) +{ + struct sigaction *a = &g_act[sig]; + void (*h)(int) = a->sa_handler; + if (h == SIG_IGN) + return; + if (h == SIG_DFL) { + default_action(sig); + return; + } + struct sigaction run = *a; + if (a->sa_flags & SA_RESETHAND) + a->sa_handler = SIG_DFL; + /* the handler's mask, and the signal itself unless SA_NODEFER, are + * blocked while it runs, as a kernel would */ + sigset_t saved = g_mask; + g_mask |= run.sa_mask; + if (!(run.sa_flags & SA_NODEFER)) + g_mask |= BIT(sig); + if (run.sa_flags & SA_SIGINFO) { + siginfo_t info; + memset(&info, 0, sizeof info); + info.si_signo = sig; + info.si_code = SI_USER; + info.si_pid = (pid_t)__os_getid(__OS_ID_PID); + run.sa_sigaction(sig, &info, NULL); + } else + h(sig); + g_mask = saved; } -/* The dispatcher. A backend that can deliver a signal calls this; raise - * calls it directly. The handler is cleared BEFORE the call, as C11 - * §7.14.1.1p3 requires: a handler that does not reinstall itself runs - * once, and a signal arriving during its own handler therefore takes - * the default rather than recursing. */ +static void deliver_pending(void) +{ + for (int sig = 1; sig < NSIG; sig++) + if ((g_pending & BIT(sig)) && !(g_mask & BIT(sig))) { + g_pending &= ~BIT(sig); + deliver(sig); + } +} + +/* A backend that can deliver a signal of its own calls this; raise in + * the model with no kernel calls it. */ int __raise_signal(int sig) { - if (sig <= 0 || sig >= NSIG) + if (!valid(sig)) { + errno = EINVAL; return -1; - void (*h)(int) = handlers[sig]; - if (h == SIG_IGN) + } + if (g_mask & BIT(sig)) { + g_pending |= BIT(sig); return 0; - if (h == SIG_DFL || !h) { - default_action(sig); + } + deliver(sig); + return 0; +} + +/* ---- sets ------------------------------------------------------------------ */ + +__attribute__((weak)) int sigemptyset(sigset_t *s) { *s = 0; return 0; } +__attribute__((weak)) int sigfillset(sigset_t *s) { *s = ~0ULL; return 0; } + +__attribute__((weak)) int sigaddset(sigset_t *s, int sig) +{ + if (!valid(sig)) { errno = EINVAL; return -1; } + *s |= BIT(sig); + return 0; +} + +__attribute__((weak)) int sigdelset(sigset_t *s, int sig) +{ + if (!valid(sig)) { errno = EINVAL; return -1; } + *s &= ~BIT(sig); + return 0; +} + +__attribute__((weak)) int sigismember(const sigset_t *s, int sig) +{ + if (!valid(sig)) { errno = EINVAL; return -1; } + return (*s & BIT(sig)) != 0; +} + +__attribute__((weak)) int sigisemptyset(const sigset_t *s) { return *s == 0; } + +__attribute__((weak)) int sigorset(sigset_t *d, const sigset_t *a, const sigset_t *b) +{ + *d = *a | *b; + return 0; +} + +__attribute__((weak)) int sigandset(sigset_t *d, const sigset_t *a, const sigset_t *b) +{ + *d = *a & *b; + return 0; +} + +/* ---- dispositions ----------------------------------------------------------- */ + +__attribute__((weak)) int sigaction(int sig, const struct sigaction *restrict act, + struct sigaction *restrict old) +{ + if (!valid(sig) || + (act && (sig == SIGKILL || sig == SIGSTOP) && + act->sa_handler != SIG_DFL)) { + errno = EINVAL; + return -1; + } + if (kernel()) { + struct __os_sigaction k, ko; + if (act) { + k.handler = (act->sa_flags & SA_SIGINFO) + ? (void *)act->sa_sigaction + : (void *)act->sa_handler; + k.mask = act->sa_mask; + k.flags = act->sa_flags; + } + if (__os_sigaction(sig, act ? &k : NULL, old ? &ko : NULL) < 0) + return -1; + if (old) { + memset(old, 0, sizeof *old); + if (ko.flags & SA_SIGINFO) + old->sa_sigaction = + (void (*)(int, siginfo_t *, void *))ko.handler; + else + old->sa_handler = (void (*)(int))ko.handler; + old->sa_mask = ko.mask; + old->sa_flags = ko.flags; + } return 0; } - handlers[sig] = SIG_DFL; - h(sig); + if (old) + *old = g_act[sig]; + if (act) { + g_act[sig] = *act; + if (act->sa_handler == SIG_IGN) + g_pending &= ~BIT(sig); + } return 0; } -int raise(int sig) { return __raise_signal(sig); } +/* C's signal: BSD's semantics on a kernel (the handler stays installed, + * an interrupted call restarts), C's own without one -- the handler is + * reset before it runs, as C11 7.14.1.1p3 describes. */ +void (*signal(int sig, void (*handler)(int)))(int) +{ + struct sigaction sa, old; + memset(&sa, 0, sizeof sa); + sa.sa_handler = handler; + sa.sa_flags = kernel() ? SA_RESTART : SA_RESETHAND | SA_NODEFER; + if (sigaction(sig, &sa, &old) < 0) + return SIG_ERR; + return old.sa_handler; +} + +__attribute__((weak)) int siginterrupt(int sig, int flag) +{ + struct sigaction sa; + if (sigaction(sig, NULL, &sa) < 0) + return -1; + if (flag) + sa.sa_flags &= ~SA_RESTART; + else + sa.sa_flags |= SA_RESTART; + return sigaction(sig, &sa, NULL); +} + +/* ---- the mask -------------------------------------------------------------- */ + +__attribute__((weak)) int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict old) +{ + if (kernel()) + return __os_sigprocmask(how, set, old); + if (old) + *old = g_mask; + if (set) { + switch (how) { + case SIG_BLOCK: g_mask |= *set; break; + case SIG_UNBLOCK: g_mask &= ~*set; break; + case SIG_SETMASK: g_mask = *set; break; + default: errno = EINVAL; return -1; + } + g_mask &= ~(BIT(SIGKILL) | BIT(SIGSTOP)); + deliver_pending(); + } + return 0; +} + +__attribute__((weak)) int pthread_sigmask(int how, const sigset_t *restrict set, + sigset_t *restrict old) +{ + int saved = errno; + int r = sigprocmask(how, set, old) < 0 ? errno : 0; + errno = saved; + return r; +} + +__attribute__((weak)) int sigpending(sigset_t *set) +{ + if (kernel()) + return __os_sigpending(set); + *set = g_pending; + return 0; +} + +/* With no kernel, nothing can arrive while suspended: a pending signal + * the new mask lets through is delivered at once, and otherwise the call + * would wait for ever -- so it says it cannot. */ +__attribute__((weak)) int sigsuspend(const sigset_t *mask) +{ + if (kernel()) + return __os_sigsuspend(mask); + sigset_t saved = g_mask; + g_mask = *mask; + if (g_pending & ~g_mask) { + deliver_pending(); + g_mask = saved; + errno = EINTR; + return -1; + } + g_mask = saved; + errno = ENOSYS; + return -1; +} + +__attribute__((weak)) int sigtimedwait(const sigset_t *restrict set, siginfo_t *restrict info, + const struct timespec *restrict timeout) +{ + if (kernel()) { + struct __os_timespec t, *tp = NULL; + if (timeout) { + t.sec = timeout->tv_sec; + t.nsec = timeout->tv_nsec; + tp = &t; + } + return __os_sigtimedwait(set, info, tp); + } + for (int sig = 1; sig < NSIG; sig++) + if (g_pending & *set & BIT(sig)) { + g_pending &= ~BIT(sig); + if (info) { + memset(info, 0, sizeof *info); + info->si_signo = sig; + info->si_code = SI_USER; + } + return sig; + } + errno = timeout ? EAGAIN : ENOSYS; + return -1; +} + +__attribute__((weak)) int sigwaitinfo(const sigset_t *restrict set, siginfo_t *restrict info) +{ + return sigtimedwait(set, info, NULL); +} + +__attribute__((weak)) int sigwait(const sigset_t *restrict set, int *restrict sig) +{ + int saved = errno; + int r = sigtimedwait(set, NULL, NULL); + int err = errno; + errno = saved; + if (r < 0) + return err; + *sig = r; + return 0; +} + +__attribute__((weak)) int sigaltstack(const stack_t *restrict ss, stack_t *restrict old) +{ + return __os_sigaltstack(ss, old); +} + +/* ---- sending ---------------------------------------------------------------- */ + +int raise(int sig) +{ + if (!valid(sig)) { + errno = EINVAL; + return -1; + } + if (kernel()) + return __os_thread_kill(0, sig); + return __raise_signal(sig); +} + +/* Where the kernel cannot send a signal, this program can still send + * one to itself -- the library delivers it. To any other process it is + * ENOSYS: there may well be one with that id (EmbLinkOS has processes, + * just no signals between them), and ESRCH would say there is not. */ +__attribute__((weak)) int kill(pid_t pid, int sig) +{ + int saved = errno; + if (__os_kill(pid, sig) == 0) + return 0; + if (errno != ENOSYS) + return -1; + if (pid != 0 && pid != -1 && pid != (pid_t)__os_getid(__OS_ID_PID)) + return -1; + errno = saved; + if (sig == 0) + return 0; + return __raise_signal(sig); +} + +__attribute__((weak)) int killpg(pid_t pgrp, int sig) +{ + if (pgrp < 0) { + errno = EINVAL; + return -1; + } + return kill(-pgrp, sig); +} + +__attribute__((weak)) int pthread_kill(unsigned long thread, int sig) +{ + int saved = errno; + int r; + if (kernel()) + r = __os_thread_kill(thread, sig) < 0 ? errno : 0; + else + r = __raise_signal(sig) < 0 ? errno : 0; + errno = saved; + return r; +} + +/* ---- names ----------------------------------------------------------------- */ + +__attribute__((weak)) const char *const sys_siglist[NSIG] = { + [SIGHUP] = "Hangup", [SIGINT] = "Interrupt", [SIGQUIT] = "Quit", + [SIGILL] = "Illegal instruction", [SIGTRAP] = "Trace/breakpoint trap", + [SIGABRT] = "Aborted", [SIGBUS] = "Bus error", + [SIGFPE] = "Floating point exception", [SIGKILL] = "Killed", + [SIGUSR1] = "User defined signal 1", [SIGSEGV] = "Segmentation fault", + [SIGUSR2] = "User defined signal 2", [SIGPIPE] = "Broken pipe", + [SIGALRM] = "Alarm clock", [SIGTERM] = "Terminated", + [SIGSTKFLT] = "Stack fault", [SIGCHLD] = "Child exited", + [SIGCONT] = "Continued", [SIGSTOP] = "Stopped (signal)", + [SIGTSTP] = "Stopped", [SIGTTIN] = "Stopped (tty input)", + [SIGTTOU] = "Stopped (tty output)", [SIGURG] = "Urgent I/O condition", + [SIGXCPU] = "CPU time limit exceeded", + [SIGXFSZ] = "File size limit exceeded", + [SIGVTALRM] = "Virtual timer expired", + [SIGPROF] = "Profiling timer expired", [SIGWINCH] = "Window changed", + [SIGIO] = "I/O possible", [SIGPWR] = "Power failure", + [SIGSYS] = "Bad system call", +}; + +/* glibc's words, so a program's message is the one its users know */ +__attribute__((weak)) char *strsignal(int sig) +{ + static char buf[32]; + if (valid(sig) && sys_siglist[sig]) + return (char *)sys_siglist[sig]; + if (sig >= SIGRTMIN && sig <= SIGRTMAX) + snprintf(buf, sizeof buf, "Real-time signal %d", sig - SIGRTMIN); + else + snprintf(buf, sizeof buf, "Unknown signal %d", sig); + return buf; +} + +__attribute__((weak)) void psignal(int sig, const char *msg) +{ + if (msg && *msg) + fprintf(stderr, "%s: %s\n", msg, strsignal(sig)); + else + fprintf(stderr, "%s\n", strsignal(sig)); +} + +__attribute__((weak)) void psiginfo(const siginfo_t *info, const char *msg) +{ + psignal(info->si_signo, msg); +} + +/* ---- abort ----------------------------------------------------------------- + * + * C11 7.22.4.1 and POSIX: SIGABRT is raised, so a handler may run -- and + * may not return, by longjmp or by ending the program itself. If it + * returns, abort still ends the program: the handler is taken away, the + * signal unblocked and raised again, and if even that came back, the + * program stops with the status a shell reports for SIGABRT. */ +void abort(void) +{ + raise(SIGABRT); + struct sigaction dfl; + memset(&dfl, 0, sizeof dfl); + dfl.sa_handler = SIG_DFL; + sigaction(SIGABRT, &dfl, NULL); + sigset_t s = BIT(SIGABRT); + sigprocmask(SIG_UNBLOCK, &s, NULL); + raise(SIGABRT); + __stdio_flush_all(); + __os_exit(128 + SIGABRT); + for (;;) + ; +} diff --git a/lib/libc/src/stdlib/tempfile.c b/lib/libc/src/stdlib/tempfile.c new file mode 100644 index 000000000..48aa0d8a8 --- /dev/null +++ b/lib/libc/src/stdlib/tempfile.c @@ -0,0 +1,118 @@ +/* mkstemp and its family, mkdtemp and mktemp: a file or directory made + * under a name the caller shapes, "prefixXXXXXX", the X's replaced. + * + * mkstemp's whole value is that it creates the file in the same step that + * picks the name -- O_EXCL, so the open fails if the name was taken in + * between -- and tries another name when it was. mktemp only names, which + * leaves the race to its caller; it is here because programs still call + * it, and POSIX's last edition to list it says so too. + */ +#include +#include +#include + +#include "../../os/backend.h" +#include "../internal/tmpname.h" + +unsigned long long __tmp_bits(void) +{ + static unsigned long count; + unsigned long long r; + int saved = errno; + if (__os_getentropy(&r, sizeof r) != 0) + r = (unsigned long long)__os_clock_ns() ^ + (unsigned long long)__os_time() << 32; + errno = saved; + return r + (unsigned long long)++count * 0x9E3779B97F4A7C15ull; +} + +/* The six X's before a suffix of `suffix` bytes, or NULL with EINVAL: + * fewer than six is a template the caller got wrong, and filling what + * there is would quietly make guessing the name easy. */ +static char *xs(char *t, int suffix) +{ + size_t n = strlen(t); + if (suffix < 0 || n < (size_t)suffix + 6 || + memcmp(t + n - suffix - 6, "XXXXXX", 6) != 0) { + errno = EINVAL; + return NULL; + } + return t + n - suffix - 6; +} + +static void fill(char *x) +{ + static const char abc[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + unsigned long long r = __tmp_bits(); + for (int i = 0; i < 6; i++, r /= 62) + x[i] = abc[r % 62]; +} + +/* As many tries as glibc makes. A collision is a name some other file + * already has; this many in a row is a directory full of them, or a + * failure that is not EEXIST and has already been returned. */ +#define TRIES (62 * 62 * 62) + +__attribute__((weak)) int mkostemps(char *t, int suffix, int flags) +{ + char *x = xs(t, suffix); + if (!x) + return -1; + flags &= ~(__OS_O_RDONLY | __OS_O_WRONLY | __OS_O_RDWR); + for (int i = 0; i < TRIES; i++) { + fill(x); + int fd = __os_open(t, flags | __OS_O_RDWR | __OS_O_CREAT | __OS_O_EXCL, + 0600); + if (fd >= 0 || errno != EEXIST) + return fd; + } + memcpy(x, "XXXXXX", 6); + return -1; +} + +__attribute__((weak)) int mkstemps(char *t, int suffix) { return mkostemps(t, suffix, 0); } +__attribute__((weak)) int mkostemp(char *t, int flags) { return mkostemps(t, 0, flags); } +__attribute__((weak)) int mkstemp(char *t) { return mkostemps(t, 0, 0); } + +__attribute__((weak)) char *mkdtemp(char *t) +{ + char *x = xs(t, 0); + if (!x) + return NULL; + for (int i = 0; i < TRIES; i++) { + fill(x); + if (__os_mkdir(t, 0700) == 0) + return t; + if (errno != EEXIST) + return NULL; + } + memcpy(x, "XXXXXX", 6); + return NULL; +} + +/* A name nothing has: free if lstat finds nothing there. On failure the + * template becomes the empty string, which is how mktemp reports one. */ +__attribute__((weak)) char *mktemp(char *t) +{ + char *x = xs(t, 0); + if (!x) { + if (t) + *t = 0; + return t; + } + int saved = errno; + for (int i = 0; i < TRIES; i++) { + struct __os_fileinfo fi; + fill(x); + if (__os_lstat(t, &fi) == 0) + continue; /* taken */ + if (errno == ENOENT) { + errno = saved; + return t; + } + break; /* cannot tell, so no name is safe */ + } + *t = 0; + return t; +} diff --git a/lib/libc/src/string/posix.c b/lib/libc/src/string/posix.c new file mode 100644 index 000000000..1a8370d03 --- /dev/null +++ b/lib/libc/src/string/posix.c @@ -0,0 +1,191 @@ +/* 's POSIX functions beyond C's, and the GNU and BSD ones every + * Unix C library has come to carry: strtok_r, strsep, strlcpy, strlcat, + * memmem (POSIX.1-2024 took these three in), strcasestr, memrchr, + * strchrnul, explicit_bzero. + * + * Weak, as in strings.c and for the same reason: none of these names is + * reserved to the implementation, and a program that brings its own + * strlcpy -- many do, from before C libraries had one -- keeps it. */ +#include +#include +#include + +/* strtok with the position kept by the caller. *save is NOT required to + * be meaningful when s is non-null, which is what lets a caller start a + * second, nested tokenisation with its own pointer. */ +__attribute__((weak)) char *strtok_r(char *restrict s, const char *restrict sep, + char **restrict save) +{ + if (!s) + s = *save; + if (!s) + return NULL; + s += strspn(s, sep); + if (!*s) { + *save = NULL; + return NULL; + } + char *end = s + strcspn(s, sep); + if (*end) + *end++ = 0; + else + end = NULL; + *save = end; + return s; +} + +/* strtok's opposite in the one way that matters: two separators in a row + * are an EMPTY field, not one separator. That is what parsing + * "a::b" as three fields needs. */ +__attribute__((weak)) char *strsep(char **sp, const char *sep) +{ + char *s = *sp; + if (!s) + return NULL; + char *end = s + strcspn(s, sep); + if (*end) + *end++ = 0; + else + end = NULL; + *sp = end; + return s; +} + +/* The length of the string it TRIED to make, which is how a caller asks + * "did it fit": the result is >= n exactly when it was truncated. */ +__attribute__((weak)) size_t strlcpy(char *restrict d, const char *restrict s, + size_t n) +{ + size_t len = strlen(s); + if (n) { + size_t k = len < n - 1 ? len : n - 1; + memcpy(d, s, k); + d[k] = 0; + } + return len; +} + +/* When d holds no NUL in its n bytes, it is not a string this can append + * to: nothing is written and the result is n + strlen(s), still at least + * n, so the caller sees a truncation and not a success. */ +__attribute__((weak)) size_t strlcat(char *restrict d, const char *restrict s, + size_t n) +{ + size_t have = strnlen(d, n); + if (have == n) + return n + strlen(s); + return have + strlcpy(d + have, s, n - have); +} + +/* The first place the n bytes of `needle` occur in the hn of `hay`. An + * empty needle is found at the start, as strstr finds "". */ +__attribute__((weak)) void *memmem(const void *hay, size_t hn, + const void *needle, size_t nn) +{ + const unsigned char *h = hay, *n = needle; + if (nn == 0) + return (void *)h; + if (nn > hn) + return NULL; + const unsigned char *last = h + (hn - nn); + for (const unsigned char *p = h; p <= last; p++) { + p = memchr(p, n[0], (size_t)(last - p) + 1); + if (!p) + return NULL; + if (memcmp(p, n, nn) == 0) + return (void *)p; + } + return NULL; +} + +__attribute__((weak)) char *strcasestr(const char *h, const char *n) +{ + size_t k = strlen(n); + for (; *h; h++) + if (strncasecmp(h, n, k) == 0) + return (char *)h; + return k == 0 ? (char *)h : NULL; +} + +__attribute__((weak)) void *memrchr(const void *s, int c, size_t n) +{ + const unsigned char *p = (const unsigned char *)s + n; + while (n--) + if (*--p == (unsigned char)c) + return (void *)p; + return NULL; +} + +/* strchr, except that not finding c answers the terminating NUL rather + * than NULL -- so the result is always somewhere a loop can stop. */ +__attribute__((weak)) char *strchrnul(const char *s, int c) +{ + while (*s && *s != (char)c) + s++; + return (char *)s; +} + +/* bzero that the compiler may not delete as a dead store: the bytes are + * a secret that is about to go out of scope, which is exactly the store + * an optimizer proves useless. C23's memset_explicit is that guarantee, + * and this is its BSD spelling. */ +__attribute__((weak)) void explicit_bzero(void *s, size_t n) +{ + memset_explicit(s, 0, n); +} + +/* Strings compared as versions, so "file9" sorts before "file10" -- + * glibc's state machine, whose order `ls -v` and versionsort users know: + * a digit run is an integer unless it starts with 0, when it is a + * fraction, and a fraction sorts before an integer. */ +#define VS_N 0 /* not in a number */ +#define VS_I 3 /* in an integer */ +#define VS_F 6 /* in a fraction */ +#define VS_Z 9 /* in leading zeros */ +#define VS_CMP 2 +#define VS_LEN 3 + +__attribute__((weak)) int strverscmp(const char *a, const char *b) +{ + static const unsigned char next[] = { + /* x d 0 */ + /* N */ VS_N, VS_I, VS_Z, + /* I */ VS_N, VS_I, VS_I, + /* F */ VS_N, VS_F, VS_F, + /* Z */ VS_N, VS_F, VS_Z, + }; + static const signed char result[] = { + /* x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */ + /* N */ VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_LEN, VS_CMP, VS_CMP, VS_CMP, VS_CMP, + /* I */ VS_CMP, -1, -1, +1, VS_LEN, VS_LEN, +1, VS_LEN, VS_LEN, + /* F */ VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_CMP, VS_CMP, + /* Z */ VS_CMP, +1, +1, -1, VS_CMP, VS_CMP, -1, VS_CMP, VS_CMP, + }; + const unsigned char *p = (const unsigned char *)a, + *q = (const unsigned char *)b; + if (p == q) + return 0; + unsigned c1 = *p++, c2 = *q++; + int state = VS_N + (c1 == '0') + (isdigit(c1) != 0); + int diff; + while ((diff = (int)c1 - (int)c2) == 0) { + if (!c1) + return 0; + state = next[state]; + c1 = *p++; + c2 = *q++; + state += (c1 == '0') + (isdigit(c1) != 0); + } + state = result[state * 3 + (c2 == '0') + (isdigit(c2) != 0)]; + switch (state) { + case VS_CMP: + return diff; + case VS_LEN: + while (isdigit(*p++)) + if (!isdigit(*q++)) + return 1; + return isdigit(*q) ? -1 : diff; + default: + return state; + } +} diff --git a/lib/libc/src/string/strerror.c b/lib/libc/src/string/strerror.c index 521279fb2..5a84b75c1 100644 --- a/lib/libc/src/string/strerror.c +++ b/lib/libc/src/string/strerror.c @@ -93,3 +93,26 @@ char *strerror(int errnum) snprintf(unknown, sizeof unknown, "Unknown error %d", errnum); return unknown; } + +/* XSI's strerror_r: the message copied into the caller's buffer, so two + * threads asking at once do not share `unknown` above. 0, or ERANGE when + * the buffer was too small -- in which case it still holds as much of + * the message as fit, terminated, because a truncated message is more + * use to a person than an empty one. EINVAL for a number with no + * message, whose buffer says "Unknown error N" as strerror would. */ +__attribute__((weak)) int strerror_r(int errnum, char *buf, size_t n) +{ + int known = errnum >= 0 && (size_t)errnum < sizeof msg / sizeof msg[0] && + msg[errnum]; + char tmp[32]; + const char *m = known ? msg[errnum] : tmp; + if (!known) + snprintf(tmp, sizeof tmp, "Unknown error %d", errnum); + size_t len = strlen(m); + if (n) { + size_t k = len < n - 1 ? len : n - 1; + memcpy(buf, m, k); + buf[k] = 0; + } + return !known ? EINVAL : len >= n ? ERANGE : 0; +} diff --git a/lib/libc/src/threads/pthread.c b/lib/libc/src/threads/pthread.c new file mode 100644 index 000000000..354f0e64c --- /dev/null +++ b/lib/libc/src/threads/pthread.c @@ -0,0 +1,783 @@ +/* POSIX threads, over the OS seam (lib/libc/os/backend.h), and the core + * C11's is built on too (threads.c). + * + * ---- the thread table ------------------------------------------------------ + * + * One record per thread this library started, linked under one lock, and + * one made on first need for a thread it did not start (the program's + * first, or a C++ std::thread, which goes to the seam directly). A + * record holds what a thread leaves behind for its joiner -- its result + * -- and what it carries while it runs: its thread-specific data, its + * name, and the jump that pthread_exit takes. + * + * pthread_exit is a longjmp back to the start of the thread, where its + * keys' destructors run and the record is marked done: the seam ends a + * thread when its function returns, and has no other way to. Frames it + * jumps over are not unwound -- a C++ destructor between pthread_exit + * and the thread's function does not run, as it does not under musl. + * + * ---- what a backend without threads gets ---------------------------------- + * + * The same code with one thread: pthread_create fails with EAGAIN, every + * lock is taken at once, and thread-specific data works -- one record, + * the program's. A timed wait waits no longer than one try, since there + * is nobody who could make it succeed. + */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include + +#include "../../os/backend.h" +#include "../internal/lock.h" +#include "thr.h" + +/* ---- the futex word ------------------------------------------------------- */ + +long __thr_until(const struct timespec *abs, clockid_t clock) +{ + if (!abs) + return -1; + struct timespec now; + if (clock_gettime(clock, &now) != 0) + return 0; /* no clock: the deadline is now */ + long long ns = (long long)(abs->tv_sec - now.tv_sec) * 1000000000LL + + (abs->tv_nsec - now.tv_nsec); + if (ns <= 0) + return 0; + /* a long may be 32 bits: wait that long and look again */ + return ns > LONG_MAX ? LONG_MAX : (long)ns; +} + +static void sleep_on(volatile int *w, int expected, long ns) +{ + int saved = errno; + if (__os_futex_wait(w, expected, ns) < 0 && errno == ENOSYS) + __os_thread_yield(); + errno = saved; +} + +int __word_trylock(volatile int *w) +{ + int z = 0; + return __atomic_compare_exchange_n(w, &z, 1, 0, __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED); +} + +/* The three-state lock in its standard form: the exchange that marks the + * word contended is also the attempt to take it, so a lock released + * between a failed try and the sleep is taken, never slept on. */ +int __word_lock(volatile int *w, const struct timespec *abs, clockid_t clock) +{ + int c = 0; + if (__atomic_compare_exchange_n(w, &c, 1, 0, __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED)) + return 0; + if (c != 2) + c = __atomic_exchange_n(w, 2, __ATOMIC_ACQUIRE); + while (c != 0) { + long ns = __thr_until(abs, clock); + if (abs && ns == 0) + return ETIMEDOUT; + sleep_on(w, 2, ns); + c = __atomic_exchange_n(w, 2, __ATOMIC_ACQUIRE); + } + return 0; +} + +void __word_unlock(volatile int *w) +{ + if (__atomic_exchange_n(w, 0, __ATOMIC_RELEASE) == 2) + __os_futex_wake(w, 1); +} + +static int bad_time(const struct timespec *t) +{ + return !t || t->tv_nsec < 0 || t->tv_nsec >= 1000000000L; +} + +/* ---- the thread table ----------------------------------------------------- */ + +static __lock_t g_tlock = LOCK_INIT; +static struct __thr *g_threads; +static unsigned long g_main_id; +static int g_main_known; +#if defined(__linux__) +static __thread struct __thr *g_me; /* a lookup per call otherwise */ +#endif + +static void link_thr(struct __thr *t) +{ + t->next = g_threads; + g_threads = t; +} + +static void unlink_thr(struct __thr *t) +{ + for (struct __thr **p = &g_threads; *p; p = &(*p)->next) + if (*p == t) { + *p = t->next; + return; + } +} + +/* a running thread's record: a finished one may still hold its id until + * it is joined, and the kernel may already have given the id away */ +static struct __thr *find_live(unsigned long id) +{ + for (struct __thr *t = g_threads; t; t = t->next) + if (t->id == id && !t->done) + return t; + return NULL; +} + +static struct __thr *find_any(unsigned long id) +{ + struct __thr *live = find_live(id); + if (live) + return live; + for (struct __thr *t = g_threads; t; t = t->next) + if (t->id == id && t->ours) + return t; + return NULL; +} + +struct __thr *__thr_self(void) +{ +#if defined(__linux__) + if (g_me) + return g_me; +#endif + unsigned long me = __os_thread_self(); + __lock(&g_tlock); + if (!g_main_known) { + /* the first thread to ask, before any other was started here; + * from now on a fork's child must hear that it has one thread */ + g_main_id = me; + g_main_known = 1; + __atfork_hook = __pthread_atfork_run; + } + struct __thr *t = find_live(me); + if (!t) { + t = calloc(1, sizeof *t); + if (t) { + t->id = me; + link_thr(t); + } + } + __unlock(&g_tlock); + if (!t) /* no memory: a record nobody else shares */ + abort(); +#if defined(__linux__) + g_me = t; +#endif + return t; +} + +/* ---- thread-specific data ------------------------------------------------- */ + +static __lock_t g_klock = LOCK_INIT; +static struct { int used; void (*dtor)(void *); } g_keys[__THR_KEYS]; + +WEAK int pthread_key_create(pthread_key_t *key, void (*dtor)(void *)) +{ + __lock(&g_klock); + for (unsigned k = 0; k < __THR_KEYS; k++) + if (!g_keys[k].used) { + g_keys[k].used = 1; + g_keys[k].dtor = dtor; + /* a key used before belongs to nobody now: no thread may + * find the old one's value under the new one */ + __lock(&g_tlock); + for (struct __thr *t = g_threads; t; t = t->next) + t->tsd[k] = NULL; + __unlock(&g_tlock); + __unlock(&g_klock); + *key = k; + return 0; + } + __unlock(&g_klock); + return EAGAIN; +} + +WEAK int pthread_key_delete(pthread_key_t key) +{ + if (key >= __THR_KEYS) + return EINVAL; + __lock(&g_klock); + int was = g_keys[key].used; + g_keys[key].used = 0; + g_keys[key].dtor = NULL; + __unlock(&g_klock); + return was ? 0 : EINVAL; +} + +WEAK void *pthread_getspecific(pthread_key_t key) +{ + return key < __THR_KEYS ? __thr_self()->tsd[key] : NULL; +} + +WEAK int pthread_setspecific(pthread_key_t key, const void *value) +{ + if (key >= __THR_KEYS || !g_keys[key].used) + return EINVAL; + __thr_self()->tsd[key] = (void *)value; + return 0; +} + +/* POSIX's rounds: a destructor may set a value again, and up to + * PTHREAD_DESTRUCTOR_ITERATIONS passes are made while any is left */ +static void run_dtors(struct __thr *t) +{ + for (int round = 0; round < __THR_DTOR_ROUNDS; round++) { + int any = 0; + for (unsigned k = 0; k < __THR_KEYS; k++) { + void *v = t->tsd[k]; + void (*d)(void *) = g_keys[k].used ? g_keys[k].dtor : NULL; + if (v && d) { + t->tsd[k] = NULL; + d(v); + any = 1; + } + } + if (!any) + return; + } +} + +/* ---- threads -------------------------------------------------------------- */ + +static void finish(struct __thr *t) +{ + __lock(&g_tlock); + t->done = 1; + /* detached and already handed over: nobody will join, so the record + * goes now. A thread detached before pthread_create returned is + * freed there instead (registered == 0). */ + int drop = t->detached == 1 && t->registered; + if (drop) + unlink_thr(t); + __unlock(&g_tlock); + if (drop) + free(t); +} + +static void start(void *p) +{ + struct __thr *t = p; + __atomic_store_n(&t->id, __os_thread_self(), __ATOMIC_RELEASE); +#if defined(__linux__) + g_me = t; +#endif + if (!setjmp(t->exit_to)) + t->result = t->fn(t->arg); + run_dtors(t); + finish(t); +} + +/* errno-style, for both spellings: ENOMEM and the backend's own reason + * are kept apart here and mapped by each caller */ +int __thr_create(pthread_t *out, const void *attr_, + void *(*fn)(void *), void *arg) +{ + const pthread_attr_t *a = attr_; + if (!out || !fn) + return EINVAL; + if (a && a->__stackaddr) + return EINVAL; /* the seam starts a thread on its own stack */ + if (a && a->__inherit == PTHREAD_EXPLICIT_SCHED && + (a->__policy != SCHED_OTHER || a->__param.sched_priority != 0)) + return EPERM; /* no policy can be set here: say so */ + struct __thr *t = calloc(1, sizeof *t); + if (!t) + return ENOMEM; + t->fn = fn; + t->arg = arg; + t->ours = 1; + t->detached = a && a->__detach == PTHREAD_CREATE_DETACHED; + (void)__thr_self(); /* the creator has a record before any child */ + __lock(&g_tlock); + link_thr(t); + __unlock(&g_tlock); + unsigned long id = 0; + int saved = errno; + if (__os_thread_create(&id, start, t) != 0) { + int e = errno; + errno = saved; + __lock(&g_tlock); + unlink_thr(t); + __unlock(&g_tlock); + free(t); + return e == ENOMEM ? ENOMEM : EAGAIN; + } + errno = saved; + __lock(&g_tlock); + t->id = id; + t->registered = 1; + int gone = t->detached && t->done; + if (gone) + unlink_thr(t); + __unlock(&g_tlock); + if (t->detached) + __os_thread_detach(id); + if (gone) + free(t); + *out = id; + return 0; +} + +WEAK int pthread_create(pthread_t *__restrict out, + const pthread_attr_t *__restrict a, + void *(*fn)(void *), void *__restrict arg) +{ + int e = __thr_create(out, a, fn, arg); + return e == ENOMEM ? EAGAIN : e; +} + +WEAK pthread_t pthread_self(void) { return __os_thread_self(); } + +WEAK int pthread_equal(pthread_t a, pthread_t b) { return a == b; } + +static int join(pthread_t id, void **result, int wait, + const struct timespec *abs) +{ + if (id == __os_thread_self()) + return EDEADLK; + __lock(&g_tlock); + struct __thr *t = find_any(id); + if (!t || !t->ours) { + __unlock(&g_tlock); + return ESRCH; + } + if (t->detached) { + __unlock(&g_tlock); + return EINVAL; + } + if (!wait && !t->done) { + __unlock(&g_tlock); + return EBUSY; + } + __unlock(&g_tlock); + if (abs) { + /* the seam's join has no deadline: look until it is done */ + while (!__atomic_load_n(&t->done, __ATOMIC_ACQUIRE)) { + long ns = __thr_until(abs, CLOCK_REALTIME); + if (ns == 0) + return ETIMEDOUT; + __os_sleep_ns(ns < 1000000 ? ns : 1000000); + } + } + __lock(&g_tlock); + t->detached = 2; /* joined: nobody else may */ + __unlock(&g_tlock); + int saved = errno; + __os_thread_join(id); + errno = saved; + if (result) + *result = t->result; + __lock(&g_tlock); + unlink_thr(t); + __unlock(&g_tlock); + free(t); + return 0; +} + +WEAK int pthread_join(pthread_t t, void **result) { return join(t, result, 1, NULL); } +WEAK int pthread_tryjoin_np(pthread_t t, void **result) { return join(t, result, 0, NULL); } + +WEAK int pthread_timedjoin_np(pthread_t t, void **result, + const struct timespec *abstime) +{ + if (bad_time(abstime)) + return EINVAL; + return join(t, result, 1, abstime); +} + +WEAK int pthread_detach(pthread_t id) +{ + __lock(&g_tlock); + struct __thr *t = find_any(id); + if (!t || !t->ours) { + __unlock(&g_tlock); + return ESRCH; + } + if (t->detached) { + __unlock(&g_tlock); + return EINVAL; + } + t->detached = 1; + int done = t->done && t->registered; + if (done) + unlink_thr(t); + __unlock(&g_tlock); + int saved = errno; + __os_thread_detach(id); + errno = saved; + if (done) + free(t); + return 0; +} + +static int others_running(unsigned long me) +{ + __lock(&g_tlock); + int n = 0; + for (struct __thr *t = g_threads; t; t = t->next) + if (t->ours && !t->done && t->id != me) + n++; + __unlock(&g_tlock); + return n; +} + +WEAK _Noreturn void pthread_exit(void *result) +{ + struct __thr *t = __thr_self(); + if (t->ours) { + t->result = result; + longjmp(t->exit_to, 1); + } + run_dtors(t); + if (g_main_known && t->id == g_main_id) { + /* the program's first thread: the process ends when its last + * thread does, with status 0 */ + while (others_running(t->id)) + if (__os_sleep_ns(1000000) != 0) + __os_thread_yield(); + exit(0); + } + /* a thread started outside this library cannot be unwound to its + * start; it stops here for good */ + for (;;) + if (__os_sleep_ns(1000000000L) != 0) + __os_thread_yield(); +} + +WEAK int pthread_once(pthread_once_t *once, void (*fn)(void)) +{ + if (__atomic_load_n(once, __ATOMIC_ACQUIRE) == 2) + return 0; + for (;;) { + int e = 0; + if (__atomic_compare_exchange_n(once, &e, 1, 0, __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED)) { + fn(); + __atomic_store_n(once, 2, __ATOMIC_RELEASE); + __os_futex_wake(once, -1); + return 0; + } + if (e == 2) + return 0; + sleep_on(once, 1, -1); + } +} + +/* ---- fork's handlers ------------------------------------------------------- */ + +#define ATFORK_MAX 32 +static struct { void (*prepare)(void), (*parent)(void), (*child)(void); } + g_atfork[ATFORK_MAX]; +static int g_natfork; + +WEAK int pthread_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)) +{ + __lock(&g_tlock); + if (g_natfork == ATFORK_MAX) { + __unlock(&g_tlock); + return ENOMEM; + } + __atfork_hook = __pthread_atfork_run; + g_atfork[g_natfork].prepare = prepare; + g_atfork[g_natfork].parent = parent; + g_atfork[g_natfork].child = child; + g_natfork++; + __unlock(&g_tlock); + return 0; +} + +/* fork() calls this (proc.c, through the hook pthread_atfork sets: a + * program that never names a pthread function does not link this file): + * 0 before, 1 in the parent after, 2 in the child. POSIX's order -- + * prepare handlers in reverse of registration, the others forward. */ +void __pthread_atfork_run(int phase) +{ + if (phase == 0) { + for (int i = g_natfork - 1; i >= 0; i--) + if (g_atfork[i].prepare) + g_atfork[i].prepare(); + return; + } + if (phase == 2) { + /* the child is one thread, the one that forked, under a new id: + * every other record names a thread that is not there */ + struct __thr *me = __thr_self(); + g_threads = me; + me->next = NULL; + me->id = __os_thread_self(); + g_main_id = me->id; + g_tlock.v = 0; + g_klock.v = 0; + } + for (int i = 0; i < g_natfork; i++) { + void (*f)(void) = phase == 1 ? g_atfork[i].parent : g_atfork[i].child; + if (f) + f(); + } +} + +/* ---- scheduling, cancellation, names ---------------------------------------- */ + +static int g_concurrency; +WEAK int pthread_getconcurrency(void) { return g_concurrency; } +WEAK int pthread_setconcurrency(int level) +{ + if (level < 0) + return EINVAL; + g_concurrency = level; + return 0; +} + +/* Every thread runs under SCHED_OTHER at priority 0, and this library + * has no way to change that, so asking for anything else is EPERM -- + * what an unprivileged Linux process is told. */ +WEAK int pthread_getschedparam(pthread_t t, int *__restrict policy, + struct sched_param *__restrict param) +{ + (void)t; + *policy = SCHED_OTHER; + param->sched_priority = 0; + return 0; +} + +WEAK int pthread_setschedparam(pthread_t t, int policy, + const struct sched_param *param) +{ + (void)t; + if (policy != SCHED_OTHER && policy != SCHED_FIFO && policy != SCHED_RR) + return EINVAL; + return policy == SCHED_OTHER && param->sched_priority == 0 ? 0 : EPERM; +} + +WEAK int pthread_setschedprio(pthread_t t, int prio) +{ + (void)t; + return prio == 0 ? 0 : EINVAL; +} + +WEAK int pthread_getcpuclockid(pthread_t t, clockid_t *clock) +{ + if (t != __os_thread_self()) + return ENOENT; /* only a thread's own time can be read */ + *clock = CLOCK_THREAD_CPUTIME_ID; + return 0; +} + +/* No call here is a cancellation point, so a request could never be + * acted on: it is refused rather than accepted and ignored. */ +WEAK int pthread_cancel(pthread_t t) +{ + (void)t; + return ENOTSUP; +} + +WEAK int pthread_setcancelstate(int state, int *old) +{ + if (state != PTHREAD_CANCEL_ENABLE && state != PTHREAD_CANCEL_DISABLE) + return EINVAL; + struct __thr *t = __thr_self(); + if (old) + *old = t->cancel_state; + t->cancel_state = state; + return 0; +} + +WEAK int pthread_setcanceltype(int type, int *old) +{ + if (type != PTHREAD_CANCEL_DEFERRED && type != PTHREAD_CANCEL_ASYNCHRONOUS) + return EINVAL; + struct __thr *t = __thr_self(); + if (old) + *old = t->cancel_type; + t->cancel_type = type; + return 0; +} + +WEAK void pthread_testcancel(void) {} + +WEAK int pthread_yield(void) { __os_thread_yield(); return 0; } + +static struct __thr *named(pthread_t id) +{ + if (id == __os_thread_self()) + return __thr_self(); + __lock(&g_tlock); + struct __thr *t = find_live(id); + __unlock(&g_tlock); + return t; +} + +WEAK int pthread_setname_np(pthread_t id, const char *name) +{ + if (strlen(name) >= sizeof ((struct __thr *)0)->name) + return ERANGE; + struct __thr *t = named(id); + if (!t) + return ESRCH; + strcpy(t->name, name); + return 0; +} + +WEAK int pthread_getname_np(pthread_t id, char *name, size_t n) +{ + struct __thr *t = named(id); + if (!t) + return ESRCH; + if (strlen(t->name) >= n) + return ERANGE; + strcpy(name, t->name); + return 0; +} + +/* ---- attributes ------------------------------------------------------------- */ + +#define DEFAULT_STACK (8UL * 1024 * 1024) + +WEAK int pthread_attr_init(pthread_attr_t *a) +{ + memset(a, 0, sizeof *a); + a->__stacksize = DEFAULT_STACK; + a->__guardsize = 4096; + return 0; +} + +WEAK int pthread_attr_destroy(pthread_attr_t *a) { (void)a; return 0; } + +WEAK int pthread_attr_getdetachstate(const pthread_attr_t *a, int *s) +{ + *s = a->__detach; + return 0; +} + +WEAK int pthread_attr_setdetachstate(pthread_attr_t *a, int s) +{ + if (s != PTHREAD_CREATE_JOINABLE && s != PTHREAD_CREATE_DETACHED) + return EINVAL; + a->__detach = s; + return 0; +} + +/* The size is kept and reported. Whether a thread gets it is the + * backend's: Linux's threads have 8 MiB, mapped on demand, as glibc's + * do. */ +WEAK int pthread_attr_getstacksize(const pthread_attr_t *__restrict a, + size_t *__restrict n) +{ + *n = a->__stacksize; + return 0; +} + +WEAK int pthread_attr_setstacksize(pthread_attr_t *a, size_t n) +{ + if (n < PTHREAD_STACK_MIN) + return EINVAL; + a->__stacksize = n; + return 0; +} + +WEAK int pthread_attr_getguardsize(const pthread_attr_t *__restrict a, + size_t *__restrict n) +{ + *n = a->__guardsize; + return 0; +} + +WEAK int pthread_attr_setguardsize(pthread_attr_t *a, size_t n) +{ + a->__guardsize = n; + return 0; +} + +WEAK int pthread_attr_getstack(const pthread_attr_t *__restrict a, + void **__restrict addr, size_t *__restrict n) +{ + *addr = a->__stackaddr; + *n = a->__stacksize; + return 0; +} + +/* kept, and refused by pthread_create: the seam starts every thread on + * a stack of its own making */ +WEAK int pthread_attr_setstack(pthread_attr_t *a, void *addr, size_t n) +{ + if (n < PTHREAD_STACK_MIN) + return EINVAL; + a->__stackaddr = addr; + a->__stacksize = n; + return 0; +} + +WEAK int pthread_attr_getinheritsched(const pthread_attr_t *__restrict a, + int *__restrict s) +{ + *s = a->__inherit; + return 0; +} + +WEAK int pthread_attr_setinheritsched(pthread_attr_t *a, int s) +{ + if (s != PTHREAD_INHERIT_SCHED && s != PTHREAD_EXPLICIT_SCHED) + return EINVAL; + a->__inherit = s; + return 0; +} + +WEAK int pthread_attr_getschedpolicy(const pthread_attr_t *__restrict a, + int *__restrict p) +{ + *p = a->__policy; + return 0; +} + +WEAK int pthread_attr_setschedpolicy(pthread_attr_t *a, int p) +{ + if (p != SCHED_OTHER && p != SCHED_FIFO && p != SCHED_RR) + return EINVAL; + a->__policy = p; + return 0; +} + +WEAK int pthread_attr_getschedparam(const pthread_attr_t *__restrict a, + struct sched_param *__restrict p) +{ + *p = a->__param; + return 0; +} + +WEAK int pthread_attr_setschedparam(pthread_attr_t *__restrict a, + const struct sched_param *__restrict p) +{ + a->__param = *p; + return 0; +} + +WEAK int pthread_attr_getscope(const pthread_attr_t *__restrict a, + int *__restrict s) +{ + *s = a->__scope; + return 0; +} + +/* every thread is the kernel's: PROCESS scope is not there to have */ +WEAK int pthread_attr_setscope(pthread_attr_t *a, int s) +{ + if (s == PTHREAD_SCOPE_PROCESS) + return ENOTSUP; + if (s != PTHREAD_SCOPE_SYSTEM) + return EINVAL; + a->__scope = s; + return 0; +} diff --git a/lib/libc/src/threads/sem.c b/lib/libc/src/threads/sem.c new file mode 100644 index 000000000..e3c4f6236 --- /dev/null +++ b/lib/libc/src/threads/sem.c @@ -0,0 +1,134 @@ +/* : a counter and a futex on it. A waiter sleeps only while + * the count is 0 -- the futex compares that under the kernel's lock -- + * so a post between its look and its sleep is never lost; the waiter + * count lets a post skip the wake when nobody sleeps. These functions + * set errno and return -1, as POSIX's semaphores do. */ +#include +#include +#include + +#include "../../os/backend.h" +#include "thr.h" + +WEAK int sem_init(sem_t *s, int pshared, unsigned value) +{ + if (value > SEM_VALUE_MAX) { + errno = EINVAL; + return -1; + } + if (pshared) { + errno = ENOTSUP; /* every futex here is private */ + return -1; + } + s->__value = (int)value; + s->__waiters = 0; + return 0; +} + +WEAK int sem_destroy(sem_t *s) { (void)s; return 0; } + +static int take(sem_t *s) +{ + int v = __atomic_load_n(&s->__value, __ATOMIC_RELAXED); + while (v > 0) + if (__atomic_compare_exchange_n(&s->__value, &v, v - 1, 0, + __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) + return 1; + return 0; +} + +WEAK int sem_trywait(sem_t *s) +{ + if (take(s)) + return 0; + errno = EAGAIN; + return -1; +} + +static int wait_until(sem_t *s, const struct timespec *abs, clockid_t clock) +{ + if (abs && (abs->tv_nsec < 0 || abs->tv_nsec >= 1000000000L)) { + errno = EINVAL; + return -1; + } + for (;;) { + if (take(s)) + return 0; + long ns = __thr_until(abs, clock); + if (abs && ns == 0) { + errno = ETIMEDOUT; + return -1; + } + __atomic_fetch_add(&s->__waiters, 1, __ATOMIC_ACQ_REL); + int saved = errno; + if (__os_futex_wait(&s->__value, 0, ns) < 0 && errno == ENOSYS) + __os_thread_yield(); + errno = saved; + __atomic_fetch_sub(&s->__waiters, 1, __ATOMIC_ACQ_REL); + } +} + +WEAK int sem_wait(sem_t *s) { return wait_until(s, NULL, CLOCK_REALTIME); } + +WEAK int sem_timedwait(sem_t *__restrict s, + const struct timespec *__restrict abstime) +{ + if (!abstime) { + errno = EINVAL; + return -1; + } + return wait_until(s, abstime, CLOCK_REALTIME); +} + +WEAK int sem_clockwait(sem_t *__restrict s, clockid_t clock, + const struct timespec *__restrict abstime) +{ + if (!abstime || (clock != CLOCK_REALTIME && clock != CLOCK_MONOTONIC)) { + errno = EINVAL; + return -1; + } + return wait_until(s, abstime, clock); +} + +WEAK int sem_post(sem_t *s) +{ + int v = __atomic_load_n(&s->__value, __ATOMIC_RELAXED); + do { + if (v == SEM_VALUE_MAX) { + errno = EOVERFLOW; + return -1; + } + } while (!__atomic_compare_exchange_n(&s->__value, &v, v + 1, 0, + __ATOMIC_RELEASE, __ATOMIC_RELAXED)); + if (__atomic_load_n(&s->__waiters, __ATOMIC_ACQUIRE)) + __os_futex_wake(&s->__value, 1); + return 0; +} + +WEAK int sem_getvalue(sem_t *__restrict s, int *__restrict value) +{ + *value = __atomic_load_n(&s->__value, __ATOMIC_RELAXED); + return 0; +} + +WEAK sem_t *sem_open(const char *name, int oflag, ...) +{ + (void)name; + (void)oflag; + errno = ENOSYS; + return SEM_FAILED; +} + +WEAK int sem_close(sem_t *s) +{ + (void)s; + errno = EINVAL; /* no semaphore was ever opened */ + return -1; +} + +WEAK int sem_unlink(const char *name) +{ + (void)name; + errno = ENOSYS; + return -1; +} diff --git a/lib/libc/src/threads/sync.c b/lib/libc/src/threads/sync.c new file mode 100644 index 000000000..87e19963f --- /dev/null +++ b/lib/libc/src/threads/sync.c @@ -0,0 +1,612 @@ +/* POSIX's locks: mutexes, condition variables, read-write locks, + * barriers and spin locks, each made of the futex word in thr.h. + * + * Process-shared objects are refused (ENOTSUP): every futex here is the + * private kind, which another process cannot wake. So are robust and + * priority-protocol mutexes, which need the kernel's help. */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include + +#include "../../os/backend.h" +#include "thr.h" + +static int bad_time(const struct timespec *t) +{ + return !t || t->tv_nsec < 0 || t->tv_nsec >= 1000000000L; +} + +static int bad_clock(clockid_t c) +{ + return c != CLOCK_REALTIME && c != CLOCK_MONOTONIC; +} + +/* 1 when there is no futex here -- and so no other thread to wake us */ +static int sleep_on(volatile int *w, int expected, long ns) +{ + int saved = errno, absent = 0; + if (__os_futex_wait(w, expected, ns) < 0 && errno == ENOSYS) { + __os_thread_yield(); + absent = 1; + } + errno = saved; + return absent; +} + +/* ---- mutexes ---------------------------------------------------------------- */ + +WEAK int pthread_mutexattr_init(pthread_mutexattr_t *a) +{ + memset(a, 0, sizeof *a); + return 0; +} + +WEAK int pthread_mutexattr_destroy(pthread_mutexattr_t *a) { (void)a; return 0; } + +WEAK int pthread_mutexattr_gettype(const pthread_mutexattr_t *__restrict a, + int *__restrict t) +{ + *t = a->__type; + return 0; +} + +WEAK int pthread_mutexattr_settype(pthread_mutexattr_t *a, int t) +{ + if (t != PTHREAD_MUTEX_NORMAL && t != PTHREAD_MUTEX_RECURSIVE && + t != PTHREAD_MUTEX_ERRORCHECK) + return EINVAL; + a->__type = t; + return 0; +} + +WEAK int pthread_mutexattr_getpshared(const pthread_mutexattr_t *__restrict a, + int *__restrict s) +{ + *s = a->__pshared; + return 0; +} + +static int pshared(int s) +{ + if (s == PTHREAD_PROCESS_PRIVATE) + return 0; + return s == PTHREAD_PROCESS_SHARED ? ENOTSUP : EINVAL; +} + +WEAK int pthread_mutexattr_setpshared(pthread_mutexattr_t *a, int s) +{ + int e = pshared(s); + if (!e) + a->__pshared = s; + return e; +} + +WEAK int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *__restrict a, + int *__restrict p) +{ + *p = a->__protocol; + return 0; +} + +WEAK int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int p) +{ + if (p == PTHREAD_PRIO_NONE) { + a->__protocol = p; + return 0; + } + return p == PTHREAD_PRIO_INHERIT || p == PTHREAD_PRIO_PROTECT ? ENOTSUP + : EINVAL; +} + +WEAK int pthread_mutexattr_getrobust(const pthread_mutexattr_t *__restrict a, + int *__restrict r) +{ + *r = a->__robust; + return 0; +} + +WEAK int pthread_mutexattr_setrobust(pthread_mutexattr_t *a, int r) +{ + if (r == PTHREAD_MUTEX_STALLED) { + a->__robust = r; + return 0; + } + return r == PTHREAD_MUTEX_ROBUST ? ENOTSUP : EINVAL; +} + +WEAK int pthread_mutex_init(pthread_mutex_t *__restrict m, + const pthread_mutexattr_t *__restrict a) +{ + memset(m, 0, sizeof *m); + m->__type = a ? a->__type : PTHREAD_MUTEX_NORMAL; + return 0; +} + +WEAK int pthread_mutex_destroy(pthread_mutex_t *m) +{ + return __atomic_load_n(&m->__state, __ATOMIC_RELAXED) ? EBUSY : 0; +} + +/* Ownership is recorded for all three kinds, so that a recursive lock + * can count and an error-checking one can refuse; a NORMAL mutex relocked + * by its owner deadlocks, as POSIX says it does. */ +static int mutex_lock(pthread_mutex_t *m, const struct timespec *abs, + clockid_t clock) +{ + unsigned long me = __os_thread_self(); + if (m->__count && m->__owner == me) { + if (m->__type == PTHREAD_MUTEX_RECURSIVE) { + if (m->__count == UINT_MAX) + return EAGAIN; + m->__count++; + return 0; + } + if (m->__type == PTHREAD_MUTEX_ERRORCHECK) + return EDEADLK; + } + int e = __word_lock(&m->__state, abs, clock); + if (e) + return e; + m->__owner = me; + m->__count = 1; + return 0; +} + +WEAK int pthread_mutex_lock(pthread_mutex_t *m) +{ + return mutex_lock(m, NULL, CLOCK_REALTIME); +} + +WEAK int pthread_mutex_timedlock(pthread_mutex_t *__restrict m, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime)) + return EINVAL; + return mutex_lock(m, abstime, CLOCK_REALTIME); +} + +WEAK int pthread_mutex_clocklock(pthread_mutex_t *__restrict m, clockid_t clock, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime) || bad_clock(clock)) + return EINVAL; + return mutex_lock(m, abstime, clock); +} + +WEAK int pthread_mutex_trylock(pthread_mutex_t *m) +{ + unsigned long me = __os_thread_self(); + if (m->__count && m->__owner == me && + m->__type == PTHREAD_MUTEX_RECURSIVE) { + if (m->__count == UINT_MAX) + return EAGAIN; + m->__count++; + return 0; + } + if (!__word_trylock(&m->__state)) + return EBUSY; + m->__owner = me; + m->__count = 1; + return 0; +} + +WEAK int pthread_mutex_unlock(pthread_mutex_t *m) +{ + if (m->__type != PTHREAD_MUTEX_NORMAL && + (!m->__count || m->__owner != __os_thread_self())) + return EPERM; + if (m->__count > 1) { + m->__count--; + return 0; + } + m->__owner = 0; + m->__count = 0; + __word_unlock(&m->__state); + return 0; +} + +WEAK int pthread_mutex_consistent(pthread_mutex_t *m) +{ + (void)m; + return EINVAL; /* no mutex here is robust */ +} + +WEAK int pthread_mutex_getprioceiling(const pthread_mutex_t *__restrict m, + int *__restrict ceiling) +{ + (void)m; + (void)ceiling; + return EINVAL; /* none is PTHREAD_PRIO_PROTECT */ +} + +WEAK int pthread_mutex_setprioceiling(pthread_mutex_t *__restrict m, + int ceiling, int *__restrict old) +{ + (void)m; + (void)ceiling; + (void)old; + return EINVAL; +} + +/* ---- condition variables ------------------------------------------------------ */ + +WEAK int pthread_condattr_init(pthread_condattr_t *a) +{ + a->__clock = CLOCK_REALTIME; + a->__pshared = PTHREAD_PROCESS_PRIVATE; + return 0; +} + +WEAK int pthread_condattr_destroy(pthread_condattr_t *a) { (void)a; return 0; } + +WEAK int pthread_condattr_getclock(const pthread_condattr_t *__restrict a, + clockid_t *__restrict c) +{ + *c = a->__clock; + return 0; +} + +WEAK int pthread_condattr_setclock(pthread_condattr_t *a, clockid_t c) +{ + if (bad_clock(c)) + return EINVAL; + a->__clock = c; + return 0; +} + +WEAK int pthread_condattr_getpshared(const pthread_condattr_t *__restrict a, + int *__restrict s) +{ + *s = a->__pshared; + return 0; +} + +WEAK int pthread_condattr_setpshared(pthread_condattr_t *a, int s) +{ + int e = pshared(s); + if (!e) + a->__pshared = s; + return e; +} + +WEAK int pthread_cond_init(pthread_cond_t *__restrict c, + const pthread_condattr_t *__restrict a) +{ + c->__seq = 0; + c->__clock = a ? a->__clock : CLOCK_REALTIME; + return 0; +} + +WEAK int pthread_cond_destroy(pthread_cond_t *c) { (void)c; return 0; } + +/* Bump, then wake: a waiter about to sleep finds the number moved and + * does not sleep, so a signal is never lost between its unlock and its + * sleep. */ +WEAK int pthread_cond_signal(pthread_cond_t *c) +{ + __atomic_fetch_add(&c->__seq, 1, __ATOMIC_RELEASE); + __os_futex_wake(&c->__seq, 1); + return 0; +} + +WEAK int pthread_cond_broadcast(pthread_cond_t *c) +{ + __atomic_fetch_add(&c->__seq, 1, __ATOMIC_RELEASE); + __os_futex_wake(&c->__seq, -1); + return 0; +} + +static int cond_wait(pthread_cond_t *c, pthread_mutex_t *m, + const struct timespec *abs, clockid_t clock) +{ + unsigned long me = __os_thread_self(); + if (m->__type != PTHREAD_MUTEX_NORMAL && + (!m->__count || m->__owner != me)) + return EPERM; + /* read under the mutex: a signal after this changes the number the + * sleep below compares against */ + int s = __atomic_load_n(&c->__seq, __ATOMIC_RELAXED); + unsigned count = m->__count; + m->__owner = 0; + m->__count = 0; + __word_unlock(&m->__state); + long ns = __thr_until(abs, clock); + /* with no futex there is one thread, which nothing can signal: a + * timed wait waits out its deadline rather than return at once */ + if ((!abs || ns > 0) && sleep_on(&c->__seq, s, ns) && abs) + while (__atomic_load_n(&c->__seq, __ATOMIC_RELAXED) == s && + __thr_until(abs, clock) > 0) + __os_thread_yield(); + __word_lock(&m->__state, NULL, CLOCK_REALTIME); + m->__owner = me; + m->__count = count; + /* timed out only if nothing signalled: a wake that raced the + * deadline is a wake */ + if (abs && __atomic_load_n(&c->__seq, __ATOMIC_RELAXED) == s && + __thr_until(abs, clock) == 0) + return ETIMEDOUT; + return 0; +} + +WEAK int pthread_cond_wait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m) +{ + return cond_wait(c, m, NULL, CLOCK_REALTIME); +} + +WEAK int pthread_cond_timedwait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime)) + return EINVAL; + return cond_wait(c, m, abstime, c->__clock); +} + +WEAK int pthread_cond_clockwait(pthread_cond_t *__restrict c, + pthread_mutex_t *__restrict m, clockid_t clock, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime) || bad_clock(clock)) + return EINVAL; + return cond_wait(c, m, abstime, clock); +} + +/* ---- read-write locks -------------------------------------------------------- * + * + * Readers first, as glibc's default: a reader waits only while a writer + * HOLDS the lock, so a thread may take a read lock it already holds + * without deadlocking behind a waiting writer. A stream of readers can + * keep a writer waiting; that is the price of the guarantee. */ + +WEAK int pthread_rwlockattr_init(pthread_rwlockattr_t *a) +{ + memset(a, 0, sizeof *a); + return 0; +} + +WEAK int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a) { (void)a; return 0; } + +WEAK int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *__restrict a, + int *__restrict s) +{ + *s = a->__pshared; + return 0; +} + +WEAK int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s) +{ + int e = pshared(s); + if (!e) + a->__pshared = s; + return e; +} + +WEAK int pthread_rwlock_init(pthread_rwlock_t *__restrict l, + const pthread_rwlockattr_t *__restrict a) +{ + (void)a; + memset(l, 0, sizeof *l); + return 0; +} + +WEAK int pthread_rwlock_destroy(pthread_rwlock_t *l) +{ + return l->__readers || l->__writer ? EBUSY : 0; +} + +/* sleep until the lock's state changes, with its word released */ +static int rw_wait(pthread_rwlock_t *l, const struct timespec *abs, + clockid_t clock) +{ + int s = __atomic_load_n(&l->__seq, __ATOMIC_RELAXED); + __word_unlock(&l->__lock); + long ns = __thr_until(abs, clock); + if (abs && ns == 0) { + __word_lock(&l->__lock, NULL, CLOCK_REALTIME); + return ETIMEDOUT; + } + sleep_on(&l->__seq, s, ns); + __word_lock(&l->__lock, NULL, CLOCK_REALTIME); + return 0; +} + +static int rdlock(pthread_rwlock_t *l, int try, const struct timespec *abs, + clockid_t clock) +{ + unsigned long me = __os_thread_self(); + __word_lock(&l->__lock, NULL, CLOCK_REALTIME); + if (l->__writer && l->__owner == me) { + __word_unlock(&l->__lock); + return try ? EBUSY : EDEADLK; /* a try only says it cannot */ + } + while (l->__writer) { + if (try || rw_wait(l, abs, clock)) { + __word_unlock(&l->__lock); + return try ? EBUSY : ETIMEDOUT; + } + } + if (l->__readers == UINT_MAX) { + __word_unlock(&l->__lock); + return EAGAIN; + } + l->__readers++; + __word_unlock(&l->__lock); + return 0; +} + +static int wrlock(pthread_rwlock_t *l, int try, const struct timespec *abs, + clockid_t clock) +{ + unsigned long me = __os_thread_self(); + __word_lock(&l->__lock, NULL, CLOCK_REALTIME); + if (l->__writer && l->__owner == me) { + __word_unlock(&l->__lock); + return try ? EBUSY : EDEADLK; + } + while (l->__writer || l->__readers) { + if (try || rw_wait(l, abs, clock)) { + __word_unlock(&l->__lock); + return try ? EBUSY : ETIMEDOUT; + } + } + l->__writer = 1; + l->__owner = me; + __word_unlock(&l->__lock); + return 0; +} + +WEAK int pthread_rwlock_rdlock(pthread_rwlock_t *l) { return rdlock(l, 0, NULL, 0); } +WEAK int pthread_rwlock_tryrdlock(pthread_rwlock_t *l) { return rdlock(l, 1, NULL, 0); } +WEAK int pthread_rwlock_wrlock(pthread_rwlock_t *l) { return wrlock(l, 0, NULL, 0); } +WEAK int pthread_rwlock_trywrlock(pthread_rwlock_t *l) { return wrlock(l, 1, NULL, 0); } + +WEAK int pthread_rwlock_timedrdlock(pthread_rwlock_t *__restrict l, + const struct timespec *__restrict abstime) +{ + return bad_time(abstime) ? EINVAL : rdlock(l, 0, abstime, CLOCK_REALTIME); +} + +WEAK int pthread_rwlock_timedwrlock(pthread_rwlock_t *__restrict l, + const struct timespec *__restrict abstime) +{ + return bad_time(abstime) ? EINVAL : wrlock(l, 0, abstime, CLOCK_REALTIME); +} + +WEAK int pthread_rwlock_clockrdlock(pthread_rwlock_t *__restrict l, + clockid_t clock, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime) || bad_clock(clock)) + return EINVAL; + return rdlock(l, 0, abstime, clock); +} + +WEAK int pthread_rwlock_clockwrlock(pthread_rwlock_t *__restrict l, + clockid_t clock, + const struct timespec *__restrict abstime) +{ + if (bad_time(abstime) || bad_clock(clock)) + return EINVAL; + return wrlock(l, 0, abstime, clock); +} + +WEAK int pthread_rwlock_unlock(pthread_rwlock_t *l) +{ + __word_lock(&l->__lock, NULL, CLOCK_REALTIME); + if (l->__writer) { + if (l->__owner != __os_thread_self()) { + __word_unlock(&l->__lock); + return EPERM; + } + l->__writer = 0; + l->__owner = 0; + } else if (l->__readers) { + l->__readers--; + } else { + __word_unlock(&l->__lock); + return EPERM; + } + int wake = !l->__writer && !l->__readers; + if (wake) + __atomic_fetch_add(&l->__seq, 1, __ATOMIC_RELEASE); + __word_unlock(&l->__lock); + if (wake) + __os_futex_wake(&l->__seq, -1); + return 0; +} + +/* ---- barriers ----------------------------------------------------------------- */ + +WEAK int pthread_barrierattr_init(pthread_barrierattr_t *a) +{ + a->__pshared = PTHREAD_PROCESS_PRIVATE; + return 0; +} + +WEAK int pthread_barrierattr_destroy(pthread_barrierattr_t *a) { (void)a; return 0; } + +WEAK int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict a, + int *__restrict s) +{ + *s = a->__pshared; + return 0; +} + +WEAK int pthread_barrierattr_setpshared(pthread_barrierattr_t *a, int s) +{ + int e = pshared(s); + if (!e) + a->__pshared = s; + return e; +} + +WEAK int pthread_barrier_init(pthread_barrier_t *__restrict b, + const pthread_barrierattr_t *__restrict a, + unsigned count) +{ + (void)a; + if (count == 0) + return EINVAL; + memset(b, 0, sizeof *b); + b->__count = count; + return 0; +} + +WEAK int pthread_barrier_destroy(pthread_barrier_t *b) +{ + return b->__waiting ? EBUSY : 0; +} + +/* The last to arrive opens the barrier for this generation and is told + * so; the generation number is what the others wait on, so the barrier + * can be reused at once without a late waker seeing the next round. */ +WEAK int pthread_barrier_wait(pthread_barrier_t *b) +{ + __word_lock(&b->__lock, NULL, CLOCK_REALTIME); + if (++b->__waiting == b->__count) { + b->__waiting = 0; + __atomic_fetch_add(&b->__seq, 1, __ATOMIC_RELEASE); + __word_unlock(&b->__lock); + __os_futex_wake(&b->__seq, -1); + return PTHREAD_BARRIER_SERIAL_THREAD; + } + int s = __atomic_load_n(&b->__seq, __ATOMIC_RELAXED); + __word_unlock(&b->__lock); + while (__atomic_load_n(&b->__seq, __ATOMIC_ACQUIRE) == s) + sleep_on(&b->__seq, s, -1); + return 0; +} + +/* ---- spin locks ---------------------------------------------------------------- */ + +WEAK int pthread_spin_init(pthread_spinlock_t *l, int ps) +{ + int e = pshared(ps); + if (!e) + *l = 0; + return e; +} + +WEAK int pthread_spin_destroy(pthread_spinlock_t *l) { (void)l; return 0; } + +WEAK int pthread_spin_lock(pthread_spinlock_t *l) +{ + while (__atomic_exchange_n(l, 1, __ATOMIC_ACQUIRE)) + while (__atomic_load_n(l, __ATOMIC_RELAXED)) + __os_thread_yield(); + return 0; +} + +WEAK int pthread_spin_trylock(pthread_spinlock_t *l) +{ + return __atomic_exchange_n(l, 1, __ATOMIC_ACQUIRE) ? EBUSY : 0; +} + +WEAK int pthread_spin_unlock(pthread_spinlock_t *l) +{ + __atomic_store_n(l, 0, __ATOMIC_RELEASE); + return 0; +} diff --git a/lib/libc/src/threads/thr.h b/lib/libc/src/threads/thr.h new file mode 100644 index 000000000..dd066d316 --- /dev/null +++ b/lib/libc/src/threads/thr.h @@ -0,0 +1,55 @@ +/* What and share: one table of the threads this + * library started, thread-specific data, and the futex word every lock + * here is made of. C11's thrd_* are pthread_* with other spellings, so + * there is one implementation of each idea, in pthread.c. */ +#ifndef EMBLIBC_THR_H +#define EMBLIBC_THR_H + +#include +#include + +#define __THR_KEYS 128 /* PTHREAD_KEYS_MAX */ +#define __THR_DTOR_ROUNDS 4 /* PTHREAD_DESTRUCTOR_ITERATIONS */ + +struct __thr { + struct __thr *next; + unsigned long id; + void *(*fn)(void *); + void *arg; + void *result; + int detached; /* 1 detached, 2 being joined */ + int done, ours; /* ours: started here, so exit can unwind */ + int registered; /* pthread_create has returned */ + int cancel_state, cancel_type; + jmp_buf exit_to; + void *tsd[__THR_KEYS]; + char name[16]; +}; + +#ifndef WEAK +#define WEAK __attribute__((weak)) +#endif + +/* pthread_create with the reason kept: ENOMEM apart from EAGAIN */ +int __thr_create(unsigned long *out, const void *attr, + void *(*fn)(void *), void *arg); + +/* fork's three moments (proc.c calls it through __atfork_hook, which + * the thread library sets as soon as it is used) */ +void __pthread_atfork_run(int phase); +extern void (*__atfork_hook)(int phase); + +/* this thread's record, made on first need for one this library did not + * start (the program's first thread, a C++ std::thread) */ +struct __thr *__thr_self(void); + +/* the futex word: 0 free, 1 held, 2 held and maybe waited on. abs is a + * deadline on `clock`, NULL for none; 0, or ETIMEDOUT */ +int __word_lock(volatile int *w, const struct timespec *abs, clockid_t clock); +int __word_trylock(volatile int *w); +void __word_unlock(volatile int *w); + +/* nanoseconds from now until abs on clock, at least 0; -1 for no abs */ +long __thr_until(const struct timespec *abs, clockid_t clock); + +#endif diff --git a/lib/libc/src/threads/threads.c b/lib/libc/src/threads/threads.c index a8f40c0ea..5fe090e24 100644 --- a/lib/libc/src/threads/threads.c +++ b/lib/libc/src/threads/threads.c @@ -1,83 +1,51 @@ -/* C11 threads, over the OS seam. +/* C11 threads: POSIX's, under C's names (pthread.c holds the one + * implementation -- the thread table, thread-specific data, the futex + * word), and the same seam the C++ and use, so a target + * that gains threads lights up all three at once. * - * The same primitives the C++ and use, so a target - * that gains threads lights up both at once and neither library knows - * which OS it is on. See lib/libc/os/backend.h. - * - * The locks here are the three-state futex mutex, for the reason given - * at length in lib/libcxx/include/mutex: the uncontended path is one - * compare-exchange and never enters the kernel, and two states are not - * enough because unlock has to know whether anybody is waiting. - */ + * C11's timed waits take a DEADLINE on TIME_UTC, not a duration -- the + * reading this file once got wrong, which turned any real deadline into + * a wait of decades. */ +#define _GNU_SOURCE 1 #include +#include #include +#include #include #include #include "../../os/backend.h" +#include "thr.h" -/* ---- threads ----------------------------------------------------------- */ - -/* A C thread's function returns an int and the seam's takes none, so the - * value is held here until somebody joins. One record per live thread, - * in a small fixed table: a target with no threads never uses it, and - * one with threads has a bounded number of C11 threads by construction - * -- there is no way to create one without going through here. */ -#define MAXTHREADS 64 +/* ---- threads ------------------------------------------------------------- */ -struct rec { - thrd_t id; - thrd_start_t fn; - void *arg; - int result; - int done; - int used; -}; +/* A C thread's function returns an int and a POSIX one a pointer: the + * int travels in the pointer. */ +struct c11 { thrd_start_t fn; void *arg; }; -static struct rec g_recs[MAXTHREADS]; - -static struct rec *rec_of(thrd_t id) +static void *c11_start(void *p) { - for (int i = 0; i < MAXTHREADS; i++) - if (g_recs[i].used && g_recs[i].id == id) - return &g_recs[i]; - return NULL; -} - -static void trampoline(void *p) -{ - struct rec *r = (struct rec *)p; - r->result = r->fn(r->arg); - r->done = 1; + struct c11 c = *(struct c11 *)p; + free(p); + return (void *)(intptr_t)c.fn(c.arg); } int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { if (!thr || !func) return thrd_error; - struct rec *r = NULL; - for (int i = 0; i < MAXTHREADS; i++) - if (!g_recs[i].used) { - r = &g_recs[i]; - break; - } - if (!r) + struct c11 *c = malloc(sizeof *c); + if (!c) return thrd_nomem; - r->fn = func; - r->arg = arg; - r->result = 0; - r->done = 0; - r->used = 1; - unsigned long id = 0; - if (__os_thread_create(&id, trampoline, r) != 0) { - r->used = 0; - /* thrd_error, not thrd_nomem: there is nothing to run out of on - * a target with no threads, and a caller told nomem would - * sensibly retry. */ - return errno == ENOMEM ? thrd_nomem : thrd_error; + c->fn = func; + c->arg = arg; + int e = __thr_create(thr, NULL, c11_start, c); + if (e) { + free(c); + /* thrd_error, not thrd_nomem, where there are no threads: a + * caller told nomem would sensibly retry */ + return e == ENOMEM ? thrd_nomem : thrd_error; } - r->id = id; - *thr = id; return thrd_success; } @@ -108,38 +76,25 @@ int thrd_sleep(const struct timespec *duration, struct timespec *remaining) return 0; } +/* The program's first thread ending this way ends the program when the + * last thread does, with EXIT_SUCCESS, as C11 7.26.5.5 says. */ _Noreturn void thrd_exit(int res) { - struct rec *r = rec_of((thrd_t)__os_thread_self()); - if (r) { - r->result = res; - r->done = 1; - } - /* With no threads the only thread is the program, so ending it is - * ending the program -- which is what the standard says for the - * main thread anyway. */ - exit(res); + pthread_exit((void *)(intptr_t)res); } int thrd_detach(thrd_t thr) { - struct rec *r = rec_of(thr); - if (__os_thread_detach(thr) != 0) - return thrd_error; - if (r) - r->used = 0; /* nobody will join it: release the slot */ - return thrd_success; + return pthread_detach(thr) ? thrd_error : thrd_success; } int thrd_join(thrd_t thr, int *res) { - if (__os_thread_join(thr) != 0) + void *v; + if (pthread_join(thr, &v)) return thrd_error; - struct rec *r = rec_of(thr); if (res) - *res = r ? r->result : 0; - if (r) - r->used = 0; + *res = (int)(intptr_t)v; return thrd_success; } @@ -158,31 +113,7 @@ int mtx_init(mtx_t *mtx, int type) void mtx_destroy(mtx_t *mtx) { (void)mtx; } -static int mtx_try_raw(mtx_t *m) -{ - int expected = 0; - return __atomic_compare_exchange_n(&m->__state, &expected, 1, 0, - __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); -} - -int mtx_trylock(mtx_t *mtx) -{ - if (!mtx) - return thrd_error; - unsigned long me = __os_thread_self(); - if ((mtx->__type & mtx_recursive) && mtx->__count && - mtx->__owner == me) { - mtx->__count++; - return thrd_success; - } - if (!mtx_try_raw(mtx)) - return thrd_busy; - mtx->__owner = me; - mtx->__count = 1; - return thrd_success; -} - -int mtx_lock(mtx_t *mtx) +static int mtx_take(mtx_t *mtx, int try, const struct timespec *abs) { if (!mtx) return thrd_error; @@ -192,38 +123,25 @@ int mtx_lock(mtx_t *mtx) mtx->__count++; return thrd_success; } - for (;;) { - if (mtx_try_raw(mtx)) - break; - /* Announce the wait BEFORE sleeping, or an unlock in between - * sees state 1, skips the wake, and leaves this thread asleep - * forever. */ - int two = 2; - __atomic_exchange_n(&mtx->__state, two, __ATOMIC_ACQUIRE); - if (__atomic_load_n(&mtx->__state, __ATOMIC_RELAXED) == 2) - if (__os_futex_wait(&mtx->__state, 2, -1) < 0) - __os_thread_yield(); + if (try) { + if (!__word_trylock(&mtx->__state)) + return thrd_busy; + } else if (__word_lock(&mtx->__state, abs, CLOCK_REALTIME)) { + return thrd_timedout; } mtx->__owner = me; mtx->__count = 1; return thrd_success; } +int mtx_lock(mtx_t *mtx) { return mtx_take(mtx, 0, NULL); } +int mtx_trylock(mtx_t *mtx) { return mtx_take(mtx, 1, NULL); } + int mtx_timedlock(mtx_t *__restrict mtx, const struct timespec *__restrict ts) { - if (!mtx) + if (!ts || ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000L) return thrd_error; - if (mtx_trylock(mtx) == thrd_success) - return thrd_success; - long ns = ts ? ts->tv_sec * 1000000000L + ts->tv_nsec : 0; - /* A bounded wait must END. With no futex there is no second thread - * that could release it, so one more attempt is the whole of the - * wait -- see lib/libcxx/include/mutex on why polling a deadline is - * worse than useless when the clock may not advance either. */ - if (__os_futex_wait(&mtx->__state, mtx->__state, ns) < 0) - return mtx_trylock(mtx) == thrd_success ? thrd_success - : thrd_timedout; - return mtx_trylock(mtx) == thrd_success ? thrd_success : thrd_timedout; + return mtx_take(mtx, 0, ts); } int mtx_unlock(mtx_t *mtx) @@ -236,8 +154,7 @@ int mtx_unlock(mtx_t *mtx) } mtx->__owner = 0; mtx->__count = 0; - if (__atomic_exchange_n(&mtx->__state, 0, __ATOMIC_RELEASE) == 2) - __os_futex_wake(&mtx->__state, 1); + __word_unlock(&mtx->__state); return thrd_success; } @@ -257,9 +174,6 @@ int cnd_signal(cnd_t *cond) { if (!cond) return thrd_error; - /* Bump first, wake second: a waiter about to sleep finds the - * number changed and does not sleep, so an early notify is not - * lost. */ __atomic_fetch_add(&cond->__seq, 1, __ATOMIC_RELEASE); __os_futex_wake(&cond->__seq, 1); return thrd_success; @@ -274,78 +188,76 @@ int cnd_broadcast(cnd_t *cond) return thrd_success; } -int cnd_wait(cnd_t *cond, mtx_t *mtx) +static int cnd_wait_until(cnd_t *cond, mtx_t *mtx, const struct timespec *abs) { if (!cond || !mtx) return thrd_error; - /* The sequence is read while the mutex is HELD, so a signal cannot - * slip between this read and the sleep without changing the number - * the sleep tests. That indivisibility is the whole reason a - * condition variable is a primitive. */ + /* read with the mutex held: a signal after this moves the number the + * sleep compares, so it cannot fall between unlock and sleep */ int s = __atomic_load_n(&cond->__seq, __ATOMIC_RELAXED); - mtx_unlock(mtx); - if (__os_futex_wait(&cond->__seq, s, -1) < 0) - __os_thread_yield(); - mtx_lock(mtx); + unsigned count = mtx->__count; + unsigned long owner = mtx->__owner; + mtx->__owner = 0; + mtx->__count = 0; + __word_unlock(&mtx->__state); + long ns = __thr_until(abs, CLOCK_REALTIME); + if (!abs || ns > 0) { + int saved = errno; + if (__os_futex_wait(&cond->__seq, s, ns) < 0 && errno == ENOSYS) { + /* one thread, nothing to signal it: a deadline is waited out */ + __os_thread_yield(); + while (abs && __atomic_load_n(&cond->__seq, __ATOMIC_RELAXED) == s && + __thr_until(abs, CLOCK_REALTIME) > 0) + __os_thread_yield(); + } + errno = saved; + } + __word_lock(&mtx->__state, NULL, CLOCK_REALTIME); + mtx->__owner = owner; + mtx->__count = count; + if (abs && __atomic_load_n(&cond->__seq, __ATOMIC_RELAXED) == s && + __thr_until(abs, CLOCK_REALTIME) == 0) + return thrd_timedout; return thrd_success; } +int cnd_wait(cnd_t *cond, mtx_t *mtx) { return cnd_wait_until(cond, mtx, NULL); } + int cnd_timedwait(cnd_t *__restrict cond, mtx_t *__restrict mtx, const struct timespec *__restrict ts) { - if (!cond || !mtx) + if (!ts || ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000L) return thrd_error; - long ns = ts ? ts->tv_sec * 1000000000L + ts->tv_nsec : 0; - int s = __atomic_load_n(&cond->__seq, __ATOMIC_RELAXED); - mtx_unlock(mtx); - int r = __os_futex_wait(&cond->__seq, s, ns); - mtx_lock(mtx); - /* Timed out only if the sequence really did not move: a wakeup that - * raced with the timeout is a wakeup. */ - if (r < 0 && __atomic_load_n(&cond->__seq, __ATOMIC_RELAXED) == s) - return thrd_timedout; - return thrd_success; + return cnd_wait_until(cond, mtx, ts); } -/* ---- call_once ----------------------------------------------------------- */ +/* ---- call_once ------------------------------------------------------------ */ void call_once(once_flag *flag, void (*func)(void)) { - if (!flag || !func) - return; - /* Acquire on the fast path: a thread that sees "done" must also see - * everything the initializing thread wrote. */ - if (__atomic_load_n(&flag->__state, __ATOMIC_ACQUIRE) == 2) - return; - for (;;) { - int e = 0; - if (__atomic_compare_exchange_n(&flag->__state, &e, 1, 0, - __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { - func(); - __atomic_store_n(&flag->__state, 2, __ATOMIC_RELEASE); - __os_futex_wake(&flag->__state, -1); - return; - } - if (e == 2) - return; - if (__os_futex_wait(&flag->__state, 1, -1) < 0) - __os_thread_yield(); - } + if (flag && func) + pthread_once(&flag->__state, func); } -/* ---- thread-specific storage --------------------------------------------- */ +/* ---- thread-specific storage ------------------------------------------- */ -/* Needs thread-local storage underneath, which this compiler does not - * have. Reporting failure is the only honest answer: a key that every - * thread shared would be a global under another name, and the failure - * would not appear until two threads corrupted each other. */ +/* POSIX's keys: a table per thread in its record, so no thread-local + * storage is needed underneath -- one key works on every target. */ int tss_create(tss_t *key, tss_dtor_t dtor) { - (void)key; - (void)dtor; - return thrd_error; + pthread_key_t k; + if (!key || pthread_key_create(&k, dtor)) + return thrd_error; + *key = (tss_t)k; + return thrd_success; +} + +void *tss_get(tss_t key) { return pthread_getspecific((pthread_key_t)key); } + +int tss_set(tss_t key, void *val) +{ + return pthread_setspecific((pthread_key_t)key, val) ? thrd_error + : thrd_success; } -void *tss_get(tss_t key) { (void)key; return NULL; } -int tss_set(tss_t key, void *val) { (void)key; (void)val; return thrd_error; } -void tss_delete(tss_t key) { (void)key; } +void tss_delete(tss_t key) { pthread_key_delete((pthread_key_t)key); } diff --git a/lib/libc/src/time/time.c b/lib/libc/src/time/time.c index 93687bd9a..173476a1c 100644 --- a/lib/libc/src/time/time.c +++ b/lib/libc/src/time/time.c @@ -13,6 +13,7 @@ * of time_t, branch-free, and needs no table. */ #include +#include #include #include @@ -88,6 +89,8 @@ struct tm *gmtime_r(const time_t *restrict t, struct tm *restrict out) out->tm_mday = (int)d; out->tm_yday = (int)(days - days_from_civil(y, 1, 1)); out->tm_isdst = 0; + out->tm_gmtoff = 0; + out->tm_zone = "GMT"; /* glibc's gmtime's name, which %Z prints */ return out; } @@ -112,20 +115,11 @@ time_t timegm(struct tm *tm) return out; } -time_t mktime(struct tm *tm) { return timegm(tm); } - -/* No timezone database, so local time is UTC. That is a real limitation - * and it is stated in the header rather than hidden behind a TZ variable - * this library would silently ignore. */ -struct tm *localtime_r(const time_t *restrict t, struct tm *restrict out) -{ - return gmtime_r(t, out); -} +/* mktime, localtime and localtime_r are tz.c's: local time is the TZ's. */ static struct tm tm_static; struct tm *gmtime(const time_t *t) { return gmtime_r(t, &tm_static); } -struct tm *localtime(const time_t *t) { return gmtime_r(t, &tm_static); } double difftime(time_t end, time_t start) { return (double)(end - start); } @@ -145,10 +139,22 @@ clock_t clock(void) return (clock_t)(ns / (1000000000L / CLOCKS_PER_SEC)); } +/* TIME_UTC is the backend's real-time clock, to the nanosecond where it + * has one -- a deadline for cnd_timedwait is "now plus a little", and a + * "now" rounded down to the second is a deadline already past -- and + * whole seconds from __os_time where it has only those. */ int timespec_get(struct timespec *ts, int base) { if (base != TIME_UTC) return 0; + int saved = errno; + struct __os_timespec t; + if (__os_clock_gettime(0, &t) == 0) { + ts->tv_sec = (time_t)t.sec; + ts->tv_nsec = t.nsec; + return base; + } + errno = saved; long now = __os_time(); if (now < 0) return 0; @@ -157,11 +163,21 @@ int timespec_get(struct timespec *ts, int base) return base; } -/* timespec_get counts whole seconds -- __os_time's -- so that is its - * resolution; where there is no clock there is no TIME_UTC either. */ int timespec_getres(struct timespec *ts, int base) { - if (base != TIME_UTC || __os_time() < 0) + if (base != TIME_UTC) + return 0; + int saved = errno; + struct __os_timespec t; + if (__os_clock_getres(0, &t) == 0) { + if (ts) { + ts->tv_sec = (time_t)t.sec; + ts->tv_nsec = t.nsec; + } + return base; + } + errno = saved; + if (__os_time() < 0) return 0; if (ts) { ts->tv_sec = 1; @@ -338,8 +354,14 @@ size_t strftime(char *restrict s, size_t max, const char *restrict fmt, case 'X': sb_fmt(&b, "%H:%M:%S", tm); break; case 'y': sb_num(&b, (tm->tm_year + 1900) % 100, 2, '0'); break; case 'Y': sb_num(&b, tm->tm_year + 1900, 0, '0'); break; - case 'z': sb_str(&b, "+0000"); break; /* UTC, per localtime */ - case 'Z': sb_str(&b, "UTC"); break; + case 'z': { /* +hhmm east of UTC */ + long off = tm->tm_gmtoff, a = off < 0 ? -off : off; + sb_put(&b, off < 0 ? "-" : "+", 1); + sb_num(&b, a / 3600, 2, '0'); + sb_num(&b, a / 60 % 60, 2, '0'); + break; + } + case 'Z': sb_str(&b, tm->tm_zone ? tm->tm_zone : "UTC"); break; case '%': sb_put(&b, "%", 1); break; case 0: f--; break; /* trailing '%': stop */ default: sb_put(&b, "%", 1); sb_put(&b, f, 1); break; diff --git a/lib/libc/src/time/tz.c b/lib/libc/src/time/tz.c new file mode 100644 index 000000000..0cb9fd581 --- /dev/null +++ b/lib/libc/src/time/tz.c @@ -0,0 +1,930 @@ +/* Local time: the TZ variable, tzset, localtime and mktime. + * + * TZ is read as POSIX writes it -- "CET-1CEST,M3.5.0,M10.5.0/3": a name, + * an offset WEST of UTC, and optionally a summer name, its offset, and + * the two rules for when it starts and ends (Mm.w.d, Jn or n, each with an + * optional time, which may be negative or past 24 hours as POSIX.1-2024 + * allows). A name in angle brackets may hold digits and signs ("<+0530>"). + * + * A TZ that is a ZONE's name ("Europe/Paris", ":Europe/Paris") is read + * from the compiled time zone file of that name under TZDIR or + * /usr/share/zoneinfo -- TZif, versions 1 to 3, with its footer's POSIX + * rule for times after its last transition. No TZ at all means + * /etc/localtime where there is one, and UTC where there is not. + * + * A TZ that is neither -- unparseable, or naming a file that is not there + * -- is UTC, with the abbreviation "UTC". That is not an error: a program + * started with a TZ it cannot use must still tell the time. + */ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include + +#include "../internal/lock.h" + +char *tzname[2] = { (char *)"UTC", (char *)"UTC" }; +long timezone; +int daylight; + +/* ---- the calendar this needs ------------------------------------------------ */ + +static long days_from_civil(long y, unsigned m, unsigned d) +{ + y -= m <= 2; + long era = (y >= 0 ? y : y - 399) / 400; + unsigned yoe = (unsigned)(y - era * 400); + unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + (long)doe - 719468; +} + +static int is_leap(long y) +{ + return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; +} + +static const unsigned char mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, + 31, 30, 31 }; + +/* ---- a POSIX TZ rule --------------------------------------------------------- */ + +struct rule { + char kind; /* 'J' (1..365, no Feb 29), 'D' (0..365), 'M' */ + int m, w, d, n; + long time; /* seconds after local midnight */ +}; + +struct zone { + char std[16], dst[16]; + long std_east, dst_east; /* seconds east of UTC */ + int has_dst; + struct rule start, end; + /* from a TZif file: transitions, and the types they switch to */ + long long *when; + unsigned char *which; + int ntrans; + struct { long east; int isdst; char abbr[16]; } type[32]; + int ntypes; + int has_rule; /* a POSIX rule after the last transition */ +}; + +static struct zone g; +static char g_tzkey[256]; +static int g_set; +static __lock_t g_lock = LOCK_INIT; + +static const char *parse_name(const char *s, char *out, size_t cap) +{ + size_t n = 0; + if (*s == '<') { + s++; + while (*s && *s != '>') { + if (n + 1 < cap) + out[n++] = *s; + s++; + } + if (*s != '>') + return NULL; + s++; + } else + while (isalpha((unsigned char)*s)) { + if (n + 1 < cap) + out[n++] = *s; + s++; + } + out[n] = 0; + return n >= 3 ? s : NULL; +} + +/* [+-]hh[:mm[:ss]], hours up to 167 as POSIX.1-2024 allows for rule times */ +static const char *parse_hms(const char *s, long *out) +{ + int neg = 0; + if (*s == '+' || *s == '-') + neg = *s++ == '-'; + if (!isdigit((unsigned char)*s)) + return NULL; + long h = 0, m = 0, sec = 0; + while (isdigit((unsigned char)*s)) + h = h * 10 + (*s++ - '0'); + if (*s == ':') { + s++; + while (isdigit((unsigned char)*s)) + m = m * 10 + (*s++ - '0'); + if (*s == ':') { + s++; + while (isdigit((unsigned char)*s)) + sec = sec * 10 + (*s++ - '0'); + } + } + if (h > 167 || m > 59 || sec > 59) + return NULL; + long v = h * 3600 + m * 60 + sec; + *out = neg ? -v : v; + return s; +} + +static const char *parse_rule(const char *s, struct rule *r) +{ + r->time = 7200; /* 02:00, POSIX's default */ + if (*s == 'M') { + r->kind = 'M'; + char *e; + r->m = (int)strtol(s + 1, &e, 10); + if (*e != '.') + return NULL; + r->w = (int)strtol(e + 1, &e, 10); + if (*e != '.') + return NULL; + r->d = (int)strtol(e + 1, &e, 10); + if (r->m < 1 || r->m > 12 || r->w < 1 || r->w > 5 || r->d < 0 || + r->d > 6) + return NULL; + s = e; + } else { + r->kind = 'D'; + if (*s == 'J') { + r->kind = 'J'; + s++; + } + if (!isdigit((unsigned char)*s)) + return NULL; + char *e; + r->n = (int)strtol(s, &e, 10); + if (r->kind == 'J' ? r->n < 1 || r->n > 365 : r->n > 365) + return NULL; + s = e; + } + if (*s == '/' && !(s = parse_hms(s + 1, &r->time))) + return NULL; + return s; +} + +/* the TZ string into z; 0, or -1 when it is not one */ +static int parse_posix(const char *s, struct zone *z) +{ + long off; + if (!(s = parse_name(s, z->std, sizeof z->std)) || + !(s = parse_hms(s, &off))) + return -1; + z->std_east = -off; + z->dst_east = z->std_east; + z->has_dst = 0; + strcpy(z->dst, z->std); + if (!*s) + return 0; + if (!(s = parse_name(s, z->dst, sizeof z->dst))) + return -1; + z->has_dst = 1; + z->dst_east = z->std_east + 3600; + if (*s && *s != ',') { + if (!(s = parse_hms(s, &off))) + return -1; + z->dst_east = -off; + } + if (!*s) { + /* no rules: the United States' since 2007, which is what glibc's + * posixrules file has said for as long as there has been one */ + z->start = (struct rule){ 'M', 3, 2, 0, 0, 7200 }; + z->end = (struct rule){ 'M', 11, 1, 0, 0, 7200 }; + return 0; + } + if (*s != ',' || !(s = parse_rule(s + 1, &z->start)) || *s != ',' || + !(s = parse_rule(s + 1, &z->end)) || *s) + return -1; + return 0; +} + +/* the day of year y (0-based) a rule names */ +static long rule_day(const struct rule *r, long y) +{ + if (r->kind == 'J') + return r->n - 1 + (is_leap(y) && r->n >= 60); + if (r->kind == 'D') + return r->n; + long first = days_from_civil(y, (unsigned)r->m, 1); + long yday0 = first - days_from_civil(y, 1, 1); + int wday = (int)(((first % 7) + 11) % 7); /* 1970-01-01 a Thursday */ + long day = (r->d - wday + 7) % 7 + (long)(r->w - 1) * 7; + int len = mdays[r->m - 1] + (r->m == 2 && is_leap(y)); + while (day >= len) + day -= 7; + return yday0 + day; +} + +/* the year (proleptic Gregorian) holding day `days` since 1970-01-01 */ +static long year_of(long long days) +{ + long long z = days + 719468; + long long era = (z >= 0 ? z : z - 146096) / 146097; + long long doe = z - era * 146097; + long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + long long mp = (5 * doy + 2) / 153; + return (long)(yoe + era * 400 + (mp >= 10)); +} + +/* Whether t (UTC) is in summer time, by the zone's rules: in the year that + * holds t in standard time, summer starts at the start rule's time in + * standard time and ends at the end rule's time in summer time. A zone + * whose start comes after its end in the year (the southern hemisphere's) + * is in summer outside that span rather than inside it. */ +static int rule_isdst(const struct zone *z, long long t) +{ + if (!z->has_dst) + return 0; + long long ls = t + z->std_east; + long y = year_of(ls >= 0 ? ls / 86400 : (ls - 86399) / 86400); + long long base = (long long)days_from_civil(y, 1, 1) * 86400; + long long st = base + rule_day(&z->start, y) * 86400LL + z->start.time - + z->std_east; + long long en = base + rule_day(&z->end, y) * 86400LL + z->end.time - + z->dst_east; + if (st < en) + return t >= st && t < en; + return !(t >= en && t < st); +} + +/* ---- the compiled zone files (TZif) ---------------------------------------- */ + +static long long be(const unsigned char *p, int n) +{ + long long v = 0; + for (int i = 0; i < n; i++) + v = v << 8 | p[i]; + if (n == 4) + v = (long long)(int)(unsigned)v; + return v; +} + +/* The whole file, in a buffer that grows: a zone's is a few kilobytes, + * the largest under 100, and 256 KiB is the most this will hold -- less + * where size_t is 16 bits, as on AVR. */ +static unsigned char *slurp(FILE *f, size_t *n) +{ + size_t cap = 4096, len = 0; + unsigned char *buf = malloc(cap); + while (buf) { + len += fread(buf + len, 1, cap - len, f); + if (len < cap) + break; + if (cap >= 262144UL || cap > (size_t)-1 / 4) { + free(buf); + return NULL; + } + unsigned char *b = realloc(buf, cap * 2); + if (!b) + free(buf); + buf = b; + cap *= 2; + } + *n = len; + return buf; +} + +static int load_tzif(const char *path, struct zone *z) +{ + FILE *f = fopen(path, "rb"); + if (!f) + return -1; + size_t n = 0; + unsigned char *buf = slurp(f, &n); + fclose(f); + if (!buf || n < 44 || memcmp(buf, "TZif", 4)) { + free(buf); + return -1; + } + const unsigned char *p = buf; + int version = buf[4]; + int sz = 4; + for (int pass = 0; pass < 2; pass++) { + long isut = (long)be(p + 20, 4), isstd = (long)be(p + 24, 4), + leap = (long)be(p + 28, 4), timecnt = (long)be(p + 32, 4), + typecnt = (long)be(p + 36, 4), charcnt = (long)be(p + 40, 4); + const unsigned char *d = p + 44; + size_t body = (size_t)(timecnt * sz + timecnt + typecnt * 6 + charcnt + + leap * (sz + 4) + isstd + isut); + if ((size_t)(d - buf) + body > n || typecnt < 1 || typecnt > 32) { + free(buf); + return -1; + } + if (pass == 0 && version >= '2') { + p = d + body; /* the 64-bit block follows */ + sz = 8; + continue; + } + free(z->when); + free(z->which); + z->when = malloc((size_t)(timecnt ? timecnt : 1) * sizeof *z->when); + z->which = malloc((size_t)(timecnt ? timecnt : 1)); + if (!z->when || !z->which) { + free(buf); + return -1; + } + for (long i = 0; i < timecnt; i++) + z->when[i] = be(d + i * sz, sz); + d += timecnt * sz; + memcpy(z->which, d, (size_t)timecnt); + d += timecnt; + const unsigned char *types = d, *chars = d + typecnt * 6; + z->ntrans = (int)timecnt; + z->ntypes = (int)typecnt; + for (long i = 0; i < typecnt; i++) { + z->type[i].east = (long)be(types + i * 6, 4); + z->type[i].isdst = types[i * 6 + 4]; + int idx = types[i * 6 + 5]; + snprintf(z->type[i].abbr, sizeof z->type[i].abbr, "%s", + idx < charcnt ? (const char *)chars + idx : ""); + } + for (int i = 0; i < z->ntrans; i++) + if (z->which[i] >= z->ntypes) + z->which[i] = 0; + /* the footer: a POSIX TZ for the times after the last transition */ + z->has_rule = 0; + const unsigned char *foot = d + typecnt * 6 + charcnt + + leap * (sz + 4) + isstd + isut; + if (sz == 8 && foot < buf + n && *foot == '\n') { + char rule[128]; + size_t k = 0; + for (const unsigned char *q = foot + 1; + q < buf + n && *q != '\n' && k + 1 < sizeof rule; q++) + rule[k++] = (char)*q; + rule[k] = 0; + struct zone r; + memset(&r, 0, sizeof r); + if (k && parse_posix(rule, &r) == 0) { + memcpy(z->std, r.std, sizeof z->std); + memcpy(z->dst, r.dst, sizeof z->dst); + z->std_east = r.std_east; + z->dst_east = r.dst_east; + z->has_dst = r.has_dst; + z->start = r.start; + z->end = r.end; + z->has_rule = 1; + } + } + if (!z->has_rule) { + /* the names of the last standard and summer types */ + for (int i = 0; i < z->ntypes; i++) { + char *dst = z->type[i].isdst ? z->dst : z->std; + snprintf(dst, sizeof z->std, "%s", z->type[i].abbr); + if (z->type[i].isdst) + z->has_dst = 1, z->dst_east = z->type[i].east; + else + z->std_east = z->type[i].east; + } + } + break; + } + free(buf); + return 0; +} + +/* ---- tzset ---------------------------------------------------------------------- */ + +static void set_utc(struct zone *z) +{ + strcpy(z->std, "UTC"); + strcpy(z->dst, "UTC"); + z->std_east = z->dst_east = 0; + z->has_dst = 0; + z->ntrans = 0; + z->has_rule = 0; +} + +static void tzset_locked(void) +{ + const char *tz = getenv("TZ"); + const char *key = tz ? tz : "\x01localtime"; + if (g_set && !strncmp(key, g_tzkey, sizeof g_tzkey - 1)) + return; + snprintf(g_tzkey, sizeof g_tzkey, "%s", key); + g_set = 1; + set_utc(&g); + if (!tz) { + load_tzif("/etc/localtime", &g); + } else { + const char *name = *tz == ':' ? tz + 1 : tz; + int posix = *tz != ':' && parse_posix(tz, &g) == 0; + if (!posix && *name && !strstr(name, "..")) { + char path[512]; + const char *dir = getenv("TZDIR"); + if (*name == '/') + snprintf(path, sizeof path, "%s", name); + else + snprintf(path, sizeof path, "%s/%s", + dir && *dir ? dir : "/usr/share/zoneinfo", name); + if (load_tzif(path, &g) < 0) + set_utc(&g); + } else if (!posix) + set_utc(&g); + } + tzname[0] = g.std; + tzname[1] = g.dst; + timezone = -g.std_east; + daylight = g.has_dst; +} + +void tzset(void) +{ + __lock(&g_lock); + tzset_locked(); + __unlock(&g_lock); +} + +/* ---- the offset at a time ---------------------------------------------------------- */ + +static void offset_at(long long t, long *east, int *isdst, const char **abbr) +{ + if (g.ntrans && (!g.has_rule || t < g.when[g.ntrans - 1])) { + int i; + if (t < g.when[0]) { + /* before the first transition: the first standard type */ + i = 0; + for (int k = 0; k < g.ntypes; k++) + if (!g.type[k].isdst) { + i = k; + break; + } + } else { + int lo = 0, hi = g.ntrans - 1; + while (lo < hi) { + int mid = (lo + hi + 1) / 2; + if (g.when[mid] <= t) + lo = mid; + else + hi = mid - 1; + } + i = g.which[lo]; + } + *east = g.type[i].east; + *isdst = g.type[i].isdst; + *abbr = g.type[i].abbr; + return; + } + int d = rule_isdst(&g, t); + *east = d ? g.dst_east : g.std_east; + *isdst = d; + *abbr = d ? g.dst : g.std; +} + +struct tm *localtime_r(const time_t *restrict t, struct tm *restrict out) +{ + long east; + int isdst; + const char *abbr; + __lock(&g_lock); + tzset_locked(); + offset_at(*t, &east, &isdst, &abbr); + __unlock(&g_lock); + time_t local = *t + east; + if (!gmtime_r(&local, out)) + return NULL; + out->tm_isdst = isdst; + out->tm_gmtoff = east; + out->tm_zone = abbr; + return out; +} + +struct tm *localtime(const time_t *t) +{ + static struct tm tm; + return localtime_r(t, &tm); +} + +/* Local fields to a time. The fields are read at an offset, and which + * offset is what tm_isdst says, as glibc reads it: > 0 the summer one, + * 0 the standard one -- even where that offset is not the one in force + * then, so "02:30, standard time" on the morning the clocks go forward is + * 03:30 summer time -- and < 0 whichever is in force: the earlier of the + * two where the time happens twice (the hour the clocks go back), the + * standard one where it does not happen at all. The two candidates are + * the offsets either side of the time, a day out, so a zone's history is + * honoured as well as its rule. */ +static int valid_at(long long as_utc, long east) +{ + long e; + int d; + const char *a; + offset_at(as_utc - east, &e, &d, &a); + return e == east; +} + +time_t mktime(struct tm *tm) +{ + __lock(&g_lock); + tzset_locked(); + struct tm u = *tm; + long long as_utc = timegm(&u); + long e1, e2; + int d1, d2; + const char *a; + offset_at(as_utc - 86400, &e1, &d1, &a); + offset_at(as_utc + 86400, &e2, &d2, &a); + long o_std = !d1 ? e1 : !d2 ? e2 : g.std_east; + long o_dst = d1 ? e1 : d2 ? e2 : g.has_dst ? g.dst_east : o_std; + if (!d1 && !d2 && e1 != e2 && !valid_at(as_utc, o_std)) + o_std = e2; /* the standard offset itself changed */ + long long t; + if (tm->tm_isdst > 0) + t = as_utc - o_dst; + else if (tm->tm_isdst == 0) + t = as_utc - o_std; + else if (o_dst != o_std && valid_at(as_utc, o_dst)) + t = as_utc - o_dst; + else + t = as_utc - o_std; + __unlock(&g_lock); + time_t r = (time_t)t; + localtime_r(&r, tm); + return r; +} + +/* ---- strptime ---------------------------------------------------------------- + * + * POSIX's conversions in the "C" locale, with glibc's extensions (%k %l %s + * %z %F %g %G) and glibc's readings wherever POSIX leaves a choice -- each + * checked against glibc 2.36: + * + * - A space in the format matches any amount of white space, none + * included; a number skips white space before it; a name does not. + * - A number reads digits only while one more could still fit the field: + * "61" for %M is 6, with "1" left over. + * - %y is 1969-2068, unless %C gives the century; %C alone is that + * century's year 0; %I is joined by %p; %H forgets an earlier %I. + * - Once a date field is read, the fields it implies follow: tm_wday and + * tm_yday from the year, month and day; the month and day from %j; and + * from %U or %W with a weekday, the date itself. + * + * Where glibc reads outside its month table (day 366 of a common year, + * week 0) or truncates a negative year, the calendar is followed instead: + * December 32, January -6, and the proleptic weekday. + * + * The nested formats (%c %D %r ...) share one state with the outer one, + * so "%D" followed by "%C" combines; a nested format that fails leaves tm + * as it was. */ +static const char *const st_wday[7] = { "sunday", "monday", "tuesday", + "wednesday", "thursday", "friday", "saturday" }; +static const char *const st_mon[12] = { "january", "february", "march", + "april", "may", "june", "july", "august", "september", "october", + "november", "december" }; +static const unsigned short st_yday[2][13] = { + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }, +}; + +struct st_state { + int century, want_century, want_xday; + int have_I, is_pm, have_wday, have_yday, have_mon, have_mday; + int have_uweek, have_wweek, week_no; +}; + +static const char *st_num(const char *s, int min, int max, int digits, + int *out) +{ + while (isspace((unsigned char)*s)) + s++; + if (!isdigit((unsigned char)*s)) + return NULL; + int v = 0; + do + v = v * 10 + (*s++ - '0'); + while (--digits > 0 && v * 10 <= max && isdigit((unsigned char)*s)); + if (v < min || v > max) + return NULL; + *out = v; + return s; +} + +/* The longest of the full and the three-letter names that matches. */ +static const char *st_name(const char *s, const char *const *names, int n, + int *out) +{ + const char *best = NULL; + for (int i = 0; i < n; i++) { + size_t full = strlen(names[i]); + if (!strncasecmp(s, names[i], full)) { + if (!best || s + full > best) { + best = s + full; + *out = i; + } + } else if (!strncasecmp(s, names[i], 3) && (!best || s + 3 > best)) { + best = s + 3; + *out = i; + } + } + return best; +} + +static int st_leap(const struct tm *tm) +{ + return is_leap(1900L + tm->tm_year); +} + +static void st_wday_of(struct tm *tm) +{ + long y = 1900L + tm->tm_year; + long days = days_from_civil(y, (unsigned)tm->tm_mon + 1, 1) + + tm->tm_mday - 1; + tm->tm_wday = (int)(((days + 4) % 7 + 7) % 7); +} + +static void st_mon_mday_of(struct tm *tm, const struct st_state *st) +{ + const unsigned short *c = st_yday[st_leap(tm)]; + int m = 0; + /* (int): where int is 16 bits an unsigned short would promote to + * unsigned, and week 0's negative yday compare as huge */ + while (m < 11 && (int)c[m + 1] <= tm->tm_yday) /* day 366: Dec 32 */ + m++; + if (!st->have_mon) + tm->tm_mon = m; + if (!st->have_mday) + tm->tm_mday = tm->tm_yday - (int)c[m] + 1; +} + +static const char *st_parse(const char *s, const char *fmt, struct tm *tm, + struct st_state *st); + +static const char *st_nested(const char *s, const char *fmt, struct tm *tm, + struct st_state *st) +{ + struct tm copy = *tm; + struct st_state inner = *st; + if (!(s = st_parse(s, fmt, ©, &inner))) + return NULL; + *tm = copy; + *st = inner; + return s; +} + +static const char *st_parse(const char *s, const char *fmt, struct tm *tm, + struct st_state *st) +{ + int v; + while (*fmt) { + if (isspace((unsigned char)*fmt)) { + while (isspace((unsigned char)*s)) + s++; + fmt++; + continue; + } + if (*fmt != '%') { + if (*s++ != *fmt++) + return NULL; + continue; + } + fmt++; + /* strftime's flags and widths mean nothing here */ + while (*fmt == '-' || *fmt == '_' || *fmt == '0' || *fmt == '^' || + *fmt == '#') + fmt++; + while (isdigit((unsigned char)*fmt)) + fmt++; + if (*fmt == 'E' || *fmt == 'O') { /* the "C" locale has no alt */ + if (!*++fmt || !strchr(*(fmt - 1) == 'E' ? "cCxXyY" + : "bBhdeHImMSUVWwy", + *fmt)) + return NULL; + } + switch (*fmt++) { + case '%': + if (*s++ != '%') + return NULL; + break; + case 'a': case 'A': + if (!(s = st_name(s, st_wday, 7, &tm->tm_wday))) + return NULL; + st->have_wday = 1; + break; + case 'b': case 'B': case 'h': + if (!(s = st_name(s, st_mon, 12, &tm->tm_mon))) + return NULL; + st->have_mon = st->want_xday = 1; + break; + case 'c': + if (!(s = st_nested(s, "%a %b %e %H:%M:%S %Y", tm, st))) + return NULL; + st->want_xday = 1; + break; + case 'C': + if (!(s = st_num(s, 0, 99, 2, &st->century))) + return NULL; + st->want_xday = 1; + break; + case 'd': case 'e': + if (!(s = st_num(s, 1, 31, 2, &tm->tm_mday))) + return NULL; + st->have_mday = st->want_xday = 1; + break; + case 'D': case 'x': + if (!(s = st_nested(s, "%m/%d/%y", tm, st))) + return NULL; + st->want_xday = 1; + break; + case 'F': + if (!(s = st_nested(s, "%Y-%m-%d", tm, st))) + return NULL; + st->want_xday = 1; + break; + case 'g': /* an ISO year alone names no date */ + if (!(s = st_num(s, 0, 99, 2, &v))) + return NULL; + break; + case 'G': + if (!isdigit((unsigned char)*s)) + return NULL; + while (isdigit((unsigned char)*s)) + s++; + break; + case 'H': case 'k': + if (!(s = st_num(s, 0, 23, 2, &tm->tm_hour))) + return NULL; + st->have_I = 0; + break; + case 'I': case 'l': + if (!(s = st_num(s, 1, 12, 2, &v))) + return NULL; + tm->tm_hour = v % 12; + st->have_I = 1; + break; + case 'j': + if (!(s = st_num(s, 1, 366, 3, &v))) + return NULL; + tm->tm_yday = v - 1; + st->have_yday = 1; + break; + case 'm': + if (!(s = st_num(s, 1, 12, 2, &v))) + return NULL; + tm->tm_mon = v - 1; + st->have_mon = st->want_xday = 1; + break; + case 'M': + if (!(s = st_num(s, 0, 59, 2, &tm->tm_min))) + return NULL; + break; + case 'n': case 't': + while (isspace((unsigned char)*s)) + s++; + break; + case 'p': + if (!strncasecmp(s, "AM", 2)) + st->is_pm = 0; + else if (!strncasecmp(s, "PM", 2)) + st->is_pm = 1; + else + return NULL; + s += 2; + break; + case 'r': + if (!(s = st_nested(s, "%I:%M:%S %p", tm, st))) + return NULL; + break; + case 'R': + if (!(s = st_nested(s, "%H:%M", tm, st))) + return NULL; + break; + case 's': { + /* seconds since the Epoch, in local time; no sign, as glibc */ + if (!isdigit((unsigned char)*s)) + return NULL; + unsigned long long t = 0; + while (isdigit((unsigned char)*s)) + t = t * 10 + (unsigned)(*s++ - '0'); + time_t tt = (time_t)t; + if (!localtime_r(&tt, tm)) + return NULL; + break; + } + case 'S': /* 60 and 61: leap seconds, as C89 */ + if (!(s = st_num(s, 0, 61, 2, &tm->tm_sec))) + return NULL; + break; + case 'T': case 'X': + if (!(s = st_nested(s, "%H:%M:%S", tm, st))) + return NULL; + break; + case 'u': + if (!(s = st_num(s, 1, 7, 1, &v))) + return NULL; + tm->tm_wday = v % 7; + st->have_wday = 1; + break; + case 'U': case 'W': + if (!(s = st_num(s, 0, 53, 2, &st->week_no))) + return NULL; + st->have_uweek = fmt[-1] == 'U'; + st->have_wweek = fmt[-1] == 'W'; + break; + case 'V': /* an ISO week alone names no date */ + if (!(s = st_num(s, 0, 53, 2, &v))) + return NULL; + break; + case 'w': + if (!(s = st_num(s, 0, 6, 1, &tm->tm_wday))) + return NULL; + st->have_wday = 1; + break; + case 'y': + if (!(s = st_num(s, 0, 99, 2, &v))) + return NULL; + tm->tm_year = v >= 69 ? v : v + 100; + st->want_century = st->want_xday = 1; + break; + case 'Y': + if (!(s = st_num(s, 0, 9999, 4, &v))) + return NULL; + tm->tm_year = v - 1900; + st->want_century = 0; + st->want_xday = 1; + break; + case 'z': { + /* Z, or a sign and hh, hhmm or hh:mm */ + while (isspace((unsigned char)*s)) + s++; + if (*s == 'Z') { + s++; + tm->tm_gmtoff = 0; + break; + } + if (*s != '+' && *s != '-') + return NULL; + int neg = *s++ == '-', n = 0; + v = 0; + while (n < 4 && isdigit((unsigned char)*s)) { + v = v * 10 + (*s++ - '0'); + if (++n == 2 && *s == ':' && isdigit((unsigned char)s[1])) + s++; + } + if (n == 2) + v *= 100; + else if (n != 4 || v % 100 >= 60) + return NULL; + tm->tm_gmtoff = (v / 100) * 3600L + (v % 100) * 60L; + if (neg) + tm->tm_gmtoff = -tm->tm_gmtoff; + break; + } + case 'Z': /* a name: passed over, not read */ + while (isspace((unsigned char)*s)) + s++; + while (*s && !isspace((unsigned char)*s)) + s++; + break; + default: + return NULL; + } + } + return s; +} + +char *strptime(const char *restrict s, const char *restrict fmt, + struct tm *restrict tm) +{ + struct st_state st; + memset(&st, 0, sizeof st); + st.century = -1; + if (!(s = st_parse(s, fmt, tm, &st))) + return NULL; + + if (st.have_I && st.is_pm) + tm->tm_hour += 12; + if (st.century != -1) + tm->tm_year = (st.want_century ? tm->tm_year % 100 : 0) + + (st.century - 19) * 100; + if (st.want_xday && !st.have_wday) { + if (!(st.have_mon && st.have_mday) && st.have_yday) { + st_mon_mday_of(tm, &st); + st.have_mon = st.have_mday = 1; + } + if ((unsigned)tm->tm_mon <= 11 && tm->tm_year >= -1900) + st_wday_of(tm); + } + if (st.want_xday && !st.have_yday && + (st.have_mon || (unsigned)tm->tm_mon <= 11)) + tm->tm_yday = (int)st_yday[st_leap(tm)][tm->tm_mon] + tm->tm_mday - 1; + if ((st.have_uweek || st.have_wweek) && st.have_wday) { + /* the weekday of the year's first day places the week */ + int wday = tm->tm_wday, mday = tm->tm_mday, mon = tm->tm_mon; + int off = st.have_wweek; + tm->tm_mday = 1; + tm->tm_mon = 0; + st_wday_of(tm); + if (st.have_mday) + tm->tm_mday = mday; + if (st.have_mon) + tm->tm_mon = mon; + if (!st.have_yday) + tm->tm_yday = (7 - (tm->tm_wday - off)) % 7 + + (st.week_no - 1) * 7 + (wday - off + 7) % 7; + if (!st.have_mday || !st.have_mon) + st_mon_mday_of(tm, &st); + tm->tm_wday = wday; + } + return (char *)s; +} diff --git a/lib/libcxx/include/filesystem b/lib/libcxx/include/filesystem index 385806f31..832debf00 100644 --- a/lib/libcxx/include/filesystem +++ b/lib/libcxx/include/filesystem @@ -60,6 +60,9 @@ extern "C" { #define __OS_FT_FIFO 7 #define __OS_FT_SOCKET 8 +/* lib/libc/os/backend.h's, field for field: the backend fills ALL of it, + * so a shorter copy here is a stack overwrite on every status() -- + * tests/golden/libc-backends.sh compares the two. */ struct __os_fileinfo { unsigned long long size; long mtime; @@ -67,6 +70,12 @@ struct __os_fileinfo { int type; unsigned long long dev, ino; unsigned long long nlink; + unsigned int uid, gid; + unsigned long long rdev; + long long atime, ctime; + long atime_nsec, mtime_nsec, ctime_nsec; + long long blocks; + long blksize; }; int __os_stat(const char *path, struct __os_fileinfo *out); diff --git a/lib/rt/unwind.c b/lib/rt/unwind.c index 41a81ef50..5d9188255 100644 --- a/lib/rt/unwind.c +++ b/lib/rt/unwind.c @@ -9,8 +9,8 @@ * * ---- why it is here rather than nowhere ---------------------------------- * - * Every other target EmbCC has gets this from the platform -- libgcc on - * EmbLinkOS, libunwind inside libSystem on macOS. The static Linux + * Every other target EmbCC has gets this from the platform -- libunwind + * inside libSystem on macOS, libgcc on a bare x86_64-elf. The static Linux * target links against nothing, so it had neither, and a C++ program * that threw compiled and then failed at the link with an undefined * `_Unwind_Resume`. D-014's second amendment called this the harder @@ -48,7 +48,11 @@ */ #include "rt.h" -#if defined(__linux__) || defined(__EMBCC_LINUX__) +/* EmbLinkOS too, where its library is built with __EMBCC_RT_UNWIND + * (make libc-emblinkos): its programs start with -nostartfiles, so + * libgcc's unwinder, which crtbegin's frame registration feeds, would + * find no tables -- and its linker script brackets .eh_frame for this. */ +#if defined(__linux__) || defined(__EMBCC_LINUX__) || defined(__EMBCC_RT_UNWIND) typedef unsigned long uw_word; typedef long uw_sword; @@ -1022,4 +1026,4 @@ void *__deregister_frame_info(const void *b) { (void)b; return 0; } void __register_frame_info_bases(const void *b, void *o, void *t, void *d) { (void)b; (void)o; (void)t; (void)d; } -#endif /* Linux targets */ +#endif /* Linux targets, and EmbLinkOS */ diff --git a/src/driver/main.c b/src/driver/main.c index d595aec33..49f81603f 100644 --- a/src/driver/main.c +++ b/src/driver/main.c @@ -961,10 +961,14 @@ static int has_link_input_suffix(const char *p) } /* -lNAME, as a linker finds it: libNAME.a in each -L directory in order. - * Three names are this toolchain's own, because a Makefile written for - * arm-none-eabi-gcc says them: -lc and -lm are EmbCC's libc (its math is - * in it), -lgcc is the compiler runtime, librt.a. Those return 1 having - * found nothing to add, when the driver adds them itself anyway. */ + * Some names are this toolchain's own, because Makefiles say them: -lc + * and -lm are EmbCC's libc (its math is in it), -lgcc is the compiler + * runtime, librt.a; and -lpthread, -ldl, -lrt and -lutil, which glibc + * since 2.34 keeps only as empty archives beside a libc that has their + * functions, as this one has POSIX threads, the clocks and openpty. A + * name the library lacks a function of (dlopen) still fails the link, + * naming the function. These return 1 having found nothing to add, when + * the driver adds them itself anyway. */ static int find_lib(const char *name, char *out, size_t cap, int *own) { *own = 0; @@ -973,10 +977,14 @@ static int find_lib(const char *name, char *out, size_t cap, int *own) if (plat_file_exists(out)) return 1; } - if (!strcmp(name, "c") || !strcmp(name, "m") || !strcmp(name, "gcc")) { - *own = 1; - return 1; - } + static const char *const own_names[] = { + "c", "m", "gcc", "pthread", "dl", "rt", "util", + }; + for (size_t k = 0; k < sizeof own_names / sizeof *own_names; k++) + if (!strcmp(name, own_names[k])) { + *own = 1; + return 1; + } return 0; } @@ -2535,6 +2543,22 @@ static int pic32_script(const char *path) return 0; } +/* A default include directory the command line already named, as + * -I lib/libc/include: searched once, where the user put it, and as a + * system directory, which is GCC's rule. Twice, it broke #include_next -- + * the C library's reaches the compiler's that way, and found + * ITSELF again in the second copy, include guard already set, so + * INT_MAX was never defined. */ +static int searched_already(const char *dir) +{ + for (int i = 0; i < nincdirs; i++) + if (plat_same_dir(incdirs[i], dir)) { + incdir_sys[i] = 1; + return 1; + } + return 0; +} + static void incdirs_with_defaults(void) { if (g_incdirs_done) @@ -2616,6 +2640,8 @@ static void incdirs_with_defaults(void) cpp_set_config_data(cfgdata); } for (int k = 0; k < ndef && nincdirs < MAX_INCDIRS; k++) { + if (searched_already(def[k])) + continue; incdir_sys[nincdirs] = 1; incdirs[nincdirs++] = def[k]; } @@ -6466,7 +6492,7 @@ static int embcc_main(int argc, char **argv) } else if (strcmp(argv[i], "-pthread") == 0) { /* POSIX threads: the C library's. With --libc=system, * glibc's: _REENTRANT and -lpthread, as GCC has it; EmbCC's - * own libc has none, and macOS's libSystem has them */ + * own libc has them in libc.a, and macOS's in libSystem */ g_pthread = 1; } else if (strcmp(argv[i], "-isysroot") == 0) { if (i + 1 >= argc) { @@ -6811,8 +6837,19 @@ static int embcc_main(int argc, char **argv) * target, whose order is the SDK's and is set in full by * incdirs_with_defaults: from the source tree this put EmbCC's * before the SDK's, so the tree's compiler and an installed - * one read different headers. */ - if (nincdirs < MAX_INCDIRS && target_os_get() != TGT_OS_DARWIN) { + * one read different headers. + * + * Only when paths.c found no support files, though. When it did, its + * defaults already hold this directory, LAST and for a reason: it + * has a declarations-only , and added here it came before + * the C library's, so lost every POSIX declaration in it + * (strtok_r, strlcpy, ...) for any program built without -I. Run from + * $PATH, argv[0] has no '/', and this was ./include -- whatever + * directory the user happened to be in. */ + int ndefault = 0; + paths_default_includes(&ndefault); + if (nincdirs < MAX_INCDIRS && target_os_get() != TGT_OS_DARWIN && + ndefault == 0) { static char selfinc[4096]; const char *slash = strrchr(argv[0], '/'); if (slash) diff --git a/src/link/link.c b/src/link/link.c index 79d8d069f..9b2d8a312 100644 --- a/src/link/link.c +++ b/src/link/link.c @@ -5167,6 +5167,7 @@ static void write_exec(struct linker *l, const char *out, } if (plat_write_file(out, img, (size_t)total) != 0) die("cannot write '%s'", out); + plat_make_executable(out); free(img); } @@ -5268,6 +5269,7 @@ static void emit_embx(struct linker *l, const char *out, unsigned long long caps if (plat_write_file(out, img, (size_t)image_size) != 0) die("cannot write '%s'", out); + plat_make_executable(out); free(img); fprintf(stderr, "embld: wrote %s (EMBX, %d capabilit%s)\n", out, ncaps, ncaps == 1 ? "y" : "ies"); @@ -6642,6 +6644,7 @@ static void write_exec_script(struct linker *l, struct ls_script *sc, } if (plat_write_file(out, img, (size_t)total) != 0) die("cannot write '%s'", out); + plat_make_executable(out); free(img); free(em); free(fo); free(n_osec); free(n_dbg); free(dbg_at); free(symname); free(symstr.p); free(shstr.p); } diff --git a/src/platform/platform.h b/src/platform/platform.h index ce7d0f57a..40bb4e57d 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -46,6 +46,18 @@ int plat_write_file(const char *path, const void *data, size_t len); * answer can be stale by the time the file is opened. */ int plat_file_exists(const char *path); +/* Whether two paths name the same directory: by the directory's identity + * where the host has one -- so a relative and an absolute spelling, or a + * symlink, are the same -- and by spelling elsewhere. What keeps one + * include directory from being searched twice. */ +int plat_same_dir(const char *a, const char *b); + +/* Mark a file just written as a program that can be run: the mode GNU ld + * gives its output, 0777 less the umask, where the host has modes. A + * linker whose executables cannot be executed has not finished the job. + * Nothing where the host has no such idea. */ +void plat_make_executable(const char *path); + /* The names in a directory (not "." or ".."), malloc'd, NULL-terminated, * in no particular order; NULL when it cannot be read or the host cannot * list a directory at all (ISO C has no words for it), which callers treat diff --git a/src/platform/platform_iso.c b/src/platform/platform_iso.c index 5f7d8c72d..8ab600390 100644 --- a/src/platform/platform_iso.c +++ b/src/platform/platform_iso.c @@ -12,6 +12,8 @@ * compiler was built with (EMBCC_DEFAULT_PREFIX). */ #include "platform.h" +#include + int plat_stderr_is_terminal(void) { return 0; @@ -34,6 +36,18 @@ const char *plat_self_path(void) return plat_argv0_path(); } +/* ISO C has no file identity: two directories are the same by spelling */ +int plat_same_dir(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + +/* ISO C has no file modes */ +void plat_make_executable(const char *path) +{ + (void)path; +} + /* ISO C cannot list a directory */ char **plat_list_dir(const char *path) { diff --git a/src/platform/platform_posix.c b/src/platform/platform_posix.c index c125a0670..b483c3b63 100644 --- a/src/platform/platform_posix.c +++ b/src/platform/platform_posix.c @@ -32,6 +32,13 @@ #include #endif #include /* isatty, readlink */ +/* stat, for plat_same_dir: a device and inode number that mean something. + * EmbLinkOS's newlib stat reports no inode, which would make every + * directory "the same", so there the answer is by spelling. */ +#if defined(__APPLE__) || defined(__linux__) +#define PLAT_HAS_INODES 1 +#include +#endif /* threads, for plat_run_on_stack's big stack: macOS's and glibc's (in * libc from 2.34, -lpthread before). EmbLinkOS's newlib has none. */ #if defined(__APPLE__) || defined(__linux__) @@ -133,6 +140,27 @@ const char *plat_self_path(void) return buf[0] ? buf : NULL; } +void plat_make_executable(const char *path) +{ +#ifdef PLAT_HAS_INODES + mode_t mask = umask(0); + umask(mask); + chmod(path, 0777 & ~mask); +#else + (void)path; +#endif +} + +int plat_same_dir(const char *a, const char *b) +{ +#ifdef PLAT_HAS_INODES + struct stat sa, sb; + if (stat(a, &sa) == 0 && stat(b, &sb) == 0) + return sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino; +#endif + return strcmp(a, b) == 0; +} + #ifndef PLAT_HAS_DIRENT char **plat_list_dir(const char *path) { diff --git a/tests/exec/malloc-gnu.c b/tests/exec/malloc-gnu.c new file mode 100644 index 000000000..15b9fdcd7 --- /dev/null +++ b/tests/exec/malloc-gnu.c @@ -0,0 +1,63 @@ +/* glibc's : what an allocator may say about itself, checked + * as properties any allocator has -- a block holds at least what was + * asked, mallinfo2 counts a block while it is in use, valloc and pvalloc + * are page-aligned and pvalloc rounds up to whole pages. (QuickJS sizes + * its heap with malloc_usable_size; EmbLinkOS builds it on this.) */ +// expect-exit: 42 +// needs-libc: glibc's , which newlib has only in part +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include + +int main(void) +{ + if (malloc_usable_size(NULL) != 0) + return 1; + for (size_t n = 1; n < 5000; n = n * 3 + 1) { + unsigned char *p = malloc(n); + size_t u = malloc_usable_size(p); + if (!p || u < n) + return 2; + memset(p, 0xA5, u); /* all of it is the program's */ + free(p); + } + char *a = aligned_alloc(64, 100); + if (!a || malloc_usable_size(a) < 100) + return 3; + free(a); + + /* a block the counts must see: smaller where memory is a few KB */ + size_t bigsz = SIZE_MAX > 0xFFFF ? 100000 : 256; + struct mallinfo2 before = mallinfo2(); + void *big = malloc(bigsz); + struct mallinfo2 during = mallinfo2(); + if (!big || during.uordblks < before.uordblks + bigsz || + during.arena < during.uordblks) + return 4; + free(big); + struct mallinfo2 after = mallinfo2(); + if (after.uordblks >= during.uordblks) + return 5; + +#if SIZE_MAX > 0xFFFF /* a page is more than an AVR has */ + long page = sysconf(_SC_PAGESIZE); + if (page <= 0) + page = 4096; + void *v = valloc(10); + void *pv = pvalloc(1); + if (!v || (uintptr_t)v % (uintptr_t)page || !pv || + (uintptr_t)pv % (uintptr_t)page || + malloc_usable_size(pv) < (size_t)page) + return 6; + free(v); + free(pv); +#endif + void *m = memalign(256, 33); + if (!m || (uintptr_t)m % 256) + return 7; + free(m); + return 42; +} diff --git a/tests/exec/posix-fd.c b/tests/exec/posix-fd.c new file mode 100644 index 000000000..5ccda65bc --- /dev/null +++ b/tests/exec/posix-fd.c @@ -0,0 +1,206 @@ +/* POSIX's descriptors and filesystem: open, read and write in their + * forms, dup and fcntl, pipes, locks, stat's fields, links, modes and + * times, the *at functions relative to a directory descriptor (the real + * *at calls on Linux, which work with no /proc), and directories as + * readdir reads them -- `.` and `..` included, positions kept. + * + * The expected values are what glibc 2.36 gave for the same calls on the + * same kernel. Without a filesystem (EMBCC_TEST_HAS_FS unset: the boards) + * what is asked is that the calls fail and say so. */ +// expect-exit: 42 +// needs-libc: POSIX's descriptor and file calls, on lib/libc's backends +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBCC_TEST_HAS_FS +static int descriptors(void) +{ + int fd = open("/tmp/f1", O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0 || write(fd, "hello world", 11) != 11 || + lseek(fd, 0, SEEK_CUR) != 11) + return 1; + char b[32] = { 0 }; + if (pread(fd, b, 5, 6) != 5 || strcmp(b, "world") != 0) + return 2; + struct iovec iov[2] = { { "AB", 2 }, { "CD", 2 } }; + lseek(fd, 0, SEEK_END); + struct stat st; + if (writev(fd, iov, 2) != 4 || fstat(fd, &st) != 0 || st.st_size != 15 || + !S_ISREG(st.st_mode) || (st.st_mode & 0777) != 0644 || + st.st_nlink != 1) + return 3; + int d = dup(fd), d2 = dup2(fd, 20), d3 = fcntl(fd, F_DUPFD, 30); + if (d <= fd || d2 != 20 || d3 != 30) + return 4; + if (fcntl(fd, F_GETFD) != 0 || fcntl(fd, F_SETFD, FD_CLOEXEC) != 0 || + fcntl(fd, F_GETFD) != FD_CLOEXEC || fcntl(d, F_GETFD) != 0) + return 5; + int fl = fcntl(fd, F_GETFL); + if ((fl & O_ACCMODE) != O_RDWR || (fl & O_APPEND)) + return 6; + fcntl(fd, F_SETFL, fl | O_APPEND | O_NONBLOCK); + fl = fcntl(fd, F_GETFL); + if (!(fl & O_APPEND) || !(fl & O_NONBLOCK)) + return 7; + struct flock lk = { F_WRLCK, SEEK_SET, 0, 5, 0 }; + struct flock q = { F_WRLCK, SEEK_SET, 0, 5, 0 }; + /* a process's own locks do not conflict with it: F_UNLCK */ + if (fcntl(fd, F_SETLK, &lk) != 0 || fcntl(fd, F_GETLK, &q) != 0 || + q.l_type != F_UNLCK) + return 8; + if (ftruncate(fd, 3) != 0 || fstat(fd, &st) != 0 || st.st_size != 3 || + fsync(fd) != 0 || fdatasync(fd) != 0) + return 9; + errno = 0; + if (close(999) != -1 || errno != EBADF) + return 10; + errno = 0; + if (dup3(fd, fd, 0) != -1 || errno != EINVAL) + return 11; + close(fd), close(d), close(d2), close(d3); + + int p[2]; + if (pipe(p) != 0 || write(p[1], "xyz", 3) != 3) + return 12; + close(p[1]); + memset(b, 0, sizeof b); + if (read(p[0], b, sizeof b) != 3 || strcmp(b, "xyz") || + read(p[0], b, sizeof b) != 0) + return 13; + close(p[0]); + if (pipe2(p, O_CLOEXEC) != 0 || fcntl(p[0], F_GETFD) != FD_CLOEXEC) + return 14; + close(p[0]), close(p[1]); + return 0; +} + +static int names(void) +{ + struct stat st; + char b[64]; + errno = 0; + if (access("/tmp/f1", R_OK | W_OK) != 0 || + access("/tmp/none", F_OK) != -1 || errno != ENOENT) + return 20; + errno = 0; + if (mkdir("/tmp/d", 0755) != 0 || mkdir("/tmp/d", 0755) != -1 || + errno != EEXIST) + return 21; + memset(b, 0, sizeof b); + if (symlink("../f1", "/tmp/d/ln") != 0 || + readlink("/tmp/d/ln", b, sizeof b) != 5 || strcmp(b, "../f1")) + return 22; + if (lstat("/tmp/d/ln", &st) || !S_ISLNK(st.st_mode) || + stat("/tmp/d/ln", &st) || st.st_size != 3) + return 23; + if (link("/tmp/f1", "/tmp/d/hard") || stat("/tmp/f1", &st) || + st.st_nlink != 2 || rename("/tmp/d/hard", "/tmp/d/hard2") || + unlink("/tmp/d/hard2")) + return 24; + if (chmod("/tmp/f1", 0600) || stat("/tmp/f1", &st) || + (st.st_mode & 0777) != 0600 || umask(077) != 022 || umask(022) != 077) + return 25; + int dfd = open("/tmp/d", O_RDONLY | O_DIRECTORY); + errno = 0; + if (dfd < 0 || open("/tmp/f1", O_RDONLY | O_DIRECTORY) != -1 || + errno != ENOTDIR) + return 26; + int fd = openat(dfd, "at.txt", O_WRONLY | O_CREAT, 0644); + if (fd < 0 || fstatat(dfd, "at.txt", &st, 0) || !S_ISREG(st.st_mode) || + mkdirat(dfd, "sub", 0755) || unlinkat(dfd, "at.txt", 0) || + unlinkat(dfd, "sub", AT_REMOVEDIR)) + return 27; + close(fd); + struct timespec ts[2] = { { 1000000, 0 }, { 2000000, 500 } }; + if (utimensat(AT_FDCWD, "/tmp/f1", ts, 0) || stat("/tmp/f1", &st) || + st.st_atime != 1000000 || st.st_mtime != 2000000 || + st.st_mtim.tv_nsec != 500) + return 28; + char cwd[256]; + if (chdir("/tmp/d") || !getcwd(cwd, sizeof cwd) || strcmp(cwd, "/tmp/d")) + return 29; + char *dyn = getcwd(NULL, 0); + if (!dyn || strcmp(dyn, "/tmp/d")) + return 30; + free(dyn); + errno = 0; + if (getcwd(cwd, 3) || errno != ERANGE || fchdir(dfd) || chdir("/")) + return 31; + close(dfd); + return 0; +} + +static int directories(void) +{ + DIR *d = opendir("/tmp/d"); + if (!d || dirfd(d) < 0) + return 40; + int dots = 0, other = 0; + long after_first = -1; + struct dirent *e; + while ((e = readdir(d))) { + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) + dots++; + else if (strcmp(e->d_name, "ln") || e->d_type != DT_LNK || !e->d_ino) + return 41; + else + other++; + if (after_first < 0) + after_first = telldir(d); + } + if (dots != 2 || other != 1) + return 42; + rewinddir(d); + int again = 0; + while (readdir(d)) + again++; + seekdir(d, after_first); + int rest = 0; + while (readdir(d)) + rest++; + if (again != 3 || rest != 2) + return 43; + closedir(d); + struct dirent **list; + int n = scandir("/tmp/d", &list, NULL, alphasort); + if (n != 3 || strcmp(list[0]->d_name, ".") || strcmp(list[2]->d_name, "ln")) + return 44; + while (n) + free(list[--n]); + free(list); + errno = 0; + if (opendir("/tmp/nonexistent") || errno != ENOENT) + return 45; + return 0; +} +#endif + +int main(void) +{ +#ifdef EMBCC_TEST_HAS_FS + int r; + if ((r = descriptors()) || (r = names()) || (r = directories())) + return r; +#else + /* no filesystem: every one fails, and says why */ + struct stat st; + errno = 0; + if (stat("/tmp", &st) != -1 || errno == 0) + return 50; + errno = 0; + if (opendir("/") || errno == 0) + return 51; + errno = 0; + if (mkdir("/x", 0755) != -1 || errno == 0) + return 52; +#endif + return 42; +} diff --git a/tests/exec/posix-files.c b/tests/exec/posix-files.c new file mode 100644 index 000000000..59f793d88 --- /dev/null +++ b/tests/exec/posix-files.c @@ -0,0 +1,146 @@ +/* POSIX's functions that need a filesystem: mkstemp and its family, + * mkdtemp and mktemp, realpath through ".", ".." and a symlink, fdopen + * and fileno, and freopen -- which must reopen the SAME stream, so that + * freopen("x", "w", stdout) leaves stdout itself writing to x (it used + * to hand back another FILE and close the one it was given). + * + * tests/golden/linux-libc-exec.sh runs it on Linux with an empty /tmp, + * built with EMBCC_TEST_HAS_FS. Without that, there is no filesystem, + * and what can be asked is that realpath and mkdtemp fail and say so. */ +// expect-exit: 42 +// needs-libc: mkstemps and the rest, against lib/libc's backends +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBCC_TEST_HAS_FS +/* the six letters that replaced "XXXXXX" -- which may themselves include + * an X, so "no X left" is not the test */ +static int filled(const char *name, size_t suffix) +{ + return memcmp(name + strlen(name) - suffix - 6, "XXXXXX", 6) != 0; +} + + +static int temporaries(void) +{ + char t[] = "/tmp/fileXXXXXX"; + int fd = mkstemp(t); + if (fd < 0 || strncmp(t, "/tmp/file", 9) != 0 || !filled(t, 0)) + return 1; + FILE *f = fdopen(fd, "w+"); + if (!f || fileno(f) != fd) + return 2; + fprintf(f, "through fdopen\n"); + rewind(f); + char line[64]; + if (!fgets(line, sizeof line, f) || strcmp(line, "through fdopen\n")) + return 3; + fclose(f); + /* the same template, made again: a different name */ + char u[] = "/tmp/fileXXXXXX"; + if (mkstemp(u) < 0 || strcmp(t, u) == 0) + return 4; + + char s[] = "/tmp/sXXXXXX.txt"; + if (mkstemps(s, 4) < 0 || strcmp(s + strlen(s) - 4, ".txt") != 0 || + !filled(s, 4)) + return 5; + char bad[] = "/tmp/noxs"; + errno = 0; + if (mkstemp(bad) != -1 || errno != EINVAL) + return 6; + char d[] = "/tmp/dirXXXXXX"; + if (!mkdtemp(d) || mkdtemp((char[]){ "/tmp/no/such/XXXXXX" })) + return 7; + char m[] = "/tmp/mtXXXXXX"; + if (mktemp(m) != m || m[0] != '/' || !filled(m, 0)) + return 8; + return 0; +} + +static int resolved(void) +{ + char out[PATH_MAX], in[PATH_MAX]; + if (mkdir("/tmp/r", 0755) || mkdir("/tmp/r/inner", 0755) || + symlink("inner", "/tmp/r/link") || + symlink("/tmp/r/inner/..", "/tmp/r/up")) + return 10; + if (!realpath("/tmp/r/./inner/../inner/", out) || + strcmp(out, "/tmp/r/inner") != 0) + return 11; + /* ".." after a link leaves what the link points at */ + if (!realpath("/tmp/r/link/..", out) || strcmp(out, "/tmp/r") != 0) + return 12; + if (!realpath("/tmp/r/up/link", out) || strcmp(out, "/tmp/r/inner") != 0) + return 13; + snprintf(in, sizeof in, "//tmp///r"); + char *p = realpath(in, NULL); + if (!p || strcmp(p, "/tmp/r") != 0) + return 14; + free(p); + errno = 0; + if (realpath("/tmp/none/x", out) || errno != ENOENT) + return 15; + FILE *f = fopen("/tmp/r/file", "w"); + fclose(f); + errno = 0; + if (realpath("/tmp/r/file/x", out) || errno != ENOTDIR) + return 16; + if (symlink("loop", "/tmp/r/loop") || realpath("/tmp/r/loop", out) || + errno != ELOOP) + return 17; + return 0; +} + +static int reopened(void) +{ + FILE *a = fopen("/tmp/a.txt", "w"); + if (!a) + return 20; + FILE *b = freopen("/tmp/b.txt", "w", a); + if (b != a) + return 21; + fputs("in b", b); + fclose(b); + char line[16]; + FILE *r = fopen("/tmp/b.txt", "r"); + if (!r || !fgets(line, sizeof line, r) || strcmp(line, "in b") != 0) + return 22; + fclose(r); + /* stdout itself, which is no slot in the table of streams: the case + * where a new FILE could never have been the old one */ + if (freopen("/tmp/c.txt", "w", stdout) != stdout) + return 23; + printf("to c %d", 3); + fflush(stdout); + r = fopen("/tmp/c.txt", "r"); + if (!r || !fgets(line, sizeof line, r) || strcmp(line, "to c 3") != 0) + return 24; + fclose(r); + return 0; +} +#endif + +int main(void) +{ +#ifdef EMBCC_TEST_HAS_FS + int r; + if ((r = temporaries()) || (r = resolved()) || (r = reopened())) + return r; +#else + char out[PATH_MAX]; + errno = 0; + if (realpath("x", out) || errno == 0) + return 30; + errno = 0; + if (mkdtemp((char[]){ "/tmp/dirXXXXXX" }) || errno == 0) + return 31; +#endif + return 42; +} diff --git a/tests/exec/posix-glob.c b/tests/exec/posix-glob.c new file mode 100644 index 000000000..20d330eac --- /dev/null +++ b/tests/exec/posix-glob.c @@ -0,0 +1,228 @@ +/* glob, nftw and ftw over a small tree, and syslog's LOG_PERROR line: + * every answer is glibc 2.36's for the same calls. */ +// expect-exit: 42 +// needs-libc: , and , from lib/libc +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBCC_TEST_HAS_FS +#define R "/tmp/gt" + +static void put(const char *p) +{ + int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) + close(fd); +} + +static void tree(void) +{ + mkdir(R, 0755); + mkdir(R "/d1", 0755); + mkdir(R "/d2", 0755); + mkdir(R "/d1/sub", 0755); + put(R "/a.c"); + put(R "/b.c"); + put(R "/c.h"); + put(R "/.hidden.c"); + put(R "/d1/x.c"); + put(R "/d1/sub/y.c"); + put(R "/d2/z.txt"); + put(R "/we*rd"); + symlink("d1", R "/link"); +} + +static int same(glob_t *g, const char *const *want) +{ + size_t n = 0; + while (want[n]) + n++; + if (g->gl_pathc != n) + return 0; + for (size_t i = 0; i < n; i++) + if (strcmp(g->gl_pathv[g->gl_offs + i], want[i])) + return 0; + return g->gl_pathv[g->gl_offs + n] == NULL; +} + +static int globs(void) +{ + glob_t g; + if (glob(R "/*.c", 0, NULL, &g) || + !same(&g, (const char *[]){ R "/a.c", R "/b.c", NULL })) + return 1; + globfree(&g); + if (glob(R "/*/*.c", 0, NULL, &g) || + !same(&g, (const char *[]){ R "/d1/x.c", R "/link/x.c", NULL })) + return 2; + globfree(&g); + if (glob(R "/d*", GLOB_MARK, NULL, &g) || + !same(&g, (const char *[]){ R "/d1/", R "/d2/", NULL })) + return 3; + globfree(&g); + /* a leading dot only by a dot; GLOB_PERIOD lets a wildcard have it */ + if (glob(R "/.*.c", 0, NULL, &g) || + !same(&g, (const char *[]){ R "/.hidden.c", NULL })) + return 4; + globfree(&g); + if (glob(R "/*.c", GLOB_PERIOD, NULL, &g) || + !same(&g, (const char *[]){ R "/.hidden.c", R "/a.c", R "/b.c", NULL })) + return 5; + globfree(&g); + if (glob(R "/*.zz", 0, NULL, &g) != GLOB_NOMATCH) + return 6; + if (glob(R "/*.zz", GLOB_NOCHECK, NULL, &g) || + !same(&g, (const char *[]){ R "/*.zz", NULL })) + return 7; + globfree(&g); + /* an escaped star is a star */ + if (glob(R "/we\\*rd", 0, NULL, &g) || + !same(&g, (const char *[]){ R "/we*rd", NULL })) + return 8; + globfree(&g); + /* offsets, then appending */ + g.gl_offs = 2; + if (glob(R "/a.c", GLOB_DOOFFS, NULL, &g) || + glob(R "/c.*", GLOB_DOOFFS | GLOB_APPEND, NULL, &g) || + g.gl_pathv[0] || g.gl_pathv[1] || + !same(&g, (const char *[]){ R "/a.c", R "/c.h", NULL })) + return 9; + globfree(&g); + /* GNU's braces, each alternative in order */ + if (glob(R "/{c,a}.?", GLOB_BRACE, NULL, &g) || + !same(&g, (const char *[]){ R "/c.h", R "/a.c", NULL })) + return 10; + globfree(&g); + if (glob(R "/d?", GLOB_ONLYDIR, NULL, &g) || + !same(&g, (const char *[]){ R "/d1", R "/d2", NULL }) || + !(g.gl_flags & GLOB_MAGCHAR)) + return 11; + globfree(&g); + setenv("HOME", R, 1); + if (glob("~/b.*", GLOB_TILDE, NULL, &g) || + !same(&g, (const char *[]){ R "/b.c", NULL })) + return 12; + globfree(&g); + if (!glob_pattern_p("a[bc]", 1) || glob_pattern_p("a\\*", 1) || + !glob_pattern_p("a\\*", 0)) + return 13; + return 0; +} + +static char g_seen[512]; +static int g_n; + +static int note(const char *p, const struct stat *st, int type, struct FTW *f) +{ + (void)st; + char b[96]; + snprintf(b, sizeof b, "%d:%d:%s:%s;", type, f->level, p + strlen(R), + p + f->base); + strcat(g_seen, b); + g_n++; + return 0; +} + +static int prune(const char *p, const struct stat *st, int type, + struct FTW *f) +{ + (void)st; + (void)f; + g_n++; + if (type == FTW_D && !strcmp(p + f->base, "d1")) + return FTW_SKIP_SUBTREE; + return 0; +} + +static int g_old; +static int old_fn(const char *p, const struct stat *st, int type) +{ + (void)p; + (void)st; + if (type == FTW_F) + g_old++; + return 0; +} + +static int walks(void) +{ + /* the order inside a directory is readdir's, so count and check + * shapes rather than a sequence */ + if (nftw(R "/d1", note, 4, FTW_PHYS) || g_n != 4 || + !strstr(g_seen, "1:0:/d1:d1;") || !strstr(g_seen, "0:1:/d1/x.c:x.c;") || + !strstr(g_seen, "1:1:/d1/sub:sub;") || + !strstr(g_seen, "0:2:/d1/sub/y.c:y.c;")) + return 20; + g_seen[0] = 0; + g_n = 0; + if (nftw(R "/d1", note, 4, FTW_PHYS | FTW_DEPTH) || g_n != 4 || + !strstr(g_seen, "5:0:/d1:d1;") || + strstr(g_seen, "5:0:/d1:d1;") != g_seen + strlen(g_seen) - 11) + return 21; + /* FTW_PHYS: the link is a link; without it, a directory */ + g_seen[0] = 0; + g_n = 0; + nftw(R, note, 4, FTW_PHYS); + if (!strstr(g_seen, "4:1:/link:link;")) + return 22; + g_n = 0; + if (nftw(R, prune, 4, FTW_PHYS | FTW_ACTIONRETVAL) || g_n != 10) + return 23; + if (ftw(R "/d1", old_fn, 4) || g_old != 2) + return 24; + if (nftw(R "/nothere", note, 4, 0) != -1 || errno != ENOENT) + return 25; + return 0; +} + +static int logs(void) +{ + int p[2]; + if (pipe(p)) + return 30; + int saved = dup(2); + dup2(p[1], 2); + openlog("gtest", LOG_PERROR, LOG_USER); + errno = ENOENT; + syslog(LOG_ERR, "x=%d %m", 5); + setlogmask(LOG_UPTO(LOG_WARNING)); + syslog(LOG_INFO, "not shown"); + closelog(); + dup2(saved, 2); + close(p[1]); + char b[200] = { 0 }; + ssize_t n = read(p[0], b, sizeof b - 1); + close(p[0]); + return n > 0 && !strcmp(b, "gtest: x=5 No such file or directory\n") ? 0 : 31; +} + +#endif + +int main(void) +{ +#ifdef EMBCC_TEST_HAS_FS + tree(); + int r; + if ((r = globs()) || (r = walks()) || (r = logs())) + return r; +#else + /* no filesystem: what needs none -- the pattern test, and a pattern + * that matches nothing kept by GLOB_NOCHECK */ + glob_t g; + if (!glob_pattern_p("a[bc]", 1) || glob_pattern_p("a\\*", 1) || + glob("/x/*.c", GLOB_NOCHECK, NULL, &g) || g.gl_pathc != 1 || + strcmp(g.gl_pathv[0], "/x/*.c")) + return 1; + globfree(&g); +#endif + return 42; +} diff --git a/tests/exec/posix-net.c b/tests/exec/posix-net.c new file mode 100644 index 000000000..4b91d7095 --- /dev/null +++ b/tests/exec/posix-net.c @@ -0,0 +1,233 @@ +/* POSIX's networking: addresses as text both ways (inet_pton strict, + * inet_aton lenient, inet_ntop as RFC 5952 and glibc write IPv6), and, + * where there is a kernel with sockets, a socketpair, poll and select, + * TCP and a local datagram socket over loopback, and getaddrinfo and + * getnameinfo from the hosts and services files. + * + * glibc 2.36 gave the same answers on the same kernel. The program writes + * its own /etc/hosts and /etc/services, and -- booted straight to /init, + * with no init system to do it -- brings the loopback interface up. */ +// expect-exit: 42 +// needs-libc: POSIX's networking, on lib/libc's backends +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int text(void) +{ + static const char *const in[] = { + "::", "::1", "1::", "2001:db8::1", "fe80::1:2:3:4", "::ffff:1.2.3.4", + "::1.2.3.4", "1:0:0:2:0:0:0:3", "2001:DB8:0:0:1:0:0:1", + "1:2:3:4:5:6:7:8" }; + static const char *const out[] = { + "::", "::1", "1::", "2001:db8::1", "fe80::1:2:3:4", "::ffff:1.2.3.4", + "::1.2.3.4", "1:0:0:2::3", "2001:db8::1:0:0:1", "1:2:3:4:5:6:7:8" }; + unsigned char a[16]; + char t[INET6_ADDRSTRLEN]; + for (size_t i = 0; i < sizeof in / sizeof *in; i++) + if (inet_pton(AF_INET6, in[i], a) != 1 || + !inet_ntop(AF_INET6, a, t, sizeof t) || strcmp(t, out[i])) + return 1 + (int)i; + static const char *const bad6[] = { "1:::2", "1:2:3:4:5:6:7:8:9", + "12345::", ":1", "1:", "" }; + for (size_t i = 0; i < sizeof bad6 / sizeof *bad6; i++) + if (inet_pton(AF_INET6, bad6[i], a) != 0) + return 20 + (int)i; + static const char *const four[] = { "1.2.3.4", "01.2.3.4", "256.1.1.1", + "1.2.3", "1.2.3.4.5", " 1.2.3.4" }; + static const int ok4[] = { 1, 0, 0, 0, 0, 0 }; + for (size_t i = 0; i < sizeof four / sizeof *four; i++) + if (inet_pton(AF_INET, four[i], a) != ok4[i]) + return 30 + (int)i; + errno = 0; + if (inet_pton(99, "1.2.3.4", a) != -1 || errno != EAFNOSUPPORT) + return 36; + if (inet_ntop(AF_INET6, a, t, 3) || errno != ENOSPC) + return 37; + struct in_addr ia; + if (!inet_aton("127.1", &ia) || strcmp(inet_ntoa(ia), "127.0.0.1") || + !inet_aton("0x7f.0.0.010", &ia) || strcmp(inet_ntoa(ia), "127.0.0.8") || + inet_aton("1.2.3.4x", &ia) || ntohl(inet_addr("10.0.0.1")) != 0x0a000001 || + inet_addr("nope") != INADDR_NONE) + return 38; + if (htons(0x1234) != (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ ? 0x1234 + : 0x3412)) + return 39; + return 0; +} + +#ifdef EMBCC_TEST_HAS_FS +static void lo_up(void) +{ + struct { char name[16]; short flags; char pad[22]; } ifr = { "lo", 0, { 0 } }; + int s = socket(AF_INET, SOCK_DGRAM, 0); + ioctl(s, 0x8913, &ifr); /* SIOCGIFFLAGS */ + ifr.flags |= 1; /* IFF_UP */ + ioctl(s, 0x8914, &ifr); /* SIOCSIFFLAGS */ + close(s); +} + +static int sockets(void) +{ + int sv[2]; + char b[32] = { 0 }; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) + return 50; + struct pollfd pf = { sv[0], POLLIN, 0 }; + if (poll(&pf, 1, 0) != 0 || send(sv[1], "ping", 4, 0) != 4 || + poll(&pf, 1, 1000) != 1 || !(pf.revents & POLLIN)) + return 51; + fd_set rs; + FD_ZERO(&rs); + FD_SET(sv[0], &rs); + struct timeval tmo = { 1, 0 }; + if (select(sv[0] + 1, &rs, NULL, NULL, &tmo) != 1 || !FD_ISSET(sv[0], &rs) || + recv(sv[0], b, sizeof b, 0) != 4 || strcmp(b, "ping")) + return 52; + close(sv[0]); + close(sv[1]); + + lo_up(); + int ls = socket(AF_INET, SOCK_STREAM, 0), one = 1; + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); + struct sockaddr_in sin; + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + socklen_t sl = sizeof sin; + if (bind(ls, (struct sockaddr *)&sin, sizeof sin) || listen(ls, 4) || + getsockname(ls, (struct sockaddr *)&sin, &sl) || !sin.sin_port) + return 53; + int cs = socket(AF_INET, SOCK_STREAM, 0); + if (connect(cs, (struct sockaddr *)&sin, sizeof sin)) + return 54; + int as = accept(ls, NULL, NULL); + memset(b, 0, sizeof b); + if (as < 0 || send(cs, "over tcp", 8, 0) != 8 || + recv(as, b, sizeof b, 0) != 8 || strcmp(b, "over tcp")) + return 55; + int ty; + socklen_t tl = sizeof ty; + if (getsockopt(cs, SOL_SOCKET, SO_TYPE, &ty, &tl) || ty != SOCK_STREAM) + return 56; + close(cs); + close(as); + close(ls); + + int us = socket(AF_UNIX, SOCK_DGRAM, 0), uc = socket(AF_UNIX, SOCK_DGRAM, 0); + struct sockaddr_un sun = { AF_UNIX, "/tmp/sock" }; + memset(b, 0, sizeof b); + if (bind(us, (struct sockaddr *)&sun, sizeof sun) || + sendto(uc, "dgram", 5, 0, (struct sockaddr *)&sun, sizeof sun) != 5 || + recvfrom(us, b, sizeof b, 0, NULL, NULL) != 5 || strcmp(b, "dgram")) + return 57; + close(us); + close(uc); + return 0; +} + +static int lookups(void) +{ + mkdir("/etc", 0755); + FILE *f = fopen("/etc/hosts", "w"); + if (!f) + return 58; + fputs("127.0.0.1 localhost\n10.1.2.3 alpha.example alpha a1\n" + "2001:db8::5 six.example\n", f); + fclose(f); + if (!(f = fopen("/etc/services", "w"))) + return 59; + fputs("http 80/tcp www\nhttp 80/udp\nmyservice 4321/tcp\n", f); + fclose(f); + + struct addrinfo hints, *res, *ai; + memset(&hints, 0, sizeof hints); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + if (getaddrinfo("a1", "http", &hints, &res)) + return 60; + struct sockaddr_in *s4 = (struct sockaddr_in *)res->ai_addr; + if (res->ai_family != AF_INET || ntohs(s4->sin_port) != 80 || + ntohl(s4->sin_addr.s_addr) != 0x0a010203 || res->ai_next || + strcmp(res->ai_canonname, "alpha.example")) + return 61; + freeaddrinfo(res); + memset(&hints, 0, sizeof hints); + int n = 0; + if (getaddrinfo("six.example", "4321", &hints, &res)) + return 62; + for (ai = res; ai; ai = ai->ai_next) + n += ai->ai_family == AF_INET6; + freeaddrinfo(res); + if (n != 3) /* stream, datagram and raw */ + return 63; + hints.ai_family = AF_INET; + hints.ai_flags = AI_PASSIVE; + if (getaddrinfo(NULL, "myservice", &hints, &res)) + return 64; + s4 = (struct sockaddr_in *)res->ai_addr; + if (s4->sin_addr.s_addr != 0 || ntohs(s4->sin_port) != 4321) + return 65; + freeaddrinfo(res); + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo("alpha", NULL, &hints, &res) != EAI_NONAME) + return 66; + hints.ai_flags = 0; + if (getaddrinfo("alpha", "nosuchsvc", &hints, &res) != EAI_SERVICE) + return 67; + hints.ai_family = 99; + if (getaddrinfo("alpha", NULL, &hints, &res) != EAI_FAMILY || + strcmp(gai_strerror(EAI_NONAME), "Name or service not known")) + return 68; + hints.ai_family = AF_UNSPEC; + if (getaddrinfo("localhost", NULL, &hints, &res)) + return 69; + freeaddrinfo(res); + + struct sockaddr_in back = { AF_INET, htons(80), { htonl(0x0a010203) }, { 0 } }; + char host[64], serv[16]; + if (getnameinfo((struct sockaddr *)&back, sizeof back, host, sizeof host, + serv, sizeof serv, 0) || + strcmp(host, "alpha.example") || strcmp(serv, "http")) + return 70; + if (getnameinfo((struct sockaddr *)&back, sizeof back, host, sizeof host, + serv, sizeof serv, NI_NUMERICHOST | NI_NUMERICSERV) || + strcmp(host, "10.1.2.3") || strcmp(serv, "80")) + return 71; + struct hostent *he = gethostbyname("alpha"); + if (!he || strcmp(he->h_name, "alpha.example") || + ntohl(*(in_addr_t *)he->h_addr) != 0x0a010203) + return 72; + struct servent *se = getservbyname("http", "tcp"); + if (!se || ntohs((uint16_t)se->s_port) != 80) + return 73; + return 0; +} +#endif + +int main(void) +{ + int r; + if ((r = text())) + return r; +#ifdef EMBCC_TEST_HAS_FS + if ((r = sockets()) || (r = lookups())) + return r; +#else + errno = 0; + if (socket(AF_INET, SOCK_STREAM, 0) != -1 || errno != ENOSYS) + return 80; +#endif + return 42; +} diff --git a/tests/exec/posix-process.c b/tests/exec/posix-process.c new file mode 100644 index 000000000..da37e289d --- /dev/null +++ b/tests/exec/posix-process.c @@ -0,0 +1,180 @@ +/* POSIX's processes: fork and the wait family, the exec family's PATH + * search, posix_spawn with file actions and posix_spawnp's script rule, + * system and popen, and the ids. + * + * The program is its own child and its own shell: run as "sh -c CMD" it + * does what the few commands below ask, so tests/golden/linux-libc-exec.sh + * need only copy it to /bin/sh in the guest. glibc 2.36 gave the same + * results on the same kernel. Where there is no kernel to start a + * process (the boards), each call fails with ENOSYS. */ +// expect-exit: 42 +// needs-libc: POSIX's process calls, on lib/libc's backends +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern char **environ; + +/* "exit N", "echo WORDS", "cat" -- the shell this test needs */ +static int as_shell(const char *cmd) +{ + if (!strncmp(cmd, "exit ", 5)) + return atoi(cmd + 5); + if (!strncmp(cmd, "echo ", 5)) { + printf("%s\n", cmd + 5); + fflush(stdout); + return 0; + } + if (!strcmp(cmd, "cat")) { + char b[64]; + long n; + while ((n = read(0, b, sizeof b)) > 0) + write(1, b, (size_t)n); + return 0; + } + return 127; +} + +#ifdef EMBCC_TEST_HAS_FS +static int run(void) +{ + int st; + fflush(stdout); + pid_t c = fork(); + if (c == 0) + _exit(23); + if (c < 0 || waitpid(c, &st, 0) != c || !WIFEXITED(st) || + WEXITSTATUS(st) != 23) + return 1; + c = fork(); + if (c == 0) { + pause(); + _exit(0); + } + kill(c, SIGTERM); + if (waitpid(c, &st, 0) != c || !WIFSIGNALED(st) || WTERMSIG(st) != SIGTERM) + return 2; + errno = 0; + if (wait(&st) != -1 || errno != ECHILD) + return 3; + c = fork(); + if (c == 0) { + usleep(200000); + _exit(4); + } + if (waitpid(c, &st, WNOHANG) != 0 || waitpid(c, &st, 0) != c || + WEXITSTATUS(st) != 4) + return 4; + + char *av[] = { "sh", "-c", "exit 9", NULL }; + pid_t sp; + if (posix_spawn(&sp, "/bin/sh", NULL, NULL, av, environ) || + waitpid(sp, &st, 0) != sp || WEXITSTATUS(st) != 9) + return 5; + /* a program that is not there is posix_spawn's error, not a child's + * exit status of 127 */ + if (posix_spawn(&sp, "/nonexistent", NULL, NULL, av, environ) != ENOENT) + return 6; + posix_spawn_file_actions_t fa; + posix_spawn_file_actions_init(&fa); + posix_spawn_file_actions_addopen(&fa, 1, "/tmp/out", + O_WRONLY | O_CREAT | O_TRUNC, 0644); + char *av2[] = { "sh", "-c", "echo spawned output", NULL }; + setenv("PATH", "/bin", 1); + if (posix_spawnp(&sp, "sh", &fa, NULL, av2, environ) || + waitpid(sp, &st, 0) != sp) + return 7; + posix_spawn_file_actions_destroy(&fa); + /* a descriptor no process can have is EBADF from the call that names + * it (POSIX: negative, or OPEN_MAX or more), as glibc's is */ + posix_spawn_file_actions_init(&fa); + if (posix_spawn_file_actions_adddup2(&fa, 0, (int)sysconf(_SC_OPEN_MAX)) != + EBADF || + posix_spawn_file_actions_addclose(&fa, -1) != EBADF) + return 16; + posix_spawn_file_actions_destroy(&fa); + char b[64] = { 0 }; + FILE *f = fopen("/tmp/out", "r"); + if (!f || !fgets(b, sizeof b, f) || strcmp(b, "spawned output\n")) + return 8; + fclose(f); + + /* a script with no binary format: posix_spawnp runs /bin/sh on it */ + f = fopen("/bin/script7", "w"); + fputs("exit 7\n", f); + fclose(f); + chmod("/bin/script7", 0755); + char *av3[] = { "script7", NULL }; + if (posix_spawnp(&sp, "script7", NULL, NULL, av3, environ) || + waitpid(sp, &st, 0) != sp || WEXITSTATUS(st) != 7) + return 9; + + if (WEXITSTATUS(system("exit 3")) != 3 || system(NULL) != 1) + return 10; + f = popen("echo from popen", "r"); + memset(b, 0, sizeof b); + if (!f || !fgets(b, sizeof b, f) || strcmp(b, "from popen\n") || + WEXITSTATUS(pclose(f)) != 0) + return 11; + f = popen("cat", "w"); + if (!f || fputs("to cat\n", f) < 0 || pclose(f) != 0) + return 12; + + fflush(stdout); + c = fork(); + if (c == 0) { + char *ev[] = { "sh", "-c", "exit 11", NULL }; + execvp("sh", ev); + _exit(99); + } + if (waitpid(c, &st, 0) != c || WEXITSTATUS(st) != 11) + return 13; + errno = 0; + char *none[] = { "nosuchprogram", NULL }; + if (execvp("nosuchprogram", none) != -1 || errno != ENOENT) + return 14; + if (getpid() <= 0 || getppid() < 0) + return 15; + return 0; +} +#endif + +int main(int argc, char **argv) +{ +#ifdef EMBCC_TEST_HAS_FS + /* as the guest's /bin/sh -- only where there is a filesystem and a + * kernel to pass arguments: a board's startup promises no argc */ + if (argc >= 3 && !strcmp(argv[1], "-c")) + return as_shell(argv[2]); + /* argv[0] is the script's path when run as a script's interpreter */ + if (argc == 2 && strstr(argv[1], "script")) { + char line[64] = ""; + FILE *f = fopen(argv[1], "r"); + if (f && fgets(line, sizeof line, f)) + line[strcspn(line, "\n")] = 0; + return as_shell(line); + } + int r = run(); + if (r) + return r; +#else + (void)argc; + (void)argv; + char *av[] = { "x", NULL }; + pid_t pid; + errno = 0; + if (fork() != -1 || errno != ENOSYS) + return 20; + if (posix_spawn(&pid, "/bin/x", NULL, NULL, av, environ) != ENOSYS) + return 21; +#endif + return 42; +} diff --git a/tests/exec/posix-regex.c b/tests/exec/posix-regex.c new file mode 100644 index 000000000..4f4fab5bf --- /dev/null +++ b/tests/exec/posix-regex.c @@ -0,0 +1,166 @@ +/* : 88 patterns, flags and strings, each with glibc 2.36's + * answer -- its compile error, or no match, or the match and every + * group's offsets. Basic and extended syntax, the GNU extensions, + * REG_ICASE, REG_NEWLINE, REG_NOTBOL/NOTEOL, back-references, bracket + * classes, intervals, POSIX's leftmost-longest. The table was made by + * running the same calls against glibc and printing what it said. */ +// expect-exit: 42 +// needs-libc: , from lib/libc +#define _GNU_SOURCE 1 +#include +#include +#include +#include + +static const struct { + const char *pat; + int cflags; + const char *s; + int eflags; + int comp; /* regcomp's error, 0 for none */ + int n; /* groups reported, -1 for no match */ + int off[14]; +} cases[] = { + { "abc", 0, "xxabcxx", 0, 0, 1, { 2, 5 } }, + { "a*", 0, "baaa", 0, 0, 1, { 0, 0 } }, + { "a*", 0, "aaab", 0, 0, 1, { 0, 3 } }, + { "a\\(b*\\)c", 0, "xabbbcx", 0, 0, 2, { 1, 6, 2, 5 } }, + { "a(b*)c", REG_EXTENDED, "xabbbcx", 0, 0, 2, { 1, 6, 2, 5 } }, + { "(a|ab)(c|bcd)(d*)", REG_EXTENDED, "abcd", 0, 0, 4, { 0, 4, 0, 1, 1, 4, 4, 4 } }, + { "(a*)*", REG_EXTENDED, "aaa", 0, 0, 2, { 0, 3, 0, 3 } }, + { "(a*)+", REG_EXTENDED, "aaa", 0, 0, 2, { 0, 3, 0, 3 } }, + { "(a|b)*c", REG_EXTENDED, "ababc", 0, 0, 2, { 0, 5, 3, 4 } }, + { "x(a|b)*", REG_EXTENDED, "xabab", 0, 0, 2, { 0, 5, 4, 5 } }, + { "(wee|week)(knights|night)", REG_EXTENDED, "weeknights", 0, 0, 3, { 0, 10, 0, 3, 3, 10 } }, + { "(.*)(.*)", REG_EXTENDED, "abc", 0, 0, 3, { 0, 3, 0, 3, 3, 3 } }, + { "(.*)x(.*)", REG_EXTENDED, "axbxc", 0, 0, 3, { 0, 5, 0, 3, 4, 5 } }, + { "([a-c]+)([b-d]+)", REG_EXTENDED, "abcd", 0, 0, 3, { 0, 4, 0, 3, 3, 4 } }, + { "^abc$", 0, "abc", 0, 0, 1, { 0, 3 } }, + { "^abc$", 0, "xabc", 0, 0, -1, { 0 } }, + { "^b", REG_EXTENDED | REG_NEWLINE, "a\nb", 0, 0, 1, { 2, 3 } }, + { "a$", REG_EXTENDED | REG_NEWLINE, "a\nb", 0, 0, 1, { 0, 1 } }, + { "^b", REG_EXTENDED, "a\nb", 0, 0, -1, { 0 } }, + { "a.b", REG_EXTENDED | REG_NEWLINE, "a\nb", 0, 0, -1, { 0 } }, + { "a.b", REG_EXTENDED, "a\nb", 0, 0, 1, { 0, 3 } }, + { "a[^x]b", REG_EXTENDED | REG_NEWLINE, "a\nb", 0, 0, -1, { 0 } }, + { "^a", REG_EXTENDED, "abc", REG_NOTBOL, 0, -1, { 0 } }, + { "c$", REG_EXTENDED, "abc", REG_NOTEOL, 0, -1, { 0 } }, + { "\\(a\\)\\1", 0, "xaax", 0, 0, 2, { 1, 3, 1, 2 } }, + { "(a+)b\\1", REG_EXTENDED, "aaabaa", 0, 0, 2, { 1, 6, 1, 3 } }, + { "\\(.\\)\\1", 0, "abccd", 0, 0, 2, { 2, 4, 2, 3 } }, + { "a\\{2,3\\}", 0, "aaaa", 0, 0, 1, { 0, 3 } }, + { "a{2,3}", REG_EXTENDED, "aaaa", 0, 0, 1, { 0, 3 } }, + { "a{2}", REG_EXTENDED, "aaaa", 0, 0, 1, { 0, 2 } }, + { "a{,2}", REG_EXTENDED, "aaa", 0, 0, 1, { 0, 2 } }, + { "a{2,1}", REG_EXTENDED, "a", 0, 10, 0, { 0 } }, + { "a\\{1", 0, "a", 0, 9, 0, { 0 } }, + { "a{1", REG_EXTENDED, "a", 0, 9, 0, { 0 } }, + { "a{x", REG_EXTENDED, "a{x", 0, 9, 0, { 0 } }, + { "[[:alpha:]]+", REG_EXTENDED, "12abc34", 0, 0, 1, { 2, 5 } }, + { "[[:digit:][:space:]]+", REG_EXTENDED, "ab1 2 3c", 0, 0, 1, { 2, 7 } }, + { "[[:foo:]]", REG_EXTENDED, "a", 0, 4, 0, { 0 } }, + { "[a-]+", REG_EXTENDED, "x-a-", 0, 0, 1, { 1, 4 } }, + { "[]a]+", REG_EXTENDED, "x]a]", 0, 0, 1, { 1, 4 } }, + { "[^]a]", REG_EXTENDED, "]ab", 0, 0, 1, { 2, 3 } }, + { "[z-a]", REG_EXTENDED, "a", 0, 11, 0, { 0 } }, + { "[.-.]", REG_EXTENDED, "-", 0, 0, -1, { 0 } }, + { "[[=a=]]", REG_EXTENDED, "bab", 0, 0, 1, { 1, 2 } }, + { "[[.a.]-c]+", REG_EXTENDED, "xabcd", 0, 0, 1, { 1, 4 } }, + { "ABC", REG_ICASE, "xabcx", 0, 0, 1, { 1, 4 } }, + { "[a-c]+", REG_EXTENDED | REG_ICASE, "xABCx", 0, 0, 1, { 1, 4 } }, + { "(a)\\1", REG_EXTENDED | REG_ICASE, "aA", 0, 0, 2, { 0, 2, 0, 1 } }, + { "a+?", REG_EXTENDED, "aaa", 0, 0, 1, { 0, 3 } }, + { "*a", 0, "x*a", 0, 0, 1, { 1, 3 } }, + { "*a", REG_EXTENDED, "x*a", 0, 13, 0, { 0 } }, + { "^*a", 0, "*a", 0, 0, 1, { 0, 2 } }, + { "a**", 0, "aaa", 0, 13, 0, { 0 } }, + { "a\\+", 0, "aaa", 0, 0, 1, { 0, 3 } }, + { "a\\?b", 0, "b", 0, 0, 1, { 0, 1 } }, + { "a\\|b", 0, "xb", 0, 0, 1, { 1, 2 } }, + { "a|b|", REG_EXTENDED, "x", 0, 0, 1, { 0, 0 } }, + { "()", REG_EXTENDED, "x", 0, 0, 2, { 0, 0, 0, 0 } }, + { "\\", 0, "a foo b", 0, 0, 1, { 2, 5 } }, + { "\\bfoo", REG_EXTENDED, "xfoo foo", 0, 0, 1, { 5, 8 } }, + { "o\\B", REG_EXTENDED, "foo", 0, 0, 1, { 1, 2 } }, + { "\\w+", REG_EXTENDED, " ab_1 ", 0, 0, 1, { 2, 6 } }, + { "\\W+", REG_EXTENDED, "ab, cd", 0, 0, 1, { 2, 4 } }, + { "\\s+\\S", REG_EXTENDED, "a b", 0, 0, 1, { 1, 4 } }, + { "a\\`", REG_EXTENDED, "a", 0, 0, -1, { 0 } }, + { "\\'", REG_EXTENDED, "ab", 0, 0, 1, { 2, 2 } }, + { "(", REG_EXTENDED, "a", 0, 8, 0, { 0 } }, + { ")", REG_EXTENDED, "a)", 0, 0, 1, { 1, 2 } }, + { "a\\)", 0, "a", 0, 8, 0, { 0 } }, + { "\\(a", 0, "a", 0, 8, 0, { 0 } }, + { "[abc", REG_EXTENDED, "a", 0, 7, 0, { 0 } }, + { "a\\", REG_EXTENDED, "a", 0, 5, 0, { 0 } }, + { "\\1", REG_EXTENDED, "a", 0, 6, 0, { 0 } }, + { "(a)\\2", REG_EXTENDED, "a", 0, 6, 0, { 0 } }, + { "a||b", REG_EXTENDED, "b", 0, 0, 1, { 0, 1 } }, + { "(a|b)c|d", REG_EXTENDED, "xbc", 0, 0, 2, { 1, 3, 1, 2 } }, + { "x*", REG_EXTENDED, "", 0, 0, 1, { 0, 0 } }, + { "", REG_EXTENDED, "abc", 0, 0, 1, { 0, 0 } }, + { "$", REG_EXTENDED, "ab", 0, 0, 1, { 2, 2 } }, + { "(a)(b)?", REG_EXTENDED, "a", 0, 0, 3, { 0, 1, 0, 1, -1, -1 } }, + { "(a(b)?)+", REG_EXTENDED, "aba", 0, 0, 3, { 0, 3, 2, 3, 1, 2 } }, + { "(a*)b\\1", REG_EXTENDED, "aabaa", 0, 0, 2, { 0, 5, 0, 2 } }, + { "[[:upper:]]+", REG_ICASE | REG_EXTENDED, "abC", 0, 0, 1, { 0, 3 } }, + { "abc", REG_EXTENDED | REG_NOSUB, "xabc", 0, 0, 0, { 0 } }, + { "(.)(.)(.)(.)(.)(.)", REG_EXTENDED, "abcdefg", 0, 0, 6, { 0, 6, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5 } }, + { "\\(ab*\\)*c", 0, "abbabc", 0, 0, 2, { 0, 6, 3, 5 } }, + { "x.*y", REG_EXTENDED, "x\ny", 0, 0, 1, { 0, 3 } }, + { "[[:alnum:]_]+@[a-z]+\\.[a-z]{2,}", REG_EXTENDED, "mail me@host.org now", 0, 0, 1, { 5, 16 } }, +}; + +int main(void) +{ + for (unsigned i = 0; i < sizeof cases / sizeof *cases; i++) { + regex_t re; + int e = regcomp(&re, cases[i].pat, cases[i].cflags); + if (e != cases[i].comp) + return 1; + if (e) + continue; + regmatch_t m[7]; + memset(m, 0x7f, sizeof m); + int r = regexec(&re, cases[i].s, 7, m, cases[i].eflags); + if (cases[i].n < 0) { + if (r != REG_NOMATCH) + return 2; + } else if (r != 0) { + return 3; + } else if (!(cases[i].cflags & REG_NOSUB)) { + for (int g = 0; g < cases[i].n; g++) + if (m[g].rm_so != cases[i].off[2 * g] || + m[g].rm_eo != cases[i].off[2 * g + 1]) + return 4; + } + regfree(&re); + } + /* REG_STARTEND: the search starts at rm_so, but ^ is the string's */ + regex_t re; + regmatch_t m[1] = { { 1, 3 } }; + if (regcomp(&re, "^b", REG_EXTENDED) || + regexec(&re, "abc", 1, m, REG_STARTEND) != REG_NOMATCH) + return 5; + regfree(&re); + m[0].rm_so = 1; + m[0].rm_eo = 3; + if (regcomp(&re, "c", REG_EXTENDED) || + regexec(&re, "abc", 1, m, REG_STARTEND) || m[0].rm_so != 2 || + m[0].rm_eo != 3) + return 6; + regfree(&re); + /* regerror: the whole length, and a cut copy */ + char b[8]; + if (regerror(REG_EBRACK, NULL, b, sizeof b) != 31 || strcmp(b, "Unmatch")) + return 7; + /* and the "C" locale's own words, which glibc's are too */ + if (strcmp(nl_langinfo(D_FMT), "%m/%d/%y") || strcmp(nl_langinfo(DAY_1), "Sunday") || + strcmp(nl_langinfo(ABMON_12), "Dec") || strcmp(nl_langinfo(RADIXCHAR), ".") || + strcmp(nl_langinfo(YESEXPR), "^[yY]")) + return 8; + nl_catd cat = catopen("embcc-no-such-catalog", 0); + if (cat != (nl_catd)-1 || strcmp(catgets(cat, 1, 1, "kept"), "kept")) + return 9; + return 42; +} diff --git a/tests/exec/posix-signals.c b/tests/exec/posix-signals.c new file mode 100644 index 000000000..45d3b82ce --- /dev/null +++ b/tests/exec/posix-signals.c @@ -0,0 +1,186 @@ +/* POSIX's signals, in what every target can do: handlers by sigaction, + * raise and kill to oneself, the mask and the pending set, sigwait on a + * pending signal, SA_SIGINFO and SA_RESETHAND, the set functions, and + * strsignal's words (glibc's). + * + * The same program passes where the kernel delivers signals (Linux) and + * where lib/libc does it all itself (the boards): there raise is a call, + * a blocked signal waits in the pending set, and unblocking delivers it + * -- which is POSIX's behaviour, from either. Where the kernel delivers, + * alarm interrupts pause and abort ends a child with SIGABRT. */ +// expect-exit: 42 +// needs-libc: POSIX's signals, kernel-delivered or lib/libc's own +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t got, count, code; + +static void on_sig(int s) { got = s; count++; } + +static void on_info(int s, siginfo_t *i, void *u) +{ + (void)u; + got = s; + code = i->si_code; +} + +static int sets(void) +{ + sigset_t s; + if (sigemptyset(&s) || sigismember(&s, SIGINT) != 0 || + sigaddset(&s, SIGINT) || sigismember(&s, SIGINT) != 1 || + sigaddset(&s, 64) || sigismember(&s, 64) != 1 || + sigdelset(&s, SIGINT) || sigismember(&s, SIGINT) != 0) + return 1; + errno = 0; + if (sigaddset(&s, 0) != -1 || errno != EINVAL || + sigismember(&s, 65) != -1) + return 2; + if (sigfillset(&s) || sigismember(&s, SIGTERM) != 1) + return 3; + return 0; +} + +static int handlers(void) +{ + struct sigaction sa, old; + memset(&sa, 0, sizeof sa); + sa.sa_handler = on_sig; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGUSR1, &sa, &old) || old.sa_handler != SIG_DFL) + return 10; + got = 0; + if (raise(SIGUSR1) || got != SIGUSR1) + return 11; + got = 0; + if (kill(getpid(), SIGUSR1) || got != SIGUSR1) + return 12; + + /* blocked: pending, and delivered when unblocked */ + sigset_t m, om, pend; + sigemptyset(&m); + sigaddset(&m, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &m, &om)) + return 13; + got = 0; + raise(SIGUSR1); + if (got || sigpending(&pend) || sigismember(&pend, SIGUSR1) != 1) + return 14; + if (sigprocmask(SIG_SETMASK, &om, NULL) || got != SIGUSR1) + return 15; + if (sigpending(&pend) || sigismember(&pend, SIGUSR1) != 0) + return 16; + + /* sigwait takes a pending signal instead of a handler running */ + sigprocmask(SIG_BLOCK, &m, NULL); + raise(SIGUSR1); + got = 0; + int which = 0; + if (sigwait(&m, &which) || which != SIGUSR1 || got) + return 17; + sigprocmask(SIG_UNBLOCK, &m, NULL); + + /* SA_SIGINFO: the three-argument handler, and how it was sent */ + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = on_info; + sa.sa_flags = SA_SIGINFO; + sigaction(SIGUSR2, &sa, NULL); + got = 0; + code = 99; + kill(getpid(), SIGUSR2); + if (got != SIGUSR2 || code != SI_USER) + return 18; + + /* SA_RESETHAND: once, then the default -- here, ignored, since the + * default for SIGWINCH is to ignore it */ + memset(&sa, 0, sizeof sa); + sa.sa_handler = on_sig; + sa.sa_flags = SA_RESETHAND; + sigaction(SIGWINCH, &sa, NULL); + count = 0; + raise(SIGWINCH); + raise(SIGWINCH); + if (count != 1 || sigaction(SIGWINCH, NULL, &old) || + old.sa_handler != SIG_DFL) + return 19; + + /* SIG_IGN: nothing at all */ + signal(SIGUSR1, SIG_IGN); + got = 0; + raise(SIGUSR1); + if (got) + return 20; + + /* C's signal returns the previous handler */ + if (signal(SIGUSR1, on_sig) != SIG_IGN || signal(SIGUSR1, SIG_DFL) != on_sig) + return 21; + errno = 0; + if (sigaction(SIGKILL, &sa, NULL) != -1 || errno != EINVAL || + signal(0, on_sig) != SIG_ERR) + return 22; + return 0; +} + +static int names(void) +{ + if (strcmp(strsignal(SIGINT), "Interrupt") || + strcmp(strsignal(SIGSEGV), "Segmentation fault") || + strcmp(strsignal(SIGRTMIN + 6), "Real-time signal 6") || + strcmp(strsignal(99), "Unknown signal 99")) + return 30; + return 0; +} + +#ifdef EMBCC_TEST_HAS_FS +static void on_abrt(int s) { (void)s; count = 100; } + +/* what only a kernel that delivers can do */ +static int kernel(void) +{ + signal(SIGALRM, on_sig); + got = 0; + alarm(1); + pause(); + if (got != SIGALRM) + return 40; + pid_t c = fork(); + if (c == 0) + abort(); + int st; + if (waitpid(c, &st, 0) != c || !WIFSIGNALED(st) || WTERMSIG(st) != SIGABRT) + return 41; + /* a handler that returns does not stop abort */ + c = fork(); + if (c == 0) { + signal(SIGABRT, on_abrt); + abort(); + } + if (waitpid(c, &st, 0) != c || !WIFSIGNALED(st) || WTERMSIG(st) != SIGABRT) + return 43; + struct timespec zero = { 0, 0 }; + sigset_t m; + sigemptyset(&m); + sigaddset(&m, SIGUSR1); + errno = 0; + if (sigtimedwait(&m, NULL, &zero) != -1 || errno != EAGAIN) + return 42; + return 0; +} +#endif + +int main(void) +{ + int r; + if ((r = sets()) || (r = handlers()) || (r = names())) + return r; +#ifdef EMBCC_TEST_HAS_FS + if ((r = kernel())) + return r; +#endif + return 42; +} diff --git a/tests/exec/posix-stdio.c b/tests/exec/posix-stdio.c new file mode 100644 index 000000000..7ece2f140 --- /dev/null +++ b/tests/exec/posix-stdio.c @@ -0,0 +1,260 @@ +/* POSIX's streams that are not files, and the stdio functions beyond C's: + * fmemopen (its NUL rules are glibc's since 2.22), open_memstream, + * fopencookie, getline and getdelim, asprintf and dprintf, flockfile -- + * which nests, so a thread holding a stream may still printf to it -- + * and more streams open at once than FOPEN_MAX. + * + * The expected values are glibc 2.36's but for one: open_memstream's + * length after a write in the middle, where glibc answers what POSIX says + * it must not (see `grows`). */ +// expect-exit: 42 +// needs-libc: POSIX stdio the harnesses' newlib has not got all of +#define _GNU_SOURCE 1 +#include +#include +#include +#include + +static int same(const char *got, const char *want, size_t n) +{ + return memcmp(got, want, n) == 0; +} + +static int fixed(void) +{ + char buf[16]; + /* "w": a NUL after the data, and never past the size */ + memset(buf, 'x', sizeof buf); + FILE *f = fmemopen(buf, 8, "w"); + if (!f) + return 1; + fputs("abc", f); + fflush(f); + if (!same(buf, "abc\0xxxxxx", 10)) + return 2; + fputs("defghijk", f); + fflush(f); + if (!same(buf, "abcdefg\0xx", 10) || ftell(f) != 8) + return 3; + fclose(f); + + /* "a": from the first NUL */ + memset(buf, 'x', sizeof buf); + strcpy(buf, "hi"); + f = fmemopen(buf, 8, "a"); + fputs("yo", f); + fclose(f); + if (!same(buf, "hiyo\0xxxxx", 10)) + return 4; + + /* "r": the whole buffer, seekable within it and not beyond */ + strcpy(buf, "0123456789"); + f = fmemopen(buf, 10, "r"); + char line[32]; + fseek(f, 4, SEEK_SET); + if (fread(line, 1, 3, f) != 3 || !same(line, "456", 3) || ftell(f) != 7) + return 5; + errno = 0; + if (fseek(f, 11, SEEK_SET) != -1 || errno != EINVAL) + return 6; + fseek(f, -2, SEEK_END); + if (fgetc(f) != '8' || fgetc(f) != '9' || fgetc(f) != EOF || !feof(f)) + return 7; + fclose(f); + + /* "w+": written, then read back through the same stream */ + memset(buf, 'x', sizeof buf); + f = fmemopen(buf, 12, "w+"); + fprintf(f, "n=%d", 42); + rewind(f); + int v = 0; + if (fscanf(f, "n=%d", &v) != 1 || v != 42) + return 8; + fclose(f); + if (!same(buf, "n=42\0xxxxxxx", 12)) + return 9; + + /* no buffer: one of its own, freed at fclose */ + f = fmemopen(NULL, 16, "w+"); + fputs("scratch", f); + rewind(f); + if (!fgets(line, sizeof line, f) || strcmp(line, "scratch") != 0) + return 10; + fclose(f); + return 0; +} + +static int grows(void) +{ + char *p = NULL; + size_t n = 0; + FILE *f = open_memstream(&p, &n); + if (!f) + return 20; + fprintf(f, "hello %s", "world"); + fflush(f); + if (strcmp(p, "hello world") != 0 || n != 11) + return 21; + fseek(f, 2, SEEK_SET); + fflush(f); + if (n != 2) /* the position, not the length */ + return 22; + fputs("LL", f); + /* POSIX: a write moves the length only when it passes it, so the end + * is still 11. (glibc 2.36 answers 4.) */ + fseek(f, 0, SEEK_END); + fflush(f); + if (strcmp(p, "heLLo world") != 0 || n != 11) + return 23; + fseek(f, 15, SEEK_SET); /* past the end: a gap of zeros */ + fputc('!', f); + fclose(f); + if (n != 16 || !same(p, "heLLo world\0\0\0\0!", 17)) + return 24; + free(p); + return 0; +} + +static int lines(void) +{ + /* "one\n", "t\0o\n" (a NUL inside a line), 300 letters, "last" */ + static char text[400]; + size_t tn = 0; + memcpy(text + tn, "one\nt\0o\n", 8); + tn += 8; + for (int i = 0; i < 300; i++) + text[tn++] = (char)('a' + i % 26); + text[tn++] = '\n'; + memcpy(text + tn, "last", 4); + tn += 4; + FILE *f = fmemopen(text, tn, "r"); + char *lp = NULL; + size_t cap = 0; + static const ssize_t want[] = { 4, 4, 301, 4 }; + for (int i = 0; i < 4; i++) + if (getline(&lp, &cap, f) != want[i]) + return 30 + i; + if (memcmp(lp, "last", 5) != 0 || cap < 302) + return 34; + if (getline(&lp, &cap, f) != -1 || !feof(f)) + return 35; + rewind(f); + if (getdelim(&lp, &cap, 'e', f) != 3 || strcmp(lp, "one") != 0) + return 36; + fclose(f); + free(lp); + + /* a NULL line pointer: getline allocates, even at end of file */ + f = fmemopen((char[]){ "x" }, 1, "r"); + lp = NULL; + cap = 0; + if (getline(&lp, &cap, f) != 1 || getline(&lp, &cap, f) != -1 || !lp) + return 37; + free(lp); + fclose(f); + return 0; +} + +static int formatted(void) +{ + char *s = NULL; + if (asprintf(&s, "%05d|%-4s|%x", 42, "ab", 255) != 13 || + strcmp(s, "00042|ab |ff") != 0) + return 40; + free(s); + fflush(stdout); + if (dprintf(1, "dprintf %d\n", 7) != 10) + return 41; + return 0; +} + +struct ck { char data[64]; size_t len, pos; int closed; }; + +static ssize_t ck_read(void *c, char *b, size_t n) +{ + struct ck *k = c; + if (n > k->len - k->pos) + n = k->len - k->pos; + memcpy(b, k->data + k->pos, n); + k->pos += n; + return (ssize_t)n; +} + +static ssize_t ck_write(void *c, const char *b, size_t n) +{ + struct ck *k = c; + for (size_t i = 0; i < n; i++) + k->data[k->len++] = (char)(b[i] >= 'a' && b[i] <= 'z' ? b[i] - 32 : b[i]); + return (ssize_t)n; +} + +static int ck_close(void *c) +{ + ((struct ck *)c)->closed = 1; + return 0; +} + +static int cookies(void) +{ + struct ck k = { "cookie data\nsecond\n", 19, 0, 0 }; + cookie_io_functions_t io = { ck_read, ck_write, NULL, ck_close }; + FILE *f = fopencookie(&k, "r", io); + char line[32]; + if (!f || !fgets(line, sizeof line, f) || strcmp(line, "cookie data\n")) + return 50; + errno = 0; + if (fileno(f) != -1 || errno != EBADF || fseek(f, 0, SEEK_SET) != -1) + return 51; + fclose(f); + if (!k.closed) + return 52; + struct ck w = { "", 0, 0, 0 }; + f = fopencookie(&w, "w", io); + fprintf(f, "upper %d", 9); + fclose(f); + if (w.len != 7 || !same(w.data, "UPPER 9", 7)) + return 53; + return 0; +} + +static int locking(void) +{ + flockfile(stdout); + flockfile(stdout); + if (ftrylockfile(stdout) != 0) + return 60; + printf("nested ok\n"); /* takes the lock a fourth time */ + funlockfile(stdout); + funlockfile(stdout); + funlockfile(stdout); + putc_unlocked('\n', stdout); + return 0; +} + +static int many(void) +{ + /* more than FOPEN_MAX: the table is a minimum, not a limit */ + static char mb[40][4]; + FILE *s[40]; + for (int i = 0; i < 40; i++) { + s[i] = fmemopen(mb[i], 4, "w"); + if (!s[i]) + return 70; + fprintf(s[i], "%d", i); + } + for (int i = 0; i < 40; i++) + fclose(s[i]); + if (strcmp(mb[0], "0") != 0 || strcmp(mb[39], "39") != 0) + return 71; + return 0; +} + +int main(void) +{ + int r; + if ((r = fixed()) || (r = grows()) || (r = lines()) || + (r = formatted()) || (r = cookies()) || (r = locking()) || + (r = many())) + return r; + return 42; +} diff --git a/tests/exec/posix-strings.c b/tests/exec/posix-strings.c new file mode 100644 index 000000000..c95b267b8 --- /dev/null +++ b/tests/exec/posix-strings.c @@ -0,0 +1,470 @@ +/* POSIX's string, environment and search functions, and the small + * modules: strtok_r and strsep, strlcpy and strlcat, memmem and its + * neighbours, strerror_r (XSI's), basename and dirname, fnmatch, getopt + * and getopt_long (which permute, as glibc's and newlib's do), qsort_r, + * the random and *rand48 generators (glibc's sequences, which programs + * print and tests compare), tsearch and hsearch, setenv and its family. + * + * Every expected value here was what glibc 2.36 printed for the same + * calls, except where a comment says otherwise. */ +// expect-exit: 42 +// needs-libc: POSIX functions the harnesses' newlib has not got, or gives other sequences for +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include + +static int strings(void) +{ + char buf[32], *save, *t, out[64] = ""; + strcpy(buf, ",,a,b,,c,"); + for (t = strtok_r(buf, ",", &save); t; t = strtok_r(NULL, ",", &save)) + strcat(strcat(strcat(out, "<"), t), ">"); + if (strcmp(out, "") != 0) + return 1; + strcpy(buf, ",,a,b,,c,"); + out[0] = 0; + char *sp = buf; + while ((t = strsep(&sp, ","))) + strcat(strcat(strcat(out, "<"), t), ">"); + if (strcmp(out, "<><><><>") != 0) + return 2; + + /* BSD's answers: the length it tried for, so >= size means cut */ + char small[6]; + if (strlcpy(small, "hello world", sizeof small) != 11 || + strcmp(small, "hello") != 0) + return 3; + strcpy(small, "ab"); + if (strlcat(small, "cdefgh", sizeof small) != 8 || + strcmp(small, "abcde") != 0 || strlcat(small, "xyz", 3) != 6) + return 4; + + const char *hay = "abcabcabd"; + if ((char *)memmem(hay, 9, "abd", 3) - hay != 6 || + memmem(hay, 9, "", 0) != hay || memmem(hay, 9, "abe", 3) || + memmem(hay, 2, "abc", 3)) + return 5; + if (strcmp(strcasestr("Hello World", "WORLD"), "World") != 0 || + (char *)memrchr(hay, 'a', 9) - hay != 6 || + strchrnul(hay, 'z') - hay != 9 || strchrnul(hay, 'c') - hay != 2) + return 6; + + char eb[8], eb2[64]; + if (strerror_r(ENOENT, eb, sizeof eb) != ERANGE || + strcmp(eb, "No such") != 0 || + strerror_r(12345, eb2, sizeof eb2) != EINVAL || + strcmp(eb2, "Unknown error 12345") != 0 || + strerror_r(ENOENT, eb2, sizeof eb2) != 0) + return 7; + char secret[8] = "hunter2"; + explicit_bzero(secret, sizeof secret); + for (int i = 0; i < 8; i++) + if (secret[i]) + return 8; + return 0; +} + +static int paths(void) +{ + static const char *const in[] = { "/usr/lib/", "usr", "/", "//", "a/b//", + "", "///a", "a//b", ".", "..", "//a" }; + static const char *const base[] = { "lib", "usr", "/", "/", "b", ".", + "a", "b", ".", "..", "a" }; + static const char *const dir[] = { "/usr", ".", "/", "//", "a", ".", "/", + "a", ".", ".", "//" }; + for (size_t i = 0; i < sizeof in / sizeof *in; i++) { + char a[16], b[16]; + strcpy(a, in[i]); + strcpy(b, in[i]); + if (strcmp(basename(a), base[i]) != 0) + return 10 + (int)i; + if (strcmp(dirname(b), dir[i]) != 0) + return 30 + (int)i; + } + if (strcmp(basename(NULL), ".") != 0 || strcmp(dirname(NULL), ".") != 0) + return 49; + return 0; +} + +static int globs(void) +{ + static const struct { const char *p, *s; int f, want; } t[] = { + { "*.c", "main.c", 0, 0 }, + { "*.c", "dir/main.c", FNM_PATHNAME, 1 }, + { "*/*.c", "dir/main.c", FNM_PATHNAME, 0 }, + { "*", ".hidden", FNM_PERIOD, 1 }, + { ".*", ".hidden", FNM_PERIOD, 0 }, + { "*", "a/.b", FNM_PERIOD, 0 }, + { "*/*", "a/.b", FNM_PERIOD | FNM_PATHNAME, 1 }, + { "?", "/", FNM_PATHNAME, 1 }, + { "[a-c]x", "bx", 0, 0 }, { "[!a-c]x", "bx", 0, 1 }, + { "[]]", "]", 0, 0 }, { "[!]]", "a", 0, 0 }, + { "[[:digit:]]*", "7up", 0, 0 }, { "[[:alpha:][:digit:]]", "_", 0, 1 }, + { "\\*", "*", 0, 0 }, { "\\*", "\\*", FNM_NOESCAPE, 0 }, + { "a[", "a[", 0, 0 }, { "a[b", "ab", 0, 1 }, + { "ABC", "abc", FNM_CASEFOLD, 0 }, { "[A-C]", "b", FNM_CASEFOLD, 0 }, + { "dir", "dir/file", FNM_LEADING_DIR, 0 }, + /* one backtracking point: linear, where a recursive matcher + * takes exponential time on this */ + { "*a*a*a*a*a*a*a*a*b", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + 0, 1 }, + { "a*b*c", "aXbYc", 0, 0 }, { "a*b*c", "aXbYcZ", 0, 1 }, + { "", "", 0, 0 }, { "[a-]", "-", 0, 0 }, { "[-a]", "-", 0, 0 }, + { "[\\]]", "]", 0, 0 }, { "x[[.a.]]", "xa", 0, 0 }, + { "[[=b=]]", "b", 0, 0 }, + { "*\\", "a\\", 0, 1 }, /* a trailing '\' matches nothing */ + { "[[:upper:]]", "a", FNM_CASEFOLD, 1 }, /* classes do not fold */ + { "a/*", "a/b/c", FNM_PATHNAME, 1 }, { "a/*/c", "a/b/c", FNM_PATHNAME, 0 }, + { "**", "a/b", FNM_PATHNAME, 1 }, + }; + for (size_t i = 0; i < sizeof t / sizeof *t; i++) + if ((fnmatch(t[i].p, t[i].s, t[i].f) != 0) != t[i].want) + return 50 + (int)i; + return 0; +} + +/* Runs one option string over an argument vector and records what was + * seen, as "c:arg" items, then "|" and the operands in argv order. */ +static char seen[256]; +static void parse(int argc, const char *const *av, const char *opts, + const struct option *lo) +{ + char *argv[16]; + for (int i = 0; i < argc; i++) + argv[i] = (char *)av[i]; + argv[argc] = NULL; + optind = 0; + opterr = 0; + seen[0] = 0; + int c; + while ((c = lo ? getopt_long(argc, argv, opts, lo, NULL) + : getopt(argc, argv, opts)) != -1) { + char item[32]; + snprintf(item, sizeof item, "%c:%s ", c > 32 ? c : '0' + c, + optarg ? optarg : (c == '?' || c == ':') ? + (char[]){ (char)optopt, 0 } : ""); + strcat(seen, item); + } + strcat(seen, "|"); + for (int i = optind; i < argc; i++) + strcat(strcat(seen, " "), argv[i]); +} + +static int options(void) +{ + static const char *const a1[] = { "p", "-ab", "-c", "arg", "-dval", "--", + "-x", "f" }; + parse(8, a1, "abc:d:", NULL); + if (strcmp(seen, "a: b: c:arg d:val | -x f") != 0) + return 90; + static const char *const a2[] = { "p", "file", "-a", "-c", "x", "g" }; + parse(6, a2, "ac:", NULL); /* permuted, as glibc */ + if (strcmp(seen, "a: c:x | file g") != 0) + return 91; + parse(6, a2, "+ac:", NULL); /* POSIX's: stop at "file" */ + if (strcmp(seen, "| file -a -c x g") != 0) + return 92; + static const char *const a3[] = { "p", "-z", "-c" }; + parse(3, a3, "c:", NULL); + if (strcmp(seen, "?:z ?:c |") != 0) + return 93; + parse(3, a3, ":c:", NULL); + if (strcmp(seen, "?:z ::c |") != 0) + return 94; + + static int flag; + static const struct option lo[] = { + { "verbose", no_argument, NULL, 'v' }, + { "output", required_argument, NULL, 'o' }, + { "opt", optional_argument, NULL, 'p' }, + { "flag", no_argument, &flag, 7 }, + { "verify", no_argument, NULL, 'V' }, + { 0, 0, 0, 0 } }; + static const char *const a4[] = { + "p", "in", "--verbose", "--output=x", "--out", "y", "--opt", "--opt=z", + "--flag", "--ver", "--verb", "--nope", "-ab", "tail", "--output" }; + parse(15, a4, "vab", lo); + /* --ver is ambiguous (verbose, verify); --verb is not */ + if (strcmp(seen, "v: o:x o:y p: p:z 0: ?: v: ?: a: b: ?:o | in tail") != 0) + return 95; + if (flag != 7) + return 96; + static const char *const a5[] = { "p", "x", "-a", "y", "--", "-b", "z" }; + parse(7, a5, "ab", lo); + if (strcmp(seen, "a: | x y -b z") != 0) + return 97; + parse(7, a5, "-ab", lo); /* operands in order, as code 1 */ + if (strcmp(seen, "1:x a: 1:y | -b z") != 0) + return 98; + static const char *const a6[] = { "p", "f1", "-abc", "f2" }; + parse(4, a6, "abc", lo); + if (strcmp(seen, "a: b: c: | f1 f2") != 0) + return 99; + return 0; +} + +static int icmp(const void *a, const void *b) +{ + return *(const int *)a - *(const int *)b; +} + +static int icmp_r(const void *a, const void *b, void *dir) +{ + return *(int *)dir * (*(const int *)a - *(const int *)b); +} + +static int generators(void) +{ + int v[] = { 5, 3, 9, 1, 7 }, dir = -1; + qsort_r(v, 5, sizeof *v, icmp_r, &dir); + if (v[0] != 9 || v[1] != 7 || v[2] != 5 || v[3] != 3 || v[4] != 1) + return 100; + + srandom(1); + if (random() != 1804289383 || random() != 846930886 || + random() != 1681692777) + return 101; + static char st[5][256]; + static const size_t sizes[] = { 8, 32, 64, 128, 256 }; + static const long first[5][2] = { + { 1406932606, 654583775 }, { 2129646341, 1708620114 }, + { 1848518023, 594153704 }, { 542256677, 294305677 }, + { 155396598, 489107431 } }; + for (int k = 0; k < 5; k++) { + initstate(12345u + k, st[k], sizes[k]); + if (random() != first[k][0] || random() != first[k][1]) + return 102 + k; + } + random(); + random(); + setstate(st[1]); /* resumes where it was left */ + if (random() != 1729007179 || random() != 670702363) + return 107; + /* still the 32-byte state's generator; seed 0 is taken as 1 */ + srandom(0); + long z = random(); + srandom(1); + if (z != 964237963 || random() != z) + return 108; + srandom(4000000000u); + if (random() != 519042614) + return 109; + + srand48(7); + if (drand48() != 0.2664441967654092 || lrand48() != 1464659504 || + mrand48() != 1140273416) + return 110; + unsigned short xs[3] = { 1, 2, 3 }; + if (erand48(xs) != 0.44199632268870914 || nrand48(xs) != 565063343 || + jrand48(xs) != -1485464893) + return 111; + unsigned short sd[3] = { 9, 8, 7 }; + unsigned short *old = seed48(sd); + if (old[0] != 10787 || old[1] != 12552 || old[2] != 17399 || + lrand48() != 575374558) + return 112; + unsigned short pp[7] = { 1, 2, 3, 4, 5, 6, 7 }; + lcong48(pp); + if (lrand48() != 917510 || mrand48() != 15925355) + return 113; + unsigned seed = 99; + if (rand_r(&seed) != 2063792036 || rand_r(&seed) != 780411989) + return 114; + return 0; +} + +static int visited[64], nvisited, deepest; +static void collect(const void *n, VISIT v, int depth) +{ + if (v == postorder || v == leaf) + visited[nvisited++] = **(int *const *)n; + if (depth > deepest) + deepest = depth; +} + +/* AVL's own invariant, from twalk alone: each node's subtree heights, + * rebuilt from the order of its visits -- a child seen before the node's + * postorder visit is its left, after it its right -- may differ by one + * at most. */ +static int side[64], lh[64], rh[64], unbalanced; +static void report(int depth, int h) +{ + if (depth >= 0) + *(side[depth] ? &rh[depth] : &lh[depth]) = h; +} +static void balance(const void *n, VISIT v, int depth) +{ + (void)n; + if (v == preorder) { + side[depth] = lh[depth] = rh[depth] = 0; + } else if (v == postorder) { + side[depth] = 1; + } else { + int h = 1; + if (v == endorder) { + int d = lh[depth] - rh[depth]; + if (d > 1 || d < -1) + unbalanced = 1; + h += lh[depth] > rh[depth] ? lh[depth] : rh[depth]; + } + report(depth - 1, h); + } +} + +static int tables(void) +{ + /* inserted in an order that unbalances a plain binary tree, and + * walked in order; then every third key deleted */ + void *root = NULL; + static int keys[40]; + for (int i = 0; i < 40; i++) { + keys[i] = i; + if (*(int **)tsearch(&keys[i], &root, icmp) != &keys[i]) + return 120; + } + int dup = 7; + if (*(int **)tsearch(&dup, &root, icmp) != &keys[7]) + return 121; + nvisited = deepest = 0; + twalk(root, collect); + if (nvisited != 40) + return 122; + for (int i = 0; i < 40; i++) + if (visited[i] != i) + return 123; + if (deepest > 7) /* AVL: 1.44 log2(41), rounded */ + return 124; + void **held = tfind(&keys[20], &root, icmp); + for (int i = 0; i < 40; i += 3) + if (!tdelete(&keys[i], &root, icmp)) + return 125; + int absent = 3; + if (tdelete(&absent, &root, icmp) || tfind(&absent, &root, icmp)) + return 126; + /* a node a caller holds keeps naming its key across deletions */ + if (*(int **)held != &keys[20]) + return 127; + nvisited = 0; + twalk(root, collect); + if (nvisited != 26) + return 128; + for (int i = 1; i < nvisited; i++) + if (visited[i] <= visited[i - 1] || visited[i] % 3 == 0) + return 129; + tdestroy(root, NULL); + + /* random inserts and deletes against a model of which keys are in: + * deletion is where a balanced tree's rotations go wrong */ + root = NULL; + unsigned char in[64] = { 0 }; + unsigned lcg = 12345; + for (int op = 0; op < 400; op++) { + lcg = lcg * 1103515245u + 12345u; + int k = (int)(lcg >> 16) % 64; + if ((lcg >> 8) & 1) { + tsearch(&keys[k % 40], &root, icmp); + in[k % 40] = 1; + } else { + void *d = tdelete(&keys[k % 40], &root, icmp); + if ((d != NULL) != in[k % 40]) + return 134; + in[k % 40] = 0; + } + if (op % 50 == 49) { + nvisited = deepest = 0; + twalk(root, collect); + int want = 0; + for (int i = 0; i < 40; i++) + if (in[i] && visited[want++] != i) + return 135; + if (nvisited != want || deepest > 7) + return 136; + unbalanced = 0; + twalk(root, balance); + if (unbalanced) + return 137; + } + } + tdestroy(root, NULL); + + /* hcreate(4), then forty: the table grows (glibc's fills and refuses) */ + if (!hcreate(4)) + return 130; + static char names[40][8]; + for (int i = 0; i < 40; i++) { + sprintf(names[i], "k%d", i); + if (!hsearch((ENTRY){ names[i], (void *)(long)(i * i) }, ENTER)) + return 131; + } + ENTRY *e = hsearch((ENTRY){ "k17", 0 }, FIND); + if (!e || (long)e->data != 289 || hsearch((ENTRY){ "nope", 0 }, FIND)) + return 132; + hdestroy(); + + int arr[8] = { 4, 2 }; + size_t n = 2; + int key = 9; + lsearch(&key, arr, &n, sizeof(int), icmp); + key = 2; + if (n != 3 || arr[2] != 9 || + (int *)lsearch(&key, arr, &n, sizeof(int), icmp) != &arr[1] || + lfind(&(int){ 5 }, arr, &n, sizeof(int), icmp)) + return 133; + return 0; +} + +static int environment(void) +{ + extern char **environ; + if (clearenv() != 0 || environ) + return 140; + if (setenv("A", "1", 1) || setenv("B", "2", 1) || setenv("A", "3", 0)) + return 141; + if (strcmp(getenv("A"), "1") != 0 || strcmp(getenv("B"), "2") != 0) + return 142; + setenv("A", "4", 1); + static char pe[] = "C=5"; + putenv(pe); + pe[2] = '6'; /* the caller's string IS the entry */ + if (strcmp(getenv("A"), "4") != 0 || strcmp(getenv("C"), "6") != 0) + return 143; + if (unsetenv("A") || getenv("A")) + return 144; + errno = 0; + if (setenv("X=Y", "1", 1) != -1 || errno != EINVAL || + unsetenv("") != -1 || setenv(NULL, "1", 1) != -1) + return 145; + int count = 0; + for (char **p = environ; *p; p++) + count++; + if (count != 2) + return 146; + /* many, so the vector is reallocated under the strings */ + for (int i = 0; i < 100; i++) { + char name[8], val[8]; + sprintf(name, "V%d", i); + sprintf(val, "%d", i * 3); + if (setenv(name, val, 1)) + return 147; + } + if (strcmp(getenv("V77"), "231") != 0 || strcmp(getenv("C"), "6") != 0) + return 148; + clearenv(); + return 0; +} + +int main(void) +{ + int r; + if ((r = strings()) || (r = paths()) || (r = globs()) || + (r = options()) || (r = generators()) || (r = tables()) || + (r = environment())) + return r; + return 42; +} diff --git a/tests/exec/posix-threads.c b/tests/exec/posix-threads.c new file mode 100644 index 000000000..f98671ec9 --- /dev/null +++ b/tests/exec/posix-threads.c @@ -0,0 +1,435 @@ +/* POSIX threads and C11's: creating, joining, exiting with a value from + * deep inside, thread-specific data and its destructors, the three + * kinds of mutex, condition variables with a deadline on either clock, + * read-write locks, barriers, once, spin locks, semaphores, fork's + * handlers -- and C11's spellings, whose deadlines are absolute. + * + * Every answer is glibc's (the program ran against glibc 2.36 too). + * Where the target has no threads, pthread_create fails with EAGAIN and + * only what one thread can show is checked: keys, error-checking + * mutexes, once, semaphores without waiting. */ +// expect-exit: 42 +// needs-libc: POSIX threads, from lib/libc +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int threads_ok; +static int clock_ok; /* a board may have no clock: no timing then */ + +static struct timespec in_ms(clockid_t c, long ms) +{ + struct timespec t = { 0, 0 }; /* where there is no clock: from 0 */ + clock_gettime(c, &t); + t.tv_nsec += ms % 1000 * 1000000L; + t.tv_sec += ms / 1000 + t.tv_nsec / 1000000000L; + t.tv_nsec %= 1000000000L; + return t; +} + +static long elapsed_ms(const struct timespec *a) +{ + struct timespec b; + clock_gettime(CLOCK_MONOTONIC, &b); + return (b.tv_sec - a->tv_sec) * 1000 + (b.tv_nsec - a->tv_nsec) / 1000000; +} + +/* ---- create, join, exit, keys ---------------------------------------------- */ + +static pthread_key_t g_key; +static int g_dtor_runs, g_dtor_value; + +static void dtor(void *v) +{ + g_dtor_runs++; + g_dtor_value += (int)(intptr_t)v; + /* set again: POSIX gives it another round */ + if ((intptr_t)v == 7) + pthread_setspecific(g_key, (void *)(intptr_t)8); +} + +static void deep_exit(int n) +{ + if (n == 0) + pthread_exit((void *)(intptr_t)99); + deep_exit(n - 1); +} + +static void *worker(void *arg) +{ + pthread_setspecific(g_key, (void *)(intptr_t)7); + if ((intptr_t)arg == 1) + deep_exit(5); + return (void *)(intptr_t)((intptr_t)arg * 10); +} + +static int basics(void) +{ + if (pthread_key_create(&g_key, dtor)) + return 1; + pthread_setspecific(g_key, (void *)(intptr_t)5); + if (pthread_getspecific(g_key) != (void *)(intptr_t)5) + return 2; + if (!pthread_equal(pthread_self(), pthread_self())) + return 3; + if (pthread_join(pthread_self(), NULL) != EDEADLK) + return 4; + + pthread_t t; + int e = pthread_create(&t, NULL, worker, (void *)(intptr_t)4); + if (e == EAGAIN) + return 0; /* a target with no threads */ + if (e) + return 5; + threads_ok = 1; + void *r; + if (pthread_join(t, &r) || r != (void *)(intptr_t)40) + return 6; + /* the destructor ran twice in that thread (7, then the 8 it set), + * and this thread's value is untouched */ + if (g_dtor_runs != 2 || g_dtor_value != 15 || + pthread_getspecific(g_key) != (void *)(intptr_t)5) + return 7; + if (pthread_create(&t, NULL, worker, (void *)(intptr_t)1) || + pthread_join(t, &r) || r != (void *)(intptr_t)99 || g_dtor_runs != 4) + return 8; + + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + if (pthread_create(&t, &a, worker, (void *)(intptr_t)2)) + return 9; + size_t ss; + if (pthread_attr_setstacksize(&a, 1 << 20) || + pthread_attr_getstacksize(&a, &ss) || ss != 1 << 20 || + pthread_attr_setstacksize(&a, 1) != EINVAL || + pthread_attr_setscope(&a, PTHREAD_SCOPE_PROCESS) != ENOTSUP) + return 10; + pthread_attr_destroy(&a); + usleep(50000); /* let the detached one finish */ + return 0; +} + +/* ---- mutexes --------------------------------------------------------------- */ + +static pthread_mutex_t g_m = PTHREAD_MUTEX_INITIALIZER; +static long g_count; + +static void *bump(void *arg) +{ + for (int i = 0; i < 5000; i++) { + pthread_mutex_lock(&g_m); + g_count++; + pthread_mutex_unlock(&g_m); + } + return arg; +} + +static void *try_held(void *arg) +{ + pthread_mutex_t *m = arg; + struct timespec d = in_ms(CLOCK_REALTIME, 50); + int busy = pthread_mutex_trylock(m); + int timed = pthread_mutex_timedlock(m, &d); + return (void *)(intptr_t)(busy == EBUSY && timed == ETIMEDOUT); +} + +static int mutexes(void) +{ + pthread_mutexattr_t at; + pthread_mutex_t m; + pthread_mutexattr_init(&at); + pthread_mutexattr_settype(&at, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m, &at); + if (pthread_mutex_lock(&m) || pthread_mutex_lock(&m) || + pthread_mutex_unlock(&m) || pthread_mutex_unlock(&m) || + pthread_mutex_unlock(&m) != EPERM) + return 20; + pthread_mutex_destroy(&m); + pthread_mutexattr_settype(&at, PTHREAD_MUTEX_ERRORCHECK); + pthread_mutex_init(&m, &at); + if (pthread_mutex_lock(&m) || pthread_mutex_lock(&m) != EDEADLK || + pthread_mutex_destroy(&m) != EBUSY || pthread_mutex_unlock(&m) || + pthread_mutex_unlock(&m) != EPERM) + return 21; + if (pthread_mutexattr_setpshared(&at, 2) != EINVAL) + return 22; + if (!threads_ok) + return 0; + + pthread_t t[4]; + for (int i = 0; i < 4; i++) + pthread_create(&t[i], NULL, bump, NULL); + for (int i = 0; i < 4; i++) + pthread_join(t[i], NULL); + if (g_count != 20000) + return 23; + + pthread_mutex_init(&m, NULL); + pthread_mutex_lock(&m); + void *r; + struct timespec t0; + clock_gettime(CLOCK_MONOTONIC, &t0); + pthread_create(&t[0], NULL, try_held, &m); + pthread_join(t[0], &r); + long ms = elapsed_ms(&t0); + pthread_mutex_unlock(&m); + if (!r || ms < 40 || ms > 2000) + return 24; + return 0; +} + +/* ---- condition variables ------------------------------------------------- */ + +static pthread_cond_t g_c = PTHREAD_COND_INITIALIZER; +static int g_ready; + +static void *producer(void *arg) +{ + usleep(20000); + pthread_mutex_lock(&g_m); + g_ready = 1; + pthread_cond_signal(&g_c); + pthread_mutex_unlock(&g_m); + return arg; +} + +static int conds(void) +{ + /* a deadline that passes: ETIMEDOUT, on either clock */ + pthread_mutex_lock(&g_m); + struct timespec d = in_ms(CLOCK_REALTIME, 30), t0; + clock_gettime(CLOCK_MONOTONIC, &t0); + if (pthread_cond_timedwait(&g_c, &g_m, &d) != ETIMEDOUT || + (clock_ok && elapsed_ms(&t0) < 20)) + return 30; + pthread_condattr_t ca; + pthread_cond_t mc; + pthread_condattr_init(&ca); + if (pthread_condattr_setclock(&ca, CLOCK_MONOTONIC) || + pthread_condattr_setclock(&ca, CLOCK_PROCESS_CPUTIME_ID) != EINVAL) + return 31; + pthread_cond_init(&mc, &ca); + d = in_ms(CLOCK_MONOTONIC, 30); + if (pthread_cond_timedwait(&mc, &g_m, &d) != ETIMEDOUT) + return 32; + d.tv_nsec = 1000000000L; + if (pthread_cond_timedwait(&mc, &g_m, &d) != EINVAL) + return 33; + pthread_mutex_unlock(&g_m); + if (!threads_ok) + return 0; + + pthread_t t; + pthread_mutex_lock(&g_m); + pthread_create(&t, NULL, producer, NULL); + while (!g_ready) + pthread_cond_wait(&g_c, &g_m); + pthread_mutex_unlock(&g_m); + pthread_join(t, NULL); + return 0; +} + +/* ---- read-write locks, barriers, once, spin ------------------------------- */ + +static pthread_rwlock_t g_rw = PTHREAD_RWLOCK_INITIALIZER; +static pthread_barrier_t g_bar; +static pthread_once_t g_once = PTHREAD_ONCE_INIT; +static int g_once_runs, g_serial, g_in_read, g_max_read; +static pthread_spinlock_t g_spin; +static long g_spun; + +static void once_fn(void) { g_once_runs++; } + +static void *crowd(void *arg) +{ + pthread_once(&g_once, once_fn); + pthread_rwlock_rdlock(&g_rw); + int n = __atomic_add_fetch(&g_in_read, 1, __ATOMIC_SEQ_CST); + if (n > g_max_read) + g_max_read = n; + if (pthread_barrier_wait(&g_bar) == PTHREAD_BARRIER_SERIAL_THREAD) + __atomic_add_fetch(&g_serial, 1, __ATOMIC_SEQ_CST); + __atomic_sub_fetch(&g_in_read, 1, __ATOMIC_SEQ_CST); + pthread_rwlock_unlock(&g_rw); + for (int i = 0; i < 2000; i++) { + pthread_spin_lock(&g_spin); + g_spun++; + pthread_spin_unlock(&g_spin); + } + return arg; +} + +static int others(void) +{ + if (pthread_rwlock_wrlock(&g_rw) || pthread_rwlock_wrlock(&g_rw) != EDEADLK || + pthread_rwlock_tryrdlock(&g_rw) != EBUSY || + pthread_rwlock_unlock(&g_rw) || + pthread_rwlock_rdlock(&g_rw) || pthread_rwlock_rdlock(&g_rw) || + pthread_rwlock_trywrlock(&g_rw) != EBUSY || + pthread_rwlock_unlock(&g_rw) || pthread_rwlock_unlock(&g_rw)) + return 40; + if (pthread_barrier_init(&g_bar, NULL, 0) != EINVAL) + return 41; + pthread_spin_init(&g_spin, PTHREAD_PROCESS_PRIVATE); + if (pthread_spin_trylock(&g_spin) || pthread_spin_trylock(&g_spin) != EBUSY || + pthread_spin_unlock(&g_spin)) + return 42; + if (!threads_ok) { + pthread_once(&g_once, once_fn); + pthread_once(&g_once, once_fn); + return g_once_runs == 1 ? 0 : 43; + } + /* four readers inside at once, which the barrier proves: none can + * pass it until all four have the read lock */ + pthread_barrier_init(&g_bar, NULL, 4); + pthread_t t[4]; + for (int i = 0; i < 4; i++) + pthread_create(&t[i], NULL, crowd, NULL); + for (int i = 0; i < 4; i++) + pthread_join(t[i], NULL); + pthread_barrier_destroy(&g_bar); + if (g_once_runs != 1 || g_serial != 1 || g_max_read != 4 || g_spun != 8000) + return 44; + return 0; +} + +/* ---- semaphores ------------------------------------------------------------- */ + +static sem_t g_sem; + +static void *poster(void *arg) +{ + usleep(20000); + sem_post(&g_sem); + return arg; +} + +static int sems(void) +{ + int v; + if (sem_init(&g_sem, 0, 2) || sem_trywait(&g_sem) || sem_trywait(&g_sem)) + return 50; + errno = 0; + if (sem_trywait(&g_sem) != -1 || errno != EAGAIN) + return 51; + struct timespec d = in_ms(CLOCK_REALTIME, 30); + errno = 0; + if (sem_timedwait(&g_sem, &d) != -1 || errno != ETIMEDOUT) + return 52; + if (sem_post(&g_sem) || sem_getvalue(&g_sem, &v) || v != 1 || + sem_wait(&g_sem)) + return 53; + if (threads_ok) { + pthread_t t; + pthread_create(&t, NULL, poster, NULL); + if (sem_wait(&g_sem)) + return 54; + pthread_join(t, NULL); + } + sem_destroy(&g_sem); + return 0; +} + +/* ---- C11's spellings ------------------------------------------------------- */ + +static tss_t g_tss; +static int g_tss_dtor; + +static void tss_dtor(void *v) { g_tss_dtor += (int)(intptr_t)v; } + +static int c11_worker(void *arg) +{ + tss_set(g_tss, (void *)(intptr_t)3); + if ((intptr_t)arg == 1) + thrd_exit(17); + return 5; +} + +static int c11(void) +{ + if (tss_create(&g_tss, tss_dtor) != thrd_success) + return 60; + tss_set(g_tss, (void *)(intptr_t)9); + if (tss_get(g_tss) != (void *)(intptr_t)9) + return 61; + /* a DEADLINE: 30 ms from now, not 30 ms past 1970 */ + mtx_t m; + cnd_t c; + mtx_init(&m, mtx_timed); + cnd_init(&c); + struct timespec d = { 0, 0 }, t0; + timespec_get(&d, TIME_UTC); + d.tv_nsec += 30000000L; + if (d.tv_nsec >= 1000000000L) { + d.tv_sec++; + d.tv_nsec -= 1000000000L; + } + clock_gettime(CLOCK_MONOTONIC, &t0); + mtx_lock(&m); + int r = cnd_timedwait(&c, &m, &d); + mtx_unlock(&m); + long ms = elapsed_ms(&t0); + if (r != thrd_timedout || (clock_ok && (ms < 20 || ms > 2000))) + return 62; + if (!threads_ok) + return 0; + thrd_t t; + int res; + if (thrd_create(&t, c11_worker, NULL) != thrd_success || + thrd_join(t, &res) != thrd_success || res != 5) + return 63; + if (thrd_create(&t, c11_worker, (void *)(intptr_t)1) != thrd_success || + thrd_join(t, &res) != thrd_success || res != 17) + return 64; + if (g_tss_dtor != 6 || tss_get(g_tss) != (void *)(intptr_t)9) + return 65; + return 0; +} + +/* ---- fork's handlers: prepare in reverse, the others in order --------------- */ + +static char g_log[16]; +static void log_c(char c) { size_t n = strlen(g_log); g_log[n] = c; } +static void p1(void) { log_c('a'); } +static void p2(void) { log_c('b'); } +static void pa1(void) { log_c('c'); } +static void pa2(void) { log_c('d'); } +static void ch1(void) { log_c('e'); } +static void ch2(void) { log_c('f'); } + +static int atfork(void) +{ + pthread_atfork(p1, pa1, ch1); + pthread_atfork(p2, pa2, ch2); + fflush(stdout); + pid_t p = fork(); + if (p < 0) + return 0; /* no fork here: nothing to order */ + if (p == 0) + _exit(strcmp(g_log, "baef") == 0 ? 0 : 1); + int st; + if (waitpid(p, &st, 0) != p || !WIFEXITED(st) || WEXITSTATUS(st) != 0) + return 70; + return strcmp(g_log, "bacd") == 0 ? 0 : 71; +} + +int main(void) +{ + struct timespec t; + clock_ok = clock_gettime(CLOCK_MONOTONIC, &t) == 0; + int r; + if ((r = basics()) || (r = mutexes()) || (r = conds()) || (r = others()) || + (r = sems()) || (r = c11()) || (r = atfork())) + return r; + return 42; +} diff --git a/tests/exec/posix-time.c b/tests/exec/posix-time.c new file mode 100644 index 000000000..4cda237a0 --- /dev/null +++ b/tests/exec/posix-time.c @@ -0,0 +1,191 @@ +/* Local time: TZ as POSIX writes it, tzset's variables, localtime's + * tm_isdst, tm_gmtoff and tm_zone, mktime's reading of tm_isdst on the + * mornings the clocks change, strftime's %z and %Z, and strptime. + * + * Every value is what glibc 2.36 gave for the same call (the program was + * run against both on Linux); posixdemo's cases are EmbLinkOS's own test + * of its time zones, which this library now serves there. No files are + * read, so it runs wherever there is a setenv -- the boards too. */ +// expect-exit: 42 +// needs-libc: POSIX's time zones and strptime, from lib/libc +#define _GNU_SOURCE 1 +#include +#include +#include +#include + +static int at(const char *tz, long long t, const char *want, int isdst, + long gmtoff) +{ + setenv("TZ", tz, 1); + tzset(); + time_t tt = (time_t)t; + struct tm tm; + char b[64]; + if (!localtime_r(&tt, &tm)) + return 1; + strftime(b, sizeof b, "%Y-%m-%d %H:%M:%S %Z %z", &tm); + if (strcmp(b, want) || tm.tm_isdst != isdst || tm.tm_gmtoff != gmtoff) + return 1; + struct tm back = tm; + return mktime(&back) != tt; /* and back again, exactly */ +} + +static int zones(void) +{ + static const struct { + const char *tz; + long long t; + const char *want; + int isdst; + long gmtoff; + } c[] = { + { "UTC0", 1700000000, "2023-11-14 22:13:20 UTC +0000", 0, 0 }, + { "EST5EDT,M3.2.0,M11.1.0", 1700000000, + "2023-11-14 17:13:20 EST -0500", 0, -18000 }, + { "EST5EDT,M3.2.0,M11.1.0", 1689000000, + "2023-07-10 10:40:00 EDT -0400", 1, -14400 }, + { "CET-1CEST,M3.5.0,M10.5.0/3", 1700000000, + "2023-11-14 23:13:20 CET +0100", 0, 3600 }, + { "CET-1CEST,M3.5.0,M10.5.0/3", 1689000000, + "2023-07-10 16:40:00 CEST +0200", 1, 7200 }, + { "IST-5:30", 1700000000, "2023-11-15 03:43:20 IST +0530", 0, 19800 }, + /* the southern hemisphere: summer across New Year */ + { "AEST-10AEDT,M10.1.0,M4.1.0/3", 1700000000, + "2023-11-15 09:13:20 AEDT +1100", 1, 39600 }, + { "AEST-10AEDT,M10.1.0,M4.1.0/3", 1689000000, + "2023-07-11 00:40:00 AEST +1000", 0, 36000 }, + /* the transition instants themselves, each side */ + { "EST5EDT,M3.2.0,M11.1.0", 1710054000, + "2024-03-10 03:00:00 EDT -0400", 1, -14400 }, + { "EST5EDT,M3.2.0,M11.1.0", 1710053999, + "2024-03-10 01:59:59 EST -0500", 0, -18000 }, + { "EST5EDT,M3.2.0,M11.1.0", 1730613600, + "2024-11-03 01:00:00 EST -0500", 0, -18000 }, + { "EST5EDT,M3.2.0,M11.1.0", 1730613599, + "2024-11-03 01:59:59 EDT -0400", 1, -14400 }, + /* no rules: the United States' */ + { "PST8PDT", 1689000000, "2023-07-10 07:40:00 PDT -0700", 1, -25200 }, + /* a quoted name, Julian days, a rule time past midnight */ + { "<+0330>-3:30", 1700000000, "2023-11-15 01:43:20 +0330 +0330", 0, + 12600 }, + { "XXX3YYY,J60/1,J300/1", 1689000000, + "2023-07-10 12:40:00 YYY -0200", 1, -7200 }, + { "EST5EDT,M3.2.0/-1,M11.1.0/26", 1730613600, + "2024-11-03 02:00:00 EDT -0400", 1, -14400 }, + }; + for (size_t i = 0; i < sizeof c / sizeof *c; i++) + if (at(c[i].tz, c[i].t, c[i].want, c[i].isdst, c[i].gmtoff)) + return 1 + (int)i; + + setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); + tzset(); + if (strcmp(tzname[0], "CET") || strcmp(tzname[1], "CEST") || + timezone != -3600 || !daylight) + return 20; + /* gmtime ignores TZ */ + time_t t = 1700000000; + struct tm g; + gmtime_r(&t, &g); + if (g.tm_hour != 22 || g.tm_gmtoff != 0) + return 21; + return 0; +} + +/* tm_isdst as glibc reads it, where a local time is ambiguous (the + * clocks go back) or missing (they go forward) */ +static int hints(void) +{ + setenv("TZ", "EST5EDT,M3.2.0,M11.1.0", 1); + tzset(); + static const long long fall[3] = { 1730611800, 1730615400, 1730611800 }; + static const long long gap[3] = { 1710055800, 1710055800, 1710052200 }; + for (int h = -1; h <= 1; h++) { + struct tm f = { .tm_year = 124, .tm_mon = 10, .tm_mday = 3, + .tm_hour = 1, .tm_min = 30, .tm_isdst = h }; + struct tm s = { .tm_year = 124, .tm_mon = 2, .tm_mday = 10, + .tm_hour = 2, .tm_min = 30, .tm_isdst = h }; + if (mktime(&f) != fall[h + 1] || mktime(&s) != gap[h + 1]) + return 30 + h; + } + /* out-of-range fields are normalised, and the result follows */ + struct tm n = { .tm_year = 123, .tm_mon = 12, .tm_mday = 32, + .tm_hour = 25, .tm_isdst = -1 }; + time_t r = mktime(&n); + if (n.tm_year != 124 || n.tm_mon != 1 || n.tm_mday != 2 || + n.tm_hour != 1 || r != 1706853600) + return 33; + return 0; +} + +static int parse(void) +{ + struct tm tm; + memset(&tm, 0, sizeof tm); + char *e = strptime("2024-03-10 14:05:09", "%Y-%m-%d %H:%M:%S", &tm); + if (!e || *e || tm.tm_year != 124 || tm.tm_mon != 2 || tm.tm_mday != 10 || + tm.tm_hour != 14 || tm.tm_min != 5 || tm.tm_sec != 9) + return 40; + memset(&tm, 0, sizeof tm); + e = strptime("Tue, 5 nOv 69 11:30 PM rest", "%a, %d %b %y %I:%M %p", + &tm); + if (!e || strcmp(e, " rest") || tm.tm_wday != 2 || tm.tm_mon != 10 || + tm.tm_mday != 5 || tm.tm_year != 69 || tm.tm_hour != 23 || + tm.tm_min != 30) + return 41; + /* a name is not preceded by blanks the format does not have */ + if (strptime(" Tue", "%a", &tm)) + return 45; + memset(&tm, 0, sizeof tm); + if (!strptime("12/31/68", "%D", &tm) || tm.tm_year != 168) + return 42; + memset(&tm, 0, sizeof tm); + if (!strptime("366 +0530", "%j %z", &tm) || tm.tm_yday != 365 || + tm.tm_gmtoff != 19800) + return 43; + memset(&tm, 0, sizeof tm); + if (strptime("2024-13-01", "%Y-%m-%d", &tm) || strptime("x", "%d", &tm) || + strptime("-5", "%Y", &tm) || strptime("+1260", "%z", &tm) || + strptime("10:59:62", "%T", &tm)) + return 44; + /* a number stops where one more digit could not fit its field */ + memset(&tm, 0, sizeof tm); + e = strptime("10:61", "%H:%M", &tm); + if (!e || strcmp(e, "1") || tm.tm_min != 6) + return 46; + /* the fields a date implies: weekday and day of the year */ + memset(&tm, 0, sizeof tm); + if (!strptime("2024-02-29", "%F", &tm) || tm.tm_wday != 4 || + tm.tm_yday != 59) + return 47; + memset(&tm, 0, sizeof tm); + if (!strptime("60 2024", "%j %Y", &tm) || tm.tm_mon != 1 || + tm.tm_mday != 29 || tm.tm_wday != 4) + return 48; + memset(&tm, 0, sizeof tm); + if (!strptime("2024 12 3", "%Y %U %w", &tm) || tm.tm_mon != 2 || + tm.tm_mday != 27 || tm.tm_yday != 86) + return 49; + memset(&tm, 0, sizeof tm); + if (!strptime("Mon 2024 10", "%a %Y %W", &tm) || tm.tm_mon != 2 || + tm.tm_mday != 4) + return 50; + /* %D's year takes a later %C's century; %I joins %p, %H does not */ + memset(&tm, 0, sizeof tm); + if (!strptime("03/09/24 21", "%D %C", &tm) || tm.tm_year != 224 || + tm.tm_wday != 4) + return 51; + memset(&tm, 0, sizeof tm); + if (!strptime("12 AM", "%I %p", &tm) || tm.tm_hour != 0 || + !strptime("3 PM", "%H %p", &tm) || tm.tm_hour != 3) + return 52; + return 0; +} + +int main(void) +{ + int r; + if ((r = zones()) || (r = hints()) || (r = parse())) + return r; + return 42; +} diff --git a/tests/golden/install.sh b/tests/golden/install.sh index 8582d1f56..17d3c5766 100644 --- a/tests/golden/install.sh +++ b/tests/golden/install.sh @@ -54,6 +54,26 @@ EOF cat "$out/cc1.log"; exit 1; } echo "a build-tree binary finds its own headers: with no -I" +# In the right ORDER, with and without the C library named by -I as +# well. With none, is the C library's -- the compiler's own +# directory has a declarations-only one, which once came first and +# took strtok_r and the rest of POSIX's away. Named by -I too, the +# directory is searched once: twice, the C library's found +# itself again through #include_next and INT_MAX was never defined. +cat > "$out/order.c" << 'EOF' +#include +#include +char *(*tok)(char *, const char *, char **) = strtok_r; +int widest = INT_MAX, longest = PATH_MAX; +EOF +for inc in "" "-I$EMBCC_ROOT/lib/libc/include"; do + ( cd "$out" && "$EMBCC" --target=x86_64-linux-gnu $inc -c order.c \ + -o order.o ) 2> "$out/order.log" || { + echo "FAIL: and in the wrong order (${inc:-no -I}):" + cat "$out/order.log"; exit 1; } +done +echo "the C library's first, and reaching the compiler's" + # ---- 2. a staged install, and the copy that results ---------------------- # # The install rule itself, into a DESTDIR -- which is what a package diff --git a/tests/golden/libc-backends.sh b/tests/golden/libc-backends.sh new file mode 100644 index 000000000..737d0ff35 --- /dev/null +++ b/tests/golden/libc-backends.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Every backend of lib/libc defines every primitive os/backend.h names. +# +# A primitive a backend leaves out is not a missing feature, it is a +# program that fails to LINK on that target the first time it calls the +# function above it -- and nothing else would notice, since each backend +# is built only for its own targets. So the list is read out of the +# contract itself, every backend is compiled, and its object must define +# all of them: really, or as os/absent.inc's ENOSYS, but defined. +# +# The Linux backend is three files (backend.c, posix.c, thread.c), so its +# definitions are the union; EmbLinkOS's needs that OS's headers +# (EMBLINKOS=...) and is skipped without them. +set -u +echo "TEST-MARKER libc-backends" +. "$(dirname "$0")/../lib.sh" +EMBCC=${EMBCC:-$EMBCC_ROOT/embcc} +out=$EMBCC_ROOT/tests/golden/out/libc-backends +rm -rf "${out:?}"; mkdir -p "$out" +NM=${EMBCC_NM:-llvm-nm} +command -v "$NM" > /dev/null 2>&1 || NM=x86_64-elf-nm +command -v "$NM" > /dev/null 2>&1 || { echo "skipped: no nm"; exit 0; } + +L=$EMBCC_ROOT/lib/libc +sed -n 's/^[a-z][a-z ]*\**\(__os_[a-z0-9_]*\)(.*/\1/p' "$L/os/backend.h" | + sort -u > "$out/contract.txt" +n=$(wc -l < "$out/contract.txt" | tr -d ' ') +[ "$n" -gt 100 ] || { echo "FAIL: read only $n primitives out of os/backend.h"; exit 1; } + +fail=0 +check() { # check NAME TRIPLE EXTRA-FLAGS FILE... + name=$1 triple=$2 flags=$3; shift 3 + : > "$out/$name.def" + for f in "$@"; do + o=$out/$name-$(basename "$f" .c).o + "$EMBCC" --target="$triple" -c -O1 -I"$L/include" -I"$L/src/math" \ + $flags "$f" -o "$o" > "$out/$name.cc" 2>&1 || + { echo "FAIL: $f does not compile for $triple"; head -3 "$out/$name.cc" + fail=1; return; } + "$NM" --defined-only "$o" | awk 'NF == 3 { print $3 }' >> "$out/$name.def" + done + sort -u "$out/$name.def" -o "$out/$name.def" + comm -23 "$out/contract.txt" "$out/$name.def" > "$out/$name.missing" + if [ -s "$out/$name.missing" ]; then + echo "FAIL: the $name backend does not define:" + sed 's/^/ /' "$out/$name.missing" | head -20 + fail=1 + else + echo "$name ($triple): all $n primitives defined" + fi +} + +check baremetal thumbv7m-none-eabi "" "$L/os/baremetal/backend.c" +check baremetal-avr avr-none-elf "" "$L/os/baremetal/backend.c" +check posixlike x86_64-elf "" "$L/os/posixlike/backend.c" +for a in x86_64 aarch64; do + check linux-$a $a-linux-gnu "" "$L/os/linux/backend.c" \ + "$L/os/linux/posix.c" "$L/os/linux/thread.c" +done +EMBLINKOS=${EMBLINKOS:-$HOME/EmbLinkOs} +if [ -f "$EMBLINKOS/user/lib/embk.h" ]; then + check emblinkos x86_64-elf "-I$EMBLINKOS/user/lib" "$L/os/emblinkos/backend.c" +else + echo "emblinkos: skipped (no $EMBLINKOS/user/lib/embk.h; set EMBLINKOS)" +fi +# The C++ library reaches the same primitives from its own headers, which +# cannot include os/backend.h: its copies must be the contract's, or a +# backend writes past a structure C++ allocated (a shorter __os_fileinfo +# in once hung every status()). Fields and prototypes, +# comments and spacing aside. +body() { # body FILE STRUCT -> the struct's fields, one per line + sed -n "/^struct $2 {/,/^};/p" "$1" | sed 's|/\*.*\*/||; s|^ *\*.*||; s|/\*.*||' | + tr -s ' ' | sed 's/^ //; s/ $//' | grep -v '^$' +} +for st in __os_fileinfo; do + for h in "$EMBCC_ROOT"/lib/libcxx/include/*; do + grep -q "^struct $st {" "$h" || continue + if [ "$(body "$L/os/backend.h" $st)" != "$(body "$h" $st)" ]; then + echo "FAIL: struct $st in lib/libcxx/include/$(basename "$h") is not os/backend.h's" + fail=1 + fi + done +done +grep -h "^[a-z][^{=]*[ *]__os_[a-z_]*(.*);" "$EMBCC_ROOT"/lib/libcxx/include/* \ + "$EMBCC_ROOT"/lib/libcxx/src/*.cc | tr -s ' ' | sort -u | +while read -r line; do + n=$(echo "$line" | grep -o "__os_[a-z_]*" | head -1) + want=$(grep -h "^[a-z].*[ *]$n(" "$L/os/backend.h" | tr -s ' ' | head -1) + [ "$line" = "$want" ] || { echo "FAIL: libcxx declares [$line], the contract [$want]"; exit 1; } +done || fail=1 +[ $fail = 0 ] && echo "lib/libcxx: its copies of the contract are the contract's" +[ $fail = 0 ] || exit 1 diff --git a/tests/golden/libc-emblinkos.sh b/tests/golden/libc-emblinkos.sh index 75d330436..ec80139d3 100755 --- a/tests/golden/libc-emblinkos.sh +++ b/tests/golden/libc-emblinkos.sh @@ -108,7 +108,11 @@ references the kernel ABI -- the OS is confined to the backend" # What is genuinely external, which is not the same as what nm calls # undefined: a member that calls another member reports it undefined, so # the closure is (undefined - defined) across the whole archive. -x86_64-elf-ar rcs "$out/libc.a" "$out"/lib/*.o "$out/backend.o" +# os/posix-names.c: the POSIX names a kernel's backend gives (read, write, +# _exit ...), which the Makefile builds for EmbLinkOS beside the backend +"$EMBCC" -c -O2 -Ilib/libc/include lib/libc/os/posix-names.c \ + -o "$out/posix-names.o" || { echo "FAIL: os/posix-names.c does not build"; exit 1; } +x86_64-elf-ar rcs "$out/libc.a" "$out"/lib/*.o "$out/backend.o" "$out/posix-names.o" x86_64-elf-nm "$out/libc.a" > "$out/nm.txt" awk '$1 == "U" { print $2 }' "$out/nm.txt" | sort -u > "$out/undef.txt" awk 'NF == 3 && $2 != "U" { print $3 }' "$out/nm.txt" | sort -u \ diff --git a/tests/golden/libc.sh b/tests/golden/libc.sh index 5160075ea..f4efe45b0 100755 --- a/tests/golden/libc.sh +++ b/tests/golden/libc.sh @@ -95,11 +95,12 @@ H=tests/harness/x86_64 # The harness's entry and syscall shims stay: they are the BACKEND this # library was designed to sit on (lib/libc/os/backend.h). Everything above # them -- buffering, formatting, allocation -- is ours. +# Except kill: the library's own is the one a program here must reach. for part in start sys crt; do src=$H/$part.S; [ "$part" = sys ] && src=$H/sys.c [ "$part" = crt ] && src=$H/../crt.c $GCC -ffreestanding -mno-red-zone -isystem "$X86_NEWLIB/include" \ - -c "$src" -o "$out/$part.o" + -DSYS_NO_KILL -c "$src" -o "$out/$part.o" done build_run() { # build_run diff --git a/tests/golden/libcxx-std.sh b/tests/golden/libcxx-std.sh index 3bb7b223d..029c0eef3 100755 --- a/tests/golden/libcxx-std.sh +++ b/tests/golden/libcxx-std.sh @@ -227,8 +227,8 @@ int main() /* Both spellings have to work: the standard requires std::, and every * real implementation also leaves the global one, which code relies on. */ std::size_t n = std::strlen("abc"); - /* nine ints: our lays tm out as C11 names it and nothing - * more -- glibc's is larger because it adds two non-standard fields. */ + /* nine ints and the two fields glibc and BSD add, tm_gmtoff and + * tm_zone: our lays tm out as glibc does, 56 bytes on LP64. */ std::printf("%zu %d %g %d\n", n, std::toupper('a'), std::sqrt(4.0), (int)sizeof(std::tm)); assert(n == 3); @@ -307,5 +307,5 @@ echo "and std::cout really prints, through our streambuf and our FILE" if run "$out/cheaders.cc"; then rc=0; else rc=$?; fi cat "$out/run.txt" [ "$rc" = 42 ] || { echo "FAIL: the program exited $rc"; exit 1; } -want "3 65 2 36" +want "3 65 2 56" # struct tm with tm_gmtoff and tm_zone: glibc's 56 echo "and both run on our own C++ runtime and C library" diff --git a/tests/golden/linux-libc-exec.sh b/tests/golden/linux-libc-exec.sh index 2f0728266..d3dbd1b5d 100644 --- a/tests/golden/linux-libc-exec.sh +++ b/tests/golden/linux-libc-exec.sh @@ -14,6 +14,22 @@ # tgmath-selection.c, wide-printf.c, wide-scanf.c, c23-library.c # strtod-rounding.c strtold at the x87's 64 bits and binary128's 113 # math-special.c ilogbl and logbl at long double's range +# posix-files.c mkstemp's family, realpath through links, fdopen, +# and freopen keeping the stream it was given +# posix-strings.c, posix-stdio.c +# POSIX's strings, getopt, generators, memory streams: +# built with no -I, so must be the C +# library's and not the compiler's declarations-only one +# posix-fd.c, posix-process.c, posix-signals.c, posix-net.c +# descriptors and the *at calls, fork/spawn/system/popen, +# signals the kernel delivers, sockets and lookups. Each +# program is also the guest's /bin/sh, which +# posix-process.c is written to be (see it) +# posix-time.c TZ rules, mktime's tm_isdst, %z/%Z, strptime +# malloc-gnu.c : usable sizes, mallinfo2, valloc/pvalloc +# posix-threads.c pthreads, semaphores and C11 threads, with real threads +# posix-glob.c glob, nftw, ftw and syslog over a real tree +# posix-regex.c regcomp/regexec, 88 cases with glibc's answers # # At -O0 and -O2, each exiting 42. Skipped without a kernel for # tests/harness/linux (EMBCC_LINUX_KERNEL_X86_64 / _AARCH64). @@ -25,7 +41,9 @@ out=$EMBCC_ROOT/tests/golden/out/linux-libc-exec rm -rf "${out:?}"; mkdir -p "$out/root/tmp" progs="tmpfile next-toward quick-exit multibyte-stdlib inttypes-formats tgmath-selection wide-streams wide-printf wide-scanf c23-library - strtod-rounding math-special" + strtod-rounding math-special posix-files posix-strings posix-stdio + posix-fd posix-process posix-signals posix-net posix-time + malloc-gnu posix-threads posix-glob posix-regex" A64LD=${EMBCC_AARCH64_LD:-aarch64-elf-ld} A64GCC=$(dirname "$(aarch64-elf-gcc -print-libgcc-file-name 2>/dev/null)") @@ -59,6 +77,9 @@ for arch in x86_64 aarch64; do "$LIBDIR/libc.a" -L"$A64GCC" -lgcc > "$out/$tag.ld" 2>&1 fi || { echo "FAIL $tag: did not link"; head -3 "$out/$tag.ld" fail=1; continue; } + mkdir -p "$out/root/bin" + cp "$out/$tag.bin" "$out/root/bin/sh" + chmod 755 "$out/root/bin/sh" EMBCC_LINUX_ROOT=$out/root \ "$EMBCC_ROOT/tests/harness/linux/run.sh" $arch "$out/$tag.bin" \ > "$out/$tag.run" 2>&1 diff --git a/tests/golden/linux.sh b/tests/golden/linux.sh index 4f4ef7ea9..c4d6b90a8 100644 --- a/tests/golden/linux.sh +++ b/tests/golden/linux.sh @@ -684,3 +684,38 @@ echo "and the C++ layer over it: std::thread joined and detached, std::mutex kept 10000 contended increments exact, std::atomic::wait and notify blocked and woke through the futex, and each thread had its own thread_local while the creator's kept the template's value" + +# A Makefile written for glibc names the libraries glibc split out once +# and keeps as empty archives now (2.34): -lpthread -ldl -lrt -lutil. +# EmbCC's libc has their functions, so the driver takes the names as its +# own, as it takes -lm, and the program links and runs. +cat > "$out/glibcnames.c" << 'GN' +#include +#include +static void *f(void *a) { return a; } +int main(void) +{ + pthread_t t; + void *r; + struct timespec ts; + if (pthread_create(&t, 0, f, (void *)42) || pthread_join(t, &r) || + clock_gettime(CLOCK_MONOTONIC, &ts)) + return 1; + return (int)(long)r; +} +GN +"$EMBCC" --target="$triple" "$out/glibcnames.c" -pthread -lpthread -ldl \ + -lrt -lutil -lm -o "$out/glibcnames" 2> "$out/glibcnames-ld.log" || { + echo "FAIL: -lpthread -ldl -lrt -lutil -lm did not link:" + cat "$out/glibcnames-ld.log"; exit 1; } +set +e +if [ "$runner" = native ]; then + "$out/glibcnames" > /dev/null 2>&1 +else + "$EMBCC_ROOT/tests/harness/linux/run.sh" "$ARCH" "$out/glibcnames" \ + > "$out/glibcnames.txt" 2>&1 +fi +rc=$? +set -e +[ "$rc" = 42 ] || { echo "FAIL: the -lpthread program exited $rc, wanted 42"; exit 1; } +echo "and -lpthread -ldl -lrt -lutil -lm, glibc's names, link through the driver" diff --git a/tests/harness/x86_64/sys.c b/tests/harness/x86_64/sys.c index 0777b8a48..56c9b1218 100644 --- a/tests/harness/x86_64/sys.c +++ b/tests/harness/x86_64/sys.c @@ -75,12 +75,18 @@ int fstat(int fd, struct stat *st) int isatty(int fd) { (void)fd; return 1; } int getpid(void) { return 1; } +/* newlib's raise ends here for a signal left at its default. EmbCC's C + * library has a kill of its own -- one process, so a signal to itself is + * delivered by the library -- and tests/golden/libc.sh builds this file + * with SYS_NO_KILL, so that one is not overridden by this. */ +#ifndef SYS_NO_KILL int kill(int pid, int sig) { (void)pid; (void)sig; errno = EINVAL; return -1; } +#endif extern char __heap_start[], __heap_end[];