diff --git a/.gitignore b/.gitignore index 3d392c1363..e7dcd0d68d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ sysroot/ **/target/ .gdb_history *.patch +*.swp +*.swo +/.vim +.vscode/ + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b697aa503..61e301ae68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,66 +1,73 @@ image: "redoxos/redoxer:latest" +variables: + GIT_SUBMODULE_STRATEGY: recursive + +workflow: + rules: + - if: '$CI_COMMIT_BRANCH == "master" && $CI_PROJECT_NAMESPACE == "redox-os"' + - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' + stages: - build + - cross-build - test - -default: - before_script: - - apt-get update -qq - - apt-get install -qq git - - git submodule sync --recursive - - git submodule update --init --recursive - - cache: - paths: - - target/ - -build:linux: - stage: build - script: - - rustup toolchain add "$(cat rust-toolchain)" - - rustup show # Print version info for debugging - - make -j "$(nproc)" all - -build:redox: - stage: build - variables: - TARGET: x86_64-unknown-redox - script: - - export RUSTUP_TOOLCHAIN="$HOME/.redoxer/toolchain" - - export PATH="$RUSTUP_TOOLCHAIN/bin:$PATH" - - rustup show # Print version info for debugging - - make -j "$(nproc)" all - -test:linux: - stage: test - needs: - - build:linux - dependencies: - - build:linux - script: - - make test - -test:redox: - stage: test - needs: - - build:redox - dependencies: - - build:redox - variables: - TARGET: x86_64-unknown-redox - script: - - export CARGO_TEST="redoxer" - - export RUSTUP_TOOLCHAIN="$HOME/.redoxer/toolchain" - - export PATH="$RUSTUP_TOOLCHAIN/bin:$PATH" - - export TEST_RUNNER="redoxer exec --folder . -- sh --" - - make test - # TODO: Out of memory - allow_failure: true +before_script: + cargo install cbindgen fmt: - stage: test + stage: build needs: [] script: - rustup component add rustfmt-preview - ./fmt.sh -- --check + +linux: + stage: build + script: + - ./check.sh --host + +x86_64: + stage: build + script: + - ./check.sh --arch=x86_64 + +i586: + stage: cross-build + script: + - ./check.sh --arch=i586 + +aarch64: + stage: cross-build + image: "redoxos/redoxer:aarch64" + script: + - ./check.sh --arch=aarch64 + +riscv64gc: + stage: cross-build + script: + - ./check.sh --arch=riscv64gc + +test:linux: + stage: test + needs: [linux] + script: + - ./check.sh --host --test + +test:x86_64: + stage: test + needs: [x86_64] + script: + # timeout: https://gitlab.redox-os.org/redox-os/relibc/-/issues/238 + - timeout -s KILL 9m ./check.sh --arch=x86_64 --test + +test:aarch64: + stage: test + needs: [aarch64] + image: "redoxos/redoxer:aarch64" + # many issues that not exist in x86_64, and lack of interest to fix so far + allow_failure: true + script: + - timeout -s KILL 9m ./check.sh --arch=aarch64 --test + +#TODO: Enable more arch once dynamic linker working diff --git a/.gitmodules b/.gitmodules index aaee5b3c47..50a75a071b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,7 @@ [submodule "openlibm"] path = openlibm url = https://gitlab.redox-os.org/redox-os/openlibm.git -[submodule "ralloc"] - path = ralloc - url = https://gitlab.redox-os.org/redox-os/ralloc.git -[submodule "core_io"] - path = core_io - url = https://gitlab.redox-os.org/redox-os/core_io.git -[submodule "posix-regex"] - path = posix-regex - url = https://gitlab.redox-os.org/redox-os/posix-regex.git -[submodule "pthreads-emb"] - path = pthreads-emb - url = https://gitlab.redox-os.org/redox-os/pthreads-emb.git + branch = master +[submodule "src/dlmalloc-rs"] + path = dlmalloc-rs + url = https://gitlab.redox-os.org/redox-os/dlmalloc-rs.git diff --git a/Cargo.lock b/Cargo.lock index b020fc4764..307ca75963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,86 +1,141 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] +version = 4 [[package]] -name = "atty" -version = "0.2.14" +name = "argon2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ - "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "base64ct", + "blake2", + "cpufeatures", + "password-hash", ] [[package]] name = "autocfg" -version = "1.0.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "bitflags" -version = "1.2.1" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "cbindgen" -version = "0.13.2" +name = "bcrypt-pbkdf" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" dependencies = [ - "clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "blowfish", + "pbkdf2", + "sha2", ] [[package]] -name = "cbitset" -version = "0.1.0" +name = "bitflags" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" +dependencies = [ + "num-traits", ] [[package]] name = "cc" -version = "1.0.58" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "clap" -version = "2.33.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "1.1.22" +source = "git+https://github.com/tea/cc-rs?branch=riscv-abi-arch-fix#588ceacb084af41415690c57688e338a32a1f1b4" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "shlex", ] [[package]] -name = "core_io" -version = "0.1.20181107" +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", ] [[package]] @@ -96,45 +151,92 @@ name = "crtn" version = "0.1.0" [[package]] -name = "getrandom" -version = "0.1.14" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array", + "typenum", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dlmalloc" +version = "0.2.8" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "generic-rt" +version = "0.1.0" + [[package]] name = "goblin" -version = "0.0.21" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log", + "plain", + "scroll", ] [[package]] -name = "hermit-abi" -version = "0.1.15" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", + "digest", ] [[package]] -name = "itoa" -version = "0.4.6" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] [[package]] -name = "lazy_static" -version = "1.2.0" +name = "ioslice" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "5e571352c8a3b89074d12e3ee5173ffe162159105352aaaf1fc5764da747e31b" [[package]] name = "ld_so" @@ -142,459 +244,486 @@ version = "0.1.0" [[package]] name = "libc" -version = "0.2.72" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.18+rb0.2.5" +dependencies = [ + "bitflags", + "libc", + "plain", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] [[package]] name = "log" -version = "0.4.8" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "digest", ] [[package]] name = "memchr" -version = "2.3.3" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memoffset" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "git+https://gitlab.redox-os.org/andypython/object#7270e3f0d06e5ef4c2b80abc6166d31f4ddf4fea" +dependencies = [ + "memchr", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "sha2", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", ] [[package]] name = "plain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "posix-regex" -version = "0.1.0" - -[[package]] -name = "ppv-lite86" -version = "0.2.8" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66df580334caab2f744839ab1be85493d7ec731a92d6cf928008ab0b212bf3bc" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro2" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-ident", ] [[package]] name = "quote" -version = "0.6.13" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quote" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ralloc" -version = "1.0.0" -dependencies = [ - "ralloc_shim 0.1.1", - "unborrow 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ralloc_shim" -version = "0.1.1" -dependencies = [ - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "sc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "rand" -version = "0.5.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.10.1", ] [[package]] name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_jitter" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdcd80e68f0a8f9ca5ec7cfd02fd5fbb8fbe6ef4e9b90ea2f48bb929b74f88e" +dependencies = [ + "libc", + "rand_core 0.10.1", + "windows-sys", +] + +[[package]] +name = "rand_xorshift" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redox-ioctl" +version = "0.1.0" +dependencies = [ + "drm-sys", + "redox_syscall", +] + +[[package]] +name = "redox-path" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" + +[[package]] +name = "redox-rt" +version = "0.1.0" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "generic-rt", + "goblin", + "ioslice", + "libredox", + "plain", + "redox-path", + "redox_syscall", ] [[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_core" -version = "0.5.1" +name = "redox_event" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5018d583d6d2f5499352aea8d177e9067d1eb03ab17c78169d5ba7a30001b15" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "libredox", + "redox_syscall", ] [[package]] name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "redox_syscall" -version = "0.2.0" -source = "git+https://gitlab.redox-os.org/redox-os/syscall?rev=4115e0f43547449ce56f7d7749732813e9505955#4115e0f43547449ce56f7d7749732813e9505955" +version = "0.9.0+rb0.2.5" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", ] [[package]] name = "relibc" -version = "0.1.0" +version = "0.2.5+rb0.2.5" dependencies = [ - "cbindgen 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cbitset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", - "core_io 0.1.20181107", - "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "posix-regex 0.1.0", - "ralloc 1.0.0", - "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.2.0 (git+https://gitlab.redox-os.org/redox-os/syscall?rev=4115e0f43547449ce56f7d7749732813e9505955)", - "sc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "argon2", + "base64ct", + "bcrypt-pbkdf", + "bitflags", + "cbitset", + "cc", + "chrono", + "chrono-tz", + "dlmalloc", + "generic-rt", + "ioslice", + "libc", + "libm", + "libredox", + "log", + "md-5", + "memchr", + "object", + "pbkdf2", + "plain", + "posix-regex", + "rand", + "rand_jitter", + "rand_xorshift", + "redox-ioctl", + "redox-path", + "redox-rt", + "redox_event", + "redox_syscall", + "sc", + "scrypt", + "sha-crypt", + "sha2", + "spin", + "unicode-width", ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "salsa20" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cipher", ] -[[package]] -name = "rustc_version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "sc" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scroll" -version = "0.9.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.9.5" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "semver" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "semver" -version = "0.9.0" +name = "scrypt" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "password-hash", + "pbkdf2", + "salsa20", + "sha2", ] [[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.114" +name = "sha-crypt" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e79009728d8311d42d754f2f319a975f9e38f156fd5e422d2451486c78b286" dependencies = [ - "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", + "base64ct", + "sha2", ] [[package]] -name = "serde_derive" -version = "1.0.114" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "serde_json" -version = "1.0.56" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "spin" -version = "0.4.10" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] -name = "strsim" -version = "0.8.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "0.15.44" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "syn" -version = "1.0.33" +name = "typenum" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "tempfile" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "toml" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unborrow" -version = "0.3.1" +name = "unicode-ident" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-targets", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[metadata] -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cbindgen 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2db2df1ebc842c41fd2c4ae5b5a577faf63bd5151b953db752fc686812bee318" -"checksum cbitset 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3a9afa72f63942dd7e7f01c67b863ce9df35c523ae10e3dddd3eec8f1e07eac" -"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6a4013e9182f2345c6b7829b9ef6e670bce0dfca12c6f974457ed2160c2c7fe9" -"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" -"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" -"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" -"checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" -"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" -"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum redox_syscall 0.2.0 (git+https://gitlab.redox-os.org/redox-os/syscall?rev=4115e0f43547449ce56f7d7749732813e9505955)" = "" -"checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum sc 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "176365c8253e381ad147774b6d9730a1b3fe2d7db498af521ed7d968674a55b3" -"checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" -"checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" -"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" -"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" -"checksum serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)" = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" -"checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -"checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" -"checksum unborrow 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e92e959f029e4f8ee25d70d15ab58d2b46f98a17bc238b9265ff0c26f6f3d67f" -"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" -"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[patch.unused]] +name = "redox-scheme" +version = "0.11.2+rb0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 540c420380..0fdeb4f4e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,53 +1,151 @@ [package] name = "relibc" -version = "0.1.0" -authors = ["Jeremy Soller "] -edition = "2018" +version = "0.6.0+rb0.3.0" +authors = ["Jeremy Soller ", "vasilito "] +edition = "2024" [lib] name = "relibc" crate-type = ["staticlib"] [workspace] -members = ["src/crt0", "src/crti", "src/crtn", "src/ld_so"] -exclude = ["core_io", "ralloc"] +members = [ + "src/crt0", + "src/crti", + "src/crtn", + "redox-rt", + "ld_so", + "generic-rt", +] +exclude = ["tests", "dlmalloc-rs"] + +[workspace.lints.clippy] +borrow_as_ptr = "deny" +cast_lossless = "warn" # TODO review occurrences +cast_possible_truncation = "allow" # TODO review occurrences +cast_possible_wrap = "allow" # TODO review occurrences +cast_precision_loss = "allow" # TODO review occurrences +cast_ptr_alignment = "allow" # TODO review occurrences +cast_sign_loss = "allow" # TODO review occurrences +missing_errors_doc = "allow" # TODO review occurrences +missing_panics_doc = "allow" # TODO review occurrences +missing_safety_doc = "allow" # TODO review occurrences +mut_from_ref = "warn" # TODO review occurrences +precedence = "deny" +ptr_as_ptr = "warn" # TODO review occurrences +ptr_cast_constness = "warn" # TODO review occurrences +ref_as_ptr = "warn" # TODO review occurrences +upper_case_acronyms = "allow" # TODO review occurrences +zero_ptr = "warn" # must allow on public constants due to cbindgen issue + +[workspace.lints.rust] +dangling_pointers_from_temporaries = "deny" +dead_code = "allow" # TODO review occuurences +deprecated = "deny" +improper_ctypes_definitions = "deny" +internal_features = "allow" # core_intrinsics and lang_items +irrefutable_let_patterns = "deny" +mismatched_lifetime_syntaxes = "deny" +non_camel_case_types = "allow" # needed for most POSIX type names +non_snake_case = "allow" # TODO review occuurences +non_upper_case_globals = "allow" # TODO review occuurences +unexpected_cfgs = "deny" +unpredictable_function_pointer_comparisons = "deny" +unreachable_code = "allow" # TODO review occuurences +unsafe_op_in_unsafe_fn = "deny" +unused_imports = "deny" +unused_must_use = "deny" +unused_mut = "deny" +unused_unsafe = "deny" +unused_variables = "allow" # TODO review occurrences (too many for now) + +[lints] +workspace = true + +[workspace.dependencies] +bitflags = "2" +ioslice = { version = "0.6", default-features = false } +plain = "0.2" +redox-path = "0.3" +redox_protocols = { package = "libredox", path = "../libredox", default-features = false, features = ["protocol"] } +redox_syscall = { path = "../syscall" } [build-dependencies] -cbindgen = "0.13.2" -cc = "1.0.25" +cc = "1" [dependencies] -cbitset = "0.1.0" -core_io = { path = "core_io", features = ["collections"] } -lazy_static = { version = "1.2.0", features = ["nightly", "spin_no_std"] } -memoffset = "0.5.1" -posix-regex = { path = "posix-regex", features = ["no_std"] } -rand = { version = "0.5.5", default-features = false } +bitflags.workspace = true +cbitset = "0.2" +posix-regex = { version = "0.1.4", features = ["no_std"] } + +rand = { version = "0.10", default-features = false } +rand_xorshift = "0.5" +rand_jitter = "0.6" + memchr = { version = "2.2.0", default-features = false } +plain.workspace = true +unicode-width = "0.1" +__libc_only_for_layout_checks = { package = "libc", version = "0.2.149", optional = true } +md5-crypto = { package = "md-5", version = "0.10.6", default-features = false } +sha-crypt = { version = "0.5", default-features = false } +base64ct = { version = "1.6", default-features = false, features = ["alloc"] } +bcrypt-pbkdf = { version = "0.10", default-features = false, features = [ + "alloc", +] } +scrypt = { version = "0.11", default-features = false, features = ["simple"] } +pbkdf2 = { version = "0.12", features = ["sha2"] } +sha2 = { version = "0.10", default-features = false } +generic-rt = { path = "generic-rt" } +chrono-tz = { version = "0.10", default-features = false } +chrono = { version = "0.4", default-features = false, features = ["alloc"] } +libm = "0.2" +log = "0.4" +spin = "0.9.8" +argon2 = "0.5.3" -[dependencies.goblin] -version = "0.0.21" +[dependencies.dlmalloc] +path = "dlmalloc-rs" default-features = false -features = ["elf32", "elf64", "endian_fd"] +features = ["c_api"] -[dependencies.ralloc] -path = "ralloc" +[dependencies.object] +version = "0.36.7" +git = "https://gitlab.redox-os.org/andypython/object" default-features = false -optional = true +features = ["elf", "read_core"] [target.'cfg(target_os = "linux")'.dependencies] -sc = "0.2.3" +sc = "0.2.7" [target.'cfg(target_os = "redox")'.dependencies] -redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall", rev = "4115e0f43547449ce56f7d7749732813e9505955" } -spin = "0.4.10" +redox_syscall.workspace = true +redox-rt = { path = "redox-rt" } +redox-path.workspace = true +redox_event = { version = "0.4.8", default-features = false, features = [ + "redox_syscall", +] } +ioslice.workspace = true +redox-ioctl = { path = "redox-ioctl" } +redox_protocols.workspace = true [features] -default = [] -trace = [] +# to enable trace level, take out this `no_trace` +default = ["check_against_libc_crate", "ld_so_cache", "no_trace"] +check_against_libc_crate = ["__libc_only_for_layout_checks"] +ld_so_cache = [] +math_libm = [] +no_trace = ["log/release_max_level_debug"] +# for very verbose activity beyond trace level +trace_tls = [] [profile.dev] panic = "abort" [profile.release] panic = "abort" + +[patch.crates-io] +cc-11 = { git = "https://github.com/tea/cc-rs", branch = "riscv-abi-arch-fix", package = "cc" } +redox_syscall = { path = "../syscall" } +libredox = { path = "../libredox" } +redox-scheme = { path = "../redox-scheme" } diff --git a/Makefile b/Makefile index 1fc07c26bf..82e7170115 100644 --- a/Makefile +++ b/Makefile @@ -1,59 +1,63 @@ -TARGET?= +include config.mk CARGO?=cargo CARGO_TEST?=$(CARGO) -CARGOFLAGS?= +CARGO_COMMON_FLAGS=-Z build-std=core,alloc,compiler_builtins +CARGOFLAGS?=$(CARGO_COMMON_FLAGS) +CC_WRAPPER?= RUSTCFLAGS?= +LINKFLAGS?=-lgcc +USE_RUST_LIBM?= +TESTBIN?= +export OBJCOPY?=objcopy -# When using xargo, build it in local location -export XARGO_HOME=$(CURDIR)/target/xargo - -export OBJCOPY=objcopy - -BUILD=target -ifneq ($(TARGET),) - BUILD="target/$(TARGET)" - CARGOFLAGS+="--target=$(TARGET)" +export CARGO_TARGET_DIR?=$(shell pwd)/target +BUILD?=$(CARGO_TARGET_DIR)/$(TARGET) +CARGOFLAGS+=--target=$(TARGET) +EXCEPT_MATH=-not -name "math" +FEATURE_MATH= +ifneq ($(USE_RUST_LIBM),) +FEATURE_MATH=--features math_libm +EXCEPT_MATH= endif -ifeq ($(TARGET),aarch64-unknown-linux-gnu) - export CC=aarch64-linux-gnu-gcc - export LD=aarch64-linux-gnu-ld - export AR=aarch64-linux-gnu-ar - export OBJCOPY=aarch64-linux-gnu-objcopy -endif +TARGET_HEADERS?=$(BUILD)/include +export CFLAGS=-I$(TARGET_HEADERS) -ifeq ($(TARGET),aarch64-unknown-redox) - export CC=aarch64-unknown-redox-gcc - export LD=aarch64-unknown-redox-ld - export AR=aarch64-unknown-redox-ar - export OBJCOPY=aarch64-unknown-redox-objcopy -endif +PROFILE?=release -ifeq ($(TARGET),x86_64-unknown-redox) - export CC=x86_64-unknown-redox-gcc - export LD=x86_64-unknown-redox-ld - export AR=x86_64-unknown-redox-ar - export OBJCOPY=x86_64-unknown-redox-objcopy -endif +HEADERS_UNPARSED=$(shell find src/header -mindepth 1 -maxdepth 1 -type d -not -name "_*" $(EXCEPT_MATH) -printf "%f\n") +HEADERS_DEPS=$(shell find src/header -type f \( -name "cbindgen.toml" -o -name "*.rs" \)) +#HEADERS=$(patsubst %,%.h,$(subst _,/,$(HEADERS_UNPARSED))) SRC=\ Cargo.* \ - $(shell find src -type f) + $(shell find src/ redox-rt/src/ ld_so/src/ redox-ioctl/src/ include/ -type f) -# FIXME: Remove the following line. It's only required since xargo automatically links with compiler_builtins, which conflicts with the compiler_builtins that rustc always links with. -WEAKEN_SYMBOLS=\ - -W __divti3 \ - -W __fixdfti \ - -W __floattidf \ - -W __muloti4 \ - -W __udivti3 \ - -W __umodti3 \ - -W __rust_probestack +BUILTINS_VERSION=0.1.70 -.PHONY: all clean fmt install install-headers libs submodules test +.PHONY: all clean fmt install install-libs install-headers install-tests libs headers submodules test -all: | libs +all: | headers libs + +headers: $(HEADERS_DEPS) + rm -rf $(TARGET_HEADERS) + mkdir -p $(TARGET_HEADERS) + cp -r include/* $(TARGET_HEADERS) +ifeq ($(USE_RUST_LIBM),) + cp "openlibm/include"/*.h $(TARGET_HEADERS) + cp "openlibm/src"/*.h $(TARGET_HEADERS) +endif + @set -e ; \ + for header in $(HEADERS_UNPARSED); do \ + if test -f "src/header/$$header/cbindgen.toml"; then \ + echo -e "\033[0;36;49mWriting Header $$header\033[0m"; \ + out=`echo "$$header" | sed 's/_/\//g'`; \ + out="$(TARGET_HEADERS)/$$out.h"; \ + cat "src/header/$$header/cbindgen.toml" cbindgen.globdefs.toml \ + | cbindgen "src/header/$$header/mod.rs" --config=/dev/stdin --output "$$out" 2>/dev/null; \ + fi \ + done; echo -e "\033[0;36;49mAll headers written\033[0m"; clean: $(CARGO) clean @@ -66,33 +70,40 @@ check: fmt: ./fmt.sh -install-headers: libs +install-headers: headers libs mkdir -pv "$(DESTDIR)/include" - cp -rv "include"/* "$(DESTDIR)/include" - cp -rv "target/include"/* "$(DESTDIR)/include" - cp -v "openlibm/include"/*.h "$(DESTDIR)/include" - cp -v "openlibm/src"/*.h "$(DESTDIR)/include" - cp -v "pthreads-emb/"*.h "$(DESTDIR)/include" + cp -rv "$(TARGET_HEADERS)"/* "$(DESTDIR)/include" libs: \ - $(BUILD)/release/libc.a \ - $(BUILD)/release/libc.so \ - $(BUILD)/release/crt0.o \ - $(BUILD)/release/crti.o \ - $(BUILD)/release/crtn.o \ - $(BUILD)/release/ld_so + $(BUILD)/$(PROFILE)/libc.a \ + $(BUILD)/$(PROFILE)/libc.so \ + $(BUILD)/$(PROFILE)/crt0.o \ + $(BUILD)/$(PROFILE)/crti.o \ + $(BUILD)/$(PROFILE)/crtn.o \ + $(BUILD)/$(PROFILE)/ld.so -install-libs: libs +install-libs: headers libs mkdir -pv "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/libc.a" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/libc.so" "$(DESTDIR)/lib" - ln -frsv "$(DESTDIR)/lib/libc.so" "$(DESTDIR)/lib/libc.so.6" - cp -v "$(BUILD)/release/crt0.o" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/crti.o" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/crtn.o" "$(DESTDIR)/lib" - cp -v "$(BUILD)/release/ld_so" "$(DESTDIR)/lib/ld64.so.1" + cp -v "$(BUILD)/$(PROFILE)/libc.a" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/libc.so" "$(DESTDIR)/lib" + ln -vnfs libc.so "$(DESTDIR)/lib/libc.so.6" + cp -v "$(BUILD)/$(PROFILE)/crt0.o" "$(DESTDIR)/lib" + ln -vnfs crt0.o "$(DESTDIR)/lib/crt1.o" + cp -v "$(BUILD)/$(PROFILE)/crti.o" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/crtn.o" "$(DESTDIR)/lib" + cp -v "$(BUILD)/$(PROFILE)/ld.so" "$(DESTDIR)/$(LD_SO_PATH)" +ifeq ($(USE_RUST_LIBM),) cp -v "$(BUILD)/openlibm/libopenlibm.a" "$(DESTDIR)/lib/libm.a" - cp -v "$(BUILD)/pthreads-emb/libpthread.a" "$(DESTDIR)/lib/libpthread.a" +endif + # Empty libraries for dl, pthread, and rt + $(AR) -rcs "$(DESTDIR)/lib/libdl.a" + $(AR) -rcs "$(DESTDIR)/lib/libpthread.a" + $(AR) -rcs "$(DESTDIR)/lib/librt.a" + +install-tests: tests + $(MAKE) -C tests + mkdir -p "$(DESTDIR)/relibc-tests" + cp -vr tests/build_$(TARGET)/* "$(DESTDIR)/relibc-tests/" install: install-headers install-libs @@ -100,96 +111,101 @@ submodules: git submodule sync git submodule update --init --recursive -sysroot: all +sysroot: + @mkdir -p $@ + +.PHONY: sysroot/$(TARGET) +sysroot/$(TARGET): | sysroot rm -rf $@ rm -rf $@.partial mkdir -p $@.partial - $(MAKE) install DESTDIR=$@.partial + $(MAKE) install DESTDIR=$(shell pwd)/$@.partial mv $@.partial $@ touch $@ -test: sysroot +test: sysroot/$(TARGET) # TODO: Fix SIGILL when running cargo test # $(CARGO_TEST) test - $(MAKE) -C tests verify + $(MAKE) -C tests run + +test-once: sysroot/$(TARGET) + $(MAKE) -C tests run-once TESTBIN=$(TESTBIN) + + +$(BUILD)/$(PROFILE)/libc.so: $(BUILD)/$(PROFILE)/libc.a + $(CC) -nostdlib \ + -shared \ + -Wl,--gc-sections \ + -Wl,-z,pack-relative-relocs \ + -Wl,--sort-common \ + -Wl,--whole-archive $^ -Wl,--no-whole-archive \ + -Wl,-soname,libc.so.6 \ + $(LINKFLAGS) \ + -o $@ + +$(BUILD)/$(PROFILE)/ld.so: $(BUILD)/$(PROFILE)/ld_so.o $(BUILD)/$(PROFILE)/libc.a + # TODO: merge ld.so with libc.so: --dynamic-list=dynamic-list-file + $(LD) --shared -Bsymbolic --no-relax -T ld_so/ld_script/$(TARGET).ld --gc-sections $^ -o $@ + +$(BUILD)/$(PROFILE)/libc.a: $(BUILD)/$(PROFILE)/librelibc.a $(BUILD)/openlibm/libopenlibm.a + echo "create $@" > "$@.mri" + for lib in $^; do\ + echo "addlib $$lib" >> "$@.mri"; \ + done + echo "save" >> "$@.mri" + echo "end" >> "$@.mri" + $(AR) -M < "$@.mri" # Debug targets -$(BUILD)/debug/libc.a: $(BUILD)/debug/librelibc.a $(BUILD)/pthreads-emb/libpthread.a $(BUILD)/openlibm/libopenlibm.a - echo "create $@" > "$@.mri" - for lib in $^; do\ - echo "addlib $$lib" >> "$@.mri"; \ - done - echo "save" >> "$@.mri" - echo "end" >> "$@.mri" - $(AR) -M < "$@.mri" - -$(BUILD)/debug/libc.so: $(BUILD)/debug/librelibc.a $(BUILD)/pthreads-emb/libpthread.a $(BUILD)/openlibm/libopenlibm.a - $(CC) -nostdlib -shared -Wl,--allow-multiple-definition -Wl,--whole-archive $^ -Wl,--no-whole-archive -Wl,-soname,libc.so.6 -o $@ - $(BUILD)/debug/librelibc.a: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) - # FIXME: Remove the following line. It's only required since xargo automatically links with compiler_builtins, which conflicts with the compiler_builtins that rustc always links with. - $(OBJCOPY) $@ $(WEAKEN_SYMBOLS) + $(CARGO) rustc $(CARGOFLAGS) $(FEATURE_MATH) -- --emit link=$@ -g -C debug-assertions=no $(RUSTCFLAGS) + ./renamesyms.sh "$@" "$(BUILD)/debug/deps/" + ./stripcore.sh "$@" touch $@ $(BUILD)/debug/crt0.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/debug/crti.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/debug/crtn.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/debug/ld_so.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --manifest-path src/ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort -g -C debug-assertions=no $(RUSTCFLAGS) touch $@ -$(BUILD)/debug/ld_so: $(BUILD)/debug/ld_so.o $(BUILD)/debug/crti.o $(BUILD)/debug/libc.a $(BUILD)/debug/crtn.o - $(LD) --no-relax -T src/ld_so/ld_script --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@ - # Release targets -$(BUILD)/release/libc.a: $(BUILD)/release/librelibc.a $(BUILD)/pthreads-emb/libpthread.a $(BUILD)/openlibm/libopenlibm.a - echo "create $@" > "$@.mri" - for lib in $^; do\ - echo "addlib $$lib" >> "$@.mri"; \ - done - echo "save" >> "$@.mri" - echo "end" >> "$@.mri" - $(AR) -M < "$@.mri" - -$(BUILD)/release/libc.so: $(BUILD)/release/librelibc.a $(BUILD)/pthreads-emb/libpthread.a $(BUILD)/openlibm/libopenlibm.a - $(CC) -nostdlib -shared -Wl,--allow-multiple-definition -Wl,--whole-archive $^ -Wl,--no-whole-archive -Wl,-soname,libc.so.6 -o $@ - $(BUILD)/release/librelibc.a: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --release $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) - $(OBJCOPY) $@ $(WEAKEN_SYMBOLS) + $(CARGO) rustc --release $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) + @# TODO: Better to only allow a certain whitelisted set of symbols? Perhaps + @# use some cbindgen hook, specify them manually, or grep for #[unsafe(no_mangle)]. + ./renamesyms.sh "$@" "$(BUILD)/release/deps/" + ./stripcore.sh "$@" touch $@ $(BUILD)/release/crt0.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --release --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --release --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/release/crti.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --release --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --release --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/release/crtn.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --release --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --release --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ $(BUILD)/release/ld_so.o: $(SRC) - CARGO_INCREMENTAL=0 $(CARGO) rustc --release --manifest-path src/ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) + $(CARGO) rustc --release --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS) touch $@ -$(BUILD)/release/ld_so: $(BUILD)/release/ld_so.o $(BUILD)/release/crti.o $(BUILD)/release/libc.a $(BUILD)/release/crtn.o - $(LD) --no-relax -T src/ld_so/ld_script --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@ - # Other targets $(BUILD)/openlibm: openlibm @@ -199,15 +215,12 @@ $(BUILD)/openlibm: openlibm mv $@.partial $@ touch $@ -$(BUILD)/openlibm/libopenlibm.a: $(BUILD)/openlibm $(BUILD)/release/librelibc.a - $(MAKE) CC=$(CC) CPPFLAGS="-fno-stack-protector -I $(shell pwd)/include -I $(shell pwd)/target/include" -C $< libopenlibm.a - -$(BUILD)/pthreads-emb: pthreads-emb - rm -rf $@ $@.partial - mkdir -p $(BUILD) - cp -r $< $@.partial - mv $@.partial $@ - touch $@ - -$(BUILD)/pthreads-emb/libpthread.a: $(BUILD)/pthreads-emb $(BUILD)/release/librelibc.a - $(MAKE) CC=$(CC) CFLAGS="-fno-stack-protector -I $(shell pwd)/include -I $(shell pwd)/target/include" -C $< libpthread.a +ifeq ($(USE_RUST_LIBM),) +$(BUILD)/openlibm/libopenlibm.a: $(BUILD)/openlibm $(BUILD)/$(PROFILE)/librelibc.a + $(MAKE) -s AR=$(AR) CC="$(CC_WRAPPER) $(CC)" LD=$(LD) CPPFLAGS="$(CPPFLAGS) -fno-stack-protector -I$(shell pwd)/include -I$(TARGET_HEADERS)" -C $< libopenlibm.a + ./renamesyms.sh "$@" "$(BUILD)/release/deps/" +else +$(BUILD)/openlibm/libopenlibm.a: + mkdir -p "$(BUILD)/openlibm" + $(AR) -rcs "$(BUILD)/openlibm/libopenlibm.a" +endif diff --git a/README.md b/README.md index 786555f559..0e8c6628dc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,173 @@ -# relibc ![build](https://travis-ci.org/redox-os/relibc.svg?branch=master) -relibc is a portable POSIX C standard library written in Rust. It is under heavy development, and currently supports Redox and Linux. +# Redox C Library (relibc) -The motivation for this project is twofold: Reduce issues the redox crew was having with newlib, and create a safer alternative to a C standard library written in C. It is mainly designed to be used under redox, as an alternative to newlib, but it also supports linux syscalls via the [sc](https://crates.io/crates/sc) crate. +relibc is a portable C standard library written in Rust and is under heavy development, this library contain the following items: -### [Contributing](CONTRIBUTING.md) +- C, Linux, BSD functions and extensions +- POSIX compatibility layer +- Interfaces for system components + +The motivation for this project is twofold: Reduce issues that the Redox developers were having with [newlib](https://sourceware.org/newlib/), and create a more stable and safe alternative to C standard libraries written in C. It is mainly designed to be used under Redox, as an alternative to newlib, but it also supports Linux via the [sc](https://crates.io/crates/sc) crate. + +Currently Redox and Linux are supported. + +## `redox-rt` + +`redox-rt` is a runtime library that provides much of the code that enables POSIX on Redox, like `fork`, `exec`, signal handling, etc. +Relibc uses it as backend in `src/platform/redox`, and it's intended to eventually be usable independently, without relibc. + +## Repository Layout + +- `include` - Header files (mostly macros and variadic functions `cbindgen` can't generate) +- `src` - Source files +- `src/c` - C code +- `src/crt0` - Runtime code +- `src/crti` - Runtime code +- `src/crtn` - Runtime code +- `src/header` - Header files implementation +- `src/header/*` - Each folder has a `cbindgen.toml` file, it generates a C-to-Rust interface and header files +- `src/ld_so` - Dynamic loader code +- `src/platform` - Platform-specific and common code +- `src/platform/redox` - Redox-specific code +- `src/platform/linux` - Linux-specific code +- `src/pthread` - pthread implementation +- `src/sync` - Synchronization primitives +- `tests` - C tests (each MR needs to give success in all of them) + +## Download the sources + +To download the relibc sources run the following command: + +```sh +git clone --recursive https://gitlab.redox-os.org/redox-os/relibc +``` + +## Build Instructions + +To build relibc out of the Redox build system, do the following steps: + +### Dependencies + +- Install `cbindgen` + +```sh +cargo install cbindgen +``` + +#### Install the `expect` tool + +- Debian, Ubuntu and PopOS: + +```sh +sudo apt install expect +``` + +- Fedora: + +```sh +sudo dnf install expect +``` + +- Arch Linux: + +```sh +sudo pacman -S expect +``` + +### Build Relibc + +To build the relibc library objects, run the following command: + +```sh +make all +``` + +- Clean old library objects and tests + +```sh +make clean +``` + +## Build relibc inside the Redox build system + +Inside of your Redox build system, run: + +```sh +make prefix +``` + +If you need to rebuild `relibc` for testing a Cookbook recipe, run: + +```sh +touch relibc +make prefix r.recipe-name +``` + +Touching (changing the "last modified time" of) the `relibc` folder is needed to trigger recompilation for `make prefix`. Replace `recipe-name` with your desired recipe name. + +Note: Do not edit `relibc` inside `prefix` folder! Do your work on `relibc` folder directly inside your Redox build system instead. + +## Tests + +Relibc has a test suite that also runs every time a new commit get pushed. You can see `.gitlab-ci.yml` to see how it's being executed. That being said, `./check.sh` is the recommended way to run tests. Here's few examples: + ++ `./check.sh` - Run build, without running the test ++ `./check.sh --test` - Run all tests in x86_64 Redox using Redoxer ++ `./check.sh --test --host` - Run all tests in host (Linux) ++ `./check.sh --test --arch=aarch64` - Run all tests in specified arch + - Arch can be `x86_64`, `aarch64`, `i586`, or `riscv64gc` ++ `./check.sh --test=stdio/printf` - Run a single test + - Can be combined with `--host` or `--arch` + - Will run statically linked test in Linux, dynamically linked in Redox + +Couple of notes: + +- Relibc and its tests will rebuild if files changed, however switching between arch or host requires you to run `make clean` +- Redoxer is needed to run tests for Redox without `--host`. You can install it using `cargo install redoxer` +- Tests can hangs, the test runner can anticipate this, assuming the kernel doesn't hang too. + +## Issues + +#### I'm building for my own platform which I run, and am getting `x86_64-linux-gnu-ar: command not found` (or similar) + +The Makefile expects GNU compiler tools prefixed with the platform specifier, as would be present when you installed a cross compiler. Since you are building for your own platform, some Linux distributions (like Manjaro) don't install/symlink the prefixed executables. + +An easy fix would be to replace the corresponding lines in `config.mk`, e.g. + +```diff +ifeq ($(TARGET),x86_64-unknown-linux-gnu) +- export CC=x86_64-linux-gnu-gcc +- export LD=x86_64-linux-gnu-ld +- export AR=x86_64-linux-gnu-ar +- export NM=x86_64-linux-gnu-nm ++ export CC=gcc ++ export LD=ld ++ export AR=ar ++ export NM=nm + export OBJCOPY=objcopy + export CPPFLAGS= + LD_SO_PATH=lib/ld64.so.1 +endif +``` + +## Contributing + +Before starting to contribute, read [this](CONTRIBUTING.md) document. ## Supported OSes - - Redox OS - - Linux +- Redox OS +- Linux ## Supported architectures - - x86\_64 - - Aarch64 +- i586 (Intel/AMD) +- x86_64 (Intel/AMD) +- aarch64 (ARM64) +- riscv64gc (RISC-V) + +## Funding - _Unix-style Signals and Process Management_ + +This project is funded through [NGI Zero Core](https://nlnet.nl/core), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/RedoxOS-Signals). + +[NLnet foundation logo](https://nlnet.nl) +[NGI Zero Logo](https://nlnet.nl/core) diff --git a/Xargo.toml b/Xargo.toml deleted file mode 100644 index ea3144eb6a..0000000000 --- a/Xargo.toml +++ /dev/null @@ -1 +0,0 @@ -[dependencies.alloc] diff --git a/bindgen_transform.sh b/bindgen_transform.sh deleted file mode 100755 index b0f122edb8..0000000000 --- a/bindgen_transform.sh +++ /dev/null @@ -1,4 +0,0 @@ -sed -i 's/::std::os::raw:://g' $1 -perl -i -p0e 's/extern "C" \{\n pub fn/#[no_mangle]\npub extern "C" fn/g' $1 -perl -i -p0e 's/;\n\}/ {\n unimplemented!();\n\}\n/g' $1 -rustfmt $1 diff --git a/build.rs b/build.rs index 34834ff76d..e0fd5be41f 100644 --- a/build.rs +++ b/build.rs @@ -1,62 +1,22 @@ -extern crate cbindgen; extern crate cc; -use std::{env, fs, fs::DirEntry, path::Path}; - -// include src/header directories that don't start with '_' -fn include_dir(d: &DirEntry) -> bool { - d.metadata().map(|m| m.is_dir()).unwrap_or(false) - && d.path() - .iter() - .nth(2) - .map_or(false, |c| c.to_str().map_or(false, |x| !x.starts_with("_"))) -} - -fn generate_bindings(cbindgen_config_path: &Path) { - let relative_path = cbindgen_config_path - .strip_prefix("src/header") - .ok() - .and_then(|p| p.parent()) - .and_then(|p| p.to_str()) - .unwrap() - .replace("_", "/"); - let header_path = Path::new("target/include") - .join(&relative_path) - .with_extension("h"); - let mod_path = cbindgen_config_path.with_file_name("mod.rs"); - let config = cbindgen::Config::from_file(cbindgen_config_path).unwrap(); - cbindgen::Builder::new() - .with_config(config) - .with_src(mod_path) - .generate() - .expect("Unable to generate bindings") - .write_to_file(header_path); -} +use std::{env, fs}; fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let _crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let target = env::var("TARGET").unwrap(); - // Generate C includes - // - based on contents of src/header/** - // - headers written to target/include - fs::read_dir(&Path::new("src/header")) - .unwrap() - .into_iter() - .filter_map(Result::ok) - .filter(|d| include_dir(d)) - .map(|d| d.path().as_path().join("cbindgen.toml")) - .filter(|p| p.exists()) - .for_each(|p| { - println!("cargo:rerun-if-changed={:?}", p.parent().unwrap()); - println!("cargo:rerun-if-changed={:?}", p); - println!("cargo:rerun-if-changed={:?}", p.with_file_name("mod.rs")); - generate_bindings(&p); - }); + println!("cargo:rerun-if-changed=src/c"); - cc::Build::new() - .flag("-nostdinc") - .flag("-nostdlib") - .include(&format!("{}/include", crate_dir)) + let mut cc_builder = &mut cc::Build::new(); + + cc_builder = cc_builder.flag("-nostdinc").flag("-nostdlib"); + + if target.starts_with("aarch64") { + cc_builder = cc_builder.flag("-mno-outline-atomics") + } + + cc_builder .flag("-fno-stack-protector") .flag("-Wno-expansion-to-defined") .files( diff --git a/cbindgen.globdefs.toml b/cbindgen.globdefs.toml new file mode 100644 index 0000000000..7e7e19e231 --- /dev/null +++ b/cbindgen.globdefs.toml @@ -0,0 +1,24 @@ + +# needs a leading newline +[defines] +"target_os=redox" = "__redox__" +"target_os=linux" = "__linux__" +"target_pointer_width=64" = "__LP64__" +"target_pointer_width=32" = "__ILP32__" +"target_arch=x86" = "__i386__" +"target_arch=x86_64" = "__x86_64__" +"target_arch=aarch64" = "__aarch64__" +# This is not exact. It should be `defined(__riscv) && defined(__LP64__)`, or `defined(__riscv) && __riscv_xlen==64` +# This will do however, as long as we only support riscv64 and not riscv32 +"target_arch=riscv64" = "__riscv" + +# XXX: silences a warning +"feature = no_std" = "__relibc__" + +# Ensure attributes are passed down from Rust +# must be included where attributes are used in relibc +[fn] +must_use = "__nodiscard" +deprecated = "__deprecated" +deprecated_with_note = "__deprecatedNote({})" +no_return = "__noreturn" diff --git a/check.sh b/check.sh new file mode 100755 index 0000000000..9e4040040b --- /dev/null +++ b/check.sh @@ -0,0 +1,168 @@ +#!/bin/bash + +RED='\033[1;38;5;196m' +GREEN='\033[1;38;5;46m' +NC='\033[0m' + +show_help() { + echo "Usage: $(basename "$0") [OPTIONS]" + echo "" + echo "Description:" + echo " Wrapper for Makefile / Cargo to run checks or tests on Redox OS targets." + echo "" + echo "Options:" + echo " --test Run 'make test' instead of 'make all'" + echo " --test= Run single 'make test'" + echo " --cargo Run 'cargo check' / 'cargo test' instead" + echo " (note: cargo test is currently not maintained for relibc)" + echo " --host Run the command on host (linux) target" + echo " --all-target Run the command on all supported Redox architectures" + echo " --target= Override the target architecture (e.g., i586-unknown-redox)" + echo " --arch= Override the target architecture using arch (e.g., i586)" + echo " --help Show this help message" + echo "" + echo "Supported Targets:" + for t in "${SUPPORTED_TARGETS[@]}"; do + echo " - $t" + done + echo " - $(uname -m)-unknown-linux-gnu" + echo "" + echo "Environment:" + echo " TARGET Sets the default target (overridden by --target)" +} + +if ! command -v cbindgen &> /dev/null; then + echo "Error: 'cbindgen' CLI not found." + echo "Please install it: cargo install cbindgen" + exit 1 +fi + +SUPPORTED_TARGETS=( + "x86_64-unknown-redox" + "i586-unknown-redox" + "aarch64-unknown-redox" + "riscv64gc-unknown-redox" +) + +CURRENT_TARGET="${TARGET:-x86_64-unknown-redox}" +CHECK_ALL=false +CMD_ACTION="make" +CARGO_ACTION="check" +MAKE_ACTION="all" +TEST_BIN="" +IS_HOST=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --all-target) + CHECK_ALL=true + ;; + --test) + MAKE_ACTION="test" + CARGO_ACTION="test" + ;; + --test=*) + TEST_BIN="${1#*=}" + MAKE_ACTION="test-once" + ;; + --cargo) + CMD_ACTION="cargo" + ;; + --host) + CURRENT_TARGET="$(uname -m)-unknown-linux-gnu" + IS_HOST=1 + ;; + --target=*) + CURRENT_TARGET="${1#*=}" + ;; + --arch=*) + CURRENT_TARGET="${1#*=}-unknown-redox" + ;; + --help) + show_help + exit 0 + ;; + *) + echo -e "${RED}Error: Unknown option '$1'${NC}" + show_help + exit 1 + ;; + esac + shift +done + +if [ "$IS_HOST" -eq 0 ]; then +if ! command -v redoxer &> /dev/null; then + echo "Error: 'redoxer' CLI not found." + echo "Please install it: cargo install redoxer" + exit 1 +fi +fi + +run_redoxer() { + export TARGET=$1 + REDOXER_ENV="redoxer env" + if [ "$IS_HOST" -eq 0 ]; then + redoxer toolchain || { echo -e "${RED}Fail: redoxer toolchain for: $target.${NC}" && exit 1; } + export CARGO_TEST="redoxer" + export TEST_RUNNER="redoxer exec --folder ../../sysroot/$TARGET/:/usr --folder . --" +# TODO: Identify hang issue with pthread/barrier and pthread/once tests in multi core to get rid of this limit + export REDOXER_QEMU_ARGS="-smp 1" + + MAKE_ACTION="$MAKE_ACTION IS_REDOX=1" + else + REDOXER_ENV="" + fi + + if [ "$TEST_BIN" != "" ]; then + if [ "$IS_HOST" -eq 0 ]; then + MAKE_ACTION="$MAKE_ACTION TESTBIN=bins_dynamic/$TEST_BIN" + else + MAKE_ACTION="$MAKE_ACTION TESTBIN=bins_static/$TEST_BIN" + fi + fi + + if [ "$CMD_ACTION" == "make" ]; then + CMD_OPT="-j $(nproc) $MAKE_ACTION" + else + CMD_OPT="$CARGO_ACTION" + fi + + echo "----------------------------------------" + echo "Running $REDOXER_ENV $CMD_ACTION $CMD_OPT for: $TARGET" + + if $REDOXER_ENV $CMD_ACTION $CMD_OPT; then + return 0 + else + echo -e "${RED}Fail: $CMD_ACTION $CMD_OPT for $TARGET failed.${NC}" + return 1 + fi +} + +if [ "$CHECK_ALL" = true ]; then + echo "Running $CMD_ACTION for all supported Redox targets..." + + has_error=false + + for target in "${SUPPORTED_TARGETS[@]}"; do + if ! run_redoxer "$target"; then + has_error=true + fi + done + + echo "----------------------------------------" + if [ "$has_error" = true ]; then + echo -e "${RED}Summary: One or more targets failed.${NC}" + exit 1 + else + echo -e "${GREEN}Summary: All targets passed!${NC}" + exit 0 + fi +else + if run_redoxer "$CURRENT_TARGET"; then + echo -e "${GREEN}Success: $CARGO_ACTION for $CURRENT_TARGET passed.${NC}" + exit 0 + else + exit 1 + fi +fi diff --git a/ci.sh b/ci.sh deleted file mode 100755 index 1c5d33b038..0000000000 --- a/ci.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -ex - -./fmt.sh -- --check -if [ -z "$TARGET" ] -then - make all - make test -else - make libs -fi diff --git a/config.mk b/config.mk new file mode 100644 index 0000000000..e9ffa218ae --- /dev/null +++ b/config.mk @@ -0,0 +1,73 @@ +ifndef TARGET + export TARGET:=$(shell rustc -Z unstable-options --print target-spec-json | grep llvm-target | cut -d '"' -f4) +endif + +ifeq ($(TARGET),aarch64-unknown-linux-gnu) + export CC=aarch64-linux-gnu-gcc + export LD=aarch64-linux-gnu-ld + export AR=aarch64-linux-gnu-ar + export NM=aarch64-linux-gnu-nm + export OBJCOPY=aarch64-linux-gnu-objcopy + export CPPFLAGS= + LD_SO_PATH=lib/ld.so.1 +endif + +ifeq ($(TARGET),aarch64-unknown-redox) + export CC=aarch64-unknown-redox-gcc + export LD=aarch64-unknown-redox-ld + export AR=aarch64-unknown-redox-ar + export NM=aarch64-unknown-redox-nm + export OBJCOPY=aarch64-unknown-redox-objcopy + export CPPFLAGS= + LD_SO_PATH=lib/ld.so.1 +endif + +ifeq ($(TARGET),i586-unknown-redox) + export CC=i586-unknown-redox-gcc + export LD=i586-unknown-redox-ld + export AR=i586-unknown-redox-ar + export NM=i586-unknown-redox-nm + export OBJCOPY=i586-unknown-redox-objcopy + export CPPFLAGS= + LD_SO_PATH=lib/libc.so.1 +endif + +ifeq ($(TARGET),i686-unknown-redox) + export CC=i686-unknown-redox-gcc + export LD=i686-unknown-redox-ld + export AR=i686-unknown-redox-ar + export NM=i686-unknown-redox-nm + export OBJCOPY=i686-unknown-redox-objcopy + export CPPFLAGS= + LD_SO_PATH=lib/libc.so.1 +endif + +ifeq ($(TARGET),x86_64-unknown-linux-gnu) + export CC=x86_64-linux-gnu-gcc + export LD=x86_64-linux-gnu-ld + export AR=x86_64-linux-gnu-ar + export NM=x86_64-linux-gnu-nm + export OBJCOPY=objcopy + export CPPFLAGS= + LD_SO_PATH=lib/ld64.so.1 +endif + +ifeq ($(TARGET),x86_64-unknown-redox) + export CC=x86_64-unknown-redox-gcc + export LD=x86_64-unknown-redox-ld + export AR=x86_64-unknown-redox-ar + export NM=x86_64-unknown-redox-nm + export OBJCOPY=x86_64-unknown-redox-objcopy + export CPPFLAGS= + LD_SO_PATH=lib/ld64.so.1 +endif + +ifeq ($(TARGET),riscv64gc-unknown-redox) + export CC=riscv64-unknown-redox-gcc + export LD=riscv64-unknown-redox-ld + export AR=riscv64-unknown-redox-ar + export NM=riscv64-unknown-redox-nm + export OBJCOPY=riscv64-unknown-redox-objcopy + export CPPFLAGS=-march=rv64gc -mabi=lp64d + LD_SO_PATH=lib/ld.so.1 +endif diff --git a/core_io b/core_io deleted file mode 160000 index 23118bedb0..0000000000 --- a/core_io +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23118bedb028808c69ccd0f378160723fbbca586 diff --git a/dlmalloc-rs/.github/workflows/main.yml b/dlmalloc-rs/.github/workflows/main.yml new file mode 100644 index 0000000000..2c7a782930 --- /dev/null +++ b/dlmalloc-rs/.github/workflows/main.yml @@ -0,0 +1,117 @@ +name: CI +on: [push, pull_request] + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + rust: stable + - os: ubuntu-latest + rust: beta + - os: ubuntu-latest + rust: nightly + - os: macos-latest + rust: stable + - os: windows-latest + rust: stable + - os: ubuntu-latest + rust: stable + target: wasm32-wasip1 + steps: + - uses: actions/checkout@v4 + - run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} + shell: bash + + # Configure cross-builds by adding the rustup target and configuring future + # cargo invocations. + - run: | + rustup target add ${{ matrix.target }} + echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV + if: matrix.target != '' + + # For wasm install wasmtime as a test runner and configure it with Cargo. + - name: Setup `wasmtime` + uses: bytecodealliance/actions/wasmtime/setup@v1 + if: matrix.target == 'wasm32-wasip1' + - run: echo CARGO_TARGET_WASM32_WASIP1_RUNNER=wasmtime >> $GITHUB_ENV + if: matrix.target == 'wasm32-wasip1' + + - run: cargo test + - run: cargo test --features debug + - run: cargo test --features global + - run: cargo test --release + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true + - run: cargo test --release + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: false + - run: cargo test --features debug --release + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true + - run: RUSTFLAGS='--cfg test_lots' cargo test --release + shell: bash + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true + - run: RUSTFLAGS='--cfg test_lots' cargo test --release --features debug + shell: bash + env: + CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true + + rustfmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - run: cargo fmt -- --check + + wasm: + name: WebAssembly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup target add wasm32-unknown-unknown + - run: cargo build --target wasm32-unknown-unknown + - run: cargo build --target wasm32-unknown-unknown --release + + external-platform: + name: external-platform + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup target add x86_64-fortanix-unknown-sgx + - run: cargo build --target x86_64-fortanix-unknown-sgx + + fuzz: + name: Build Fuzzers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update nightly && rustup default nightly + - run: cargo install cargo-fuzz + - run: cargo fuzz build --dev + + miri: + name: Miri + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Miri + run: | + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup + - name: Test with Miri Stack Borrows + run: cargo miri test + - name: Test with Miri Tree Borrows + run: cargo miri test + env: + MIRIFLAGS: -Zmiri-tree-borrows diff --git a/dlmalloc-rs/.gitignore b/dlmalloc-rs/.gitignore new file mode 100644 index 0000000000..6aa106405a --- /dev/null +++ b/dlmalloc-rs/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/dlmalloc-rs/Cargo.toml b/dlmalloc-rs/Cargo.toml new file mode 100644 index 0000000000..f9e20c3e7f --- /dev/null +++ b/dlmalloc-rs/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "dlmalloc" +version = "0.2.8" +authors = ["Alex Crichton "] +license = "MIT/Apache-2.0" +readme = "README.md" +repository = "https://github.com/alexcrichton/dlmalloc-rs" +homepage = "https://github.com/alexcrichton/dlmalloc-rs" +documentation = "https://docs.rs/dlmalloc" +description = """ +A Rust port of the dlmalloc allocator +""" +edition.workspace = true + +[workspace] +members = ['fuzz'] + +[workspace.package] +edition = '2021' + +[package.metadata.docs.rs] +features = ['global'] + +[lib] +doctest = false + +[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies] +libc = { version = "0.2", default-features = false, optional = true } + +[dependencies] +# For more information on these dependencies see rust-lang/rust's +# `src/tools/rustc-std-workspace` folder +core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' } +compiler_builtins = { version = '0.1.0', optional = true } +cfg-if = "1.0" + +[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +version = ">=0.52.0, <=0.59.*" +features = [ + "Win32_Foundation", + "Win32_System_Memory", + "Win32_System_Threading", + "Win32_System_SystemInformation", +] + +[dev-dependencies] +arbitrary = "1.3.2" +rand = { version = "0.8", features = ['small_rng'] } + +[profile.release] +debug-assertions = true + +[features] +# Enable implementations of the `GlobalAlloc` standard library API, exporting a +# new `GlobalDlmalloc` as well which implements this trait. +global = ["system", "rust_api"] + +# Enable very expensive debug checks in this crate +debug = [] + +# Enables OS APIs based on the current target, can be implemented manually +# otherwise. +system = ["libc"] + +rustc-dep-of-std = ['core', 'compiler_builtins/rustc-dep-of-std'] + +c_api = [] +rust_api = [] + +default = ["global", "rust_api"] diff --git a/dlmalloc-rs/LICENSE-APACHE b/dlmalloc-rs/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/dlmalloc-rs/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/dlmalloc-rs/LICENSE-MIT b/dlmalloc-rs/LICENSE-MIT new file mode 100644 index 0000000000..39e0ed6602 --- /dev/null +++ b/dlmalloc-rs/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014 Alex Crichton + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dlmalloc-rs/README.md b/dlmalloc-rs/README.md new file mode 100644 index 0000000000..317d8c8bf1 --- /dev/null +++ b/dlmalloc-rs/README.md @@ -0,0 +1,40 @@ +# dlmalloc-rs + +A port of [dlmalloc] to Rust. + +[Documentation](https://docs.rs/dlmalloc) + +[dlmalloc]: https://gee.cs.oswego.edu/dl/html/malloc.html + +## Why dlmalloc? + +This crate is a port of [dlmalloc] to Rust, and doesn't rely on C. The primary +purpose of this crate is to serve as the default allocator for Rust on the +`wasm32-unknown-unknown` target. At the time this was written the wasm target +didn't support C code, so it was required to have a Rust-only solution. + +This allocator is not the most performant by a longshot. It is primarily, I +think, intended for being easy to port and easy to learn. I didn't dive too deep +into the implementation when writing it, it's just a straight port of the C +version. + +It's unlikely that Rust code needs to worry/interact with this allocator in +general. Most of the time you'll be manually switching to a different allocator +:) + +# License + +This project is licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or + http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this project by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. diff --git a/dlmalloc-rs/fuzz/.gitignore b/dlmalloc-rs/fuzz/.gitignore new file mode 100644 index 0000000000..b400c27826 --- /dev/null +++ b/dlmalloc-rs/fuzz/.gitignore @@ -0,0 +1,2 @@ +corpus +artifacts diff --git a/dlmalloc-rs/fuzz/Cargo.toml b/dlmalloc-rs/fuzz/Cargo.toml new file mode 100644 index 0000000000..fb9970fa97 --- /dev/null +++ b/dlmalloc-rs/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dlmalloc-fuzz" +version = "0.0.1" +publish = false +edition.workspace = true + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = "1.3.2" +dlmalloc = { path = '..' } +libfuzzer-sys = "0.4.7" + +[[bin]] +name = "alloc" +path = "fuzz_targets/alloc.rs" +test = false +bench = false diff --git a/dlmalloc-rs/fuzz/fuzz_targets/alloc.rs b/dlmalloc-rs/fuzz/fuzz_targets/alloc.rs new file mode 100644 index 0000000000..eddfb8b64a --- /dev/null +++ b/dlmalloc-rs/fuzz/fuzz_targets/alloc.rs @@ -0,0 +1,8 @@ +#![no_main] + +use arbitrary::Unstructured; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|bytes: &[u8]| { + let _ = dlmalloc_fuzz::run(&mut Unstructured::new(bytes)); +}); diff --git a/dlmalloc-rs/fuzz/src/lib.rs b/dlmalloc-rs/fuzz/src/lib.rs new file mode 100644 index 0000000000..3b337bb92c --- /dev/null +++ b/dlmalloc-rs/fuzz/src/lib.rs @@ -0,0 +1,108 @@ +use arbitrary::{Result, Unstructured}; +use dlmalloc::Dlmalloc; +use std::cmp; + +const MAX_ALLOCATED: usize = 100 << 20; // 100 MB + +pub fn run(u: &mut Unstructured<'_>) -> Result<()> { + let mut a = Dlmalloc::new(); + let mut ptrs = Vec::new(); + let mut allocated = 0; + unsafe { + while u.arbitrary()? { + // If there are pointers to free then have a chance of deallocating + // a pointer. Try not to deallocate things until there's a "large" + // working set but afterwards give it a 50/50 chance of allocating + // or deallocating. + let free = match ptrs.len() { + 0 => false, + 0..=10_000 => u.ratio(1, 3)?, + _ => u.arbitrary()?, + }; + if free { + let idx = u.choose_index(ptrs.len())?; + let (ptr, size, align) = ptrs.swap_remove(idx); + allocated -= size; + a.free(ptr, size, align); + continue; + } + + // 1/100 chance of reallocating a pointer to a different size. + if ptrs.len() > 0 && u.ratio(1, 100)? { + let idx = u.choose_index(ptrs.len())?; + let (ptr, size, align) = ptrs.swap_remove(idx); + + // Arbitrarily choose whether to make this allocation either + // twice as large or half as small. + let new_size = if u.arbitrary()? { + u.int_in_range(size..=size * 2)? + } else if size > 10 { + u.int_in_range(size / 2..=size)? + } else { + continue; + }; + if allocated + new_size - size > MAX_ALLOCATED { + ptrs.push((ptr, size, align)); + continue; + } + allocated -= size; + allocated += new_size; + + // Perform the `realloc` and assert that all bytes were copied. + let mut tmp = Vec::new(); + for i in 0..cmp::min(size, new_size) { + tmp.push(*ptr.offset(i as isize)); + } + let ptr = a.realloc(ptr, size, align, new_size); + assert!(!ptr.is_null()); + for (i, byte) in tmp.iter().enumerate() { + assert_eq!(*byte, *ptr.offset(i as isize)); + } + ptrs.push((ptr, new_size, align)); + } + + // Aribtrarily choose a size to allocate as well as an alignment. + // Enable small sizes with standard alignment happening a fair bit. + let size = if u.arbitrary()? { + u.int_in_range(1..=128)? + } else { + u.int_in_range(1..=128 * 1024)? + }; + let align = if u.ratio(1, 10)? { + 1 << u.int_in_range(3..=8)? + } else { + 8 + }; + + if size + allocated > MAX_ALLOCATED { + continue; + } + allocated += size; + + // Choose arbitrarily between a zero-allocated chunk and a normal + // allocated chunk. + let zero = u.ratio(1, 50)?; + let ptr = if zero { + a.calloc(size, align) + } else { + a.malloc(size, align) + }; + for i in 0..size { + if zero { + assert_eq!(*ptr.offset(i as isize), 0); + } + *ptr.offset(i as isize) = 0xce; + } + ptrs.push((ptr, size, align)); + } + + // Deallocate everythign when we're done. + for (ptr, size, align) in ptrs { + a.free(ptr, size, align); + } + + a.destroy(); + } + + Ok(()) +} diff --git a/src/c/dlmalloc.c b/dlmalloc-rs/src/dlmalloc.c similarity index 99% rename from src/c/dlmalloc.c rename to dlmalloc-rs/src/dlmalloc.c index efdfd21142..649cfbc705 100644 --- a/src/c/dlmalloc.c +++ b/dlmalloc-rs/src/dlmalloc.c @@ -521,58 +521,6 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP improvement at the expense of carrying around more memory. */ -/* Customizations { */ - -#define HAVE_MMAP 1 -#define ONLY_MSPACES 1 -#define LACKS_ERRNO_H -#define LACKS_FCNTL_H -#define LACKS_SCHED_H -#define LACKS_STDLIB_H -#define LACKS_STRING_H -#define LACKS_SYS_MMAN_H -#define LACKS_TIME_H -#define LACKS_UNISTD_H -#define NO_MALLOC_STATS 1 -#define USE_DL_PREFIX 1 -#define USE_LOCKS 1 -#define USE_SPIN_LOCKS 1 -#define HAVE_MREMAP 0 -#define malloc_getpagesize ((size_t)4096U) - -#include - -#define ENOMEM 12 -#define EINVAL 22 - -extern __thread int errno; -#if defined(__linux__) -#define O_RDWR 2 -#define PROT_READ 1 -#define PROT_WRITE 2 -#elif defined(__redox__) -#define O_RDWR 196608 -#define PROT_READ 4 -#define PROT_WRITE 2 -#endif /*defined(__redox__)*/ - -#define MAP_PRIVATE 2 -#define MAP_ANON 32 -typedef long off_t; - -int open(const char *pathname, int flags); -void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); -int munmap(void *addr, size_t len); -void abort(void); -void *memcpy(void *dest, const void *src, size_t n); -void *memset(void *s, int c, size_t n); - -// LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H -// LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H -// LACKS_STDLIB_H LACKS_SCHED_H LACKS_TIME_H - -/* } Customizations */ - /* Version identifier to allow people to support multiple versions */ #ifndef DLMALLOC_VERSION #define DLMALLOC_VERSION 20806 @@ -630,7 +578,7 @@ void *memset(void *s, int c, size_t n); #endif /* DARWIN */ #ifndef LACKS_SYS_TYPES_H -#include /* For size_t */ +#include /* For size_t */ #endif /* LACKS_SYS_TYPES_H */ /* The maximum possible size_t value has all bits set */ diff --git a/dlmalloc-rs/src/dlmalloc.rs b/dlmalloc-rs/src/dlmalloc.rs new file mode 100644 index 0000000000..f0bf7cada0 --- /dev/null +++ b/dlmalloc-rs/src/dlmalloc.rs @@ -0,0 +1,1900 @@ +// This is a version of dlmalloc.c ported to Rust. You can find the original +// source at ftp://g.oswego.edu/pub/misc/malloc.c +// +// The original source was written by Doug Lea and released to the public domain + +// TODO +#![allow(missing_docs)] + +macro_rules! debug_assert { + ($($arg:tt)*) => { + if cfg!(all(feature = "debug", debug_assertions)) { + assert!($($arg)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($arg:tt)*) => { + if cfg!(all(feature = "debug", debug_assertions)) { + assert_eq!($($arg)*); + } + }; +} + +use core::{cmp, mem, ptr}; + +use crate::Allocator; + +pub struct Dlmalloc { + smallmap: u32, + treemap: u32, + smallbins: [*mut Chunk; (NSMALLBINS + 1) * 2], + treebins: [*mut TreeChunk; NTREEBINS], + dvsize: usize, + topsize: usize, + dv: *mut Chunk, + top: *mut Chunk, + footprint: usize, + max_footprint: usize, + seg: Segment, + trim_check: usize, + least_addr: *mut u8, + release_checks: usize, + system_allocator: A, +} +unsafe impl Send for Dlmalloc {} + +// TODO: document this +const NSMALLBINS: usize = 32; +const NTREEBINS: usize = 32; +const SMALLBIN_SHIFT: usize = 3; +const TREEBIN_SHIFT: usize = 8; + +const NSMALLBINS_U32: u32 = NSMALLBINS as u32; +const NTREEBINS_U32: u32 = NTREEBINS as u32; + +// TODO: runtime configurable? documentation? +const DEFAULT_GRANULARITY: usize = 64 * 1024; +const DEFAULT_TRIM_THRESHOLD: usize = 2 * 1024 * 1024; +const MAX_RELEASE_CHECK_RATE: usize = 4095; + +#[repr(C)] +struct Chunk { + prev_foot: usize, + head: usize, + prev: *mut Chunk, + next: *mut Chunk, +} + +#[repr(C)] +struct TreeChunk { + chunk: Chunk, + child: [*mut TreeChunk; 2], + parent: *mut TreeChunk, + index: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct Segment { + base: *mut u8, + size: usize, + next: *mut Segment, + flags: u32, +} + +fn align_up(a: usize, alignment: usize) -> usize { + debug_assert!(alignment.is_power_of_two()); + (a + (alignment - 1)) & !(alignment - 1) +} + +fn left_bits(x: u32) -> u32 { + (x << 1) | (!(x << 1)).wrapping_add(1) +} + +fn least_bit(x: u32) -> u32 { + x & (!x + 1) +} + +fn leftshift_for_tree_index(x: u32) -> u32 { + let x = usize::try_from(x).unwrap(); + if x == NTREEBINS - 1 { + 0 + } else { + (mem::size_of::() * 8 - 1 - ((x >> 1) + TREEBIN_SHIFT - 2)) as u32 + } +} + +impl Dlmalloc { + pub const fn new(system_allocator: A) -> Dlmalloc { + Dlmalloc { + smallmap: 0, + treemap: 0, + smallbins: [ptr::null_mut(); (NSMALLBINS + 1) * 2], + treebins: [ptr::null_mut(); NTREEBINS], + dvsize: 0, + topsize: 0, + dv: ptr::null_mut(), + top: ptr::null_mut(), + footprint: 0, + max_footprint: 0, + seg: Segment { + base: ptr::null_mut(), + size: 0, + next: ptr::null_mut(), + flags: 0, + }, + trim_check: 0, + least_addr: ptr::null_mut(), + release_checks: 0, + system_allocator, + } + } + + pub fn allocator(&self) -> &A { + &self.system_allocator + } +} + +impl Dlmalloc { + // TODO: can we get rid of this? + pub fn malloc_alignment(&self) -> usize { + mem::size_of::() * 2 + } + + // TODO: dox + fn chunk_overhead(&self) -> usize { + mem::size_of::() + } + + fn mmap_chunk_overhead(&self) -> usize { + 2 * mem::size_of::() + } + + // TODO: dox + fn min_large_size(&self) -> usize { + 1 << TREEBIN_SHIFT + } + + // TODO: dox + fn max_small_size(&self) -> usize { + self.min_large_size() - 1 + } + + // TODO: dox + fn max_small_request(&self) -> usize { + self.max_small_size() - (self.malloc_alignment() - 1) - self.chunk_overhead() + } + + // TODO: dox + fn min_chunk_size(&self) -> usize { + align_up(mem::size_of::(), self.malloc_alignment()) + } + + // TODO: dox + fn min_request(&self) -> usize { + self.min_chunk_size() - self.chunk_overhead() - 1 + } + + // TODO: dox + fn max_request(&self) -> usize { + // min_sys_alloc_space: the largest `X` such that + // pad_request(X - 1) -- minus 1, because requests of exactly + // `max_request` will not be honored + // + self.top_foot_size() + // + self.malloc_alignment() + // + DEFAULT_GRANULARITY + // == + // usize::MAX + let min_sys_alloc_space = + ((!0 - (DEFAULT_GRANULARITY + self.top_foot_size() + self.malloc_alignment()) + 1) + & !self.malloc_alignment()) + - self.chunk_overhead() + + 1; + + cmp::min((!self.min_chunk_size() + 1) << 2, min_sys_alloc_space) + } + + fn pad_request(&self, amt: usize) -> usize { + align_up(amt + self.chunk_overhead(), self.malloc_alignment()) + } + + fn small_index(&self, size: usize) -> u32 { + (size >> SMALLBIN_SHIFT) as u32 + } + + fn small_index2size(&self, idx: u32) -> usize { + usize::try_from(idx).unwrap() << SMALLBIN_SHIFT + } + + fn is_small(&self, s: usize) -> bool { + s >> SMALLBIN_SHIFT < NSMALLBINS + } + + fn is_aligned(&self, a: usize) -> bool { + a & (self.malloc_alignment() - 1) == 0 + } + + fn align_offset(&self, addr: *mut u8) -> usize { + addr.align_offset(self.malloc_alignment()) + } + + fn align_offset_usize(&self, addr: usize) -> usize { + align_up(addr, self.malloc_alignment()) - addr + } + + fn top_foot_size(&self) -> usize { + self.align_offset_usize(Chunk::mem_offset()) + + self.pad_request(mem::size_of::()) + + self.min_chunk_size() + } + + fn mmap_foot_pad(&self) -> usize { + 4 * mem::size_of::() + } + + fn align_as_chunk(&self, ptr: *mut u8) -> *mut Chunk { + unsafe { + let chunk = Chunk::to_mem(ptr.cast()); + ptr.add(self.align_offset(chunk)).cast() + } + } + + fn request2size(&self, req: usize) -> usize { + if req < self.min_request() { + self.min_chunk_size() + } else { + self.pad_request(req) + } + } + + unsafe fn overhead_for(&self, p: *mut Chunk) -> usize { + if Chunk::mmapped(p) { + self.mmap_chunk_overhead() + } else { + self.chunk_overhead() + } + } + + pub unsafe fn calloc_must_clear(&self, ptr: *mut u8) -> bool { + !self.system_allocator.allocates_zeros() || !Chunk::mmapped(Chunk::from_mem(ptr)) + } + + pub unsafe fn malloc(&mut self, size: usize) -> *mut u8 { + self.check_malloc_state(); + + let nb; + if size <= self.max_small_request() { + nb = self.request2size(size); + let mut idx = self.small_index(nb); + let smallbits = self.smallmap >> idx; + + // Check the bin for `idx` (the lowest bit) but also check the next + // bin up to use that to satisfy our request, if needed. + if smallbits & 0b11 != 0 { + // If our the lowest bit, our `idx`, is unset then bump up the + // index as we'll be using the next bucket up. + idx += !smallbits & 1; + + let b = self.smallbin_at(idx); + let p = (*b).prev; + self.unlink_first_small_chunk(b, p, idx); + let smallsize = self.small_index2size(idx); + Chunk::set_inuse_and_pinuse(p, smallsize); + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + return ret; + } + + if nb > self.dvsize { + // If there's some other bin with some memory, then we just use + // the next smallest bin + if smallbits != 0 { + let leftbits = (smallbits << idx) & left_bits(1 << idx); + let leastbit = least_bit(leftbits); + let i = leastbit.trailing_zeros(); + let b = self.smallbin_at(i); + let p = (*b).prev; + debug_assert_eq!(Chunk::size(p), self.small_index2size(i)); + self.unlink_first_small_chunk(b, p, i); + let smallsize = self.small_index2size(i); + let rsize = smallsize - nb; + if mem::size_of::() != 4 && rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(p, smallsize); + } else { + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + let r = Chunk::plus_offset(p, nb); + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + self.replace_dv(r, rsize); + } + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + return ret; + } else if self.treemap != 0 { + let mem = self.tmalloc_small(nb); + if !mem.is_null() { + self.check_malloced_chunk(mem, nb); + self.check_malloc_state(); + return mem; + } + } + } + } else if size >= self.max_request() { + // TODO: translate this to unsupported + return ptr::null_mut(); + } else { + nb = self.pad_request(size); + if self.treemap != 0 { + let mem = self.tmalloc_large(nb); + if !mem.is_null() { + self.check_malloced_chunk(mem, nb); + self.check_malloc_state(); + return mem; + } + } + } + + // use the `dv` node if we can, splitting it if necessary or otherwise + // exhausting the entire chunk + if nb <= self.dvsize { + let rsize = self.dvsize - nb; + let p = self.dv; + if rsize >= self.min_chunk_size() { + self.dv = Chunk::plus_offset(p, nb); + self.dvsize = rsize; + let r = self.dv; + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + } else { + let dvs = self.dvsize; + self.dvsize = 0; + self.dv = ptr::null_mut(); + Chunk::set_inuse_and_pinuse(p, dvs); + } + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + self.check_malloc_state(); + return ret; + } + + // Split the top node if we can + if nb < self.topsize { + self.topsize -= nb; + let rsize = self.topsize; + let p = self.top; + self.top = Chunk::plus_offset(p, nb); + let r = self.top; + (*r).head = rsize | PINUSE; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + self.check_top_chunk(self.top); + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + self.check_malloc_state(); + return ret; + } + + self.sys_alloc(nb) + } + + /// allocates system resources + unsafe fn sys_alloc(&mut self, size: usize) -> *mut u8 { + self.check_malloc_state(); + // keep in sync with max_request + let asize = align_up( + size + self.top_foot_size() + self.malloc_alignment(), + DEFAULT_GRANULARITY, + ); + + let (tbase, tsize, flags) = self.system_allocator.alloc(asize); + if tbase.is_null() { + return tbase; + } + + self.footprint += tsize; + self.max_footprint = cmp::max(self.max_footprint, self.footprint); + + if self.top.is_null() { + if self.least_addr.is_null() || tbase < self.least_addr { + self.least_addr = tbase; + } + self.seg.base = tbase; + self.seg.size = tsize; + self.seg.flags = flags; + self.release_checks = MAX_RELEASE_CHECK_RATE; + self.init_bins(); + let tsize = tsize - self.top_foot_size(); + self.init_top(tbase.cast(), tsize); + // let mn = Chunk::next(Chunk::from_mem(self as *mut _ as *mut u8)); + // let top_foot_size = self.top_foot_size(); + // self.init_top(mn, tbase as usize + tsize - mn as usize - top_foot_size); + } else { + let mut sp: *mut Segment = &mut self.seg; + while !sp.is_null() && tbase != Segment::top(sp) { + sp = (*sp).next; + } + if !sp.is_null() + && !Segment::is_extern(sp) + && Segment::sys_flags(sp) == flags + && Segment::holds(sp, self.top.cast()) + { + (*sp).size += tsize; + let ptr = self.top; + let size = self.topsize + tsize; + self.init_top(ptr, size); + } else { + self.least_addr = cmp::min(tbase, self.least_addr); + let mut sp: *mut Segment = &mut self.seg; + while !sp.is_null() && (*sp).base != tbase.add(tsize) { + sp = (*sp).next; + } + if !sp.is_null() && !Segment::is_extern(sp) && Segment::sys_flags(sp) == flags { + let oldbase = (*sp).base; + (*sp).base = tbase; + (*sp).size += tsize; + return self.prepend_alloc(tbase, oldbase, size); + } else { + self.add_segment(tbase, tsize, flags); + } + } + } + + if size < self.topsize { + self.topsize -= size; + let rsize = self.topsize; + let p = self.top; + self.top = Chunk::plus_offset(p, size); + let r = self.top; + (*r).head = rsize | PINUSE; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, size); + let ret = Chunk::to_mem(p); + self.check_top_chunk(self.top); + self.check_malloced_chunk(ret, size); + self.check_malloc_state(); + return ret; + } + + return ptr::null_mut(); + } + + pub unsafe fn realloc(&mut self, oldmem: *mut u8, bytes: usize) -> *mut u8 { + if bytes >= self.max_request() { + return ptr::null_mut(); + } + let nb = self.request2size(bytes); + let oldp = Chunk::from_mem(oldmem); + let newp = self.try_realloc_chunk(oldp, nb, true); + if !newp.is_null() { + self.check_inuse_chunk(newp); + return Chunk::to_mem(newp); + } + let ptr = self.malloc(bytes); + if !ptr.is_null() { + let oc = Chunk::size(oldp) - self.overhead_for(oldp); + ptr::copy_nonoverlapping(oldmem, ptr, cmp::min(oc, bytes)); + self.free(oldmem); + } + return ptr; + } + + unsafe fn try_realloc_chunk(&mut self, p: *mut Chunk, nb: usize, can_move: bool) -> *mut Chunk { + let oldsize = Chunk::size(p); + let next = Chunk::plus_offset(p, oldsize); + + if Chunk::mmapped(p) { + self.mmap_resize(p, nb, can_move) + } else if oldsize >= nb { + let rsize = oldsize - nb; + if rsize >= self.min_chunk_size() { + let r = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(r, rsize); + self.dispose_chunk(r, rsize); + } + p + } else if next == self.top { + // extend into top + if oldsize + self.topsize <= nb { + return ptr::null_mut(); + } + let newsize = oldsize + self.topsize; + let newtopsize = newsize - nb; + let newtop = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + (*newtop).head = newtopsize | PINUSE; + self.top = newtop; + self.topsize = newtopsize; + p + } else if next == self.dv { + // extend into dv + let dvs = self.dvsize; + if oldsize + dvs < nb { + return ptr::null_mut(); + } + let dsize = oldsize + dvs - nb; + if dsize >= self.min_chunk_size() { + let r = Chunk::plus_offset(p, nb); + let n = Chunk::plus_offset(r, dsize); + Chunk::set_inuse(p, nb); + Chunk::set_size_and_pinuse_of_free_chunk(r, dsize); + Chunk::clear_pinuse(n); + self.dvsize = dsize; + self.dv = r; + } else { + // exhaust dv + let newsize = oldsize + dvs; + Chunk::set_inuse(p, newsize); + self.dvsize = 0; + self.dv = ptr::null_mut(); + } + return p; + } else if !Chunk::cinuse(next) { + // extend into the next free chunk + let nextsize = Chunk::size(next); + if oldsize + nextsize < nb { + return ptr::null_mut(); + } + let rsize = oldsize + nextsize - nb; + self.unlink_chunk(next, nextsize); + if rsize < self.min_chunk_size() { + let newsize = oldsize + nextsize; + Chunk::set_inuse(p, newsize); + } else { + let r = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(r, rsize); + self.dispose_chunk(r, rsize); + } + p + } else { + ptr::null_mut() + } + } + + unsafe fn mmap_resize(&mut self, oldp: *mut Chunk, nb: usize, can_move: bool) -> *mut Chunk { + let oldsize = Chunk::size(oldp); + // Can't shrink mmap regions below a small size + if self.is_small(nb) { + return ptr::null_mut(); + } + + // Keep the old chunk if it's big enough but not too big + if oldsize >= nb + mem::size_of::() && (oldsize - nb) <= (DEFAULT_GRANULARITY << 1) { + return oldp; + } + + let offset = (*oldp).prev_foot; + let oldmmsize = oldsize + offset + self.mmap_foot_pad(); + let newmmsize = + self.mmap_align(nb + 6 * mem::size_of::() + self.malloc_alignment() - 1); + let ptr = self.system_allocator.remap( + oldp.cast::().sub(offset), + oldmmsize, + newmmsize, + can_move, + ); + if ptr.is_null() { + return ptr::null_mut(); + } + let newp = ptr.add(offset).cast::(); + let psize = newmmsize - offset - self.mmap_foot_pad(); + (*newp).head = psize; + (*Chunk::plus_offset(newp, psize)).head = Chunk::fencepost_head(); + (*Chunk::plus_offset(newp, psize + mem::size_of::())).head = 0; + self.least_addr = cmp::min(ptr, self.least_addr); + self.footprint = self.footprint + newmmsize - oldmmsize; + self.max_footprint = cmp::max(self.max_footprint, self.footprint); + self.check_mmapped_chunk(newp); + return newp; + } + + fn mmap_align(&self, a: usize) -> usize { + align_up(a, self.system_allocator.page_size()) + } + + // Only call this with power-of-two alignment and alignment > + // `self.malloc_alignment()` + pub unsafe fn memalign(&mut self, mut alignment: usize, bytes: usize) -> *mut u8 { + if alignment < self.min_chunk_size() { + alignment = self.min_chunk_size(); + } + if bytes >= self.max_request() - alignment { + return ptr::null_mut(); + } + let nb = self.request2size(bytes); + let req = nb + alignment + self.min_chunk_size() - self.chunk_overhead(); + let mem = self.malloc(req); + if mem.is_null() { + return mem; + } + let mut p = Chunk::from_mem(mem); + if mem as usize & (alignment - 1) != 0 { + // Here we find an aligned sopt inside the chunk. Since we need to + // give back leading space in a chunk of at least `min_chunk_size`, + // if the first calculation places us at a spot with less than + // `min_chunk_size` leader we can move to the next aligned spot. + // we've allocated enough total room so that this is always possible + let br = + Chunk::from_mem(((mem as usize + alignment - 1) & (!alignment + 1)) as *mut u8); + let pos = if (br as usize - p as usize) > self.min_chunk_size() { + br.cast::() + } else { + br.cast::().add(alignment) + }; + let newp = pos.cast::(); + let leadsize = pos as usize - p as usize; + let newsize = Chunk::size(p) - leadsize; + + // for mmapped chunks just adjust the offset + if Chunk::mmapped(p) { + (*newp).prev_foot = (*p).prev_foot + leadsize; + (*newp).head = newsize; + } else { + // give back the leader, use the rest + Chunk::set_inuse(newp, newsize); + Chunk::set_inuse(p, leadsize); + self.dispose_chunk(p, leadsize); + } + p = newp; + } + + // give back spare room at the end + if !Chunk::mmapped(p) { + let size = Chunk::size(p); + if size > nb + self.min_chunk_size() { + let remainder_size = size - nb; + let remainder = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(remainder, remainder_size); + self.dispose_chunk(remainder, remainder_size); + } + } + + let mem = Chunk::to_mem(p); + debug_assert!(Chunk::size(p) >= nb); + debug_assert_eq!(align_up(mem as usize, alignment), mem as usize); + self.check_inuse_chunk(p); + return mem; + } + + // consolidate and bin a chunk, differs from exported versions of free + // mainly in that the chunk need not be marked as inuse + unsafe fn dispose_chunk(&mut self, mut p: *mut Chunk, mut psize: usize) { + let next = Chunk::plus_offset(p, psize); + if !Chunk::pinuse(p) { + let prevsize = (*p).prev_foot; + if Chunk::mmapped(p) { + psize += prevsize + self.mmap_foot_pad(); + if self + .system_allocator + .free(p.cast::().sub(prevsize), psize) + { + self.footprint -= psize; + } + return; + } + let prev = Chunk::minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if p != self.dv { + self.unlink_chunk(p, prevsize); + } else if (*next).head & INUSE == INUSE { + self.dvsize = psize; + Chunk::set_free_with_pinuse(p, psize, next); + return; + } + } + + if !Chunk::cinuse(next) { + // consolidate forward + if next == self.top { + self.topsize += psize; + let tsize = self.topsize; + self.top = p; + (*p).head = tsize | PINUSE; + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } + return; + } else if next == self.dv { + self.dvsize += psize; + let dsize = self.dvsize; + self.dv = p; + Chunk::set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } else { + let nsize = Chunk::size(next); + psize += nsize; + self.unlink_chunk(next, nsize); + Chunk::set_size_and_pinuse_of_free_chunk(p, psize); + if p == self.dv { + self.dvsize = psize; + return; + } + } + } else { + Chunk::set_free_with_pinuse(p, psize, next); + } + self.insert_chunk(p, psize); + } + + unsafe fn init_top(&mut self, ptr: *mut Chunk, size: usize) { + let offset = self.align_offset(Chunk::to_mem(ptr)); + let p = Chunk::plus_offset(ptr, offset); + let size = size - offset; + + self.top = p; + self.topsize = size; + (*p).head = size | PINUSE; + (*Chunk::plus_offset(p, size)).head = self.top_foot_size(); + self.trim_check = DEFAULT_TRIM_THRESHOLD; + } + + unsafe fn init_bins(&mut self) { + for i in 0..NSMALLBINS_U32 { + let bin = self.smallbin_at(i); + (*bin).next = bin; + (*bin).prev = bin; + } + } + + unsafe fn prepend_alloc(&mut self, newbase: *mut u8, oldbase: *mut u8, size: usize) -> *mut u8 { + let p = self.align_as_chunk(newbase); + let mut oldfirst = self.align_as_chunk(oldbase); + let psize = oldfirst as usize - p as usize; + let q = Chunk::plus_offset(p, size); + let mut qsize = psize - size; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, size); + + debug_assert!(oldfirst > q); + debug_assert!(Chunk::pinuse(oldfirst)); + debug_assert!(qsize >= self.min_chunk_size()); + + // consolidate the remainder with the first chunk of the old base + if oldfirst == self.top { + self.topsize += qsize; + let tsize = self.topsize; + self.top = q; + (*q).head = tsize | PINUSE; + self.check_top_chunk(q); + } else if oldfirst == self.dv { + self.dvsize += qsize; + let dsize = self.dvsize; + self.dv = q; + Chunk::set_size_and_pinuse_of_free_chunk(q, dsize); + } else { + if !Chunk::inuse(oldfirst) { + let nsize = Chunk::size(oldfirst); + self.unlink_chunk(oldfirst, nsize); + oldfirst = Chunk::plus_offset(oldfirst, nsize); + qsize += nsize; + } + Chunk::set_free_with_pinuse(q, qsize, oldfirst); + self.insert_chunk(q, qsize); + self.check_free_chunk(q); + } + + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, size); + self.check_malloc_state(); + return ret; + } + + // add a segment to hold a new noncontiguous region + unsafe fn add_segment(&mut self, tbase: *mut u8, tsize: usize, flags: u32) { + // TODO: what in the world is this function doing + + // Determine locations and sizes of segment, fenceposts, and the old top + let old_top = self.top.cast::(); + let oldsp = self.segment_holding(old_top); + let old_end = Segment::top(oldsp); + let ssize = self.pad_request(mem::size_of::()); + let offset = ssize + mem::size_of::() * 4 + self.malloc_alignment() - 1; + let rawsp = old_end.sub(offset); + let offset = self.align_offset(Chunk::to_mem(rawsp.cast())); + let asp = rawsp.add(offset); + let csp = if asp < old_top.add(self.min_chunk_size()) { + old_top + } else { + asp + }; + let sp = csp.cast::(); + let ss = Chunk::to_mem(sp).cast::(); + let tnext = Chunk::plus_offset(sp, ssize); + let mut p = tnext; + let mut nfences = 0; + + // reset the top to our new space + let size = tsize - self.top_foot_size(); + self.init_top(tbase.cast(), size); + + // set up our segment record + debug_assert!(self.is_aligned(ss as usize)); + Chunk::set_size_and_pinuse_of_inuse_chunk(sp, ssize); + *ss = self.seg; // push our current record + self.seg.base = tbase; + self.seg.size = tsize; + self.seg.flags = flags; + self.seg.next = ss; + + // insert trailing fences + loop { + let nextp = Chunk::plus_offset(p, mem::size_of::()); + (*p).head = Chunk::fencepost_head(); + nfences += 1; + if ptr::addr_of!((*nextp).head).cast::() < old_end { + p = nextp; + } else { + break; + } + } + debug_assert!(nfences >= 2); + + // insert the rest of the old top into a bin as an ordinary free chunk + if csp != old_top { + let q = old_top.cast::(); + let psize = csp as usize - old_top as usize; + let tn = Chunk::plus_offset(q, psize); + Chunk::set_free_with_pinuse(q, psize, tn); + self.insert_chunk(q, psize); + } + + self.check_top_chunk(self.top); + self.check_malloc_state(); + } + + unsafe fn segment_holding(&self, ptr: *mut u8) -> *mut Segment { + let mut sp = &self.seg as *const Segment as *mut Segment; + while !sp.is_null() { + if (*sp).base <= ptr && ptr < Segment::top(sp) { + return sp; + } + sp = (*sp).next; + } + ptr::null_mut() + } + + unsafe fn tmalloc_small(&mut self, size: usize) -> *mut u8 { + let leastbit = least_bit(self.treemap); + let i = leastbit.trailing_zeros(); + let mut v = *self.treebin_at(i); + let mut t = v; + let mut rsize = Chunk::size(TreeChunk::chunk(t)) - size; + + loop { + t = TreeChunk::leftmost_child(t); + if t.is_null() { + break; + } + let trem = Chunk::size(TreeChunk::chunk(t)) - size; + if trem < rsize { + rsize = trem; + v = t; + } + } + + let vc = TreeChunk::chunk(v); + let r = Chunk::plus_offset(vc, size).cast::(); + debug_assert_eq!(Chunk::size(vc), rsize + size); + self.unlink_large_chunk(v); + if rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(vc, rsize + size); + } else { + let rc = TreeChunk::chunk(r); + Chunk::set_size_and_pinuse_of_inuse_chunk(vc, size); + Chunk::set_size_and_pinuse_of_free_chunk(rc, rsize); + self.replace_dv(rc, rsize); + } + Chunk::to_mem(vc) + } + + unsafe fn tmalloc_large(&mut self, size: usize) -> *mut u8 { + let mut v = ptr::null_mut(); + let mut rsize = !size + 1; + let idx = self.compute_tree_index(size); + let mut t = *self.treebin_at(idx); + if !t.is_null() { + // Traverse thre tree for this bin looking for a node with size + // equal to the `size` above. + let mut sizebits = size << leftshift_for_tree_index(idx); + // Keep track of the deepest untaken right subtree + let mut rst = ptr::null_mut(); + loop { + let csize = Chunk::size(TreeChunk::chunk(t)); + if csize >= size && csize - size < rsize { + v = t; + rsize = csize - size; + if rsize == 0 { + break; + } + } + let rt = (*t).child[1]; + t = (*t).child[(sizebits >> (mem::size_of::() * 8 - 1)) & 1]; + if !rt.is_null() && rt != t { + rst = rt; + } + if t.is_null() { + // Reset `t` to the least subtree holding sizes greater than + // the `size` above, breaking out + t = rst; + break; + } + sizebits <<= 1; + } + } + + // Set t to the root of the next non-empty treebin + if t.is_null() && v.is_null() { + let leftbits = left_bits(1 << idx) & self.treemap; + if leftbits != 0 { + let leastbit = least_bit(leftbits); + let i = leastbit.trailing_zeros(); + t = *self.treebin_at(i); + } + } + + // Find the smallest of this tree or subtree + while !t.is_null() { + let csize = Chunk::size(TreeChunk::chunk(t)); + if csize >= size && csize - size < rsize { + rsize = csize - size; + v = t; + } + t = TreeChunk::leftmost_child(t); + } + + // If dv is a better fit, then return null so malloc will use it + if v.is_null() || (self.dvsize >= size && !(rsize < self.dvsize - size)) { + return ptr::null_mut(); + } + + let vc = TreeChunk::chunk(v); + let r = Chunk::plus_offset(vc, size); + debug_assert_eq!(Chunk::size(vc), rsize + size); + self.unlink_large_chunk(v); + if rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(vc, rsize + size); + } else { + Chunk::set_size_and_pinuse_of_inuse_chunk(vc, size); + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + self.insert_chunk(r, rsize); + } + Chunk::to_mem(vc) + } + + unsafe fn smallbin_at(&mut self, idx: u32) -> *mut Chunk { + let idx = usize::try_from(idx * 2).unwrap(); + debug_assert!(idx < self.smallbins.len()); + self.smallbins.as_mut_ptr().add(idx).cast() + } + + unsafe fn treebin_at(&mut self, idx: u32) -> *mut *mut TreeChunk { + let idx = usize::try_from(idx).unwrap(); + debug_assert!(idx < self.treebins.len()); + self.treebins.as_mut_ptr().add(idx) + } + + fn compute_tree_index(&self, size: usize) -> u32 { + let x = size >> TREEBIN_SHIFT; + if x == 0 { + 0 + } else if x > 0xffff { + NTREEBINS_U32 - 1 + } else { + let k = mem::size_of_val(&x) * 8 - 1 - (x.leading_zeros() as usize); + ((k << 1) + (size >> (k + TREEBIN_SHIFT - 1) & 1)) as u32 + } + } + + unsafe fn unlink_first_small_chunk(&mut self, head: *mut Chunk, next: *mut Chunk, idx: u32) { + let ptr = (*next).prev; + debug_assert!(next != head); + debug_assert!(next != ptr); + debug_assert_eq!(Chunk::size(next), self.small_index2size(idx)); + if head == ptr { + self.clear_smallmap(idx); + } else { + (*ptr).next = head; + (*head).prev = ptr; + } + } + + unsafe fn replace_dv(&mut self, chunk: *mut Chunk, size: usize) { + let dvs = self.dvsize; + debug_assert!(self.is_small(dvs)); + if dvs != 0 { + let dv = self.dv; + self.insert_small_chunk(dv, dvs); + } + self.dvsize = size; + self.dv = chunk; + } + + unsafe fn insert_chunk(&mut self, chunk: *mut Chunk, size: usize) { + if self.is_small(size) { + self.insert_small_chunk(chunk, size); + } else { + self.insert_large_chunk(chunk.cast(), size); + } + } + + unsafe fn insert_small_chunk(&mut self, chunk: *mut Chunk, size: usize) { + let idx = self.small_index(size); + let head = self.smallbin_at(idx); + let mut f = head; + debug_assert!(size >= self.min_chunk_size()); + if !self.smallmap_is_marked(idx) { + self.mark_smallmap(idx); + } else { + f = (*head).prev; + } + + (*head).prev = chunk; + (*f).next = chunk; + (*chunk).prev = f; + (*chunk).next = head; + } + + unsafe fn insert_large_chunk(&mut self, chunk: *mut TreeChunk, size: usize) { + let idx = self.compute_tree_index(size); + let h = self.treebin_at(idx); + (*chunk).index = idx; + (*chunk).child[0] = ptr::null_mut(); + (*chunk).child[1] = ptr::null_mut(); + let chunkc = TreeChunk::chunk(chunk); + if !self.treemap_is_marked(idx) { + *h = chunk; + (*chunk).parent = h.cast(); // TODO: dubious? + (*chunkc).next = chunkc; + (*chunkc).prev = chunkc; + self.mark_treemap(idx); + } else { + let mut t = *h; + let mut k = size << leftshift_for_tree_index(idx); + loop { + if Chunk::size(TreeChunk::chunk(t)) != size { + let c = &mut (*t).child[(k >> mem::size_of::() * 8 - 1) & 1]; + k <<= 1; + if !c.is_null() { + t = *c; + } else { + *c = chunk; + (*chunk).parent = t; + (*chunkc).next = chunkc; + (*chunkc).prev = chunkc; + break; + } + } else { + let tc = TreeChunk::chunk(t); + let f = (*tc).prev; + (*f).next = chunkc; + (*tc).prev = chunkc; + (*chunkc).prev = f; + (*chunkc).next = tc; + (*chunk).parent = ptr::null_mut(); + break; + } + } + } + } + + unsafe fn smallmap_is_marked(&self, idx: u32) -> bool { + self.smallmap & (1 << idx) != 0 + } + + unsafe fn mark_smallmap(&mut self, idx: u32) { + self.smallmap |= 1 << idx; + } + + unsafe fn clear_smallmap(&mut self, idx: u32) { + self.smallmap &= !(1 << idx); + } + + unsafe fn treemap_is_marked(&self, idx: u32) -> bool { + self.treemap & (1 << idx) != 0 + } + + unsafe fn mark_treemap(&mut self, idx: u32) { + self.treemap |= 1 << idx; + } + + unsafe fn clear_treemap(&mut self, idx: u32) { + self.treemap &= !(1 << idx); + } + + unsafe fn unlink_chunk(&mut self, chunk: *mut Chunk, size: usize) { + if self.is_small(size) { + self.unlink_small_chunk(chunk, size) + } else { + self.unlink_large_chunk(chunk.cast()); + } + } + + unsafe fn unlink_small_chunk(&mut self, chunk: *mut Chunk, size: usize) { + let f = (*chunk).prev; + let b = (*chunk).next; + let idx = self.small_index(size); + debug_assert!(chunk != b); + debug_assert!(chunk != f); + debug_assert_eq!(Chunk::size(chunk), self.small_index2size(idx)); + if b == f { + self.clear_smallmap(idx); + } else { + (*f).next = b; + (*b).prev = f; + } + } + + unsafe fn unlink_large_chunk(&mut self, chunk: *mut TreeChunk) { + let xp = (*chunk).parent; + let mut r; + if TreeChunk::next(chunk) != chunk { + let f = TreeChunk::prev(chunk); + r = TreeChunk::next(chunk); + (*f).chunk.next = TreeChunk::chunk(r); + (*r).chunk.prev = TreeChunk::chunk(f); + } else { + let mut rp = &mut (*chunk).child[1]; + if rp.is_null() { + rp = &mut (*chunk).child[0]; + } + r = *rp; + if !rp.is_null() { + loop { + let mut cp = &mut (**rp).child[1]; + if cp.is_null() { + cp = &mut (**rp).child[0]; + } + if cp.is_null() { + break; + } + rp = cp; + } + r = *rp; + *rp = ptr::null_mut(); + } + } + + if xp.is_null() { + return; + } + + let h = self.treebin_at((*chunk).index); + if chunk == *h { + *h = r; + if r.is_null() { + self.clear_treemap((*chunk).index); + } + } else { + if (*xp).child[0] == chunk { + (*xp).child[0] = r; + } else { + (*xp).child[1] = r; + } + } + + if !r.is_null() { + (*r).parent = xp; + let c0 = (*chunk).child[0]; + if !c0.is_null() { + (*r).child[0] = c0; + (*c0).parent = r; + } + let c1 = (*chunk).child[1]; + if !c1.is_null() { + (*r).child[1] = c1; + (*c1).parent = r; + } + } + } + + pub unsafe fn usable_size(&mut self, ptr: *mut u8) -> usize { + let p = Chunk::from_mem(ptr); + let psize = Chunk::size(p); + + psize + } + + pub unsafe fn validate_size(&mut self, ptr: *mut u8, size: usize) { + let p = Chunk::from_mem(ptr); + let psize = Chunk::size(p); + + let min_overhead = self.overhead_for(p); + assert!(psize >= size + min_overhead); + + if !Chunk::mmapped(p) { + let max_overhead = + min_overhead + self.min_chunk_size() * 2 + mem::align_of::() - 1; + + assert!(psize <= size + max_overhead); + } + } + + pub unsafe fn free(&mut self, mem: *mut u8) { + self.check_malloc_state(); + + let mut p = Chunk::from_mem(mem); + let mut psize = Chunk::size(p); + let next = Chunk::plus_offset(p, psize); + if !Chunk::pinuse(p) { + let prevsize = (*p).prev_foot; + + if Chunk::mmapped(p) { + psize += prevsize + self.mmap_foot_pad(); + if self + .system_allocator + .free(p.cast::().sub(prevsize), psize) + { + self.footprint -= psize; + } + return; + } + + let prev = Chunk::minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if p != self.dv { + self.unlink_chunk(p, prevsize); + } else if (*next).head & INUSE == INUSE { + self.dvsize = psize; + Chunk::set_free_with_pinuse(p, psize, next); + return; + } + } + + // Consolidate forward if we can + if !Chunk::cinuse(next) { + if next == self.top { + self.topsize += psize; + let tsize = self.topsize; + self.top = p; + (*p).head = tsize | PINUSE; + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } + if self.should_trim(tsize) { + self.sys_trim(0); + } + return; + } else if next == self.dv { + self.dvsize += psize; + let dsize = self.dvsize; + self.dv = p; + Chunk::set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } else { + let nsize = Chunk::size(next); + psize += nsize; + self.unlink_chunk(next, nsize); + Chunk::set_size_and_pinuse_of_free_chunk(p, psize); + if p == self.dv { + self.dvsize = psize; + return; + } + } + } else { + Chunk::set_free_with_pinuse(p, psize, next); + } + + if self.is_small(psize) { + self.insert_small_chunk(p, psize); + self.check_free_chunk(p); + } else { + self.insert_large_chunk(p.cast(), psize); + self.check_free_chunk(p); + self.release_checks -= 1; + if self.release_checks == 0 { + self.release_unused_segments(); + } + } + } + + fn should_trim(&self, size: usize) -> bool { + size > self.trim_check + } + + unsafe fn sys_trim(&mut self, mut pad: usize) -> bool { + let mut released = 0; + if pad < self.max_request() && !self.top.is_null() { + pad += self.top_foot_size(); + if self.topsize > pad { + let unit = DEFAULT_GRANULARITY; + let extra = ((self.topsize - pad + unit - 1) / unit - 1) * unit; + let sp = self.segment_holding(self.top.cast()); + debug_assert!(!sp.is_null()); + + if !Segment::is_extern(sp) { + if Segment::can_release_part(&self.system_allocator, sp) { + if (*sp).size >= extra && !self.has_segment_link(sp) { + let newsize = (*sp).size - extra; + if self + .system_allocator + .free_part((*sp).base, (*sp).size, newsize) + { + released = extra; + } + } + } + } + + if released != 0 { + (*sp).size -= released; + self.footprint -= released; + let top = self.top; + let topsize = self.topsize - released; + self.init_top(top, topsize); + self.check_top_chunk(self.top); + } + } + + released += self.release_unused_segments(); + + if released == 0 && self.topsize > self.trim_check { + self.trim_check = usize::max_value(); + } + } + + released != 0 + } + + unsafe fn has_segment_link(&self, ptr: *mut Segment) -> bool { + let mut sp = &self.seg as *const Segment as *mut Segment; + while !sp.is_null() { + if Segment::holds(ptr, sp.cast()) { + return true; + } + sp = (*sp).next; + } + false + } + + /// Unmap and unlink any mapped segments that don't contain used chunks + unsafe fn release_unused_segments(&mut self) -> usize { + let mut released = 0; + let mut nsegs = 0; + let mut pred: *mut Segment = &mut self.seg; + let mut sp = (*pred).next; + while !sp.is_null() { + let base = (*sp).base; + let size = (*sp).size; + let next = (*sp).next; + nsegs += 1; + + if Segment::can_release_part(&self.system_allocator, sp) && !Segment::is_extern(sp) { + let p = self.align_as_chunk(base); + let psize = Chunk::size(p); + // We can unmap if the first chunk holds the entire segment and + // isn't pinned. + let chunk_top = p.cast::().add(psize); + let top = base.add(size - self.top_foot_size()); + if !Chunk::inuse(p) && chunk_top >= top { + let tp = p.cast::(); + debug_assert!(Segment::holds(sp, sp.cast())); + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } else { + self.unlink_large_chunk(tp); + } + if self.system_allocator.free(base, size) { + released += size; + self.footprint -= size; + // unlink our obsolete record + sp = pred; + (*sp).next = next; + } else { + // back out if we can't unmap + self.insert_large_chunk(tp, psize); + } + } + } + pred = sp; + sp = next; + } + self.release_checks = if nsegs > MAX_RELEASE_CHECK_RATE { + nsegs + } else { + MAX_RELEASE_CHECK_RATE + }; + return released; + } + + // Sanity checks + + unsafe fn check_any_chunk(&self, p: *mut Chunk) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + } + + unsafe fn check_top_chunk(&self, p: *mut Chunk) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let sp = self.segment_holding(p.cast()); + let sz = (*p).head & !INUSE; + debug_assert!(!sp.is_null()); + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + debug_assert_eq!(sz, self.topsize); + debug_assert!(sz > 0); + debug_assert_eq!( + sz, + (*sp).base as usize + (*sp).size - p as usize - self.top_foot_size() + ); + debug_assert!(Chunk::pinuse(p)); + debug_assert!(!Chunk::pinuse(Chunk::plus_offset(p, sz))); + } + + unsafe fn check_malloced_chunk(&self, mem: *mut u8, s: usize) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + if mem.is_null() { + return; + } + let p = Chunk::from_mem(mem); + let sz = (*p).head & !INUSE; + self.check_inuse_chunk(p); + debug_assert_eq!(align_up(sz, self.malloc_alignment()), sz); + debug_assert!(sz >= self.min_chunk_size()); + debug_assert!(sz >= s); + debug_assert!(Chunk::mmapped(p) || sz < (s + self.min_chunk_size())); + } + + unsafe fn check_inuse_chunk(&self, p: *mut Chunk) { + self.check_any_chunk(p); + debug_assert!(Chunk::inuse(p)); + debug_assert!(Chunk::pinuse(Chunk::next(p))); + debug_assert!(Chunk::mmapped(p) || Chunk::pinuse(p) || Chunk::next(Chunk::prev(p)) == p); + if Chunk::mmapped(p) { + self.check_mmapped_chunk(p); + } + } + + unsafe fn check_mmapped_chunk(&self, p: *mut Chunk) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let sz = Chunk::size(p); + let len = sz + (*p).prev_foot + self.mmap_foot_pad(); + debug_assert!(Chunk::mmapped(p)); + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + debug_assert!(!self.is_small(sz)); + debug_assert_eq!(align_up(len, self.system_allocator.page_size()), len); + debug_assert_eq!((*Chunk::plus_offset(p, sz)).head, Chunk::fencepost_head()); + debug_assert_eq!( + (*Chunk::plus_offset(p, sz + mem::size_of::())).head, + 0 + ); + } + + unsafe fn check_free_chunk(&self, p: *mut Chunk) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let sz = Chunk::size(p); + let next = Chunk::plus_offset(p, sz); + self.check_any_chunk(p); + debug_assert!(!Chunk::inuse(p)); + debug_assert!(!Chunk::pinuse(Chunk::next(p))); + debug_assert!(!Chunk::mmapped(p)); + if p != self.dv && p != self.top { + if sz >= self.min_chunk_size() { + debug_assert_eq!(align_up(sz, self.malloc_alignment()), sz); + debug_assert!(self.is_aligned(Chunk::to_mem(p) as usize)); + debug_assert_eq!((*next).prev_foot, sz); + debug_assert!(Chunk::pinuse(p)); + debug_assert!(next == self.top || Chunk::inuse(next)); + debug_assert_eq!((*(*p).next).prev, p); + debug_assert_eq!((*(*p).prev).next, p); + } else { + debug_assert_eq!(sz, mem::size_of::()); + } + } + } + + unsafe fn check_malloc_state(&mut self) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + for i in 0..NSMALLBINS_U32 { + self.check_smallbin(i); + } + for i in 0..NTREEBINS_U32 { + self.check_treebin(i); + } + if self.dvsize != 0 { + self.check_any_chunk(self.dv); + debug_assert_eq!(self.dvsize, Chunk::size(self.dv)); + debug_assert!(self.dvsize >= self.min_chunk_size()); + let dv = self.dv; + debug_assert!(!self.bin_find(dv)); + } + if !self.top.is_null() { + self.check_top_chunk(self.top); + debug_assert!(self.topsize > 0); + let top = self.top; + debug_assert!(!self.bin_find(top)); + } + let total = self.traverse_and_check(); + debug_assert!(total <= self.footprint); + debug_assert!(self.footprint <= self.max_footprint); + } + + unsafe fn check_smallbin(&mut self, idx: u32) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let b = self.smallbin_at(idx); + let mut p = (*b).next; + let empty = self.smallmap & (1 << idx) == 0; + if p == b { + debug_assert!(empty) + } + if !empty { + while p != b { + let size = Chunk::size(p); + self.check_free_chunk(p); + debug_assert_eq!(self.small_index(size), idx); + debug_assert!((*p).next == b || Chunk::size((*p).next) == Chunk::size(p)); + let q = Chunk::next(p); + if (*q).head != Chunk::fencepost_head() { + self.check_inuse_chunk(q); + } + p = (*p).next; + } + } + } + + unsafe fn check_treebin(&mut self, idx: u32) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let t = *self.treebin_at(idx); + let empty = self.treemap & (1 << idx) == 0; + if t.is_null() { + debug_assert!(empty); + } + if !empty { + self.check_tree(t); + } + } + + unsafe fn check_tree(&mut self, t: *mut TreeChunk) { + if !cfg!(all(feature = "debug", debug_assertions)) { + return; + } + let tc = TreeChunk::chunk(t); + let tindex = (*t).index; + let tsize = Chunk::size(tc); + let idx = self.compute_tree_index(tsize); + debug_assert_eq!(tindex, idx); + debug_assert!(tsize >= self.min_large_size()); + debug_assert!(tsize >= self.min_size_for_tree_index(idx)); + debug_assert!(idx == NTREEBINS_U32 - 1 || tsize < self.min_size_for_tree_index(idx + 1)); + + let mut u = t; + let mut head = ptr::null_mut::(); + loop { + let uc = TreeChunk::chunk(u); + self.check_any_chunk(uc); + debug_assert_eq!((*u).index, tindex); + debug_assert_eq!(Chunk::size(uc), tsize); + debug_assert!(!Chunk::inuse(uc)); + debug_assert!(!Chunk::pinuse(Chunk::next(uc))); + debug_assert_eq!((*(*uc).next).prev, uc); + debug_assert_eq!((*(*uc).prev).next, uc); + let left = (*u).child[0]; + let right = (*u).child[1]; + if (*u).parent.is_null() { + debug_assert!(left.is_null()); + debug_assert!(right.is_null()); + } else { + debug_assert!(head.is_null()); + head = u; + debug_assert!((*u).parent != u); + // TODO: unsure why this triggers UB in stacked borrows in MIRI + // (works in tree borrows though) + #[cfg(not(miri))] + debug_assert!( + (*(*u).parent).child[0] == u + || (*(*u).parent).child[1] == u + || *((*u).parent as *mut *mut TreeChunk) == u + ); + if !left.is_null() { + debug_assert_eq!((*left).parent, u); + debug_assert!(left != u); + self.check_tree(left); + } + if !right.is_null() { + debug_assert_eq!((*right).parent, u); + debug_assert!(right != u); + self.check_tree(right); + } + if !left.is_null() && !right.is_null() { + debug_assert!( + Chunk::size(TreeChunk::chunk(left)) < Chunk::size(TreeChunk::chunk(right)) + ); + } + } + + u = TreeChunk::prev(u); + if u == t { + break; + } + } + debug_assert!(!head.is_null()); + } + + fn min_size_for_tree_index(&self, idx: u32) -> usize { + let idx = usize::try_from(idx).unwrap(); + (1 << ((idx >> 1) + TREEBIN_SHIFT)) | ((idx & 1) << ((idx >> 1) + TREEBIN_SHIFT - 1)) + } + + unsafe fn bin_find(&mut self, chunk: *mut Chunk) -> bool { + let size = Chunk::size(chunk); + if self.is_small(size) { + let sidx = self.small_index(size); + let b = self.smallbin_at(sidx); + if !self.smallmap_is_marked(sidx) { + return false; + } + let mut p = b; + loop { + if p == chunk { + return true; + } + p = (*p).prev; + if p == b { + return false; + } + } + } else { + let tidx = self.compute_tree_index(size); + if !self.treemap_is_marked(tidx) { + return false; + } + let mut t = *self.treebin_at(tidx); + let mut sizebits = size << leftshift_for_tree_index(tidx); + while !t.is_null() && Chunk::size(TreeChunk::chunk(t)) != size { + t = (*t).child[(sizebits >> (mem::size_of::() * 8 - 1)) & 1]; + sizebits <<= 1; + } + if t.is_null() { + return false; + } + let mut u = t; + let chunk = chunk.cast(); + loop { + if u == chunk { + return true; + } + u = TreeChunk::prev(u); + if u == t { + return false; + } + } + } + } + + unsafe fn traverse_and_check(&self) -> usize { + 0 + } + + pub unsafe fn trim(&mut self, pad: usize) -> bool { + self.sys_trim(pad) + } + + pub unsafe fn destroy(mut self) -> usize { + let mut freed = 0; + let mut sp: *mut Segment = &mut self.seg; + while !sp.is_null() { + let base = (*sp).base; + let size = (*sp).size; + let can_free = !base.is_null() && !Segment::is_extern(sp); + sp = (*sp).next; + + if can_free && self.system_allocator.free(base, size) { + freed += size; + } + } + freed + } +} + +const PINUSE: usize = 1 << 0; +const CINUSE: usize = 1 << 1; +const FLAG4: usize = 1 << 2; +const INUSE: usize = PINUSE | CINUSE; +const FLAG_BITS: usize = PINUSE | CINUSE | FLAG4; + +impl Chunk { + unsafe fn fencepost_head() -> usize { + INUSE | mem::size_of::() + } + + unsafe fn size(me: *mut Chunk) -> usize { + (*me).head & !FLAG_BITS + } + + unsafe fn next(me: *mut Chunk) -> *mut Chunk { + me.cast::().add((*me).head & !FLAG_BITS).cast() + } + + unsafe fn prev(me: *mut Chunk) -> *mut Chunk { + me.cast::().sub((*me).prev_foot).cast() + } + + unsafe fn cinuse(me: *mut Chunk) -> bool { + (*me).head & CINUSE != 0 + } + + unsafe fn pinuse(me: *mut Chunk) -> bool { + (*me).head & PINUSE != 0 + } + + unsafe fn clear_pinuse(me: *mut Chunk) { + (*me).head &= !PINUSE; + } + + unsafe fn inuse(me: *mut Chunk) -> bool { + (*me).head & INUSE != PINUSE + } + + unsafe fn mmapped(me: *mut Chunk) -> bool { + (*me).head & INUSE == 0 + } + + unsafe fn set_inuse(me: *mut Chunk, size: usize) { + (*me).head = ((*me).head & PINUSE) | size | CINUSE; + let next = Chunk::plus_offset(me, size); + (*next).head |= PINUSE; + } + + unsafe fn set_inuse_and_pinuse(me: *mut Chunk, size: usize) { + (*me).head = PINUSE | size | CINUSE; + let next = Chunk::plus_offset(me, size); + (*next).head |= PINUSE; + } + + unsafe fn set_size_and_pinuse_of_inuse_chunk(me: *mut Chunk, size: usize) { + (*me).head = size | PINUSE | CINUSE; + } + + unsafe fn set_size_and_pinuse_of_free_chunk(me: *mut Chunk, size: usize) { + (*me).head = size | PINUSE; + Chunk::set_foot(me, size); + } + + unsafe fn set_free_with_pinuse(p: *mut Chunk, size: usize, n: *mut Chunk) { + Chunk::clear_pinuse(n); + Chunk::set_size_and_pinuse_of_free_chunk(p, size); + } + + unsafe fn set_foot(me: *mut Chunk, size: usize) { + let next = Chunk::plus_offset(me, size); + (*next).prev_foot = size; + } + + unsafe fn plus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { + me.cast::().add(offset).cast() + } + + unsafe fn minus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { + me.cast::().sub(offset).cast() + } + + unsafe fn to_mem(me: *mut Chunk) -> *mut u8 { + me.cast::().add(Chunk::mem_offset()) + } + + fn mem_offset() -> usize { + 2 * mem::size_of::() + } + + unsafe fn from_mem(mem: *mut u8) -> *mut Chunk { + mem.sub(2 * mem::size_of::()).cast() + } +} + +impl TreeChunk { + unsafe fn leftmost_child(me: *mut TreeChunk) -> *mut TreeChunk { + let left = (*me).child[0]; + if left.is_null() { + (*me).child[1] + } else { + left + } + } + + unsafe fn chunk(me: *mut TreeChunk) -> *mut Chunk { + ptr::addr_of_mut!((*me).chunk) + } + + unsafe fn next(me: *mut TreeChunk) -> *mut TreeChunk { + (*TreeChunk::chunk(me)).next.cast() + } + + unsafe fn prev(me: *mut TreeChunk) -> *mut TreeChunk { + (*TreeChunk::chunk(me)).prev.cast() + } +} + +const EXTERN: u32 = 1 << 0; + +impl Segment { + unsafe fn is_extern(seg: *mut Segment) -> bool { + (*seg).flags & EXTERN != 0 + } + + unsafe fn can_release_part(system_allocator: &A, seg: *mut Segment) -> bool { + system_allocator.can_release_part((*seg).flags >> 1) + } + + unsafe fn sys_flags(seg: *mut Segment) -> u32 { + (*seg).flags >> 1 + } + + unsafe fn holds(seg: *mut Segment, addr: *mut u8) -> bool { + (*seg).base <= addr && addr < Segment::top(seg) + } + + unsafe fn top(seg: *mut Segment) -> *mut u8 { + (*seg).base.add((*seg).size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::System; + + // Prime the allocator with some allocations such that there will be free + // chunks in the treemap + unsafe fn setup_treemap(a: &mut Dlmalloc) { + let large_request_size = NSMALLBINS * (1 << SMALLBIN_SHIFT); + assert!(!a.is_small(large_request_size)); + let large_request1 = a.malloc(large_request_size); + assert_ne!(large_request1, ptr::null_mut()); + let large_request2 = a.malloc(large_request_size); + assert_ne!(large_request2, ptr::null_mut()); + a.free(large_request1); + assert_ne!(a.treemap, 0); + } + + #[test] + // Test allocating, with a non-empty treemap, a specific size that used to + // trigger an integer overflow bug + fn treemap_alloc_overflow_minimal() { + let mut a = Dlmalloc::new(System::new()); + unsafe { + setup_treemap(&mut a); + let min_idx31_size = (0xc000 << TREEBIN_SHIFT) - a.chunk_overhead() + 1; + assert_ne!(a.malloc(min_idx31_size), ptr::null_mut()); + } + } + + #[test] + #[cfg(not(miri))] + // Test allocating the maximum request size with a non-empty treemap + fn treemap_alloc_max() { + let mut a = Dlmalloc::new(System::new()); + unsafe { + setup_treemap(&mut a); + let max_request_size = a.max_request() - 1; + assert_eq!(a.malloc(max_request_size), ptr::null_mut()); + } + } +} diff --git a/dlmalloc-rs/src/dummy.rs b/dlmalloc-rs/src/dummy.rs new file mode 100644 index 0000000000..8654518638 --- /dev/null +++ b/dlmalloc-rs/src/dummy.rs @@ -0,0 +1,42 @@ +use crate::Allocator; +use core::ptr; + +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +unsafe impl Allocator for System { + fn alloc(&self, _size: usize) -> (*mut u8, usize, u32) { + (ptr::null_mut(), 0, 0) + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + false + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + false + } + + fn page_size(&self) -> usize { + 1 + } +} diff --git a/dlmalloc-rs/src/global.rs b/dlmalloc-rs/src/global.rs new file mode 100644 index 0000000000..d07fdf953c --- /dev/null +++ b/dlmalloc-rs/src/global.rs @@ -0,0 +1,56 @@ +use crate::Dlmalloc; +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr; + +pub use crate::sys::enable_alloc_after_fork; + +/// An instance of a "global allocator" backed by `Dlmalloc` +/// +/// This API requires the `global` feature is activated, and this type +/// implements the `GlobalAlloc` trait in the standard library. +pub struct GlobalDlmalloc; + +static mut DLMALLOC: Dlmalloc = Dlmalloc::new(); + +unsafe impl GlobalAlloc for GlobalDlmalloc { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let _guard = lock(); + let dlmalloc = ptr::addr_of_mut!(DLMALLOC); + (*dlmalloc).malloc(layout.size(), layout.align()) + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let _guard = lock(); + let dlmalloc = ptr::addr_of_mut!(DLMALLOC); + (*dlmalloc).free(ptr, layout.size(), layout.align()) + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let _guard = lock(); + let dlmalloc = ptr::addr_of_mut!(DLMALLOC); + (*dlmalloc).calloc(layout.size(), layout.align()) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let _guard = lock(); + let dlmalloc = ptr::addr_of_mut!(DLMALLOC); + (*dlmalloc).realloc(ptr, layout.size(), layout.align(), new_size) + } +} + +unsafe fn lock() -> impl Drop { + crate::sys::acquire_global_lock(); + + struct Guard; + impl Drop for Guard { + fn drop(&mut self) { + crate::sys::release_global_lock() + } + } + + Guard +} diff --git a/dlmalloc-rs/src/lib.rs b/dlmalloc-rs/src/lib.rs new file mode 100644 index 0000000000..ed20c51a91 --- /dev/null +++ b/dlmalloc-rs/src/lib.rs @@ -0,0 +1,230 @@ +//! A Rust port of the `dlmalloc` allocator. +//! +//! The `dlmalloc` allocator is described at +//! and this Rust crate is a straight +//! port of the C code for the allocator into Rust. The implementation is +//! wrapped up in a `Dlmalloc` type and has support for Linux, OSX, and Wasm +//! currently. +//! +//! The primary purpose of this crate is that it serves as the default memory +//! allocator for the `wasm32-unknown-unknown` target in the standard library. +//! Support for other platforms is largely untested and unused, but is used when +//! testing this crate. + +#![allow(dead_code)] +#![no_std] +#![deny(missing_docs)] + +#[cfg(feature = "rust_api")] +use core::{cmp, ptr}; + +#[cfg(feature = "system")] +use sys::System; + +#[cfg(feature = "global")] +pub use self::global::{enable_alloc_after_fork, GlobalDlmalloc}; + +mod dlmalloc; + +#[cfg(feature = "c_api")] +pub use dlmalloc::Dlmalloc as DlmallocCApi; + +#[cfg(feature = "global")] +mod global; + +/// In order for this crate to efficiently manage memory, it needs a way to communicate with the +/// underlying platform. This `Allocator` trait provides an interface for this communication. +pub unsafe trait Allocator: Send { + /// Allocates system memory region of at least `size` bytes + /// Returns a triple of `(base, size, flags)` where `base` is a pointer to the beginning of the + /// allocated memory region. `size` is the actual size of the region while `flags` specifies + /// properties of the allocated region. If `EXTERN_BIT` (bit 0) set in flags, then we did not + /// allocate this segment and so should not try to deallocate or merge with others. + /// This function can return a `std::ptr::null_mut()` when allocation fails (other values of + /// the triple will be ignored). + fn alloc(&self, size: usize) -> (*mut u8, usize, u32); + + /// Remaps system memory region at `ptr` with size `oldsize` to a potential new location with + /// size `newsize`. `can_move` indicates if the location is allowed to move to a completely new + /// location, or that it is only allowed to change in size. Returns a pointer to the new + /// location in memory. + /// This function can return a `std::ptr::null_mut()` to signal an error. + fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8; + + /// Frees a part of a memory chunk. The original memory chunk starts at `ptr` with size `oldsize` + /// and is turned into a memory region starting at the same address but with `newsize` bytes. + /// Returns `true` iff the access memory region could be freed. + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool; + + /// Frees an entire memory region. Returns `true` iff the operation succeeded. When `false` is + /// returned, the `dlmalloc` may re-use the location on future allocation requests + fn free(&self, ptr: *mut u8, size: usize) -> bool; + + /// Indicates if the system can release a part of memory. For the `flags` argument, see + /// `Allocator::alloc` + fn can_release_part(&self, flags: u32) -> bool; + + /// Indicates whether newly allocated regions contain zeros. + fn allocates_zeros(&self) -> bool; + + /// Returns the page size. Must be a power of two + fn page_size(&self) -> usize; +} + +/// An allocator instance +/// +/// Instances of this type are used to allocate blocks of memory. For best +/// results only use one of these. Currently doesn't implement `Drop` to release +/// lingering memory back to the OS. That may happen eventually though! +#[cfg(feature = "rust_api")] +pub struct Dlmalloc< + #[cfg(feature = "system")] + A = System, + #[cfg(not(feature = "system"))] + A, +>(dlmalloc::Dlmalloc); + +cfg_if::cfg_if! { + if #[cfg(all(feature = "system", target_family = "wasm"))] { + #[path = "wasm.rs"] + mod sys; + } else if #[cfg(all(feature = "system", target_os = "windows"))] { + #[path = "windows.rs"] + mod sys; + } else if #[cfg(all(feature = "system", target_os = "xous"))] { + #[path = "xous.rs"] + mod sys; + } else if #[cfg(all(feature = "system", any(target_os = "linux", target_os = "macos", target_os = "redox")))] { + #[path = "unix.rs"] + mod sys; + } else { + #[path = "dummy.rs"] + mod sys; + } +} + +#[cfg(feature = "system")] +#[cfg(feature = "rust_api")] +impl Dlmalloc { + /// Creates a new instance of an allocator + pub const fn new() -> Dlmalloc { + Dlmalloc(dlmalloc::Dlmalloc::new(System::new())) + } +} + +#[cfg(feature = "rust_api")] +impl Dlmalloc { + /// Creates a new instance of an allocator + pub const fn new_with_allocator(sys_allocator: A) -> Dlmalloc { + Dlmalloc(dlmalloc::Dlmalloc::new(sys_allocator)) + } +} + +#[cfg(feature = "rust_api")] +impl Dlmalloc { + /// Allocates `size` bytes with `align` align. + /// + /// Returns a null pointer if allocation fails. Returns a valid pointer + /// otherwise. + /// + /// Safety and contracts are largely governed by the `GlobalAlloc::alloc` + /// method contracts. + #[inline] + pub unsafe fn malloc(&mut self, size: usize, align: usize) -> *mut u8 { + if align <= self.0.malloc_alignment() { + self.0.malloc(size) + } else { + self.0.memalign(align, size) + } + } + + /// Same as `malloc`, except if the allocation succeeds it's guaranteed to + /// point to `size` bytes of zeros. + #[inline] + pub unsafe fn calloc(&mut self, size: usize, align: usize) -> *mut u8 { + let ptr = self.malloc(size, align); + if !ptr.is_null() && self.0.calloc_must_clear(ptr) { + ptr::write_bytes(ptr, 0, size); + } + ptr + } + + /// Deallocates a `ptr` with `size` and `align` as the previous request used + /// to allocate it. + /// + /// Safety and contracts are largely governed by the `GlobalAlloc::dealloc` + /// method contracts. + #[inline] + pub unsafe fn free(&mut self, ptr: *mut u8, size: usize, align: usize) { + let _ = align; + self.0.validate_size(ptr, size); + self.0.free(ptr) + } + + /// Reallocates `ptr`, a previous allocation with `old_size` and + /// `old_align`, to have `new_size` and the same alignment as before. + /// + /// Returns a null pointer if the memory couldn't be reallocated, but `ptr` + /// is still valid. Returns a valid pointer and frees `ptr` if the request + /// is satisfied. + /// + /// Safety and contracts are largely governed by the `GlobalAlloc::realloc` + /// method contracts. + #[inline] + pub unsafe fn realloc( + &mut self, + ptr: *mut u8, + old_size: usize, + old_align: usize, + new_size: usize, + ) -> *mut u8 { + self.0.validate_size(ptr, old_size); + + if old_align <= self.0.malloc_alignment() { + self.0.realloc(ptr, new_size) + } else { + let res = self.malloc(new_size, old_align); + if !res.is_null() { + let size = cmp::min(old_size, new_size); + ptr::copy_nonoverlapping(ptr, res, size); + self.free(ptr, old_size, old_align); + } + res + } + } + + /// If possible, gives memory back to the system if there is unused memory + /// at the high end of the malloc pool or in unused segments. + /// + /// You can call this after freeing large blocks of memory to potentially + /// reduce the system-level memory requirements of a program. However, it + /// cannot guarantee to reduce memory. Under some allocation patterns, some + /// large free blocks of memory will be locked between two used chunks, so + /// they cannot be given back to the system. + /// + /// The `pad` argument represents the amount of free trailing space to + /// leave untrimmed. If this argument is zero, only the minimum amount of + /// memory to maintain internal data structures will be left. Non-zero + /// arguments can be supplied to maintain enough trailing space to service + /// future expected allocations without having to re-obtain memory from the + /// system. + /// + /// Returns `true` if it actually released any memory, else `false`. + pub unsafe fn trim(&mut self, pad: usize) -> bool { + self.0.trim(pad) + } + + /// Releases all allocations in this allocator back to the system, + /// consuming self and preventing further use. + /// + /// Returns the number of bytes released to the system. + pub unsafe fn destroy(self) -> usize { + self.0.destroy() + } + + /// Get a reference the underlying [`Allocator`] that this `Dlmalloc` was + /// constructed with. + pub fn allocator(&self) -> &A { + self.0.allocator() + } +} diff --git a/dlmalloc-rs/src/unix.rs b/dlmalloc-rs/src/unix.rs new file mode 100644 index 0000000000..046014f867 --- /dev/null +++ b/dlmalloc-rs/src/unix.rs @@ -0,0 +1,131 @@ +use crate::Allocator; +use core::ptr; + +/// System setting for Linux +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +#[cfg(feature = "global")] +static mut LOCK: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER; + +unsafe impl Allocator for System { + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let addr = unsafe { + libc::mmap( + ptr::null_mut(), + size, + libc::PROT_WRITE | libc::PROT_READ, + libc::MAP_ANON | libc::MAP_PRIVATE, + -1, + 0, + ) + }; + if addr == libc::MAP_FAILED { + (ptr::null_mut(), 0, 0) + } else { + (addr.cast(), size, 0) + } + } + + #[cfg(target_os = "linux")] + fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8 { + let flags = if can_move { libc::MREMAP_MAYMOVE } else { 0 }; + let ptr = unsafe { libc::mremap(ptr.cast(), oldsize, newsize, flags) }; + if ptr == libc::MAP_FAILED { + ptr::null_mut() + } else { + ptr.cast() + } + } + + #[cfg(any(target_os = "redox", target_os = "macos"))] + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + ptr::null_mut() + } + + #[cfg(target_os = "linux")] + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { + unsafe { + let rc = libc::mremap(ptr.cast(), oldsize, newsize, 0); + if rc != libc::MAP_FAILED { + return true; + } + libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0 + } + } + + #[cfg(any(target_os = "redox", target_os = "macos"))] + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { + unsafe { libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0 } + } + + fn free(&self, ptr: *mut u8, size: usize) -> bool { + unsafe { libc::munmap(ptr.cast(), size) == 0 } + } + + fn can_release_part(&self, _flags: u32) -> bool { + true + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + 4096 + } +} + +#[cfg(feature = "global")] +pub fn acquire_global_lock() { + unsafe { assert_eq!(libc::pthread_mutex_lock(ptr::addr_of_mut!(LOCK)), 0) } +} + +#[cfg(feature = "global")] +pub fn release_global_lock() { + unsafe { assert_eq!(libc::pthread_mutex_unlock(ptr::addr_of_mut!(LOCK)), 0) } +} + +#[cfg(feature = "global")] +/// allows the allocator to remain unsable in the child process, +/// after a call to `fork(2)` +/// +/// #Safety +/// +/// if used, this function must be called, +/// before any allocations are made with the global allocator. +pub unsafe fn enable_alloc_after_fork() { + // atfork must only be called once, to avoid a deadlock, + // where the handler attempts to acquire the global lock twice + static mut FORK_PROTECTED: bool = false; + + unsafe extern "C" fn _acquire_global_lock() { + acquire_global_lock() + } + + unsafe extern "C" fn _release_global_lock() { + release_global_lock() + } + + acquire_global_lock(); + // if a process forks, + // it will acquire the lock before any other thread, + // protecting it from deadlock, + // due to the child being created with only the calling thread. + if !FORK_PROTECTED { + libc::pthread_atfork( + Some(_acquire_global_lock), + Some(_release_global_lock), + Some(_release_global_lock), + ); + FORK_PROTECTED = true; + } + release_global_lock(); +} diff --git a/dlmalloc-rs/src/wasm.rs b/dlmalloc-rs/src/wasm.rs new file mode 100644 index 0000000000..a8bf3ef315 --- /dev/null +++ b/dlmalloc-rs/src/wasm.rs @@ -0,0 +1,76 @@ +use crate::Allocator; +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; +use core::ptr; + +/// System setting for Wasm +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +unsafe impl Allocator for System { + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let pages = size / self.page_size(); + let prev = wasm::memory_grow(0, pages); + if prev == usize::max_value() { + return (ptr::null_mut(), 0, 0); + } + ( + (prev * self.page_size()) as *mut u8, + pages * self.page_size(), + 0, + ) + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + // TODO: I think this can be implemented near the end? + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + false + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + 64 * 1024 + } +} + +#[cfg(feature = "global")] +pub fn acquire_global_lock() { + // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); +} + +#[cfg(feature = "global")] +pub fn release_global_lock() { + // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); +} + +#[allow(missing_docs)] +#[cfg(feature = "global")] +pub unsafe fn enable_alloc_after_fork() { + // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); +} diff --git a/dlmalloc-rs/src/windows.rs b/dlmalloc-rs/src/windows.rs new file mode 100644 index 0000000000..ba88adca68 --- /dev/null +++ b/dlmalloc-rs/src/windows.rs @@ -0,0 +1,88 @@ +use crate::Allocator; +use core::mem::MaybeUninit; +use core::ptr; +use windows_sys::Win32::System::Memory::*; +use windows_sys::Win32::System::SystemInformation::*; +#[cfg(feature = "global")] +use windows_sys::Win32::System::Threading::*; + +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +unsafe impl Allocator for System { + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let addr = unsafe { + VirtualAlloc( + ptr::null_mut(), + size, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + ) + }; + + if addr.is_null() { + (ptr::null_mut(), 0, 0) + } else { + (addr.cast(), size, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + ptr::null_mut() + } + + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { + unsafe { VirtualFree(ptr.add(newsize).cast(), oldsize - newsize, MEM_DECOMMIT) != 0 } + } + + fn free(&self, ptr: *mut u8, _size: usize) -> bool { + unsafe { VirtualFree(ptr.cast(), 0, MEM_DECOMMIT) != 0 } + } + + fn can_release_part(&self, _flags: u32) -> bool { + true + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + unsafe { + let mut info = MaybeUninit::uninit(); + GetSystemInfo(info.as_mut_ptr()); + info.assume_init_ref().dwPageSize as usize + } + } +} + +// NB: `SRWLOCK_INIT` doesn't appear to be in `windows-sys` +#[cfg(feature = "global")] +static mut LOCK: SRWLOCK = SRWLOCK { + Ptr: ptr::null_mut(), +}; + +#[cfg(feature = "global")] +pub fn acquire_global_lock() { + unsafe { + AcquireSRWLockExclusive(ptr::addr_of_mut!(LOCK)); + } +} + +#[cfg(feature = "global")] +pub fn release_global_lock() { + unsafe { + ReleaseSRWLockExclusive(ptr::addr_of_mut!(LOCK)); + } +} + +/// Not needed on Windows +#[cfg(feature = "global")] +pub unsafe fn enable_alloc_after_fork() {} diff --git a/dlmalloc-rs/src/xous.rs b/dlmalloc-rs/src/xous.rs new file mode 100644 index 0000000000..14dbfa1bca --- /dev/null +++ b/dlmalloc-rs/src/xous.rs @@ -0,0 +1,117 @@ +use crate::Allocator; +use core::ptr; + +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +#[cfg(target_arch = "riscv32")] +mod sys { + use core::arch::asm; + + pub fn increase_heap(length: usize) -> Result<(usize, usize), ()> { + let syscall_no_increase_heap = 10usize; + let memory_flags_read_write = 2usize | 4usize; + + let mut a0 = syscall_no_increase_heap; + let mut a1 = length; + let mut a2 = memory_flags_read_write; + + unsafe { + asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2, + out("a3") _, + out("a4") _, + out("a5") _, + out("a6") _, + out("a7") _, + ) + }; + + let result = a0; + let address = a1; + let length = a2; + + // 3 is the "MemoryRange" type, and the result is only valid + // if we get nonzero address and length. + if result == 3 && address != 0 && length != 0 { + Ok((address, length)) + } else { + Err(()) + } + } +} + +unsafe impl Allocator for System { + /// Allocate an additional `size` bytes on the heap, and return a new + /// chunk of memory, as well as the size of the allocation and some + /// flags. Since flags are unused on this platform, they will always + /// be `0`. + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let size = if size == 0 { + 4096 + } else if size & 4095 == 0 { + size + } else { + size + (4096 - (size & 4095)) + }; + + if let Ok((address, length)) = sys::increase_heap(size) { + let start = address - size + length; + (start as *mut u8, size, 0) + } else { + (ptr::null_mut(), 0, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + // TODO + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + false + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + 4 * 1024 + } +} + +#[cfg(feature = "global")] +pub fn acquire_global_lock() { + // global feature should not be enabled + unimplemented!() +} + +#[cfg(feature = "global")] +pub fn release_global_lock() { + // global feature should not be enabled + unimplemented!() +} + +#[cfg(feature = "global")] +pub unsafe fn enable_alloc_after_fork() { + // platform does not support `fork()` call +} diff --git a/dlmalloc-rs/tests/global.rs b/dlmalloc-rs/tests/global.rs new file mode 100644 index 0000000000..b5cfacd2aa --- /dev/null +++ b/dlmalloc-rs/tests/global.rs @@ -0,0 +1,32 @@ +extern crate dlmalloc; + +use std::collections::HashMap; +use std::thread; + +#[global_allocator] +#[cfg(feature = "global")] +static A: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; + +#[test] +fn foo() { + println!("hello"); +} + +#[test] +fn map() { + let mut m = HashMap::new(); + m.insert(1, 2); + m.insert(5, 3); + drop(m); +} + +#[test] +fn strings() { + format!("foo, bar, {}", "baz"); +} + +#[test] +#[cfg(not(target_family = "wasm"))] +fn threads() { + assert!(thread::spawn(|| panic!()).join().is_err()); +} diff --git a/dlmalloc-rs/tests/smoke.rs b/dlmalloc-rs/tests/smoke.rs new file mode 100644 index 0000000000..8bcb5d7e46 --- /dev/null +++ b/dlmalloc-rs/tests/smoke.rs @@ -0,0 +1,36 @@ +use arbitrary::Unstructured; +use dlmalloc::Dlmalloc; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; + +#[test] +fn smoke() { + let mut a = Dlmalloc::new(); + unsafe { + let ptr = a.malloc(1, 1); + assert!(!ptr.is_null()); + *ptr = 9; + assert_eq!(*ptr, 9); + a.free(ptr, 1, 1); + + let ptr = a.malloc(1, 1); + assert!(!ptr.is_null()); + *ptr = 10; + assert_eq!(*ptr, 10); + a.free(ptr, 1, 1); + } +} + +#[path = "../fuzz/src/lib.rs"] +mod fuzz; + +#[test] +fn stress() { + let mut rng = SmallRng::seed_from_u64(0); + let mut buf = vec![0; 4096]; + let iters = if cfg!(miri) { 5 } else { 2000 }; + for _ in 0..iters { + rng.fill_bytes(&mut buf); + let mut u = Unstructured::new(&buf); + let _ = fuzz::run(&mut u); + } +} diff --git a/fmt.sh b/fmt.sh index be8636e2ab..8caff707ae 100755 --- a/fmt.sh +++ b/fmt.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -cargo fmt --package relibc --package crt0 "$@" +cargo fmt --package relibc --package crt0 --package redox-rt "$@" diff --git a/generic-rt/Cargo.toml b/generic-rt/Cargo.toml new file mode 100644 index 0000000000..ba01e30d2b --- /dev/null +++ b/generic-rt/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "generic-rt" +version = "0.1.0" +edition = "2024" + +[lints] +workspace = true diff --git a/generic-rt/src/lib.rs b/generic-rt/src/lib.rs new file mode 100644 index 0000000000..dca94521f0 --- /dev/null +++ b/generic-rt/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] +#![allow(internal_features)] +#![feature(core_intrinsics)] +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{ + arch::asm, + mem::{self, offset_of}, +}; + +#[derive(Debug)] +#[repr(C)] +pub struct GenericTcb { + /// Pointer to the end of static TLS. Must be the first member + pub tls_end: *mut u8, + /// Size of the memory allocated for the static TLS in bytes (multiple of page size) + pub tls_len: usize, + /// Pointer to this structure + pub tcb_ptr: *mut Self, + /// Size of the memory allocated for this structure in bytes (should be same as page size) + pub tcb_len: usize, + pub os_specific: Os, +} +impl GenericTcb { + /// Architecture specific code to read a usize from the TCB - aarch64 + #[allow(unsafe_op_in_unsafe_fn)] + #[inline(always)] + #[cfg(target_arch = "aarch64")] + pub unsafe fn arch_read(offset: usize) -> usize { + let abi_ptr: usize; + asm!( + "mrs {}, tpidr_el0", + out(reg) abi_ptr, + ); + + let tcb_ptr = *(abi_ptr as *const usize); + *((tcb_ptr + offset) as *const usize) + } + + /// Architecture specific code to read a usize from the TCB - x86 + #[allow(unsafe_op_in_unsafe_fn)] + #[inline(always)] + #[cfg(target_arch = "x86")] + pub unsafe fn arch_read(offset: usize) -> usize { + let value; + asm!( + " + mov {}, gs:[{}] + ", + out(reg) value, + in(reg) offset, + ); + value + } + + /// Architecture specific code to read a usize from the TCB - x86_64 + #[allow(unsafe_op_in_unsafe_fn)] + #[inline(always)] + #[cfg(target_arch = "x86_64")] + pub unsafe fn arch_read(offset: usize) -> usize { + let value; + asm!( + " + mov {}, fs:[{}] + ", + out(reg) value, + in(reg) offset, + ); + value + } + + /// Architecture specific code to read a usize from the TCB - riscv64 + #[allow(unsafe_op_in_unsafe_fn)] + #[inline(always)] + #[cfg(target_arch = "riscv64")] + unsafe fn arch_read(offset: usize) -> usize { + let value; + asm!( + "ld {value}, -8(tp)", // TCB + "add {value}, {value}, {offset}", + "ld {value}, 0({value})", + value = out(reg) value, + offset = in(reg) offset, + ); + value + } + + pub unsafe fn current_ptr() -> Option<*mut Self> { + let tcb_ptr = unsafe { Self::arch_read(offset_of!(Self, tcb_ptr)) as *mut Self }; + let tcb_len = unsafe { Self::arch_read(offset_of!(Self, tcb_len)) }; + if tcb_ptr.is_null() || tcb_len < mem::size_of::() { + None + } else { + Some(tcb_ptr) + } + } + pub unsafe fn current() -> Option<&'static mut Self> { + unsafe { Some(&mut *Self::current_ptr()?) } + } +} +pub fn panic_notls(_msg: impl core::fmt::Display) -> ! { + // TODO: actually print _msg, perhaps by having panic_notls take a `T: DebugBackend` that can + // propagate until called by e.g. relibc start + core::intrinsics::abort(); +} + +pub trait ExpectTlsFree { + type Unwrapped; + + fn expect_notls(self, msg: &str) -> Self::Unwrapped; +} +impl ExpectTlsFree for Result { + type Unwrapped = T; + + fn expect_notls(self, msg: &str) -> T { + match self { + Ok(t) => t, + Err(err) => panic_notls(format_args!( + "{msg}: expect failed for Result with err: {err:?}", + )), + } + } +} +impl ExpectTlsFree for Option { + type Unwrapped = T; + + fn expect_notls(self, msg: &str) -> T { + match self { + Some(t) => t, + None => panic_notls(format_args!("{msg}: expect failed for Option")), + } + } +} diff --git a/include/bits/assert.h b/include/bits/assert.h deleted file mode 100644 index ff1cad697e..0000000000 --- a/include/bits/assert.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef _BITS_ASSERT_H -#define _BITS_ASSERT_H - -#ifdef NDEBUG -# define assert(cond) (void) 0 -#else -# define assert(cond) \ - ((void)((cond) || (__assert_fail(__func__, __FILE__, __LINE__, #cond), 0))) -#endif - -#endif diff --git a/include/bits/ctype.h b/include/bits/ctype.h deleted file mode 100644 index e5dedf5d30..0000000000 --- a/include/bits/ctype.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef _BITS_CTYPE_H -#define _BITS_CTYPE_H - -#define _tolower(c) tolower(c) -#define _toupper(c) toupper(c) - -#endif /* _BITS_CTYPE_H */ diff --git a/include/bits/dirent.h b/include/bits/dirent.h deleted file mode 100644 index 6fb5ed37b6..0000000000 --- a/include/bits/dirent.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef _BITS_DIRENT_H -#define _BITS_DIRENT_H - -// Shamelessly stolen from musl -#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 -#define DT_WHT 14 -#define IFTODT(x) ((x)>>12 & 017) -#define DTTOIF(x) ((x)<<12) - -#endif /* _BITS_DIRENT_H */ diff --git a/include/bits/elf.h b/include/bits/elf.h deleted file mode 100644 index 930f99ed8f..0000000000 --- a/include/bits/elf.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _BITS_ELF_H -#define _BITS_ELF_H -#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4) -#define ELF32_ST_TYPE(val) ((val) & 0xf) -#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) - -#define ELF64_ST_BIND(val) ELF32_ST_BIND (val) -#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val) -#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type)) - - -#define ELF32_ST_VISIBILITY(o) ((o) & 0x03) -#define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o) - -#define ELF32_R_SYM(val) ((val) >> 8) -#define ELF32_R_TYPE(val) ((val) & 0xff) -#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff)) - -#define ELF64_R_SYM(i) ((i) >> 32) -#define ELF64_R_TYPE(i) ((i) & 0xffffffff) -#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type)) - -#define DT_VALRNGHI 0x6ffffdff -#define DT_VALTAGIDX(tag) (DT_VALRNGHI - (tag)) - -#define DT_ADDRRNGHI 0x6ffffeff -#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) - -#define DT_VERNEEDNUM 0x6fffffff -#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) - -#define ELF32_M_SYM(info) ((info) >> 8) -#define ELF32_M_SIZE(info) ((unsigned char) (info)) -#define ELF32_M_INFO(sym, size) (((sym) << 8) + (unsigned char) (size)) - -#define ELF64_M_SYM(info) ELF32_M_SYM (info) -#define ELF64_M_SIZE(info) ELF32_M_SIZE (info) -#define ELF64_M_INFO(sym, size) ELF32_M_INFO (sym, size) - -#endif /* ifdef _BITS_ELF_H*/ diff --git a/include/bits/errno.h b/include/bits/errno.h deleted file mode 100644 index 9594ee574d..0000000000 --- a/include/bits/errno.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _BITS_ERRNO_H -#define _BITS_ERRNO_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define ENOTSUP EOPNOTSUPP - -#define errno (*__errno_location()) -#define program_invocation_name (*__program_invocation_name()) -#define program_invocation_short_name (*__program_invocation_short_name()) - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* _BITS_ERRNO_H */ diff --git a/include/bits/fcntl.h b/include/bits/fcntl.h deleted file mode 100644 index 3776447736..0000000000 --- a/include/bits/fcntl.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _BITS_FCNTL_H -#define _BITS_FCNTL_H - -#ifdef __cplusplus -extern "C" { -#endif - -int open(const char* filename, int flags, ...); -int fcntl(int fildes, int cmd, ...); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/include/bits/float.h b/include/bits/float.h deleted file mode 100644 index fd1728a1a3..0000000000 --- a/include/bits/float.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef _BITS_FLOAT_H -#define _BITS_FLOAT_H - -#define FLT_ROUNDS (flt_rounds()) - -// Shamelessly copy pasted from musl: - -#define FLT_TRUE_MIN 1.40129846432481707092e-45F -#define FLT_MIN 1.17549435082228750797e-38F -#define FLT_MAX 3.40282346638528859812e+38F -#define FLT_EPSILON 1.1920928955078125e-07F - -#define FLT_MANT_DIG 24 -#define FLT_MIN_EXP (-125) -#define FLT_MAX_EXP 128 -#define FLT_HAS_SUBNORM 1 - -#define FLT_DIG 6 -#define FLT_DECIMAL_DIG 9 -#define FLT_MIN_10_EXP (-37) -#define FLT_MAX_10_EXP 38 - -#define DBL_TRUE_MIN 4.94065645841246544177e-324 -#define DBL_MIN 2.22507385850720138309e-308 -#define DBL_MAX 1.79769313486231570815e+308 -#define DBL_EPSILON 2.22044604925031308085e-16 - -#define DBL_MANT_DIG 53 -#define DBL_MIN_EXP (-1021) -#define DBL_MAX_EXP 1024 -#define DBL_HAS_SUBNORM 1 - -#define DBL_DIG 15 -#define DBL_DECIMAL_DIG 17 -#define DBL_MIN_10_EXP (-307) -#define DBL_MAX_10_EXP 308 - -#define LDBL_HAS_SUBNORM 1 -#define LDBL_DECIMAL_DIG DECIMAL_DIG - -// TODO: Support more architectures than x86_64 here: -#define LDBL_TRUE_MIN 3.6451995318824746025e-4951L -#define LDBL_MIN 3.3621031431120935063e-4932L -#define LDBL_MAX 1.1897314953572317650e+4932L -#define LDBL_EPSILON 1.0842021724855044340e-19L - -#define LDBL_MANT_DIG 64 -#define LDBL_MIN_EXP (-16381) -#define LDBL_MAX_EXP 16384 - -#endif diff --git a/include/bits/inttypes.h b/include/bits/inttypes.h deleted file mode 100644 index 1117ce0cb2..0000000000 --- a/include/bits/inttypes.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef _BITS_INTTYPE_H -#define _BITS_INTTYPE_H - -#define PRId8 "hhd" -#define PRId16 "hd" -#define PRId32 "d" -#define PRId64 "ld" - -#define PRIdLEAST8 "hhd" -#define PRIdLEAST16 "hd" -#define PRIdLEAST32 "d" -#define PRIdLEAST64 "ld" - -#define PRIdFAST8 "hhd" -#define PRIdFAST16 "hd" -#define PRIdFAST32 "d" -#define PRIdFAST64 "ld" - -#define PRIi8 "hhi" -#define PRIi16 "hi" -#define PRIi32 "i" -#define PRIi64 "li" - -#define PRIiLEAST8 "hhi" -#define PRIiLEAST16 "hi" -#define PRIiLEAST32 "i" -#define PRIiLEAST64 "li" - -#define PRIiFAST8 "hhi" -#define PRIiFAST16 "hi" -#define PRIiFAST32 "i" -#define PRIiFAST64 "li" - -#define PRIo8 "hho" -#define PRIo16 "ho" -#define PRIo32 "o" -#define PRIo64 "lo" - -#define PRIoLEAST8 "hho" -#define PRIoLEAST16 "ho" -#define PRIoLEAST32 "o" -#define PRIoLEAST64 "lo" - -#define PRIoFAST8 "hho" -#define PRIoFAST16 "ho" -#define PRIoFAST32 "o" -#define PRIoFAST64 "lo" - -#define PRIu8 "hhu" -#define PRIu16 "hu" -#define PRIu32 "u" -#define PRIu64 "lu" - -#define PRIuLEAST8 "hhu" -#define PRIuLEAST16 "hu" -#define PRIuLEAST32 "u" -#define PRIuLEAST64 "lu" - -#define PRIuFAST8 "hhu" -#define PRIuFAST16 "hu" -#define PRIuFAST32 "u" -#define PRIuFAST64 "lu" - -#define PRIx8 "hhx" -#define PRIx16 "hx" -#define PRIx32 "x" -#define PRIx64 "lx" - -#define PRIxLEAST8 "hhx" -#define PRIxLEAST16 "hx" -#define PRIxLEAST32 "x" -#define PRIxLEAST64 "lx" - -#define PRIxFAST8 "hhx" -#define PRIxFAST16 "hx" -#define PRIxFAST32 "x" -#define PRIxFAST64 "lx" - -#define PRIX8 "hhX" -#define PRIX16 "hX" -#define PRIX32 "X" -#define PRIX64 "lX" - -#define PRIXLEAST8 "hhX" -#define PRIXLEAST16 "hX" -#define PRIXLEAST32 "X" -#define PRIXLEAST64 "lX" - -#define PRIXFAST8 "hhX" -#define PRIXFAST16 "hX" -#define PRIXFAST32 "X" -#define PRIXFAST64 "lX" - -#define PRIdMAX "jd" -#define PRIiMAX "ji" -#define PRIoMAX "jo" -#define PRIuMAX "ju" -#define PRIxMAX "jx" -#define PRIXMAX "jX" - -#define PRIdPTR "td" -#define PRIiPTR "ti" -#define PRIoPTR "to" -#define PRIuPTR "tu" -#define PRIxPTR "tx" -#define PRIXPTR "tX" - -#define SCNd8 PRId8 -#define SCNd16 PRId16 -#define SCNd32 PRId32 -#define SCNd64 PRId64 - -#define SCNdLEAST8 PRIdLEAST8 -#define SCNdLEAST16 PRIdLEAST16 -#define SCNdLEAST32 PRIdLEAST32 -#define SCNdLEAST64 PRIdLEAST64 - -#define SCNdFAST8 PRIdFAST8 -#define SCNdFAST16 PRIdFAST16 -#define SCNdFAST32 PRIdFAST32 -#define SCNdFAST64 PRIdFAST64 - -#define SCNi8 PRIi8 -#define SCNi16 PRIi16 -#define SCNi32 PRIi32 -#define SCNi64 PRIi64 - -#define SCNiLEAST8 PRIiLEAST8 -#define SCNiLEAST16 PRIiLEAST16 -#define SCNiLEAST32 PRIiLEAST32 -#define SCNiLEAST64 PRIiLEAST64 - -#define SCNiFAST8 PRIiFAST8 -#define SCNiFAST16 PRIiFAST16 -#define SCNiFAST32 PRIiFAST32 -#define SCNiFAST64 PRIiFAST64 - -#define SCNo8 PRIo8 -#define SCNo16 PRIo16 -#define SCNo32 PRIo32 -#define SCNo64 PRIo64 - -#define SCNoLEAST8 PRIoLEAST8 -#define SCNoLEAST16 PRIoLEAST16 -#define SCNoLEAST32 PRIoLEAST32 -#define SCNoLEAST64 PRIoLEAST64 - -#define SCNoFAST8 PRIoFAST8 -#define SCNoFAST16 PRIoFAST16 -#define SCNoFAST32 PRIoFAST32 -#define SCNoFAST64 PRIoFAST64 - -#define SCNu8 PRIu8 -#define SCNu16 PRIu16 -#define SCNu32 PRIu32 -#define SCNu64 PRIu64 - -#define SCNuLEAST8 PRIuLEAST8 -#define SCNuLEAST16 PRIuLEAST16 -#define SCNuLEAST32 PRIuLEAST32 -#define SCNuLEAST64 PRIuLEAST64 - -#define SCNuFAST8 PRIuFAST8 -#define SCNuFAST16 PRIuFAST16 -#define SCNuFAST32 PRIuFAST32 -#define SCNuFAST64 PRIuFAST64 - -#define SCNx8 PRIx8 -#define SCNx16 PRIx16 -#define SCNx32 PRIx32 -#define SCNx64 PRIx64 - -#define SCNxLEAST8 PRIxLEAST8 -#define SCNxLEAST16 PRIxLEAST16 -#define SCNxLEAST32 PRIxLEAST32 -#define SCNxLEAST64 PRIxLEAST64 - -#define SCNxFAST8 PRIxFAST8 -#define SCNxFAST16 PRIxFAST16 -#define SCNxFAST32 PRIxFAST32 -#define SCNxFAST64 PRIxFAST64 - -#define SCNdMAX PRIdMAX -#define SCNiMAX PRIiMAX -#define SCNoMAX PRIoMAX -#define SCNuMAX PRIuMAX -#define SCNxMAX PRIxMAX - -#define SCNdPTR PRIdPTR -#define SCNiPTR PRIiPTR -#define SCNoPTR PRIoPTR -#define SCNuPTR PRIuPTR -#define SCNxPTR PRIxPTR - -#endif diff --git a/include/bits/limits.h b/include/bits/limits.h deleted file mode 100644 index ec4b39a9b0..0000000000 --- a/include/bits/limits.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef _BITS_LIMIT_H -#define _BITS_LIMIT_H - -#define CHAR_BIT __CHAR_BIT__ -#ifdef __CHAR_MAX__ -# define CHAR_MAX __CHAR_MAX__ -#else -# define CHAR_MAX 0xFF -#endif -#define CHAR_MIN 0 -#define INT_MAX __INT_MAX__ -#define INT_MIN (-INT_MAX - 1) -#define LLONG_MAX __LONG_LONG_MAX__ -#define LLONG_MIN (-LLONG_MAX - 1) -#define LONG_BIT __LONG_WIDTH__ -#define LONG_MAX __LONG_MAX__ -#define LONG_MIN (-LONG_MAX - 1) -#define SCHAR_MAX __SCHAR_MAX__ -#define SCHAR_MIN (-SCHAR_MAX - 1) -#define SHRT_MAX __SHRT_MAX__ -#define SHRT_MIN (-SHRT_MAX - 1) - -// TODO: These might not be accurate on all platforms -#define SSIZE_MAX 0x7fffffffffffffff -#define UCHAR_MAX 255 -#define UINT_MAX 0xffffffff -#define ULLONG_MAX 0xffffffffffffffff -#define ULONG_MAX 0xffffffffffffffff -#define USHRT_MAX 0xffffffffffffffff -#define WORD_BIT 32 - -#endif diff --git a/include/bits/locale.h b/include/bits/locale.h deleted file mode 100644 index ceb24b1ed2..0000000000 --- a/include/bits/locale.h +++ /dev/null @@ -1,7 +0,0 @@ -#define LC_ALL 0 -#define LC_COLLATE 1 -#define LC_CTYPE 2 -#define LC_MESSAGES 3 -#define LC_MONETARY 4 -#define LC_NUMERIC 5 -#define LC_TIME 6 diff --git a/include/bits/netdb.h b/include/bits/netdb.h deleted file mode 100644 index 698e0e216a..0000000000 --- a/include/bits/netdb.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _BITS_NETDB_H -#define _BITS_NETDB_H - -# define h_addr h_addr_list[0] /* Address, for backward compatibility.*/ - -#endif /* _BITS_NETDB_H */ diff --git a/include/bits/netinet/in.h b/include/bits/netinet/in.h deleted file mode 100644 index c446cc3f85..0000000000 --- a/include/bits/netinet/in.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef _BITS_NETINET_IN_H -#define _BITS_NETINET_IN_H - -extern const struct in6_addr in6addr_any; -extern const struct in6_addr 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 } } } - -// from musl { -#define IN6_IS_ADDR_UNSPECIFIED(a) \ - (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ - ((uint32_t *) (a))[2] == 0 && ((uint32_t *) (a))[3] == 0) - -#define IN6_IS_ADDR_LOOPBACK(a) \ - (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ - ((uint32_t *) (a))[2] == 0 && \ - ((uint8_t *) (a))[12] == 0 && ((uint8_t *) (a))[13] == 0 && \ - ((uint8_t *) (a))[14] == 0 && ((uint8_t *) (a))[15] == 1 ) - -#define IN6_IS_ADDR_MULTICAST(a) (((uint8_t *) (a))[0] == 0xff) - -#define IN6_IS_ADDR_LINKLOCAL(a) \ - ((((uint8_t *) (a))[0]) == 0xfe && (((uint8_t *) (a))[1] & 0xc0) == 0x80) - -#define IN6_IS_ADDR_SITELOCAL(a) \ - ((((uint8_t *) (a))[0]) == 0xfe && (((uint8_t *) (a))[1] & 0xc0) == 0xc0) - -#define IN6_IS_ADDR_V4MAPPED(a) \ - (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ - ((uint8_t *) (a))[8] == 0 && ((uint8_t *) (a))[9] == 0 && \ - ((uint8_t *) (a))[10] == 0xff && ((uint8_t *) (a))[11] == 0xff) - -#define IN6_IS_ADDR_V4COMPAT(a) \ - (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ - ((uint32_t *) (a))[2] == 0 && ((uint8_t *) (a))[15] > 1) - -#define IN6_IS_ADDR_MC_NODELOCAL(a) \ - (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x1)) - -#define IN6_IS_ADDR_MC_LINKLOCAL(a) \ - (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x2)) - -#define IN6_IS_ADDR_MC_SITELOCAL(a) \ - (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x5)) - -#define IN6_IS_ADDR_MC_ORGLOCAL(a) \ - (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x8)) - -#define IN6_IS_ADDR_MC_GLOBAL(a) \ - (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0xe)) - -#define __ARE_4_EQUAL(a,b) \ - (!( (0[a]-0[b]) | (1[a]-1[b]) | (2[a]-2[b]) | (3[a]-3[b]) )) -#define IN6_ARE_ADDR_EQUAL(a,b) \ - __ARE_4_EQUAL((const uint32_t *)(a), (const uint32_t *)(b)) - -#define IN_CLASSA(a) ((((in_addr_t)(a)) & 0x80000000) == 0) -#define IN_CLASSA_NET 0xff000000 -#define IN_CLASSA_NSHIFT 24 -#define IN_CLASSA_HOST (0xffffffff & ~IN_CLASSA_NET) -#define IN_CLASSA_MAX 128 -#define IN_CLASSB(a) ((((in_addr_t)(a)) & 0xc0000000) == 0x80000000) -#define IN_CLASSB_NET 0xffff0000 -#define IN_CLASSB_NSHIFT 16 -#define IN_CLASSB_HOST (0xffffffff & ~IN_CLASSB_NET) -#define IN_CLASSB_MAX 65536 -#define IN_CLASSC(a) ((((in_addr_t)(a)) & 0xe0000000) == 0xc0000000) -#define IN_CLASSC_NET 0xffffff00 -#define IN_CLASSC_NSHIFT 8 -#define IN_CLASSC_HOST (0xffffffff & ~IN_CLASSC_NET) -#define IN_CLASSD(a) ((((in_addr_t)(a)) & 0xf0000000) == 0xe0000000) -#define IN_MULTICAST(a) IN_CLASSD(a) -#define IN_EXPERIMENTAL(a) ((((in_addr_t)(a)) & 0xe0000000) == 0xe0000000) -#define IN_BADCLASS(a) ((((in_addr_t)(a)) & 0xf0000000) == 0xf0000000) -// } from musl - -#endif // _BITS_NETINET_IN_H diff --git a/include/bits/signal.h b/include/bits/signal.h deleted file mode 100644 index 46cc2553ed..0000000000 --- a/include/bits/signal.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef _BITS_SIGNAL_H -#define _BITS_SIGNAL_H - -#define SIG_DFL ((void (*)(int))0) -#define SIG_IGN ((void (*)(int))1) -#define SIG_ERR ((void (*)(int))-1) - -#endif // _BITS_SIGNAL_H diff --git a/include/bits/stdio.h b/include/bits/stdio.h deleted file mode 100644 index 1eecbc0751..0000000000 --- a/include/bits/stdio.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _BITS_STDIO_H -#define _BITS_STDIO_H - -// XXX: this is only here because cbindgen can't handle string constants -#define P_tmpdir "/tmp" - -typedef struct FILE FILE; - -// A typedef doesn't suffice, because libgmp uses this definition to check if -// STDIO was loaded. -#define FILE FILE - -#ifdef __cplusplus -extern "C" { -#endif - -int asprintf(char **strp, const char * fmt, ...); -int fprintf(FILE * stream, const char * fmt, ...); -int printf(const char * fmt, ...); -int snprintf(char *s, size_t n, const char * fmt, ...); -int sprintf(char *s, const char * fmt, ...); -int fscanf(FILE * stream, const char * fmt, ...); -int scanf(const char * fmt, ...); -int sscanf(const char * input, const char * fmt, ...); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* _BITS_STDIO_H */ diff --git a/include/bits/stdlib.h b/include/bits/stdlib.h deleted file mode 100644 index 8d639cef38..0000000000 --- a/include/bits/stdlib.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _BITS_STDLIB_H -#define _BITS_STDLIB_H - -#ifdef __cplusplus -extern "C" { -#endif - -long double strtold(const char *nptr, char **endptr); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* _BITS_STDLIB_H */ diff --git a/include/bits/sys/ioctl.h b/include/bits/sys/ioctl.h deleted file mode 100644 index 2a2aab608a..0000000000 --- a/include/bits/sys/ioctl.h +++ /dev/null @@ -1,11 +0,0 @@ -// Shamelessly copy-pasted from musl - -#define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) ) -#define _IOC_NONE 0U -#define _IOC_WRITE 1U -#define _IOC_READ 2U - -#define _IO(a,b) _IOC(_IOC_NONE,(a),(b),0) -#define _IOW(a,b,c) _IOC(_IOC_WRITE,(a),(b),sizeof(c)) -#define _IOR(a,b,c) _IOC(_IOC_READ,(a),(b),sizeof(c)) -#define _IOWR(a,b,c) _IOC(_IOC_READ|_IOC_WRITE,(a),(b),sizeof(c)) diff --git a/include/bits/sys/mman.h b/include/bits/sys/mman.h deleted file mode 100644 index e2ece78cc5..0000000000 --- a/include/bits/sys/mman.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _BITS_SYS_MMAN_H -#define _BITS_SYS_MMAN_H - -#define MAP_FAILED ((void *) -1) - -#endif diff --git a/include/bits/sys/ptrace.h b/include/bits/sys/ptrace.h deleted file mode 100644 index 568341c68f..0000000000 --- a/include/bits/sys/ptrace.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _BITS_SYS_PTRACE_H -#define _BITS_SYS_PTRACE_H - -int ptrace(int request, ...); - -#endif diff --git a/include/bits/sys/resource.h b/include/bits/sys/resource.h deleted file mode 100644 index 3786ea331b..0000000000 --- a/include/bits/sys/resource.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _BITS_SYS_RESOURCE_H -#define _BITS_SYS_RESOURCE_H - -#define RUSAGE_SELF 0 -#define RUSAGE_CHILDREN (-1) -#define RUSAGE_BOTH (-2) -#define RUSAGE_THREAD 1 - -#endif /* _BITS_SYS_RESOURCE_H */ diff --git a/include/bits/sys/select.h b/include/bits/sys/select.h deleted file mode 100644 index 02fa52a21f..0000000000 --- a/include/bits/sys/select.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef _BITS_SYS_SELECT_H -#define _BITS_SYS_SELECT_H - -#define FD_SETSIZE 1024 - -typedef struct fd_set { - unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))]; -} fd_set; - -#define _FD_INDEX(fd) ((fd) / (8 * sizeof(unsigned long))) -#define _FD_BITMASK(fd) (1UL << ((fd) & (8 * sizeof(unsigned long) - 1))) - -#define FD_ZERO(set) for (int i = 0; i < sizeof((set)->fds_bits) / sizeof(unsigned long); i += 1) { \ - (set)->fds_bits[i] = 0; \ - } - -#define FD_SET(fd, set) ((set)->fds_bits[_FD_INDEX(fd)] |= _FD_BITMASK(fd)) -#define FD_CLR(fd, set) ((set)->fds_bits[_FD_INDEX(fd)] &= ~(_FD_BITMASK(fd))) - -#define FD_ISSET(fd, set) (((set)->fds_bits[_FD_INDEX(fd)] & _FD_BITMASK(fd)) == _FD_BITMASK(fd)) - -#endif diff --git a/include/bits/sys/socket.h b/include/bits/sys/socket.h deleted file mode 100644 index 454ce03abc..0000000000 --- a/include/bits/sys/socket.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _BITS_SYS_SOCKET_H -#define _BITS_SYS_SOCKET_H - -struct sockaddr_storage { - sa_family_t ss_family; - char __ss_padding[128-sizeof(long)-sizeof(sa_family_t)]; - unsigned long __ss_align; -}; - -#endif // _BITS_SYS_SOCKET_H diff --git a/include/bits/sys/stat.h b/include/bits/sys/stat.h deleted file mode 100644 index 1a27d243e7..0000000000 --- a/include/bits/sys/stat.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _BITS_STAT_H -#define _BITS_STAT_H - -#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) -#define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) -#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) -#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) -#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) -#define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) - -#define st_atime st_atim.tv_sec -#define st_mtime st_mtim.tv_sec -#define st_ctime st_ctim.tv_sec - -#endif diff --git a/include/bits/sys/time.h b/include/bits/sys/time.h deleted file mode 100644 index 73b0683f5c..0000000000 --- a/include/bits/sys/time.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _BITS_SYS_TIME -#define _BITS_SYS_TIME - -#define timeradd(x,y,res) (void) (\ - (res)->tv_sec = (x)->tv_sec + (y)->tv_sec + (((x)->tv_usec + (y)->tv_usec) / 1000000), \ - (res)->tv_usec = ((x)->tv_usec + (y)->tv_usec) % 1000000 \ - ) -#define timersub(x,y,res) (void) ( \ - (res)->tv_sec = (x)->tv_sec - (y)->tv_sec, \ - (res)->tv_usec = ((x)->tv_usec - (y)->tv_usec), \ - ((res)->tv_usec < 0) && ((res)->tv_sec -= 1, (res)->tv_usec += 1000000) \ - ) -#define timerclear(t) (void) ( \ - (t)->tv_sec = 0, \ - (t)->tv_usec = 0 \ - ) -#define timerisset(t) ((t)->tv_sec || (t)->tv_usec) -#define timercmp(x,y,op) ((x)->tv_sec == (y)->tv_sec ? \ - (x)->tv_usec op (y)->tv_usec \ - : \ - (x)->tv_sec op (y)->tv_sec) - -#endif diff --git a/include/bits/sys/wait.h b/include/bits/sys/wait.h deleted file mode 100644 index 7af6a82bbe..0000000000 --- a/include/bits/sys/wait.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef _BITS_SYS_WAIT_H -#define _BITS_SYS_WAIT_H - -#define WEXITSTATUS(s) (((s) >> 8) & 0xff) -#define WTERMSIG(s) (((s) & 0x7f) != 0) -#define WSTOPSIG(s) WEXITSTATUS(s) -#define WCOREDUMP(s) (((s) & 0x80) != 0) -#define WIFEXITED(s) (((s) & 0x7f) == 0) -#define WIFSTOPPED(s) (((s) & 0xff) == 0x7f) -#define WIFSIGNALED(s) (((((s) & 0x7f) + 1U) & 0x7f) >= 2) // Ends with 1111111 or 10000000 -#define WIFCONTINUED(s) ((s) == 0xffff) - -#endif /* _BITS_SYS_WAIT_H */ diff --git a/include/bits/unistd.h b/include/bits/unistd.h deleted file mode 100644 index 17cf6fa25f..0000000000 --- a/include/bits/unistd.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _BITS_UNISTD_H -#define _BITS_UNISTD_H - -#define _POSIX_VERSION 200809L - -#ifdef __cplusplus -extern "C" { -#endif - -int execl(const char *path, const char* argv0, ...); -int execle(const char *path, const char* argv0, ...); -int execlp(const char *file, const char* argv0, ...); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/include/bits/wchar.h b/include/bits/wchar.h deleted file mode 100644 index 1619cb0fab..0000000000 --- a/include/bits/wchar.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _BITS_WCHAR_H -#define _BITS_WCHAR_H -#include - -#define WEOF (0xffffffffu) -#define WCHAR_MIN (0) -#define WCHAR_MAX (0x7fffffff) - -#define __need_size_t -#define __need_wchar_t -#define __need_wint_t -#define __need_NULL - -#endif /* _BITS_WCHAR_H */ diff --git a/include/bits/wctype.h b/include/bits/wctype.h deleted file mode 100644 index fc22135332..0000000000 --- a/include/bits/wctype.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef _BITS_WCTYPE_H -#define _BITS_WCTYPE_H -#include - -#define __need_wint_t - -#endif /* _BITS_WCTYPE_H */ diff --git a/include/features.h b/include/features.h index 7fbdd2dcdf..6d0a0d6225 100644 --- a/include/features.h +++ b/include/features.h @@ -3,26 +3,93 @@ * Copyright (c) 2020 Rich Felker musl-libc */ -#ifndef _FEATURES_H -#define _FEATURES_H +#ifndef _FEATURES_H__RELIBC +#define _FEATURES_H__RELIBC -#if __STDC_VERSION__ >= 199901L -#define __restrict restrict -#elif !defined(__GNUC__) -#define __restrict +// Version metadata for feature gating +// This is useful for divergent implementation specific behavior +// glibc, ulibc, and likely others define a similar macro +// musl does not define an equivalent macro +#define __RELIBC__ 1 +#define __RELIBC__MAJOR 0 +#define __RELIBC__MINOR 2 + +/* + * Sources: + * https://en.cppreference.com/w/c/language/attributes + * https://clang.llvm.org/docs/LanguageExtensions.html + * https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005fc_005fattribute.html + * https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html + */ + +// Clang doesn't define __has_cpp_attribute if compiling C code +#if !defined(__has_cpp_attribute) + #define __has_cpp_attribute(x) 0 #endif -#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) -#define __inline inline -#elif !defined(__GNUC__) -#define __inline +// Clang doesn't define __has_c_attribute if compiling C++ code +#if !defined(__has_c_attribute) + #define __has_c_attribute(x) 0 #endif -#if __STDC_VERSION__ >= 201112L -#elif defined(__GNUC__) -#define _Noreturn __attribute__((__noreturn__)) +// Check if C23+ attributes are available +#if defined(__cplusplus) +// HACK: GCC backports C++ attributes to C++98 but doesn't accept attributes +// placed before the function like cbindgen emits. +// Let's just disable attributes for C++98 by checking if a random C++11 +// feature is available. +#define __HAS_ATTRIBUTE(x) __cpp_variable_templates &&__has_cpp_attribute(x) #else -#define _Noreturn +#define __HAS_ATTRIBUTE(x) \ + (__has_c_attribute(x) || __STDC_VERSION__ >= 202311L || \ + __has_cpp_attribute(x)) +#endif + +// TODO: Not emitted with cbindgen +#if __STDC_VERSION__ >= 199901L + #define __restrict restrict +#elif !defined(__GNUC__) + #define __restrict +#endif + +// TODO: Not emitted with cbindgen +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) + #define __inline inline +#elif !defined(__GNUC__) + #define __inline +#endif + +// Analogous to Rust's Never type +//TODO: clang fails to compile C with [[noreturn]] +#if defined(__cplusplus) && __HAS_ATTRIBUTE(noreturn) && !(__clang__) + #define __noreturn [[noreturn]] +// #elif __STDC_VERSION__ >= 201112L +// FIXME: cbindgen incorrectly places _Noreturn +// #define __noreturn _Noreturn +#elif defined(__GNUC__) + #define __noreturn __attribute__((__noreturn__)) +#else + #define __noreturn +#endif + +// Analogous to Rust's #[must_use] +// C23 only +#if __HAS_ATTRIBUTE(nodiscard) + #define __nodiscard [[nodiscard]] + #define __nodiscardNote(x) [[nodiscard(x)]] +#else + #define __nodiscard + #define __nodiscardNote(x) +#endif + +// Analogous to Rust's #[deprecated] +// C23 only +#if __HAS_ATTRIBUTE(deprecated) + #define __deprecated [[deprecated]] + #define __deprecatedNote(x) [[deprecated(x)]] +#else + #define __deprecated + #define __deprecatedNote(x) #endif #endif diff --git a/include/getopt.h b/include/getopt.h deleted file mode 100644 index 3db90d7080..0000000000 --- a/include/getopt.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _GETOPT_H -#define _GETOPT_H - -// Generated from: -// `grep "opt" target/include/unistd.h` - -#ifdef __cplusplus -extern "C" { -#endif - -extern char* optarg; -extern int opterr; -extern int optind; -extern int optopt; -int getopt(int argc, char *const *argv, const char *optstring); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/include/iso646.h b/include/iso646.h new file mode 100644 index 0000000000..d94c240909 --- /dev/null +++ b/include/iso646.h @@ -0,0 +1,22 @@ +// Copied from musl + +#ifndef _ISO646_H +#define _ISO646_H + +#ifndef __cplusplus + +#define and && +#define and_eq &= +#define bitand & +#define bitor | +#define compl ~ +#define not ! +#define not_eq != +#define or || +#define or_eq |= +#define xor ^ +#define xor_eq ^= + +#endif + +#endif diff --git a/include/math.h b/include/math.h index 8063e8877f..eba89dc734 100644 --- a/include/math.h +++ b/include/math.h @@ -1,6 +1,11 @@ #include -// Included to fix mesa issues +// Missing typedefs +typedef float float_t; +typedef double double_t; + +/* double */ + #ifndef M_PI #define M_PI 3.14159265358979323846 /* pi */ #endif @@ -12,3 +17,97 @@ #ifndef M_PI_4 #define M_PI_4 0.78539816339744830962 /* pi/4 */ #endif + +#ifndef M_2_PI +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#endif + +#ifndef M_E +#define M_E 2.7182818284590452354 /* e */ +#endif + +#ifndef M_LOG2E +#define M_LOG2E 1.4426950408889634074 /* log_2 e */ +#endif + +#ifndef M_LOG10E +#define M_LOG10E 0.43429448190325182765 /* log_10 e */ +#endif + +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif + +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif + +#ifndef M_1_PI +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#endif + +#ifndef M_2_SQRTPI +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif + +/* long double */ + +#ifndef M_El +#define M_El 2.718281828459045235360287471352662498L /* e */ +#endif + +#ifndef M_LOG2El +#define M_LOG2El 1.442695040888963407359924681001892137L /* log_2 e */ +#endif + +#ifndef M_LOG10El +#define M_LOG10El 0.434294481903251827651128918916605082L /* log_10 e */ +#endif + +#ifndef M_LN2l +#define M_LN2l 0.693147180559945309417232121458176568L /* log_e 2 */ +#endif + +#ifndef M_LN10l +#define M_LN10l 2.302585092994045684017991454684364208L /* log_e 10 */ +#endif + +#ifndef M_PIl +#define M_PIl 3.141592653589793238462643383279502884L /* pi */ +#endif + +#ifndef M_PI_2l +#define M_PI_2l 1.570796326794896619231321691639751442L /* pi/2 */ +#endif + +#ifndef M_PI_4l +#define M_PI_4l 0.785398163397448309615660845819875721L /* pi/4 */ +#endif + +#ifndef M_1_PIl +#define M_1_PIl 0.318309886183790671537767526745028724L /* 1/pi */ +#endif + +#ifndef M_2_PIl +#define M_2_PIl 0.636619772367581343075535053490057448L /* 2/pi */ +#endif + +#ifndef M_2_SQRTPIl +#define M_2_SQRTPIl 1.128379167095512573896158903121545172L /* 2/sqrt(pi) */ +#endif + +#ifndef M_SQRT2l +#define M_SQRT2l 1.414213562373095048801688724209698079L /* sqrt(2) */ +#endif + +#ifndef M_SQRT1_2l +#define M_SQRT1_2l 0.707106781186547524400844362104849039L /* 1/sqrt(2) */ +#endif \ No newline at end of file diff --git a/include/paths.h b/include/paths.h new file mode 100644 index 0000000000..8bbfa920c6 --- /dev/null +++ b/include/paths.h @@ -0,0 +1,6 @@ +#ifndef _RELIBC_PATHS_H +#define _RELIBC_PATHS_H + +#define _PATH_BSHELL "/bin/sh" + +#endif diff --git a/include/setjmp.h b/include/setjmp.h index 6816380600..a8bb37622d 100644 --- a/include/setjmp.h +++ b/include/setjmp.h @@ -58,9 +58,15 @@ typedef unsigned long long jmp_buf[8]; #endif #ifdef __x86_64__ -typedef unsigned long jmp_buf[8]; +typedef unsigned long jmp_buf[16]; #endif +#ifdef __riscv +typedef unsigned long jmp_buf[26]; +#endif + +typedef jmp_buf sigjmp_buf; + #ifdef __cplusplus extern "C" { #endif diff --git a/include/stdatomic.h b/include/stdatomic.h new file mode 100644 index 0000000000..bfcfdf664c --- /dev/null +++ b/include/stdatomic.h @@ -0,0 +1,243 @@ +/* Copyright (C) 2013-2022 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +. */ + +/* ISO C11 Standard: 7.17 Atomics . */ + +#ifndef _STDATOMIC_H +#define _STDATOMIC_H + +typedef enum + { + memory_order_relaxed = __ATOMIC_RELAXED, + memory_order_consume = __ATOMIC_CONSUME, + memory_order_acquire = __ATOMIC_ACQUIRE, + memory_order_release = __ATOMIC_RELEASE, + memory_order_acq_rel = __ATOMIC_ACQ_REL, + memory_order_seq_cst = __ATOMIC_SEQ_CST + } memory_order; + + +typedef _Atomic _Bool atomic_bool; +typedef _Atomic char atomic_char; +typedef _Atomic signed char atomic_schar; +typedef _Atomic unsigned char atomic_uchar; +typedef _Atomic short atomic_short; +typedef _Atomic unsigned short atomic_ushort; +typedef _Atomic int atomic_int; +typedef _Atomic unsigned int atomic_uint; +typedef _Atomic long atomic_long; +typedef _Atomic unsigned long atomic_ulong; +typedef _Atomic long long atomic_llong; +typedef _Atomic unsigned long long atomic_ullong; +typedef _Atomic __CHAR16_TYPE__ atomic_char16_t; +typedef _Atomic __CHAR32_TYPE__ atomic_char32_t; +typedef _Atomic __WCHAR_TYPE__ atomic_wchar_t; +typedef _Atomic __INT_LEAST8_TYPE__ atomic_int_least8_t; +typedef _Atomic __UINT_LEAST8_TYPE__ atomic_uint_least8_t; +typedef _Atomic __INT_LEAST16_TYPE__ atomic_int_least16_t; +typedef _Atomic __UINT_LEAST16_TYPE__ atomic_uint_least16_t; +typedef _Atomic __INT_LEAST32_TYPE__ atomic_int_least32_t; +typedef _Atomic __UINT_LEAST32_TYPE__ atomic_uint_least32_t; +typedef _Atomic __INT_LEAST64_TYPE__ atomic_int_least64_t; +typedef _Atomic __UINT_LEAST64_TYPE__ atomic_uint_least64_t; +typedef _Atomic __INT_FAST8_TYPE__ atomic_int_fast8_t; +typedef _Atomic __UINT_FAST8_TYPE__ atomic_uint_fast8_t; +typedef _Atomic __INT_FAST16_TYPE__ atomic_int_fast16_t; +typedef _Atomic __UINT_FAST16_TYPE__ atomic_uint_fast16_t; +typedef _Atomic __INT_FAST32_TYPE__ atomic_int_fast32_t; +typedef _Atomic __UINT_FAST32_TYPE__ atomic_uint_fast32_t; +typedef _Atomic __INT_FAST64_TYPE__ atomic_int_fast64_t; +typedef _Atomic __UINT_FAST64_TYPE__ atomic_uint_fast64_t; +typedef _Atomic __INTPTR_TYPE__ atomic_intptr_t; +typedef _Atomic __UINTPTR_TYPE__ atomic_uintptr_t; +typedef _Atomic __SIZE_TYPE__ atomic_size_t; +typedef _Atomic __PTRDIFF_TYPE__ atomic_ptrdiff_t; +typedef _Atomic __INTMAX_TYPE__ atomic_intmax_t; +typedef _Atomic __UINTMAX_TYPE__ atomic_uintmax_t; + + +#define ATOMIC_VAR_INIT(VALUE) (VALUE) + +/* Initialize an atomic object pointed to by PTR with VAL. */ +#define atomic_init(PTR, VAL) \ + atomic_store_explicit (PTR, VAL, __ATOMIC_RELAXED) + +#define kill_dependency(Y) \ + __extension__ \ + ({ \ + __auto_type __kill_dependency_tmp = (Y); \ + __kill_dependency_tmp; \ + }) + +extern void atomic_thread_fence (memory_order); +#define atomic_thread_fence(MO) __atomic_thread_fence (MO) +extern void atomic_signal_fence (memory_order); +#define atomic_signal_fence(MO) __atomic_signal_fence (MO) +#define atomic_is_lock_free(OBJ) __atomic_is_lock_free (sizeof (*(OBJ)), (OBJ)) + +#define ATOMIC_BOOL_LOCK_FREE __GCC_ATOMIC_BOOL_LOCK_FREE +#define ATOMIC_CHAR_LOCK_FREE __GCC_ATOMIC_CHAR_LOCK_FREE +#define ATOMIC_CHAR16_T_LOCK_FREE __GCC_ATOMIC_CHAR16_T_LOCK_FREE +#define ATOMIC_CHAR32_T_LOCK_FREE __GCC_ATOMIC_CHAR32_T_LOCK_FREE +#define ATOMIC_WCHAR_T_LOCK_FREE __GCC_ATOMIC_WCHAR_T_LOCK_FREE +#define ATOMIC_SHORT_LOCK_FREE __GCC_ATOMIC_SHORT_LOCK_FREE +#define ATOMIC_INT_LOCK_FREE __GCC_ATOMIC_INT_LOCK_FREE +#define ATOMIC_LONG_LOCK_FREE __GCC_ATOMIC_LONG_LOCK_FREE +#define ATOMIC_LLONG_LOCK_FREE __GCC_ATOMIC_LLONG_LOCK_FREE +#define ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE + + +/* Note that these macros require __auto_type to remove + _Atomic qualifiers (and const qualifiers, if those are valid on + macro operands). + + Also note that the header file uses the generic form of __atomic + builtins, which requires the address to be taken of the value + parameter, and then we pass that value on. This allows the macros + to work for any type, and the compiler is smart enough to convert + these to lock-free _N variants if possible, and throw away the + temps. */ + +#define atomic_store_explicit(PTR, VAL, MO) \ + __extension__ \ + ({ \ + __auto_type __atomic_store_ptr = (PTR); \ + __typeof__ ((void)0, *__atomic_store_ptr) __atomic_store_tmp = (VAL); \ + __atomic_store (__atomic_store_ptr, &__atomic_store_tmp, (MO)); \ + }) + +#define atomic_store(PTR, VAL) \ + atomic_store_explicit (PTR, VAL, __ATOMIC_SEQ_CST) + + +#define atomic_load_explicit(PTR, MO) \ + __extension__ \ + ({ \ + __auto_type __atomic_load_ptr = (PTR); \ + __typeof__ ((void)0, *__atomic_load_ptr) __atomic_load_tmp; \ + __atomic_load (__atomic_load_ptr, &__atomic_load_tmp, (MO)); \ + __atomic_load_tmp; \ + }) + +#define atomic_load(PTR) atomic_load_explicit (PTR, __ATOMIC_SEQ_CST) + + +#define atomic_exchange_explicit(PTR, VAL, MO) \ + __extension__ \ + ({ \ + __auto_type __atomic_exchange_ptr = (PTR); \ + __typeof__ ((void)0, *__atomic_exchange_ptr) __atomic_exchange_val = (VAL); \ + __typeof__ ((void)0, *__atomic_exchange_ptr) __atomic_exchange_tmp; \ + __atomic_exchange (__atomic_exchange_ptr, &__atomic_exchange_val, \ + &__atomic_exchange_tmp, (MO)); \ + __atomic_exchange_tmp; \ + }) + +#define atomic_exchange(PTR, VAL) \ + atomic_exchange_explicit (PTR, VAL, __ATOMIC_SEQ_CST) + + +#define atomic_compare_exchange_strong_explicit(PTR, VAL, DES, SUC, FAIL) \ + __extension__ \ + ({ \ + __auto_type __atomic_compare_exchange_ptr = (PTR); \ + __typeof__ ((void)0, *__atomic_compare_exchange_ptr) __atomic_compare_exchange_tmp \ + = (DES); \ + __atomic_compare_exchange (__atomic_compare_exchange_ptr, (VAL), \ + &__atomic_compare_exchange_tmp, 0, \ + (SUC), (FAIL)); \ + }) + +#define atomic_compare_exchange_strong(PTR, VAL, DES) \ + atomic_compare_exchange_strong_explicit (PTR, VAL, DES, __ATOMIC_SEQ_CST, \ + __ATOMIC_SEQ_CST) + +#define atomic_compare_exchange_weak_explicit(PTR, VAL, DES, SUC, FAIL) \ + __extension__ \ + ({ \ + __auto_type __atomic_compare_exchange_ptr = (PTR); \ + __typeof__ ((void)0, *__atomic_compare_exchange_ptr) __atomic_compare_exchange_tmp \ + = (DES); \ + __atomic_compare_exchange (__atomic_compare_exchange_ptr, (VAL), \ + &__atomic_compare_exchange_tmp, 1, \ + (SUC), (FAIL)); \ + }) + +#define atomic_compare_exchange_weak(PTR, VAL, DES) \ + atomic_compare_exchange_weak_explicit (PTR, VAL, DES, __ATOMIC_SEQ_CST, \ + __ATOMIC_SEQ_CST) + + + +#define atomic_fetch_add(PTR, VAL) __atomic_fetch_add ((PTR), (VAL), \ + __ATOMIC_SEQ_CST) +#define atomic_fetch_add_explicit(PTR, VAL, MO) \ + __atomic_fetch_add ((PTR), (VAL), (MO)) + +#define atomic_fetch_sub(PTR, VAL) __atomic_fetch_sub ((PTR), (VAL), \ + __ATOMIC_SEQ_CST) +#define atomic_fetch_sub_explicit(PTR, VAL, MO) \ + __atomic_fetch_sub ((PTR), (VAL), (MO)) + +#define atomic_fetch_or(PTR, VAL) __atomic_fetch_or ((PTR), (VAL), \ + __ATOMIC_SEQ_CST) +#define atomic_fetch_or_explicit(PTR, VAL, MO) \ + __atomic_fetch_or ((PTR), (VAL), (MO)) + +#define atomic_fetch_xor(PTR, VAL) __atomic_fetch_xor ((PTR), (VAL), \ + __ATOMIC_SEQ_CST) +#define atomic_fetch_xor_explicit(PTR, VAL, MO) \ + __atomic_fetch_xor ((PTR), (VAL), (MO)) + +#define atomic_fetch_and(PTR, VAL) __atomic_fetch_and ((PTR), (VAL), \ + __ATOMIC_SEQ_CST) +#define atomic_fetch_and_explicit(PTR, VAL, MO) \ + __atomic_fetch_and ((PTR), (VAL), (MO)) + + +typedef _Atomic struct +{ +#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1 + _Bool __val; +#else + unsigned char __val; +#endif +} atomic_flag; + +#define ATOMIC_FLAG_INIT { 0 } + + +extern _Bool atomic_flag_test_and_set (volatile atomic_flag *); +#define atomic_flag_test_and_set(PTR) \ + __atomic_test_and_set ((PTR), __ATOMIC_SEQ_CST) +extern _Bool atomic_flag_test_and_set_explicit (volatile atomic_flag *, + memory_order); +#define atomic_flag_test_and_set_explicit(PTR, MO) \ + __atomic_test_and_set ((PTR), (MO)) + +extern void atomic_flag_clear (volatile atomic_flag *); +#define atomic_flag_clear(PTR) __atomic_clear ((PTR), __ATOMIC_SEQ_CST) +extern void atomic_flag_clear_explicit (volatile atomic_flag *, memory_order); +#define atomic_flag_clear_explicit(PTR, MO) __atomic_clear ((PTR), (MO)) + +#endif /* _STDATOMIC_H */ diff --git a/include/stdbool.h b/include/stdbool.h index 59cce7a961..66987b5737 100644 --- a/include/stdbool.h +++ b/include/stdbool.h @@ -2,12 +2,12 @@ #define _STDBOOL_H #ifndef __cplusplus -typedef _Bool bool; +#define bool _Bool #define true 1 #define false 0 #else /* __cplusplus */ -typedef bool _Bool; #if __cplusplus < 201103L +#define bool bool #define false false #define true true #endif /*__cplusplus < 201103L*/ diff --git a/include/stddef.h b/include/stddef.h deleted file mode 100644 index 1ee56ff6f3..0000000000 --- a/include/stddef.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef _STDDEF_H -#define _STDDEF_H -#include - -#define NULL 0 - -#ifndef __PTRDIFF_TYPE__ -#define __PTRDIFF_TYPE__ long int -#endif -typedef __PTRDIFF_TYPE__ ptrdiff_t; - -#ifndef __cplusplus -typedef int32_t wchar_t; -#endif /* #ifndef __cplusplus */ -typedef int32_t wint_t; - - -typedef long unsigned int size_t; - -typedef struct { long long __ll; long double __ld; } max_align_t; - -#define offsetof(type, member) __builtin_offsetof(type, member) - -#endif /* _STDDEF_H */ diff --git a/include/stdint.h b/include/stdint.h index 3a578783fa..963b8e9b5d 100644 --- a/include/stdint.h +++ b/include/stdint.h @@ -44,15 +44,23 @@ typedef __INT64_TYPE__ int64_t; #endif #ifdef __UINT8_TYPE__ typedef __UINT8_TYPE__ uint8_t; +typedef __UINT8_TYPE__ u_int8_t; #endif #ifdef __UINT16_TYPE__ typedef __UINT16_TYPE__ uint16_t; +typedef __UINT16_TYPE__ u_int16_t; #endif #ifdef __UINT32_TYPE__ typedef __UINT32_TYPE__ uint32_t; +typedef __UINT32_TYPE__ u_int32_t; +// Required by openlibm +typedef __UINT32_TYPE__ __uint32_t; #endif #ifdef __UINT64_TYPE__ typedef __UINT64_TYPE__ uint64_t; +typedef __UINT64_TYPE__ u_int64_t; +// Required by openlibm +typedef __UINT64_TYPE__ __uint64_t; #endif /* 7.8.1.2 Minimum-width integer types */ @@ -359,9 +367,11 @@ typedef __UINTMAX_TYPE__ uintmax_t; #undef WINT_WIDTH #define WINT_WIDTH __WINT_WIDTH__ +#ifdef __ILP32__ +#define SIZE_MAX UINT32_MAX +#else #define SIZE_MAX UINT64_MAX #endif - -typedef long sig_atomic_t; +#endif #endif /* _GCC_STDINT_H */ diff --git a/include/stdio_ext.h b/include/stdio_ext.h index aa95d16fcf..563e4ccce3 100644 --- a/include/stdio_ext.h +++ b/include/stdio_ext.h @@ -3,4 +3,20 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + +size_t __freadahead(FILE *stream); +size_t __fpending(FILE *stream); +int __freadable(FILE *stream); +int __freading(FILE *stream); +void __fseterr(FILE *stream); +int __fwritable(FILE *stream); +int __fwriting(FILE *stream); + +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* _STDIO_EXT_H */ diff --git a/include/stdnoreturn.h b/include/stdnoreturn.h new file mode 100644 index 0000000000..7df19e742f --- /dev/null +++ b/include/stdnoreturn.h @@ -0,0 +1,19 @@ +/* Spec: + * The header shall define the macro noreturn which shall + * expand to _Noreturn */ + +#ifndef _STDNORETURN_H +#define _STDNORETURN_H + +#ifndef __cplusplus +/* Borrowed from musl */ +#if __STDC_VERSION__ >= 201112L +#elif defined(__GNUC__) + #define _Noreturn __attribute__((__noreturn__)) +#else + #define _Noreturn +#endif +#define noreturn _Noreturn +#endif + +#endif diff --git a/include/sys/poll.h b/include/sys/poll.h new file mode 100644 index 0000000000..779ec774fe --- /dev/null +++ b/include/sys/poll.h @@ -0,0 +1 @@ +#include diff --git a/include/sys/queue.h b/include/sys/queue.h new file mode 100644 index 0000000000..a38499a267 --- /dev/null +++ b/include/sys/queue.h @@ -0,0 +1,846 @@ +/* $NetBSD: queue.h,v 1.70 2015/11/02 15:21:23 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * A singly-linked list is headed by a single forward pointer. The + * elements are singly linked for minimum space and pointer manipulation + * overhead at the expense of O(n) removal for arbitrary elements. New + * elements can be added to the list after an existing element or at the + * head of the list. Elements being removed from the head of the list + * should use the explicit macro for this purpose for optimum + * efficiency. A singly-linked list may only be traversed in the forward + * direction. Singly-linked lists are ideal for applications with large + * datasets and few or no removals or for implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * Include the definition of NULL only on NetBSD because sys/null.h + * is not available elsewhere. This conditional makes the header + * portable and it can simply be dropped verbatim into any system. + * The caveat is that on other systems some other header + * must provide NULL before the macros can be used. + */ +#ifdef __NetBSD__ +#include +#endif + +#if defined(QUEUEDEBUG) +# if defined(_KERNEL) +# define QUEUEDEBUG_ABORT(...) panic(__VA_ARGS__) +# else +# include +# define QUEUEDEBUG_ABORT(...) err(1, __VA_ARGS__) +# endif +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = (head)->slh_first; \ + (var) != SLIST_END(head); \ + (var) = (var)->field.sle_next) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) != SLIST_END(head) && \ + ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) do { \ + (head)->slh_first = SLIST_END(head); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_AFTER(slistelm, field) do { \ + (slistelm)->field.sle_next = \ + SLIST_NEXT(SLIST_NEXT((slistelm), field), field); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while(curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (/*CONSTCOND*/0) + + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods. + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) ((head)->lh_first == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = ((head)->lh_first); \ + (var) != LIST_END(head); \ + (var) = ((var)->field.le_next)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) != LIST_END(head) && \ + ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_MOVE(head1, head2) do { \ + LIST_INIT((head2)); \ + if (!LIST_EMPTY((head1))) { \ + (head2)->lh_first = (head1)->lh_first; \ + LIST_INIT((head1)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * List functions. + */ +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) \ + if ((head)->lh_first && \ + (head)->lh_first->field.le_prev != &(head)->lh_first) \ + QUEUEDEBUG_ABORT("LIST_INSERT_HEAD %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_LIST_OP(elm, field) \ + if ((elm)->field.le_next && \ + (elm)->field.le_next->field.le_prev != \ + &(elm)->field.le_next) \ + QUEUEDEBUG_ABORT("LIST_* forw %p %s:%d", (elm), \ + __FILE__, __LINE__); \ + if (*(elm)->field.le_prev != (elm)) \ + QUEUEDEBUG_ABORT("LIST_* back %p %s:%d", (elm), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) \ + (elm)->field.le_next = (void *)1L; \ + (elm)->field.le_prev = (void *)1L; +#else +#define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) +#define QUEUEDEBUG_LIST_OP(elm, field) +#define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) +#endif + +#define LIST_INIT(head) do { \ + (head)->lh_first = LIST_END(head); \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QUEUEDEBUG_LIST_OP((listelm), field) \ + if (((elm)->field.le_next = (listelm)->field.le_next) != \ + LIST_END(head)) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QUEUEDEBUG_LIST_OP((listelm), field) \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_LIST_INSERT_HEAD((head), (elm), field) \ + if (((elm)->field.le_next = (head)->lh_first) != LIST_END(head))\ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (/*CONSTCOND*/0) + +#define LIST_REMOVE(elm, field) do { \ + QUEUEDEBUG_LIST_OP((elm), field) \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->sqh_first); \ + (var) != SIMPLEQ_END(head); \ + (var) = ((var)->field.sqe_next)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = ((head)->sqh_first); \ + (var) != SIMPLEQ_END(head) && \ + ((next = ((var)->field.sqe_next)), 1); \ + (var) = (next)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ + if ((head)->sqh_first == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->sqh_first; \ + while (curelm->field.sqe_next != (elm)) \ + curelm = curelm->field.sqe_next; \ + if ((curelm->field.sqe_next = \ + curelm->field.sqe_next->field.sqe_next) == NULL) \ + (head)->sqh_last = &(curelm)->field.sqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_CONCAT(head1, head2) do { \ + if (!SIMPLEQ_EMPTY((head2))) { \ + *(head1)->sqh_last = (head2)->sqh_first; \ + (head1)->sqh_last = (head2)->sqh_last; \ + SIMPLEQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_LAST(head, type, field) \ + (SIMPLEQ_EMPTY((head)) ? \ + NULL : \ + ((struct type *)(void *) \ + ((char *)((head)->sqh_last) - offsetof(struct type, field)))) + +/* + * Tail queue definitions. + */ +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { TAILQ_END(head), &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +/* + * Tail queue access methods. + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) (NULL) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)(void *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)(void *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) + + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head) && \ + ((next) = TAILQ_NEXT(var, field), 1); (var) = (next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) != TAILQ_END(head) && \ + ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev)) + +/* + * Tail queue functions. + */ +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) \ + if ((head)->tqh_first && \ + (head)->tqh_first->field.tqe_prev != &(head)->tqh_first) \ + QUEUEDEBUG_ABORT("TAILQ_INSERT_HEAD %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) \ + if (*(head)->tqh_last != NULL) \ + QUEUEDEBUG_ABORT("TAILQ_INSERT_TAIL %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_OP(elm, field) \ + if ((elm)->field.tqe_next && \ + (elm)->field.tqe_next->field.tqe_prev != \ + &(elm)->field.tqe_next) \ + QUEUEDEBUG_ABORT("TAILQ_* forw %p %s:%d", (elm), \ + __FILE__, __LINE__); \ + if (*(elm)->field.tqe_prev != (elm)) \ + QUEUEDEBUG_ABORT("TAILQ_* back %p %s:%d", (elm), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) \ + if ((elm)->field.tqe_next == NULL && \ + (head)->tqh_last != &(elm)->field.tqe_next) \ + QUEUEDEBUG_ABORT("TAILQ_PREREMOVE head %p elm %p %s:%d",\ + (head), (elm), __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) \ + (elm)->field.tqe_next = (void *)1L; \ + (elm)->field.tqe_prev = (void *)1L; +#else +#define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) +#define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) +#define QUEUEDEBUG_TAILQ_OP(elm, field) +#define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) +#define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) +#endif + +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = TAILQ_END(head); \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_INSERT_HEAD((head), (elm), field) \ + if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_INSERT_TAIL((head), (elm), field) \ + (elm)->field.tqe_next = TAILQ_END(head); \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QUEUEDEBUG_TAILQ_OP((listelm), field) \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QUEUEDEBUG_TAILQ_OP((listelm), field) \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_PREREMOVE((head), (elm), field) \ + QUEUEDEBUG_TAILQ_OP((elm), field) \ + if (((elm)->field.tqe_next) != TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ +} while (/*CONSTCOND*/0) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue access methods. + */ +#define STAILQ_FIRST(head) ((head)->stqh_first) +#define STAILQ_END(head) NULL +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) +#define STAILQ_EMPTY(head) (STAILQ_FIRST(head) == STAILQ_END(head)) + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_INIT(head) do { \ + (head)->stqh_first = NULL; \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (head)->stqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (listelm)->field.stqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + if ((curelm->field.stqe_next = \ + curelm->field.stqe_next->field.stqe_next) == NULL) \ + (head)->stqh_last = &(curelm)->field.stqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->stqh_first); \ + (var); \ + (var) = ((var)->field.stqe_next)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? \ + NULL : \ + ((struct type *)(void *) \ + ((char *)((head)->stqh_last) - offsetof(struct type, field)))) + + +#ifndef _KERNEL +/* + * Circular queue definitions. Do not use. We still keep the macros + * for compatibility but because of pointer aliasing issues their use + * is discouraged! + */ + +/* + * __launder_type(): We use this ugly hack to work around the the compiler + * noticing that two types may not alias each other and elide tests in code. + * We hit this in the CIRCLEQ macros when comparing 'struct name *' and + * 'struct type *' (see CIRCLEQ_HEAD()). Modern compilers (such as GCC + * 4.8) declare these comparisons as always false, causing the code to + * not run as designed. + * + * This hack is only to be used for comparisons and thus can be fully const. + * Do not use for assignment. + * + * If we ever choose to change the ABI of the CIRCLEQ macros, we could fix + * this by changing the head/tail sentinal values, but see the note above + * this one. + */ +static __inline const void * __launder_type(const void *); +static __inline const void * +__launder_type(const void *__x) +{ + __asm __volatile("" : "+r" (__x)); + return __x; +} + +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field) \ + if ((head)->cqh_first != CIRCLEQ_ENDC(head) && \ + (head)->cqh_first->field.cqe_prev != CIRCLEQ_ENDC(head)) \ + QUEUEDEBUG_ABORT("CIRCLEQ head forw %p %s:%d", (head), \ + __FILE__, __LINE__); \ + if ((head)->cqh_last != CIRCLEQ_ENDC(head) && \ + (head)->cqh_last->field.cqe_next != CIRCLEQ_ENDC(head)) \ + QUEUEDEBUG_ABORT("CIRCLEQ head back %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field) \ + if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) { \ + if ((head)->cqh_last != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm last %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } else { \ + if ((elm)->field.cqe_next->field.cqe_prev != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm forw %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } \ + if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) { \ + if ((head)->cqh_first != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm first %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } else { \ + if ((elm)->field.cqe_prev->field.cqe_next != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm prev %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } +#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field) \ + (elm)->field.cqe_next = (void *)1L; \ + (elm)->field.cqe_prev = (void *)1L; +#else +#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field) +#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field) +#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field) +#endif + +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field) \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field) \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (elm), field) \ + if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + QUEUEDEBUG_CIRCLEQ_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->cqh_first); \ + (var) != CIRCLEQ_ENDC(head); \ + (var) = ((var)->field.cqe_next)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for ((var) = ((head)->cqh_last); \ + (var) != CIRCLEQ_ENDC(head); \ + (var) = ((var)->field.cqe_prev)) + +/* + * Circular queue access methods. + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +/* For comparisons */ +#define CIRCLEQ_ENDC(head) (__launder_type(head)) +/* For assignments */ +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_ENDC(head)) + +#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ + (((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + ? ((head)->cqh_first) \ + : (elm->field.cqe_next)) +#define CIRCLEQ_LOOP_PREV(head, elm, field) \ + (((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + ? ((head)->cqh_last) \ + : (elm->field.cqe_prev)) +#endif /* !_KERNEL */ + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/include/sys/redox.h b/include/sys/redox.h index 6dd2322368..14b906c05d 100644 --- a/include/sys/redox.h +++ b/include/sys/redox.h @@ -12,8 +12,6 @@ extern "C" { ssize_t redox_fpath(int fd, void * buf, size_t count); void * redox_physalloc(size_t size); int redox_physfree(void * physical_address, size_t size); -void * redox_physmap(void * physical_address, size_t size, int flags); -int redox_physunmap(void * virtual_address); #endif diff --git a/include/sys/sysmacros.h b/include/sys/sysmacros.h new file mode 100644 index 0000000000..efe296e7f0 --- /dev/null +++ b/include/sys/sysmacros.h @@ -0,0 +1,16 @@ +// From musl, license MIT +#ifndef _SYS_SYSMACROS_H +#define _SYS_SYSMACROS_H + +#define major(x) \ + ((unsigned)( (((x)>>31>>1) & 0xfffff000) | (((x)>>8) & 0x00000fff) )) +#define minor(x) \ + ((unsigned)( (((x)>>12) & 0xffffff00) | ((x) & 0x000000ff) )) + +#define makedev(x,y) ( \ + (((x)&0xfffff000ULL) << 32) | \ + (((x)&0x00000fffULL) << 8) | \ + (((y)&0xffffff00ULL) << 12) | \ + (((y)&0x000000ffULL)) ) + +#endif \ No newline at end of file diff --git a/include/sys/types_internal.h b/include/sys/types_internal.h deleted file mode 100644 index 5e533e810a..0000000000 --- a/include/sys/types_internal.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef _SYS_TYPES_INTERNAL_H -#define _SYS_TYPES_INTERNAL_H -#include - -typedef long blksize_t; -typedef long dev_t; -typedef unsigned long ino_t; -typedef int gid_t; -typedef int uid_t; -typedef int mode_t; -typedef unsigned long nlink_t; -typedef long off_t; -typedef int pid_t; -typedef unsigned id_t; -typedef long ssize_t; -typedef long time_t; -typedef unsigned int useconds_t; -typedef int suseconds_t; -typedef long clock_t; -typedef int clockid_t; -typedef void* timer_t; -typedef unsigned long int blkcnt_t; - -typedef unsigned long int fsblkcnt_t; -typedef unsigned long int fsfilcnt_t; - -typedef unsigned char u_char, uchar; -typedef unsigned short u_short, ushort; -typedef unsigned int u_int, uint; -typedef unsigned long u_long, ulong; -typedef long long quad_t; -typedef unsigned long long u_quad_t; -typedef char *caddr_t; -#endif /* _SYS_TYPES_INTERNAL_H */ diff --git a/include/sys/user.h b/include/sys/user.h index cc0333a628..aadbc7a659 100644 --- a/include/sys/user.h +++ b/include/sys/user.h @@ -4,8 +4,10 @@ #include #elif defined(__aarch64__) #include +#elif defined(__riscv) && __riscv_xlen==64 +#include #else #error "Unknown architecture" #endif -#endif \ No newline at end of file +#endif diff --git a/include/syslog.h b/include/syslog.h new file mode 100644 index 0000000000..830b4928ad --- /dev/null +++ b/include/syslog.h @@ -0,0 +1 @@ +#include diff --git a/src/ld_so/Cargo.toml b/ld_so/Cargo.toml similarity index 76% rename from src/ld_so/Cargo.toml rename to ld_so/Cargo.toml index d5e0298cd8..93b1fb1130 100644 --- a/src/ld_so/Cargo.toml +++ b/ld_so/Cargo.toml @@ -2,7 +2,11 @@ name = "ld_so" version = "0.1.0" authors = ["Jeremy Soller "] +edition = "2024" [lib] name = "ld_so" crate-type = ["staticlib"] + +[lints] +workspace = true diff --git a/ld_so/ld_script/aarch64-unknown-linux-gnu.ld b/ld_so/ld_script/aarch64-unknown-linux-gnu.ld new file mode 100644 index 0000000000..a344feef5d --- /dev/null +++ b/ld_so/ld_script/aarch64-unknown-linux-gnu.ld @@ -0,0 +1,263 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64", "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_start) +SEARCH_DIR("=/usr/aarch64-linux-gnu/lib64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/aarch64-linux-gnu/lib"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); + . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS; + /* Place the build-id as close to the ELF headers as possible. This + maximises the chance the build-id will be present in core files, + which GDB can then use to locate the associated debuginfo file. */ + .note.gnu.build-id : { *(.note.gnu.build-id) } + .interp : { *(.interp) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + .relr.dyn : { *(.relr.dyn) } + /* Start of the executable code region. */ + .init : + { + KEEP (*(SORT_NONE(.init))) + } =0x1f2003d5 + .plt : ALIGN(16) { *(.plt) *(.iplt) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(SORT(.text.sorted.*)) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } =0x1f2003d5 + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } =0x1f2003d5 + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + /* Start of the Read Only Data region. */ + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .sframe : ONLY_IF_RO { *(.sframe) *(.sframe.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Various note sections. Placed here so that they are always included + in the read-only segment and not treated as orphan sections. The + current orphan handling algorithm does place note sections after R/O + data, but this is not guaranteed to always be the case. */ + .note.build-id : { *(.note.build-id) } + .note.GNU-stack : { *(.note.GNU-stack) } + .note.gnu-property : { *(.note.gnu-property) } + .note.ABI-tag : { *(.note.ABI-tag) } + .note.package : { *(.note.package) } + .note.dlopen : { *(.note.dlopen) } + .note.netbsd.ident : { *(.note.netbsd.ident) } + .note.openbsd.ident : { *(.note.openbsd.ident) } + /* Start of the Read Write Data region. */ + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling. */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .sframe : ONLY_IF_RW { *(.sframe) *(.sframe.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections. */ + /* .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + /* .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } */ + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (24, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + PROVIDE (__data_start = .); + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; + PROVIDE (edata = .); + . = ALIGN(ALIGNOF(NEXT_SECTION)); + __bss_start = .; + __bss_start__ = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that in the common case of there only being one + type of .bss section, the section occupies space up to _end. + Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + _bss_end__ = .; __bss_end__ = .; + . = ALIGN(64 / 8); + /* Start of the Large Data region. */ + . = SEGMENT_START("ldata-segment", .); + . = ALIGN(64 / 8); + __end__ = .; + _end = .; + PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Start of the Tiny Data region. */ + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 (INFO) : { *(.comment); LINKER_VERSION; } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } +} diff --git a/ld_so/ld_script/aarch64-unknown-redox.ld b/ld_so/ld_script/aarch64-unknown-redox.ld new file mode 100644 index 0000000000..64ead42b5e --- /dev/null +++ b/ld_so/ld_script/aarch64-unknown-redox.ld @@ -0,0 +1,255 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", + "elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_start) +SEARCH_DIR("/aarch64-unknown-redox/lib"); +SEARCH_DIR("/usr/local/lib64"); +SEARCH_DIR("/lib64"); +SEARCH_DIR("/usr/lib64"); +SEARCH_DIR("/usr/local/lib"); +SEARCH_DIR("/lib"); +SEARCH_DIR("/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) + *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) + *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + . = ALIGN(CONSTANT (MAXPAGESIZE)); + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } +.plt.got : { *(.plt.got) } +.plt.sec : { *(.plt.sec) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(CONSTANT (MAXPAGESIZE)); + /* Adjust the address for the rodata segment. We want to adjust up to + the same address within the page on the next page up. */ + . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + /* .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + .lbss : + { + *(.dynlbss) + *(.lbss .lbss.* .gnu.linkonce.lb.*) + *(LARGE_COMMON) + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + .lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.lrodata .lrodata.* .gnu.linkonce.lr.*) + } + .ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.ldata .ldata.* .gnu.linkonce.l.*) + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array need to be not discarded unlike x86_64 linker variant + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + } +} diff --git a/ld_so/ld_script/i586-unknown-redox.ld b/ld_so/ld_script/i586-unknown-redox.ld new file mode 100644 index 0000000000..86b9e46c4c --- /dev/null +++ b/ld_so/ld_script/i586-unknown-redox.ld @@ -0,0 +1,256 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", + "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SEARCH_DIR("/i686-unknown-redox/lib"); +SEARCH_DIR("/usr/local/lib32"); +SEARCH_DIR("/lib32"); +SEARCH_DIR("/usr/lib32"); +SEARCH_DIR("/usr/local/lib"); +SEARCH_DIR("/lib"); +SEARCH_DIR("/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) + *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) + *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + . = ALIGN(CONSTANT (MAXPAGESIZE)); + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } +.plt.got : { *(.plt.got) } +.plt.sec : { *(.plt.sec) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(CONSTANT (MAXPAGESIZE)); + /* Adjust the address for the rodata segment. We want to adjust up to + the same address within the page on the next page up. */ + . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + /* .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + /* .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } */ + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + .lbss : + { + *(.dynlbss) + *(.lbss .lbss.* .gnu.linkonce.lb.*) + *(LARGE_COMMON) + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + .lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.lrodata .lrodata.* .gnu.linkonce.lr.*) + } + .ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.ldata .ldata.* .gnu.linkonce.l.*) + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } +} diff --git a/ld_so/ld_script/i686-unknown-redox.ld b/ld_so/ld_script/i686-unknown-redox.ld new file mode 100644 index 0000000000..7e68c6c8bb --- /dev/null +++ b/ld_so/ld_script/i686-unknown-redox.ld @@ -0,0 +1,256 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", + "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SEARCH_DIR("/i686-unknown-redox/lib"); +SEARCH_DIR("/usr/local/lib32"); +SEARCH_DIR("/lib32"); +SEARCH_DIR("/usr/lib32"); +SEARCH_DIR("/usr/local/lib"); +SEARCH_DIR("/lib"); +SEARCH_DIR("/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) + *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) + *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + . = ALIGN(CONSTANT (MAXPAGESIZE)); + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } +.plt.got : { *(.plt.got) } +.plt.sec : { *(.plt.sec) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(CONSTANT (MAXPAGESIZE)); + /* Adjust the address for the rodata segment. We want to adjust up to + the same address within the page on the next page up. */ + . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + /* .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + /* .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } */ + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + .lbss : + { + *(.dynlbss) + *(.lbss .lbss.* .gnu.linkonce.lb.*) + *(LARGE_COMMON) + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + .lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.lrodata .lrodata.* .gnu.linkonce.lr.*) + } + .ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.ldata .ldata.* .gnu.linkonce.l.*) + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } +} diff --git a/ld_so/ld_script/riscv64gc-unknown-redox.ld b/ld_so/ld_script/riscv64gc-unknown-redox.ld new file mode 100644 index 0000000000..fc657797ea --- /dev/null +++ b/ld_so/ld_script/riscv64gc-unknown-redox.ld @@ -0,0 +1,247 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv" ) +OUTPUT_ARCH(riscv) +ENTRY(_start) +SEARCH_DIR("/riscv64-unknown-redox/lib"); +SEARCH_DIR("/usr/local/lib64"); +SEARCH_DIR("/lib64"); +SEARCH_DIR("/usr/lib64"); +SEARCH_DIR("/usr/local/lib"); +SEARCH_DIR("/lib"); +SEARCH_DIR("/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) + *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) + *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + . = ALIGN(CONSTANT (MAXPAGESIZE)); + .plt : { *(.plt) *(.iplt) } +.plt.got : { *(.plt.got) } +.plt.sec : { *(.plt.sec) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(CONSTANT (MAXPAGESIZE)); + /* Adjust the address for the rodata segment. We want to adjust up to + the same address within the page on the next page up. */ + . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + /* .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + /* .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } */ + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + .lbss : + { + *(.dynlbss) + *(.lbss .lbss.* .gnu.linkonce.lb.*) + *(LARGE_COMMON) + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + .lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.lrodata .lrodata.* .gnu.linkonce.lr.*) + } + .ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.ldata .ldata.* .gnu.linkonce.l.*) + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } +} diff --git a/src/ld_so/ld_script b/ld_so/ld_script/x86_64-unknown-linux-gnu.ld similarity index 94% rename from src/ld_so/ld_script rename to ld_so/ld_script/x86_64-unknown-linux-gnu.ld index 5f05fed757..be84e120e1 100644 --- a/src/ld_so/ld_script +++ b/ld_so/ld_script/x86_64-unknown-linux-gnu.ld @@ -7,7 +7,6 @@ OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") OUTPUT_ARCH(i386:x86-64) ENTRY(_start) -SEARCH_DIR("/x86_64-unknown-redox/lib"); SEARCH_DIR("/usr/x86_64-pc-linux-gnu/lib64"); SEARCH_DIR("/usr/lib64/binutils/x86_64-pc-linux-gnu/2.33.164"); SEARCH_DIR("/usr/local/lib64"); @@ -102,25 +101,25 @@ SECTIONS .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } /* Thread Local Storage sections */ - .tdata : + /* .tdata : ALIGN(4K) { PROVIDE_HIDDEN (__tdata_start = .); *(.tdata .tdata.* .gnu.linkonce.td.*) } - .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ .preinit_array : { PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array)) PROVIDE_HIDDEN (__preinit_array_end = .); } - .init_array : + /* .init_array : { PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) PROVIDE_HIDDEN (__init_array_end = .); - } + } */ .fini_array : { PROVIDE_HIDDEN (__fini_array_start = .); @@ -245,5 +244,16 @@ SECTIONS .debug_macro 0 : { *(.debug_macro) } .debug_addr 0 : { *(.debug_addr) } .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } - /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } } diff --git a/ld_so/ld_script/x86_64-unknown-redox.ld b/ld_so/ld_script/x86_64-unknown-redox.ld new file mode 100644 index 0000000000..3e54c4a6d7 --- /dev/null +++ b/ld_so/ld_script/x86_64-unknown-redox.ld @@ -0,0 +1,256 @@ +/* Script for -z combreloc */ +/* Copyright (C) 2014-2020 Free Software Foundation, Inc. + Copying and distribution of this script, with or without modification, + are permitted in any medium without royalty provided the copyright + notice and this notice are preserved. */ +OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", + "elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) +SEARCH_DIR("/x86_64-unknown-redox/lib"); +SEARCH_DIR("/usr/local/lib64"); +SEARCH_DIR("/lib64"); +SEARCH_DIR("/usr/lib64"); +SEARCH_DIR("/usr/local/lib"); +SEARCH_DIR("/lib"); +SEARCH_DIR("/usr/lib"); +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x20000000) + SIZEOF_HEADERS; + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*) + *(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*) + *(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + . = ALIGN(CONSTANT (MAXPAGESIZE)); + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } +.plt.got : { *(.plt.got) } +.plt.sec : { *(.plt.sec) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + . = ALIGN(CONSTANT (MAXPAGESIZE)); + /* Adjust the address for the rodata segment. We want to adjust up to + the same address within the page on the next page up. */ + . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + /* .tdata : ALIGN(4K) + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */ + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + /* .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } */ + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .got : { *(.got) *(.igot) } + . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .); + .got.plt : { *(.got.plt) *(.igot.plt) } + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + .lbss : + { + *(.dynlbss) + *(.lbss .lbss.* .gnu.linkonce.lb.*) + *(LARGE_COMMON) + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + .lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.lrodata .lrodata.* .gnu.linkonce.lr.*) + } + .ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) : + { + *(.ldata .ldata.* .gnu.linkonce.l.*) + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF Extension. */ + .debug_macro 0 : { *(.debug_macro) } + .debug_addr 0 : { *(.debug_addr) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { + *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) + /* + * XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor, + * ld.so will be moved out of relibc. So, till that time, we have to discard any sections + * that may reference use thread local storage. + * + * .init_array also depends on TLS and is discarded as we don't need it. + */ + *(.gnu.linkonce.tb.*) *(.tcommon) + *(.init_array) + } +} diff --git a/ld_so/src/lib.rs b/ld_so/src/lib.rs new file mode 100644 index 0000000000..e68f260ef6 --- /dev/null +++ b/ld_so/src/lib.rs @@ -0,0 +1,136 @@ +#![no_std] +#![feature(linkage)] +#![deny(unsafe_op_in_unsafe_fn)] + +use core::arch::global_asm; + +#[cfg(target_arch = "aarch64")] +global_asm!( + " +.weak _DYNAMIC +.hidden _DYNAMIC + +.global _start +_start: + mov x28, sp + // align stack to 16 bytes + and sp, x28, #0xfffffffffffffff0 + adr x1, _start + mov x0, x28 + adrp x2, _DYNAMIC + add x2, x2, #:lo12:_DYNAMIC + // ld_so_start(stack=x0, ld_entry=x1, dynamic=x2) + bl relibc_ld_so_start + // restore original stack, clear registers, and jump to the new start function + mov sp, x28 + mov x1, xzr + mov x2, xzr + mov x3, xzr + mov x4, xzr + mov x5, xzr + mov x6, xzr + mov x7, xzr + mov x8, xzr + mov x9, xzr + mov x10, xzr + mov x11, xzr + mov x12, xzr + mov x13, xzr + mov x14, xzr + mov x15, xzr + mov x16, xzr + mov x17, xzr + mov x18, xzr + mov x19, xzr + mov x20, xzr + mov x21, xzr + mov x22, xzr + mov x23, xzr + mov x24, xzr + mov x25, xzr + mov x26, xzr + mov x27, xzr + mov x28, xzr + mov x29, xzr + mov x30, xzr + br x0 + udf #0 +" +); + +#[cfg(target_arch = "x86")] +global_asm!( + " +.globl _start +_start: + push esp + call relibc_ld_so_start + pop esp + # TODO: x86 + ud2 +" +); + +#[cfg(target_arch = "x86_64")] +global_asm!( + " +.weak _DYNAMIC +.hidden _DYNAMIC + +.globl _start +_start: + lea rsi, [rip + _start] + + # Save original stack and align stack to 16 bytes + mov rbp, rsp + and rsp, 0xfffffffffffffff0 + + # Call ld_so_start(stack=rdi, ld_entry=rsi, dynamic=rdx) + mov rdi, rbp + lea rdx, [rip + _DYNAMIC] + call relibc_ld_so_start + + # Restore original stack, clear registers, and jump to new start function + mov rsp, rbp + xor rcx, rcx + xor rdx, rdx + xor rdi, rdi + xor rsi, rsi + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r11, r11 + fninit + jmp rax + ud2 +" +); + +#[cfg(target_arch = "riscv64")] +global_asm!( + " +.globl _start +_start: + mv a0, sp + jal relibc_ld_so_start + unimp +" +); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn main(_argc: isize, _argv: *const *const i8) -> usize { + // LD + 0x1D +} + +#[linkage = "weak"] +#[unsafe(no_mangle)] +extern "C" fn relibc_panic(_pi: &::core::panic::PanicInfo) -> ! { + loop {} +} + +#[panic_handler] +#[linkage = "weak"] +pub unsafe fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { + relibc_panic(pi) +} diff --git a/posix-regex b/posix-regex deleted file mode 160000 index 0d996efe5c..0000000000 --- a/posix-regex +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d996efe5cfe7ce181af35d8817ac4deae644d4a diff --git a/pthreads-emb b/pthreads-emb deleted file mode 160000 index 0db50689be..0000000000 --- a/pthreads-emb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0db50689bec259ab99d130118b27b440614c9958 diff --git a/ralloc b/ralloc deleted file mode 160000 index f9b8c35fe8..0000000000 --- a/ralloc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9b8c35fe8a58c0216b06d24832408fe2d9c0206 diff --git a/redox-ioctl/Cargo.toml b/redox-ioctl/Cargo.toml new file mode 100644 index 0000000000..6e810b6b1a --- /dev/null +++ b/redox-ioctl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "redox-ioctl" +authors = ["bjorn3 "] +version = "0.1.0" +edition = "2024" +license = "MIT" +description = "Ioctl definitions and (de)serialization for Redox" + +[dependencies] +drm-sys = "0.8.0" +redox_syscall = { path = "../../syscall" } + +[features] diff --git a/redox-ioctl/src/drm.rs b/redox-ioctl/src/drm.rs new file mode 100644 index 0000000000..062407b103 --- /dev/null +++ b/redox-ioctl/src/drm.rs @@ -0,0 +1,319 @@ +use alloc::vec::Vec; +use core::{ + cmp, + ffi::{c_char, c_int, c_uint}, + iter, mem, slice, +}; +use drm_sys::drm_clip_rect; + +pub use drm_sys::{ + __kernel_size_t, DRM_PROP_NAME_LEN, drm_get_cap, drm_mode_card_res, + drm_mode_connector_set_property, drm_mode_create_dumb, drm_mode_crtc, + drm_mode_crtc_page_flip_target, drm_mode_cursor, drm_mode_cursor2, drm_mode_destroy_dumb, + drm_mode_fb_cmd, drm_mode_fb_cmd2, drm_mode_fb_dirty_cmd, drm_mode_get_blob, + drm_mode_get_connector, drm_mode_get_encoder, drm_mode_get_plane, drm_mode_get_plane_res, + drm_mode_get_property, drm_mode_map_dumb, drm_mode_modeinfo, drm_mode_obj_get_properties, + drm_mode_property_enum, drm_mode_set_plane, drm_set_client_cap, drm_version, +}; + +pub const VERSION: u64 = 0; +define_ioctl_data! { + struct drm_version, DrmVersion { + version_major: c_int, + version_minor: c_int, + version_patchlevel: c_int, + name_len: __kernel_size_t, + name: *mut c_char [array], + date_len: __kernel_size_t, + date: *mut c_char [array], + desc_len: __kernel_size_t, + desc: *mut c_char [array], + } +} + +pub const GET_CAP: u64 = 0x0C; +pub use drm_sys::DRM_CAP_DUMB_BUFFER; +define_ioctl_data! { + struct drm_get_cap, DrmGetCap { + capability: u64, + value: u64, + } +} + +pub const SET_CLIENT_CAP: u64 = 0x0D; +pub use drm_sys::DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT; +define_ioctl_data! { + struct drm_set_client_cap, DrmSetClientCap { + capability: u64, + value: u64, + } +} + +pub const MODE_CARD_RES: u64 = 0xA0; +define_ioctl_data! { + struct drm_mode_card_res, DrmModeCardRes { + fb_id_ptr: u64 [array], + crtc_id_ptr: u64 [array], + connector_id_ptr: u64 [array], + encoder_id_ptr: u64 [array], + count_fbs: u32, + count_crtcs: u32, + count_connectors: u32, + count_encoders: u32, + min_width: u32, + max_width: u32, + min_height: u32, + max_height: u32, + } +} + +pub const MODE_GET_CRTC: u64 = 0xA1; +pub const MODE_SET_CRTC: u64 = 0xA2; +define_ioctl_data! { + struct drm_mode_crtc, DrmModeCrtc { + set_connectors_ptr: u64 [array], + count_connectors: u32, + crtc_id: u32, + fb_id: u32, + x: u32, + y: u32, + gamma_size: u32, + mode_valid: u32, + mode: drm_mode_modeinfo, + } +} + +pub const MODE_CURSOR: u64 = 0xA3; +define_ioctl_data! { + struct drm_mode_cursor, DrmModeCursor { + flags: u32, + crtc_id: u32, + x: i32, + y:i32, + width:u32, + height:u32, + handle:u32, + } +} + +pub const MODE_GET_ENCODER: u64 = 0xA6; +define_ioctl_data! { + struct drm_mode_get_encoder, DrmModeGetEncoder { + encoder_id: u32, + encoder_type: u32, + crtc_id: u32, + possible_crtcs: u32, + possible_clones: u32, + } +} + +pub const MODE_GET_CONNECTOR: u64 = 0xA7; +define_ioctl_data! { + struct drm_mode_get_connector, DrmModeGetConnector { + encoders_ptr: u64 [array], + modes_ptr: u64 [array], + props_ptr: u64 [array], + prop_values_ptr: u64 [array], + count_modes: u32, + count_props: u32, + count_encoders: u32, + encoder_id: u32, + connector_id: u32, + connector_type: u32, + connector_type_id: u32, + connection: u32, + mm_width: u32, + mm_height: u32, + subpixel: u32, + pad: u32, + } +} + +pub const MODE_GET_PROPERTY: u64 = 0xAA; +define_ioctl_data! { + struct drm_mode_get_property, DrmModeGetProperty { + values_ptr: u64 [array], + enum_blob_ptr: u64 [array], + prop_id: u32, + flags: u32, + name: [c_char; DRM_PROP_NAME_LEN as usize], + count_values: u32, + count_enum_blobs: u32, + } +} + +pub const MODE_SET_PROPERTY: u64 = 0xAB; +define_ioctl_data! { + struct drm_mode_connector_set_property, DrmModeConnectorSetProperty { + value: u64, + prop_id: u32, + connector_id: u32, + } +} + +pub const MODE_GET_PROP_BLOB: u64 = 0xAC; +define_ioctl_data! { + struct drm_mode_get_blob, DrmModeGetBlob { + blob_id: u32, + length: u32, + data: u64 [array], + } +} + +pub const MODE_GET_FB: u64 = 0xAD; +pub const MODE_ADD_FB: u64 = 0xAE; +define_ioctl_data! { + struct drm_mode_fb_cmd, DrmModeFbCmd { + fb_id: u32, + width: u32, + height: u32, + pitch: u32, + bpp: u32, + depth: u32, + handle: u32, + } +} + +pub const MODE_RM_FB: u64 = 0xAF; +define_ioctl_data! { + struct standin_for_uint, StandinForUint { + inner: c_uint, + } +} + +#[repr(transparent)] +#[allow(non_camel_case_types)] +pub struct standin_for_uint { + pub inner: c_uint, +} + +pub const MODE_PAGE_FLIP: u64 = 0xB0; +define_ioctl_data! { + struct drm_mode_crtc_page_flip_target, DrmModeCrtcPageFlipTarget { + crtc_id: u32, + fb_id: u32, + flags: u32, + sequence: u32, + user_data: u64, + } +} + +pub const MODE_DIRTYFB: u64 = 0xB1; +define_ioctl_data! { + struct drm_mode_fb_dirty_cmd, DrmModeFbDirtyCmd { + fb_id: u32, + flags: u32, + color: u32, + num_clips: u32, + clips_ptr: u64 [array], + } +} + +pub const MODE_CREATE_DUMB: u64 = 0xB2; +define_ioctl_data! { + struct drm_mode_create_dumb, DrmModeCreateDumb { + height: u32, + width: u32, + bpp: u32, + flags: u32, + handle: u32, + pitch: u32, + size: u64, + } +} + +pub const MODE_MAP_DUMB: u64 = 0xB3; +define_ioctl_data! { + struct drm_mode_map_dumb, DrmModeMapDumb { + handle: u32, + pad: u32, + offset: u64, + } +} + +pub const MODE_DESTROY_DUMB: u64 = 0xB4; +define_ioctl_data! { + struct drm_mode_destroy_dumb, DrmModeDestroyDumb { + handle: u32, + } +} + +pub const MODE_GET_PLANE_RES: u64 = 0xB5; +define_ioctl_data! { + struct drm_mode_get_plane_res, DrmModeGetPlaneRes { + plane_id_ptr: u64 [array], + count_planes: u32, + } +} + +pub const MODE_GET_PLANE: u64 = 0xB6; +define_ioctl_data! { + struct drm_mode_get_plane, DrmModeGetPlane { + plane_id: u32, + crtc_id: u32, + fb_id: u32, + possible_crtcs: u32, + gamma_size: u32, + count_format_types: u32, + format_type_ptr: u64 [array], + } +} + +pub const MODE_SET_PLANE: u64 = 0xB7; +define_ioctl_data! { + struct drm_mode_set_plane, DrmModeSetPlane { + plane_id: u32, + crtc_id: u32, + fb_id: u32, + flags: u32, + crtc_x: i32, + crtc_y: i32, + crtc_w: u32, + crtc_h: u32, + src_x: u32, + src_y: u32, + src_h: u32, + src_w: u32, + } +} + +pub const MODE_OBJ_GET_PROPERTIES: u64 = 0xB9; +define_ioctl_data! { + struct drm_mode_obj_get_properties, DrmModeObjGetProperties { + props_ptr: u64 [array], + prop_values_ptr: u64 [array], + count_props: u32, + obj_id: u32, + obj_type: u32, + } +} + +pub const MODE_CURSOR2: u64 = 0xBB; +define_ioctl_data! { + struct drm_mode_cursor2, DrmModeCursor2 { + flags: u32, + crtc_id: u32, + x: i32, + y: i32, + width: u32, + height: u32, + handle: u32, + hot_x: i32, + hot_y: i32, + } +} + +pub const MODE_GET_FB2: u64 = 0xCE; +define_ioctl_data! { + struct drm_mode_fb_cmd2, DrmModeFbCmd2 { + fb_id: u32, + width: u32, + height: u32, + pixel_format: u32, + flags: u32, + handles: [u32; 4], + pitches: [u32; 4], + offsets: [u32; 4], + modifier: [u64; 4], + } +} diff --git a/redox-ioctl/src/ioctl_data.rs b/redox-ioctl/src/ioctl_data.rs new file mode 100644 index 0000000000..f9673f6d7d --- /dev/null +++ b/redox-ioctl/src/ioctl_data.rs @@ -0,0 +1,169 @@ +use alloc::vec::Vec; + +pub trait IoctlData { + unsafe fn write(&self) -> Vec; + unsafe fn read_from(&mut self, buf: &[u8]); +} + +macro_rules! define_ioctl_data { + (struct $ioctl_ty:ident, $mem_ty:ident { + $($rest:tt)* + }) => { + define_ioctl_data!( + struct $ioctl_ty, $mem_ty { $($rest)* } => (), (), () + ); + }; + (struct $ioctl_ty:ident, $mem_ty:ident { + $field:ident: $ty:ty, + $($rest:tt)* + } => + ($($ioctl_fields:tt)*), + ($($counted_fields:tt)*), + ($($noncounted_fields:tt)*) + ) => { + define_ioctl_data!( + struct $ioctl_ty, $mem_ty { $($rest)* } => + ($($ioctl_fields)* $field: $ty,), + ($($counted_fields)*), + ($($noncounted_fields)* $field: $ty,) + ); + }; + (struct $ioctl_ty:ident, $mem_ty:ident { + $field:ident: $ty:ty [array<$el:ty, $counted_by:ident>], + $($rest:tt)* + } => + ($($ioctl_fields:tt)*), + ($($counted_fields:tt)*), + ($($noncounted_fields:tt)*) + ) => { + define_ioctl_data!( + struct $ioctl_ty, $mem_ty { $($rest)* } => + ($($ioctl_fields)* $field: $ty,), + ($($counted_fields)* $field: $ty [array<$el, $counted_by>],), + ($($noncounted_fields)*) + ); + }; + (struct $ioctl_ty:ident, $mem_ty:ident {} => + ($($ioctl_field:ident: $ioctl_field_ty:ty,)*), + ($($counted_field:ident: $counted_ty:ty [array<$el:ty, $counted_by:ident>],)*), + ($($noncounted_field:ident: $noncounted_ty:ty,)*) + ) => { + // FIXME check ioctl_ty doesn't have padding + const _: $ioctl_ty = $ioctl_ty { + $($ioctl_field: unsafe { mem::zeroed::<$ioctl_field_ty>() },)* + }; + + #[repr(C)] + pub struct ${concat(__, $mem_ty, Noncounted)} { + $($noncounted_field: $noncounted_ty,)* + } + + pub struct $mem_ty<'a> { + noncounted_fields: &'a mut ${concat(__, $mem_ty, Noncounted)}, + $($counted_field: &'a mut [$el],)* + } + + impl $crate::ioctl_data::IoctlData for $ioctl_ty { + unsafe fn write(&self) -> Vec { + let noncounted_fields = ${concat(__, $mem_ty, Noncounted)} { + $($noncounted_field: self.$noncounted_field,)* + }; + // FIXME use Vec::with_capacity + let mut data = Vec::::new(); + data.extend_from_slice(&unsafe { + mem::transmute::< + ${concat(__, $mem_ty, Noncounted)}, + [u8; size_of::<${concat(__, $mem_ty, Noncounted)}>()], + >(noncounted_fields) + }); + $( + let size = self.$counted_by as usize * size_of::<$el>(); + if self.$counted_field as usize != 0 { + let $counted_field = unsafe { + slice::from_raw_parts(self.$counted_field as *const u8, size) + }; + data.extend_from_slice(&$counted_field); + } else { + data.extend(iter::repeat(0u8).take(size)); + }; + + )* + data + } + + unsafe fn read_from(&mut self, mut buf: &[u8]) { + // FIXME be robust against malicious scheme implementations by returning an error + // when the buf is the wrong size + let noncounted_fields = buf.split_off(..size_of::<${concat(__, $mem_ty, Noncounted)}>()).unwrap(); + + $( + let size = self.$counted_by as usize * size_of::<$el>(); + let $counted_field = buf.split_off(..size).unwrap(); + if self.$counted_field as usize != 0 { + unsafe { + slice::from_raw_parts_mut(self.$counted_field as *mut u8, size).copy_from_slice($counted_field); + } + } + )* + + assert!(buf.is_empty()); + + let noncounted_fields = unsafe { &*(noncounted_fields as *const _ as *const ${concat(__, $mem_ty, Noncounted)}) }; + $(self.$noncounted_field = noncounted_fields.$noncounted_field;)* + } + } + + impl<'a> $mem_ty<'a> { + pub fn with( + mut buf: &'a mut [u8], + f: impl FnOnce($mem_ty<'a>) -> syscall::Result, + ) -> syscall::Result { + let noncounted_fields = buf.split_off_mut(..size_of::<${concat(__, $mem_ty, Noncounted)}>()) + .ok_or(syscall::Error::new(syscall::EINVAL))?; + let noncounted_fields = unsafe { &mut *(noncounted_fields as *mut _ as *mut ${concat(__, $mem_ty, Noncounted)}) }; + + $( + let $counted_field = buf.split_off_mut(..noncounted_fields.$counted_by as usize * size_of::<$el>()) + .ok_or(syscall::Error::new(syscall::EINVAL))?; + let $counted_field = unsafe { + slice::from_raw_parts_mut($counted_field as *mut _ as *mut $el, noncounted_fields.$counted_by as usize) + }; + )* + + if !buf.is_empty() { + return Err(syscall::Error::new(syscall::EINVAL)); + } + + + + Ok( f($mem_ty { + noncounted_fields, + $($counted_field,)* + })?) + } + + $( + pub fn $noncounted_field(&self) -> $noncounted_ty { + self.noncounted_fields.$noncounted_field + } + + /// Should not be called for fields used as array length + pub fn ${concat(set_, $noncounted_field)}(&mut self, data: $noncounted_ty) { + self.noncounted_fields.$noncounted_field = data; + } + )* + + $( + pub fn $counted_field(&self) -> &[$el] { + self.$counted_field + } + + pub fn ${concat(set_, $counted_field)}(&mut self, data: &[$el]) { + let copied_count = cmp::min(data.len(), self.$counted_field.len()); + self.$counted_field[..copied_count].copy_from_slice(&data[..copied_count]); + self.noncounted_fields.$counted_by = data.len() as _; + } + )* + } + }; +} diff --git a/redox-ioctl/src/lib.rs b/redox-ioctl/src/lib.rs new file mode 100644 index 0000000000..0746aba78a --- /dev/null +++ b/redox-ioctl/src/lib.rs @@ -0,0 +1,11 @@ +#![feature(macro_metavar_expr_concat)] +#![deny(unsafe_op_in_unsafe_fn)] +#![no_std] + +extern crate alloc; + +#[macro_use] +mod ioctl_data; +pub use ioctl_data::IoctlData; + +pub mod drm; diff --git a/redox-rt/Cargo.toml b/redox-rt/Cargo.toml new file mode 100644 index 0000000000..773af3bbef --- /dev/null +++ b/redox-rt/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "redox-rt" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +version = "0.1.0" +edition = "2024" +license = "MIT" +description = "Libc-independent runtime for Redox" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags.workspace = true +goblin = { version = "0.10", default-features = false, features = ["elf32", "elf64", "endian_fd"] } +plain.workspace = true +ioslice.workspace = true +redox_syscall.workspace = true +redox-path.workspace = true +redox_protocols.workspace = true + +generic-rt = { path = "../generic-rt" } + +[features] +proc = [] +default = ["proc"] + +[lints] +workspace = true diff --git a/redox-rt/src/arch/aarch64.rs b/redox-rt/src/arch/aarch64.rs new file mode 100644 index 0000000000..0724c9bb5b --- /dev/null +++ b/redox-rt/src/arch/aarch64.rs @@ -0,0 +1,485 @@ +use core::{cell::SyncUnsafeCell, mem::offset_of, ptr::NonNull}; + +use syscall::{data::*, error::*}; + +use crate::{ + Tcb, + proc::{FdGuard, FdGuardUpper, ForkArgs, fork_inner}, + signal::{PosixStackt, RtSigarea, SigStack, inner_c}, +}; +use redox_protocols::protocol::{ProcCall, RtSigInfo}; + +use super::ForkScratchpad; + +// Setup a stack starting from the very end of the address space, and then growing downwards. +pub const STACK_TOP: usize = 1 << 47; +pub const STACK_SIZE: usize = 1024 * 1024; + +#[derive(Debug, Default)] +#[repr(C)] +pub struct SigArea { + pub altstack_top: usize, + pub altstack_bottom: usize, + pub tmp_x1_x2: [usize; 2], + pub tmp_x3_x4: [usize; 2], + pub tmp_x5_x6: [usize; 2], + pub tmp_x7_x8: [usize; 2], + pub tmp_sp: usize, + pub onstack: u64, + pub disable_signals_depth: u64, + pub pctl: usize, // TODO: remove + pub last_sig_was_restart: bool, + pub last_sigstack: Option>, + pub tmp_rt_inf: RtSigInfo, + pub tmp_id_inf: u64, +} +#[repr(C)] +#[derive(Debug, Default)] +pub struct ArchIntRegs { + pub x30: usize, + pub x29: usize, + pub x28: usize, + pub x27: usize, + pub x26: usize, + pub x25: usize, + pub x24: usize, + pub x23: usize, + pub x22: usize, + pub x21: usize, + pub x20: usize, + pub x19: usize, + pub x18: usize, + pub x17: usize, + pub x16: usize, + pub x15: usize, + pub x14: usize, + pub x13: usize, + pub x12: usize, + pub x11: usize, + pub x10: usize, + pub x9: usize, + pub x8: usize, + pub x7: usize, + pub x6: usize, + pub x5: usize, + pub x4: usize, + pub x3: usize, + pub x2: usize, + pub x1: usize, + + pub sp: usize, + pub nzcv: usize, // user-accessible PSTATE bits + + pub pc: usize, + pub x0: usize, +} + +/// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS +/// is already initialized as if it was a thread. +pub unsafe fn deactivate_tcb(open_via_dup: &FdGuardUpper) -> Result<()> { + let mut env = syscall::EnvRegisters::default(); + + let file = open_via_dup.dup(b"regs/env")?; + + env.tpidr_el0 = 0; + + file.write(&mut env)?; + Ok(()) +} + +unsafe extern "C" fn fork_impl(args: &ForkArgs, initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp, args)) +} + +unsafe extern "C" fn child_hook(scratchpad: &ForkScratchpad) { + //let _ = syscall::write(1, alloc::format!("CUR{cur_filetable_fd}PROC{new_proc_fd}THR{new_thr_fd}\n").as_bytes()); + let _ = syscall::close(scratchpad.cur_filetable_fd); + unsafe { + crate::child_hook_common(crate::ChildHookCommonArgs { + new_thr_fd: FdGuard::new(scratchpad.new_thr_fd), + new_proc_fd: if scratchpad.new_proc_fd == usize::MAX { + None + } else { + Some(FdGuard::new(scratchpad.new_proc_fd)) + }, + }) + }; +} + +asmfunction!(__relibc_internal_fork_wrapper (usize) -> usize: [" + stp x29, x30, [sp, #-16]! + stp x27, x28, [sp, #-16]! + stp x25, x26, [sp, #-16]! + stp x23, x24, [sp, #-16]! + stp x21, x22, [sp, #-16]! + stp x19, x20, [sp, #-16]! + + //TODO: store floating point regs + + // x0: &ForkArgs + mov x1, sp + bl {fork_impl} + + ldp x19, x20, [sp], #16 + ldp x21, x22, [sp], #16 + ldp x23, x24, [sp], #16 + ldp x25, x26, [sp], #16 + ldp x27, x28, [sp], #16 + ldp x29, x30, [sp], #16 + ret +"] <= [fork_impl = sym fork_impl]); + +asmfunction!(__relibc_internal_fork_ret: [" + # scratchpad is in x1, move to x0 for child_hook + mov x0, x1 + + bl {child_hook} + + //TODO: load floating point regs + + mov x0, xzr + + ldp x19, x20, [sp], #16 + ldp x21, x22, [sp], #16 + ldp x23, x24, [sp], #16 + ldp x25, x26, [sp], #16 + ldp x27, x28, [sp], #16 + ldp x29, x30, [sp], #16 + + ret +"] <= [child_hook = sym child_hook]); + +// https://devblogs.microsoft.com/oldnewthing/20220811-00/?p=106963 +asmfunction!(__relibc_internal_sigentry: [" + // Clear any active reservation. + clrex + + // The old pc and x0 are saved in the sigcontrol struct. + mrs x0, tpidr_el0 // ABI ptr + ldr x0, [x0] // TCB ptr + + // Save x1-x6 and sp + stp x1, x2, [x0, #{tcb_sa_off} + {sa_tmp_x1_x2}] + stp x3, x4, [x0, #{tcb_sa_off} + {sa_tmp_x3_x4}] + stp x5, x6, [x0, #{tcb_sa_off} + {sa_tmp_x5_x6}] + stp x7, x8, [x0, #{tcb_sa_off} + {sa_tmp_x7_x8}] + mov x1, sp + str x1, [x0, #{tcb_sa_off} + {sa_tmp_sp}] + + ldr x6, [x0, #{tcb_sa_off} + {sa_pctl}] +1: + // Load x1 with the thread's bits + add x5, x0, #{tcb_sc_off} + {sc_word} + ldaxr x1, [x5] + + // First check if there are standard thread signals, + and x4, x1, x1, lsr #32 // x4 := x1 & (x1 >> 32) + cbnz x4, 3f // jump if x4 != 0 + clrex + + // and if not, load process pending bitset. + add x5, x6, #{pctl_pending} + ldaxr x2, [x5] + + // Check if there are standard proc signals: + lsr x3, x1, #32 // mask + and w3, w2, w3 // pending unblocked proc + cbz w3, 4f // skip 'fetch_andn' step if zero + + // If there was one, find which one, and try clearing the bit (last value in x3, addr in x6) + // this picks the MSB rather than the LSB, unlike x86. POSIX does not require any specific + // ordering though. + clz w3, w3 + mov w4, #31 + sub w3, w4, w3 + // x3 now contains the sig_idx + + mov x4, #1 + lsl x4, x4, x3 // bit to remove + + sub x4, x2, x4 // bit was certainly set, so sub is allowed + // x4 is now the new mask to be set + add x5, x6, #{pctl_pending} + + add x2, x5, #{pctl_sender_infos} + add x2, x2, w3, uxtb 3 + ldar x2, [x2] + + // Try clearing the bit, retrying on failure. + stxr w1, x4, [x5] // try setting pending set to x4, set w1 := 0 on success + cbnz w1, 1b // retry everything if this fails + mov x1, x3 + b 2f +4: + // Check for realtime signals, thread/proc. + clrex + + // Load the pending set again. TODO: optimize this? + // Process pending - realtime + add x1, x6, #{pctl_pending} + ldaxr x2, [x1] + lsr x2, x2, #32 + + // Thread pending - realtime and allowset + add x5, x0, #{tcb_sc_off} + {sc_word} + 8 + ldar x1, [x5] + + orr x2, x1, x2 // combine proc and thread pending + and x2, x2, x2, lsr #32 // AND pending with allowset + cbz x2, 7f // spurious signal if realtime is clear + + rbit x2, x2 + clz x2, x2 + // x2 now contains sig_idx - 32 + + // If realtime signal was directed at thread, handle it as an idempotent signal. + lsr x3, x1, x2 // x3 := x1 >> x2; x1 is thread pending + tbnz x3, #0, 5f // jump if bit is nonzero + + // SYS_CALL(fd, payload_base, payload_len, metadata_len, metadata_base | (flags << 8)) + // x8 x0 x1 x2 x3 x4 + + mov x5, x0 // save TCB pointer + mov x6, x2 + ldr x8, ={SYS_CALL} + adrp x0, {proc_fd} + ldr x0, [x0, #:lo12:{proc_fd}] + add x1, x5, #{tcb_sa_off} + {sa_tmp_rt_inf} + str x6, [x1] + mov x2, #{RTINF_SIZE} + adrp x4, {proc_call} + add x4, x4, :lo12:{proc_call} + mov x3, #1 + svc 0 + mov x3, x0 + mov x0, x5 // restore TCB pointer + mov x2, x6 // restore signal number - 32 + add x1, x2, #32 // signal number + cbnz x3, 1b + b 2f +5: + // A realtime signal was sent to this thread, try clearing its bit. + // x3 contains last rt signal word, x2 contains rt_idx + clrex + + // Calculate the absolute sig_idx + add x1, x3, 32 + + // Load si_pid and si_uid + add x2, x0, #{tcb_sc_off} + {sc_sender_infos} + add x2, x2, w1, uxtb #3 + ldar x2, [x2] + + add x3, x0, #{tcb_sc_off} + {sc_word} + 8 + ldxr x2, [x3] + + // Calculate new mask + mov x4, #1 + lsl x4, x4, x2 + sub x2, x2, x4 // remove bit + + stxr w5, x2, [x3] + cbnz w5, 1b + str x2, [x0, #{tcb_sa_off} + {sa_tmp_id_inf}] + b 2f +3: + // A standard signal was sent to this thread, try clearing its bit. + clz w1, w1 + mov x2, #31 + sub x1, x2, x1 + + // Load si_pid and si_uid + add x2, x0, #{tcb_sc_off} + {sc_sender_infos} + add x2, x2, w1, uxtb #3 + ldar x2, [x2] + + // Clear bit from mask + mov x3, #1 + lsl x3, x3, x1 + sub x4, x4, x3 + + // Try updating the mask + stxr w3, x1, [x5] + cbnz w3, 1b + + str x2, [x0, #{tcb_sa_off} + {sa_tmp_id_inf}] +2: + ldr x3, [x0, #{tcb_sa_off} + {sa_pctl}] + add x3, x3, {pctl_actions} + add x2, x3, w1, uxtb #4 // actions_base + sig_idx * sizeof Action + // TODO: NOT ATOMIC (tearing allowed between regs)! + ldxp x2, x3, [x2] + clrex + + // Calculate new sp wrt redzone and alignment + mov x4, sp + sub x4, x4, {REDZONE_SIZE} + and x4, x4, -{STACK_ALIGN} + mov sp, x4 + + // skip sigaltstack step if SA_ONSTACK is clear + // tbz x2, #{SA_ONSTACK_BIT}, 2f + + ldr x2, [x0, #{tcb_sc_off} + {sc_saved_pc}] + ldr x3, [x0, #{tcb_sc_off} + {sc_saved_x0}] + stp x2, x3, [sp, #-16]! + + ldr x2, [x0, #{tcb_sa_off} + {sa_tmp_sp}] + mrs x3, nzcv + stp x2, x3, [sp, #-16]! + + ldp x2, x3, [x0, #{tcb_sa_off} + {sa_tmp_x1_x2}] + stp x2, x3, [sp, #-16]! + ldp x3, x4, [x0, #{tcb_sa_off} + {sa_tmp_x3_x4}] + stp x4, x3, [sp, #-16]! + ldp x5, x6, [x0, #{tcb_sa_off} + {sa_tmp_x5_x6}] + stp x6, x5, [sp, #-16]! + ldp x7, x8, [x0, #{tcb_sa_off} + {sa_tmp_x7_x8}] + stp x8, x7, [sp, #-16]! + + stp x10, x9, [sp, #-16]! + stp x12, x11, [sp, #-16]! + stp x14, x13, [sp, #-16]! + stp x16, x15, [sp, #-16]! + stp x18, x17, [sp, #-16]! + stp x20, x19, [sp, #-16]! + stp x22, x21, [sp, #-16]! + stp x24, x23, [sp, #-16]! + stp x26, x25, [sp, #-16]! + stp x28, x27, [sp, #-16]! + stp x30, x29, [sp, #-16]! + + str w1, [sp, #-4] + sub sp, sp, #64 + + mov x0, sp + bl {inner} + + add sp, sp, #64 + + ldp x30, x29, [sp], #16 + ldp x28, x27, [sp], #16 + ldp x26, x25, [sp], #16 + ldp x24, x23, [sp], #16 + ldp x22, x21, [sp], #16 + ldp x20, x19, [sp], #16 + ldp x18, x17, [sp], #16 + ldp x16, x15, [sp], #16 + ldp x14, x13, [sp], #16 + ldp x12, x11, [sp], #16 + ldp x10, x9, [sp], #16 + ldp x8, x7, [sp], #16 + ldp x6, x5, [sp], #16 + ldp x4, x3, [sp], #16 + ldp x2, x1, [sp], #16 + + ldr x0, [sp, #8] + msr nzcv, x0 + +8: + // x18 is reserved by ABI as 'platform register', so clobbering it should be safe. + mov x18, sp + ldr x0, [x18] + mov sp, x0 + + ldp x18, x0, [x18, #16] + br x18 +7: + // Spurious signal, i.e. all bitsets were 0 at the time they were checked + clrex + + ldr x1, [x0, #{tcb_sc_off} + {sc_flags}] + and x1, x1, ~1 + str x1, [x0, #{tcb_sc_off} + {sc_flags}] + + ldp x1, x2, [x0, #{tcb_sa_off} + {sa_tmp_x1_x2}] + ldp x3, x4, [x0, #{tcb_sa_off} + {sa_tmp_x3_x4}] + ldp x5, x6, [x0, #{tcb_sa_off} + {sa_tmp_x5_x6}] + ldr x18, [x0, #{tcb_sc_off} + {sc_saved_pc}] + ldr x0, [x0, #{tcb_sc_off} + {sc_saved_x0}] + br x18 +"] <= [ + pctl_pending = const (offset_of!(SigProcControl, pending)), + pctl_actions = const (offset_of!(SigProcControl, actions)), + pctl_sender_infos = const (offset_of!(SigProcControl, sender_infos)), + tcb_sc_off = const (offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control)), + tcb_sa_off = const (offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, arch)), + sa_tmp_x1_x2 = const offset_of!(SigArea, tmp_x1_x2), + sa_tmp_x3_x4 = const offset_of!(SigArea, tmp_x3_x4), + sa_tmp_x5_x6 = const offset_of!(SigArea, tmp_x5_x6), + sa_tmp_x7_x8 = const offset_of!(SigArea, tmp_x7_x8), + sa_tmp_sp = const offset_of!(SigArea, tmp_sp), + sa_tmp_rt_inf = const offset_of!(SigArea, tmp_rt_inf), + sa_tmp_id_inf = const offset_of!(SigArea, tmp_id_inf), + sa_pctl = const offset_of!(SigArea, pctl), + sc_saved_pc = const offset_of!(Sigcontrol, saved_ip), + sc_saved_x0 = const offset_of!(Sigcontrol, saved_archdep_reg), + sc_sender_infos = const offset_of!(Sigcontrol, sender_infos), + sc_word = const offset_of!(Sigcontrol, word), + sc_flags = const offset_of!(Sigcontrol, control_flags), + proc_fd = sym PROC_FD, + inner = sym inner_c, + proc_call = sym PROC_CALL, + + SA_ONSTACK_BIT = const 58, // (1 << 58) >> 32 = 0x0400_0000 + + SYS_CALL = const syscall::SYS_CALL, + + STACK_ALIGN = const 16, + REDZONE_SIZE = const 128, + RTINF_SIZE = const size_of::(), +]); + +asmfunction!(__relibc_internal_rlct_clone_ret: [" + # Load registers + ldp x8, x0, [sp], #16 + ldp x1, x2, [sp], #16 + ldp x3, x4, [sp], #16 + + # Call entry point + blr x8 + + ret +"] <= []); + +pub fn current_sp() -> usize { + let sp: usize; + unsafe { + core::arch::asm!("mov {}, sp", out(reg) sp); + } + sp +} + +pub unsafe fn manually_enter_trampoline() { + let ctl = unsafe { &Tcb::current().unwrap().os_specific.control }; + + ctl.saved_archdep_reg.set(0); + let ip_location = &ctl.saved_ip as *const _ as usize; + + unsafe { + core::arch::asm!(" + bl 2f + b 3f + 2: + str lr, [x0] + b __relibc_internal_sigentry + 3: + ", + inout("x0") ip_location => _, out("lr") _); + } +} + +pub unsafe fn arch_pre(stack: &mut SigStack, os: &mut SigArea) -> PosixStackt { + PosixStackt { + sp: core::ptr::null_mut(), // TODO + size: 0, // TODO + flags: 0, // TODO + } +} +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_pc = core::mem::replace(&mut stack.regs.pc, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_pc); + control.saved_archdep_reg.set(stack.regs.x0); +} +pub(crate) static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); +static PROC_CALL: [usize; 1] = [ProcCall::Sigdeq as usize]; diff --git a/redox-rt/src/arch/i686.rs b/redox-rt/src/arch/i686.rs new file mode 100644 index 0000000000..6461fe5f87 --- /dev/null +++ b/redox-rt/src/arch/i686.rs @@ -0,0 +1,411 @@ +use core::{cell::SyncUnsafeCell, mem::offset_of, ptr::NonNull, sync::atomic::Ordering}; + +use syscall::*; + +use crate::{ + proc::{FdGuard, FdGuardUpper, ForkArgs, fork_inner}, + signal::{PROC_CONTROL_STRUCT, PosixStackt, RtSigarea, SigStack, inner_fastcall}, +}; +use redox_protocols::protocol::{ProcCall, RtSigInfo}; + +use super::ForkScratchpad; + +// Setup a stack starting from the very end of the address space, and then growing downwards. +pub const STACK_TOP: usize = 1 << 31; +pub const STACK_SIZE: usize = 1024 * 1024; + +#[derive(Debug, Default)] +#[repr(C)] +pub struct SigArea { + pub altstack_top: usize, + pub altstack_bottom: usize, + pub tmp_eip: usize, + pub tmp_esp: usize, + pub tmp_eax: usize, + pub tmp_ebx: usize, + pub tmp_ecx: usize, + pub tmp_edx: usize, + pub tmp_edi: usize, + pub tmp_esi: usize, + pub tmp_rt_inf: RtSigInfo, + pub tmp_signo: usize, + pub tmp_id_inf: u64, + pub tmp_mm0: u64, + pub disable_signals_depth: u64, + pub last_sig_was_restart: bool, + pub last_sigstack: Option>, +} +#[derive(Debug, Default)] +#[repr(C, align(16))] +pub struct ArchIntRegs { + pub fxsave: [u16; 29], + + // ensure fxsave region is 16 byte aligned + pub _pad: [usize; 2], // fxsave "available" +0 + + pub ebp: usize, // fxsave "available" +8 + pub esi: usize, // avail +12 + pub edi: usize, // avail +16 + pub ebx: usize, // avail +20 + pub eax: usize, // avail +24 + pub ecx: usize, // avail +28 + pub edx: usize, // avail +32 + + pub eflags: usize, // avail +36 + pub eip: usize, // avail +40 + pub esp: usize, // avail +44 +} + +/// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS +/// is already initialized as if it was a thread. +pub unsafe fn deactivate_tcb(open_via_dup: &FdGuardUpper) -> Result<()> { + let mut env = syscall::EnvRegisters::default(); + + let file = open_via_dup.dup(b"regs/env")?; + + env.fsbase = 0; + env.gsbase = 0; + + file.write(&mut env)?; + Ok(()) +} + +unsafe extern "fastcall" fn fork_impl(args: &ForkArgs, initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp, args)) +} + +// TODO: duplicate code with x86_64 +unsafe extern "cdecl" fn child_hook(scratchpad: ForkScratchpad) { + let _ = syscall::close(scratchpad.cur_filetable_fd); + unsafe { + crate::child_hook_common(crate::ChildHookCommonArgs { + new_thr_fd: FdGuard::new(scratchpad.new_thr_fd), + new_proc_fd: if scratchpad.new_proc_fd == usize::MAX { + None + } else { + Some(FdGuard::new(scratchpad.new_proc_fd)) + }, + }) + }; +} + +asmfunction!(__relibc_internal_fork_wrapper (usize) -> usize: [" + mov ecx, [esp+4] + + push ebp + mov ebp, esp + + // Push preserved registers + push ebx + push edi + push esi + push ebp + + sub esp, 32 + + //TODO stmxcsr [esp+16] + fnstcw [esp+24] + + mov edx, esp + call {fork_impl} + + jmp 2f +"] <= [fork_impl = sym fork_impl]); + +asmfunction!(__relibc_internal_fork_ret: [" + // Arguments already on the stack + call {child_hook} + + //TODO ldmxcsr [esp+16] + fldcw [esp+24] + + xor eax, eax + + .p2align 4 +2: + add esp, 32 + + // Pop preserved registers + pop ebp + pop esi + pop edi + pop ebx + + pop ebp + + ret +"] <= [child_hook = sym child_hook]); +asmfunction!(__relibc_internal_sigentry: [" + // Save some registers + mov gs:[{tcb_sa_off} + {sa_tmp_esp}], esp + mov gs:[{tcb_sa_off} + {sa_tmp_eax}], eax + mov gs:[{tcb_sa_off} + {sa_tmp_edx}], edx + mov gs:[{tcb_sa_off} + {sa_tmp_ecx}], ecx + mov gs:[{tcb_sa_off} + {sa_tmp_ebx}], ebx + mov gs:[{tcb_sa_off} + {sa_tmp_edi}], edi + mov gs:[{tcb_sa_off} + {sa_tmp_esi}], esi +1: + // Read standard signal word - first for this thread + mov edx, gs:[{tcb_sc_off} + {sc_word} + 4] + mov eax, gs:[{tcb_sc_off} + {sc_word}] + and eax, edx + bsf eax, eax + jnz 9f + + // Read standard signal word - for the process + lea ecx, [{pctl}] + mov eax, [ecx + {pctl_pending}] + and eax, edx + bsf eax, eax + jz 3f + + // Read si_pid and si_uid, atomically. + movq gs:[{tcb_sa_off} + {sa_tmp_mm0}], mm0 + movq mm0, [ecx + {pctl_sender_infos} + eax * 8] + movq gs:[{tcb_sa_off} + {sa_tmp_id_inf}], mm0 + movq mm0, gs:[{tcb_sa_off} + {sa_tmp_mm0}] + + // Try clearing the pending bit, otherwise retry if another thread did that first + lock btr [ecx + {pctl_pending}], eax + jnc 1b + jmp 2f +3: + // Read realtime thread and process signal word together + mov edx, [ecx + {pctl_pending} + 4] + mov eax, gs:[{tcb_sc_off} + {sc_word} + 8] + or eax, edx + and eax, gs:[{tcb_sc_off} + {sc_word} + 12] + bsf eax, eax + jz 7f // spurious signal + + // If thread rather than process was specifically targeted, send the signal to it first. + bt edx, eax + jnc 8f + + // SYS_CALL(fd, payload_base, payload_len, metadata_len, metadata_base) + // eax ebx ecx edx esi edi + + mov ebx, [{proc_fd}] + mov ecx, gs:[0] + add ecx, {tcb_sa_off} + {sa_tmp_rt_inf} + mov [ecx], eax + mov gs:[{tcb_sa_off} + {sa_tmp_signo}], eax + mov edx, {RTINF_SIZE} + mov esi, 1 + lea edi, [{proc_call}] + mov eax, {SYS_CALL} + int 0x80 + test eax, eax + jnz 1b + + mov eax, gs:[{tcb_sa_off} + {sa_tmp_signo}] + add eax, 32 + jmp 2f +8: + add eax, 32 +9: + // Read si_pid and si_uid, atomically. + movq gs:[{tcb_sa_off} + {sa_tmp_mm0}], mm0 + movq mm0, gs:[{tcb_sc_off} + {sc_sender_infos} + eax * 8] + movq gs:[{tcb_sa_off} + {sa_tmp_id_inf}], mm0 + movq mm0, gs:[{tcb_sa_off} + {sa_tmp_mm0}] + mov edx, eax + shr edx, 5 + mov ecx, eax + and ecx, 31 + lock btr gs:[{tcb_sc_off} + {sc_word} + edx * 8], ecx + + add eax, 64 +2: + and esp, -{STACK_ALIGN} + + mov edx, eax + add edx, edx + bt dword ptr [{pctl} + {pctl_actions} + edx * 8 + 4], 28 + jnc 4f + + mov edx, gs:[{tcb_sa_off} + {sa_altstack_top}] + cmp esp, edx + ja 3f + + cmp esp, gs:[{tcb_sa_off} + {sa_altstack_bottom}] + jnbe 4f +3: + mov esp, edx +4: + // Now that we have a stack, we can finally start populating the signal stack. + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_esp}] + push dword ptr gs:[{tcb_sc_off} + {sc_saved_eip}] + push dword ptr gs:[{tcb_sc_off} + {sc_saved_eflags}] + + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_edx}] + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_ecx}] + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_eax}] + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_ebx}] + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_edi}] + push dword ptr gs:[{tcb_sa_off} + {sa_tmp_esi}] + push ebp + + sub esp, 2 * 4 + 29 * 16 + fxsave [esp] + + mov [esp - 4], eax + sub esp, 48 + + mov ecx, esp + call {inner} + + fxrstor [esp + 48] + add esp, 48 + 29 * 16 + 2 * 4 + + pop ebp + pop esi + pop edi + pop ebx + pop eax + pop ecx + pop edx + + popfd + pop dword ptr gs:[{tcb_sa_off} + {sa_tmp_eip}] + + .globl __relibc_internal_sigentry_crit_first +__relibc_internal_sigentry_crit_first: + pop esp + + .globl __relibc_internal_sigentry_crit_second +__relibc_internal_sigentry_crit_second: + jmp dword ptr gs:[{tcb_sa_off} + {sa_tmp_eip}] +7: + mov eax, gs:[0] + lea esp, [eax + {tcb_sc_off} + {sc_saved_eflags}] + popfd + + mov esp, gs:[{tcb_sa_off} + {sa_tmp_esp}] + + mov eax, gs:[{tcb_sc_off} + {sc_saved_eip}] + mov gs:[{tcb_sa_off} + {sa_tmp_eip}], eax + + mov eax, gs:[{tcb_sa_off} + {sa_tmp_eax}] + mov ebx, gs:[{tcb_sa_off} + {sa_tmp_ebx}] + mov ecx, gs:[{tcb_sa_off} + {sa_tmp_ecx}] + mov edx, gs:[{tcb_sa_off} + {sa_tmp_edx}] + mov edi, gs:[{tcb_sa_off} + {sa_tmp_edi}] + mov esi, gs:[{tcb_sa_off} + {sa_tmp_esi}] + + and dword ptr gs:[{tcb_sc_off} + {sc_control}], ~1 + .globl __relibc_internal_sigentry_crit_third +__relibc_internal_sigentry_crit_third: + jmp dword ptr gs:[{tcb_sa_off} + {sa_tmp_eip}] +"] <= [ + inner = sym inner_fastcall, + sa_tmp_eip = const offset_of!(SigArea, tmp_eip), + sa_tmp_esp = const offset_of!(SigArea, tmp_esp), + sa_tmp_eax = const offset_of!(SigArea, tmp_eax), + sa_tmp_ebx = const offset_of!(SigArea, tmp_ebx), + sa_tmp_ecx = const offset_of!(SigArea, tmp_ecx), + sa_tmp_edx = const offset_of!(SigArea, tmp_edx), + sa_tmp_edi = const offset_of!(SigArea, tmp_edi), + sa_tmp_esi = const offset_of!(SigArea, tmp_esi), + sa_tmp_mm0 = const offset_of!(SigArea, tmp_mm0), + sa_tmp_rt_inf = const offset_of!(SigArea, tmp_rt_inf), + sa_tmp_id_inf = const offset_of!(SigArea, tmp_id_inf), + sa_tmp_signo = const offset_of!(SigArea, tmp_signo), + sa_altstack_top = const offset_of!(SigArea, altstack_top), + sa_altstack_bottom = const offset_of!(SigArea, altstack_bottom), + sc_control = const offset_of!(Sigcontrol, control_flags), + sc_saved_eflags = const offset_of!(Sigcontrol, saved_archdep_reg), + sc_saved_eip = const offset_of!(Sigcontrol, saved_ip), + sc_word = const offset_of!(Sigcontrol, word), + sc_sender_infos = const offset_of!(Sigcontrol, sender_infos), + tcb_sa_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, arch), + tcb_sc_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control), + pctl_actions = const offset_of!(SigProcControl, actions), + pctl_sender_infos = const offset_of!(SigProcControl, sender_infos), + pctl_pending = const offset_of!(SigProcControl, pending), + pctl = sym PROC_CONTROL_STRUCT, + proc_fd = sym PROC_FD, + proc_call = sym PROC_CALL, + STACK_ALIGN = const 16, + SYS_CALL = const syscall::SYS_CALL, + RTINF_SIZE = const size_of::(), +]); + +asmfunction!(__relibc_internal_rlct_clone_ret -> usize: [" + # Load registers + pop eax + + sub esp, 8 + + mov DWORD PTR [esp], 0x00001F80 + # TODO: ldmxcsr [esp] + mov WORD PTR [esp], 0x037F + fldcw [esp] + + add esp, 8 + + # Call entry point + call eax + + ret +"] <= []); +unsafe extern "C" { + fn __relibc_internal_sigentry_crit_first(); + fn __relibc_internal_sigentry_crit_second(); + fn __relibc_internal_sigentry_crit_third(); +} +pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt { + if stack.regs.eip == __relibc_internal_sigentry_crit_first as usize { + let stack_ptr = stack.regs.esp as *const usize; + stack.regs.esp = unsafe { stack_ptr.read() }; + stack.regs.eip = unsafe { stack_ptr.sub(1).read() }; + } else if stack.regs.eip == __relibc_internal_sigentry_crit_second as usize + || stack.regs.eip == __relibc_internal_sigentry_crit_third as usize + { + stack.regs.eip = area.tmp_eip; + } + PosixStackt { + sp: stack.regs.esp as *mut (), + size: 0, // TODO + flags: 0, // TODO + } +} +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_eip = core::mem::replace(&mut stack.regs.eip, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_eip); + control.saved_archdep_reg.set(stack.regs.eflags); +} +#[unsafe(no_mangle)] +pub unsafe fn manually_enter_trampoline() { + let c = unsafe { &crate::Tcb::current().unwrap().os_specific.control }; + c.control_flags.store( + c.control_flags.load(Ordering::Relaxed) | syscall::flag::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + c.saved_archdep_reg.set(0); // TODO: Just reset DF on x86? + + unsafe { + core::arch::asm!(" + call 2f + jmp 3f + 2: + pop dword ptr gs:[{tcb_sc_off} + {sc_saved_eip}] + jmp __relibc_internal_sigentry + 3: + ", + tcb_sc_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control), + sc_saved_eip = const offset_of!(Sigcontrol, saved_ip), + ); + } +} +/// Get current stack pointer, weak granularity guarantees. +pub fn current_sp() -> usize { + let sp: usize; + unsafe { + core::arch::asm!("mov {}, esp", out(reg) sp); + } + sp +} + +pub static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); +static PROC_CALL: u64 = ProcCall::Sigdeq as u64; diff --git a/redox-rt/src/arch/mod.rs b/redox-rt/src/arch/mod.rs new file mode 100644 index 0000000000..282f28ed5d --- /dev/null +++ b/redox-rt/src/arch/mod.rs @@ -0,0 +1,27 @@ +#[cfg(target_arch = "aarch64")] +pub use self::aarch64::*; +#[cfg(target_arch = "aarch64")] +pub mod aarch64; + +#[cfg(target_arch = "x86")] +pub use self::i686::*; +#[cfg(target_arch = "x86")] +pub mod i686; + +#[cfg(target_arch = "x86_64")] +pub use self::x86_64::*; +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +#[cfg(target_arch = "riscv64")] +pub use self::riscv64::*; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; + +#[derive(Debug)] +#[repr(C)] +pub struct ForkScratchpad { + pub cur_filetable_fd: usize, + pub new_proc_fd: usize, + pub new_thr_fd: usize, +} diff --git a/redox-rt/src/arch/riscv64.rs b/redox-rt/src/arch/riscv64.rs new file mode 100644 index 0000000000..a72cc32913 --- /dev/null +++ b/redox-rt/src/arch/riscv64.rs @@ -0,0 +1,654 @@ +use core::cell::SyncUnsafeCell; + +use crate::{ + Tcb, + proc::{FdGuard, FdGuardUpper, ForkArgs, fork_inner}, + signal::{PosixStackt, RtSigarea, SigStack, get_sigaltstack, inner_c}, +}; +use core::{mem::offset_of, ptr::NonNull, sync::atomic::Ordering}; +use redox_protocols::protocol::{ProcCall, RtSigInfo}; +use syscall::{data::*, error::*}; + +use super::ForkScratchpad; + +// Setup a stack starting from the very end of the address space, and then growing downwards. +pub const STACK_TOP: usize = 1 << 38; +pub const STACK_SIZE: usize = 1024 * 1024; + +#[derive(Debug, Default)] +#[repr(C)] +pub struct SigArea { + pub tmp_sp: u64, + pub tmp_t1: u64, + pub tmp_t2: u64, + pub tmp_t3: u64, + pub tmp_t4: u64, + pub tmp_a0: u64, + pub tmp_a1: u64, + pub tmp_a2: u64, + pub tmp_a3: u64, + pub tmp_a4: u64, + pub tmp_a7: u64, + + pub pctl: usize, // TODO: remove + pub tmp_ip: u64, + pub tmp_rt_inf: RtSigInfo, + pub tmp_id_inf: u64, + pub altstack_top: usize, + pub altstack_bottom: usize, + pub disable_signals_depth: u64, + pub last_sig_was_restart: bool, + pub last_sigstack: Option>, +} +#[repr(C)] +#[derive(Debug, Default)] +pub struct ArchIntRegs { + pub int_regs: [u64; 31], + pub pc: u64, + pub fp_regs: [u64; 32], + pub fcsr: u32, + _pad: u32, +} + +/// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS +/// is already initialized as if it was a thread. +pub unsafe fn deactivate_tcb(open_via_dup: &FdGuardUpper) -> Result<()> { + let mut env = syscall::EnvRegisters::default(); + + let file = open_via_dup.dup(b"regs/env")?; + + env.tp = 0; + + file.write(&mut env)?; + Ok(()) +} + +unsafe extern "C" fn fork_impl(args: &ForkArgs, initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp, args)) +} + +unsafe extern "C" fn child_hook(scratchpad: &ForkScratchpad) { + let _ = syscall::close(scratchpad.cur_filetable_fd); + unsafe { + crate::child_hook_common(crate::ChildHookCommonArgs { + new_thr_fd: FdGuard::new(scratchpad.new_thr_fd), + new_proc_fd: if scratchpad.new_proc_fd == usize::MAX { + None + } else { + Some(FdGuard::new(scratchpad.new_proc_fd)) + }, + }) + }; +} + +asmfunction!(__relibc_internal_fork_wrapper (usize) -> usize: [" + .attribute arch, \"rv64gc\" # rust bug 80608 + addi sp, sp, -200 + sd s0, 0(sp) + sd s1, 8(sp) + sd s2, 16(sp) + sd s3, 24(sp) + sd s4, 32(sp) + sd s5, 40(sp) + sd s6, 48(sp) + sd s7, 56(sp) + sd s8, 64(sp) + sd s9, 72(sp) + sd s10, 80(sp) + sd s11, 88(sp) + sd ra, 96(sp) + + fsd fs0, 104(sp) + fsd fs1, 112(sp) + fsd fs2, 120(sp) + fsd fs3, 128(sp) + fsd fs4, 136(sp) + fsd fs5, 144(sp) + fsd fs6, 152(sp) + fsd fs7, 160(sp) + fsd fs8, 168(sp) + fsd fs9, 176(sp) + fsd fs10, 184(sp) + fsd fs11, 192(sp) + + // a0 is forwarded from this function + mv a1, sp + jal {fork_impl} + + ld s0, 0(sp) + ld s1, 8(sp) + ld s2, 16(sp) + ld s3, 24(sp) + ld s4, 32(sp) + ld s5, 40(sp) + ld s6, 48(sp) + ld s7, 56(sp) + ld s8, 64(sp) + ld s9, 72(sp) + ld s10, 80(sp) + ld s11, 88(sp) + ld ra, 96(sp) + + fld fs0, 104(sp) + fld fs1, 112(sp) + fld fs2, 120(sp) + fld fs3, 128(sp) + fld fs4, 136(sp) + fld fs5, 144(sp) + fld fs6, 152(sp) + fld fs7, 160(sp) + fld fs8, 168(sp) + fld fs9, 176(sp) + fld fs10, 184(sp) + fld fs11, 192(sp) + + addi sp, sp, 200 + ret +"] <= [fork_impl = sym fork_impl]); + +asmfunction!(__relibc_internal_fork_ret: [" + .attribute arch, \"rv64gc\" # rust bug 80608 + + # scratchpad is in a1, move to a0 for child_hook + mv a0, a1 + + jal {child_hook} + + mv a0, zero + + ld s0, 0(sp) + ld s1, 8(sp) + ld s2, 16(sp) + ld s3, 24(sp) + ld s4, 32(sp) + ld s5, 40(sp) + ld s6, 48(sp) + ld s7, 56(sp) + ld s8, 64(sp) + ld s9, 72(sp) + ld s10, 80(sp) + ld s11, 88(sp) + ld ra, 96(sp) + + fld fs0, 104(sp) + fld fs1, 112(sp) + fld fs2, 120(sp) + fld fs3, 128(sp) + fld fs4, 136(sp) + fld fs5, 144(sp) + fld fs6, 152(sp) + fld fs7, 160(sp) + fld fs8, 168(sp) + fld fs9, 176(sp) + fld fs10, 184(sp) + fld fs11, 192(sp) + + addi sp, sp, 200 + ret +"] <= [child_hook = sym child_hook]); + +asmfunction!(__relibc_internal_sigentry: [" + .attribute arch, \"rv64gc\" # rust bug 80608 + // Save some registers + ld t0, -8(tp) // Tcb + sd sp, ({tcb_sa_off} + {sa_tmp_sp})(t0) + sd t1, ({tcb_sa_off} + {sa_tmp_t1})(t0) + sd t2, ({tcb_sa_off} + {sa_tmp_t2})(t0) + sd t3, ({tcb_sa_off} + {sa_tmp_t3})(t0) + sd t4, ({tcb_sa_off} + {sa_tmp_t4})(t0) + ld t4, ({tcb_sa_off} + {sa_off_pctl})(t0) + + // First, select signal, always pick first available bit +99: + // Read first signal word + ld t1, ({tcb_sc_off} + {sc_word})(t0) + srli t2, t1, 32 // bitset to low word + and t1, t1, t2 // masked bitset in low word + beqz t1, 3f + + // Found in first thread signal word + mv t3, x0 +2: andi t2, t1, 1 + bnez t2, 10f + addi t3, t3, 1 + srli t1, t1, 1 + j 2b + + // If no unblocked thread signal was found, check for process. + // This is competitive; we need to atomically check if *we* cleared the process-wide pending + // bit, otherwise restart. +3: lw t1, {pctl_off_pending}(t4) + and t1, t1, t2 + beqz t1, 3f + // Found in first process signal word + li t3, -1 +2: andi t2, t1, 1 + addi t3, t3, 1 + srli t1, t1, 1 + beqz t2, 2b + slli t1, t3, 3 // * 8 == size_of SenderInfo + add t1, t1, t4 + ld t1, {pctl_off_sender_infos}(t1) + sd t1, ({tcb_sa_off} + {sa_tmp_id_inf})(t0) + li t1, 1 + sll t1, t1, t3 + not t1, t1 + addi t2, t4, {pctl_off_pending} + amoand.w.aq t2, t1, (t2) + and t1, t1, t2 + bne t1, t2, 9f + +3: + // Read second signal word - both process and thread simultaneously. + // This must be done since POSIX requires low realtime signals to be picked first. + ld t1, ({tcb_sc_off} + {sc_word} + 8)(t0) + lw t2, ({pctl_off_pending} + 4)(t4) + or t4, t1, t2 + srli t2, t1, 32 + and t4, t2, t4 + beqz t4, 7f + li t3, -1 +2: andi t2, t4, 1 + addi t3, t3, 1 + srli t4, t4, 1 + beqz t2, 2b + li t2, 1 + sll t2, t2, t3 + and t1, t1, t2 + addi t3, t3, 32 + bnez t1, 10f // thread signal + + // otherwise, try (competitively) dequeueing realtime signal + + // SYS_CALL(fd, payload_base, payload_len, metadata_len, metadata_base) + // a7 a0 a1 a2 a3 a4 + + // TODO: This SYS_CALL invocation has not yet been tested due to toolchain issues. + + sd a0, ({tcb_sa_off} + {sa_tmp_a0})(t0) + sd a1, ({tcb_sa_off} + {sa_tmp_a1})(t0) + sd a2, ({tcb_sa_off} + {sa_tmp_a2})(t0) + sd a3, ({tcb_sa_off} + {sa_tmp_a3})(t0) + sd a4, ({tcb_sa_off} + {sa_tmp_a4})(t0) + sd a7, ({tcb_sa_off} + {sa_tmp_a7})(t0) + li a7, {SYS_CALL} + addi a2, t3, -32 + add a1, t0, {tcb_sa_off} + {sa_tmp_rt_inf} // out pointer of dequeued realtime sig + sd a2, (a1) + li a2, {RTINF_SIZE} + li a3, 1 +1337: + auipc a4, %pcrel_hi({proc_fd}) + addi a4, a4, %pcrel_lo(1337b) + ecall + ld a3, ({tcb_sa_off} + {sa_tmp_a3})(t0) + ld a4, ({tcb_sa_off} + {sa_tmp_a4})(t0) + bnez a0, 99b // assumes error can only be EAGAIN + j 9f + +10: // thread signal. t3 holds signal number + srli t1, t3, 5 + bnez t1, 2f // FIXME senderinfo? + sll t2, t3, 3 // * 8 == size_of SenderInfo + add t2, t2, t0 + ld t2, ({tcb_sc_off} + {sc_sender_infos})(t2) + sd t2, ({tcb_sa_off} + {sa_tmp_id_inf})(t0) +2: andi t4, t3, 31 + li t2, 1 + sll t2, t2, t4 + not t2, t2 + sll t1, t1, 3 + add t1, t1, t0 + addi t1, t1, {tcb_sc_off} + {sc_word} + amoand.w.aq x0, t2, (t1) + addi t3, t3, 64 // indicate signal was targeted at thread + +9: // process signal t3 holds signal number + + // By now we have selected a signal, stored in eax (6-bit). We now need to choose whether or + // not to switch to the alternate signal stack. If SA_ONSTACK is clear for this signal, then + // skip the sigaltstack logic. + ld t4, ({tcb_sa_off} + {sa_off_pctl})(t0) + andi t1, t3, 63 + slli t1, t1, 4 // * 16 == size_of RawAction + add t1, t1, t4 + ld t1, {pctl_off_actions}(t1) + slli t1, t1, 63-58 // SA_ONSTACK in sign bit + bgez t1, 3f + + // If current RSP is above altstack region, switch to altstack + ld t1, ({tcb_sa_off} + {sa_altstack_top})(t0) + bgtu sp, t1, 2f + ld t2, ({tcb_sa_off} + {sa_altstack_bottom})(t0) + bgtu sp, t3, 3f +2: mv sp, t1 +3: + // form mcontext on stack + addi sp, sp, -33 * 8 + fsd f0, (0 * 8)(sp) + fsd f1, (1 * 8)(sp) + fsd f2, (2 * 8)(sp) + fsd f3, (3 * 8)(sp) + fsd f4, (4 * 8)(sp) + fsd f5, (5 * 8)(sp) + fsd f6, (6 * 8)(sp) + fsd f7, (7 * 8)(sp) + fsd f8, (8 * 8)(sp) + fsd f9, (9 * 8)(sp) + fsd f10, (10 * 8)(sp) + fsd f11, (11 * 8)(sp) + fsd f12, (12 * 8)(sp) + fsd f13, (13 * 8)(sp) + fsd f14, (14 * 8)(sp) + fsd f15, (15 * 8)(sp) + fsd f16, (16 * 8)(sp) + fsd f17, (17 * 8)(sp) + fsd f18, (18 * 8)(sp) + fsd f19, (19 * 8)(sp) + fsd f20, (20 * 8)(sp) + fsd f21, (21 * 8)(sp) + fsd f22, (22 * 8)(sp) + fsd f23, (23 * 8)(sp) + fsd f24, (24 * 8)(sp) + fsd f25, (25 * 8)(sp) + fsd f26, (26 * 8)(sp) + fsd f27, (27 * 8)(sp) + fsd f28, (28 * 8)(sp) + fsd f29, (29 * 8)(sp) + fsd f30, (30 * 8)(sp) + fsd f31, (31 * 8)(sp) + csrr t1, fcsr + sw t1, (32 * 8)(sp) + + addi sp, sp, -32 * 8 + sd x1, 0(sp) + ld t1, ({tcb_sa_off} + {sa_tmp_sp})(t0) + sd t1, (1 * 8)(sp) // x2 is sp + sd x3, (2 * 8)(sp) + sd x4, (3 * 8)(sp) + ld t1, ({tcb_sc_off} + {sc_saved_t0})(t0) + sd t1, (4 * 8)(sp) // x5 is t0 + ld t1, ({tcb_sa_off} + {sa_tmp_t1})(t0) + sd t1, (5 * 8)(sp) // x6 is t1 + ld t1, ({tcb_sa_off} + {sa_tmp_t2})(t0) + sd t1, (6 * 8)(sp) // x7 is t2 + sd x8, (7 * 8)(sp) + sd x9, (8 * 8)(sp) + sd x10, (9 * 8)(sp) + sd x11, (10 * 8)(sp) + sd x12, (11 * 8)(sp) + sd x13, (12 * 8)(sp) + sd x14, (13 * 8)(sp) + sd x15, (14 * 8)(sp) + sd x16, (15 * 8)(sp) + sd x17, (16 * 8)(sp) + sd x18, (17 * 8)(sp) + sd x19, (18 * 8)(sp) + sd x20, (19 * 8)(sp) + sd x21, (20 * 8)(sp) + sd x22, (21 * 8)(sp) + sd x23, (22 * 8)(sp) + sd x24, (23 * 8)(sp) + sd x25, (24 * 8)(sp) + sd x26, (25 * 8)(sp) + sd x27, (26 * 8)(sp) + ld t1, ({tcb_sa_off} + {sa_tmp_t3})(t0) + sd t1, (27 * 8)(sp) // t3 is x28 + ld t1, ({tcb_sa_off} + {sa_tmp_t4})(t0) + sd t1, (28 * 8)(sp) // t4 is x29 + sd x30, (29 * 8)(sp) + sd x31, (30 * 8)(sp) + ld t1, ({tcb_sc_off} + {sc_saved_ip})(t0) + sd t1, (31 * 8)(sp) + + // form ucontext + addi sp, sp, -64 + sw t3, 60(sp) + + mv t0, sp + jal {inner} + + addi sp, sp, 64 + + addi t0, sp, 32 * 8 + fld f0, (0 * 8)(t0) + fld f1, (1 * 8)(t0) + fld f2, (2 * 8)(t0) + fld f3, (3 * 8)(t0) + fld f4, (4 * 8)(t0) + fld f5, (5 * 8)(t0) + fld f6, (6 * 8)(t0) + fld f7, (7 * 8)(t0) + fld f8, (8 * 8)(t0) + fld f9, (9 * 8)(t0) + fld f10, (10 * 8)(t0) + fld f11, (11 * 8)(t0) + fld f12, (12 * 8)(t0) + fld f13, (13 * 8)(t0) + fld f14, (14 * 8)(t0) + fld f15, (15 * 8)(t0) + fld f16, (16 * 8)(t0) + fld f17, (17 * 8)(t0) + fld f18, (18 * 8)(t0) + fld f19, (19 * 8)(t0) + fld f20, (20 * 8)(t0) + fld f21, (21 * 8)(t0) + fld f22, (22 * 8)(t0) + fld f23, (23 * 8)(t0) + fld f24, (24 * 8)(t0) + fld f25, (25 * 8)(t0) + fld f26, (26 * 8)(t0) + fld f27, (27 * 8)(t0) + fld f28, (28 * 8)(t0) + fld f29, (29 * 8)(t0) + fld f30, (30 * 8)(t0) + fld f31, (31 * 8)(t0) + lw t1, (32 * 8)(t0) + csrw fcsr, t1 + + ld x1, 0(sp) + // skip sp + // skip gp + ld x4, (3 * 8)(sp) + ld x5, (4 * 8)(sp) + ld x6, (5 * 8)(sp) + ld x7, (6 * 8)(sp) + ld x8, (7 * 8)(sp) + ld x9, (8 * 8)(sp) + ld x10, (9 * 8)(sp) + ld x11, (10 * 8)(sp) + ld x12, (11 * 8)(sp) + ld x13, (12 * 8)(sp) + ld x14, (13 * 8)(sp) + ld x15, (14 * 8)(sp) + ld x16, (15 * 8)(sp) + ld x17, (16 * 8)(sp) + ld x18, (17 * 8)(sp) + ld x19, (18 * 8)(sp) + ld x20, (19 * 8)(sp) + ld x21, (20 * 8)(sp) + ld x22, (21 * 8)(sp) + ld x23, (22 * 8)(sp) + ld x24, (23 * 8)(sp) + ld x25, (24 * 8)(sp) + ld x26, (25 * 8)(sp) + ld x27, (26 * 8)(sp) + ld x28, (27 * 8)(sp) + ld x29, (28 * 8)(sp) + ld x30, (29 * 8)(sp) + ld x31, (30 * 8)(sp) + ld gp, (31 * 8)(sp) // new IP; this clobbers register x3/gp which is ABI reserved + .global __relibc_internal_sigentry_crit_first +__relibc_internal_sigentry_crit_first: + ld sp, (1 * 8)(sp) + .global __relibc_internal_sigentry_crit_second +__relibc_internal_sigentry_crit_second: + jr gp +7: + // A spurious signal occurred. Signals are still disabled here, but will need to be re-enabled. + + // restore stack + ld sp, ({tcb_sa_off} + {sa_tmp_sp})(t0) + + // move saved IP away from control, allowing arch_pre to save us if interrupted. + ld t1, ({tcb_sc_off} + {sc_saved_ip})(t0) + sd t1, ({tcb_sa_off} + {sa_tmp_ip})(t0) + + // restore regs + ld t2, ({tcb_sa_off} + {sa_tmp_t2})(t0) + ld t3, ({tcb_sa_off} + {sa_tmp_t3})(t0) + ld t4, ({tcb_sa_off} + {sa_tmp_t4})(t0) + + // move saved t0 away from control as well + mv t1, t0 + ld t0, ({tcb_sc_off} + {sc_saved_t0})(t0) + + // Re-enable signals. This code can be interrupted after this signal, so we need to define + // 'crit_third'. + ld gp, ({tcb_sc_off} + {sc_control})(t1) + andi gp, gp, ~1 + sd gp, ({tcb_sc_off} + {sc_control})(t1) + + .globl __relibc_internal_sigentry_crit_third +__relibc_internal_sigentry_crit_third: + ld gp, ({tcb_sa_off} + {sa_tmp_ip})(t1) + .globl __relibc_internal_sigentry_crit_fourth +__relibc_internal_sigentry_crit_fourth: + ld t1, ({tcb_sa_off} + {sa_tmp_t1})(t1) + .globl __relibc_internal_sigentry_crit_fifth +__relibc_internal_sigentry_crit_fifth: + jr gp + "] <= [ + tcb_sc_off = const (offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control)), + sc_word = const offset_of!(Sigcontrol, word), + sc_saved_t0 = const offset_of!(Sigcontrol, saved_archdep_reg), + sc_saved_ip = const offset_of!(Sigcontrol, saved_ip), + sc_sender_infos = const offset_of!(Sigcontrol, sender_infos), + sc_control = const offset_of!(Sigcontrol, control_flags), + + tcb_sa_off = const (offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, arch)), + sa_off_pctl = const offset_of!(SigArea, pctl), + sa_tmp_sp = const offset_of!(SigArea, tmp_sp), + sa_tmp_t1 = const offset_of!(SigArea, tmp_t1), + sa_tmp_t2 = const offset_of!(SigArea, tmp_t2), + sa_tmp_t3 = const offset_of!(SigArea, tmp_t3), + sa_tmp_t4 = const offset_of!(SigArea, tmp_t4), + sa_tmp_a0 = const offset_of!(SigArea, tmp_a0), + sa_tmp_a1 = const offset_of!(SigArea, tmp_a1), + sa_tmp_a2 = const offset_of!(SigArea, tmp_a2), + sa_tmp_a3 = const offset_of!(SigArea, tmp_a3), + sa_tmp_a4 = const offset_of!(SigArea, tmp_a4), + sa_tmp_a7 = const offset_of!(SigArea, tmp_a7), + sa_tmp_ip = const offset_of!(SigArea, tmp_ip), + sa_tmp_id_inf = const offset_of!(SigArea, tmp_id_inf), + sa_tmp_rt_inf = const offset_of!(SigArea, tmp_rt_inf), + sa_altstack_top = const offset_of!(SigArea, altstack_top), + sa_altstack_bottom = const offset_of!(SigArea, altstack_bottom), + + pctl_off_actions = const offset_of!(SigProcControl, actions), + inner = sym inner_c, + pctl_off_pending = const offset_of!(SigProcControl, pending), + pctl_off_sender_infos = const offset_of!(SigProcControl, sender_infos), + SYS_CALL = const syscall::SYS_CALL, + RTINF_SIZE = const size_of::(), + proc_fd = sym PROC_FD, +]); + +asmfunction!(__relibc_internal_rlct_clone_ret: [" + ld t0, 0(sp) + ld a0, 8(sp) + ld a1, 16(sp) + ld a2, 24(sp) + ld a3, 32(sp) + ld a4, 40(sp) + ld a5, 48(sp) + addi sp, sp, 56 + + jalr t0 + ret +"] <= []); + +pub fn current_sp() -> usize { + let sp: usize; + unsafe { + core::arch::asm!( + "mv {}, sp", + out(reg) sp, + options(nomem)); + } + sp +} + +pub unsafe fn manually_enter_trampoline() { + let ctl = unsafe { &Tcb::current().unwrap().os_specific.control }; + + ctl.control_flags.store( + ctl.control_flags.load(Ordering::Relaxed) | syscall::flag::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + ctl.saved_archdep_reg.set(0); + let ip_location = &ctl.saved_ip as *const _ as usize; + + unsafe { + core::arch::asm!(" + jal 2f + j 3f + 2: + sd ra, 0(t0) + la t0, __relibc_internal_sigentry + jalr x0, t0 + 3: + ", + inout("t0") ip_location => _, out("ra") _); + } +} + +unsafe extern "C" { + fn __relibc_internal_sigentry_crit_first(); + fn __relibc_internal_sigentry_crit_second(); + fn __relibc_internal_sigentry_crit_third(); + fn __relibc_internal_sigentry_crit_fourth(); + fn __relibc_internal_sigentry_crit_fifth(); +} +pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt { + // It is impossible to update SP and PC atomically. Instead, we abuse the fact that + // signals are disabled in the prologue of the signal trampoline, which allows us to emulate + // atomicity inside the critical section, consisting of one instruction at 'crit_first', and + // one at 'crit_second', see asm. + + if stack.regs.pc == __relibc_internal_sigentry_crit_first as u64 { + // Reexecute 'ld sp, (1 * 8)(sp)' + let stack_ptr = stack.regs.int_regs[1] as *const u64; // x2 + stack.regs.int_regs[1] = unsafe { stack_ptr.add(1).read() }; + // and 'jr gp' steps. + stack.regs.pc = stack.regs.int_regs[2]; + } else if stack.regs.pc == __relibc_internal_sigentry_crit_second as u64 + || stack.regs.pc == __relibc_internal_sigentry_crit_fifth as u64 + { + // just reexecute the jump + stack.regs.pc = stack.regs.int_regs[2]; + } else if stack.regs.pc == __relibc_internal_sigentry_crit_third as u64 { + // ld gp, ({tcb_sa_off} + {sa_tmp_ip})(t1) + stack.regs.int_regs[2] = area.tmp_ip; + // ld t1, ({tcb_sa_off} + {sa_tmp_t1})(t1) + stack.regs.int_regs[5] = area.tmp_t1; + // j gp + stack.regs.pc = stack.regs.int_regs[2]; + } else if stack.regs.pc == __relibc_internal_sigentry_crit_fourth as u64 { + // ld t1, ({tcb_sa_off} + {sa_tmp_t1})(t1) + stack.regs.int_regs[5] = area.tmp_t1; + // jr gp + stack.regs.pc = stack.regs.int_regs[2]; + } + + get_sigaltstack(area, stack.regs.int_regs[1] as usize).into() +} +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_pc = core::mem::replace(&mut stack.regs.pc, __relibc_internal_sigentry as u64); + control.saved_ip.set(orig_pc as usize); + control + .saved_archdep_reg + .set(stack.regs.int_regs[4] as usize); // t0 +} +pub(crate) static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); +static PROC_CALL: [usize; 1] = [ProcCall::Sigdeq as usize]; diff --git a/redox-rt/src/arch/x86_64.rs b/redox-rt/src/arch/x86_64.rs new file mode 100644 index 0000000000..dc0c9ad1d0 --- /dev/null +++ b/redox-rt/src/arch/x86_64.rs @@ -0,0 +1,528 @@ +use core::{ + cell::SyncUnsafeCell, + mem::offset_of, + ptr::NonNull, + sync::atomic::{AtomicU8, Ordering}, +}; + +use syscall::{ + data::{SigProcControl, Sigcontrol}, + error::*, +}; + +use crate::{ + Tcb, + proc::{FdGuard, FdGuardUpper, ForkArgs, fork_inner}, + signal::{PROC_CONTROL_STRUCT, PosixStackt, RtSigarea, SigStack, get_sigaltstack, inner_c}, +}; +use redox_protocols::protocol::{ProcCall, RtSigInfo}; + +use super::ForkScratchpad; + +// Setup a stack starting from the very end of the address space, and then growing downwards. +pub const STACK_TOP: usize = 1 << 47; +pub const STACK_SIZE: usize = 8 * 1024 * 1024; + +#[derive(Debug, Default)] +#[repr(C)] +pub struct SigArea { + pub tmp_rip: usize, + pub tmp_rsp: usize, + pub tmp_rax: usize, + pub tmp_rdx: usize, + pub tmp_rdi: usize, + pub tmp_rsi: usize, + pub tmp_r8: usize, + pub tmp_r10: usize, + pub tmp_r12: usize, + pub tmp_rt_inf: RtSigInfo, + pub tmp_id_inf: u64, + + pub altstack_top: usize, + pub altstack_bottom: usize, + pub disable_signals_depth: u64, + pub last_sig_was_restart: bool, + pub last_sigstack: Option>, +} + +#[repr(C, align(16))] +#[derive(Debug, Default)] +pub struct ArchIntRegs { + pub ymm_upper: [u128; 16], + pub fxsave: [u128; 29], + pub r15: usize, // fxsave "available" +0 + pub r14: usize, // available +8 + pub r13: usize, // available +16 + pub r12: usize, // available +24 + pub rbp: usize, // available +32 + pub rbx: usize, // available +40 + pub r11: usize, // outside fxsave, and so on + pub r10: usize, + pub r9: usize, + pub r8: usize, + pub rax: usize, + pub rcx: usize, + pub rdx: usize, + pub rsi: usize, + pub rdi: usize, + pub rflags: usize, + pub rip: usize, + pub rsp: usize, +} + +/// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS +/// is already initialized as if it was a thread. +pub unsafe fn deactivate_tcb(open_via_dup: &FdGuardUpper) -> Result<()> { + let mut env = syscall::EnvRegisters::default(); + + let file = open_via_dup.dup(b"regs/env")?; + + env.fsbase = 0; + env.gsbase = 0; + + file.write(&mut env)?; + Ok(()) +} + +unsafe extern "sysv64" fn fork_impl(args: &ForkArgs, initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp, args)) +} + +#[allow(unsafe_op_in_unsafe_fn)] +unsafe extern "sysv64" fn child_hook(scratchpad: &ForkScratchpad) { + let _ = syscall::close(scratchpad.cur_filetable_fd); + crate::child_hook_common(crate::ChildHookCommonArgs { + new_thr_fd: FdGuard::new(scratchpad.new_thr_fd), + new_proc_fd: if scratchpad.new_proc_fd == usize::MAX { + None + } else { + Some(FdGuard::new(scratchpad.new_proc_fd)) + }, + }); +} + +asmfunction!(__relibc_internal_fork_wrapper (usize) -> usize: [" + push rbp + mov rbp, rsp + + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + sub rsp, 16 + + stmxcsr [rsp+16] + fnstcw [rsp+8] + + // rdi: &ForkArgs + // rsi: initial_rsp + mov rsi, rsp + call {fork_impl} + + add rsp, 64 + + pop rbp + ret + +"] <= [fork_impl = sym fork_impl]); +asmfunction!(__relibc_internal_fork_ret: [" + # scratchpad is in rsi, move to rdi for child_hook + mov rdi, rsi + + call {child_hook} + + ldmxcsr [rsp + 16] + mov rcx, [rsp + 8] + + xor rax, rax + + add rsp, 16 + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + pop rbp + ret +"] <= [child_hook = sym child_hook]); +asmfunction!(__relibc_internal_rlct_clone_ret: [" + # Load registers + pop rax + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 + + mov DWORD PTR [rsp - 8], 0x00001F80 + ldmxcsr [rsp - 8] + mov WORD PTR [rsp - 8], 0x037F + fldcw [rsp - 8] + + # Call entry point + call rax + + ret +"] <= []); + +asmfunction!(__relibc_internal_sigentry: [" + // Save some registers + mov fs:[{tcb_sa_off} + {sa_tmp_rsp}], rsp + mov fs:[{tcb_sa_off} + {sa_tmp_rax}], rax + mov fs:[{tcb_sa_off} + {sa_tmp_rdx}], rdx + mov fs:[{tcb_sa_off} + {sa_tmp_rdi}], rdi + mov fs:[{tcb_sa_off} + {sa_tmp_rsi}], rsi + mov fs:[{tcb_sa_off} + {sa_tmp_r8}], r8 + mov fs:[{tcb_sa_off} + {sa_tmp_r10}], r10 + mov fs:[{tcb_sa_off} + {sa_tmp_r12}], r12 + + // First, select signal, always pick first available bit +1: + // Read standard signal word - first targeting this thread + mov rax, fs:[{tcb_sc_off} + {sc_word}] + mov rdx, rax + shr rdx, 32 + and eax, edx + bsf eax, eax + jnz 2f + + // If no unblocked thread signal was found, check for process. + // This is competitive; we need to atomically check if *we* cleared the process-wide pending + // bit, otherwise restart. + mov eax, [rip + {pctl} + {pctl_off_pending}] + and eax, edx + bsf eax, eax + jz 8f + lea rdi, [rip + {pctl} + {pctl_off_sender_infos}] + mov rdi, [rdi + rax * 8] + lock btr [rip + {pctl} + {pctl_off_pending}], eax + mov fs:[{tcb_sa_off} + {sa_tmp_id_inf}], rdi + jc 9f +8: + // Read second signal word - both process and thread simultaneously. + // This must be done since POSIX requires low realtime signals to be picked first. + mov edx, fs:[{tcb_sc_off} + {sc_word} + 8] + mov eax, [rip + {pctl} + {pctl_off_pending} + 4] + or eax, edx + and eax, fs:[{tcb_sc_off} + {sc_word} + 12] + bsf eax, eax + jz 7f + + bt edx, eax // check if signal was sent to thread specifically + jnc 81f + // if so, continue as usual + add eax, 32 + jmp 2f +81: + // otherwise, try (competitively) dequeueing realtime signal + + // SYS_CALL(fd, payload_base, payload_len, metadata_len | (flags << 8), metadata_base) + // rax rdi rsi rdx r10 r8 + + mov r12d, eax + mov rsi, fs:[0] + mov rdi, [rip+{proc_fd}] + add rsi, {tcb_sa_off} + {sa_tmp_rt_inf} // out pointer of dequeued realtime sig + mov rdx, {RTINF_SIZE} + mov [rsi], eax + lea r8, [rip + {proc_call_sigdeq}] + mov r10, 1 + mov eax, {SYS_CALL} + syscall + test eax, eax + jnz 1b // assumes error can only be EAGAIN + lea eax, [r12d + 32] + jmp 9f +2: + mov edx, eax + shr edx, 5 + mov rdi, fs:[{tcb_sc_off} + {sc_sender_infos} + eax * 8] + lock btr fs:[{tcb_sc_off} + {sc_word} + edx * 4], eax + mov fs:[{tcb_sa_off} + {sa_tmp_id_inf}], rdi + add eax, 64 // indicate signal was targeted at thread +9: + sub rsp, {REDZONE_SIZE} + and rsp, -{STACK_ALIGN} + + // By now we have selected a signal, stored in eax (6-bit). We now need to choose whether or + // not to switch to the alternate signal stack. If SA_ONSTACK is clear for this signal, then + // skip the sigaltstack logic. + lea rdx, [rip + {pctl} + {pctl_off_actions}] + + mov ecx, eax + and ecx, 63 + + // LEA doesn't support 16x, so just do two x8s. + lea rdx, [rdx + 8 * rcx] + lea rdx, [rdx + 8 * rcx] + + bt qword ptr [rdx], {SA_ONSTACK_BIT} + jnc 4f + + // Otherwise, the altstack is already active. The sigaltstack being disabled, is equivalent + // to setting 'top' to usize::MAX and 'bottom' to 0. + + // If current RSP is above altstack region, switch to altstack + mov rdx, fs:[{tcb_sa_off} + {sa_altstack_top}] + cmp rsp, rdx + cmova rsp, rdx + + // If current RSP is below altstack region, also switch to altstack + cmp rsp, fs:[{tcb_sa_off} + {sa_altstack_bottom}] + cmovbe rsp, rdx + + .p2align 4 +4: + // Now that we have a stack, we can finally start initializing the signal stack! + + push fs:[{tcb_sa_off} + {sa_tmp_rsp}] + push fs:[{tcb_sc_off} + {sc_saved_rip}] + push fs:[{tcb_sc_off} + {sc_saved_rflags}] + + push fs:[{tcb_sa_off} + {sa_tmp_rdi}] + push fs:[{tcb_sa_off} + {sa_tmp_rsi}] + push fs:[{tcb_sa_off} + {sa_tmp_rdx}] + push rcx + push fs:[{tcb_sa_off} + {sa_tmp_rax}] + push fs:[{tcb_sa_off} + {sa_tmp_r8}] + push r9 + push fs:[{tcb_sa_off} + {sa_tmp_r10}] + push r11 + push rbx + push rbp + push fs:[{tcb_sa_off} + {sa_tmp_r12}] + push r13 + push r14 + push r15 + sub rsp, (29 + 16) * 16 // fxsave region minus available bytes + fxsave64 [rsp + 16 * 16] + + // TODO: self-modifying? + cmp byte ptr [rip + {supports_avx}], 0 + je 5f + + // Prefer vextractf128 over vextracti128 since the former only requires AVX version 1. + vextractf128 [rsp + 15 * 16], ymm0, 1 + vextractf128 [rsp + 14 * 16], ymm1, 1 + vextractf128 [rsp + 13 * 16], ymm2, 1 + vextractf128 [rsp + 12 * 16], ymm3, 1 + vextractf128 [rsp + 11 * 16], ymm4, 1 + vextractf128 [rsp + 10 * 16], ymm5, 1 + vextractf128 [rsp + 9 * 16], ymm6, 1 + vextractf128 [rsp + 8 * 16], ymm7, 1 + vextractf128 [rsp + 7 * 16], ymm8, 1 + vextractf128 [rsp + 6 * 16], ymm9, 1 + vextractf128 [rsp + 5 * 16], ymm10, 1 + vextractf128 [rsp + 4 * 16], ymm11, 1 + vextractf128 [rsp + 3 * 16], ymm12, 1 + vextractf128 [rsp + 2 * 16], ymm13, 1 + vextractf128 [rsp + 16], ymm14, 1 + vextractf128 [rsp], ymm15, 1 +5: + mov [rsp - 4], eax + sub rsp, 64 // alloc space for ucontext fields + + mov rdi, rsp + call {inner} + + add rsp, 64 + + fxrstor64 [rsp + 16 * 16] + + cmp byte ptr [rip + {supports_avx}], 0 + je 6f + + vinsertf128 ymm0, ymm0, [rsp + 15 * 16], 1 + vinsertf128 ymm1, ymm1, [rsp + 14 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 13 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 12 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 11 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 10 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 9 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 8 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 7 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 6 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 5 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 4 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 3 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 2 * 16], 1 + vinsertf128 ymm2, ymm2, [rsp + 16], 1 + vinsertf128 ymm2, ymm2, [rsp], 1 +6: + add rsp, (29 + 16) * 16 + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + pop r11 + pop r10 + pop r9 + pop r8 + pop rax + pop rcx + pop rdx + pop rsi + pop rdi + + popfq + pop qword ptr fs:[{tcb_sa_off} + {sa_tmp_rip}] + + // x86 lacks atomic instructions for setting both the stack and instruction pointer + // simultaneously, except the slow microcoded IRETQ instruction. Thus, we let the arch_pre + // function emulate atomicity between the pop rsp and indirect jump. + + .globl __relibc_internal_sigentry_crit_first +__relibc_internal_sigentry_crit_first: + + pop rsp + + .globl __relibc_internal_sigentry_crit_second +__relibc_internal_sigentry_crit_second: + jmp qword ptr fs:[{tcb_sa_off} + {sa_tmp_rip}] +7: + // A spurious signal occurred. Signals are still disabled here, but will need to be re-enabled. + + // restore flags + mov rax, fs:[0] // load FS base + // TODO: Use lahf/sahf rather than pushfq/popfq? + lea rsp, [rax + {tcb_sc_off} + {sc_saved_rflags}] + popfq + + // restore stack + mov rsp, fs:[{tcb_sa_off} + {sa_tmp_rsp}] + + // move saved RIP away from control, allowing arch_pre to save us if interrupted. + mov rax, fs:[{tcb_sc_off} + {sc_saved_rip}] + mov fs:[{tcb_sa_off} + {sa_tmp_rip}], rax + + // restore regs + mov rax, fs:[{tcb_sa_off} + {sa_tmp_rax}] + mov rdx, fs:[{tcb_sa_off} + {sa_tmp_rdx}] + + // Re-enable signals. This code can be interrupted after this signal, so we need to define + // 'crit_third'. + and qword ptr fs:[{tcb_sc_off} + {sc_control}], ~1 + + .globl __relibc_internal_sigentry_crit_third +__relibc_internal_sigentry_crit_third: + jmp qword ptr fs:[{tcb_sa_off} + {sa_tmp_rip}] +"] <= [ + inner = sym inner_c, + sa_tmp_rip = const offset_of!(SigArea, tmp_rip), + sa_tmp_rsp = const offset_of!(SigArea, tmp_rsp), + sa_tmp_rax = const offset_of!(SigArea, tmp_rax), + sa_tmp_rdx = const offset_of!(SigArea, tmp_rdx), + sa_tmp_rdi = const offset_of!(SigArea, tmp_rdi), + sa_tmp_rsi = const offset_of!(SigArea, tmp_rsi), + sa_tmp_r8 = const offset_of!(SigArea, tmp_r8), + sa_tmp_r10 = const offset_of!(SigArea, tmp_r10), + sa_tmp_r12 = const offset_of!(SigArea, tmp_r12), + sa_tmp_rt_inf = const offset_of!(SigArea, tmp_rt_inf), + sa_tmp_id_inf = const offset_of!(SigArea, tmp_id_inf), + sa_altstack_top = const offset_of!(SigArea, altstack_top), + sa_altstack_bottom = const offset_of!(SigArea, altstack_bottom), + sc_saved_rflags = const offset_of!(Sigcontrol, saved_archdep_reg), + sc_saved_rip = const offset_of!(Sigcontrol, saved_ip), + sc_word = const offset_of!(Sigcontrol, word), + sc_sender_infos = const offset_of!(Sigcontrol, sender_infos), + sc_control = const offset_of!(Sigcontrol, control_flags), + tcb_sa_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, arch), + tcb_sc_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control), + pctl_off_actions = const offset_of!(SigProcControl, actions), + pctl_off_pending = const offset_of!(SigProcControl, pending), + pctl_off_sender_infos = const offset_of!(SigProcControl, sender_infos), + pctl = sym PROC_CONTROL_STRUCT, + supports_avx = sym SUPPORTS_AVX, + REDZONE_SIZE = const 128, + STACK_ALIGN = const 16, + SA_ONSTACK_BIT = const 58, // (1 << 58) >> 32 = 0x0400_0000 + SYS_CALL = const syscall::SYS_CALL, + proc_call_sigdeq = sym PROC_CALL_SIGDEQ, + RTINF_SIZE = const size_of::(), + proc_fd = sym PROC_FD, +]); + +unsafe extern "C" { + fn __relibc_internal_sigentry_crit_first(); + fn __relibc_internal_sigentry_crit_second(); + fn __relibc_internal_sigentry_crit_third(); +} +/// Fixes some edge cases, and calculates the value for uc_stack. +pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt { + // It is impossible to update RSP and RIP atomically on x86_64, without using IRETQ, which is + // almost as slow as calling a SIGRETURN syscall would be. Instead, we abuse the fact that + // signals are disabled in the prologue of the signal trampoline, which allows us to emulate + // atomicity inside the critical section, consisting of one instruction at 'crit_first', one at + // 'crit_second', and one at 'crit_third', see asm. + + if stack.regs.rip == __relibc_internal_sigentry_crit_first as usize { + // Reexecute pop rsp and jump steps. This case needs to be different from the one below, + // since rsp has not been overwritten with the previous context's stack, just yet. At this + // point, we know [rsp+0] contains the saved RSP, and [rsp-8] contains the saved RIP. + let stack_ptr = stack.regs.rsp as *const usize; + stack.regs.rsp = unsafe { stack_ptr.read() }; + stack.regs.rip = unsafe { stack_ptr.sub(1).read() }; + } else if stack.regs.rip == __relibc_internal_sigentry_crit_second as usize + || stack.regs.rip == __relibc_internal_sigentry_crit_third as usize + { + // Almost finished, just reexecute the jump before tmp_rip is overwritten by this + // deeper-level signal. + stack.regs.rip = area.tmp_rip; + } + + get_sigaltstack(area, stack.regs.rsp).into() +} + +/// Rearrange the restore-stack and sigarea in a way that makes it look like a new signal was +/// immediately delivered after restoring the allowset to what it was prior to the original signal delivery. +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_rip = core::mem::replace(&mut stack.regs.rip, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_rip); + control.saved_archdep_reg.set(stack.regs.rflags); +} + +pub(crate) static SUPPORTS_AVX: AtomicU8 = AtomicU8::new(0); + +// __relibc will be prepended to the name, so no_mangle is fine +#[allow(unsafe_op_in_unsafe_fn)] +#[unsafe(no_mangle)] +pub unsafe fn manually_enter_trampoline() { + let c = &Tcb::current().unwrap().os_specific.control; + c.control_flags.store( + c.control_flags.load(Ordering::Relaxed) | syscall::flag::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + c.saved_archdep_reg.set(0); // TODO: Just reset DF on x86? + + core::arch::asm!(" + lea rax, [rip + 2f] + mov fs:[{tcb_sc_off} + {sc_saved_rip}], rax + jmp __relibc_internal_sigentry + 2: + ", + out("rax") _, + tcb_sc_off = const offset_of!(crate::Tcb, os_specific) + offset_of!(RtSigarea, control), + sc_saved_rip = const offset_of!(Sigcontrol, saved_ip), + ); +} + +/// Get current stack pointer, weak granularity guarantees. +pub fn current_sp() -> usize { + let sp: usize; + unsafe { + core::arch::asm!("mov {}, rsp", out(reg) sp); + } + sp +} + +static PROC_CALL_SIGDEQ: u64 = ProcCall::Sigdeq as u64; +pub(crate) static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); diff --git a/redox-rt/src/lib.rs b/redox-rt/src/lib.rs new file mode 100644 index 0000000000..12835a6a7a --- /dev/null +++ b/redox-rt/src/lib.rs @@ -0,0 +1,313 @@ +#![no_std] +#![allow(internal_features)] +#![deny(unsafe_op_in_unsafe_fn)] +#![feature(core_intrinsics, int_roundings, slice_ptr_get, sync_unsafe_cell)] +#![forbid(unreachable_patterns)] + +use core::cell::UnsafeCell; + +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +use generic_rt::ExpectTlsFree; // not used on aarch64 or riscv64 +use generic_rt::GenericTcb; +use redox_protocols::protocol::ProcMeta; +use syscall::Sigcontrol; + +use self::{ + proc::{FdGuard, FdGuardUpper, STATIC_PROC_INFO}, + sync::Mutex, +}; + +extern crate alloc; + +#[macro_export] +macro_rules! asmfunction( + ($name:ident $(($($arg:ty),*))? $(-> $ret:ty)? : [$($asmstmt:expr),*$(,)?] <= [$($decl:ident = $(sym $symname:ident)?$(const $constval:expr)?),*$(,)?]$(,)? ) => { + ::core::arch::global_asm!(concat!(" + .p2align 4 + .section .text.", stringify!($name), ", \"ax\", @progbits + .globl ", stringify!($name), " + .type ", stringify!($name), ", @function + ", stringify!($name), ": + ", $($asmstmt, "\n",)* " + .size ", stringify!($name), ", . - ", stringify!($name), " + "), $($decl = $(sym $symname)?$(const $constval)?),*); + + unsafe extern "C" { + pub fn $name($($(_: $arg),*)?) $(-> $ret)?; + } + } +); + +pub mod arch; +pub mod proc; + +// TODO: Replace auxvs with a non-stack-based interface, but keep getauxval for compatibility +#[path = "../../src/platform/auxv_defs.rs"] +pub mod auxv_defs; + +pub mod signal; +pub mod sync; +pub mod sys; +pub mod thread; + +#[derive(Debug, Default)] +pub struct RtTcb { + pub control: Sigcontrol, + pub arch: UnsafeCell, + pub thr_fd: UnsafeCell>, +} +impl RtTcb { + pub fn current() -> &'static Self { + unsafe { &Tcb::current().unwrap().os_specific } + } + pub fn thread_fd(&self) -> &FdGuardUpper { + unsafe { (&*self.thr_fd.get()).as_ref().unwrap() } + } +} + +pub type Tcb = GenericTcb; + +/// OS and architecture specific code to activate TLS - Redox aarch64 +#[allow(unsafe_op_in_unsafe_fn)] +#[cfg(target_arch = "aarch64")] +pub unsafe fn tcb_activate(_tcb: &RtTcb, tls_end: usize, tls_len: usize) { + // Uses ABI page + let abi_ptr = tls_end - tls_len - 16; + core::ptr::write(abi_ptr as *mut usize, tls_end); + core::arch::asm!( + "msr tpidr_el0, {}", + in(reg) abi_ptr, + ); +} + +/// OS and architecture specific code to activate TLS - Redox x86 +#[allow(unsafe_op_in_unsafe_fn)] +#[cfg(target_arch = "x86")] +pub unsafe fn tcb_activate(tcb: &RtTcb, tls_end: usize, _tls_len: usize) { + let mut env = syscall::EnvRegisters::default(); + + let file = tcb + .thread_fd() + .dup(b"regs/env") + .expect_notls("failed to open handle for process registers"); + + file.read(&mut env).expect_notls("failed to read gsbase"); + + env.gsbase = tls_end as u32; + + file.write(&env).expect_notls("failed to write gsbase"); +} + +/// OS and architecture specific code to activate TLS - Redox x86_64 +#[allow(unsafe_op_in_unsafe_fn)] +#[cfg(target_arch = "x86_64")] +pub unsafe fn tcb_activate(tcb: &RtTcb, tls_end_and_tcb_start: usize, _tls_len: usize) { + let mut env = syscall::EnvRegisters::default(); + + let file = tcb + .thread_fd() + .dup(b"regs/env") + .expect_notls("failed to open handle for process registers"); + + file.read(&mut env).expect_notls("failed to read fsbase"); + + env.fsbase = tls_end_and_tcb_start as u64; + + file.write(&env).expect_notls("failed to write fsbase"); +} + +/// OS and architecture specific code to activate TLS - Redox riscv64 +#[allow(unsafe_op_in_unsafe_fn)] +#[cfg(target_arch = "riscv64")] +pub unsafe fn tcb_activate(_tcb: &RtTcb, tls_end: usize, tls_len: usize) { + // tp points to static tls block + // FIXME limited to a single initial master + let tls_start = tls_end - tls_len; + let abi_ptr = tls_start - 8; + core::ptr::write(abi_ptr as *mut usize, tls_end); + core::arch::asm!( + "mv tp, {}", + in(reg) tls_start + ); +} + +/// Initialize redox-rt in situations where relibc is not used +#[allow(unsafe_op_in_unsafe_fn)] +#[cfg(not(feature = "proc"))] +pub unsafe fn initialize_freestanding(this_thr_fd: FdGuardUpper) -> &'static FdGuardUpper { + // TODO: This code is a hack! Integrate the ld_so TCB code into generic-rt, and then use that + // (this function will need pointers to the ELF structs normally passed in auxvs), so the TCB + // is initialized properly. + + // TODO: TLS + let page = { + &mut *(syscall::fmap( + !0, + &syscall::Map { + offset: 0, + size: syscall::PAGE_SIZE, + flags: syscall::MapFlags::PROT_READ + | syscall::MapFlags::PROT_WRITE + | syscall::MapFlags::MAP_PRIVATE, + address: 0, + }, + ) + .unwrap() as *mut Tcb) + }; + page.tcb_ptr = page; + page.tcb_len = syscall::PAGE_SIZE; + page.tls_end = (page as *mut Tcb).cast(); + + // Make sure to use ptr::write to prevent dropping the existing FdGuard + page.os_specific.thr_fd.get().write(Some(this_thr_fd)); + + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] + unsafe { + let tcb_addr = page as *mut Tcb as usize; + tcb_activate(&page.os_specific, tcb_addr, 0) + } + #[cfg(target_arch = "aarch64")] + unsafe { + let abi_ptr = core::ptr::addr_of_mut!(page.tcb_ptr); + core::arch::asm!("msr tpidr_el0, {}", in(reg) abi_ptr); + } + #[cfg(target_arch = "riscv64")] + unsafe { + let abi_ptr = core::ptr::addr_of_mut!(page.tcb_ptr) as usize; + core::arch::asm!("mv tp, {}", in(reg) (abi_ptr + 8)); + } + initialize(); + + (*page.os_specific.thr_fd.get()).as_ref().unwrap() +} +pub(crate) fn read_proc_meta(proc: &FdGuardUpper) -> syscall::Result { + let mut bytes = [0_u8; size_of::()]; + proc.read(&mut bytes)?; + Ok(*plain::from_bytes::(&bytes).unwrap()) +} +pub unsafe fn initialize( + #[cfg(feature = "proc")] proc_fd: FdGuardUpper, + #[cfg(feature = "proc")] ns_fd: Option, +) { + #[cfg(feature = "proc")] + let metadata = read_proc_meta(&proc_fd).unwrap(); + + #[cfg(not(feature = "proc"))] + // Bootstrap mode, don't associate proc fds with PIDs + let metadata = ProcMeta::default(); + + #[cfg(feature = "proc")] + { + unsafe { crate::arch::PROC_FD.get().write(proc_fd.as_raw_fd()) }; + } + + unsafe { + STATIC_PROC_INFO.get().write(StaticProcInfo { + pid: metadata.pid, + + #[cfg(feature = "proc")] + proc_fd: Some(proc_fd), + + #[cfg(not(feature = "proc"))] + proc_fd: None, + }) + }; + + #[cfg(feature = "proc")] + { + *DYNAMIC_PROC_INFO.lock() = DynamicProcInfo { + pgid: metadata.pgid, + ruid: metadata.ruid, + euid: metadata.euid, + suid: metadata.suid, + egid: metadata.egid, + rgid: metadata.rgid, + sgid: metadata.sgid, + ns_fd, + }; + } +} + +pub(crate) struct StaticProcInfo { + pid: u32, + proc_fd: Option, +} +pub struct DynamicProcInfo { + pub pgid: u32, + pub euid: u32, + pub suid: u32, + pub ruid: u32, + pub egid: u32, + pub rgid: u32, + pub sgid: u32, + pub ns_fd: Option, +} + +static DYNAMIC_PROC_INFO: Mutex = Mutex::new(DynamicProcInfo { + pgid: u32::MAX, + ruid: u32::MAX, + euid: u32::MAX, + suid: u32::MAX, + rgid: u32::MAX, + egid: u32::MAX, + sgid: u32::MAX, + ns_fd: None, +}); + +#[inline] +pub(crate) fn static_proc_info() -> &'static StaticProcInfo { + unsafe { &*STATIC_PROC_INFO.get() } +} +#[inline] +pub fn current_proc_fd() -> &'static FdGuardUpper { + let info = static_proc_info(); + info.proc_fd.as_ref().unwrap() +} +#[inline] +pub fn current_namespace_fd() -> syscall::Result { + DYNAMIC_PROC_INFO + .lock() + .ns_fd + .as_ref() + .map(|g| g.as_raw_fd()) + .ok_or(syscall::Error::new(syscall::ENOENT)) +} +struct ChildHookCommonArgs { + new_thr_fd: FdGuard, + new_proc_fd: Option, +} + +unsafe fn child_hook_common(args: ChildHookCommonArgs) { + let new_thr_fd = args.new_thr_fd.to_upper().unwrap(); + let new_proc_fd = args.new_proc_fd.map(|x| x.to_upper().unwrap()); + + // TODO: just pass PID to child rather than obtaining it via IPC? + #[cfg(feature = "proc")] + let metadata = read_proc_meta( + new_proc_fd + .as_ref() + .expect("must be present with proc feature"), + ) + .unwrap(); + + #[cfg(not(feature = "proc"))] + let metadata = ProcMeta::default(); + + if let Some(proc_fd) = &new_proc_fd { + unsafe { crate::arch::PROC_FD.get().write(proc_fd.as_raw_fd()) }; + } + + let old_proc_fd = unsafe { + STATIC_PROC_INFO + .get() + .replace(StaticProcInfo { + pid: metadata.pid, + proc_fd: new_proc_fd, + }) + .proc_fd + }; + drop(old_proc_fd); + + let old_thr_fd = unsafe { RtTcb::current().thr_fd.get().replace(Some(new_thr_fd)) }; + drop(old_thr_fd); +} diff --git a/redox-rt/src/proc.rs b/redox-rt/src/proc.rs new file mode 100644 index 0000000000..48cce3453d --- /dev/null +++ b/redox-rt/src/proc.rs @@ -0,0 +1,1190 @@ +use core::{cell::SyncUnsafeCell, cmp, fmt::Debug, ops::Range}; + +use crate::{ + DYNAMIC_PROC_INFO, RtTcb, StaticProcInfo, + arch::*, + auxv_defs::*, + read_proc_meta, + sys::{fstat, open, proc_call, thread_call}, +}; +use redox_protocols::protocol::{ProcCall, ThreadCall}; + +use alloc::{boxed::Box, vec}; + +use goblin::elf::header::ET_DYN; +//TODO: allow use of either 32-bit or 64-bit programs +#[cfg(target_pointer_width = "32")] +use goblin::elf32::{ + header::Header, + program_header::program_header32::{PF_R, PF_W, PF_X, PT_INTERP, PT_LOAD, ProgramHeader}, +}; +#[cfg(target_pointer_width = "64")] +use goblin::elf64::{ + header::Header, + program_header::program_header64::{PF_R, PF_W, PF_X, PT_INTERP, PT_LOAD, ProgramHeader}, +}; + +use syscall::{ + CallFlags, F_GETFD, GrantDesc, GrantFlags, MAP_FIXED_NOREPLACE, MAP_SHARED, Map, O_CLOEXEC, + PAGE_SIZE, PROT_EXEC, PROT_READ, PROT_WRITE, SetSighandlerData, + error::*, + flag::{MapFlags, SEEK_SET}, +}; + +pub enum FexecResult { + Interp { + path: Box<[u8]>, + interp_override: InterpOverride, + }, +} +pub struct InterpOverride { + phdrs_vaddr: usize, + at_entry: usize, + at_phnum: usize, + at_phent: usize, + name: Box<[u8]>, + min_mmap_addr: usize, + grants_fd: usize, +} + +pub struct ExtraInfo<'a> { + pub cwd: Option<&'a [u8]>, + // POSIX states that while sigactions are reset, ignored sigactions will remain ignored. + pub sigignmask: u64, + // POSIX also states that the sigprocmask must be preserved across execs. + pub sigprocmask: u64, + /// File mode creation mask (POSIX) + pub umask: u32, + /// Thread handle + pub thr_fd: usize, + /// Process handle + pub proc_fd: usize, + /// Namespace handle + pub ns_fd: Option, + /// CWD handle + pub cwd_fd: Option, +} + +pub fn fexec_impl( + image_file: FdGuardUpper, + thread_fd: &FdGuardUpper, + proc_fd: &FdGuardUpper, + path: &[u8], + args: &[&[u8]], + envs: &[&[u8]], + extrainfo: &ExtraInfo, + interp_override: Option, +) -> Result { + // Here, we do the minimum part of loading an application, which is what the kernel used to do. + // We load the executable into memory (albeit at different offsets in this executable), fix + // some misalignments, and then switch address space. + + let mut header_bytes = [0_u8; size_of::
()]; + pread_all(&image_file, 0, &mut header_bytes)?; + let header = Header::from_bytes(&header_bytes); + + let grants_fd = if let Some(interp) = interp_override.as_ref() { + FdGuard::new(interp.grants_fd).to_upper()? + } else { + let current_addrspace_fd = thread_fd.dup(b"addrspace")?; + current_addrspace_fd.dup(b"empty")?.to_upper()? + }; + + // Never allow more than 1 MiB of program headers. + const MAX_PH_SIZE: usize = 1024 * 1024; + let phentsize = u64::from(header.e_phentsize) as usize; + let phnum = u64::from(header.e_phnum) as usize; + let pheaders_size = phentsize + .saturating_mul(phnum) + .saturating_add(size_of::
()); + + if pheaders_size > MAX_PH_SIZE { + return Err(Error::new(E2BIG)); + } + let mut phs_raw = vec![0_u8; pheaders_size]; + phs_raw[..size_of::
()].copy_from_slice(&header_bytes); + let phs = &mut phs_raw[size_of::
()..]; + + // TODO: Remove clone, but this would require more as_refs and as_muts + let mut min_mmap_addr = interp_override + .as_ref() + .map(|interp| interp.min_mmap_addr) + .unwrap_or(PAGE_SIZE); + let mut update_min_mmap_addr = |addr: usize, size: usize| { + min_mmap_addr = cmp::max(min_mmap_addr, (addr + size).next_multiple_of(PAGE_SIZE)); + }; + + pread_all(&image_file, u64::from(header.e_phoff), phs).map_err(|_| Error::new(EIO))?; + + let mut span: Option> = None; + for ph_idx in 0..phnum { + let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; + let segment: &ProgramHeader = + plain::from_bytes(ph_bytes).map_err(|_| Error::new(EINVAL))?; + if segment.p_type != PT_LOAD { + continue; + } + + let voff = segment.p_vaddr as usize % PAGE_SIZE; + let vaddr = segment.p_vaddr as usize - voff; + let vsize = (segment.p_memsz as usize + voff).next_multiple_of(segment.p_align as usize); + let b = vaddr..vaddr + vsize; + + span = Some(if let Some(a) = span { + a.start.min(b.start)..a.end.max(b.end) + } else { + b + }); + } + let span = span.expect("ELF executables must contain at least one `PT_LOAD` segment"); + let span_size = (span.end - span.start).next_multiple_of(PAGE_SIZE); + + let base_addr = if header.e_type == ET_DYN { + // PIE + let addr = mmap_anon_remote(&grants_fd, 0, 0, span_size, MapFlags::PROT_NONE)?; + update_min_mmap_addr(addr, span_size); + addr + } else { + mmap_anon_remote( + &grants_fd, + 0, + span.start, + span_size, + MapFlags::MAP_FIXED_NOREPLACE, + )?; + update_min_mmap_addr(span.start, span_size); + 0 + }; + + let mut phdrs_vaddr = 0; + let mut interpreter = None; + + for ph_idx in 0..phnum { + let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; + let segment: &ProgramHeader = + plain::from_bytes(ph_bytes).map_err(|_| Error::new(EINVAL))?; + let mut flags = if segment.p_flags & PF_R == PF_R { + syscall::PROT_READ + } else { + syscall::PROT_NONE + }; + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if segment.p_flags & PF_X == PF_X { + flags |= syscall::PROT_EXEC; + } else if segment.p_flags & PF_W == PF_W { + flags |= syscall::PROT_WRITE; + } + + match segment.p_type { + // PT_INTERP must come before any PT_LOAD, so we don't have to iterate twice. + PT_INTERP => { + let mut interp = vec![0_u8; segment.p_filesz as usize]; + pread_all(&image_file, u64::from(segment.p_offset), &mut interp)?; + + interpreter = Some(interp.into_boxed_slice()); + } + PT_LOAD => { + let voff = segment.p_vaddr as usize % PAGE_SIZE; + let vaddr = segment.p_vaddr as usize - voff; + let filesz = segment.p_filesz as usize; + + let total_page_count = (segment.p_memsz as usize + voff).div_ceil(PAGE_SIZE); + + // The case where segments overlap so that they share one page, is not handled. + // TODO: Should it be? + + if segment.p_filesz > segment.p_memsz { + return Err(Error::new(ENOEXEC)); + } + + mmap_anon_remote( + &grants_fd, + 0, + base_addr + vaddr, + total_page_count * PAGE_SIZE, + flags | MapFlags::MAP_FIXED, + )?; + + if segment.p_offset <= header.e_phoff + && header.e_phoff < segment.p_offset + segment.p_filesz + { + phdrs_vaddr = + (header.e_phoff - segment.p_offset + segment.p_vaddr) as usize + base_addr; + } + + // TODO: Attempt to mmap with MAP_PRIVATE directly from the image file instead. + + if filesz > 0 { + let (_guard, dst_memory) = unsafe { + MmapGuard::map_mut_anywhere( + &grants_fd, + base_addr + vaddr, // offset + (voff + filesz).next_multiple_of(PAGE_SIZE), // size + )? + }; + pread_all( + &image_file, + u64::from(segment.p_offset), + &mut dst_memory[voff..voff + filesz], + )?; + } + } + _ => continue, + } + } + + if let Some(interpreter_path) = interpreter { + return Ok(FexecResult::Interp { + path: interpreter_path, + interp_override: InterpOverride { + at_entry: base_addr + header.e_entry as usize, + at_phnum: phnum, + at_phent: phentsize, + phdrs_vaddr, + name: path.into(), + min_mmap_addr, + grants_fd: grants_fd.take(), + }, + }); + } + + mmap_anon_remote( + &grants_fd, + 0, + STACK_TOP - STACK_SIZE, + STACK_SIZE, + MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_FIXED_NOREPLACE, + )?; + + let mut sp = STACK_TOP; + let mut stack_page = Option::::None; + + let mut push = |word: usize| -> Result<()> { + let old_page_no = sp / PAGE_SIZE; + sp -= size_of::(); + let new_page_no = sp / PAGE_SIZE; + let new_page_off = sp % PAGE_SIZE; + + let page = if let Some(ref mut page) = stack_page + && old_page_no == new_page_no + { + page + } else if let Some(ref mut stack_page) = stack_page { + stack_page.remap(new_page_no * PAGE_SIZE, PROT_READ | PROT_WRITE)?; + stack_page + } else { + let new = MmapGuard::map( + &grants_fd, + &Map { + offset: new_page_no * PAGE_SIZE, + size: PAGE_SIZE, + flags: PROT_READ | PROT_WRITE, + address: 0, // let kernel decide + }, + )?; + + stack_page.insert(new) + }; + + unsafe { + page.as_mut_ptr_slice() + .as_mut_ptr() + .add(new_page_off) + .cast::() + .write(word); + } + + Ok(()) + }; + + push(0)?; + push(AT_NULL)?; + if let Some(ref r#override) = interp_override { + push(r#override.at_entry)?; + push(AT_ENTRY)?; + push(base_addr)?; + push(AT_BASE)?; + push(r#override.phdrs_vaddr)?; + push(AT_PHDR)?; + } else { + push(base_addr + header.e_entry as usize)?; + push(AT_ENTRY)?; + push(phdrs_vaddr)?; + push(AT_PHDR)?; + } + push( + interp_override + .as_ref() + .map_or(header.e_phnum as usize, |o| o.at_phnum), + )?; + push(AT_PHNUM)?; + push( + interp_override + .as_ref() + .map_or(header.e_phentsize as usize, |o| o.at_phent), + )?; + push(AT_PHENT)?; + + let total_args_envs_auxvpointee_size = args.iter().map(|arg| arg.len() + 1).sum::() + + envs.iter().map(|env| env.len() + 1).sum::() + + extrainfo.cwd.map_or(0, |s| s.len() + 1); + let args_envs_size_aligned = total_args_envs_auxvpointee_size.next_multiple_of(PAGE_SIZE); + let target_args_env_address = mmap_anon_remote( + &grants_fd, + 0, + 0, + args_envs_size_aligned, + MapFlags::PROT_READ | MapFlags::PROT_WRITE, + )?; + update_min_mmap_addr(target_args_env_address, args_envs_size_aligned); + + let mut offset = 0; + + let mut argc = 0; + + { + let mut append = |source_slice: &[u8]| -> Result { + // TODO + let address = target_args_env_address + offset; + + if !source_slice.is_empty() { + let containing_page = address.div_floor(PAGE_SIZE) * PAGE_SIZE; + let displacement = address - containing_page; + let size = source_slice.len() + displacement; + let aligned_size = size.next_multiple_of(PAGE_SIZE); + + let (_guard, memory) = unsafe { + MmapGuard::map_mut_anywhere(&grants_fd, containing_page, aligned_size)? + }; + memory[displacement..][..source_slice.len()].copy_from_slice(source_slice); + } + + offset += source_slice.len() + 1; + Ok(address) + }; + + if let Some(cwd) = extrainfo.cwd { + push(append(cwd)?)?; + push(AT_REDOX_INITIAL_CWD_PTR)?; + push(cwd.len())?; + push(AT_REDOX_INITIAL_CWD_LEN)?; + } + + #[cfg(target_pointer_width = "32")] + { + push((extrainfo.sigignmask >> 32) as usize)?; + push(AT_REDOX_INHERITED_SIGIGNMASK_HI)?; + } + push(extrainfo.sigignmask as usize)?; + push(AT_REDOX_INHERITED_SIGIGNMASK)?; + #[cfg(target_pointer_width = "32")] + { + push((extrainfo.sigprocmask >> 32) as usize)?; + push(AT_REDOX_INHERITED_SIGPROCMASK_HI)?; + } + push(extrainfo.sigprocmask as usize)?; + push(AT_REDOX_INHERITED_SIGPROCMASK)?; + + push(extrainfo.umask as usize)?; + push(AT_REDOX_UMASK)?; + + push(extrainfo.thr_fd as usize)?; + push(AT_REDOX_THR_FD)?; + push(extrainfo.proc_fd as usize)?; + push(AT_REDOX_PROC_FD)?; + push(extrainfo.ns_fd.unwrap_or(usize::MAX))?; + push(AT_REDOX_NS_FD)?; + push(extrainfo.cwd_fd.unwrap_or(usize::MAX))?; + push(AT_REDOX_CWD_FD)?; + + push(0)?; + + for env in envs.iter().rev() { + push(append(env)?)?; + } + + push(0)?; + + for arg in args.iter().rev() { + push(append(arg)?)?; + argc += 1; + } + } + + push(argc)?; + + if let Ok(sighandler_fd) = thread_fd.dup(b"sighandler") { + let _ = sighandler_fd.write(&SetSighandlerData { + user_handler: 0, + excp_handler: 0, + thread_control_addr: 0, + proc_control_addr: 0, + }); + // TODO: sync with procmgr + } + + // TODO: Restore old name if exec failed? + { + let mut buf = [0; 32]; + let new_name = interp_override.as_ref().map_or(path, |o| &o.name); + let len = new_name.len().min(32); + buf[..len].copy_from_slice(&new_name[..len]); + // XXX: takes &mut [] since it can mutate, but we could unsafe{}ly pass it directly + // otherwise + let _ = proc_call( + proc_fd.as_raw_fd(), + &mut buf, + CallFlags::empty(), + &[ProcCall::Rename as u64], + ); + } + + // TODO: Error handling + let _ = proc_call( + proc_fd.as_raw_fd(), + &mut [], + CallFlags::empty(), + &[ProcCall::DisableSetpgid as u64], + ); + + if interp_override.is_some() { + let mmap_min_fd = grants_fd.dup(b"mmap-min-addr")?; + let _ = mmap_min_fd.write(&usize::to_ne_bytes(min_mmap_addr)); + } + + let addrspace_selection_fd = thread_fd.dup(b"current-addrspace")?.to_upper()?; + + let _ = addrspace_selection_fd.write(&create_set_addr_space_buf( + grants_fd.as_raw_fd(), + base_addr + header.e_entry as usize, + sp, + )); + + // Close all O_CLOEXEC file descriptors. TODO: close_range? + { + // NOTE: This approach of implementing O_CLOEXEC will not work in multithreaded + // scenarios. While execve() is undefined according to POSIX if there exist sibling + // threads, it could still be allowed by keeping certain file descriptors and instead + // set the active file table. + let files_fd = syscall::dup(thread_fd.as_raw_fd(), b"filetable-binary")?; + let mut files_reader = FileBufReader::from_fd(files_fd); + loop { + let fd = match files_reader.read_le_u64()? { + None => break, + Some(fd) => fd, + }; + let fd = usize::try_from(fd).unwrap(); + + if fd == addrspace_selection_fd.as_raw_fd() || fd == files_fd { + continue; // Will be closed below + } + + let flags = syscall::fcntl(fd, F_GETFD, 0)?; + + if flags & O_CLOEXEC == O_CLOEXEC { + let _ = syscall::close(fd); + } + } + } + + unsafe { + deactivate_tcb(&thread_fd)?; + } + + // Dropping this FD will cause the address space switch. + drop(addrspace_selection_fd); + + unreachable!(); +} +fn write_usizes(fd: &FdGuardUpper, usizes: [usize; N]) -> Result { + fd.write(unsafe { plain::as_bytes(&usizes) }) +} +pub fn mmap_remote( + addrspace_fd: &FdGuardUpper, + fd: &FdGuardUpper, + offset: usize, + dst_addr: usize, + len: usize, + flags: MapFlags, +) -> Result { + write_usizes( + addrspace_fd, + [ + // op + syscall::flag::ADDRSPACE_OP_MMAP, + // fd + fd.as_raw_fd(), + // "offset" + offset, + // address + dst_addr, + // size + len, + // flags + flags.bits(), + ], + ) +} +pub fn mmap_anon_remote( + addrspace_fd: &FdGuardUpper, + offset: usize, + dst_addr: usize, + len: usize, + flags: MapFlags, +) -> Result { + write_usizes( + addrspace_fd, + [ + // op + syscall::flag::ADDRSPACE_OP_MMAP, + // fd + !0, + // "offset" + offset, + // address + dst_addr, + // size + len, + // flags + flags.bits(), + ], + ) +} +pub fn mprotect_remote( + addrspace_fd: &FdGuardUpper, + addr: usize, + len: usize, + flags: MapFlags, +) -> Result<()> { + write_usizes( + addrspace_fd, + [ + // op + syscall::flag::ADDRSPACE_OP_MPROTECT, + // address + addr, + // size + len, + // flags + flags.bits(), + ], + )?; + Ok(()) +} +pub fn munmap_remote(addrspace_fd: &FdGuardUpper, addr: usize, len: usize) -> Result<()> { + write_usizes( + addrspace_fd, + [ + // op + syscall::flag::ADDRSPACE_OP_MUNMAP, + // address + addr, + // size + len, + ], + )?; + Ok(()) +} +pub fn munmap_transfer( + src: &FdGuardUpper, + dst: &FdGuardUpper, + src_addr: usize, + dst_addr: usize, + len: usize, + flags: MapFlags, +) -> Result<()> { + write_usizes( + dst, + [ + // op + syscall::flag::ADDRSPACE_OP_TRANSFER, + // fd + src.as_raw_fd(), + // "offset" (source address) + src_addr, + // address + dst_addr, + // size + len, + // flags + (flags | MapFlags::MAP_FIXED_NOREPLACE).bits(), + ], + )?; + Ok(()) +} +fn pread_all(fd: &FdGuardUpper, offset: u64, buf: &mut [u8]) -> Result<()> { + fd.lseek(offset as isize, SEEK_SET)?; + + let mut total_bytes_read = 0; + + while total_bytes_read < buf.len() { + total_bytes_read += match fd.read(&mut buf[total_bytes_read..])? { + 0 => return Err(Error::new(ENOEXEC)), + bytes_read => bytes_read, + } + } + Ok(()) +} + +pub struct MmapGuard<'a> { + fd: &'a FdGuardUpper, + base: usize, + size: usize, +} +impl<'a> MmapGuard<'a> { + pub fn map(fd: &'a FdGuardUpper, map: &Map) -> Result { + let base = unsafe { syscall::fmap(fd.as_raw_fd(), map)? }; + Ok(Self { + fd, + size: map.size, + base, + }) + } + pub fn remap(&mut self, offset: usize, mut flags: MapFlags) -> Result<()> { + flags.remove(MapFlags::MAP_FIXED_NOREPLACE); + flags.insert(MapFlags::MAP_FIXED); + + let _new_base = unsafe { + syscall::fmap( + self.fd.as_raw_fd(), + &Map { + offset, + size: self.size, + flags, + address: self.base, + }, + )? + }; + + Ok(()) + } + pub unsafe fn map_mut_anywhere( + fd: &'a FdGuardUpper, + offset: usize, + size: usize, + ) -> Result<(Self, &'a mut [u8])> { + let mut this = Self::map( + fd, + &Map { + size, + offset, + address: 0, + flags: PROT_READ | PROT_WRITE, + }, + )?; + let slice = unsafe { &mut *this.as_mut_ptr_slice() }; + + Ok((this, slice)) + } + pub fn addr(&self) -> usize { + self.base + } + pub fn len(&self) -> usize { + self.size + } + pub fn as_mut_ptr_slice(&mut self) -> *mut [u8] { + core::ptr::slice_from_raw_parts_mut(self.base as *mut u8, self.size) + } + pub fn take(mut self) { + self.size = 0; + } +} +impl<'a> Drop for MmapGuard<'a> { + fn drop(&mut self) { + if self.size != 0 { + let _ = unsafe { syscall::funmap(self.base, self.size) }; + } + } +} + +struct FileBufReader { + fd: usize, + buf: [u8; 8192], + pos: usize, + cap: usize, +} + +impl FileBufReader { + pub fn from_fd(fd: usize) -> FileBufReader { + FileBufReader { + fd, + buf: [0; 8192], + pos: 0, + cap: 0, + } + } +} + +impl FileBufReader { + fn read_le_u64(&mut self) -> Result> { + if self.pos >= self.cap { + debug_assert!(self.pos == self.cap); + self.cap = crate::sys::posix_read(self.fd, &mut self.buf)?; + self.pos = 0; + } + + if self.cap == 0 { + return Ok(None); + } + + if self.cap - self.pos < 8 { + unreachable!(); + } + + let num = u64::from_le_bytes(self.buf[self.pos..self.pos + 8].try_into().unwrap()); + + self.pos += 8; + + Ok(Some(num)) + } +} + +#[repr(transparent)] +pub struct FdGuard { + fd: usize, +} +pub type FdGuardUpper = FdGuard; +impl FdGuard { + #[inline] + pub fn new(fd: usize) -> Self { + Self { fd } + } + + #[inline] + pub fn open>(path: T, flags: usize) -> Result { + open(path, flags).map(Self::new) + } + + #[inline] + pub fn to_upper(self) -> Result { + // Move to upper table if necessary + let fd = if self.fd & syscall::UPPER_FDTBL_TAG == 0 { + //TODO: use F_DUPFD_CLOEXEC? + let fd = syscall::fcntl(self.fd, syscall::F_DUPFD, syscall::UPPER_FDTBL_TAG)?; + drop(self); + fd + } else { + self.take() + }; + Ok(FdGuard:: { fd }) + } + + // Not implemented for UPPER to prevent misuse + #[inline] + pub fn as_c_fd(&self) -> Option { + i32::try_from(self.fd).ok() + } +} +impl FdGuard { + #[inline] + pub fn openat>( + &self, + path: T, + flags: usize, + fcntl_flags: usize, + ) -> Result> { + syscall::openat(self.fd, path, flags, fcntl_flags).map(FdGuard::new) + } + #[inline] + pub fn dup(&self, buf: &[u8]) -> Result> { + syscall::dup(self.fd, buf).map(FdGuard::new) + } + + #[inline] + pub fn fcntl(&self, cmd: usize, arg: usize) -> Result { + syscall::fcntl(self.fd, cmd, arg) + } + + #[inline] + pub fn fstat(&self, stat: &mut syscall::Stat) -> Result { + fstat(self.fd, stat) + } + + #[inline] + pub fn lseek(&self, offset: isize, whence: usize) -> Result { + syscall::lseek(self.fd, offset, whence) + } + + #[inline] + pub fn read(&self, buf: &mut [u8]) -> Result { + syscall::read(self.fd, buf) + } + + #[inline] + pub fn write(&self, buf: &[u8]) -> Result { + syscall::write(self.fd, buf) + } + + #[inline] + pub fn call_ro(&self, payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result { + syscall::call_ro(self.fd, payload, flags, metadata) + } + + #[inline] + pub fn call_wo(&self, payload: &[u8], flags: CallFlags, metadata: &[u64]) -> Result { + syscall::call_wo(self.fd, payload, flags, metadata) + } + + #[inline] + pub fn call_rw(&self, payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result { + syscall::call_rw(self.fd, payload, flags, metadata) + } + + #[inline] + pub fn as_raw_fd(&self) -> usize { + self.fd + } + + #[inline] + pub fn take(self) -> usize { + let fd = self.fd; + core::mem::forget(self); + fd + } +} +impl Drop for FdGuard { + #[inline] + fn drop(&mut self) { + let _ = syscall::close(self.fd); + } +} +impl Debug for FdGuard { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[fd {}]", self.fd) + } +} +impl Debug for FdGuardUpper { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "[fd upper {}]", self.fd & !syscall::UPPER_FDTBL_TAG) + } +} +pub fn create_set_addr_space_buf( + space: usize, + ip: usize, + sp: usize, +) -> [u8; size_of::() * 3] { + let mut buf = [0u8; size_of::() * 3]; + buf.copy_from_slice([space, sp, ip].map(usize::to_ne_bytes).as_flattened()); + buf +} +pub fn create_set_addr_space_buf_for_fork( + space: usize, + ip: usize, + sp: usize, + arg1: usize, +) -> [u8; size_of::() * 4] { + let mut buf = [0u8; size_of::() * 4]; + buf.copy_from_slice([space, sp, ip, arg1].map(usize::to_ne_bytes).as_flattened()); + buf +} + +/// Spawns a new context which will not share the same address space as the current one. File +/// descriptors from other schemes are reobtained with `dup`, and grants referencing such file +/// descriptors are reobtained through `fmap`. Other mappings are kept but duplicated using CoW. +pub fn fork_impl(args: &ForkArgs<'_>) -> Result { + let old_mask = crate::signal::get_sigmask()?; + let pid = unsafe { + Error::demux(__relibc_internal_fork_wrapper( + args as *const ForkArgs as usize, + ))? + }; + + if pid == 0 { + crate::signal::set_sigmask(Some(old_mask), None)?; + } + Ok(pid) +} + +pub enum ForkArgs<'a> { + Init { + this_thr_fd: &'a FdGuardUpper, + auth: &'a FdGuard, + }, + Managed, +} + +pub fn fork_inner(initial_rsp: *mut usize, args: &ForkArgs) -> Result { + let (cur_filetable_fd, new_proc_fd, new_thr_fd, new_pid); + + { + let cur_thr_fd = match args { + ForkArgs::Init { this_thr_fd, .. } => this_thr_fd, + ForkArgs::Managed => RtTcb::current().thread_fd(), + }; + let NewChildProc { + proc_fd, + thr_fd, + pid, + } = new_child_process(args)?; + new_proc_fd = proc_fd; + new_thr_fd = thr_fd; + new_pid = pid; + + // Copy existing files into new file table, but do not reuse the same file table (i.e. new + // parent FDs will not show up for the child). + let scratchpad = { + cur_filetable_fd = cur_thr_fd.dup(b"filetable")?; + + // This must be done before the address space is copied. + let proc_fd = new_proc_fd.as_ref().map_or(usize::MAX, |p| p.as_raw_fd()); + //let _ = syscall::write(1, alloc::format!("FDTBL{}PROC{}THR{}\n", *cur_filetable_fd, proc_fd, *new_thr_fd).as_bytes()); + + ForkScratchpad { + cur_filetable_fd: cur_filetable_fd.as_raw_fd(), + new_proc_fd: proc_fd, + new_thr_fd: new_thr_fd.as_raw_fd(), + } + }; + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" + ))] + let arg1 = { + let scratchpad_ptr: *const ForkScratchpad = &scratchpad; + scratchpad_ptr as usize + }; + #[cfg(target_arch = "x86")] + unsafe { + let scratchpad_ptr = initial_rsp as *mut ForkScratchpad; + scratchpad_ptr.write(scratchpad); + } + + // CoW-duplicate address space. + { + let new_addr_space_sel_fd = new_thr_fd.dup(b"current-addrspace")?; + + let cur_addr_space_fd = cur_thr_fd.dup(b"addrspace")?.to_upper()?; + let new_addr_space_fd = cur_addr_space_fd.dup(b"exclusive")?.to_upper()?; + + let mut grant_desc_buf = [GrantDesc::default(); 16]; + loop { + let bytes_read = { + let buf = unsafe { + core::slice::from_raw_parts_mut( + grant_desc_buf.as_mut_ptr().cast(), + grant_desc_buf.len() * size_of::(), + ) + }; + cur_addr_space_fd.read(buf)? + }; + if bytes_read == 0 { + break; + } + + let grants = &grant_desc_buf[..bytes_read / size_of::()]; + + for grant in grants { + if !grant.flags.contains(GrantFlags::GRANT_SCHEME) + || !grant.flags.contains(GrantFlags::GRANT_SHARED) + { + continue; + } + + let buf; + + // TODO: write! using some #![no_std] Cursor type (tracking the length)? + #[cfg(target_pointer_width = "64")] + { + //buf = *b"grant-fd-AAAABBBBCCCCDDDD"; + //write!(&mut buf, "grant-fd-{:>016x}", grant.base).unwrap(); + buf = alloc::format!("grant-fd-{:>016x}", grant.base).into_bytes(); + } + + #[cfg(target_pointer_width = "32")] + { + //buf = *b"grant-fd-AAAABBBB"; + //write!(&mut buf[..], "grant-fd-{:>08x}", grant.base).unwrap(); + buf = alloc::format!("grant-fd-{:>08x}", grant.base).into_bytes(); + } + + let grant_fd = cur_addr_space_fd.dup(&buf)?.to_upper()?; + + let mut flags = MAP_SHARED | MAP_FIXED_NOREPLACE; + + flags.set(PROT_READ, grant.flags.contains(GrantFlags::GRANT_READ)); + flags.set(PROT_WRITE, grant.flags.contains(GrantFlags::GRANT_WRITE)); + flags.set(PROT_EXEC, grant.flags.contains(GrantFlags::GRANT_EXEC)); + + mmap_remote( + &new_addr_space_fd, + &grant_fd, + grant.offset as usize, + grant.base, + grant.size, + flags, + )?; + } + } + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" + ))] + let buf = create_set_addr_space_buf_for_fork( + new_addr_space_fd.as_raw_fd(), + __relibc_internal_fork_ret as usize, + initial_rsp as usize, + arg1, + ); + #[cfg(target_arch = "x86")] + let buf = create_set_addr_space_buf( + new_addr_space_fd.as_raw_fd(), + __relibc_internal_fork_ret as usize, + initial_rsp as usize, + ); + new_addr_space_sel_fd.write(&buf)?; + } + { + // Reuse the same sigaltstack and signal entry (all memory will be re-mapped CoW later). + // + // Do this after the address space is cloned, since the kernel will get a shared + // reference to the TCB and whatever pages stores the signal proc control struct. + { + let new_sighandler_fd = new_thr_fd.dup(b"sighandler")?; + new_sighandler_fd.write(&crate::signal::current_setsighandler_struct())?; + } + if let Some(ref proc_fd) = new_proc_fd { + proc_call( + proc_fd.as_raw_fd(), + &mut [], + CallFlags::empty(), + &[ProcCall::SyncSigPctl as u64], + )?; + thread_call( + new_thr_fd.as_raw_fd(), + &mut [], + CallFlags::empty(), + &[ThreadCall::SyncSigTctl as u64], + )?; + } + } + { + // Copy environment registers. + let cur_env_regs_fd = cur_thr_fd.dup(b"regs/env")?; + let new_env_regs_fd = new_thr_fd.dup(b"regs/env")?; + + let mut env_regs = syscall::EnvRegisters::default(); + cur_env_regs_fd.read(&mut env_regs)?; + new_env_regs_fd.write(&env_regs)?; + } + } + // Copy the file table. We do this last to ensure that all previously used file descriptors are + // closed. The only exception -- the filetable selection fd and the current filetable fd -- + // will be closed by the child process. + { + // TODO: Use file descriptor forwarding or something similar to avoid copying the file + // table in the kernel. + let new_filetable_fd = cur_filetable_fd.dup(b"copy")?; + let new_filetable_sel_fd = new_thr_fd.dup(b"current-filetable")?; + new_filetable_sel_fd.write(&usize::to_ne_bytes(new_filetable_fd.as_raw_fd()))?; + } + let start_fd = new_thr_fd.dup(b"start")?; + start_fd.write(&[0])?; + Ok(new_pid) +} + +pub struct NewChildProc { + proc_fd: Option, + + thr_fd: FdGuardUpper, + pid: usize, +} + +pub fn new_child_process(args: &ForkArgs<'_>) -> Result { + match *args { + ForkArgs::Managed => { + let proc_info = crate::static_proc_info(); + let this_proc_fd = proc_info + .proc_fd + .as_ref() + .expect("cannot use ForkArgs::Managed without an existing proc info"); + let child_proc_fd = this_proc_fd.dup(b"fork")?.to_upper()?; + let only_thread_fd = child_proc_fd.dup(b"thread-0")?.to_upper()?; + let meta = read_proc_meta(&child_proc_fd)?; + Ok(NewChildProc { + proc_fd: Some(child_proc_fd), + thr_fd: only_thread_fd, + pid: meta.pid as usize, + }) + } + #[cfg(feature = "proc")] + ForkArgs::Init { .. } => unreachable!(), + + #[cfg(not(feature = "proc"))] + ForkArgs::Init { this_thr_fd, auth } => { + let thr_fd = auth.dup(b"new-context")?.to_upper()?; + let buf = syscall::ProcSchemeAttrs { + pid: 0, + euid: 0, + egid: 0, + prio: !0, // Value is overwritten later + debug_name: { + let mut buf = [0; 32]; + let src = b"[init]"; + buf[..src.len()].copy_from_slice(src); + buf + }, + }; + let attr_fd = + thr_fd.dup(alloc::format!("auth-{}-attrs", auth.as_raw_fd()).as_bytes())?; + attr_fd.write(&buf)?; + Ok(NewChildProc { + thr_fd, + pid: 1, // dummy fd to distinguish child from parent + proc_fd: None, + }) + } + } +} + +pub unsafe fn make_init(proc_cap: usize) -> (&'static FdGuardUpper, &'static FdGuardUpper) { + let proc_fd = FdGuard::new( + syscall::openat(proc_cap, "init", syscall::O_CLOEXEC, 0).expect("failed to create init"), + ) + .to_upper() + .unwrap(); + + syscall::sendfd( + proc_fd.as_raw_fd(), + RtTcb::current().thread_fd().dup(&[]).unwrap().take(), + 0, + 0, + ) + .expect("failed to assign current thread to init process"); + + let managed_thr_fd = proc_fd + .dup(b"thread-0") + .expect("failed to get managed thread for init") + .to_upper() + .unwrap(); + + let managed_thr_fd = unsafe { (*RtTcb::current().thr_fd.get()).insert(managed_thr_fd) }; + + unsafe { + STATIC_PROC_INFO.get().write(crate::StaticProcInfo { + pid: 1, + proc_fd: Some(proc_fd), + }) + }; + *DYNAMIC_PROC_INFO.lock() = crate::DynamicProcInfo { + pgid: 1, + ruid: 0, + euid: 0, + suid: 0, + rgid: 0, + egid: 0, + sgid: 0, + ns_fd: None, + }; + ( + unsafe { (*STATIC_PROC_INFO.get()).proc_fd.as_ref().unwrap() }, + managed_thr_fd, + ) +} +pub(crate) static STATIC_PROC_INFO: SyncUnsafeCell = + SyncUnsafeCell::new(StaticProcInfo { + pid: 0, + proc_fd: None, + }); diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs new file mode 100644 index 0000000000..9677fbb25a --- /dev/null +++ b/redox-rt/src/signal.rs @@ -0,0 +1,1074 @@ +use core::{ + ffi::c_int, + hint::unreachable_unchecked, + panic::AssertUnwindSafe, + ptr::NonNull, + sync::atomic::Ordering, +}; + +use syscall::{ + CallFlags, EAGAIN, EINTR, EINVAL, ENOMEM, EPERM, Error, RawAction, Result, SenderInfo, + SetSighandlerData, SigProcControl, Sigcontrol, SigcontrolFlags, TimeSpec, data::AtomicU64, +}; + +use crate::{ + RtTcb, Tcb, + arch::*, + current_proc_fd, static_proc_info, + sync::Mutex, + sys::{proc_call, this_thread_call}, +}; + +use redox_protocols::protocol::{ + ProcCall, RtSigInfo, SIGCHLD, SIGCONT, SIGKILL, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, + SIGWINCH, ThreadCall, +}; + +#[cfg(target_arch = "x86_64")] +static CPUID_EAX1_ECX: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + +pub fn sighandler_function() -> usize { + // TODO: HWCAP? + + __relibc_internal_sigentry as usize +} + +/// ucontext_t representation +#[repr(C)] +pub struct SigStack { + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" + ))] + _pad: [usize; 1], // pad from 7*8 to 64 + + #[cfg(target_arch = "x86")] + _pad: [usize; 3], // pad from 9*4 to 12*4 + + pub link: *mut SigStack, + + pub old_stack: PosixStackt, + pub old_mask: u64, + pub(crate) sival: usize, + pub(crate) sig_code: u32, + pub(crate) sig_num: u32, + + // x86_64: 864 bytes + // i686: 512 bytes + // aarch64: 272 bytes (SIMD TODO) + // riscv64: 520 bytes (vector extensions TODO) + pub regs: ArchIntRegs, +} +#[repr(C)] +pub struct PosixStackt { + pub sp: *mut (), + pub flags: i32, + pub size: usize, +} + +pub const SS_ONSTACK: usize = 1; +pub const SS_DISABLE: usize = 2; + +impl From for PosixStackt { + fn from(value: Sigaltstack) -> Self { + match value { + Sigaltstack::Disabled => PosixStackt { + sp: core::ptr::null_mut(), + size: 0, + flags: SS_DISABLE.try_into().unwrap(), + }, + Sigaltstack::Enabled { + onstack, + base, + size, + } => PosixStackt { + sp: base.cast(), + size, + flags: if onstack { + SS_ONSTACK.try_into().unwrap() + } else { + 0 + }, + }, + } + } +} + +#[repr(C)] +// TODO: This struct is for practical reasons locked to Linux's ABI, but avoid redefining +// it here. Alternatively, check at compile time that the structs are equivalent. +pub struct SiginfoAbi { + pub si_signo: i32, + pub si_errno: i32, + pub si_code: i32, + pub si_pid: i32, // pid_t + pub si_uid: i32, // uid_t + pub si_addr: *mut (), // *mut c_void + pub si_status: i32, + pub si_value: usize, // sigval +} + +fn invoke_signal_handler(f: AssertUnwindSafe) -> bool { + fn do_call(data: *mut u8) { + let callback = unsafe { &mut *data.cast::>>() }; + if let Some(callback) = callback.take() { + callback.0(); + } + } + + fn do_catch(_data: *mut u8, _payload: *mut u8) {} + + let mut callback = Some(f); + unsafe { + core::intrinsics::catch_unwind( + do_call::, + (&mut callback as *mut Option>).cast(), + do_catch::, + ) != 0 + } +} + +#[inline(always)] +unsafe fn return_ignored_signal( + os: &RtTcb, + stack: &SigStack, + signals_were_disabled: bool, +) { + unsafe { + (*os.arch.get()).last_sig_was_restart = true; + (*os.arch.get()).last_sigstack = NonNull::new(stack.link); + } + + if !signals_were_disabled { + core::sync::atomic::compiler_fence(Ordering::Release); + let control_flags = &os.control.control_flags; + control_flags.store( + control_flags.load(Ordering::Relaxed) & !SigcontrolFlags::INHIBIT_DELIVERY.bits(), + Ordering::Relaxed, + ); + } +} + +#[inline(always)] +unsafe fn inner(stack: &mut SigStack) { + let os = unsafe { &Tcb::current().unwrap().os_specific }; + + let stack_ptr = NonNull::from(&mut *stack); + stack.link = core::mem::replace( + unsafe { &mut (*os.arch.get()).last_sigstack }, + Some(stack_ptr), + ) + .map_or_else(core::ptr::null_mut, |x| x.as_ptr()); + + let signals_were_disabled = unsafe { (*os.arch.get()).disable_signals_depth > 0 }; + + let targeted_thread_not_process = stack.sig_num >= 64; + stack.sig_num %= 64; + + // asm counts from 0 + stack.sig_num += 1; + + let (sender_pid, sender_uid) = { + let area = unsafe { &mut *os.arch.get() }; + + // Undefined if the signal was not realtime + stack.sival = area.tmp_rt_inf.arg; + + stack.old_stack = unsafe { arch_pre(stack, area) }; + + if (stack.sig_num - 1) / 32 == 1 && !targeted_thread_not_process { + stack.sig_code = area.tmp_rt_inf.code as u32; + (area.tmp_rt_inf.pid, area.tmp_rt_inf.uid) + } else { + stack.sig_code = 0; // TODO: SI_USER constant? + // TODO: Handle SIGCHLD. Maybe that should always be queued though? + let inf = SenderInfo::from_raw(area.tmp_id_inf); + (inf.pid, inf.ruid) + } + }; + + let sigaction = { + let guard = SIGACTIONS_LOCK.lock(); + let action = convert_old(&PROC_CONTROL_STRUCT.actions[stack.sig_num as usize - 1]); + if action.flags.contains(SigactionFlags::RESETHAND) { + drop(guard); + // TODO: handle error? + let _ = sigaction( + stack.sig_num as u8, + Some(&Sigaction { + kind: SigactionKind::Default, + mask: 0, + flags: SigactionFlags::empty(), + }), + None, + ); + } + action + }; + let shall_restart = sigaction.flags.contains(SigactionFlags::RESTART); + let sig = (stack.sig_num & 0x3f) as u8; + + let handler = match sigaction.kind { + // TODO: Since sigaction may be called while procmgr is checking the IGNORED bit, it is + // likely possible there can be a race condition resulting in the signal trampoline running + // and reaching this code. If so, we do already know whether the signal is IGNORED *now*, + // and so we should return early ideally without even temporarily touching the signal mask. + SigactionKind::Ignore => { + unsafe { + return_ignored_signal(os, stack, signals_were_disabled); + } + return; + } + // this case should be treated equally as the one above + // + // _ if sigaction.flags.contains(SigactionFlags::IGNORED) => { + // panic!("ctl2 {:#x?} signal {}", os.control, stack.sig_num) + // } + SigactionKind::Default if usize::from(sig) == SIGCONT => SignalHandler { handler: None }, + SigactionKind::Default => { + let _ = proc_call( + current_proc_fd().as_raw_fd(), + &mut [], + CallFlags::empty(), + &[ProcCall::Exit as u64, u64::from(sig) << 8], + ); + // SAFETY: ProcCall::Exit terminates the current process when it succeeds, so reaching + // this point would violate the proc manager exit contract. + unsafe { unreachable_unchecked() } + } + SigactionKind::Handled { handler } => handler, + }; + + // Set sigmask to sa_mask and unmark the signal as pending. + let prev_sigallow = get_allowset_raw(&os.control.word); + + let mut sigallow_inside = !sigaction.mask & prev_sigallow; + if !sigaction.flags.contains(SigactionFlags::NODEFER) { + sigallow_inside &= !sig_bit(stack.sig_num); + } + + let _pending_when_sa_mask = set_allowset_raw(&os.control.word, prev_sigallow, sigallow_inside); + + // TODO: If sa_mask caused signals to be unblocked, deliver one or all of those first? + + // Re-enable signals again. + let control_flags = &os.control.control_flags; + control_flags.store( + control_flags.load(Ordering::Relaxed) & !SigcontrolFlags::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + core::sync::atomic::compiler_fence(Ordering::Acquire); + + stack.old_mask = prev_sigallow; + + // Call handler, either sa_handler or sa_siginfo depending on flag. + if sigaction.flags.contains(SigactionFlags::SIGINFO) + && let Some(sigaction) = unsafe { handler.sigaction } + { + let info = SiginfoAbi { + si_signo: stack.sig_num as c_int, + si_addr: core::ptr::null_mut(), + si_code: stack.sig_code as i32, + si_errno: 0, + si_pid: sender_pid as i32, + si_status: 0, + si_uid: sender_uid as i32, + si_value: stack.sival, + }; + if invoke_signal_handler(AssertUnwindSafe(|| unsafe { + sigaction( + stack.sig_num as c_int, + core::ptr::addr_of!(info).cast(), + stack as *mut SigStack as *mut (), + ) + })) { + let _ = syscall::write(2, b"redox-rt: sa_siginfo handler panicked; continuing\n"); + } + } else if let Some(handler) = unsafe { handler.handler } { + if invoke_signal_handler(AssertUnwindSafe(|| { + handler(stack.sig_num as c_int); + })) { + let _ = syscall::write(2, b"redox-rt: sa_handler panicked; continuing\n"); + } + } + + // Disable signals while we modify the sigmask again + control_flags.store( + control_flags.load(Ordering::Relaxed) | SigcontrolFlags::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + core::sync::atomic::compiler_fence(Ordering::Acquire); + + // Update allowset again, and obtain the set of pending unblocked signals with this new + // allowset. If this is nonempty, we must deliver the signal here rather than wait for it + // asynchronously, as shown e.g. in https://gitlab.redox-os.org/redox-os/relibc/-/issues/239. + // We do this even if signals_were_disabled beforehand (where the caller hence explicitly + // must have jumped to this trampoline). + + let new_mask = stack.old_mask; + let old_mask = get_allowset_raw(&os.control.word); + + let pending_unblocked = { + let thread_pending = set_allowset_raw(&os.control.word, old_mask, new_mask); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Relaxed); + (thread_pending | proc_pending) & new_mask + }; + + unsafe { (*os.arch.get()).last_sig_was_restart = shall_restart }; + + // TODO: Support setting uc_link to jump back to a different context? + unsafe { (*os.arch.get()).last_sigstack = NonNull::new(stack.link) }; + + // TODO: Support restoring uc_stack? + + if pending_unblocked > 0 { + // If there were signals visible at the time we checked (otherwise it can be considered + // "asynchronous" and will be delivered the next time an EINTR is seen, or after the + // post-preemption check in the kernel), we delay clearing the INHIBIT_DELIVERY bit, and + // rearrange the saved instr ptr field on the stack and the sc_saved_rip field, so that it + // returns to the start of the trampoline in a way that makes it look like a new signal was + // immediately delivered. There's no need to worry about the __crit_* sections since + // INHIBIT_DELIVERY is still set. + arch_ret_to_sig(stack, &os.control); + } else if !signals_were_disabled { + // Re-enable signals again, except in the case where they were disabled and the trampoline + // was explicitly jumped to when handling EINTR. + core::sync::atomic::compiler_fence(Ordering::Release); + control_flags.store( + control_flags.load(Ordering::Relaxed) & !SigcontrolFlags::INHIBIT_DELIVERY.bits(), + Ordering::Relaxed, + ); + } +} +#[cfg(not(target_arch = "x86"))] +pub(crate) unsafe extern "C" fn inner_c(stack: usize) { + unsafe { inner(&mut *(stack as *mut SigStack)) } +} +#[cfg(target_arch = "x86")] +pub(crate) unsafe extern "fastcall" fn inner_fastcall(stack: usize) { + unsafe { inner(&mut *(stack as *mut SigStack)) } +} + +pub fn get_sigmask() -> Result { + let mut mask = 0; + modify_sigmask(Some(&mut mask), Option:: u64>::None)?; + Ok(mask) +} +pub fn set_sigmask(new: Option, old: Option<&mut u64>) -> Result<()> { + modify_sigmask(old, new.map(move |newmask| move |_| newmask)) +} +pub fn or_sigmask(new: Option, old: Option<&mut u64>) -> Result<()> { + // Parsing nightmare... :) + modify_sigmask( + old, + new.map(move |newmask| move |oldmask| oldmask | newmask), + ) +} +pub fn andn_sigmask(new: Option, old: Option<&mut u64>) -> Result<()> { + modify_sigmask( + old, + new.map(move |newmask| move |oldmask| oldmask & !newmask), + ) +} +fn get_allowset_raw(words: &[AtomicU64; 2]) -> u64 { + (words[0].load(Ordering::Relaxed) >> 32) | ((words[1].load(Ordering::Relaxed) >> 32) << 32) +} +/// Sets mask from old to new, returning what was pending at the time. +fn set_allowset_raw(words: &[AtomicU64; 2], old: u64, new_raw: u64) -> u64 { + // TODO: should these bits always be set, or never be set? + let new = new_raw | ALLOWSET_ALWAYS; + + // This assumes *only this thread* can change the allowset. If this rule is broken, the use of + // fetch_add will corrupt the words entirely. fetch_add is very efficient on x86, being + // generated as LOCK XADD which is the fastest RMW instruction AFAIK. + let prev_w0 = words[0].fetch_add( + ((new & 0xffff_ffff) << 32).wrapping_sub((old & 0xffff_ffff) << 32), + Ordering::Relaxed, + ) & 0xffff_ffff; + let prev_w1 = words[1].fetch_add( + ((new >> 32) << 32).wrapping_sub((old >> 32) << 32), + Ordering::Relaxed, + ) & 0xffff_ffff; + + prev_w0 | (prev_w1 << 32) +} +const ALLOWSET_ALWAYS: u64 = sig_bit(SIGSTOP as u32) | sig_bit(SIGKILL as u32); +fn modify_sigmask(old: Option<&mut u64>, op: Option u64>) -> Result<()> { + let _guard = tmp_disable_signals(); + let ctl = current_sigctl(); + + let prev = get_allowset_raw(&ctl.word); + + if let Some(old) = old { + *old = !prev; + } + let Some(op) = op else { + return Ok(()); + }; + + let next = !op(!prev); + + let thread_pending = set_allowset_raw(&ctl.word, prev, next); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Relaxed); + + // POSIX requires that at least one pending unblocked signal be delivered before + // pthread_sigmask returns, if there is one. + if (thread_pending | proc_pending) & next != 0 { + unsafe { + manually_enter_trampoline(); + } + } + + Ok(()) +} + +#[derive(Clone, Copy, Default)] +pub enum SigactionKind { + #[default] + Default, + + Ignore, + Handled { + handler: SignalHandler, + }, +} + +#[derive(Clone, Copy, Default)] +pub struct Sigaction { + pub kind: SigactionKind, + pub mask: u64, + pub flags: SigactionFlags, +} + +impl Sigaction { + fn ip(&self) -> usize { + unsafe { + match self.kind { + SigactionKind::Handled { handler } => { + if self.flags.contains(SigactionFlags::SIGINFO) { + handler.sigaction.map_or(0, |a| a as usize) + } else { + handler.handler.map_or(0, |a| a as usize) + } + } + _ => 0, + } + } + } +} + +const MASK_DONTCARE: u64 = !0; + +fn convert_old(action: &RawAction) -> Sigaction { + let old_first = action.first.load(Ordering::Relaxed); + let old_mask = action.user_data.load(Ordering::Relaxed); + + let handler = (old_first & !(u64::from(STORED_FLAGS) << 32)) as usize; + let flags = SigactionFlags::from_bits_retain(((old_first >> 32) as u32) & STORED_FLAGS); + + let kind = if handler == default_handler as usize { + SigactionKind::Default + } else if flags.contains(SigactionFlags::IGNORED) { + SigactionKind::Ignore + } else { + SigactionKind::Handled { + handler: unsafe { core::mem::transmute(handler as usize) }, + } + }; + + Sigaction { + mask: old_mask, + flags, + kind, + } +} + +pub fn sigaction(signal: u8, new: Option<&Sigaction>, old: Option<&mut Sigaction>) -> Result<()> { + let _sigguard = tmp_disable_signals(); + let ctl = current_sigctl(); + + let _guard = SIGACTIONS_LOCK.lock(); + sigaction_inner(ctl, signal, new, old) +} +fn sigaction_inner( + ctl: &Sigcontrol, + signal: u8, + new: Option<&Sigaction>, + old: Option<&mut Sigaction>, +) -> Result<()> { + // TODO: Now that the goal of keeping logic out of the IPC backend, no longer holds when + // procmgr has taken over signal handling from the kernel, it would probably make sense to make + // parts of this function an IPC call, for synchronization purposes. Apart from SA_RESETHAND + // logic which may need to be fast, regular sigaction is typically in the 'configuration' + // category, allowed to be slower. + + if matches!(usize::from(signal), 0 | 32 | 65..) { + return Err(Error::new(EINVAL)); + } + if matches!(usize::from(signal), SIGKILL | SIGSTOP) { + if new.is_some() { + return Err(Error::new(EINVAL)); + } + if let Some(old) = old { + // TODO: Is this the correct value to set it to? + *old = Sigaction { + kind: SigactionKind::Default, + mask: 0, + flags: SigactionFlags::empty(), + }; + } + return Ok(()); + } + + let action = &PROC_CONTROL_STRUCT.actions[usize::from(signal) - 1]; + + if let Some(old) = old { + *old = convert_old(action); + } + + let Some(new) = new else { + return Ok(()); + }; + + let explicit_handler = new.ip(); + + let sig_group = (signal - 1) / 32; + let sig_idx = (signal - 1) % 32; + + let (mask, flags, handler) = match (usize::from(signal), new.kind) { + (_, SigactionKind::Ignore) | (SIGURG | SIGWINCH, SigactionKind::Default) => { + // TODO: relibc and procmgr have access to all threads, redox_rt doesn't currently. + // Do this for all threads! + ctl.word[usize::from(sig_group)].fetch_and(!(1 << sig_idx), Ordering::Relaxed); + PROC_CONTROL_STRUCT + .pending + .fetch_and(!sig_bit(signal.into()), Ordering::Relaxed); + + // TODO: handle tmp_disable_signals + ( + MASK_DONTCARE, + SigactionFlags::IGNORED, + if matches!(new.kind, SigactionKind::Default) { + default_handler as usize + } else { + 0 + }, + ) + } + // TODO: Handle pending signals before these flags are set. + (SIGTSTP | SIGTTOU | SIGTTIN, SigactionKind::Default) => ( + MASK_DONTCARE, + SigactionFlags::SIG_SPECIFIC, + default_handler as usize, + ), + (SIGCHLD, SigactionKind::Default) => { + let nocldstop_bit = new.flags & SigactionFlags::SIG_SPECIFIC; + + // Default action is to ignore. Hence all pending SIGCHLD signals should be discarded. + + // TODO: relibc and procmgr have access to all threads, redox_rt doesn't currently. + // Do this for all threads! + ctl.word[usize::from(sig_group)].fetch_and(!(1 << sig_idx), Ordering::Relaxed); + PROC_CONTROL_STRUCT + .pending + .fetch_and(!sig_bit(signal.into()), Ordering::Relaxed); + + ( + MASK_DONTCARE, + SigactionFlags::IGNORED | nocldstop_bit, + default_handler as usize, + ) + } + + (_, SigactionKind::Default) => (new.mask, new.flags, default_handler as usize), + (_, SigactionKind::Handled { .. }) => (new.mask, new.flags, explicit_handler), + }; + let new_first = (handler as u64) | (u64::from(flags.bits() & STORED_FLAGS) << 32); + action.first.store(new_first, Ordering::Relaxed); + action.user_data.store(mask, Ordering::Relaxed); + + Ok(()) +} + +fn current_sigctl() -> &'static Sigcontrol { + &unsafe { Tcb::current() }.unwrap().os_specific.control +} + +pub struct TmpDisableSignalsGuard { + _inner: (), +} + +pub fn tmp_disable_signals() -> TmpDisableSignalsGuard { + unsafe { + let ctl = ¤t_sigctl().control_flags; + ctl.store( + ctl.load(Ordering::Relaxed) | syscall::flag::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + core::sync::atomic::compiler_fence(Ordering::Acquire); + + // TODO: fence? + (*Tcb::current().unwrap().os_specific.arch.get()).disable_signals_depth += 1; + } + + TmpDisableSignalsGuard { _inner: () } +} +impl Drop for TmpDisableSignalsGuard { + fn drop(&mut self) { + unsafe { + let depth = + &mut (*Tcb::current().unwrap().os_specific.arch.get()).disable_signals_depth; + *depth -= 1; + + if *depth == 0 { + let ctl = ¤t_sigctl().control_flags; + ctl.store( + ctl.load(Ordering::Relaxed) & !syscall::flag::INHIBIT_DELIVERY.bits(), + Ordering::Release, + ); + core::sync::atomic::compiler_fence(Ordering::Acquire); + } + } + } +} + +bitflags::bitflags! { + // Some flags are ignored by the rt, but they match relibc's 1:1 to simplify conversion. + #[derive(Clone, Copy, Default)] + pub struct SigactionFlags: u32 { + const NOCLDWAIT = 2; + const RESTORER = 4; + const SIGINFO = 0x0200_0000; + const ONSTACK = 0x0400_0000; + const RESTART = 0x0800_0000; + const NODEFER = 0x1000_0000; + const RESETHAND = 0x2000_0000; + const SIG_SPECIFIC = 0x4000_0000; + const IGNORED = 0x8000_0000; + } +} + +const STORED_FLAGS: u32 = 0xfe00_0000; + +fn default_handler(_sig: c_int) { + unreachable!(); +} + +#[derive(Clone, Copy)] +pub union SignalHandler { + pub handler: Option, + pub sigaction: Option, +} + +static SIGACTIONS_LOCK: Mutex<()> = Mutex::new(()); + +pub(crate) static PROC_CONTROL_STRUCT: SigProcControl = SigProcControl { + pending: AtomicU64::new(0), + actions: [const { + RawAction { + first: AtomicU64::new(0), + user_data: AtomicU64::new(0), + } + }; 64], + sender_infos: [const { AtomicU64::new(0) }; 32], +}; + +const fn sig_bit(sig: u32) -> u64 { + //assert_ne!(sig, 32); + //assert_ne!(sig, 0); + 1 << (sig - 1) +} + +pub fn setup_sighandler(tcb: &RtTcb, first_thread: bool) { + if first_thread { + let _guard = SIGACTIONS_LOCK.lock(); + for (sig_idx, action) in PROC_CONTROL_STRUCT.actions.iter().enumerate() { + let sig = sig_idx + 1; + let bits = if matches!(sig, SIGTSTP | SIGTTIN | SIGTTOU) { + SigactionFlags::SIG_SPECIFIC + } else if matches!(sig, SIGCHLD | SIGURG | SIGWINCH) { + SigactionFlags::IGNORED + } else { + SigactionFlags::empty() + }; + action.first.store( + (u64::from(bits.bits()) << 32) | default_handler as u64, + Ordering::Relaxed, + ); + } + } + let arch = unsafe { &mut *tcb.arch.get() }; + { + // The asm decides whether to use the altstack, based on whether the saved stack pointer + // was already on that stack. Thus, setting the altstack to the entire address space, is + // equivalent to not using any altstack at all (the default). + arch.altstack_top = usize::MAX; + arch.altstack_bottom = 0; + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + { + arch.pctl = core::ptr::addr_of!(PROC_CONTROL_STRUCT) as usize; + } + } + + #[cfg(target_arch = "x86_64")] + { + #[allow(unused_unsafe)] + let cpuid_eax1_ecx = unsafe { core::arch::x86_64::__cpuid(1) }.ecx; + CPUID_EAX1_ECX.store(cpuid_eax1_ecx, core::sync::atomic::Ordering::Relaxed); + SUPPORTS_AVX.store(u8::from(cpuid_eax1_ecx & 1 << 28 != 0), Ordering::Relaxed); + } + + let data = current_setsighandler_struct(); + + let fd = tcb + .thread_fd() + .dup(b"sighandler") + .expect("failed to open sighandler fd"); + fd.write(&data).expect("failed to write to sighandler fd"); + this_thread_call( + &mut [], + CallFlags::empty(), + &[ThreadCall::SyncSigTctl as u64], + ) + .expect("failed to sync signal tctl"); + + // TODO: Inherited set of ignored signals + // TODO: handle error + let _ = set_sigmask(Some(0), None); +} +pub type RtSigarea = RtTcb; // TODO +pub fn current_setsighandler_struct() -> SetSighandlerData { + SetSighandlerData { + user_handler: sighandler_function(), + excp_handler: 0, // TODO + thread_control_addr: core::ptr::addr_of!( + unsafe { Tcb::current() }.unwrap().os_specific.control + ) as usize, + proc_control_addr: &PROC_CONTROL_STRUCT as *const SigProcControl as usize, + } +} + +#[derive(Clone, Copy, Default, PartialEq)] +pub enum Sigaltstack { + #[default] + Disabled, + + Enabled { + onstack: bool, + base: *mut (), + size: usize, + }, +} + +pub(crate) fn get_sigaltstack(tcb: &SigArea, sp: usize) -> Sigaltstack { + if tcb.altstack_bottom == 0 && tcb.altstack_top == usize::MAX { + Sigaltstack::Disabled + } else { + Sigaltstack::Enabled { + base: tcb.altstack_bottom as *mut (), + size: tcb.altstack_top - tcb.altstack_bottom, + onstack: (tcb.altstack_bottom..=tcb.altstack_top).contains(&sp), + } + } +} + +pub unsafe fn sigaltstack( + new: Option<&Sigaltstack>, + old_out: Option<&mut Sigaltstack>, +) -> Result<()> { + let _g = tmp_disable_signals(); + let tcb = unsafe { &mut *Tcb::current().unwrap().os_specific.arch.get() }; + + let old = get_sigaltstack(tcb, crate::arch::current_sp()); + + if matches!(old, Sigaltstack::Enabled { onstack: true, .. }) + && new.is_some_and(|new| *new != old) + { + return Err(Error::new(EPERM)); + } + + if let Some(old_out) = old_out { + *old_out = old; + } + if let Some(new) = new { + match *new { + Sigaltstack::Disabled => { + tcb.altstack_bottom = 0; + tcb.altstack_top = usize::MAX; + } + Sigaltstack::Enabled { onstack: true, .. } => return Err(Error::new(EINVAL)), + Sigaltstack::Enabled { + base, + size, + onstack: false, + } => { + if size < MIN_SIGALTSTACK_SIZE { + return Err(Error::new(ENOMEM)); + } + + tcb.altstack_bottom = base as usize; + tcb.altstack_top = base as usize + size; + } + } + } + Ok(()) +} + +pub const MIN_SIGALTSTACK_SIZE: usize = 2048; + +pub fn currently_pending_blocked() -> u64 { + let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; + let w0 = control.word[0].load(Ordering::Relaxed); + let w1 = control.word[1].load(Ordering::Relaxed); + let allow = (w0 >> 32) | ((w1 >> 32) << 32); + let thread_pending = (w0 & 0xffff_ffff) | ((w1 & 0xffff_ffff) << 32); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Relaxed); + + core::sync::atomic::fence(Ordering::Acquire); // TODO: Correct ordering? + + (thread_pending | proc_pending) & !allow +} +pub enum Unreachable {} + +pub fn await_signal_async(inner_allowset: u64) -> Result { + let _guard = tmp_disable_signals(); + let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; + + let old_allowset = get_allowset_raw(&control.word); + set_allowset_raw(&control.word, old_allowset, inner_allowset); + + let res = syscall::nanosleep( + &TimeSpec { + tv_sec: i64::MAX, + tv_nsec: 0, + }, + &mut TimeSpec::default(), + ); + + if res == Err(Error::new(EINTR)) { + unsafe { + manually_enter_trampoline(); + } + } + // POSIX says it shall restore the mask to what it was prior to the call, which is interpreted + // as allowing any changes to sigprocmask inside the signal handler, to be discarded. + set_allowset_raw(&control.word, inner_allowset, old_allowset); + + res?; + unreachable!() +} + +/// Run a callback with the specified signal mask, atomically +pub fn callback_or_signal_async Result>( + inner_allowset: u64, + callback: F, +) -> Result { + let _guard = tmp_disable_signals(); + let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; + + let old_allowset = get_allowset_raw(&control.word); + set_allowset_raw(&control.word, old_allowset, inner_allowset); + let res = callback(); + if let Err(err) = &res { + if err.errno == EINTR { + // Run trampoline if EINTR returned + unsafe { + manually_enter_trampoline(); + } + } + } + + // POSIX says it shall restore the mask to what it was prior to the call, which is interpreted + // as allowing any changes to sigprocmask inside the signal handler, to be discarded. + set_allowset_raw(&control.word, inner_allowset, old_allowset); + + res +} + +/*#[unsafe(no_mangle)] +pub extern "C" fn __redox_rt_debug_sigctl() { + let tcb = &RtTcb::current().control; + let _ = syscall::write(1, alloc::format!("SIGCTL: {tcb:#x?}\n").as_bytes()); +}*/ + +// TODO: deadline-based API +pub fn await_signal_sync(inner_allowset: u64, timeout: Option<&TimeSpec>) -> Result { + let _guard = tmp_disable_signals(); + let control = &unsafe { Tcb::current().unwrap() }.os_specific.control; + + let old_allowset = get_allowset_raw(&control.word); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Acquire); + let thread_pending = set_allowset_raw(&control.word, old_allowset, inner_allowset); + + // Check if there are already signals matching the requested set, before waiting. + if let Some(info) = try_claim_multiple(proc_pending, thread_pending, inner_allowset, control) { + // TODO: RAII + set_allowset_raw(&control.word, inner_allowset, old_allowset); + return Ok(info); + } + + let res = match timeout { + Some(t) => syscall::nanosleep(&t, &mut TimeSpec::default()), + None => syscall::nanosleep( + &TimeSpec { + tv_sec: i64::MAX, + tv_nsec: 0, + }, + &mut TimeSpec::default(), + ), + }; + + let thread_pending = set_allowset_raw(&control.word, inner_allowset, old_allowset); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Acquire); + + if let Err(error) = res + && error.errno != EINTR + { + return Err(error); + } + + // Then check if there were any signals left after waiting. + try_claim_multiple(proc_pending, thread_pending, inner_allowset, control) + // Normally ETIMEDOUT but not for sigtimedwait. + .ok_or(Error::new(EAGAIN)) +} + +fn try_claim_multiple( + mut proc_pending: u64, + mut thread_pending: u64, + allowset: u64, + control: &Sigcontrol, +) -> Option { + while (proc_pending | thread_pending) & allowset != 0 { + let sig_idx = ((proc_pending | thread_pending) & allowset).trailing_zeros(); + if thread_pending & allowset & (1 << sig_idx) != 0 + && let Some(res) = try_claim_single(sig_idx, Some(control)) + { + return Some(res); + } + thread_pending &= !(1 << sig_idx); + if proc_pending & allowset & (1 << sig_idx) != 0 + && let Some(res) = try_claim_single(sig_idx, None) + { + return Some(res); + } + proc_pending &= !(1 << sig_idx); + } + None +} +fn try_claim_single(sig_idx: u32, thread_control: Option<&Sigcontrol>) -> Option { + let sig_group = sig_idx / 32; + + if sig_group == 1 && thread_control.is_none() { + // Queued (realtime) signal + let rt_inf: RtSigInfo = unsafe { + let mut buf = [0_u8; size_of::()]; + buf[..4].copy_from_slice(&(sig_idx - 32).to_ne_bytes()); + proc_call( + static_proc_info().proc_fd.as_ref().unwrap().as_raw_fd(), + &mut buf, + CallFlags::empty(), + &[ProcCall::Sigdeq as u64], + ) + .ok()?; + core::mem::transmute(buf) + }; + Some(SiginfoAbi { + si_signo: sig_idx as i32 + 1, + si_errno: 0, + si_code: rt_inf.code, + si_pid: rt_inf.pid as i32, + si_uid: rt_inf.uid as i32, + si_status: 0, + si_value: rt_inf.arg, + si_addr: core::ptr::null_mut(), + }) + } else { + // Idempotent (standard or thread realtime) signal + let info = SenderInfo::from_raw(match thread_control { + Some(ctl) => { + // Only this thread can clear pending bits, so this will always succeed. + let info = match ctl.sender_infos.get(sig_idx as usize) { + Some(i) => i.load(Ordering::Relaxed), + // TODO: Protocol will need to be extended in order to allow passing si_uid and + // si_pid for per-thread (non-queued) realtime signals. + None => 0, + }; + // TODO: Ordering? + ctl.word[sig_group as usize].fetch_and(!(1 << (sig_idx % 32)), Ordering::Release); + info + } + None => { + // Must have sig_group == 0 here due to the above if stmt + let info = + PROC_CONTROL_STRUCT.sender_infos[sig_idx as usize].load(Ordering::Acquire); + if PROC_CONTROL_STRUCT + .pending + .fetch_and(!(1 << sig_idx), Ordering::Release) + & (1 << sig_idx) + == 0 + { + // already claimed + return None; + } + info + } + }); + Some(SiginfoAbi { + si_signo: sig_idx as i32 + 1, + si_errno: 0, + si_code: 0, // TODO: SI_USER const? + si_pid: info.pid as i32, + si_uid: info.ruid as i32, + si_status: 0, + si_value: 0, // undefined + si_addr: core::ptr::null_mut(), + }) + } +} +pub fn apply_inherited_sigignmask(inherited: u64) { + let _sig_guard = tmp_disable_signals(); + let _guard = SIGACTIONS_LOCK.lock(); + let ctl = current_sigctl(); + + // Set all signals in the inherited set that have explicitly been set to SIG_IGN. Those whose + // (default) effective action is to ignore but are set to SIG_DFL, are not in this set, and the + // initial SIG_DFL state would be "Ignore" anyway but still return SIG_DFL when asked, as + // usual. + for bit in (0..64).filter(|b| inherited & (1 << b) != 0) { + let sig = u8::try_from(bit + 1).unwrap(); + let _ = sigaction_inner( + ctl, + sig, + Some(&Sigaction { + // TODO: correct fields? + flags: SigactionFlags::IGNORED, + kind: SigactionKind::Ignore, + mask: 0, + }), + None, + ); + } +} +pub fn get_sigignmask_to_inherit() -> u64 { + let _sig_guard = tmp_disable_signals(); + let _guard = SIGACTIONS_LOCK.lock(); + let ctl = current_sigctl(); + + let mut mask = 0_u64; + // Fill the mask with the set of the inherited signals that have explicitly been set to + // SIG_IGN. Again this excludes signals that would have returned SIG_DFL from sigaction, + // despite their default action being "Ignore". + + for bit in 0..64 { + let sig = u8::try_from(bit + 1).unwrap(); + let mut old = Sigaction::default(); + let _ = sigaction_inner(ctl, sig, None, Some(&mut old)); + if matches!(old.kind, SigactionKind::Ignore) { + mask |= 1 << bit; + } + } + + mask +} diff --git a/redox-rt/src/sync.rs b/redox-rt/src/sync.rs new file mode 100644 index 0000000000..beae40aa05 --- /dev/null +++ b/redox-rt/src/sync.rs @@ -0,0 +1,68 @@ +// TODO: Share code for simple futex-based mutex between relibc's Mutex<()> and this. + +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicU32, Ordering}, +}; + +pub struct Mutex { + pub lockword: AtomicU32, + pub inner: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl Mutex { + /// Represents an unlocked [Mutex]. + pub const UNLOCKED: u32 = 0; + /// Represents a locked [Mutex]. + pub const LOCKED: u32 = 1; + /// Represents a waiting [Mutex]. + pub const WAITING: u32 = 2; + + pub const fn new(t: T) -> Self { + Self { + lockword: AtomicU32::new(0), + inner: UnsafeCell::new(t), + } + } + pub fn lock(&self) -> MutexGuard<'_, T> { + while self + .lockword + .compare_exchange( + Self::UNLOCKED, + Self::LOCKED, + Ordering::Acquire, + Ordering::Relaxed, + ) + .is_err() + { + core::hint::spin_loop(); + } + MutexGuard { lock: self } + } +} +pub struct MutexGuard<'l, T> { + lock: &'l Mutex, +} +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.inner.get() } + } +} +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.inner.get() } + } +} +impl Drop for MutexGuard<'_, T> { + fn drop(&mut self) { + self.lock + .lockword + .store(Mutex::::UNLOCKED, Ordering::Release); + } +} diff --git a/redox-rt/src/sys.rs b/redox-rt/src/sys.rs new file mode 100644 index 0000000000..80e3105662 --- /dev/null +++ b/redox-rt/src/sys.rs @@ -0,0 +1,569 @@ +use core::{ + mem::{replace, size_of}, + ptr::addr_of, + sync::atomic::{AtomicU32, Ordering}, +}; + +use ioslice::IoSlice; +use syscall::{ + CallFlags, EINVAL, ERESTART, StdFsCallKind, TimeSpec, + data::StdFsCallMeta, + error::{self, EINTR, ENODEV, ESRCH, Error, Result}, +}; + +use crate::{ + DYNAMIC_PROC_INFO, DynamicProcInfo, RtTcb, Tcb, + arch::manually_enter_trampoline, + proc::{FdGuard, FdGuardUpper}, + read_proc_meta, + signal::tmp_disable_signals, +}; +use alloc::vec::Vec; +use redox_protocols::protocol::{ + NsDup, ProcCall, ProcKillTarget, RtSigInfo, ThreadCall, WaitFlags, +}; + +#[inline] +fn wrapper(restart: bool, erestart: bool, mut f: impl FnMut() -> Result) -> Result { + loop { + let _guard = tmp_disable_signals(); + let rt_sigarea = unsafe { &Tcb::current().unwrap().os_specific }; + let res = f(); + let code = if erestart { ERESTART } else { EINTR }; + + if let Err(err) = res + && err == Error::new(code) + { + unsafe { + manually_enter_trampoline(); + } + if restart && unsafe { (*rt_sigarea.arch.get()).last_sig_was_restart } { + continue; + } + } + + return res; + } +} +// TODO: uninitialized memory? +#[inline] +pub fn posix_read(fd: usize, buf: &mut [u8]) -> Result { + wrapper(true, false, || syscall::read(fd, buf)) +} +#[inline] +pub fn posix_write(fd: usize, buf: &[u8]) -> Result { + wrapper(true, false, || syscall::write(fd, buf)) +} +#[inline] +pub fn posix_kill(target: ProcKillTarget, sig: usize) -> Result<()> { + if sig > 64 { + return Err(Error::new(EINVAL)); + } + + match wrapper(false, true, || { + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::Kill as u64, target.raw() as u64, sig as u64], + ) + }) { + Ok(_) | Err(Error { errno: ERESTART }) => Ok(()), + Err(error) => Err(error), + } +} +#[inline] +pub fn posix_sigqueue(pid: usize, sig: usize, arg: usize) -> Result<()> { + let target = ProcKillTarget::from_raw(pid); + if !matches!(target, ProcKillTarget::SingleProc(_)) { + return Err(Error::new(ESRCH)); + } + if sig <= 32 { + return posix_kill(target, sig); + } + let mut siginf = RtSigInfo { + arg, + code: -1, // TODO: SI_QUEUE constant + uid: 0, // TODO + pid: posix_getpid(), + }; + match wrapper(false, true, || { + this_proc_call( + unsafe { plain::as_mut_bytes(&mut siginf) }, + CallFlags::empty(), + &[ProcCall::Sigq as u64, pid as u64, sig as u64], + ) + }) { + Ok(_) + | Err(Error { + errno: error::ERESTART, + }) => Ok(()), + Err(error) => Err(error), + } +} +#[inline] +pub fn posix_getpid() -> u32 { + // SAFETY: read-only except during program/fork child initialization + unsafe { addr_of!((*crate::STATIC_PROC_INFO.get()).pid).read() } +} +#[inline] +pub fn posix_getppid() -> u32 { + this_proc_call(&mut [], CallFlags::empty(), &[ProcCall::Getppid as u64]).expect("cannot fail") + as u32 +} + +#[inline] +pub fn posix_setpriority(which: i32, who: u32, prio: u32) -> Result<(), syscall::Error> { + if which != 0 { + return Err(syscall::Error::new(syscall::EINVAL)); // TODO: Add support for PRIO_PGRP and PRIO_PROCESS + } + + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::SetProcPriority as u64, who as u64, prio as u64], + )?; + + Ok(()) +} + +#[inline] +pub fn posix_getpriority(which: i32, who: u32) -> Result { + if which != 0 { + return Err(syscall::Error::new(syscall::EINVAL)); + } + + let res = this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::GetProcPriority as u64, who as u64], + )?; + + Ok(res as u32) +} + +#[inline] +pub unsafe fn sys_futex_wait(addr: *mut u32, val: u32, deadline: Option<&TimeSpec>) -> Result<()> { + wrapper(true, false, || { + unsafe { + syscall::syscall5( + syscall::SYS_FUTEX, + addr as usize, + syscall::FUTEX_WAIT, + val as usize, + deadline.map_or(0, |d| d as *const _ as usize), + 0, + ) + } + .map(|_| ()) + }) +} +#[inline] +pub unsafe fn sys_futex_wake(addr: *mut u32, num: u32) -> Result { + unsafe { + syscall::syscall5( + syscall::SYS_FUTEX, + addr as usize, + syscall::FUTEX_WAKE, + num as usize, + 0, + 0, + ) + } + .map(|awoken| awoken as u32) +} +unsafe fn raw_sys_call( + fd: usize, + payload_ptr: *const u8, + len: usize, + flags: CallFlags, + metadata: &[u64], +) -> Result { + unsafe { + syscall::syscall5( + syscall::SYS_CALL, + fd, + payload_ptr as usize, + len, + metadata.len() | flags.bits(), + metadata.as_ptr() as usize, + ) + } +} +pub fn sys_call_ro( + fd: usize, + payload: &mut [u8], + flags: CallFlags, + metadata: &[u64], +) -> Result { + unsafe { + raw_sys_call( + fd, + payload.as_mut_ptr(), + payload.len(), + flags | CallFlags::READ, + metadata, + ) + } +} +pub fn sys_call_wo(fd: usize, payload: &[u8], flags: CallFlags, metadata: &[u64]) -> Result { + unsafe { + raw_sys_call( + fd, + payload.as_ptr(), + payload.len(), + flags | CallFlags::WRITE, + metadata, + ) + } +} +pub fn sys_call_rw( + fd: usize, + payload: &mut [u8], + flags: CallFlags, + metadata: &[u64], +) -> Result { + unsafe { + raw_sys_call( + fd, + payload.as_mut_ptr(), + payload.len(), + flags | CallFlags::READ | CallFlags::WRITE, + metadata, + ) + } +} +pub fn sys_call( + fd: usize, + payload: &mut [u8], + flags: CallFlags, + metadata: &[u64], +) -> Result { + unsafe { raw_sys_call(fd, payload.as_mut_ptr(), payload.len(), flags, metadata) } +} +pub fn this_proc_call(payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result { + proc_call( + crate::current_proc_fd().as_raw_fd(), + payload, + flags, + metadata, + ) +} +pub fn proc_call( + proc_fd: usize, + payload: &mut [u8], + flags: CallFlags, + metadata: &[u64], +) -> Result { + sys_call(proc_fd, payload, flags, metadata) +} +pub fn thread_call( + thread_fd: usize, + payload: &mut [u8], + flags: CallFlags, + metadata: &[u64], +) -> Result { + sys_call(thread_fd, payload, flags, metadata) +} +pub fn this_thread_call(payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result { + thread_call( + RtTcb::current().thread_fd().as_raw_fd(), + payload, + flags, + metadata, + ) +} + +#[derive(Clone, Copy, Debug)] +pub enum WaitpidTarget { + AnyChild, + AnyGroupMember, + SingleProc { pid: usize }, + ProcGroup { pgid: usize }, +} +impl WaitpidTarget { + pub fn from_posix_arg(raw: isize) -> Self { + match raw { + 0 => Self::AnyGroupMember, + -1 => Self::AnyChild, + 1.. => Self::SingleProc { pid: raw as usize }, + ..-1 => Self::ProcGroup { + pgid: -raw as usize, + }, + } + } +} + +pub fn sys_waitpid(target: WaitpidTarget, status: &mut usize, flags: WaitFlags) -> Result { + let (call, pid) = match target { + WaitpidTarget::AnyChild => (ProcCall::Waitpid, 0), + WaitpidTarget::SingleProc { pid } => (ProcCall::Waitpid, pid), + WaitpidTarget::AnyGroupMember => (ProcCall::Waitpgid, 0), + WaitpidTarget::ProcGroup { pgid } => (ProcCall::Waitpgid, pgid), + }; + wrapper(true, false, || { + this_proc_call( + unsafe { plain::as_mut_bytes(status) }, + CallFlags::empty(), + &[call as u64, pid as u64, flags.bits() as u64], + ) + }) +} +pub fn posix_kill_thread(thread_fd: usize, signal: u32) -> Result<()> { + // TODO: don't hardcode? + if signal > 64 { + return Err(Error::new(EINVAL)); + } + + match wrapper(false, true, || { + thread_call( + thread_fd, + &mut [], + CallFlags::empty(), + &[ThreadCall::SignalThread as u64, signal.into()], + ) + }) { + Ok(_) | Err(Error { errno: ERESTART }) => Ok(()), + Err(error) => Err(error), + } +} + +static UMASK: AtomicU32 = AtomicU32::new(0o022); + +/// Controls the set of bits removed from the `mode` mask when new file descriptors are created. +/// +/// Must be validated by the caller +// +// TODO: validate here? +#[inline] +pub fn swap_umask(mask: u32) -> u32 { + UMASK.swap(mask, Ordering::AcqRel) +} + +#[inline] +pub fn get_umask() -> u32 { + UMASK.load(Ordering::Acquire) +} + +/// Real/Effective/Set-User/Group ID +pub struct Resugid { + pub ruid: T, + pub euid: T, + pub suid: T, + pub rgid: T, + pub egid: T, + pub sgid: T, +} + +/// Sets [res][ug]id, fields that are None will be unchanged. +pub fn posix_setresugid(ids: &Resugid>) -> Result<()> { + // TODO: not sure how "tmp" an IPC call is? + let _sig_guard = tmp_disable_signals(); + let mut guard = DYNAMIC_PROC_INFO.lock(); + + let mut buf = [0_u8; size_of::() * 6]; + plain::slice_from_mut_bytes(&mut buf) + .unwrap() + .copy_from_slice(&[ + ids.ruid.unwrap_or(u32::MAX), + ids.euid.unwrap_or(u32::MAX), + ids.suid.unwrap_or(u32::MAX), + ids.rgid.unwrap_or(u32::MAX), + ids.egid.unwrap_or(u32::MAX), + ids.sgid.unwrap_or(u32::MAX), + ]); + + this_proc_call(&mut buf, CallFlags::empty(), &[ProcCall::SetResugid as u64])?; + + if let Some(ruid) = ids.ruid { + guard.ruid = ruid; + } + if let Some(euid) = ids.euid { + guard.euid = euid; + } + if let Some(suid) = ids.suid { + guard.suid = suid; + } + if let Some(rgid) = ids.rgid { + guard.rgid = rgid; + } + if let Some(egid) = ids.egid { + guard.egid = egid; + } + if let Some(sgid) = ids.sgid { + guard.sgid = sgid; + } + + Ok(()) +} +pub fn posix_getresugid() -> Resugid { + let _sig_guard = tmp_disable_signals(); + let DynamicProcInfo { + ruid, + euid, + suid, + rgid, + egid, + sgid, + .. + } = *DYNAMIC_PROC_INFO.lock(); + Resugid { + ruid, + euid, + suid, + rgid, + egid, + sgid, + } +} +pub fn getens() -> Result { + read_proc_meta(crate::current_proc_fd()).map(|meta| meta.ens as usize) +} +pub fn get_proc_credentials(cap_fd: usize, target_pid: usize, buf: &mut [u8]) -> Result { + if buf.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + proc_call( + cap_fd, + buf, + CallFlags::empty(), + &[ProcCall::GetProcCredentials as u64, target_pid as u64], + ) +} +pub fn posix_exit(status: i32) -> ! { + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::Exit as u64, (status & 0xFF) as u64], + ) + .expect("failed to call proc mgr with Exit"); + let _ = syscall::write(1, b"redox-rt: ProcCall::Exit FAILED, abort()ing!\n"); + core::intrinsics::abort(); +} +pub fn posix_getpgid(pid: usize) -> Result { + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::Setpgid as u64, pid as u64, u64::wrapping_neg(1)], + ) +} +pub fn posix_setpgid(pid: usize, pgid: usize) -> Result<()> { + if pgid == usize::wrapping_neg(1) { + return Err(Error::new(EINVAL)); + } + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::Setpgid as u64, pid as u64, pgid as u64], + )?; + Ok(()) +} +pub fn posix_getsid(pid: usize) -> Result { + this_proc_call( + &mut [], + CallFlags::empty(), + &[ProcCall::Getsid as u64, pid as u64], + ) +} +pub fn posix_setsid() -> Result { + this_proc_call(&mut [], CallFlags::empty(), &[ProcCall::Setsid as u64])?; + Ok(posix_getpid()) +} +pub fn posix_nanosleep(rqtp: &TimeSpec, rmtp: &mut TimeSpec) -> Result<()> { + wrapper(false, false, || syscall::nanosleep(rqtp, rmtp))?; + Ok(()) +} +pub fn setns(fd: usize) -> Option { + let mut info = DYNAMIC_PROC_INFO.lock(); + let new_fd_guard = FdGuard::new(fd).to_upper().unwrap(); + let old_fd_guard = replace(&mut info.ns_fd, Some(new_fd_guard)); + old_fd_guard +} +pub fn getns() -> Result { + let cur_ns = crate::current_namespace_fd()?; + if cur_ns == usize::MAX { + Err(Error::new(ENODEV)) + } else { + Ok(cur_ns) + } +} +pub fn open>(path: T, flags: usize) -> Result { + let path = path.as_ref(); + let fcntl_flags = flags & syscall::O_FCNTL_MASK; + unsafe { + syscall::syscall5( + syscall::SYS_OPENAT, + crate::current_namespace_fd()?, + path.as_ptr() as usize, + path.len(), + flags, + fcntl_flags, + ) + } +} +pub fn openat>( + fd: usize, + path: T, + flags: usize, + fcntl_flags: usize, +) -> Result { + let path = path.as_ref(); + unsafe { + syscall::syscall5( + syscall::SYS_OPENAT, + fd, + path.as_ptr() as usize, + path.len(), + flags, + fcntl_flags, + ) + } +} +pub fn unlink>(path: T, flags: usize) -> Result { + let path = path.as_ref(); + unsafe { + syscall::syscall4( + syscall::SYS_UNLINKAT, + crate::current_namespace_fd()?, + path.as_ptr() as usize, + path.len(), + flags, + ) + } +} +pub fn mkns(names: &[IoSlice]) -> Result { + let mut buf = Vec::from((NsDup::ForkNs as usize).to_ne_bytes()); + for name in names { + let name_bytes = name.as_slice(); + let len = name_bytes.len(); + let _scheme_name = core::str::from_utf8(name_bytes).map_err(|_| Error::new(EINVAL))?; + buf.extend_from_slice(&len.to_ne_bytes()); + buf.extend_from_slice(name_bytes); + } + FdGuard::new(syscall::dup(crate::current_namespace_fd()?, &buf)?).to_upper() +} +pub fn register_scheme_to_ns(ns_fd: usize, name: &str, cap_fd: usize) -> Result<()> { + let mut buf = alloc::vec::Vec::from((NsDup::IssueRegister as usize).to_ne_bytes()); + buf.extend_from_slice(name.as_bytes()); + let ns_this_scheme = FdGuard::new(syscall::dup(ns_fd, &buf)?); + let cap_bytes = cap_fd.to_ne_bytes(); + ns_this_scheme.call_wo(&cap_bytes, CallFlags::FD, &[])?; + Ok(()) +} +pub fn std_fs_call_ro(fd: usize, payload: &mut [u8], metadata: &StdFsCallMeta) -> Result { + sys_call_ro(fd, payload, CallFlags::STD_FS, metadata) +} +pub fn std_fs_call_wo(fd: usize, payload: &[u8], metadata: &StdFsCallMeta) -> Result { + sys_call_wo(fd, payload, CallFlags::STD_FS, metadata) +} +pub fn std_fs_call_rw(fd: usize, payload: &mut [u8], metadata: &StdFsCallMeta) -> Result { + sys_call_rw(fd, payload, CallFlags::STD_FS, metadata) +} +pub fn fstat(fd: usize, stat: &mut syscall::Stat) -> Result { + std_fs_call_ro(fd, stat, &StdFsCallMeta::new(StdFsCallKind::Fstat, 0, 0)) +} + +pub fn fcntl(fd: usize, cmd: usize, arg: usize) -> Result { + let _siglock = tmp_disable_signals(); + let raw_fd = syscall::fcntl(fd, cmd, arg)?; + Ok(FdGuard::new(raw_fd).as_raw_fd()) +} diff --git a/redox-rt/src/thread.rs b/redox-rt/src/thread.rs new file mode 100644 index 0000000000..9ad1d4615a --- /dev/null +++ b/redox-rt/src/thread.rs @@ -0,0 +1,70 @@ +use core::mem::size_of; + +use syscall::Result; + +use crate::{RtTcb, arch::*, proc::*, signal::tmp_disable_signals, static_proc_info}; + +/// Spawns a new context sharing the same address space as the current one (i.e. a new thread). +pub unsafe fn rlct_clone_impl(stack: *mut usize, tcb: &RtTcb) -> Result { + let proc_info = static_proc_info(); + let cur_proc_fd = proc_info.proc_fd.as_ref().unwrap(); + + let cur_thr_fd = RtTcb::current().thread_fd(); + let new_thr_fd = cur_proc_fd.dup(b"new-thread")?.to_upper().unwrap(); + + // Inherit existing address space + { + let cur_addr_space_fd = cur_thr_fd.dup(b"addrspace")?; + let new_addr_space_sel_fd = new_thr_fd.dup(b"current-addrspace")?; + + let buf = create_set_addr_space_buf( + cur_addr_space_fd.as_raw_fd(), + __relibc_internal_rlct_clone_ret as *const () as usize, + stack as usize, + ); + new_addr_space_sel_fd.write(&buf)?; + } + + // Inherit reference to file table + { + let cur_filetable_fd = cur_thr_fd.dup(b"filetable")?; + let new_filetable_sel_fd = new_thr_fd.dup(b"current-filetable")?; + + new_filetable_sel_fd.write(&usize::to_ne_bytes(cur_filetable_fd.as_raw_fd()))?; + } + + // Since the signal handler is not yet initialized, signals specifically targeting the thread + // (relibc is only required to implement thread-specific signals that already originate from + // the same process) will be discarded. Process-specific signals will ignore this new thread, + // until it has initialized its own signal handler. + + let start_fd = new_thr_fd.dup(b"start")?; + + let fd = new_thr_fd.as_raw_fd(); + unsafe { + tcb.thr_fd.get().write(Some(new_thr_fd)); + } + + // Unblock context. + start_fd.write(&[0])?; + + Ok(fd) +} + +pub unsafe fn exit_this_thread(stack_base: *mut (), stack_size: usize) -> ! { + let _guard = tmp_disable_signals(); + + let tcb = RtTcb::current(); + // TODO: modify interface so it writes directly to the thread fd? + let status_fd = tcb.thread_fd().dup(b"status").unwrap(); + + let _ = unsafe { syscall::funmap(tcb as *const RtTcb as usize, syscall::PAGE_SIZE) }; + + let mut buf = [0; size_of::() * 3]; + plain::slice_from_mut_bytes(&mut buf) + .unwrap() + .copy_from_slice(&[usize::MAX, stack_base as usize, stack_size]); + // TODO: SYS_CALL w/CONSUME + status_fd.write(&buf).unwrap(); + unreachable!() +} diff --git a/redoxer.sh b/redoxer.sh deleted file mode 100755 index fcb352c742..0000000000 --- a/redoxer.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if ! which redoxer -then - cargo install redoxer -fi - -if [ ! -d "$HOME/.redoxer/toolchain" ] -then - redoxer toolchain -fi - -export CARGO_TEST="redoxer" -export TEST_RUNNER="redoxer exec --folder . -- sh --" -redoxer env make "$@" diff --git a/renamesyms.sh b/renamesyms.sh new file mode 100755 index 0000000000..4ab73f8d54 --- /dev/null +++ b/renamesyms.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -e + +target=$1 +deps_dir=$2 + +if [ -z "$target" ] || [ -z "$deps_dir" ]; then + echo "Usage:\n\t./renamesyms.sh TARGET DEPS_DIR" + exit 1 +fi + +if [ ! -f "$target" ]; then + echo "Target file '$target' does not exist" + exit 1 +fi +if [ ! -d "$deps_dir" ] ; then + echo "Deps dir '$deps_dir' does not exist or not a directory" + exit 1 +fi + +symbols_file=`mktemp` +special_syms=( + __rdl_oom + __rg_alloc + __rg_alloc_zeroed + __rg_dealloc + __rg_oom + __rg_realloc + __rust_alloc + __rust_alloc_error_handler + __rust_alloc_error_handler_should_panic + __rust_alloc_zeroed + __rust_dealloc + __rust_no_alloc_shim_is_unstable + __rust_realloc +) + +for dep in `find $deps_dir -type f -name "*.rlib"`; do + "${NM}" --format=posix -g "$dep" 2>/dev/null | sed 's/.*:.*//g' | awk '{if ($2 == "T") print $1}' | sed 's/^\(.*\)$/\1 __relibc_\1/g' >> $symbols_file +done + +for special_sym in "${special_syms[@]}"; do + echo "$special_sym __relibc_$special_sym" >> $symbols_file +done + +mangled_alloc_syms=$("${NM}" --format=posix -g "$target" 2>/dev/null | awk '{print $1}' | grep "7___rustc" || true) +for sym in $mangled_alloc_syms; do + echo "$sym __relibc_$sym" >> $symbols_file +done + +sorted_file=`mktemp` +sort -u "$symbols_file" > "$sorted_file" +rm -f "$symbols_file" + +"${OBJCOPY}" --redefine-syms="$sorted_file" "$target" + +rm -f "$sorted_file" diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 40973dae63..0000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2020-07-27 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..b3394ccad7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-11-15" +components = ["rust-src"] diff --git a/rustfmt.toml b/rustfmt.toml index 880d52b6a9..3a0fc9168d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,24 +1,21 @@ -max_width = 100 -hard_tabs = false -tab_spaces = 4 -newline_style = "Unix" -indent_style = "Block" -format_strings = false +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 +brace_style = "SameLineWhere" +disable_all_formatting = false +edition = "2024" empty_item_single_line = true fn_single_line = false -where_single_line = false +force_explicit_abi = true +format_strings = false +hard_tabs = false +imports_granularity = "Crate" imports_indent = "Block" imports_layout = "Mixed" -fn_args_density = "Tall" -brace_style = "SameLineWhere" -trailing_comma = "Vertical" -blank_lines_upper_bound = 1 -blank_lines_lower_bound = 0 -force_explicit_abi = true -disable_all_formatting = false +indent_style = "Block" +max_width = 100 +newline_style = "Unix" +show_parse_errors = true skip_children = false -hide_parse_errors = false -report_todo = "Never" -report_fixme = "Never" -edition = "2018" -merge_imports = true +tab_spaces = 4 +trailing_comma = "Vertical" +where_single_line = false diff --git a/src/c/fcntl.c b/src/c/fcntl.c deleted file mode 100644 index 23c48fa12a..0000000000 --- a/src/c/fcntl.c +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include - -// TODO: Can be implemented in rust when cbindgen supports "..." syntax - -int sys_open(const char* filename, int flags, mode_t mode); - -int open(const char* filename, int flags, ...) { - mode_t mode = 0; - va_list ap; - va_start(ap, flags); - mode = va_arg(ap, mode_t); - va_end(ap); - return sys_open(filename, flags, mode); -} - -int sys_fcntl(int fildes, int cmd, int args); - -int fcntl(int fildes, int cmd, ...) { - int args = 0; - va_list ap; - va_start(ap, cmd); - args = va_arg(ap, int); - va_end(ap); - return sys_fcntl(fildes, cmd, args); -} diff --git a/src/c/ptrace.c b/src/c/ptrace.c deleted file mode 100644 index 513cb5d480..0000000000 --- a/src/c/ptrace.c +++ /dev/null @@ -1,13 +0,0 @@ -// TODO: Can be implemented in rust when cbindgen supports "..." syntax - -#include - -int sys_ptrace(int request, va_list ap); - -int ptrace(int request, ...) { - va_list ap; - va_start(ap, request); - int ret = sys_ptrace(request, ap); - va_end(ap); - return ret; -} diff --git a/src/c/stack_chk.c b/src/c/stack_chk.c deleted file mode 100644 index 8574090818..0000000000 --- a/src/c/stack_chk.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -void abort(); - -uintptr_t __stack_chk_guard = 0xd048c37519fcadfe; - -__attribute__((noreturn)) -void __stack_chk_fail(void) { - abort(); -} diff --git a/src/c/stdio.c b/src/c/stdio.c deleted file mode 100644 index 196ec6aca9..0000000000 --- a/src/c/stdio.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -typedef struct FILE FILE; - -// TODO: Can be implemented in rust when cbindgen supports "..." syntax - -int vasprintf(char ** strp, const char * fmt, va_list ap); - -int asprintf(char ** strp, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vasprintf(strp, fmt, ap); - va_end(ap); - return ret; -} - -int vfprintf(FILE * stream, const char * fmt, va_list ap); - -int fprintf(FILE * stream, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vfprintf(stream, fmt, ap); - va_end(ap); - return ret; -} - -int vprintf(const char * fmt, va_list ap); - -int printf(const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vprintf(fmt, ap); - va_end(ap); - return ret; -} - -int vsnprintf(char * s, size_t n, const char * fmt, va_list ap); - -int snprintf(char * s, size_t n, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vsnprintf(s, n, fmt, ap); - va_end(ap); - return ret; -} - -int vsprintf(char * s, const char * fmt, va_list ap); - -int sprintf(char *s, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vsprintf(s, fmt, ap); - va_end(ap); - return ret; -} - -int vfscanf(FILE * stream, const char * fmt, va_list ap); - -int fscanf(FILE * stream, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vfscanf(stream, fmt, ap); - va_end(ap); - return ret; -} - -int vscanf(const char * fmt, va_list ap); - -int scanf(const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vscanf(fmt, ap); - va_end(ap); - return ret; -} - -int vsscanf(const char * input, const char * fmt, va_list ap); - -int sscanf(const char * input, const char * fmt, ...) { - int ret; - va_list ap; - va_start(ap, fmt); - ret = vsscanf(input, fmt, ap); - va_end(ap); - return ret; -} diff --git a/src/c/stdlib.c b/src/c/stdlib.c index 39e4fb0f44..62e98108bb 100644 --- a/src/c/stdlib.c +++ b/src/c/stdlib.c @@ -3,3 +3,11 @@ double strtod(const char *nptr, char **endptr); long double strtold(const char *nptr, char **endptr) { return (long double)strtod(nptr, endptr); } + +double relibc_ldtod(const long double* val) { + return (double)(*val); +} + +void relibc_dtold(double val, long double* out) { + *out = (long double)val; +} diff --git a/src/c/unistd.c b/src/c/unistd.c deleted file mode 100644 index ac2629e1da..0000000000 --- a/src/c/unistd.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -// TODO: Can be implemented in rust when cbindgen supports "..." syntax - -int execv(const char *path, char *const *argv); - -int execl(const char *path, const char* argv0, ...) -{ - int argc; - va_list ap; - va_start(ap, argv0); - for (argc = 1; va_arg(ap, const char*); argc++); - va_end(ap); - { - int i; - char *argv[argc+1]; - va_start(ap, argv0); - argv[0] = (char *)argv0; - for (i = 1; i < argc; i++) { - argv[i] = va_arg(ap, char *); - } - argv[i] = NULL; - va_end(ap); - return execv(path, argv); - } -} - -int execve(const char *path, char *const *argv, char *const *envp); - -int execle(const char *path, const char* argv0, ...) -{ - int argc; - va_list ap; - va_start(ap, argv0); - for (argc = 1; va_arg(ap, const char *); argc++); - va_end(ap); - { - int i; - char *argv[argc+1]; - char **envp; - va_start(ap, argv0); - argv[0] = (char *)argv0; - for (i = 1; i <= argc; i++) { - argv[i] = va_arg(ap, char *); - } - envp = va_arg(ap, char **); - va_end(ap); - return execve(path, argv, envp); - } -} - -int execvp(const char *file, char *const *argv); - -int execlp(const char *file, const char* argv0, ...) -{ - int argc; - va_list ap; - va_start(ap, argv0); - for (argc = 1; va_arg(ap, const char*); argc++); - va_end(ap); - { - int i; - char *argv[argc+1]; - va_start(ap, argv0); - argv[0] = (char *)argv0; - for (i = 1; i < argc; i++) { - argv[i] = va_arg(ap, char *); - } - argv[i] = NULL; - va_end(ap); - return execvp(file, argv); - } -} diff --git a/src/c_str.rs b/src/c_str.rs index 3d28d49d00..37880314a7 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -1,1255 +1,322 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. +//! Nul-terminated byte strings. -use alloc::{ - borrow::{Borrow, Cow, ToOwned}, - boxed::Box, - rc::Rc, - string::String, - sync::Arc, - vec::Vec, -}; -use core::{ - ascii, - cmp::Ordering, - fmt::{self, Write}, - mem, ops, ptr, slice, - str::{self, Utf8Error}, -}; +use core::{marker::PhantomData, ptr::NonNull, str::Utf8Error}; -use crate::{header::string::strlen, platform::types::*}; +use alloc::{borrow::Cow, string::String}; -pub fn memchr(needle: u8, haystack: &[u8]) -> Option { - use crate::header::string; +use crate::platform::types::{c_char, wchar_t}; - let p = unsafe { - string::memchr( - haystack.as_ptr() as *const c_void, - needle as c_int, - haystack.len(), - ) - }; - if p.is_null() { +mod private { + pub trait Sealed {} +} +#[derive(Clone, Copy, Debug)] +pub enum Thin {} + +#[derive(Clone, Copy, Debug)] +pub enum Wide {} + +impl private::Sealed for Thin {} +impl private::Sealed for Wide {} + +pub trait Kind: private::Sealed + Copy + 'static { + /// c_char or wchar_t + type C: Copy + 'static; + // u8 or u32 + type Char: Copy + From + Into + PartialEq + 'static; + + const NUL: Self::Char; + + const IS_THIN_NOT_WIDE: bool; + + fn r2c(c: Self::Char) -> Self::C; + fn c2r(c: Self::C) -> Self::Char; + + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>; + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>; + + unsafe fn strlen(s: *const Self::C) -> usize; + unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C; + unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C; +} +impl Kind for Thin { + type C = c_char; + type Char = u8; + + const NUL: Self::Char = 0; + const IS_THIN_NOT_WIDE: bool = true; + + unsafe fn strlen(s: *const c_char) -> usize { + unsafe { crate::header::string::strlen(s) } + } + unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char { + unsafe { crate::header::string::strchr(s, c.into()) } + } + unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char { + unsafe { crate::header::string::strchrnul(s, c.into()) } + } + fn r2c(c: u8) -> c_char { + c as _ + } + fn c2r(c: c_char) -> u8 { + c as _ + } + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { + Some(b) + } + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { + Some(c) + } +} +impl Kind for Wide { + type C = wchar_t; + type Char = u32; + + const NUL: Self::Char = 0; + const IS_THIN_NOT_WIDE: bool = false; + + unsafe fn strlen(s: *const Self::C) -> usize { + unsafe { crate::header::wchar::wcslen(s) } + } + unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C { + unsafe { crate::header::wchar::wcschr(s, c) } + } + unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C { + // TODO: optimized function + while unsafe { s.read() } != c && unsafe { s.read() } != 0 { + s = unsafe { s.add(1) }; + } + s + } + fn r2c(c: Self::Char) -> Self::C { + c as _ + } + fn c2r(c: Self::C) -> Self::Char { + c as _ + } + fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { + None + } + fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { None - } else { - Some(p as usize - (haystack.as_ptr() as usize)) } } -/// A type representing an owned, C-compatible, nul-terminated string with no nul bytes in the -/// middle. -/// -/// This type serves the purpose of being able to safely generate a -/// C-compatible string from a Rust byte slice or vector. An instance of this -/// type is a static guarantee that the underlying bytes contain no interior 0 -/// bytes ("nul characters") and that the final byte is 0 ("nul terminator"). -/// -/// `CString` is to [`CStr`] as [`String`] is to [`&str`]: the former -/// in each pair are owned strings; the latter are borrowed -/// references. -/// -/// # Creating a `CString` -/// -/// A `CString` is created from either a byte slice or a byte vector, -/// or anything that implements [`Into`]`<`[`Vec`]`<`[`u8`]`>>` (for -/// example, you can build a `CString` straight out of a [`String`] or -/// a [`&str`], since both implement that trait). -/// -/// The [`new`] method will actually check that the provided `&[u8]` -/// does not have 0 bytes in the middle, and return an error if it -/// finds one. -/// -/// # Extracting a raw pointer to the whole C string -/// -/// `CString` implements a [`as_ptr`] method through the [`Deref`] -/// trait. This method will give you a `*const c_char` which you can -/// feed directly to extern functions that expect a nul-terminated -/// string, like C's `strdup()`. -/// -/// # Extracting a slice of the whole C string -/// -/// Alternatively, you can obtain a `&[`[`u8`]`]` slice from a -/// `CString` with the [`as_bytes`] method. Slices produced in this -/// way do *not* contain the trailing nul terminator. This is useful -/// when you will be calling an extern function that takes a `*const -/// u8` argument which is not necessarily nul-terminated, plus another -/// argument with the length of the string — like C's `strndup()`. -/// You can of course get the slice's length with its -/// [`len`][slice.len] method. -/// -/// If you need a `&[`[`u8`]`]` slice *with* the nul terminator, you -/// can use [`as_bytes_with_nul`] instead. -/// -/// Once you have the kind of slice you need (with or without a nul -/// terminator), you can call the slice's own -/// [`as_ptr`][slice.as_ptr] method to get a raw pointer to pass to -/// extern functions. See the documentation for that function for a -/// discussion on ensuring the lifetime of the raw pointer. -/// -/// [`Into`]: ../convert/trait.Into.html -/// [`Vec`]: ../vec/struct.Vec.html -/// [`String`]: ../string/struct.String.html -/// [`&str`]: ../primitive.str.html -/// [`u8`]: ../primitive.u8.html -/// [`new`]: #method.new -/// [`as_bytes`]: #method.as_bytes -/// [`as_bytes_with_nul`]: #method.as_bytes_with_nul -/// [`as_ptr`]: #method.as_ptr -/// [slice.as_ptr]: ../primitive.slice.html#method.as_ptr -/// [slice.len]: ../primitive.slice.html#method.len -/// [`Deref`]: ../ops/trait.Deref.html -/// [`CStr`]: struct.CStr.html -/// -/// # Examples -/// -/// ```ignore (extern-declaration) -/// # fn main() { -/// use std::ffi::CString; -/// use std::os::raw::c_char; -/// -/// extern { -/// fn my_printer(s: *const c_char); -/// } -/// -/// // We are certain that our string doesn't have 0 bytes in the middle, -/// // so we can .unwrap() -/// let c_to_print = CString::new("Hello, world!").unwrap(); -/// unsafe { -/// my_printer(c_to_print.as_ptr()); -/// } -/// # } -/// ``` -/// -/// # Safety -/// -/// `CString` is intended for working with traditional C-style strings -/// (a sequence of non-nul bytes terminated by a single nul byte); the -/// primary use case for these kinds of strings is interoperating with C-like -/// code. Often you will need to transfer ownership to/from that external -/// code. It is strongly recommended that you thoroughly read through the -/// documentation of `CString` before use, as improper ownership management -/// of `CString` instances can lead to invalid memory accesses, memory leaks, -/// and other memory errors. - -#[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Clone)] -pub struct CString { - // Invariant 1: the slice ends with a zero byte and has a length of at least one. - // Invariant 2: the slice contains only one zero byte. - // Improper usage of unsafe function can break Invariant 2, but not Invariant 1. - inner: Box<[u8]>, +/// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct NulStr<'a, T: Kind> { + ptr: NonNull, + _marker: PhantomData<&'a [u8]>, } +pub type CStr<'a> = NulStr<'a, Thin>; +pub type WStr<'a> = NulStr<'a, Wide>; -/// Representation of a borrowed C string. -/// -/// This type represents a borrowed reference to a nul-terminated -/// array of bytes. It can be constructed safely from a `&[`[`u8`]`]` -/// slice, or unsafely from a raw `*const c_char`. It can then be -/// converted to a Rust [`&str`] by performing UTF-8 validation, or -/// into an owned [`CString`]. -/// -/// `CStr` is to [`CString`] as [`&str`] is to [`String`]: the former -/// in each pair are borrowed references; the latter are owned -/// strings. -/// -/// Note that this structure is **not** `repr(C)` and is not recommended to be -/// placed in the signatures of FFI functions. Instead, safe wrappers of FFI -/// functions may leverage the unsafe [`from_ptr`] constructor to provide a safe -/// interface to other consumers. -/// -/// # Examples -/// -/// Inspecting a foreign C string: -/// -/// ```ignore (extern-declaration) -/// use std::ffi::CStr; -/// use std::os::raw::c_char; -/// -/// extern { fn my_string() -> *const c_char; } -/// -/// unsafe { -/// let slice = CStr::from_ptr(my_string()); -/// println!("string buffer size without nul terminator: {}", slice.to_bytes().len()); -/// } -/// ``` -/// -/// Passing a Rust-originating C string: -/// -/// ```ignore (extern-declaration) -/// use std::ffi::{CString, CStr}; -/// use std::os::raw::c_char; -/// -/// fn work(data: &CStr) { -/// extern { fn work_with(data: *const c_char); } -/// -/// unsafe { work_with(data.as_ptr()) } -/// } -/// -/// let s = CString::new("data data data data").unwrap(); -/// work(&s); -/// ``` -/// -/// Converting a foreign C string into a Rust [`String`]: -/// -/// ```ignore (extern-declaration) -/// use std::ffi::CStr; -/// use std::os::raw::c_char; -/// -/// extern { fn my_string() -> *const c_char; } -/// -/// fn my_string_safe() -> String { -/// unsafe { -/// CStr::from_ptr(my_string()).to_string_lossy().into_owned() -/// } -/// } -/// -/// println!("string: {}", my_string_safe()); -/// ``` -/// -/// [`u8`]: ../primitive.u8.html -/// [`&str`]: ../primitive.str.html -/// [`String`]: ../string/struct.String.html -/// [`CString`]: struct.CString.html -/// [`from_ptr`]: #method.from_ptr -#[derive(Hash)] -pub struct CStr { - // FIXME: this should not be represented with a DST slice but rather with - // just a raw `c_char` along with some form of marker to make - // this an unsized type. Essentially `sizeof(&CStr)` should be the - // same as `sizeof(&c_char)` but `CStr` should be an unsized type. - inner: [c_char], -} - -/// An error indicating that an interior nul byte was found. -/// -/// While Rust strings may contain nul bytes in the middle, C strings -/// can't, as that byte would effectively truncate the string. -/// -/// This error is created by the [`new`][`CString::new`] method on -/// [`CString`]. See its documentation for more. -/// -/// [`CString`]: struct.CString.html -/// [`CString::new`]: struct.CString.html#method.new -/// -/// # Examples -/// -/// ``` -/// use std::ffi::{CString, NulError}; -/// -/// let _: NulError = CString::new(b"f\0oo".to_vec()).unwrap_err(); -/// ``` -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct NulError(usize, Vec); - -/// An error indicating that a nul byte was not in the expected position. -/// -/// The slice used to create a [`CStr`] must have one and only one nul -/// byte at the end of the slice. -/// -/// This error is created by the -/// [`from_bytes_with_nul`][`CStr::from_bytes_with_nul`] method on -/// [`CStr`]. See its documentation for more. -/// -/// [`CStr`]: struct.CStr.html -/// [`CStr::from_bytes_with_nul`]: struct.CStr.html#method.from_bytes_with_nul -/// -/// # Examples -/// -/// ``` -/// use std::ffi::{CStr, FromBytesWithNulError}; -/// -/// let _: FromBytesWithNulError = CStr::from_bytes_with_nul(b"f\0oo").unwrap_err(); -/// ``` -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct FromBytesWithNulError { - kind: FromBytesWithNulErrorKind, -} - -#[derive(Clone, PartialEq, Eq, Debug)] -enum FromBytesWithNulErrorKind { - InteriorNul(usize), - NotNulTerminated, -} - -impl FromBytesWithNulError { - fn interior_nul(pos: usize) -> FromBytesWithNulError { - FromBytesWithNulError { - kind: FromBytesWithNulErrorKind::InteriorNul(pos), +impl<'a, T: Kind> NulStr<'a, T> { + /// Safety + /// + /// The ptr must be valid up to and including the first NUL byte from the base ptr. + pub const unsafe fn from_ptr(ptr: *const T::C) -> Self { + Self { + ptr: unsafe { NonNull::new_unchecked(ptr.cast_mut()) }, + _marker: PhantomData, } } - fn not_nul_terminated() -> FromBytesWithNulError { - FromBytesWithNulError { - kind: FromBytesWithNulErrorKind::NotNulTerminated, + pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option { + if ptr.is_null() { + None + } else { + Some(unsafe { Self::from_ptr(ptr) }) } } + /// Look for the closest occurence of `c`, and if found, split the string into a slice up to + /// that byte and a `CStr` starting at that byte. + #[inline] + #[doc(alias = "strchrnul")] + pub fn find_get_subslice_or_all( + self, + c: impl Into, + ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> { + let c = c.into(); - fn description(&self) -> &str { - match self.kind { - FromBytesWithNulErrorKind::InteriorNul(..) => { - "data provided contains an interior nul byte" - } - FromBytesWithNulErrorKind::NotNulTerminated => "data provided is not nul terminated", + // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL + // byte + let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) }; + + // SAFETY: the pointer returned from strchrnul is always a substring of this string, and + // hence always valid as a CStr. + let found = unsafe { Self::from_ptr(found) }; + let until = unsafe { self.slice_until_substr(found) }; + + if found.first() == T::NUL { + // The character was not found, and we got the end of the string instead. + Err((until, found)) + } else { + Ok((until, found)) } } -} - -/// An error indicating invalid UTF-8 when converting a [`CString`] into a [`String`]. -/// -/// `CString` is just a wrapper over a buffer of bytes with a nul -/// terminator; [`into_string`][`CString::into_string`] performs UTF-8 -/// validation on those bytes and may return this error. -/// -/// This `struct` is created by the -/// [`into_string`][`CString::into_string`] method on [`CString`]. See -/// its documentation for more. -/// -/// [`String`]: ../string/struct.String.html -/// [`CString`]: struct.CString.html -/// [`CString::into_string`]: struct.CString.html#method.into_string -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct IntoStringError { - inner: CString, - error: Utf8Error, -} - -impl CString { - /// Creates a new C-compatible string from a container of bytes. - /// - /// This function will consume the provided data and use the - /// underlying bytes to construct a new string, ensuring that - /// there is a trailing 0 byte. This trailing 0 byte will be - /// appended by this function; the provided data should *not* - /// contain any 0 bytes in it. - /// - /// # Examples - /// - /// ```ignore (extern-declaration) - /// use std::ffi::CString; - /// use std::os::raw::c_char; - /// - /// extern { fn puts(s: *const c_char); } - /// - /// let to_print = CString::new("Hello!").unwrap(); - /// unsafe { - /// puts(to_print.as_ptr()); - /// } - /// ``` - /// - /// # Errors - /// - /// This function will return an error if the supplied bytes contain an - /// internal 0 byte. The [`NulError`] returned will contain the bytes as well as - /// the position of the nul byte. - /// - /// [`NulError`]: struct.NulError.html - pub fn new>>(t: T) -> Result { - Self::_new(t.into()) - } - - fn _new(bytes: Vec) -> Result { - match memchr(0, &bytes) { - Some(i) => Err(NulError(i, bytes)), - None => Ok(unsafe { CString::from_vec_unchecked(bytes) }), - } - } - - /// Creates a C-compatible string by consuming a byte vector, - /// without checking for interior 0 bytes. - /// - /// This method is equivalent to [`new`] except that no runtime assertion - /// is made that `v` contains no 0 bytes, and it requires an actual - /// byte vector, not anything that can be converted to one with Into. - /// - /// [`new`]: #method.new - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let raw = b"foo".to_vec(); - /// unsafe { - /// let c_string = CString::from_vec_unchecked(raw); - /// } - /// ``` - pub unsafe fn from_vec_unchecked(mut v: Vec) -> CString { - v.reserve_exact(1); - v.push(0); - CString { - inner: v.into_boxed_slice(), - } - } - - /// Retakes ownership of a `CString` that was transferred to C via [`into_raw`]. - /// - /// Additionally, the length of the string will be recalculated from the pointer. - /// /// # Safety /// - /// This should only ever be called with a pointer that was earlier - /// obtained by calling [`into_raw`] on a `CString`. Other usage (e.g. trying to take - /// ownership of a string that was allocated by foreign code) is likely to lead - /// to undefined behavior or allocator corruption. - /// - /// > **Note:** If you need to borrow a string that was allocated by - /// > foreign code, use [`CStr`]. If you need to take ownership of - /// > a string that was allocated by foreign code, you will need to - /// > make your own provisions for freeing it appropriately, likely - /// > with the foreign code's API to do that. - /// - /// [`into_raw`]: #method.into_raw - /// [`CStr`]: struct.CStr.html - /// - /// # Examples - /// - /// Create a `CString`, pass ownership to an `extern` function (via raw pointer), then retake - /// ownership with `from_raw`: - /// - /// ```ignore (extern-declaration) - /// use std::ffi::CString; - /// use std::os::raw::c_char; - /// - /// extern { - /// fn some_extern_function(s: *mut c_char); - /// } - /// - /// let c_string = CString::new("Hello!").unwrap(); - /// let raw = c_string.into_raw(); - /// unsafe { - /// some_extern_function(raw); - /// let c_string = CString::from_raw(raw); - /// } - /// ``` - pub unsafe fn from_raw(ptr: *mut c_char) -> CString { - let len = strlen(ptr) + 1; // Including the NUL byte - let slice = slice::from_raw_parts_mut(ptr, len as usize); - CString { - inner: Box::from_raw(slice as *mut [c_char] as *mut [u8]), - } - } - - /// Consumes the `CString` and transfers ownership of the string to a C caller. - /// - /// The pointer which this function returns must be returned to Rust and reconstituted using - /// [`from_raw`] to be properly deallocated. Specifically, one - /// should *not* use the standard C `free()` function to deallocate - /// this string. - /// - /// Failure to call [`from_raw`] will lead to a memory leak. - /// - /// [`from_raw`]: #method.from_raw - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new("foo").unwrap(); - /// - /// let ptr = c_string.into_raw(); - /// - /// unsafe { - /// assert_eq!(b'f', *ptr as u8); - /// assert_eq!(b'o', *ptr.offset(1) as u8); - /// assert_eq!(b'o', *ptr.offset(2) as u8); - /// assert_eq!(b'\0', *ptr.offset(3) as u8); - /// - /// // retake pointer to free memory - /// let _ = CString::from_raw(ptr); - /// } - /// ``` + /// `substr` must be contained within `self` #[inline] - pub fn into_raw(self) -> *mut c_char { - Box::into_raw(self.into_inner()) as *mut c_char + pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] { + let index = unsafe { + // SAFETY: the sub-pointer as returned by strchr must be derived from the same + // allocation + substr.as_ptr().offset_from(self.as_ptr()) as usize + }; + unsafe { core::slice::from_raw_parts(self.as_ptr().cast::(), index) } } - - /// Converts the `CString` into a [`String`] if it contains valid UTF-8 data. - /// - /// On failure, ownership of the original `CString` is returned. - /// - /// [`String`]: ../string/struct.String.html - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let valid_utf8 = vec![b'f', b'o', b'o']; - /// let cstring = CString::new(valid_utf8).unwrap(); - /// assert_eq!(cstring.into_string().unwrap(), "foo"); - /// - /// let invalid_utf8 = vec![b'f', 0xff, b'o', b'o']; - /// let cstring = CString::new(invalid_utf8).unwrap(); - /// let err = cstring.into_string().err().unwrap(); - /// assert_eq!(err.utf8_error().valid_up_to(), 1); - /// ``` - - pub fn into_string(self) -> Result { - String::from_utf8(self.into_bytes()).map_err(|e| IntoStringError { - error: e.utf8_error(), - inner: unsafe { CString::from_vec_unchecked(e.into_bytes()) }, - }) - } - - /// Consumes the `CString` and returns the underlying byte buffer. - /// - /// The returned buffer does **not** contain the trailing nul - /// terminator, and it is guaranteed to not have any interior nul - /// bytes. - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new("foo").unwrap(); - /// let bytes = c_string.into_bytes(); - /// assert_eq!(bytes, vec![b'f', b'o', b'o']); - /// ``` - pub fn into_bytes(self) -> Vec { - let mut vec = self.into_inner().into_vec(); - let _nul = vec.pop(); - debug_assert_eq!(_nul, Some(0u8)); - vec - } - - /// Equivalent to the [`into_bytes`] function except that the returned vector - /// includes the trailing nul terminator. - /// - /// [`into_bytes`]: #method.into_bytes - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new("foo").unwrap(); - /// let bytes = c_string.into_bytes_with_nul(); - /// assert_eq!(bytes, vec![b'f', b'o', b'o', b'\0']); - /// ``` - pub fn into_bytes_with_nul(self) -> Vec { - self.into_inner().into_vec() - } - - /// Returns the contents of this `CString` as a slice of bytes. - /// - /// The returned slice does **not** contain the trailing nul - /// terminator, and it is guaranteed to not have any interior nul - /// bytes. If you need the nul terminator, use - /// [`as_bytes_with_nul`] instead. - /// - /// [`as_bytes_with_nul`]: #method.as_bytes_with_nul - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new("foo").unwrap(); - /// let bytes = c_string.as_bytes(); - /// assert_eq!(bytes, &[b'f', b'o', b'o']); - /// ``` + /// Look for the closest occurence of `c`, and if found, split the string into a slice up to + /// that byte and a `CStr` starting at that byte. #[inline] - pub fn as_bytes(&self) -> &[u8] { - &self.inner[..self.inner.len() - 1] - } + pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> { + let rest = self.find(c)?; - /// Equivalent to the [`as_bytes`] function except that the returned slice - /// includes the trailing nul terminator. - /// - /// [`as_bytes`]: #method.as_bytes - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new("foo").unwrap(); - /// let bytes = c_string.as_bytes_with_nul(); - /// assert_eq!(bytes, &[b'f', b'o', b'o', b'\0']); - /// ``` + // SAFETY: the output of strchr is obviously a substring if it doesn't return NULL + Some((unsafe { self.slice_until_substr(rest) }, rest)) + } + /// Look for the closest occurence of `c`, and return a new string starting at that byte if + /// found. + #[doc(alias = "strchr")] + #[doc(alias = "wcschr")] #[inline] - pub fn as_bytes_with_nul(&self) -> &[u8] { - &self.inner - } - - /// Extracts a [`CStr`] slice containing the entire string. - /// - /// [`CStr`]: struct.CStr.html - /// - /// # Examples - /// - /// ``` - /// use std::ffi::{CString, CStr}; - /// - /// let c_string = CString::new(b"foo".to_vec()).unwrap(); - /// let c_str = c_string.as_c_str(); - /// assert_eq!(c_str, CStr::from_bytes_with_nul(b"foo\0").unwrap()); - /// ``` - #[inline] - pub fn as_c_str(&self) -> &CStr { - &*self - } - - /// Converts this `CString` into a boxed [`CStr`]. - /// - /// [`CStr`]: struct.CStr.html - /// - /// # Examples - /// - /// ``` - /// use std::ffi::{CString, CStr}; - /// - /// let c_string = CString::new(b"foo".to_vec()).unwrap(); - /// let boxed = c_string.into_boxed_c_str(); - /// assert_eq!(&*boxed, CStr::from_bytes_with_nul(b"foo\0").unwrap()); - /// ``` - pub fn into_boxed_c_str(self) -> Box { - unsafe { Box::from_raw(Box::into_raw(self.into_inner()) as *mut CStr) } - } - - // Bypass "move out of struct which implements [`Drop`] trait" restriction. - /// - /// [`Drop`]: ../ops/trait.Drop.html - fn into_inner(self) -> Box<[u8]> { + pub fn find(self, c: T::Char) -> Option { unsafe { - let result = ptr::read(&self.inner); - mem::forget(self); - result + // SAFETY: the only requirement is for self.as_ptr() to be valid up to and including + // the nearest NUL byte, which this type requires + let ret = T::strchr(self.as_ptr(), T::r2c(c)); + // SAFETY: strchr must either return NULL (not found) or a substring of self, which can + // never exceed the nearest NUL byte of self + Self::from_nullable_ptr(ret) } } -} + // TODO: strrchr, strchrnul wrappers -// Turns this `CString` into an empty string to prevent -// memory unsafe code from working by accident. Inline -// to prevent LLVM from optimizing it away in debug builds. -impl Drop for CString { #[inline] - fn drop(&mut self) { + pub fn contains(self, c: T::Char) -> bool { + self.find(c).is_some() + } + #[inline] + pub fn first(self) -> T::Char { unsafe { - *self.inner.get_unchecked_mut(0) = 0; + // SAFETY: Self must be valid up to and including its nearest NUL byte, which certainly + // implies its readable length is nonzero (string is empty if this first byte is 0). + T::c2r(self.ptr.read()) } } -} - -impl ops::Deref for CString { - type Target = CStr; - #[inline] - fn deref(&self) -> &CStr { - unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) } + pub fn first_char(self) -> Option { + char::from_u32(self.first().into()) } -} - -impl fmt::Debug for CString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl From for Vec { - /// Converts a [`CString`] into a [`Vec`]``. - /// - /// The conversion consumes the [`CString`], and removes the terminating NUL byte. - /// - /// [`Vec`]: ../vec/struct.Vec.html - /// [`CString`]: ../ffi/struct.CString.html + /// Same as `split_first` except also requires that the first char be convertible into `char` #[inline] - fn from(s: CString) -> Vec { - s.into_bytes() + pub fn split_first_char(self) -> Option<(char, Self)> { + self.split_first() + .and_then(|(c, r)| Some((char::from_u32(c.into())?, r))) } -} - -impl fmt::Debug for CStr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"")?; - for byte in self - .to_bytes() - .iter() - .flat_map(|&b| ascii::escape_default(b)) - { - f.write_char(byte as char)?; + /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty. + #[inline] + pub fn split_first(self) -> Option<(T::Char, Self)> { + if self.first() == T::NUL { + return None; } - write!(f, "\"") + Some((self.first(), unsafe { + Self::from_ptr(self.as_ptr().add(1)) + })) } -} - -impl<'a> Default for &'a CStr { - fn default() -> &'a CStr { - const SLICE: &[c_char] = &[0]; - unsafe { CStr::from_ptr(SLICE.as_ptr()) } - } -} - -impl Default for CString { - /// Creates an empty `CString`. - fn default() -> CString { - let a: &CStr = Default::default(); - a.to_owned() - } -} - -impl Borrow for CString { - #[inline] - fn borrow(&self) -> &CStr { - self - } -} - -impl<'a> From> for CString { - #[inline] - fn from(s: Cow<'a, CStr>) -> Self { - s.into_owned() - } -} - -impl<'a> From<&'a CStr> for Box { - fn from(s: &'a CStr) -> Box { - let boxed: Box<[u8]> = Box::from(s.to_bytes_with_nul()); - unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr) } - } -} - -impl From> for CString { - /// Converts a [`Box`]`` into a [`CString`] without copying or allocating. - /// - /// [`Box`]: ../boxed/struct.Box.html - /// [`CString`]: ../ffi/struct.CString.html - #[inline] - fn from(s: Box) -> CString { - s.into_c_string() - } -} - -impl Clone for Box { - #[inline] - fn clone(&self) -> Self { - (**self).into() - } -} - -impl From for Box { - /// Converts a [`CString`] into a [`Box`]`` without copying or allocating. - /// - /// [`CString`]: ../ffi/struct.CString.html - /// [`Box`]: ../boxed/struct.Box.html - #[inline] - fn from(s: CString) -> Box { - s.into_boxed_c_str() - } -} - -impl<'a> From for Cow<'a, CStr> { - #[inline] - fn from(s: CString) -> Cow<'a, CStr> { - Cow::Owned(s) - } -} - -impl<'a> From<&'a CStr> for Cow<'a, CStr> { - #[inline] - fn from(s: &'a CStr) -> Cow<'a, CStr> { - Cow::Borrowed(s) - } -} - -impl<'a> From<&'a CString> for Cow<'a, CStr> { - #[inline] - fn from(s: &'a CString) -> Cow<'a, CStr> { - Cow::Borrowed(s.as_c_str()) - } -} - -impl From for Arc { - /// Converts a [`CString`] into a [`Arc`]`` without copying or allocating. - /// - /// [`CString`]: ../ffi/struct.CString.html - /// [`Arc`]: ../sync/struct.Arc.html - #[inline] - fn from(s: CString) -> Arc { - let arc: Arc<[u8]> = Arc::from(s.into_inner()); - unsafe { Arc::from_raw(Arc::into_raw(arc) as *const CStr) } - } -} - -impl<'a> From<&'a CStr> for Arc { - #[inline] - fn from(s: &CStr) -> Arc { - let arc: Arc<[u8]> = Arc::from(s.to_bytes_with_nul()); - unsafe { Arc::from_raw(Arc::into_raw(arc) as *const CStr) } - } -} - -impl From for Rc { - /// Converts a [`CString`] into a [`Rc`]`` without copying or allocating. - /// - /// [`CString`]: ../ffi/struct.CString.html - /// [`Rc`]: ../rc/struct.Rc.html - #[inline] - fn from(s: CString) -> Rc { - let rc: Rc<[u8]> = Rc::from(s.into_inner()); - unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr) } - } -} - -impl<'a> From<&'a CStr> for Rc { - #[inline] - fn from(s: &CStr) -> Rc { - let rc: Rc<[u8]> = Rc::from(s.to_bytes_with_nul()); - unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr) } - } -} - -impl Default for Box { - fn default() -> Box { - let boxed: Box<[u8]> = Box::from([0]); - unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr) } - } -} - -impl NulError { - /// Returns the position of the nul byte in the slice that caused - /// [`CString::new`] to fail. - /// - /// [`CString::new`]: struct.CString.html#method.new - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let nul_error = CString::new("foo\0bar").unwrap_err(); - /// assert_eq!(nul_error.nul_position(), 3); - /// - /// let nul_error = CString::new("foo bar\0").unwrap_err(); - /// assert_eq!(nul_error.nul_position(), 7); - /// ``` - pub fn nul_position(&self) -> usize { - self.0 - } - - /// Consumes this error, returning the underlying vector of bytes which - /// generated the error in the first place. - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let nul_error = CString::new("foo\0bar").unwrap_err(); - /// assert_eq!(nul_error.into_vec(), b"foo\0bar"); - /// ``` - pub fn into_vec(self) -> Vec { - self.1 - } -} - -impl fmt::Display for NulError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "nul byte found in provided data at position: {}", self.0) - } -} - -impl fmt::Display for FromBytesWithNulError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.description())?; - if let FromBytesWithNulErrorKind::InteriorNul(pos) = self.kind { - write!(f, " at byte pos {}", pos)?; - } - Ok(()) - } -} - -impl IntoStringError { - /// Consumes this error, returning original [`CString`] which generated the - /// error. - /// - /// [`CString`]: struct.CString.html - pub fn into_cstring(self) -> CString { - self.inner - } - - /// Access the underlying UTF-8 error that was the cause of this error. - pub fn utf8_error(&self) -> Utf8Error { - self.error - } - - fn description(&self) -> &str { - "C string contained non-utf8 bytes" - } -} - -impl fmt::Display for IntoStringError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) - } -} - -impl CStr { - /// Wraps a raw C string with a safe C string wrapper. - /// - /// This function will wrap the provided `ptr` with a `CStr` wrapper, which - /// allows inspection and interoperation of non-owned C strings. This method - /// is unsafe for a number of reasons: - /// - /// * There is no guarantee to the validity of `ptr`. - /// * The returned lifetime is not guaranteed to be the actual lifetime of - /// `ptr`. - /// * There is no guarantee that the memory pointed to by `ptr` contains a - /// valid nul terminator byte at the end of the string. - /// * It is not guaranteed that the memory pointed by `ptr` won't change - /// before the `CStr` has been destroyed. - /// - /// > **Note**: This operation is intended to be a 0-cost cast but it is - /// > currently implemented with an up-front calculation of the length of - /// > the string. This is not guaranteed to always be the case. - /// - /// # Examples - /// - /// ```ignore (extern-declaration) - /// # fn main() { - /// use std::ffi::CStr; - /// use std::os::raw::c_char; - /// - /// extern { - /// fn my_string() -> *const c_char; - /// } - /// - /// unsafe { - /// let slice = CStr::from_ptr(my_string()); - /// println!("string returned: {}", slice.to_str().unwrap()); - /// } - /// # } - /// ``` - pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr { - let len = strlen(ptr); - let ptr = ptr as *const u8; - CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len as usize + 1)) - } - - /// Creates a C string wrapper from a byte slice. - /// - /// This function will cast the provided `bytes` to a `CStr` - /// wrapper after ensuring that the byte slice is nul-terminated - /// and does not contain any interior nul bytes. - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let cstr = CStr::from_bytes_with_nul(b"hello\0"); - /// assert!(cstr.is_ok()); - /// ``` - /// - /// Creating a `CStr` without a trailing nul terminator is an error: - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"hello"); - /// assert!(c_str.is_err()); - /// ``` - /// - /// Creating a `CStr` with an interior nul byte is an error: - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"he\0llo\0"); - /// assert!(c_str.is_err()); - /// ``` - pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<&CStr, FromBytesWithNulError> { - let nul_pos = memchr(0, bytes); - if let Some(nul_pos) = nul_pos { - if nul_pos + 1 != bytes.len() { - return Err(FromBytesWithNulError::interior_nul(nul_pos)); - } - Ok(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }) - } else { - Err(FromBytesWithNulError::not_nul_terminated()) + pub fn to_chars_with_nul(self) -> &'a [T::Char] { + unsafe { + // SAFETY: The string must be valid at least until (and including) the NUL byte. + let len = T::strlen(self.ptr.as_ptr()); + core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1) } } + pub fn to_chars(self) -> &'a [T::Char] { + let s = self.to_chars_with_nul(); + &s[..s.len() - 1] + } + pub const fn as_ptr(self) -> *const T::C { + self.ptr.as_ptr() + } + pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self { + unsafe { Self::from_ptr(chars.as_ptr().cast()) } + } + pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result { + if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) { + return Err(FromCharsWithNulError); + } - /// Unsafely creates a C string wrapper from a byte slice. - /// - /// This function will cast the provided `bytes` to a `CStr` wrapper without - /// performing any sanity checks. The provided slice **must** be nul-terminated - /// and not contain any interior nul bytes. - /// - /// # Examples - /// - /// ``` - /// use std::ffi::{CStr, CString}; - /// - /// unsafe { - /// let cstring = CString::new("hello").unwrap(); - /// let cstr = CStr::from_bytes_with_nul_unchecked(cstring.to_bytes_with_nul()); - /// assert_eq!(cstr, &*cstring); - /// } - /// ``` + Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) + } + pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result { + if !chars.contains(&T::NUL) { + return Err(FromCharsUntilNulError); + } + + Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) + } + /// Scan the string to get its length. + #[doc(alias = "strlen")] + #[doc(alias = "wcslen")] + pub fn len(self) -> usize { + self.to_chars().len() + } #[inline] - pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr { - &*(bytes as *const [u8] as *const CStr) + pub fn is_empty(&self) -> bool { + self.first() == T::NUL + } +} +impl<'a> CStr<'a> { + pub fn to_owned_cstring(self) -> CString { + CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) }) + } + pub fn borrow(string: &'a CString) -> Self { + unsafe { Self::from_ptr(string.as_ptr()) } } - - /// Returns the inner pointer to this C string. - /// - /// The returned pointer will be valid for as long as `self` is, and points - /// to a contiguous region of memory terminated with a 0 byte to represent - /// the end of the string. - /// - /// **WARNING** - /// - /// It is your responsibility to make sure that the underlying memory is not - /// freed too early. For example, the following code will cause undefined - /// behavior when `ptr` is used inside the `unsafe` block: - /// - /// ```no_run - /// # #![allow(unused_must_use)] - /// use std::ffi::{CString}; - /// - /// let ptr = CString::new("Hello").unwrap().as_ptr(); - /// unsafe { - /// // `ptr` is dangling - /// *ptr; - /// } - /// ``` - /// - /// This happens because the pointer returned by `as_ptr` does not carry any - /// lifetime information and the [`CString`] is deallocated immediately after - /// the `CString::new("Hello").unwrap().as_ptr()` expression is evaluated. - /// To fix the problem, bind the `CString` to a local variable: - /// - /// ```no_run - /// # #![allow(unused_must_use)] - /// use std::ffi::{CString}; - /// - /// let hello = CString::new("Hello").unwrap(); - /// let ptr = hello.as_ptr(); - /// unsafe { - /// // `ptr` is valid because `hello` is in scope - /// *ptr; - /// } - /// ``` - /// - /// This way, the lifetime of the `CString` in `hello` encompasses - /// the lifetime of `ptr` and the `unsafe` block. - /// - /// [`CString`]: struct.CString.html #[inline] - pub fn as_ptr(&self) -> *const c_char { - self.inner.as_ptr() + pub fn to_bytes(self) -> &'a [u8] { + self.to_chars() } - - /// Converts this C string to a byte slice. - /// - /// The returned slice will **not** contain the trailing nul terminator that this C - /// string has. - /// - /// > **Note**: This method is currently implemented as a constant-time - /// > cast, but it is planned to alter its definition in the future to - /// > perform the length calculation whenever this method is called. - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap(); - /// assert_eq!(c_str.to_bytes(), b"foo"); - /// ``` #[inline] - pub fn to_bytes(&self) -> &[u8] { - let bytes = self.to_bytes_with_nul(); - &bytes[..bytes.len() - 1] + pub fn to_bytes_with_nul(self) -> &'a [u8] { + self.to_chars_with_nul() } - - /// Converts this C string to a byte slice containing the trailing 0 byte. - /// - /// This function is the equivalent of [`to_bytes`] except that it will retain - /// the trailing nul terminator instead of chopping it off. - /// - /// > **Note**: This method is currently implemented as a 0-cost cast, but - /// > it is planned to alter its definition in the future to perform the - /// > length calculation whenever this method is called. - /// - /// [`to_bytes`]: #method.to_bytes - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap(); - /// assert_eq!(c_str.to_bytes_with_nul(), b"foo\0"); - /// ``` - #[inline] - pub fn to_bytes_with_nul(&self) -> &[u8] { - unsafe { &*(&self.inner as *const [c_char] as *const [u8]) } + pub fn to_str(self) -> Result<&'a str, Utf8Error> { + core::str::from_utf8(self.to_bytes()) } - - /// Yields a [`&str`] slice if the `CStr` contains valid UTF-8. - /// - /// If the contents of the `CStr` are valid UTF-8 data, this - /// function will return the corresponding [`&str`] slice. Otherwise, - /// it will return an error with details of where UTF-8 validation failed. - /// - /// > **Note**: This method is currently implemented to check for validity - /// > after a constant-time cast, but it is planned to alter its definition - /// > in the future to perform the length calculation in addition to the - /// > UTF-8 check whenever this method is called. - /// - /// [`&str`]: ../primitive.str.html - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"foo\0").unwrap(); - /// assert_eq!(c_str.to_str(), Ok("foo")); - /// ``` - pub fn to_str(&self) -> Result<&str, str::Utf8Error> { - // NB: When CStr is changed to perform the length check in .to_bytes() - // instead of in from_ptr(), it may be worth considering if this should - // be rewritten to do the UTF-8 check inline with the length calculation - // instead of doing it afterwards. - str::from_utf8(self.to_bytes()) - } - - /// Converts a `CStr` into a [`Cow`]`<`[`str`]`>`. - /// - /// If the contents of the `CStr` are valid UTF-8 data, this - /// function will return a [`Cow`]`::`[`Borrowed`]`(`[`&str`]`)` - /// with the the corresponding [`&str`] slice. Otherwise, it will - /// replace any invalid UTF-8 sequences with - /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD] and return a - /// [`Cow`]`::`[`Owned`]`(`[`String`]`)` with the result. - /// - /// > **Note**: This method is currently implemented to check for validity - /// > after a constant-time cast, but it is planned to alter its definition - /// > in the future to perform the length calculation in addition to the - /// > UTF-8 check whenever this method is called. - /// - /// [`Cow`]: ../borrow/enum.Cow.html - /// [`Borrowed`]: ../borrow/enum.Cow.html#variant.Borrowed - /// [`Owned`]: ../borrow/enum.Cow.html#variant.Owned - /// [`str`]: ../primitive.str.html - /// [`String`]: ../string/struct.String.html - /// [U+FFFD]: ../char/constant.REPLACEMENT_CHARACTER.html - /// - /// # Examples - /// - /// Calling `to_string_lossy` on a `CStr` containing valid UTF-8: - /// - /// ``` - /// use std::borrow::Cow; - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"Hello World\0").unwrap(); - /// assert_eq!(c_str.to_string_lossy(), Cow::Borrowed("Hello World")); - /// ``` - /// - /// Calling `to_string_lossy` on a `CStr` containing invalid UTF-8: - /// - /// ``` - /// use std::borrow::Cow; - /// use std::ffi::CStr; - /// - /// let c_str = CStr::from_bytes_with_nul(b"Hello \xF0\x90\x80World\0").unwrap(); - /// assert_eq!( - /// c_str.to_string_lossy(), - /// Cow::Owned(String::from("Hello �World")) as Cow - /// ); - /// ``` - pub fn to_string_lossy(&self) -> Cow { + pub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.to_bytes()) } - - /// Converts a [`Box`]`` into a [`CString`] without copying or allocating. - /// - /// [`Box`]: ../boxed/struct.Box.html - /// [`CString`]: struct.CString.html - /// - /// # Examples - /// - /// ``` - /// use std::ffi::CString; - /// - /// let c_string = CString::new(b"foo".to_vec()).unwrap(); - /// let boxed = c_string.into_boxed_c_str(); - /// assert_eq!(boxed.into_c_string(), CString::new("foo").unwrap()); - /// ``` - pub fn into_c_string(self: Box) -> CString { - let raw = Box::into_raw(self) as *mut [u8]; - CString { - inner: unsafe { Box::from_raw(raw) }, - } - } -} - -impl PartialEq for CStr { - fn eq(&self, other: &CStr) -> bool { - self.to_bytes().eq(other.to_bytes()) - } -} - -impl Eq for CStr {} - -impl PartialOrd for CStr { - fn partial_cmp(&self, other: &CStr) -> Option { - self.to_bytes().partial_cmp(&other.to_bytes()) - } -} - -impl Ord for CStr { - fn cmp(&self, other: &CStr) -> Ordering { - self.to_bytes().cmp(&other.to_bytes()) - } -} - -impl ToOwned for CStr { - type Owned = CString; - - fn to_owned(&self) -> CString { - CString { - inner: self.to_bytes_with_nul().into(), - } - } -} - -impl<'a> From<&'a CStr> for CString { - fn from(s: &'a CStr) -> CString { - s.to_owned() - } -} - -impl ops::Index for CString { - type Output = CStr; - #[inline] - fn index(&self, _index: ops::RangeFull) -> &CStr { - self + pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self { + unsafe { Self::from_chars_with_nul_unchecked(bytes) } + } + #[inline] + pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result { + Self::from_chars_with_nul(bytes) + } + #[inline] + pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result { + Self::from_chars_until_nul(bytes) } } -impl AsRef for CStr { - #[inline] - fn as_ref(&self) -> &CStr { - self +unsafe impl Send for NulStr<'_, T> {} +unsafe impl Sync for NulStr<'_, T> {} + +impl From<&core::ffi::CStr> for CStr<'_> { + fn from(s: &core::ffi::CStr) -> Self { + // SAFETY: + // * We can assume that `s` is valid because the caller should have upheld its + // safety concerns when constructing it. + unsafe { Self::from_ptr(s.as_ptr()) } } } -impl AsRef for CString { - #[inline] - fn as_ref(&self) -> &CStr { - self - } -} +#[derive(Debug)] +pub struct FromCharsWithNulError; + +#[derive(Debug)] +pub struct FromCharsUntilNulError; + +pub use alloc::ffi::CString; diff --git a/src/c_vec.rs b/src/c_vec.rs index 3928e531d5..e5e35af527 100644 --- a/src/c_vec.rs +++ b/src/c_vec.rs @@ -1,6 +1,8 @@ +//! Equivalent of Rust's `Vec`, but using relibc's own allocator. + use crate::{ io::{self, Write}, - platform::{self, types::*, WriteByte}, + platform::{self, WriteByte, types::*}, }; use core::{ cmp, fmt, @@ -27,6 +29,7 @@ pub struct CVec { cap: usize, } impl CVec { + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { ptr: NonNull::dangling(), @@ -35,7 +38,7 @@ impl CVec { } } fn check_bounds(i: usize) -> Result { - if i > core::isize::MAX as usize { + if i > isize::MAX as usize { Err(AllocError) } else { Ok(i) @@ -51,7 +54,7 @@ impl CVec { return Ok(Self::new()); } let size = Self::check_mul(cap, mem::size_of::())?; - let ptr = NonNull::new(unsafe { platform::alloc(size) as *mut T }).ok_or(AllocError)?; + let ptr = NonNull::new(unsafe { platform::alloc(size).cast::() }).ok_or(AllocError)?; Ok(Self { ptr, len: 0, cap }) } unsafe fn resize(&mut self, cap: usize) -> Result<(), AllocError> { @@ -59,21 +62,23 @@ impl CVec { let ptr = if cap == 0 { NonNull::dangling() } else if self.cap > 0 { - NonNull::new(platform::realloc(self.ptr.as_ptr() as *mut c_void, size) as *mut T) - .ok_or(AllocError)? + NonNull::new( + unsafe { platform::realloc(self.ptr.as_ptr().cast::(), size) }.cast::(), + ) + .ok_or(AllocError)? } else { - NonNull::new((platform::alloc(size)) as *mut T).ok_or(AllocError)? + NonNull::new((unsafe { platform::alloc(size) }).cast::()).ok_or(AllocError)? }; self.ptr = ptr; self.cap = cap; Ok(()) } unsafe fn drop_range(&mut self, start: usize, end: usize) { - let mut start = self.ptr.as_ptr().add(start); - let end = self.ptr.as_ptr().add(end); + let mut start = unsafe { self.ptr.as_ptr().add(start) }; + let end = unsafe { self.ptr.as_ptr().add(end) }; while start < end { - ptr::drop_in_place(start); - start = start.add(1); + unsafe { ptr::drop_in_place(start) }; + start = unsafe { start.add(1) }; } } @@ -86,7 +91,7 @@ impl CVec { .ok_or(AllocError) .and_then(Self::check_bounds)?; if required_len > self.cap { - let new_cap = cmp::min(required_len.next_power_of_two(), core::isize::MAX as usize); + let new_cap = cmp::min(required_len.next_power_of_two(), isize::MAX as usize); unsafe { self.resize(new_cap)?; } @@ -249,7 +254,7 @@ mod tests { } #[test] fn extend_from_slice() { - use core_io::Write; + use crate::io::Write; let mut vec = CVec::new(); vec.extend_from_slice(&[1, 2, 3]).unwrap(); diff --git a/src/crt0/Cargo.toml b/src/crt0/Cargo.toml index 8c4bfad227..3ca5057316 100644 --- a/src/crt0/Cargo.toml +++ b/src/crt0/Cargo.toml @@ -2,7 +2,11 @@ name = "crt0" version = "0.1.0" authors = ["Jeremy Soller "] +edition = "2024" [lib] name = "crt0" crate-type = ["staticlib"] + +[lints] +workspace = true diff --git a/src/crt0/src/lib.rs b/src/crt0/src/lib.rs index dd7c7e470f..fef86f62f4 100644 --- a/src/crt0/src/lib.rs +++ b/src/crt0/src/lib.rs @@ -1,39 +1,105 @@ //! crt0 #![no_std] -#![feature(asm)] #![feature(linkage)] -#![feature(llvm_asm)] -#![feature(naked_functions)] -#[no_mangle] -#[naked] -pub unsafe extern "C" fn _start() { - #[cfg(target_arch = "x86_64")] - llvm_asm!("mov rdi, rsp - and rsp, 0xFFFFFFFFFFFFFFF0 - call relibc_start" - : - : - : - : "intel", "volatile" - ); - #[cfg(target_arch = "aarch64")] - llvm_asm!("mov x0, sp - bl relibc_start" - : - : - : - : "volatile" - ); +use core::{ + arch::global_asm, + ffi::{c_char, c_int}, +}; + +#[cfg(target_arch = "aarch64")] +global_asm!( + " + .globl _start +_start: + mov x0, sp + and sp, x0, #0xfffffffffffffff0 //align sp + bl relibc_crt0 +" +); + +#[cfg(target_arch = "x86")] +global_asm!( + " + .globl _start + .type _start, @function +_start: + sub esp, 8 + + mov DWORD PTR [esp], 0x00001F80 + # ldmxcsr [esp] + mov WORD PTR [esp], 0x037F + fldcw [esp] + + add esp, 8 + + push esp + call relibc_crt0 + .size _start, . - _start +" +); + +#[cfg(target_arch = "x86_64")] +global_asm!( + " + .globl _start + .type _start, @function +_start: + mov rdi, rsp + and rsp, 0xFFFFFFFFFFFFFFF0 + + sub rsp, 8 + + mov DWORD PTR [rsp], 0x00001F80 + ldmxcsr [rsp] + mov WORD PTR [rsp], 0x037F + fldcw [rsp] + + add rsp, 8 + + call relibc_crt0 + .size _start, . - _start +" +); + +#[cfg(target_arch = "riscv64")] +global_asm!( + " + .globl _start +_start: + mv a0, sp + la t0, relibc_crt0 + jalr ra, t0 +" +); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn relibc_crt0(sp: usize) -> ! { + // This wrapper ensures a dynamic libc.so can access a hidden main function + //TODO: common definition of types + unsafe extern "C" { + fn main(argc: isize, argv: *mut *mut c_char, envp: *mut *mut c_char) -> c_int; + fn relibc_start_v1( + sp: usize, + main: unsafe extern "C" fn( + argc: isize, + argv: *mut *mut c_char, + envp: *mut *mut c_char, + ) -> c_int, + ) -> !; + } + unsafe { relibc_start_v1(sp, main) } +} + +#[linkage = "weak"] +#[unsafe(no_mangle)] +pub extern "C" fn relibc_panic(_pi: &::core::panic::PanicInfo) -> ! { + loop {} } #[panic_handler] #[linkage = "weak"] -#[no_mangle] -pub unsafe extern "C" fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { - extern "C" { - fn relibc_panic(pi: &::core::panic::PanicInfo) -> !; - } +pub unsafe fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { relibc_panic(pi) } diff --git a/src/crti/Cargo.toml b/src/crti/Cargo.toml index 05052d50a9..be768bed71 100644 --- a/src/crti/Cargo.toml +++ b/src/crti/Cargo.toml @@ -2,9 +2,7 @@ name = "crti" version = "0.1.0" authors = ["jD91mZM2 "] +edition = "2024" -[lib] -name = "crti" -crate-type = ["staticlib"] - -[dependencies] +[lints] +workspace = true diff --git a/src/crti/src/lib.rs b/src/crti/src/lib.rs index 77e8f055da..c0ca4291cf 100644 --- a/src/crti/src/lib.rs +++ b/src/crti/src/lib.rs @@ -1,60 +1,3 @@ -//! crti - #![no_std] -#![feature(global_asm)] -#![feature(linkage)] -// https://wiki.osdev.org/Creating_a_C_Library#crtbegin.o.2C_crtend.o.2C_crti.o.2C_and_crtn.o -#[cfg(target_arch = "x86_64")] -global_asm!( - r#" - .section .init - .global _init - _init: - push %rbp - movq %rsp, %rbp - // Created a new stack frame and updated the stack pointer - // Body will be filled in by gcc and ended by crtn.o - - .section .fini - .global _fini - _fini: - push %rbp - movq %rsp, %rbp - // Created a new stack frame and updated the stack pointer - // Body will be filled in by gcc and ended by crtn.o -"# -); -// https://git.musl-libc.org/cgit/musl/tree/crt/aarch64/crti.s -#[cfg(target_arch = "aarch64")] -global_asm!( - r#" - .section .init - .global _init - .type _init,%function - _init: - stp x29,x30,[sp,-16]! - mov x29,sp - // stp: "stores two doublewords from the first and second argument to memory addressed by addr" - // Body will be filled in by gcc and ended by crtn.o - - .section .fini - .global _fini - .type _fini,%function - _fini: - stp x29,x30,[sp,-16]! - mov x29,sp - // stp: "stores two doublewords from the first and second argument to memory addressed by addr" - // Body will be filled in by gcc and ended by crtn.o -"# -); - -#[panic_handler] -#[linkage = "weak"] -#[no_mangle] -pub unsafe extern "C" fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { - extern "C" { - fn relibc_panic(pi: &::core::panic::PanicInfo) -> !; - } - relibc_panic(pi) -} +// we don't support _init/_fini functions, only init/fini arrays diff --git a/src/crtn/Cargo.toml b/src/crtn/Cargo.toml index cec7a20b70..a32f11631b 100644 --- a/src/crtn/Cargo.toml +++ b/src/crtn/Cargo.toml @@ -2,9 +2,7 @@ name = "crtn" version = "0.1.0" authors = ["jD91mZM2 "] +edition = "2024" -[lib] -name = "crtn" -crate-type = ["staticlib"] - -[dependencies] +[lints] +workspace = true diff --git a/src/crtn/src/lib.rs b/src/crtn/src/lib.rs index b2d9608678..c0ca4291cf 100644 --- a/src/crtn/src/lib.rs +++ b/src/crtn/src/lib.rs @@ -1,50 +1,3 @@ -//! crti - #![no_std] -#![feature(global_asm)] -#![feature(linkage)] -// https://wiki.osdev.org/Creating_a_C_Library#crtbegin.o.2C_crtend.o.2C_crti.o.2C_and_crtn.o -#[cfg(target_arch = "x86_64")] -global_asm!( - r#" - .section .init - // This happens after crti.o and gcc has inserted code - // Pop the stack frame - pop %rbp - ret - - .section .fini - // This happens after crti.o and gcc has inserted code - // Pop the stack frame - pop %rbp - ret -"# -); -// https://git.musl-libc.org/cgit/musl/tree/crt/aarch64/crtn.s -#[cfg(target_arch = "aarch64")] -global_asm!( - r#" - .section .init - // This happens after crti.o and gcc has inserted code - // ldp: "loads two doublewords from memory addressed by the third argument to the first and second" - ldp x29,x30,[sp],#16 - ret - - .section .fini - // This happens after crti.o and gcc has inserted code - // ldp: "loads two doublewords from memory addressed by the third argument to the first and second" - ldp x29,x30,[sp],#16 - ret -"# -); - -#[panic_handler] -#[linkage = "weak"] -#[no_mangle] -pub unsafe extern "C" fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { - extern "C" { - fn relibc_panic(pi: &::core::panic::PanicInfo) -> !; - } - relibc_panic(pi) -} +// we don't support _init/_fini functions, only init/fini arrays diff --git a/src/cxa.rs b/src/cxa.rs index 26221fa412..cd75de7440 100644 --- a/src/cxa.rs +++ b/src/cxa.rs @@ -1,28 +1,103 @@ -use crate::platform::types::*; - -// TODO: Implement cxa_finalize and uncomment this +use crate::platform::types::{c_int, c_void}; +use alloc::vec::Vec; +use core::cell::RefCell; +use spin::Mutex; #[derive(Clone, Copy)] struct CxaAtExitFunc { - //func: extern "C" fn(*mut c_void), -//arg: *mut c_void, -//dso: *mut c_void, + func: extern "C" fn(*mut c_void), + arg: usize, + dso: usize, } -static mut CXA_ATEXIT_FUNCS: [Option; 32] = [None; 32]; +#[derive(Clone, Copy)] +struct CxaThreadAtExitFunc { + func: extern "C" fn(*mut c_void), + obj: *mut c_void, + dso: *mut c_void, +} -#[no_mangle] +static CXA_ATEXIT_FUNCS: Mutex>> = Mutex::new(Vec::new()); +#[thread_local] +static DTORS: RefCell> = RefCell::new(Vec::new()); + +#[unsafe(no_mangle)] pub unsafe extern "C" fn __cxa_atexit( - func_opt: Option, + func: Option, arg: *mut c_void, dso: *mut c_void, ) -> c_int { - for item in &mut CXA_ATEXIT_FUNCS { - if item.is_none() { - *item = func_opt.map(|func| CxaAtExitFunc {} /*{ func, arg, dso }*/); + let Some(func) = func else { + return 0; + }; + + let entry = CxaAtExitFunc { + func, + arg: arg as usize, + dso: dso as usize, + }; + + let mut funcs = CXA_ATEXIT_FUNCS.lock(); + + for slot in funcs.iter_mut() { + if slot.is_none() { + *slot = Some(entry); return 0; } } - -1 + // No empty slots + funcs.push(Some(entry)); + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __cxa_finalize(dso: *mut c_void) { + let mut funcs = CXA_ATEXIT_FUNCS.lock(); + + let dso_usize = dso as usize; + + for slot in funcs.iter_mut().rev() { + if let Some(entry) = slot.as_ref() + && (dso.is_null() || entry.dso == dso_usize) + && let Some(entry_to_run) = slot.take() + { + (entry_to_run.func)(entry_to_run.arg as *mut c_void); + } + } + + // clean up remaining list + if dso.is_null() { + funcs.clear(); + } else { + funcs.retain(|opt| opt.is_some()); + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __cxa_thread_atexit_impl( + func: extern "C" fn(*mut c_void), + obj: *mut c_void, + dso: *mut c_void, +) { + let entry = CxaThreadAtExitFunc { func, obj, dso }; + DTORS.borrow_mut().push(entry); +} + +// called internally +pub unsafe fn __cxa_thread_finalize() { + let mut dtors = DTORS.borrow_mut(); + while let Some(entry) = dtors.pop() { + (entry.func)(entry.obj); + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _ITM_deregisterTMCloneTable(_ptr: *mut c_void) { + // No-op +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _ITM_registerTMCloneTable(_ptr: *mut c_void, _len: usize) { + // No-op } diff --git a/src/db.rs b/src/db.rs index 6fa6b8b325..777fbba8d8 100644 --- a/src/db.rs +++ b/src/db.rs @@ -44,7 +44,7 @@ impl Db { pub type FileDb = Db>; impl FileDb { - pub fn open(path: &CStr, separator: Separator) -> io::Result { + pub fn open(path: CStr, separator: Separator) -> io::Result { let file = File::open(path, fcntl::O_RDONLY | fcntl::O_CLOEXEC)?; Ok(Db::new(BufReader::new(file), separator)) } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000000..21d470b3b8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,90 @@ +use alloc::boxed::Box; + +use crate::{header::errno::STR_ERROR, platform::types::c_int}; + +/// Positive error codes (EINVAL, not -EINVAL). +#[derive(Debug, Eq, PartialEq)] +// TODO: Move to a more generic place. +pub struct Errno(pub c_int); + +impl Errno { + pub fn sync(self) -> Self { + crate::platform::ERRNO.set(self.0); + self + } +} + +pub type Result = core::result::Result; + +#[cfg(target_os = "redox")] +impl From for Errno { + #[inline] + fn from(value: syscall::Error) -> Self { + Errno(value.errno) + } +} +#[cfg(target_os = "redox")] +impl From for syscall::Error { + #[inline] + fn from(value: Errno) -> Self { + syscall::Error::new(value.0) + } +} + +impl From for crate::io::Error { + #[inline] + fn from(Errno(errno): Errno) -> Self { + Self::from_raw_os_error(errno) + } +} + +// TODO: core::error::Error + +impl core::fmt::Display for Errno { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match usize::try_from(self.0).ok().and_then(|i| STR_ERROR.get(i)) { + Some(desc) => write!(f, "{desc}"), + None => write!(f, "unknown error ({})", self.0), + } + } +} + +pub trait ResultExt { + fn or_minus_one_errno(self) -> T; +} +impl> ResultExt for Result { + fn or_minus_one_errno(self) -> T { + match self { + Self::Ok(v) => v, + Self::Err(Errno(errno)) => { + crate::platform::ERRNO.set(errno); + T::from(-1) + } + } + } +} +pub trait ResultExtPtrMut { + fn or_errno_null_mut(self) -> *mut T; +} +impl ResultExtPtrMut for Result<*mut T, Errno> { + fn or_errno_null_mut(self) -> *mut T { + match self { + Self::Ok(ptr) => ptr, + Self::Err(Errno(errno)) => { + crate::platform::ERRNO.set(errno); + core::ptr::null_mut() + } + } + } +} +impl ResultExtPtrMut for Result, Errno> { + fn or_errno_null_mut(self) -> *mut T { + match self { + Self::Ok(ptr) => Box::into_raw(ptr), + Self::Err(Errno(errno)) => { + crate::platform::ERRNO.set(errno); + core::ptr::null_mut() + } + } + } +} diff --git a/src/fs.rs b/src/fs.rs index 47f7c7422e..3f2b4265b0 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,11 +1,14 @@ use crate::{ c_str::CStr, + error::{Errno, ResultExt}, header::{ fcntl::O_CREAT, + sys_stat::stat, unistd::{SEEK_CUR, SEEK_END, SEEK_SET}, }, io, - platform::{types::*, Pal, Sys}, + out::Out, + platform::{Pal, Sys, types::*}, }; use core::ops::Deref; @@ -24,39 +27,46 @@ impl File { } } - pub fn open(path: &CStr, oflag: c_int) -> io::Result { - match Sys::open(path, oflag, 0) { - -1 => Err(io::last_os_error()), - ok => Ok(Self::new(ok)), - } + pub fn open(path: CStr, oflag: c_int) -> Result { + Sys::open(path, oflag, 0) + .map(Self::new) + .map_err(Errno::sync) } - pub fn create(path: &CStr, oflag: c_int, mode: mode_t) -> io::Result { - match Sys::open(path, oflag | O_CREAT, mode) { - -1 => Err(io::last_os_error()), - ok => Ok(Self::new(ok)), - } + pub fn openat(dirfd: c_int, path: CStr, oflag: c_int) -> Result { + Sys::openat(dirfd, path, oflag, 0) + .map(Self::new) + .map_err(Errno::sync) } - pub fn sync_all(&self) -> io::Result<()> { - match Sys::fsync(self.fd) { - -1 => Err(io::last_os_error()), - _ok => Ok(()), - } + pub fn create(path: CStr, oflag: c_int, mode: mode_t) -> Result { + Sys::open(path, oflag | O_CREAT, mode) + .map(Self::new) + .map_err(Errno::sync) } - pub fn set_len(&self, size: u64) -> io::Result<()> { - match Sys::ftruncate(self.fd, size as off_t) { - -1 => Err(io::last_os_error()), - _ok => Ok(()), - } + pub fn createat(dirfd: c_int, path: CStr, oflag: c_int, mode: mode_t) -> Result { + Sys::openat(dirfd, path, oflag | O_CREAT, mode) + .map(Self::new) + .map_err(Errno::sync) + } + + pub fn sync_all(&self) -> Result<(), Errno> { + Sys::fsync(self.fd).map_err(Errno::sync) + } + + pub fn set_len(&self, size: u64) -> Result<(), Errno> { + Sys::ftruncate(self.fd, size as off_t).map_err(Errno::sync) + } + + pub fn fstat(&self) -> Result { + let mut file_st = stat::default(); + Sys::fstat(self.fd, Out::from_mut(&mut file_st)).map_err(Errno::sync)?; + Ok(file_st) } pub fn try_clone(&self) -> io::Result { - match Sys::dup(self.fd) { - -1 => Err(io::last_os_error()), - ok => Ok(Self::new(ok)), - } + Ok(Self::new(Sys::dup(self.fd)?)) } /// Create a new file pointing to the same underlying descriptor. This file @@ -72,7 +82,7 @@ impl File { impl io::Read for &File { fn read(&mut self, buf: &mut [u8]) -> io::Result { - match Sys::read(self.fd, buf) { + match Sys::read(self.fd, buf).map(|read| read as ssize_t).or_minus_one_errno() /* TODO */ { -1 => Err(io::last_os_error()), ok => Ok(ok as usize), } @@ -81,7 +91,10 @@ impl io::Read for &File { impl io::Write for &File { fn write(&mut self, buf: &[u8]) -> io::Result { - match Sys::write(self.fd, buf) { + match Sys::write(self.fd, buf) + .map(|read| read as ssize_t) + .or_minus_one_errno() + { -1 => Err(io::last_os_error()), ok => Ok(ok as usize), } @@ -100,10 +113,7 @@ impl io::Seek for &File { io::SeekFrom::End(end) => (end as off_t, SEEK_END), }; - match Sys::lseek(self.fd, offset, whence) { - -1 => Err(io::last_os_error()), - ok => Ok(ok as u64), - } + Ok(Sys::lseek(self.fd, offset, whence)? as u64) } } diff --git a/src/header/_aio/mod.rs b/src/header/_aio/mod.rs index 8d5d816afc..b75ba38f78 100644 --- a/src/header/_aio/mod.rs +++ b/src/header/_aio/mod.rs @@ -1,8 +1,13 @@ +//! `aio.h` implementation. +//! +//! See . + use crate::{ - header::time::{sigevent, timespec}, - platform::types::*, + header::{bits_timespec::timespec, signal::sigevent}, + platform::types::{c_int, c_void}, }; +/// See . pub struct aiocb { pub aio_fildes: c_int, pub aio_lio_opcode: c_int, @@ -12,17 +17,20 @@ pub struct aiocb { pub aio_sigevent: sigevent, } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_read(aiocbp: *mut aiocb) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_write(aiocbp: *mut aiocb) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn lio_listio( mode: c_int, list: *const *const aiocb, @@ -32,22 +40,26 @@ pub extern "C" fn lio_listio( unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_error(aiocbp: *const aiocb) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_return(aiocbp: *mut aiocb) -> usize { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_cancel(fildes: c_int, aiocbp: *mut aiocb) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_suspend( list: *const *const aiocb, nent: c_int, @@ -56,7 +68,8 @@ pub extern "C" fn aio_suspend( unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn aio_fsync(operation: c_int, aiocbp: *mut aiocb) -> c_int { unimplemented!(); } diff --git a/src/header/_fenv/mod.rs b/src/header/_fenv/mod.rs index 2af01591ce..54dd24062c 100644 --- a/src/header/_fenv/mod.rs +++ b/src/header/_fenv/mod.rs @@ -1,69 +1,85 @@ -//! fenv.h implementation for Redox, following -//! http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fenv.h.html +//! `fenv.h` implementation. +//! +//! See . -use crate::platform::types::*; +use crate::platform::types::c_int; +/// See . pub const FE_ALL_EXCEPT: c_int = 0; +/// See . pub const FE_TONEAREST: c_int = 0; +/// See . pub type fexcept_t = u64; +/// See . #[repr(C)] pub struct fenv_t { pub cw: u64, } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] -pub unsafe extern "C" fn fegenenv(envp: *mut fenv_t) -> c_int { +/// See . +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fegetexceptflag(flagp: *mut fexcept_t, excepts: c_int) -> c_int { unimplemented!(); } -#[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fegetround() -> c_int { FE_TONEAREST } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn feholdexcept(envp: *mut fenv_t) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn feraiseexcept(except: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fesetexceptflag(flagp: *const fexcept_t, excepts: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fesetround(round: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub unsafe extern "C" fn feupdateenv(envp: *const fenv_t) -> c_int { unimplemented!(); } diff --git a/src/header/_paths/cbindgen.toml b/src/header/_paths/cbindgen.toml new file mode 100644 index 0000000000..885a98f571 --- /dev/null +++ b/src/header/_paths/cbindgen.toml @@ -0,0 +1,5 @@ +include_guard = "_RELIBC_PATHS_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true diff --git a/src/header/_paths/linux.rs b/src/header/_paths/linux.rs new file mode 100644 index 0000000000..34cf290f7a --- /dev/null +++ b/src/header/_paths/linux.rs @@ -0,0 +1,29 @@ +//! Linux specific constants for paths.h. +//! Entirely borrowed from musl as there isn't a list of what to include. + +pub const _PATH_DEFPATH: &str = "/usr/local/bin:/bin:/usr/bin"; +pub const _PATH_STDPATH: &str = "/bin:/usr/bin:/sbin:/usr/sbin"; + +pub const _PATH_BSHELL: &str = "/bin/sh"; +pub const _PATH_CONSOLE: &str = "/dev/console"; +pub const _PATH_DEVNULL: &str = "/dev/null"; +pub const _PATH_KLOG: &str = "/proc/kmsg"; +pub const _PATH_LASTLOG: &str = "/var/log/lastlog"; +pub const _PATH_MAILDIR: &str = "/var/mail"; +pub const _PATH_MAN: &str = "/usr/share/man"; +pub const _PATH_MNTTAB: &str = "/etc/fstab"; +pub const _PATH_NOLOGIN: &str = "/etc/nologin"; +pub const _PATH_SENDMAIL: &str = "/usr/sbin/sendmail"; +pub const _PATH_SHADOW: &str = "/etc/shadow"; +pub const _PATH_SHELLS: &str = "/etc/shells"; +pub const _PATH_TTY: &str = "/dev/tty"; +pub const _PATH_UTMP: &str = "/var/run/utmp"; +pub const _PATH_WTMP: &str = "/var/log/wtmp"; +pub const _PATH_VI: &str = "/usr/bin/vi"; + +// Trailing backslash intentional as these are dir paths. +pub const _PATH_DEV: &str = "/dev/"; +pub const _PATH_TMP: &str = "/tmp/"; +pub const _PATH_VARDB: &str = "/var/lib/misc/"; +pub const _PATH_VARRUN: &str = "/var/run/"; +pub const _PATH_VARTMP: &str = "/var/tmp/"; diff --git a/src/header/_paths/mod.rs b/src/header/_paths/mod.rs new file mode 100644 index 0000000000..7c391b437c --- /dev/null +++ b/src/header/_paths/mod.rs @@ -0,0 +1,11 @@ +//! Implementation specific, non-standard path aliases + +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +mod sys; + +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +mod sys; + +pub use sys::*; diff --git a/src/header/_paths/redox.rs b/src/header/_paths/redox.rs new file mode 100644 index 0000000000..a2a0ed0274 --- /dev/null +++ b/src/header/_paths/redox.rs @@ -0,0 +1,13 @@ +//! Redox specific constants for paths.h. + +// NOTE: Cross check that new entries correspond to files on Redox before adding. + +pub const _PATH_BSHELL: &str = "/bin/sh"; +pub const _PATH_DEVNULL: &str = "/dev/null"; +pub const _PATH_MAN: &str = "/usr/share/man"; +pub const _PATH_TTY: &str = "/dev/tty"; + +// Trailing backslash intentional as these are dir paths. +pub const _PATH_DEV: &str = "/dev/"; +pub const _PATH_TMP: &str = "/tmp/"; +pub const _PATH_VARTMP: &str = "/var/tmp/"; diff --git a/src/header/_template/mod.rs b/src/header/_template/mod.rs index ed73313a1d..3696d1e784 100644 --- a/src/header/_template/mod.rs +++ b/src/header/_template/mod.rs @@ -3,7 +3,7 @@ use platform::types::*; /* -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn func(args) -> c_int { unimplemented!(); } diff --git a/src/header/_wctype/mod.rs b/src/header/_wctype/mod.rs deleted file mode 100644 index 7162c19bbe..0000000000 --- a/src/header/_wctype/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! wctype implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/wctype.h.html - -use crate::platform::types::*; - -// #[no_mangle] -pub extern "C" fn iswalnum(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswalpha(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswcntrl(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswdigit(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswgraph(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswlower(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswprint(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswpunct(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswspace(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswupper(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswxdigit(wc: wint_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn iswctype(wc: wint_t, charclass: wctype_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn towlower(wc: wint_t) -> wint_t { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn towupper(wc: wint_t) -> wint_t { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn wctype(property: *const c_char) -> c_int { - unimplemented!(); -} diff --git a/src/header/arch_aarch64_user/mod.rs b/src/header/arch_aarch64_user/mod.rs index 6f25a5da80..5586c38bac 100644 --- a/src/header/arch_aarch64_user/mod.rs +++ b/src/header/arch_aarch64_user/mod.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::{c_double, c_uint, c_ulong, c_ulonglong}; #[repr(C)] pub struct user_regs_struct { @@ -16,11 +16,11 @@ pub struct user_fpsimd_struct { } pub type elf_greg_t = c_ulong; -pub type elf_gregset_t = [c_ulong; 34]; +pub type elf_gregset_t = *mut [c_ulong; 34]; pub type elf_fpregset_t = user_fpsimd_struct; -#[no_mangle] -pub extern "C" fn _cbindgen_only_generates_structs_if_they_are_mentioned_which_is_dumb_aarch64_user( +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_aarch64_user( a: user_regs_struct, b: user_fpsimd_struct, c: elf_gregset_t, diff --git a/src/header/arch_riscv64_user/cbindgen.toml b/src/header/arch_riscv64_user/cbindgen.toml new file mode 100644 index 0000000000..369bf8c1d6 --- /dev/null +++ b/src/header/arch_riscv64_user/cbindgen.toml @@ -0,0 +1,7 @@ +sys_includes = [] +include_guard = "_RISCV64_USER_H" +language = "C" +style = "Tag" + +[enum] +prefix_with_name = true diff --git a/src/header/arch_riscv64_user/mod.rs b/src/header/arch_riscv64_user/mod.rs new file mode 100644 index 0000000000..e0e9794f15 --- /dev/null +++ b/src/header/arch_riscv64_user/mod.rs @@ -0,0 +1,39 @@ +use crate::platform::types::{c_double, c_float, c_uint, c_ulong}; + +#[repr(C)] +pub struct user_regs_struct { + pub regs: [c_ulong; 31], // x1-x31 + pub pc: c_ulong, +} + +#[repr(C)] +pub struct user_fpregs_f_struct { + pub fpregs: [c_float; 32], + pub fcsr: c_uint, +} + +#[repr(C)] +pub struct user_fpregs_g_struct { + pub fpregs: [c_double; 32], + pub fcsr: c_uint, +} + +#[repr(C)] +pub struct user_fpregs_struct { + pub f_regs: user_fpregs_f_struct, + pub g_regs: user_fpregs_g_struct, +} + +pub type elf_greg_t = c_ulong; +pub type elf_gregset_t = user_regs_struct; +pub type elf_fpregset_t = user_fpregs_struct; + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_only_generates_structs_if_they_are_mentioned_which_is_dumb_riscv64_user( + a: user_regs_struct, + b: user_fpregs_struct, + c: elf_gregset_t, + d: elf_greg_t, + e: elf_fpregset_t, +) { +} diff --git a/src/header/arch_x64_user/mod.rs b/src/header/arch_x64_user/mod.rs index 92d34b6279..116b990282 100644 --- a/src/header/arch_x64_user/mod.rs +++ b/src/header/arch_x64_user/mod.rs @@ -1,6 +1,6 @@ //! A part of the ptrace compatibility for Redox OS -use crate::platform::types::*; +use crate::platform::types::{c_char, c_int, c_long, c_ulong}; #[repr(C)] pub struct user_fpregs_struct { @@ -50,7 +50,7 @@ pub struct user_regs_struct { pub type elf_greg_t = c_ulong; -pub type elf_gregset_t = [c_ulong; 27]; +pub type elf_gregset_t = *mut [c_ulong; 27]; pub type elf_fpregset_t = user_fpregs_struct; #[repr(C)] pub struct user { @@ -71,8 +71,8 @@ pub struct user { pub u_debugreg: [c_ulong; 8], } -#[no_mangle] -pub extern "C" fn _cbindgen_only_generates_structs_if_they_are_mentioned_which_is_dumb_x86_user( +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_x86_user( a: user_fpregs_struct, b: user_regs_struct, c: user, diff --git a/src/header/arpa_inet/cbindgen.toml b/src/header/arpa_inet/cbindgen.toml index ce0807a988..e6205e3805 100644 --- a/src/header/arpa_inet/cbindgen.toml +++ b/src/header/arpa_inet/cbindgen.toml @@ -1,5 +1,24 @@ -sys_includes = ["stddef.h", "sys/socket.h", "netinet/in.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/arpa_inet.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the in_port_t and in_addr_t types as described in and the socklen_t type as defined in ." +# - "The header shall define the in_addr structure as described in ." +# - "The header shall define the INET_ADDRSTRLEN and INET6_ADDRSTRLEN macros as described in ." +# - "The header shall define the uint32_t and uint16_t types as described in ." +# - "Inclusion of the header may also make visible all symbols from and ." +# +# Possible cycle between arpa/inet.h and netinet/in.h solved by: +# - including netinet/in.h in arpa/inet.h +# - splitting out functions (htonl, htons, ntohl, ntohs) into bits/arpainet.h +# +# netinet/in.h brings in sys/types.h which brings in features.h (needed for #[deprecated] annotation) +# bits/arpainet.h brings in inttypes.h +sys_includes = ["netinet/in.h"] include_guard = "_ARPA_INET_H" +after_includes = """ +#include // for htonl, htons, ntohl, ntohs +#include // for socklen_t +""" language = "C" style = "Tag" no_includes = true diff --git a/src/header/arpa_inet/mod.rs b/src/header/arpa_inet/mod.rs index 777430da4a..56806d4a04 100644 --- a/src/header/arpa_inet/mod.rs +++ b/src/header/arpa_inet/mod.rs @@ -1,5 +1,8 @@ -//! arpa/inet implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xns/arpainet.h.html +//! `arpa/inet.h` implementation. +//! +//! See . +use alloc::{string::String, vec::Vec}; use core::{ ptr, slice, str::{self, FromStr}, @@ -8,151 +11,437 @@ use core::{ use crate::{ c_str::CStr, header::{ - errno::*, - netinet_in::{in_addr, in_addr_t, INADDR_NONE}, - sys_socket::{constants::*, socklen_t}, + bits_arpainet::ntohl, + bits_socklen_t::socklen_t, + errno::{EAFNOSUPPORT, ENOSPC}, + netinet_in::{INADDR_NONE, INET6_ADDRSTRLEN, in6_addr, in_addr, in_addr_t}, + sys_socket::constants::{AF_INET, AF_INET6}, }, - platform::{self, types::*}, + platform::{ + self, + types::{c_char, c_int, c_void}, + }, + raw_cell::RawCell, }; -#[no_mangle] -pub extern "C" fn htonl(hostlong: uint32_t) -> uint32_t { - hostlong.to_be() -} - -#[no_mangle] -pub extern "C" fn htons(hostshort: uint16_t) -> uint16_t { - hostshort.to_be() -} - -#[no_mangle] -pub extern "C" fn ntohl(netlong: uint32_t) -> uint32_t { - u32::from_be(netlong) -} - -#[no_mangle] -pub extern "C" fn ntohs(netshort: uint16_t) -> uint16_t { - u16::from_be(netshort) -} - -#[no_mangle] -pub unsafe extern "C" fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_int { - // TODO: octal/hex - inet_pton(AF_INET, cp, inp as *mut c_void) -} - -#[no_mangle] -pub unsafe extern "C" fn inet_ntoa(addr: in_addr) -> *const c_char { - static mut NTOA_ADDR: [c_char; 16] = [0; 16]; - - inet_ntop( - AF_INET, - &addr as *const in_addr as *const c_void, - NTOA_ADDR.as_mut_ptr(), - 16, - ) -} - -#[no_mangle] -pub unsafe extern "C" fn inet_pton(domain: c_int, src: *const c_char, dest: *mut c_void) -> c_int { - if domain != AF_INET { - platform::errno = EAFNOSUPPORT; - -1 - } else { - let s_addr = slice::from_raw_parts_mut( - &mut (*(dest as *mut in_addr)).s_addr as *mut _ as *mut u8, - 4, - ); - let src_cstr = CStr::from_ptr(src); - let mut octets = str::from_utf8_unchecked(src_cstr.to_bytes()).split('.'); - for i in 0..4 { - if let Some(n) = octets.next().and_then(|x| u8::from_str(x).ok()) { - s_addr[i] = n; - } else { - return 0; - } - } - if octets.next() == None { - 1 // Success - } else { - 0 - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn inet_ntop( - domain: c_int, - src: *const c_void, - dest: *mut c_char, - size: socklen_t, -) -> *const c_char { - if domain != AF_INET { - platform::errno = EAFNOSUPPORT; - ptr::null() - } else if size < 16 { - platform::errno = ENOSPC; - ptr::null() - } else { - let s_addr = slice::from_raw_parts( - &(*(src as *const in_addr)).s_addr as *const _ as *const u8, - 4, - ); - let addr = format!("{}.{}.{}.{}\0", s_addr[0], s_addr[1], s_addr[2], s_addr[3]); - ptr::copy(addr.as_ptr() as *const c_char, dest, addr.len()); - dest - } -} - -#[no_mangle] +/// See . +/// +/// # Deprecated +/// The `inet_addr()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn inet_addr(cp: *const c_char) -> in_addr_t { let mut val: in_addr = in_addr { s_addr: 0 }; - if inet_aton(cp, &mut val) > 0 { + if unsafe { inet_aton(cp, &raw mut val) } > 0 { val.s_addr } else { INADDR_NONE } } -#[no_mangle] -pub extern "C" fn inet_lnaof(input: in_addr) -> in_addr_t { - if input.s_addr >> 24 < 128 { - input.s_addr & 0xff_ffff - } else if input.s_addr >> 24 < 192 { - input.s_addr & 0xffff +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn inet_aton(cp: *const c_char, inp: *mut in_addr) -> c_int { + let cp_cstr = unsafe { CStr::from_ptr(cp) }; + let mut four_parts_decimal_only = false; + if cp_cstr.contains(b'.') { + // 2, 3 or 4 part address + let parts = unsafe { str::from_utf8_unchecked(cp_cstr.to_bytes()).split('.') }; + let mut count = 0; + for part in parts { + if let Some(hex_or_oct) = part.strip_prefix('0') + && part.len() > 1 + { + match hex_or_oct.bytes().next() { + Some(b'x' | b'X') => todo_skip!(0, "parsing hex values unimplemented"), + // TODO: C2Y accept `0o` or `0O` as octal prefixes, C23 and below only use `0` + _ => todo_skip!(0, "parsing octal values unimplemented"), + } + } else { + count += 1; + } + } + if count == 4 { + four_parts_decimal_only = true; + } + } else if cp_cstr.len() == 4 { + // 1 part address (32 bit value to be stored directly into address without byte rearrangement) + let s_addr_bytes: [u8; 4] = cp_cstr.to_bytes().try_into().expect("guaranteed 4 bytes"); + unsafe { + (*inp.cast::()).s_addr = in_addr_t::from_ne_bytes(s_addr_bytes); + } + return 1; // successful + } + if four_parts_decimal_only { + unsafe { inet_pton(AF_INET, cp, inp.cast::()) } } else { - input.s_addr & 0xff + todo_skip!(0, "parsing 2 or more non-decimal values unimplemented"); + // TODO convert octal and hexadecimal parts into decimal and feed into `inet_pton` + 0 // indicates `cp` is an invalid string } } -#[no_mangle] -pub extern "C" fn inet_makeaddr(net: in_addr_t, host: in_addr_t) -> in_addr { +/// See . +/// +/// # Deprecation +/// The `inet_lnaof()` function was specified in Networking Services Issue 5, +/// but not in the Open Group Base Specifications Issue 6 and later. +#[deprecated] +#[unsafe(no_mangle)] +pub extern "C" fn inet_lnaof(r#in: in_addr) -> in_addr_t { + if r#in.s_addr >> 24 < 128 { + r#in.s_addr & 0xff_ffff + } else if r#in.s_addr >> 24 < 192 { + r#in.s_addr & 0xffff + } else { + r#in.s_addr & 0xff + } +} + +/// See . +/// +/// # Deprecation +/// The `inet_makeaddr()` function was specified in Networking Services Issue +/// 5, but not in the Open Group Base Specifications Issue 6 and later. +#[deprecated] +#[unsafe(no_mangle)] +pub extern "C" fn inet_makeaddr(net: in_addr_t, lna: in_addr_t) -> in_addr { let mut output: in_addr = in_addr { s_addr: 0 }; if net < 256 { - output.s_addr = host | net << 24; + output.s_addr = lna | net << 24; } else if net < 65536 { - output.s_addr = host | net << 16; + output.s_addr = lna | net << 16; } else { - output.s_addr = host | net << 8; + output.s_addr = lna | net << 8; } output } -#[no_mangle] -pub extern "C" fn inet_netof(input: in_addr) -> in_addr_t { - if input.s_addr >> 24 < 128 { - input.s_addr & 0xff_ffff - } else if input.s_addr >> 24 < 192 { - input.s_addr & 0xffff +/// See . +/// +/// # Deprecation +/// The `inet_netof()` function was specified in Networking Services Issue 5, +/// but not in the Open Group Base Specifications Issue 6 and later. +#[deprecated] +#[unsafe(no_mangle)] +pub extern "C" fn inet_netof(r#in: in_addr) -> in_addr_t { + if r#in.s_addr >> 24 < 128 { + r#in.s_addr & 0xff_ffff + } else if r#in.s_addr >> 24 < 192 { + r#in.s_addr & 0xffff } else { - input.s_addr & 0xff + r#in.s_addr & 0xff } } -#[no_mangle] -pub unsafe extern "C" fn inet_network(cp: *mut c_char) -> in_addr_t { - ntohl(inet_addr(cp)) +/// See . +/// +/// # Deprecation +/// The `inet_network()` function was specified in Networking Services Issue 5, +/// but not in the Open Group Base Specifications Issue 6 and later. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn inet_network(cp: *const c_char) -> in_addr_t { + ntohl(unsafe { + #[allow(deprecated)] + inet_addr(cp) + }) +} + +/// See . +/// +/// # Deprecation +/// The `inet_ntoa()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 8. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn inet_ntoa(r#in: in_addr) -> *mut c_char { + static NTOA_ADDR: RawCell<[c_char; 16]> = RawCell::new([0; 16]); + + unsafe { + let ptr = inet_ntop( + AF_INET, + ptr::from_ref::(&r#in).cast::(), + NTOA_ADDR.unsafe_mut().as_mut_ptr(), + NTOA_ADDR.unsafe_ref().len() as socklen_t, + ); + // Mutable pointer is required, inet_ntop returns destination as const pointer + ptr.cast_mut() + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn inet_ntop( + af: c_int, + src: *const c_void, + dst: *mut c_char, + size: socklen_t, +) -> *const c_char { + if af == AF_INET6 { + if size < INET6_ADDRSTRLEN as socklen_t { + platform::ERRNO.set(ENOSPC); + return ptr::null(); + } + let s6_addr = unsafe { &(*(src.cast::())).s6_addr }; + let output = inet_ntop6(s6_addr); + let bytes = output.as_bytes(); + unsafe { + ptr::copy(bytes.as_ptr().cast::(), dst, bytes.len()); + *dst.add(bytes.len()) = 0; + } + dst + } else if af == AF_INET { + if size < 16 { + platform::ERRNO.set(ENOSPC); + ptr::null() + } else { + let s_addr = unsafe { + slice::from_raw_parts( + ptr::from_ref(&(*(src.cast::())).s_addr).cast::(), + 4, + ) + }; + let addr = format!("{}.{}.{}.{}\0", s_addr[0], s_addr[1], s_addr[2], s_addr[3]); + unsafe { + ptr::copy(addr.as_ptr().cast::(), dst, addr.len()); + } + dst + } + } else { + platform::ERRNO.set(EAFNOSUPPORT); + ptr::null() + } +} + +fn inet_ntop6(addr: &[u8; 16]) -> String { + let groups: [u16; 8] = core::array::from_fn(|i| { + u16::from_be_bytes([addr[i * 2], addr[i * 2 + 1]]) + }); + + let mut best_start = 8usize; + let mut best_len = 1usize; + let mut cur_start = 8usize; + let mut cur_len = 0usize; + + for i in 0..8 { + if groups[i] == 0 { + if cur_len == 0 { + cur_start = i; + } + cur_len += 1; + } else { + if cur_len > best_len { + best_start = cur_start; + best_len = cur_len; + } + cur_len = 0; + } + } + if cur_len > best_len { + best_start = cur_start; + best_len = cur_len; + } + + let mut parts = Vec::new(); + let mut i = 0usize; + while i < 8 { + if i == best_start && best_len > 1 { + if i == 0 { + parts.push(String::new()); + } + if i + best_len == 8 { + parts.push(String::new()); + } + i += best_len; + } else { + parts.push(format!("{:x}", groups[i])); + i += 1; + } + } + + if best_len == 8 { + return String::from("::"); + } + + parts.join(":") +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn inet_pton(af: c_int, src: *const c_char, dst: *mut c_void) -> c_int { + if af == AF_INET6 { + let src_cstr = unsafe { CStr::from_ptr(src) }; + let src_str = match src_cstr.to_str() { + Ok(s) => s, + Err(_) => return 0, + }; + let out = unsafe { &mut *(dst.cast::()) }; + if inet_pton6(src_str, &mut out.s6_addr) { + 1 + } else { + 0 + } + } else if af == AF_INET { + let s_addr = unsafe { + slice::from_raw_parts_mut( + ptr::from_mut(&mut (*dst.cast::()).s_addr).cast::(), + 4, + ) + }; + let src_cstr = unsafe { CStr::from_ptr(src) }; + let mut octets = unsafe { str::from_utf8_unchecked(src_cstr.to_bytes()).split('.') }; + for part in s_addr.iter_mut().take(4) { + if let Some(n) = octets + .next() + .filter(|x| !x.len() > 3) + .and_then(|x| u8::from_str(x).ok()) + { + *part = n; + } else { + return 0; + } + } + if octets.next().is_none() { + 1 // Success + } else { + 0 + } + } else { + platform::ERRNO.set(EAFNOSUPPORT); + -1 + } +} + +fn inet_pton6(src: &str, dst: &mut [u8; 16]) -> bool { + dst.fill(0); + + let double_colon_pos = src.find("::"); + let second_double = if let Some(pos) = double_colon_pos { + src[pos + 2..].find("::").map(|p| p + pos + 2) + } else { + None + }; + if second_double.is_some() { + return false; + } + + let (left_str, right_str) = match double_colon_pos { + Some(pos) => (&src[..pos], &src[pos + 2..]), + None => (src, ""), + }; + + let left_groups: Vec<&str> = if left_str.is_empty() { + Vec::new() + } else { + left_str.split(':').collect() + }; + let right_groups: Vec<&str> = if right_str.is_empty() { + Vec::new() + } else { + right_str.split(':').collect() + }; + + let right_has_ipv4 = right_groups.last().is_some_and(|g| g.contains('.')); + let mut left_count = left_groups.len(); + let mut right_count = right_groups.len(); + if right_has_ipv4 { + right_count -= 1; + left_count += 1; + } + + let gap = 8 - left_count - right_count; + if double_colon_pos.is_none() && gap != 0 { + return false; + } + if double_colon_pos.is_some() && gap < 0 { + return false; + } + if double_colon_pos.is_none() && left_groups.len() + right_groups.len() != 8 { + return false; + } + + let mut idx = 0usize; + + for group in &left_groups { + if idx >= 16 { + return false; + } + let val = match parse_hex_group(group) { + Some(v) => v, + None => return false, + }; + dst[idx] = (val >> 8) as u8; + dst[idx + 1] = val as u8; + idx += 2; + } + + if double_colon_pos.is_some() { + for _ in 0..gap { + if idx >= 16 { + return false; + } + dst[idx] = 0; + dst[idx + 1] = 0; + idx += 2; + } + } + + let right_hex_count = if right_has_ipv4 { + right_groups.len().saturating_sub(1) + } else { + right_groups.len() + }; + + for group in &right_groups[..right_hex_count] { + if idx >= 16 { + return false; + } + let val = match parse_hex_group(group) { + Some(v) => v, + None => return false, + }; + dst[idx] = (val >> 8) as u8; + dst[idx + 1] = val as u8; + idx += 2; + } + + if right_has_ipv4 { + if idx != 12 { + return false; + } + let ipv4_str = right_groups[right_groups.len() - 1]; + let parts: Vec<&str> = ipv4_str.split('.').collect(); + if parts.len() != 4 { + return false; + } + for (i, part) in parts.iter().enumerate() { + match u8::from_str(part) { + Ok(v) => dst[12 + i] = v, + Err(_) => return false, + } + } + idx += 4; + } + + idx == 16 +} + +fn parse_hex_group(s: &str) -> Option { + if s.is_empty() || s.len() > 4 { + return None; + } + let mut val: u16 = 0; + for c in s.chars() { + val = val.checked_mul(16)?; + match c.to_digit(16) { + Some(d) => val = val.checked_add(d as u16)?, + None => return None, + } + } + Some(val) } diff --git a/src/header/assert/cbindgen.toml b/src/header/assert/cbindgen.toml index 7d1598f414..eea531eabd 100644 --- a/src/header/assert/cbindgen.toml +++ b/src/header/assert/cbindgen.toml @@ -1,5 +1,24 @@ -sys_includes = ["bits/assert.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/assert.h.html +# +# There are no spec quotations relating to includes +# +# features.h included for Rust never type (no return) +sys_includes = ["features.h"] include_guard = "_RELIBC_ASSERT_H" +# trailer is placed outside include_guard +trailer = """ +// Do not use include guard, to ensure assert is always defined +#ifdef assert +#undef assert +#endif + +#ifdef NDEBUG +# define assert(cond) (void) 0 +#else +# define assert(cond) \ + ((void)((cond) || (__assert_fail(__func__, __FILE__, __LINE__, #cond), 0))) +#endif +""" language = "C" style = "Tag" no_includes = true diff --git a/src/header/assert/mod.rs b/src/header/assert/mod.rs index bfd6b01c1c..e748b3b004 100644 --- a/src/header/assert/mod.rs +++ b/src/header/assert/mod.rs @@ -1,31 +1,24 @@ -//! assert implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/assert.h.html +//! `assert.h` implementation. +//! +//! See . use crate::{ c_str::CStr, - header::{stdio, stdlib}, - platform::types::*, + platform::types::{c_char, c_int}, }; -use core::fmt::Write; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn __assert_fail( func: *const c_char, file: *const c_char, line: c_int, cond: *const c_char, -) { - let func = CStr::from_ptr(func).to_str().unwrap(); - let file = CStr::from_ptr(file).to_str().unwrap(); - let cond = CStr::from_ptr(cond).to_str().unwrap(); +) -> ! { + let func = unsafe { CStr::from_ptr(func) }.to_string_lossy(); + let file = unsafe { CStr::from_ptr(file) }.to_string_lossy(); + let cond = unsafe { CStr::from_ptr(cond) }.to_string_lossy(); - writeln!( - *stdio::stderr, - "{}: {}:{}: Assertion `{}` failed.", - func, - file, - line, - cond - ) - .unwrap(); - stdlib::abort(); + eprintln!("{}: {}:{}: Assertion `{}` failed.", func, file, line, cond); + + unsafe { crate::header::stdlib::abort() }; } diff --git a/src/header/bits_arpainet/cbindgen.toml b/src/header/bits_arpainet/cbindgen.toml new file mode 100644 index 0000000000..2db885b93e --- /dev/null +++ b/src/header/bits_arpainet/cbindgen.toml @@ -0,0 +1,19 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/arpa_inet.h.html +# +# The arpa/inet header should define the following functions: +# - htonl +# - htons +# - ntohl +# - ntohs +# +# They are also meant to be available in the netinet/in header. +# The arpa/inet and netinet/in headers both say that they may include each other which creates a cycle. +# To break the cycle we include netinet/in in the arpa/inet header and split out the above functions. +# +# include inttypes.h to get uint16_t and uint32_t +sys_includes = ["inttypes.h"] +include_guard = "_RELIBC_BITS_ARPAINET_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true diff --git a/src/header/bits_arpainet/mod.rs b/src/header/bits_arpainet/mod.rs new file mode 100644 index 0000000000..176b4d7958 --- /dev/null +++ b/src/header/bits_arpainet/mod.rs @@ -0,0 +1,25 @@ +use crate::platform::types::{uint16_t, uint32_t}; + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htonl(hostlong: uint32_t) -> uint32_t { + hostlong.to_be() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htons(hostshort: uint16_t) -> uint16_t { + hostshort.to_be() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn ntohl(netlong: uint32_t) -> uint32_t { + u32::from_be(netlong) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn ntohs(netshort: uint16_t) -> uint16_t { + u16::from_be(netshort) +} diff --git a/src/header/bits_eventfd/mod.rs b/src/header/bits_eventfd/mod.rs new file mode 100644 index 0000000000..87c8d941fb --- /dev/null +++ b/src/header/bits_eventfd/mod.rs @@ -0,0 +1,4 @@ +//! `bits/eventfd.h` — eventfd counter type. + +use crate::platform::types::uint64_t; +pub type eventfd_t = uint64_t; diff --git a/src/header/bits_iovec/cbindgen.toml b/src/header/bits_iovec/cbindgen.toml new file mode 100644 index 0000000000..c9c51fbb10 --- /dev/null +++ b/src/header/bits_iovec/cbindgen.toml @@ -0,0 +1,18 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_uio.h.html +# +# Split out to avoid including all of sys/uio.h in sys/socket.h. +# +# POSIX headers that require iovec: +# - sys/socket.h +# - sys/uio.h (where it should be defined) +# +# sys/types.h included for c_void and size_t +sys_includes = ["sys/types.h"] +include_guard = "_RELIBC_BITS_IOVEC_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/bits_iovec/mod.rs b/src/header/bits_iovec/mod.rs new file mode 100644 index 0000000000..2136ad90e1 --- /dev/null +++ b/src/header/bits_iovec/mod.rs @@ -0,0 +1,38 @@ +use alloc::vec::Vec; +use core::slice; + +use crate::platform::types::{c_void, size_t}; + +/// See . +#[repr(C)] +#[derive(Debug, CheckVsLibcCrate)] +pub struct iovec { + pub iov_base: *mut c_void, + pub iov_len: size_t, +} + +impl iovec { + unsafe fn to_slice(&self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.iov_base.cast::(), self.iov_len) } + } +} + +pub unsafe fn gather(iovs: &[iovec]) -> Vec { + let mut vec = Vec::new(); + for iov in iovs.iter() { + vec.extend_from_slice(unsafe { iov.to_slice() }); + } + vec +} + +pub unsafe fn scatter(iovs: &[iovec], vec: Vec) { + let mut i = 0; + for iov in iovs.iter() { + let slice = unsafe { iov.to_slice() }; + slice.copy_from_slice(&vec[i..][..slice.len()]); + i += slice.len(); + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_alias_iovec(_: iovec) {} diff --git a/src/header/bits_locale-t/cbindgen.toml b/src/header/bits_locale-t/cbindgen.toml new file mode 100644 index 0000000000..bd79a605d1 --- /dev/null +++ b/src/header/bits_locale-t/cbindgen.toml @@ -0,0 +1,29 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html +# +# The locale header should define this type but multiple headers make use of it. +# This type is split out from the locale header to avoid including all of locale in the other headers. +# +# POSIX headers that require locale_t: +# - ctype.h +# - langinfo.h +# - libintl.h (Redox does not have this header, it seems to be supplied externally) +# - monetary.h (TODO note exists, not currently imported) +# - string.h +# - strings.h (TODO note exists, not currently imported) +# - time.h (TODO note exists, not currently imported) +# - wchar.h (required by *_l functions, no TODO note present) +# - wctype.h (required by *_l functions, TODO note exists, not currently imported) +sys_includes = [] +include_guard = "_RELIBC_BITS_LOCALE_T_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true + +[export] +include = [ + "locale_t" +] + +[enum] +prefix_with_name = true diff --git a/src/header/bits_locale-t/mod.rs b/src/header/bits_locale-t/mod.rs new file mode 100644 index 0000000000..0e1c4c3611 --- /dev/null +++ b/src/header/bits_locale-t/mod.rs @@ -0,0 +1 @@ +pub type locale_t = *mut core::ffi::c_void; diff --git a/src/header/bits_null/cbindgen.toml b/src/header/bits_null/cbindgen.toml new file mode 100644 index 0000000000..53d2880f36 --- /dev/null +++ b/src/header/bits_null/cbindgen.toml @@ -0,0 +1,20 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/stddef.h.html +# +# This type is split out to prevent importing all of stddef.h into other headers. +include_guard = "_RELIBC_BITS_NULL_T_H" +language = "C" +no_includes = true +after_includes = """ +// Null pointer constant. +// Any pointer object whose representation has all bits set to zero, perhaps by +// memset() to 0 or by calloc(), shall be treated as a null pointer. +#ifdef __cplusplus +#if __cplusplus >= 201103L + #define NULL nullptr +#else + #define NULL 0L +#endif +#else + #define NULL ((void *)0) +#endif +""" diff --git a/src/header/bits_null/mod.rs b/src/header/bits_null/mod.rs new file mode 100644 index 0000000000..92f520a41e --- /dev/null +++ b/src/header/bits_null/mod.rs @@ -0,0 +1,5 @@ +//! `NULL` from `stddef.h` implementation. +//! +//! See . +//! +//! Implemented via ifdefs and defines in cbindgen. diff --git a/src/header/bits_pthread/cbindgen.toml b/src/header/bits_pthread/cbindgen.toml new file mode 100644 index 0000000000..ea30c3892b --- /dev/null +++ b/src/header/bits_pthread/cbindgen.toml @@ -0,0 +1,53 @@ +sys_includes = [] +include_guard = "_RELIBC_BITS_PTHREAD_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true +# TODO: Any better way to implement pthread_cleanup_push/pthread_cleanup_pop? +after_includes = """ +#define PTHREAD_COND_INITIALIZER ((pthread_cond_t){0}) +#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t){0}) +#define PTHREAD_ONCE_INIT ((pthread_once_t){0}) +#define PTHREAD_RWLOCK_INITIALIZER ((pthread_rwlock_t){0}) + +#define pthread_cleanup_push(ROUTINE, ARG) do { \\ + struct { \\ + void (*routine)(void *); \\ + void *arg; \\ + void *prev; \\ + } __relibc_internal_pthread_ll_entry = { \\ + .routine = (void (*)(void *))(ROUTINE), \\ + .arg = (void *)(ARG), \\ + }; \\ + __relibc_internal_pthread_cleanup_push(&__relibc_internal_pthread_ll_entry); + +#define pthread_cleanup_pop(EXECUTE) \\ + __relibc_internal_pthread_cleanup_pop((EXECUTE)); \\ +} while(0) + +""" + +[export.rename] +"AtomicInt" = "int" +"AtomicUint" = "unsigned" + +[export] +include = [ + "pthread_attr_t", + "pthread_rwlockattr_t", + "pthread_rwlock_t", + "pthread_barrier_t", + "pthread_barrierattr_t", + "pthread_mutex_t", + "pthread_mutexattr_t", + "pthread_condattr_t", + "pthread_cond_t", + "pthread_spinlock_t", + "pthread_once_t", + "pthread_t", + "pthread_key_t", +] + +[enum] +prefix_with_name = true diff --git a/src/header/bits_pthread/mod.rs b/src/header/bits_pthread/mod.rs new file mode 100644 index 0000000000..ce21b6a3fa --- /dev/null +++ b/src/header/bits_pthread/mod.rs @@ -0,0 +1,123 @@ +#![allow(non_camel_case_types)] + +use crate::platform::types::{c_int, c_uchar, c_ulong, c_void, size_t}; + +// XXX: https://github.com/eqrion/cbindgen/issues/685 +// +// We need to write the opaque types ourselves, and apparently cbindgen doesn't even support +// expanding macros! Instead, we rely on checking that the lengths are correct, when these headers +// are parsed in the regular compilation phase. + +/// The `pthread_attr_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_attr_t { + __relibc_internal_size: [c_uchar; 32], + __relibc_internal_align: size_t, +} +/// The `pthread_rwlockattr_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_rwlockattr_t { + __relibc_internal_size: [c_uchar; 1], + __relibc_internal_align: c_uchar, +} +/// The `pthread_rwlock_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_rwlock_t { + __relibc_internal_size: [c_uchar; 4], + __relibc_internal_align: c_int, +} +/// The `pthread_barrier_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_barrier_t { + __relibc_internal_size: [c_uchar; 24], + __relibc_internal_align: c_int, +} +/// The `pthread_barrierattr_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_barrierattr_t { + __relibc_internal_size: [c_uchar; 4], + __relibc_internal_align: c_int, +} +/// The `pthread_mutex_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_mutex_t { + __relibc_internal_size: [c_uchar; 12], + __relibc_internal_align: c_int, +} +/// The `pthread_mutexattr_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_mutexattr_t { + __relibc_internal_size: [c_uchar; 20], + __relibc_internal_align: c_int, +} +/// The `pthread_cond_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_cond_t { + __relibc_internal_size: [c_uchar; 8], + __relibc_internal_align: c_int, +} +/// The `pthread_condattr_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_condattr_t { + __relibc_internal_size: [c_uchar; 8], + __relibc_internal_align: c_int, +} +/// The `pthread_spinlock_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_spinlock_t { + __relibc_internal_size: [c_uchar; 4], + __relibc_internal_align: c_int, +} +/// The `pthread_once_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[repr(C)] +pub union pthread_once_t { + __relibc_internal_size: [c_uchar; 4], + __relibc_internal_align: c_int, +} + +macro_rules! assert_equal_size( + ($export:ident, $wrapped:ident) => { + const _: () = unsafe { + type Wrapped = crate::header::pthread::$wrapped; + + // Fail at compile-time if sizes differ. + + // TODO: Is this UB? + let export = $export { __relibc_internal_align: 0 }; + let _: Wrapped = core::mem::transmute(export.__relibc_internal_size); + + // Fail at compile-time if alignments differ. + let a = [0_u8; core::mem::align_of::<$export>()]; + #[allow(clippy::useless_transmute)] + let b: [u8; core::mem::align_of::()] = core::mem::transmute(a); + }; + // TODO: Turn into a macro? + #[cfg(all(target_os = "redox", feature = "check_against_libc_crate"))] + const _: () = unsafe { + use ::__libc_only_for_layout_checks as libc; + + let export = $export { __relibc_internal_align: 0 }; + let _: libc::$export = core::mem::transmute(export.__relibc_internal_size); + + let a = [0_u8; core::mem::align_of::<$export>()]; + let b: [u8; core::mem::align_of::()] = core::mem::transmute(a); + + }; + } +); +assert_equal_size!(pthread_attr_t, RlctAttr); +assert_equal_size!(pthread_rwlock_t, RlctRwlock); +assert_equal_size!(pthread_rwlockattr_t, RlctRwlockAttr); +assert_equal_size!(pthread_barrier_t, RlctBarrier); +assert_equal_size!(pthread_barrierattr_t, RlctBarrierAttr); +assert_equal_size!(pthread_mutex_t, RlctMutex); +assert_equal_size!(pthread_mutexattr_t, RlctMutexAttr); +assert_equal_size!(pthread_cond_t, RlctCond); +assert_equal_size!(pthread_condattr_t, RlctCondAttr); +assert_equal_size!(pthread_spinlock_t, RlctSpinlock); +assert_equal_size!(pthread_once_t, RlctOnce); + +/// The `pthread_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type pthread_t = *mut c_void; +/// The `pthread_key_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type pthread_key_t = c_ulong; diff --git a/src/header/bits_safamily-t/cbindgen.toml b/src/header/bits_safamily-t/cbindgen.toml new file mode 100644 index 0000000000..9b70509927 --- /dev/null +++ b/src/header/bits_safamily-t/cbindgen.toml @@ -0,0 +1,21 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html +# +# The sys/socket header should define this type. +# It is split out to avoid other headers from including all of sys/socket. +# +# POSIX headers that require sa_family_t: +# - netinet/in.h +# - sys/socket.h +# - sys/un.h +sys_includes = [] +include_guard = "_RELIBC_BITS_SA_FAMILY_T_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true + +[export] +include = ["sa_family_t"] + +[enum] +prefix_with_name = true diff --git a/src/header/bits_safamily-t/mod.rs b/src/header/bits_safamily-t/mod.rs new file mode 100644 index 0000000000..706ee0a78e --- /dev/null +++ b/src/header/bits_safamily-t/mod.rs @@ -0,0 +1,3 @@ +use crate::platform::types::c_ushort; + +pub type sa_family_t = c_ushort; diff --git a/src/header/bits_sigset-t/cbindgen.toml b/src/header/bits_sigset-t/cbindgen.toml new file mode 100644 index 0000000000..5cd5ba7970 --- /dev/null +++ b/src/header/bits_sigset-t/cbindgen.toml @@ -0,0 +1,18 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html +# +# This type is split out to prevent importing all of signal.h into other headers. +# +# POSIX headers that require sigset_t: +# - poll.h +# - signal.h (where it should be defined) +# - sys/select.h +sys_includes = [] +include_guard = "_RELIBC_BITS_SIGSET_T_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true +[export] +include = ["sigset_t"] +[enum] +prefix_with_name = true diff --git a/src/header/bits_sigset-t/mod.rs b/src/header/bits_sigset-t/mod.rs new file mode 100644 index 0000000000..fea419ee95 --- /dev/null +++ b/src/header/bits_sigset-t/mod.rs @@ -0,0 +1,4 @@ +use crate::platform::types::c_ulonglong; + +#[allow(non_camel_case_types)] +pub type sigset_t = c_ulonglong; diff --git a/src/header/bits_size-t/cbindgen.toml b/src/header/bits_size-t/cbindgen.toml new file mode 100644 index 0000000000..09dbbe3260 --- /dev/null +++ b/src/header/bits_size-t/cbindgen.toml @@ -0,0 +1,30 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/stddef.h.html +# +# This type is split out to prevent importing all of stddef.h into other headers. +# +# C++ compatibility: in C++, `size_t` is defined by the standard library +# in namespace `std::`. A bare `typedef unsigned long size_t;` at file +# scope in a C++ translation unit collides with `std::size_t` whenever +# a host stdlib header (e.g. ``, ``) is +# transitively included — the libstdc++ internal code references +# unqualified `size_t` and resolves it to the first visible one, which +# would otherwise be this typedef. The `__cplusplus` guard makes the +# typedef a no-op under C++ compilation; the libc++/libstdc++ headers +# then provide their own `std::size_t` via `__SIZE_TYPE__` or the +# standard type machinery. +include_guard = "_RELIBC_BITS_SIZE_T_H" +after_includes = """ +/** + * Unsigned integer type of the result of the sizeof operator. + */ +#ifndef __cplusplus +typedef unsigned long size_t; +#endif +""" +language = "C" +no_includes = true +cpp_compat = true +[export] +include = ["size_t"] +[enum] +prefix_with_name = true diff --git a/src/header/bits_size-t/mod.rs b/src/header/bits_size-t/mod.rs new file mode 100644 index 0000000000..4bde8f9acf --- /dev/null +++ b/src/header/bits_size-t/mod.rs @@ -0,0 +1,9 @@ +//! `size_t` from `stddef.h` implementation. +//! +//! See . + +use crate::platform::types::c_ulong; + +/// Unsigned integer type of the result of the sizeof operator. +#[allow(non_camel_case_types)] +pub type size_t = c_ulong; diff --git a/src/header/bits_socklen-t/cbindgen.toml b/src/header/bits_socklen-t/cbindgen.toml new file mode 100644 index 0000000000..94a53c8954 --- /dev/null +++ b/src/header/bits_socklen-t/cbindgen.toml @@ -0,0 +1,12 @@ +sys_includes = [] +include_guard = "_RELIBC_BITS_SOCKLEN_T_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true +[export] +include = [ + "socklen_t" +] +[enum] +prefix_with_name = true diff --git a/src/header/bits_socklen-t/mod.rs b/src/header/bits_socklen-t/mod.rs new file mode 100644 index 0000000000..74de941e6c --- /dev/null +++ b/src/header/bits_socklen-t/mod.rs @@ -0,0 +1 @@ +pub type socklen_t = u32; diff --git a/src/header/bits_timespec/cbindgen.toml b/src/header/bits_timespec/cbindgen.toml new file mode 100644 index 0000000000..817170d32f --- /dev/null +++ b/src/header/bits_timespec/cbindgen.toml @@ -0,0 +1,25 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/time.h.html +# +# The time header should define timespec but multiple headers make use of it. +# This type is split out from the time header to avoid including all of time in the other headers. +# +# POSIX headers that require timespec: +# - aio.h (all functions currently unimplemented but timespec imported) +# - poll.h +# - sched.h +# - semaphore.h +# - signal.h +# - sys/select.h (not currently imported, needed for pselect, no TODO) +# - sys/stat.h +# - sys/time.h +# +# include sys/types.h to get time_t for timespec +sys_includes = ["sys/types.h"] +include_guard = "_RELIBC_BITS_TIME_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/bits_timespec/mod.rs b/src/header/bits_timespec/mod.rs new file mode 100644 index 0000000000..7c3e031ec9 --- /dev/null +++ b/src/header/bits_timespec/mod.rs @@ -0,0 +1,64 @@ +use crate::{ + header::time::NANOSECONDS, + platform::types::{c_long, time_t}, +}; + +/// See . +#[repr(C)] +#[derive(Clone, Default, Debug)] +pub struct timespec { + pub tv_sec: time_t, + pub tv_nsec: c_long, +} + +impl timespec { + // TODO: Write test + + /// similar logic with timeradd + #[allow(clippy::should_implement_trait)] // not to confuse std::ops::Add + pub fn add(base: timespec, interval: timespec) -> Option { + let delta_sec = base.tv_sec.checked_add(interval.tv_sec)?; + let delta_nsec = base.tv_nsec.checked_add(interval.tv_nsec)?; + + if delta_sec < 0 || delta_nsec < 0 { + return None; + } + + Some(Self { + tv_sec: delta_sec + (delta_nsec / NANOSECONDS) as time_t, + tv_nsec: delta_nsec % NANOSECONDS, + }) + } + /// similar logic with timersub + pub fn subtract(later: timespec, earlier: timespec) -> Option { + let delta_sec = later.tv_sec.checked_sub(earlier.tv_sec)?; + let delta_nsec = later.tv_nsec.checked_sub(earlier.tv_nsec)?; + + let time = if delta_nsec < 0 { + let roundup_sec = -delta_nsec / NANOSECONDS + 1; + timespec { + tv_sec: delta_sec - (roundup_sec as time_t), + tv_nsec: roundup_sec * NANOSECONDS - delta_nsec, + } + } else { + timespec { + tv_sec: delta_sec + (delta_nsec / NANOSECONDS) as time_t, + tv_nsec: delta_nsec % NANOSECONDS, + } + }; + + if time.tv_sec < 0 { + // https://man7.org/linux/man-pages/man2/settimeofday.2.html + // caller should return EINVAL + return None; + } + + Some(time) + } + pub fn is_default(&self) -> bool { + self.tv_nsec == 0 && self.tv_sec == 0 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_alias_timespec(_: timespec) {} diff --git a/src/header/bits_wchar-t/cbindgen.toml b/src/header/bits_wchar-t/cbindgen.toml new file mode 100644 index 0000000000..1d25b19d39 --- /dev/null +++ b/src/header/bits_wchar-t/cbindgen.toml @@ -0,0 +1,21 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/stddef.h.html +# +# This type is split out to prevent importing all of stddef.h into inttypes.h +include_guard = "_RELIBC_BITS_WCHAR_T_H" +after_includes = """ +// Integer type whose range of values can represent distinct codes for all +// members of the largest extended character set specified among the supported +// locales; the null character shall have the code value zero. +#ifndef _WCHAR_T +#define _WCHAR_T +#ifndef __cplusplus + #ifndef __WCHAR_TYPE__ + #define __WCHAR_TYPE__ int + #endif + typedef __WCHAR_TYPE__ wchar_t; +#endif // __cplusplus +#endif // _WCHAR_T +""" +language = "C" +no_includes = true +cpp_compat = true diff --git a/src/header/bits_wchar-t/mod.rs b/src/header/bits_wchar-t/mod.rs new file mode 100644 index 0000000000..d299be4a82 --- /dev/null +++ b/src/header/bits_wchar-t/mod.rs @@ -0,0 +1,5 @@ +//! `wchar_t` from `stddef.h` implementation. +//! +//! See . +//! +//! Implemented via ifdefs and defines in cbindgen. diff --git a/src/header/cpio/cbindgen.toml b/src/header/cpio/cbindgen.toml new file mode 100644 index 0000000000..c578d9b65e --- /dev/null +++ b/src/header/cpio/cbindgen.toml @@ -0,0 +1,16 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/cpio.h.html +# +# There are no spec quotations relating to includes +sys_includes = [] +include_guard = "_RELIBC_CPIO_H" +# cbindgen still can't handle exporting CStr +after_includes = """ +#define MAGIC "070707" +""" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/cpio/mod.rs b/src/header/cpio/mod.rs new file mode 100644 index 0000000000..a53ea2c0ab --- /dev/null +++ b/src/header/cpio/mod.rs @@ -0,0 +1,48 @@ +//! `cpio.h` implementation. +//! +//! See . + +use crate::platform::types::c_int; + +/// Read by owner. +pub const C_IRUSR: c_int = 0o0000400; +/// Write by owner. +pub const C_IWUSR: c_int = 0o0000200; +/// Execute by owner. +pub const C_IXUSR: c_int = 0o0000100; +/// Read by group. +pub const C_IRGRP: c_int = 0o0000040; +/// Write by group. +pub const C_IWGRP: c_int = 0o0000020; +/// Execute by group. +pub const C_IXGRP: c_int = 0o0000010; +/// Read by others. +pub const C_IROTH: c_int = 0o0000004; +/// Write by others. +pub const C_IWOTH: c_int = 0o0000002; +/// Execute by others. +pub const C_IXOTH: c_int = 0o0000001; + +/// Set user ID. +pub const C_ISUID: c_int = 0o0004000; +/// Set group ID. +pub const C_ISGID: c_int = 0o0002000; +/// On directories, restricted deletion flag. +pub const C_ISVTX: c_int = 0o0001000; + +/// Directory. +pub const C_ISDIR: c_int = 0o0040000; +/// FIFO. +pub const C_ISFIFO: c_int = 0o0010000; +/// Regular file. +pub const C_ISREG: c_int = 0o0100000; +/// Block special. +pub const C_ISBLK: c_int = 0o0060000; +/// Character special. +pub const C_ISCHR: c_int = 0o0020000; +/// Reserved. +pub const C_ISCTG: c_int = 0o0110000; +/// Symbolic link. +pub const C_ISLNK: c_int = 0o0120000; +/// Socket. +pub const C_ISSOCK: c_int = 0o0140000; diff --git a/src/header/crypt/argon2.rs b/src/header/crypt/argon2.rs new file mode 100644 index 0000000000..99b7e57e2d --- /dev/null +++ b/src/header/crypt/argon2.rs @@ -0,0 +1,16 @@ +use alloc::string::{String, ToString}; +use argon2::{ + Argon2, + password_hash::{PasswordHash, PasswordVerifier}, +}; + +pub fn crypt_argon2(key: &str, setting: &str) -> Option { + let hash = PasswordHash::new(setting).ok()?; + let argon2 = Argon2::default(); + + if argon2.verify_password(key.as_bytes(), &hash).is_ok() { + Some(setting.to_string()) + } else { + None + } +} diff --git a/src/header/crypt/blowfish.rs b/src/header/crypt/blowfish.rs new file mode 100644 index 0000000000..54e9fe1082 --- /dev/null +++ b/src/header/crypt/blowfish.rs @@ -0,0 +1,90 @@ +use crate::platform::types::{c_uchar, c_uint}; +use alloc::{string::String, vec::Vec}; +use base64ct::{Base64Bcrypt, Encoding}; +use bcrypt_pbkdf::bcrypt_pbkdf; +use core::str; + +const MIN_COST: u32 = 4; +const MAX_COST: u32 = 31; +const BHASH_WORDS: usize = 8; +const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4; + +/// Inspired by https://github.com/Keats/rust-bcrypt/blob/87fc59e917bcb6cf3f3752fc7f2b4c659d415597/src/lib.rs#L135 +fn split_with_prefix(hash: &str) -> Option<(&str, &str, c_uint)> { + let valid_prefixes = ["2y", "2b", "2a", "2x"]; + + // Should be [prefix, cost, hash] + let raw_parts: Vec<_> = hash.split('$').skip(1).collect(); + if raw_parts.len() != 3 { + return None; + } + + let prefix = raw_parts[0]; + let setting = raw_parts[2]; + + if !valid_prefixes.contains(&prefix) { + return None; + } + + raw_parts[1] + .parse::() + .ok() + .map(|cost| (prefix, setting, cost)) +} + +/// Performs Blowfish key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the Blowfish key derivation. It must be a string slice (`&str`) +/// and should follow the format `$$$`, where `` is a string that +/// indicates the type of the hash (e.g., "$2a$"), `` is a decimal number representing +/// the cost factor for the Blowfish operation, and `` is a base64-encoded string +/// representing the salt to be used for the Blowfish function. +/// +/// # Returns +/// * `Option`: Returns `Some(String)` if the Blowfish operation was successful, where the +/// returned string is the result of the Blowfish operation formatted according to the Modular +/// Crypt Format (MCF). If the Blowfish operation failed, it returns `None`. +/// +/// # Errors +/// * If the cost factor is outside the range `[MIN_COST, MAX_COST]`. +/// +/// # Example +/// ``` +/// let password = "correctbatteryhorsestapler"; +/// let setting = "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"; +/// let result = crypt_blowfish(password, setting); +/// assert!(result.is_some()); +///``` +/// +/// # Note +/// The `crypt_blowfish` function uses the Blowfish block cipher for hashing. +/// The output of the Blowfish operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_blowfish(passw: &str, setting: &str) -> Option { + if let Some((prefix, setting, cost)) = split_with_prefix(setting) { + if !(MIN_COST..=MAX_COST).contains(&cost) { + return None; + } + // Passwords need to be null terminated + let mut vec = Vec::with_capacity(passw.len() + 1); + vec.extend_from_slice(passw.as_bytes()); + vec.push(0); + + // We only consider the first 72 chars; truncate if necessary. + let passw_t = if vec.len() > 72 { &vec[..72] } else { &vec }; + let passw: &[c_uchar] = passw_t; + let setting = setting.as_bytes(); + let mut output = vec![0; BHASH_OUTPUT_SIZE + 1]; + + bcrypt_pbkdf(passw, setting, cost, &mut output).ok()?; + Some(format!( + "${}${}${}", + prefix, + cost, + Base64Bcrypt::encode_string(&output), + )) + } else { + None + } +} diff --git a/src/header/crypt/cbindgen.toml b/src/header/crypt/cbindgen.toml new file mode 100644 index 0000000000..bdcf5ebda1 --- /dev/null +++ b/src/header/crypt/cbindgen.toml @@ -0,0 +1,9 @@ +include_guard = "_RELIBC_CRYPT_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + + +[enum] +prefix_with_name = true \ No newline at end of file diff --git a/src/header/crypt/md5.rs b/src/header/crypt/md5.rs new file mode 100644 index 0000000000..25cfacff37 --- /dev/null +++ b/src/header/crypt/md5.rs @@ -0,0 +1,146 @@ +use crate::platform::types::c_uchar; +use alloc::string::String; +use base64ct::{Base64ShaCrypt, Encoding}; +use core::str; +use md5_crypto::{Digest, Md5}; + +// Block size for MD5 +const BLOCK_SIZE: usize = 16; +// PWD part length of the password string +const PW_SIZE_MD5: usize = 22; +// Maximum length of a setting +const SALT_MAX: usize = 8; +// Inverse encoding map for MD5. +const MAP_MD5: [c_uchar; BLOCK_SIZE] = [12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11]; + +const KEY_MAX: usize = 30000; + +fn encode_md5(source: &[c_uchar]) -> Option<[c_uchar; PW_SIZE_MD5]> { + let mut transposed = [0; BLOCK_SIZE]; + for (i, &ti) in MAP_MD5.iter().enumerate() { + transposed[i] = source[ti as usize]; + } + let mut buf = [0; PW_SIZE_MD5]; + Base64ShaCrypt::encode(&transposed, &mut buf).ok()?; + Some(buf) +} + +/// Function taken from PR: https://github.com/RustCrypto/password-hashes/pull/351 +/// This won't be needed once the PR is merged +fn inner_md5(passw: &str, setting: &str) -> Option { + let mut digest_b = Md5::default(); + digest_b.update(passw); + digest_b.update(setting); + digest_b.update(passw); + let hash_b = digest_b.finalize(); + + let mut digest_a = Md5::default(); + digest_a.update(passw); + digest_a.update("$1$"); + digest_a.update(setting); + + let mut pw_len = passw.len(); + let rounds = pw_len / BLOCK_SIZE; + for _ in 0..rounds { + digest_a.update(hash_b); + } + + // leftover passw + digest_a.update(&hash_b[..(pw_len - rounds * BLOCK_SIZE)]); + + while pw_len > 0 { + match pw_len & 1 { + 0 => digest_a.update(&passw[..1]), + 1 => digest_a.update([0u8]), + _ => unreachable!(), + } + pw_len >>= 1; + } + + let mut hash_a = digest_a.finalize(); + + // Repeatedly run the collected hash value through MD5 to burn + // CPU cycles + for i in 0..1000_usize { + // new hasher + let mut hasher = Md5::default(); + + // Add key or last result + if (i & 1) != 0 { + hasher.update(passw); + } else { + hasher.update(hash_a); + } + + // Add setting for numbers not divisible by 3 + if i % 3 != 0 { + hasher.update(setting); + } + + // Add key for numbers not divisible by 7 + if i % 7 != 0 { + hasher.update(passw); + } + + // Add key or last result + if (i & 1) != 0 { + hasher.update(hash_a); + } else { + hasher.update(passw); + } + + // digest_c.clone_from_slice(&hasher.finalize()); + hash_a = hasher.finalize(); + } + encode_md5(hash_a.as_slice()) + .map(|encstr| format!("$1${}${}", setting, str::from_utf8(&encstr).unwrap())) +} + +/// Performs MD5 hashing on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the MD5 hashing. It must be a string slice (`&str`) +/// and should start with "$1$". The rest of the string should represent the salt +/// to be used for the MD5 hashing. +/// +/// # Returns +/// * `Option`: Returns `Some(String)` if the MD5 operation was successful, where the +/// returned string is the result of the MD5 operation formatted according to the Modular +/// Crypt Format (MCF). If the MD5 operation failed, it returns `None`. +/// +/// # Errors +/// * If the `passw` length exceeds `KEY_MAX`. +/// * If the `setting` does not start with "$1$". +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$1$saltstring"; +/// let result = crypt_md5(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_md5` function uses the MD5 hashing algorithm for hashing. +/// The output of the MD5 operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_md5(passw: &str, setting: &str) -> Option { + /* reject large keys */ + if passw.len() > KEY_MAX { + return None; + } + + if &setting[0..3] != "$1$" { + return None; + } + + let cursor = 3; + let slen = cursor + + setting[cursor..cursor + SALT_MAX] + .chars() + .take_while(|c| *c != '$') + .count(); + let setting = &setting[cursor..slen]; + + inner_md5(passw, setting) +} diff --git a/src/header/crypt/mod.rs b/src/header/crypt/mod.rs new file mode 100644 index 0000000000..4a3edd6872 --- /dev/null +++ b/src/header/crypt/mod.rs @@ -0,0 +1,121 @@ +//! `crypt.h` implementation. +//! +//! Non-POSIX, see . + +use ::scrypt::password_hash::{Salt, SaltString}; +use alloc::{ + ffi::CString, + string::{String, ToString}, +}; +use core::ptr; +use rand::{Rng, SeedableRng, rngs::SmallRng}; + +use crate::{ + c_str::CStr, + header::{errno::EINVAL, stdlib::rand}, + platform::{ + self, + types::{c_char, c_int}, + }, +}; + +mod argon2; +mod blowfish; +mod md5; +mod pbkdf2; +mod scrypt; +mod sha; + +use self::{ + argon2::crypt_argon2, + blowfish::crypt_blowfish, + md5::crypt_md5, + pbkdf2::crypt_pbkdf2, + scrypt::crypt_scrypt, + sha::{ + ShaType::{Sha256, Sha512}, + crypt_sha, + }, +}; + +/// See . +#[repr(C)] +pub struct crypt_data { + initialized: c_int, + buff: [c_char; 256], +} + +impl crypt_data { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + crypt_data { + initialized: 1, + buff: [0; 256], + } + } +} + +fn gen_salt() -> Option { + let mut rng = SmallRng::seed_from_u64(unsafe { rand() as u64 }); + let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH]; + rng.fill_bytes(&mut bytes); + Some(SaltString::encode_b64(&bytes).ok()?.as_str().to_string()) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crypt_r( + key: *const c_char, + setting: *const c_char, + data: *mut crypt_data, +) -> *mut c_char { + if unsafe { (*data).initialized } == 0 { + unsafe { *data = crypt_data::new() }; + } + + let key = unsafe { CStr::from_ptr(key) } + .to_str() + .expect("key must be utf-8"); + let setting = unsafe { CStr::from_ptr(setting) } + .to_str() + .expect("setting must be utf-8"); + + let encoded = if setting.starts_with('$') { + if setting.starts_with("$1$") { + crypt_md5(key, setting) + } else if setting.starts_with("$2") && setting.as_bytes().get(3) == Some(&b'$') { + crypt_blowfish(key, setting) + } else if setting.starts_with("$5$") { + crypt_sha(key, setting, Sha256) + } else if setting.starts_with("$6$") { + crypt_sha(key, setting, Sha512) + } else if setting.starts_with("$7$") { + crypt_scrypt(key, setting) + } else if setting.starts_with("$8$") { + crypt_pbkdf2(key, setting) + } else if setting.starts_with("$argon2") { + crypt_argon2(key, setting) + } else { + platform::ERRNO.set(EINVAL); + return ptr::null_mut(); + } + } else { + None + }; + + if let Some(inner) = encoded { + let len = inner.len(); + if let Ok(ret) = CString::new(inner) { + let ret_ptr = ret.into_raw(); + unsafe { + let dst = (*data).buff.as_mut_ptr(); + ptr::copy_nonoverlapping(ret_ptr, dst.cast(), len); + } + ret_ptr.cast() + } else { + ptr::null_mut() + } + } else { + ptr::null_mut() + } +} diff --git a/src/header/crypt/pbkdf2.rs b/src/header/crypt/pbkdf2.rs new file mode 100644 index 0000000000..9a614873e6 --- /dev/null +++ b/src/header/crypt/pbkdf2.rs @@ -0,0 +1,64 @@ +use super::gen_salt; +use alloc::string::{String, ToString}; +use base64ct::{Base64Bcrypt, Encoding}; +use core::str; +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; + +/// Performs PBKDF2 key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the PBKDF2 key derivation. It must be a string slice (`&str`) +/// and should follow the format `$$`. The `` part should be a hexadecimal +/// number representing the iteration count for the PBKDF2 function. The `` part should +/// be a base64-encoded string representing the salt to be used for the PBKDF2 function. +/// +/// # Returns +/// * `Option`: Returns `Some(String)` if the PBKDF2 operation was successful, where the +/// returned string is the result of the PBKDF2 operation formatted according to the Modular +/// Crypt Format (MCF). If the PBKDF2 operation failed, it returns `None`. +/// +/// # Errors +/// * If the `setting` does not contain a '$' character. +/// * If the `setting` contains another '$' character after the first one. +/// * If the `` part of the `setting` is empty. +/// * If the `` part of the `setting` cannot be converted into a `u32` integer. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$8$3e8$salt"; +/// let result = crypt_pbkdf2(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_pbkdf2` function uses the SHA256 hashing algorithm for the PBKDF2 operation. +/// The output of the PBKDF2 operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_pbkdf2(passw: &str, setting: &str) -> Option { + if let Some((iter_str, salt)) = &setting[3..].split_once('$') { + if salt.contains('$') { + return None; + } + + let actual_salt = if !salt.is_empty() { + salt.to_string() + } else { + gen_salt()? + }; + + let iter = u32::from_str_radix(iter_str, 16).ok()?; + let mut buffer = [0u8; 32]; + pbkdf2_hmac::(passw.as_bytes(), actual_salt.as_bytes(), iter, &mut buffer); + + Some(format!( + "$8${}${}${}", + iter_str, + salt, + Base64Bcrypt::encode_string(&buffer) + )) + } else { + None + } +} diff --git a/src/header/crypt/scrypt.rs b/src/header/crypt/scrypt.rs new file mode 100644 index 0000000000..334d9dfdb4 --- /dev/null +++ b/src/header/crypt/scrypt.rs @@ -0,0 +1,113 @@ +use super::gen_salt; +use crate::platform::types::{c_uchar, c_uint}; +use alloc::string::{String, ToString}; +use base64ct::{Base64Bcrypt, Encoding}; +use core::str; +use scrypt::{Params, scrypt}; + +/// Map for encoding and decoding +#[inline(always)] +fn to_digit(c: char, radix: u32) -> Option { + match c { + '.' => Some(0), + '/' => Some(1), + _ => c.to_digit(radix).map(|d| d + 2), + } +} + +/// Decodes a 5 character lengt str value to c_uint +/// +/// # Arguments +/// +/// * `value` - A string slice that represents a u32 value in base64 +/// +/// # Returns +/// +/// * `Option` - Returns the decoded c_uint value if successful, otherwise None +fn dencode_uint(value: &str) -> Option { + if value.len() != 5 { + return None; + } + + value + .chars() + .enumerate() + .try_fold(0 as c_uint, |acc, (i, c)| { + acc.checked_add((to_digit(c, 30)? as c_uint) << (i * 6)) + }) +} + +/// Reads settings for password encryption +/// +/// # Arguments +/// +/// * `setting` - A string slice that represents the settings +/// +/// # Returns +/// +/// * `Option<(c_uchar, c_uint, c_uint, String)>` - Returns a tuple containing the settings if successful, otherwise None +fn read_setting(setting: &str) -> Option<(c_uchar, c_uint, c_uint, String)> { + let nlog2 = to_digit(setting.chars().next()?, 30)? as c_uchar; + let r = dencode_uint(&setting[1..6])?; + let p = dencode_uint(&setting[6..11])?; + + let salt = &setting[11..]; + let actual_salt = if !salt.is_empty() { + salt.to_string() + } else { + gen_salt()? + }; + + Some((nlog2, r, p, actual_salt)) +} + +/// Performs Scrypt key derivation on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the Scrypt key derivation. It must be a string slice (`&str`) +/// and should follow the format `$$$

$`. The `` part should be a decimal +/// number representing the logarithm base 2 of the CPU/memory cost factor N for Scrypt. The `` +/// part should be a decimal number representing the block size r. The `

` part should be a decimal +/// number representing the parallelization factor p. The `` part should be a base64-encoded +/// string representing the salt to be used for the Scrypt function. +/// +/// # Returns +/// * `Option`: Returns `Some(String)` if the Scrypt operation was successful, where the +/// returned string is the result of the Scrypt operation formatted according to the Modular +/// Crypt Format (MCF). If the Scrypt operation failed, it returns `None`. +/// +/// # Errors +/// * If the `setting` length is less than 14 characters. +/// * If the `scrypt` function fails to perform the Scrypt operation. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$7$C6..../....SodiumChloride"; +/// let result = crypt_scrypt(password, setting); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_scrypt` function uses the Scrypt key derivation function for hashing. +/// The output of the Scrypt operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_scrypt(passw: &str, setting: &str) -> Option { + if setting.len() < 14 { + return None; + } + + let (nlog2, r, p, salt) = read_setting(&setting[3..])?; + + let params = Params::new(nlog2, r, p, 32).ok()?; + let mut output = [0u8; 32]; + + scrypt(passw.as_bytes(), salt.as_bytes(), ¶ms, &mut output).ok()?; + + Some(format!( + "$7${}${}${}", + &setting[3..14], + salt, + Base64Bcrypt::encode_string(&output) + )) +} diff --git a/src/header/crypt/sha.rs b/src/header/crypt/sha.rs new file mode 100644 index 0000000000..a2935a77a2 --- /dev/null +++ b/src/header/crypt/sha.rs @@ -0,0 +1,141 @@ +use alloc::string::{String, ToString}; + +use sha_crypt::{ + ROUNDS_DEFAULT, ROUNDS_MAX, ROUNDS_MIN, Sha256Params, Sha512Params, sha256_crypt_b64, + sha512_crypt_b64, +}; + +use crate::platform::types::c_ulong; + +// key limit is not part of the original design, added for DoS protection. +// rounds limit has been lowered (versus the reference/spec), also for DoS +// protection. runtime is O(klen^2 + klen*rounds) +const KEY_MAX: usize = 256; +const SALT_MAX: usize = 16; +const RSTRING: &str = "rounds="; + +pub enum ShaType { + Sha256, + Sha512, +} + +/// Performs SHA hashing on a given password with a specific setting. +/// +/// # Parameters +/// * `passw`: The password to be hashed. It must be a string slice (`&str`). +/// * `setting`: The settings for the SHA hashing. It must be a string slice (`&str`) +/// and should start with "$5$" for SHA256 or "$6$" for SHA512. The rest of the string should represent the salt +/// to be used for the SHA hashing. +/// * `cipher`: The type of SHA algorithm to use. It should be either `ShaType::Sha256` or `ShaType::Sha512`. +/// +/// # Returns +/// * `Option`: Returns `Some(String)` if the SHA operation was successful, where the +/// returned string is the result of the SHA operation formatted according to the Modular +/// Crypt Format (MCF). If the SHA operation failed, it returns `None`. +/// +/// # Errors +/// * If the `passw` length exceeds `KEY_MAX`. +/// * If the `setting` does not start with "$5$" or "$6$". +/// * If the `setting` does not contain a '$' character. +/// * If the `setting` contains another '$' character after the first one. +/// * If the `setting` contains invalid characters. +/// * If the `setting` contains an invalid number of rounds. +/// * If the `sha256_crypt_b64` or `sha512_crypt_b64` function fails to hash the password. +/// +/// # Example +/// ``` +/// let password = "my_password"; +/// let setting = "$5$rounds=1400$anotherlongsaltstringg"; +/// let result = crypt_sha(password, setting, ShaType::Sha256); +/// assert!(result.is_some()); +/// ``` +/// +/// # Note +/// The `crypt_sha` function uses the SHA256 or SHA512 hashing algorithm for hashing. +/// The output of the SHA operation is base64-encoded using the BCrypt variant of base64. +pub fn crypt_sha(passw: &str, setting: &str, cipher: ShaType) -> Option { + let mut cursor = 3; + let rounds; + + /* reject large keys */ + if passw.len() > KEY_MAX { + return None; + } + + // SHA256 + // setting: $5$rounds=n$setting$ (rounds=n$ and closing $ are optional) + // SHA512 + // setting: $6$rounds=n$setting$ (rounds=n$ and closing $ are optional) + let param = match cipher { + ShaType::Sha256 => "$5$", + ShaType::Sha512 => "$6$", + }; + + if &setting[0..3] != param { + return None; + } + + let has_round; + // 7 is len("rounds=") + if &setting[cursor..cursor + 7] == RSTRING { + cursor += 7; + has_round = true; + if let Some(c_end) = setting[cursor..].chars().position(|r| r == '$') { + if let Ok(u) = setting[cursor..cursor + c_end].parse::() { + cursor += c_end + 1; + rounds = u.min(ROUNDS_MAX as c_ulong).max(ROUNDS_MIN as c_ulong); + } else { + return None; + } + } else { + return None; + } + } else { + has_round = false; + rounds = ROUNDS_DEFAULT as c_ulong; + } + + let mut slen = cursor; + + for i in 0..SALT_MAX.min(setting.len() - cursor) { + let idx = cursor + i; + + if &setting[idx..idx + 1] == "$" { + break; + } + + // reject characters that interfere with /etc/shadow parsing + if &setting[idx..idx + 1] == "\n" || &setting[idx..idx + 1] == ":" { + return None; + } + slen += 1; + } + + let setting = &setting[cursor..slen]; + + if let Ok(enc) = match cipher { + ShaType::Sha256 => { + let params = Sha256Params::new(rounds as usize) + .unwrap_or(Sha256Params::new(ROUNDS_DEFAULT).unwrap()); + sha256_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms) + } + ShaType::Sha512 => { + let params = Sha512Params::new(rounds as usize) + .unwrap_or(Sha512Params::new(ROUNDS_DEFAULT).unwrap()); + sha512_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms) + } + } { + let (r_slice, rn_slice) = if has_round { + (RSTRING, rounds.to_string() + "$") + } else { + ("", String::new()) + }; + + Some(format!( + "{}{}{}{}${}", + param, r_slice, rn_slice, setting, enc + )) + } else { + None + } +} diff --git a/src/header/ctype/cbindgen.toml b/src/header/ctype/cbindgen.toml index f47026b46c..9a13b37aab 100644 --- a/src/header/ctype/cbindgen.toml +++ b/src/header/ctype/cbindgen.toml @@ -1,8 +1,20 @@ -sys_includes = ["bits/ctype.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/ctype.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the locale_t type as described in , representing a locale object." +# +# features.h included for #[deprecated] annotation +sys_includes = ["features.h", "stddef.h"] include_guard = "_RELIBC_CTYPE_H" +after_includes = """ +#include // for locale_t + +#define _tolower(c) tolower(c) +#define _toupper(c) toupper(c) +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/ctype/mod.rs b/src/header/ctype/mod.rs index c9a58dfd59..3d8da3cffe 100644 --- a/src/header/ctype/mod.rs +++ b/src/header/ctype/mod.rs @@ -1,53 +1,117 @@ -//! ctype implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/ctype.h.html +//! `ctype.h` implementation. +//! +//! See . -use crate::platform::types::*; +use crate::{header::bits_locale_t::locale_t, platform::types::c_int}; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isalnum(c: c_int) -> c_int { c_int::from(isdigit(c) != 0 || isalpha(c) != 0) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isalnum_l(c: c_int, _loc: locale_t) -> c_int { + isalnum(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isalpha(c: c_int) -> c_int { c_int::from(islower(c) != 0 || isupper(c) != 0) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isalpha_l(c: c_int, _loc: locale_t) -> c_int { + isalpha(c) +} + +/// See . +/// +/// The `isascii()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn isascii(c: c_int) -> c_int { c_int::from((c & !0x7f) == 0) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isblank(c: c_int) -> c_int { c_int::from(c == c_int::from(b' ') || c == c_int::from(b'\t')) } -#[no_mangle] -pub extern "C" fn iscntrl(c: c_int) -> c_int { - c_int::from((c >= 0x00 && c <= 0x1f) || c == 0x7f) +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isblank_l(c: c_int, _loc: locale_t) -> c_int { + isblank(c) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iscntrl(c: c_int) -> c_int { + c_int::from((0x00..=0x1f).contains(&c) || c == 0x7f) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iscntrl_l(c: c_int, _loc: locale_t) -> c_int { + iscntrl(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isdigit(c: c_int) -> c_int { c_int::from(c >= c_int::from(b'0') && c <= c_int::from(b'9')) } -#[no_mangle] -pub extern "C" fn isgraph(c: c_int) -> c_int { - c_int::from(c >= 0x21 && c <= 0x7e) +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isdigit_l(c: c_int, _loc: locale_t) -> c_int { + isdigit(c) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isgraph(c: c_int) -> c_int { + c_int::from((0x21..=0x7e).contains(&c)) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isgraph_l(c: c_int, _loc: locale_t) -> c_int { + isgraph(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn islower(c: c_int) -> c_int { c_int::from(c >= c_int::from(b'a') && c <= c_int::from(b'z')) } -#[no_mangle] -pub extern "C" fn isprint(c: c_int) -> c_int { - c_int::from(c >= 0x20 && c < 0x7f) +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn islower_l(c: c_int, _loc: locale_t) -> c_int { + islower(c) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isprint(c: c_int) -> c_int { + c_int::from((0x20..0x7f).contains(&c)) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isprint_l(c: c_int, _loc: locale_t) -> c_int { + isprint(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn ispunct(c: c_int) -> c_int { c_int::from( (c >= c_int::from(b'!') && c <= c_int::from(b'/')) @@ -57,7 +121,14 @@ pub extern "C" fn ispunct(c: c_int) -> c_int { ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn ispunct_l(c: c_int, _loc: locale_t) -> c_int { + ispunct(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isspace(c: c_int) -> c_int { c_int::from( c == c_int::from(b' ') @@ -69,37 +140,66 @@ pub extern "C" fn isspace(c: c_int) -> c_int { ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isspace_l(c: c_int, _loc: locale_t) -> c_int { + isspace(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isupper(c: c_int) -> c_int { c_int::from(c >= c_int::from(b'A') && c <= c_int::from(b'Z')) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isupper_l(c: c_int, _loc: locale_t) -> c_int { + isupper(c) +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isxdigit(c: c_int) -> c_int { c_int::from(isdigit(c) != 0 || (c | 32 >= c_int::from(b'a') && c | 32 <= c_int::from(b'f'))) } -#[no_mangle] -/// The comment in musl: -/// "nonsense function that should NEVER be used!" +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn isxdigit_l(c: c_int, _loc: locale_t) -> c_int { + isxdigit(c) +} + +/// See . +/// +/// The `toascii()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn toascii(c: c_int) -> c_int { c & 0x7f } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn tolower(c: c_int) -> c_int { - if isupper(c) != 0 { - c | 0x20 - } else { - c - } + if isupper(c) != 0 { c | 0x20 } else { c } } -#[no_mangle] -pub extern "C" fn toupper(c: c_int) -> c_int { - if islower(c) != 0 { - c & !0x20 - } else { - c - } +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn tolower_l(c: c_int, _loc: locale_t) -> c_int { + tolower(c) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn toupper(c: c_int) -> c_int { + if islower(c) != 0 { c & !0x20 } else { c } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn toupper_l(c: c_int, _loc: locale_t) -> c_int { + toupper(c) } diff --git a/src/header/dirent/cbindgen.toml b/src/header/dirent/cbindgen.toml index 5ba9d76a14..b45ebc6b14 100644 --- a/src/header/dirent/cbindgen.toml +++ b/src/header/dirent/cbindgen.toml @@ -1,9 +1,19 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dirent.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the ino_t, reclen_t, size_t, and ssize_t types as described in ." sys_includes = ["sys/types.h"] include_guard = "_RELIBC_DIRENT_H" language = "C" style = "Both" -trailer = "#include " -no_includes = true +after_includes = """ + +// Shamelessly stolen from musl +// Macros to convert between struct dirent and struct stat types. +#define IFTODT(x) ((x)>>12 & 017) +#define DTTOIF(x) ((x)<<12) +""" +no_includes = false cpp_compat = true [enum] diff --git a/src/header/dirent/mod.rs b/src/header/dirent/mod.rs index 76b061a356..17eccf0b4e 100644 --- a/src/header/dirent/mod.rs +++ b/src/header/dirent/mod.rs @@ -1,31 +1,171 @@ -//! dirent implementation following http://pubs.opengroup.org/onlinepubs/009695399/basedefs/dirent.h.html +//! `dirent.h` implementation. +//! +//! See . -use alloc::boxed::Box; -use core::{mem, ptr}; +use alloc::{boxed::Box, vec::Vec}; +use core::{mem, ptr, slice}; use crate::{ c_str::CStr, c_vec::CVec, + error::{Errno, ResultExt, ResultExtPtrMut}, fs::File, - header::{errno, fcntl, stdlib, string}, - io::{Seek, SeekFrom}, - platform::{self, types::*, Pal, Sys}, + header::{ + errno::{EINVAL, EIO, ENOMEM, ENOTDIR}, + fcntl, stdlib, string, sys_stat, + }, + out::Out, + platform::{ + self, Pal, Sys, + types::{ + c_char, c_int, c_long, c_uchar, c_ushort, c_void, ino_t, off_t, reclen_t, size_t, + ssize_t, + }, + }, }; -const DIR_BUF_SIZE: usize = mem::size_of::() * 3; +// values of below constants taken from musl -// No repr(C) needed, C won't see the content +/// Unknown file type. +pub const DT_UNKNOWN: c_int = 0; +/// FIFO special. +pub const DT_FIFO: c_int = 1; +/// Character special. +pub const DT_CHR: c_int = 2; +/// Directory. +pub const DT_DIR: c_int = 4; +/// Block special. +pub const DT_BLK: c_int = 6; +/// Regular. +pub const DT_REG: c_int = 8; +/// Symbolic link. +pub const DT_LNK: c_int = 10; +/// Socket. +pub const DT_SOCK: c_int = 12; +/// Non-POSIX. +/// Whiteout inode. +pub const DT_WHT: c_int = 14; + +// values of above constants taken from musl + +/// cbindgen:ignore +const INITIAL_BUFSIZE: usize = 512; + +/// See . +// No repr(C) needed, as this is a completely opaque struct. Being accessed as a pointer, in C it's +// just defined as `struct DIR`. pub struct DIR { file: File, - buf: [c_char; DIR_BUF_SIZE], - // index and len are specified in bytes - index: usize, - len: usize, + buf: Vec, + buf_offset: usize, // The last value of d_off, used by telldir - offset: usize, + opaque_offset: u64, +} +impl DIR { + pub fn new(path: CStr) -> Result, Errno> { + Ok(Box::new(Self { + file: File::open( + path, + fcntl::O_RDONLY | fcntl::O_DIRECTORY | fcntl::O_CLOEXEC, + )?, + buf: Vec::with_capacity(INITIAL_BUFSIZE), + buf_offset: 0, + opaque_offset: 0, + })) + } + pub fn from_fd(fd: c_int) -> Result, Errno> { + let mut stat = sys_stat::stat::default(); + Sys::fstat(fd, Out::from_mut(&mut stat))?; + if (stat.st_mode & sys_stat::S_IFMT) != sys_stat::S_IFDIR { + return Err(Errno(ENOTDIR)); + } + Sys::fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as _)?; + + // Take ownership now but not earlier so we don't close the fd on error. + let file = File::new(fd); + Ok(Self { + file, + buf: Vec::with_capacity(INITIAL_BUFSIZE), + buf_offset: 0, + opaque_offset: 0, + } + .into()) + } + fn next_dirent(&mut self) -> Result<*mut dirent, Errno> { + let mut this_dent = self.buf.get(self.buf_offset..).ok_or(Errno(EIO))?; + if this_dent.is_empty() { + let size = loop { + self.buf.resize(self.buf.capacity(), 0_u8); + // TODO: uninitialized memory? + match Sys::getdents(*self.file, &mut self.buf, self.opaque_offset) { + Ok(size) => break size, + Err(Errno(EINVAL)) => { + self.buf + .try_reserve_exact(self.buf.len()) + .map_err(|_| Errno(ENOMEM))?; + continue; + } + Err(Errno(other)) => return Err(Errno(other)), + } + }; + self.buf.truncate(size); + self.buf_offset = 0; + + if size == 0 { + return Ok(core::ptr::null_mut()); + } + this_dent = &self.buf; + } + let (this_reclen, this_next_opaque) = + unsafe { Sys::dent_reclen_offset(this_dent, self.buf_offset).ok_or(Errno(EIO))? }; + + //println!("CDENT {} {}+{}", self.opaque_offset, self.buf_offset, this_reclen); + + let next_off = self + .buf_offset + .checked_add(usize::from(this_reclen)) + .ok_or(Errno(EIO))?; + if next_off > self.buf.len() { + return Err(Errno(EIO)); + } + if this_dent.len() < usize::from(this_reclen) { + // Don't want memory corruption if a scheme is adversarial. + return Err(Errno(EIO)); + } + let dent_ptr = this_dent.as_ptr() as *mut dirent; + + self.opaque_offset = this_next_opaque; + self.buf_offset = next_off; + Ok(dent_ptr) + } + fn seek(&mut self, off: u64) { + let Ok(_) = Sys::dir_seek(*self.file, off) else { + return; + }; + self.buf.clear(); + self.buf_offset = 0; + self.opaque_offset = off; + } + fn rewind(&mut self) { + self.opaque_offset = 0; + let Ok(_) = Sys::dir_seek(*self.file, 0) else { + return; + }; + self.buf.clear(); + self.buf_offset = 0; + self.opaque_offset = 0; + } + fn close(mut self) -> Result<(), Errno> { + // Reference files aren't closed when dropped + self.file.reference = true; + + // TODO: result + Sys::close(*self.file) + } } +/// See . #[repr(C)] #[derive(Clone)] pub struct dirent { @@ -36,69 +176,114 @@ pub struct dirent { pub d_name: [c_char; 256], } -#[no_mangle] -pub unsafe extern "C" fn opendir(path: *const c_char) -> *mut DIR { - let path = CStr::from_ptr(path); - let file = match File::open( - path, - fcntl::O_RDONLY | fcntl::O_DIRECTORY | fcntl::O_CLOEXEC, - ) { - Ok(file) => file, - Err(_) => return ptr::null_mut(), - }; - - Box::into_raw(Box::new(DIR { - file, - buf: [0; DIR_BUF_SIZE], - index: 0, - len: 0, - offset: 0, - })) +/// See . +/// must have the same struct layout as dirent +#[repr(C)] +#[derive(Clone)] +pub struct posix_dent { + pub d_ino: ino_t, + pub d_off: off_t, // not specified by posix + pub d_reclen: reclen_t, + pub d_type: c_uchar, + pub d_name: [c_char; 256], } -#[no_mangle] -pub unsafe extern "C" fn closedir(dir: *mut DIR) -> c_int { - let mut dir = Box::from_raw(dir); +/// cbindgen:ignore +#[cfg(target_os = "redox")] +const _: () = { + use core::mem::{offset_of, size_of}; + use syscall::dirent::DirentHeader; - let ret = Sys::close(*dir.file); - - // Reference files aren't closed when dropped - dir.file.reference = true; - - ret -} - -#[no_mangle] -pub unsafe extern "C" fn dirfd(dir: *mut DIR) -> c_int { - *((*dir).file) -} - -#[no_mangle] -pub unsafe extern "C" fn readdir(dir: *mut DIR) -> *mut dirent { - if (*dir).index >= (*dir).len { - let read = Sys::getdents( - *(*dir).file, - (*dir).buf.as_mut_ptr() as *mut dirent, - (*dir).buf.len(), - ); - if read <= 0 { - if read != 0 && read != -errno::ENOENT { - platform::errno = -read; - } - return ptr::null_mut(); - } - - (*dir).index = 0; - (*dir).len = read as usize; + if offset_of!(dirent, d_ino) != offset_of!(DirentHeader, inode) { + panic!("struct dirent layout mismatch (inode)"); } + if offset_of!(dirent, d_off) != offset_of!(DirentHeader, next_opaque_id) { + panic!("struct dirent layout mismatch (inode)"); + } + if offset_of!(dirent, d_reclen) != offset_of!(DirentHeader, record_len) { + panic!("struct dirent layout mismatch (len)"); + } + if offset_of!(dirent, d_type) != offset_of!(DirentHeader, kind) { + panic!("struct dirent layout mismatch (kind)"); + } + if offset_of!(dirent, d_name) != size_of::() { + panic!("struct dirent layout mismatch (name)"); + } +}; - let ptr = (*dir).buf.as_mut_ptr().add((*dir).index) as *mut dirent; - - (*dir).offset = (*ptr).d_off as usize; - (*dir).index += (*ptr).d_reclen as usize; - ptr +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn alphasort(first: *mut *const dirent, second: *mut *const dirent) -> c_int { + unsafe { string::strcoll((**first).d_name.as_ptr(), (**second).d_name.as_ptr()) } } -// #[no_mangle] + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn closedir(dir: Box

) -> c_int { + dir.close().map(|()| 0).or_minus_one_errno() +} + +/// See +/// +/// FreeBSD extension that transfers ownership of the directory file descriptor to the user. +/// +/// It doesn't matter if DIR was opened with [`opendir`] or [`fdopendir`]. +#[unsafe(no_mangle)] +pub extern "C" fn fdclosedir(dir: Box) -> c_int { + let mut file = dir.file; + file.reference = true; + + *file +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn dirfd(dir: &mut DIR) -> c_int { + *dir.file +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn opendir(path: *const c_char) -> *mut DIR { + let path = unsafe { CStr::from_ptr(path) }; + + DIR::new(path).or_errno_null_mut() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn fdopendir(fd: c_int) -> *mut DIR { + DIR::from_fd(fd).or_errno_null_mut() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn posix_getdents( + fildes: c_int, + buf: *mut c_void, + nbyte: size_t, + _flags: c_int, +) -> ssize_t { + let slice = unsafe { slice::from_raw_parts_mut(buf.cast::(), nbyte) }; + + Sys::posix_getdents(fildes, slice) + .map(|s| s as ssize_t) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn readdir(dir: &mut DIR) -> *mut dirent { + dir.next_dirent().or_errno_null_mut() +} + +/// See . +/// +/// # Deprecation +/// The `readdir_r()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 8. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn readdir_r( _dir: *mut DIR, _entry: *mut dirent, @@ -107,35 +292,21 @@ pub extern "C" fn readdir_r( unimplemented!(); // plus, deprecated } -#[no_mangle] -pub unsafe extern "C" fn telldir(dir: *mut DIR) -> c_long { - (*dir).offset as c_long -} -#[no_mangle] -pub unsafe extern "C" fn seekdir(dir: *mut DIR, off: c_long) { - let _ = (*dir).file.seek(SeekFrom::Start(off as u64)); - (*dir).offset = off as usize; - (*dir).index = 0; - (*dir).len = 0; -} -#[no_mangle] -pub unsafe extern "C" fn rewinddir(dir: *mut DIR) { - seekdir(dir, 0) +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn rewinddir(dir: &mut DIR) { + dir.rewind(); } -#[no_mangle] -pub unsafe extern "C" fn alphasort(first: *mut *const dirent, second: *mut *const dirent) -> c_int { - string::strcoll((**first).d_name.as_ptr(), (**second).d_name.as_ptr()) -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn scandir( dirp: *const c_char, namelist: *mut *mut *mut dirent, filter: Option c_int>, compare: Option c_int>, ) -> c_int { - let dir = opendir(dirp); + let dir = unsafe { opendir(dirp) }; if dir.is_null() { return -1; } @@ -145,54 +316,84 @@ pub unsafe extern "C" fn scandir( Err(err) => return -1, }; - let old_errno = platform::errno; - platform::errno = 0; + let old_errno = platform::ERRNO.get(); + platform::ERRNO.set(0); loop { - let entry: *mut dirent = readdir(dir); + let entry: *mut dirent = readdir(unsafe { &mut *dir }); if entry.is_null() { break; } - if let Some(filter) = filter { - if filter(entry) == 0 { - continue; - } + if let Some(filter) = filter + && filter(entry) == 0 + { + continue; } - let copy = platform::alloc(mem::size_of::()) as *mut dirent; + let copy = unsafe { platform::alloc(mem::size_of::()) }.cast::(); if copy.is_null() { break; } - ptr::write(copy, (*entry).clone()); - if let Err(_) = vec.push(copy) { + unsafe { ptr::write(copy, (*entry).clone()) }; + if vec.push(copy).is_err() { break; } } - closedir(dir); + closedir(unsafe { Box::from_raw(dir) }); let len = vec.len(); - if let Err(_) = vec.shrink_to_fit() { + if vec.shrink_to_fit().is_err() { return -1; } - if platform::errno != 0 { + if platform::ERRNO.get() != 0 { for ptr in &mut vec { - platform::free(*ptr as *mut c_void); + unsafe { platform::free((*ptr).cast::()) }; } -1 } else { - *namelist = vec.leak(); + unsafe { + // Empty CVecs use a dangling pointer which cannot be freed, return null instead + if vec.is_empty() { + *namelist = ptr::null_mut(); + } else { + *namelist = vec.leak(); + } + } - platform::errno = old_errno; - stdlib::qsort( - *namelist as *mut c_void, - len as size_t, - mem::size_of::<*mut dirent>(), - mem::transmute(compare), - ); + platform::ERRNO.set(old_errno); + unsafe { + stdlib::qsort( + (*namelist).cast::(), + len as size_t, + mem::size_of::<*mut dirent>(), + #[allow(clippy::missing_transmute_annotations)] // too verbose + mem::transmute(compare), + ) + }; len as c_int } } + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn seekdir(dir: &mut DIR, off: c_long) { + let Ok(off) = off.try_into() else { + platform::ERRNO.set(EINVAL); + return; + }; + + dir.seek(off); +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn telldir(dir: &mut DIR) -> c_long { + dir.opaque_offset as c_long +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_posix_dent(_: posix_dent) {} diff --git a/src/header/dl-tls/mod.rs b/src/header/dl-tls/mod.rs index 83db3fa3f3..0196bf7bc8 100644 --- a/src/header/dl-tls/mod.rs +++ b/src/header/dl-tls/mod.rs @@ -1,44 +1,102 @@ //! dl-tls implementation for Redox -use crate::{ld_so::tcb::Tcb, platform::types::*}; +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] + +#[cfg(target_arch = "x86")] +use core::arch::global_asm; + +use alloc::alloc::alloc; +use core::{alloc::Layout, ptr}; + +use crate::{ld_so::tcb::Tcb, platform::types::c_void}; #[repr(C)] +#[allow(non_camel_case_types)] pub struct dl_tls_index { - pub ti_module: u64, - pub ti_offset: u64, + pub ti_module: usize, + pub ti_offset: usize, } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn __tls_get_addr(ti: *mut dl_tls_index) -> *mut c_void { - trace!( - "__tls_get_addr({:p}: {:#x}, {:#x})", + let tcb = unsafe { Tcb::current().unwrap() }; + let ti = unsafe { &*ti }; + let masters = tcb.masters().unwrap(); + + #[cfg(feature = "trace_tls")] + log::trace!( + "__tls_get_addr({:p}: {:#x}, {:#x}, masters_len={}, dtv_len={})", ti, - (*ti).ti_module, - (*ti).ti_offset + ti.ti_module, + ti.ti_offset, + masters.len(), + tcb.dtv_mut().len() ); - if let Some(tcb) = Tcb::current() { - if let Some(tls) = tcb.tls() { - if let Some(masters) = tcb.masters() { - if let Some(master) = masters.get((*ti).ti_module as usize) { - let addr = tls - .as_mut_ptr() - .add(master.offset + (*ti).ti_offset as usize); - trace!( - "__tls_get_addr({:p}: {:#x}, {:#x}) = {:p}", - ti, - (*ti).ti_module, - (*ti).ti_offset, - addr - ); - return addr as *mut c_void; - } - } - } + + if tcb.dtv_mut().len() < masters.len() { + // Reallocate DTV. + tcb.setup_dtv(masters.len()); } - panic!( - "__tls_get_addr({:p}: {:#x}, {:#x}) failed", - ti, - (*ti).ti_module, - (*ti).ti_offset - ); + + let dtv_index = ti.ti_module - 1; + + if tcb.dtv_mut()[dtv_index].is_null() { + // Allocate TLS for module. + let master = &masters[dtv_index]; + + let module_tls = unsafe { + // FIXME(andypython): master.align + let layout = Layout::from_size_align_unchecked(master.segment_size, 16); + let ptr = alloc(layout); + + ptr::copy_nonoverlapping(master.ptr, ptr, master.image_size); + ptr::write_bytes( + ptr.add(master.image_size), + 0, + master.segment_size - master.image_size, + ); + + ptr + }; + + // Set the DTV entry. + tcb.dtv_mut()[dtv_index] = module_tls; + } + + let mut ptr = tcb.dtv_mut()[dtv_index]; + + if ptr.is_null() { + panic!( + "__tls_get_addr({ti:p}: {:#x}, {:#x})", + ti.ti_module, ti.ti_offset + ); + } + + if cfg!(target_arch = "riscv64") { + ptr = unsafe { ptr.add(0x800 + ti.ti_offset) }; // dynamic offsets are 0x800-based on risc-v + } else { + ptr = unsafe { ptr.add(ti.ti_offset) }; + } + + ptr.cast::() } + +// x86 can define a version that passes a pointer to dl_tls_index in eax +#[cfg(target_arch = "x86")] +global_asm!( + " + .globl ___tls_get_addr + .type ___tls_get_addr, @function +___tls_get_addr: + push ebp + mov ebp, esp + push eax + call __tls_get_addr + add esp, 4 + pop ebp + ret + .size ___tls_get_addr, . - ___tls_get_addr +" +); diff --git a/src/header/dlfcn/cbindgen.toml b/src/header/dlfcn/cbindgen.toml index 3a324bd8cb..4ea36e2a96 100644 --- a/src/header/dlfcn/cbindgen.toml +++ b/src/header/dlfcn/cbindgen.toml @@ -1,3 +1,6 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/dlfcn.h.html +# +# There are no spec quotations relating to includes sys_includes = [] include_guard = "_RELIBC_DLFCN_H" language = "C" diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index 784a2b83c2..923c057169 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -1,161 +1,197 @@ -//! dlfcn implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html +//! `dlfcn.h` implementation. +//! +//! See . + +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] use core::{ ptr, str, sync::atomic::{AtomicUsize, Ordering}, }; -use crate::{c_str::CStr, ld_so::tcb::Tcb, platform::types::*}; +use crate::{ + c_str::CStr, + ld_so::{ + linker::{DlError, ObjectHandle, Resolve, ScopeKind}, + tcb::Tcb, + }, + platform::types::{c_char, c_int, c_void}, +}; -pub const RTLD_LAZY: c_int = 0x0001; -pub const RTLD_NOW: c_int = 0x0002; -pub const RTLD_GLOBAL: c_int = 0x0100; +pub const RTLD_LAZY: c_int = 1 << 0; +pub const RTLD_NOW: c_int = 1 << 1; +pub const RTLD_NOLOAD: c_int = 1 << 2; +pub const RTLD_GLOBAL: c_int = 1 << 8; pub const RTLD_LOCAL: c_int = 0x0000; -static ERROR_NOT_SUPPORTED: &'static CStr = c_str!("dlfcn not supported"); +#[allow(clippy::zero_ptr)] // related cbindgen issue: https://github.com/mozilla/cbindgen/issues/948 +pub const RTLD_DEFAULT: *mut c_void = 0 as *mut c_void; // XXX: cbindgen doesn't like ptr::null_mut() for publically exported constants + +static ERROR_NOT_SUPPORTED: &core::ffi::CStr = c"dlfcn not supported"; #[thread_local] static ERROR: AtomicUsize = AtomicUsize::new(0); +fn set_last_error(error: DlError) { + ERROR.store(error.repr().as_ptr() as usize, Ordering::SeqCst); +} + +/// See . #[repr(C)] -pub struct Dl_info { +#[allow(non_camel_case_types)] +pub struct Dl_info_t { dli_fname: *const c_char, dli_fbase: *mut c_void, dli_sname: *const c_char, dli_saddr: *mut c_void, } -#[no_mangle] -pub unsafe extern "C" fn dladdr(addr: *mut c_void, info: *mut Dl_info) -> c_int { +/// alias as per spec update: +pub type Dl_info = Dl_info_t; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dladdr(_addr: *const c_void, info: *mut Dl_info_t) -> c_int { //TODO - (*info).dli_fname = ptr::null(); - (*info).dli_fbase = ptr::null_mut(); - (*info).dli_sname = ptr::null(); - (*info).dli_saddr = ptr::null_mut(); + unsafe { + (*info).dli_fname = ptr::null(); + (*info).dli_fbase = ptr::null_mut(); + (*info).dli_sname = ptr::null(); + (*info).dli_saddr = ptr::null_mut(); + } 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn dlopen(cfilename: *const c_char, flags: c_int) -> *mut c_void { //TODO support all sort of flags + let resolve = if flags & RTLD_NOW == RTLD_NOW { + Resolve::Now + } else { + Resolve::Lazy + }; + + let scope = if flags & RTLD_GLOBAL == RTLD_GLOBAL { + ScopeKind::Global + } else { + ScopeKind::Local + }; + + let noload = flags & RTLD_NOLOAD == RTLD_NOLOAD; let filename = if cfilename.is_null() { None } else { - Some(str::from_utf8_unchecked( - CStr::from_ptr(cfilename).to_bytes(), - )) + unsafe { + Some(str::from_utf8_unchecked( + CStr::from_ptr(cfilename).to_bytes(), + )) + } }; - let tcb = match Tcb::current() { + let tcb = match unsafe { Tcb::current() } { Some(tcb) => tcb, None => { - eprintln!("dlopen: tcb not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); } }; + if tcb.linker_ptr.is_null() { - eprintln!("dlopen: linker not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); } - let mut linker = (&*tcb.linker_ptr).lock(); + + let mut linker = unsafe { (*tcb.linker_ptr).lock() }; let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - let id = match (cbs.load_library)(&mut linker, filename) { - Err(err) => { - eprintln!("dlopen: failed to load {:?}", filename); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); + match (cbs.load_library)(&mut linker, filename, resolve, scope, noload) { + Ok(handle) => handle.as_ptr().cast_mut(), + Err(error) => { + set_last_error(error); + ptr::null_mut() } - Ok(id) => id, - }; - - if let Some(fname) = filename { - if let Err(err) = (cbs.link)(&mut linker, None, None, Some(id)) { - (cbs.unload)(&mut linker, id); - eprintln!("dlopen: failed to link '{}': {}", fname, err); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); - }; - - if let Err(err) = (cbs.run_init)(&mut linker, Some(id)) { - (cbs.unload)(&mut linker, id); - eprintln!("dlopen: failed to link '{}': {}", fname, err); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); - }; } - id as *mut c_void } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void { + let handle = ObjectHandle::from_ptr(handle); + if symbol.is_null() { ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); } - let symbol_str = str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()); + let symbol_str = unsafe { str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes()) }; - let tcb = match Tcb::current() { + // FIXME(andypython): just call obj.scope.get_sym() directly or search the + // global scope. The rest is unnecessary as Linker::get_sym() does not + // depend on the Linker state. + let tcb = match unsafe { Tcb::current() } { Some(tcb) => tcb, None => { - eprintln!("dlsym: tcb not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); } }; if tcb.linker_ptr.is_null() { - eprintln!("dlsym: linker not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return ptr::null_mut(); } - let linker = (&*tcb.linker_ptr).lock(); + let linker = unsafe { (*tcb.linker_ptr).lock() }; let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - if let Some(global) = (cbs.get_sym)(&linker, symbol_str, Some(handle as usize)) { - global.as_ptr() - } else { - eprintln!("dlsym: symbol not found"); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - ptr::null_mut() + match (cbs.get_sym)(&linker, handle, symbol_str) { + Some(sym) => sym, + _ => { + ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); + ptr::null_mut() + } } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn dlclose(handle: *mut c_void) -> c_int { - let tcb = match Tcb::current() { + let tcb = match unsafe { Tcb::current() } { Some(tcb) => tcb, None => { - eprintln!("dlclose: tcb not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return -1; } }; if tcb.linker_ptr.is_null() { - eprintln!("dlclose: linker not found"); ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); return -1; }; - let mut linker = (&*tcb.linker_ptr).lock(); + + let Some(handle) = ObjectHandle::from_ptr(handle) else { + set_last_error(DlError::InvalidHandle); + return -1; + }; + + let mut linker = unsafe { (*tcb.linker_ptr).lock() }; let cbs_c = linker.cbs.clone(); let cbs = cbs_c.borrow(); - if let Err(err) = (cbs.run_fini)(&mut linker, Some(handle as usize)) { - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return -1; - }; - (cbs.unload)(&mut linker, handle as usize); + (cbs.unload)(&mut linker, handle); 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn dlerror() -> *mut c_char { ERROR.swap(0, Ordering::SeqCst) as *mut c_char } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_alias_dlinfo_for_dladdr(_: Dl_info) {} diff --git a/src/header/elf/cbindgen.toml b/src/header/elf/cbindgen.toml index 0d441b5243..cf53dfec97 100644 --- a/src/header/elf/cbindgen.toml +++ b/src/header/elf/cbindgen.toml @@ -1,4 +1,41 @@ -sys_includes = ["bits/elf.h"] +after_includes = """ +#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4) +#define ELF32_ST_TYPE(val) ((val) & 0xf) +#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) + +#define ELF64_ST_BIND(val) ELF32_ST_BIND (val) +#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val) +#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type)) + + +#define ELF32_ST_VISIBILITY(o) ((o) & 0x03) +#define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o) + +#define ELF32_R_SYM(val) ((val) >> 8) +#define ELF32_R_TYPE(val) ((val) & 0xff) +#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff)) + +#define ELF64_R_SYM(i) ((i) >> 32) +#define ELF64_R_TYPE(i) ((i) & 0xffffffff) +#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type)) + +#define DT_VALRNGHI 0x6ffffdff +#define DT_VALTAGIDX(tag) (DT_VALRNGHI - (tag)) + +#define DT_ADDRRNGHI 0x6ffffeff +#define DT_ADDRTAGIDX(tag) (DT_ADDRRNGHI - (tag)) + +#define DT_VERNEEDNUM 0x6fffffff +#define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) + +#define ELF32_M_SYM(info) ((info) >> 8) +#define ELF32_M_SIZE(info) ((unsigned char) (info)) +#define ELF32_M_INFO(sym, size) (((sym) << 8) + (unsigned char) (size)) + +#define ELF64_M_SYM(info) ELF32_M_SYM (info) +#define ELF64_M_SIZE(info) ELF32_M_SIZE (info) +#define ELF64_M_INFO(sym, size) ELF32_M_INFO (sym, size) +""" include_guard = "_ELF_H" language = "C" style = "Tag" diff --git a/src/header/elf/mod.rs b/src/header/elf/mod.rs index db7b6d41f8..cb471271f1 100644 --- a/src/header/elf/mod.rs +++ b/src/header/elf/mod.rs @@ -1,12 +1,16 @@ -use crate::platform::types::*; +//! `elf.h` implementation. +//! +//! Non-POSIX, see . + +use crate::platform::types::{c_char, c_uchar, int32_t, int64_t, uint16_t, uint32_t, uint64_t}; pub type Elf32_Half = uint16_t; pub type Elf64_Half = uint16_t; pub type Elf32_Word = uint32_t; pub type Elf32_Sword = int32_t; -pub type Elf64_Word = uint64_t; -pub type Elf64_Sword = int64_t; +pub type Elf64_Word = uint32_t; +pub type Elf64_Sword = int32_t; pub type Elf32_Xword = uint64_t; pub type Elf32_Sxword = int64_t; @@ -27,6 +31,7 @@ pub type Elf64_Versym = Elf64_Half; pub const EI_NIDENT: usize = 16; +/// See . #[repr(C)] pub struct Elf32_Ehdr { pub e_ident: [c_uchar; EI_NIDENT], @@ -45,6 +50,7 @@ pub struct Elf32_Ehdr { pub e_shstrndx: Elf32_Half, } +/// See . #[repr(C)] pub struct Elf64_Ehdr { pub e_ident: [c_uchar; EI_NIDENT], @@ -71,7 +77,7 @@ pub const EI_MAG2: usize = 2; pub const ELFMAG2: c_char = 'L' as c_char; pub const EI_MAG3: usize = 3; pub const ELFMAG3: c_char = 'F' as c_char; -pub const ELFMAG: &'static str = "\x7fELF"; +pub const ELFMAG: &str = "\x7fELF"; pub const SELFMAG: usize = 4; pub const EI_CLASS: usize = 4; pub const ELFCLASSNONE: usize = 0; @@ -196,6 +202,7 @@ pub const EV_NONE: usize = 0; pub const EV_CURRENT: usize = 1; pub const EV_NUM: usize = 2; +/// See . #[repr(C)] pub struct Elf32_Shdr { pub sh_name: Elf32_Word, @@ -210,6 +217,7 @@ pub struct Elf32_Shdr { pub sh_entsize: Elf32_Word, } +/// See . #[repr(C)] pub struct Elf64_Shdr { pub sh_name: Elf64_Word, @@ -289,6 +297,7 @@ pub const SHF_ORDERED: usize = 1 << 30; pub const SHF_EXCLUDE: usize = 1 << 31; pub const GRP_COMDAT: usize = 0x1; +/// See . #[repr(C)] pub struct Elf32_Sym { pub st_name: Elf32_Word, @@ -299,6 +308,7 @@ pub struct Elf32_Sym { pub st_shndx: Elf32_Section, } +/// See . #[repr(C)] pub struct Elf64_Sym { pub st_name: Elf64_Word, @@ -361,18 +371,21 @@ pub const STV_INTERNAL: usize = 1; pub const STV_HIDDEN: usize = 2; pub const STV_PROTECTED: usize = 3; +/// See . #[repr(C)] pub struct Elf32_Rel { pub r_offset: Elf32_Addr, pub r_info: Elf32_Word, } +/// See . #[repr(C)] pub struct Elf64_Rel { pub r_offset: Elf64_Addr, pub r_info: Elf64_Xword, } +/// See . #[repr(C)] pub struct Elf32_Rela { pub r_offset: Elf32_Addr, @@ -380,6 +393,7 @@ pub struct Elf32_Rela { pub r_addend: Elf32_Sword, } +/// See . #[repr(C)] pub struct Elf64_Rela { pub r_offset: Elf64_Addr, @@ -387,6 +401,7 @@ pub struct Elf64_Rela { pub r_addend: Elf64_Sxword, } +/// See . #[repr(C)] pub struct Elf32_Phdr { pub p_type: Elf32_Word, @@ -399,6 +414,7 @@ pub struct Elf32_Phdr { pub p_align: Elf32_Word, } +/// See . #[repr(C)] pub struct Elf64_Phdr { pub p_type: Elf64_Word, @@ -487,6 +503,7 @@ pub union Elf32_Dyn_Union { d_ptr: Elf32_Addr, } +/// See . #[repr(C)] pub struct Elf32_Dyn { pub d_tag: Elf32_Sword, @@ -499,6 +516,7 @@ pub union Elf64_Dyn_Union { d_ptr: Elf64_Addr, } +/// See . #[repr(C)] pub struct Elf64_Dyn { pub d_tag: Elf64_Sxword, @@ -740,6 +758,7 @@ pub const AT_L1D_CACHESHAPE: usize = 35; pub const AT_L2_CACHESHAPE: usize = 36; pub const AT_L3_CACHESHAPE: usize = 37; +/// See . #[repr(C)] pub struct Elf32_Nhdr { pub n_namesz: Elf32_Word, @@ -747,6 +766,7 @@ pub struct Elf32_Nhdr { pub n_type: Elf32_Word, } +/// See . #[repr(C)] pub struct Elf64_Nhdr { pub n_namesz: Elf64_Word, @@ -754,8 +774,8 @@ pub struct Elf64_Nhdr { pub n_type: Elf64_Word, } -pub const ELF_NOTE_SOLARIS: &'static str = "SUNW Solaris"; -pub const ELF_NOTE_GNU: &'static str = "GNU"; +pub const ELF_NOTE_SOLARIS: &str = "SUNW Solaris"; +pub const ELF_NOTE_GNU: &str = "GNU"; pub const ELF_NOTE_PAGESIZE_HINT: usize = 1; @@ -943,8 +963,8 @@ pub const R_X86_64_IRELATIVE: usize = 37; pub const R_X86_64_RELATIVE64: usize = 38; pub const R_X86_64_NUM: usize = 39; -#[no_mangle] -pub extern "C" fn stupid_cbindgen_needs_a_function_that_holds_all_elf_structs( +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_elf( a: Elf32_Ehdr, b: Elf64_Ehdr, c: Elf32_Shdr, diff --git a/src/header/endian/cbindgen.toml b/src/header/endian/cbindgen.toml new file mode 100644 index 0000000000..698eb0088c --- /dev/null +++ b/src/header/endian/cbindgen.toml @@ -0,0 +1,12 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/endian.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the uint16_t, uint32_t, and uint64_t types as described in ." +# - "Inclusion of the header may also make visible all symbols from ." +sys_includes = ["stdint.h"] +include_guard = "_RELIBC_ENDIAN_H" +trailer = "#include " +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true diff --git a/src/header/endian/mod.rs b/src/header/endian/mod.rs new file mode 100644 index 0000000000..8c78b3bdfa --- /dev/null +++ b/src/header/endian/mod.rs @@ -0,0 +1,77 @@ +//! `endian.h` implementation. +//! +//! See . + +use crate::platform::types::{uint16_t, uint32_t, uint64_t}; + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn be16toh(x: uint16_t) -> uint16_t { + uint16_t::from_be(x) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn be32toh(x: uint32_t) -> uint32_t { + uint32_t::from_be(x) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn be64toh(x: uint64_t) -> uint64_t { + uint64_t::from_be(x) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htobe16(x: uint16_t) -> uint16_t { + x.to_be() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htobe32(x: uint32_t) -> uint32_t { + x.to_be() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htobe64(x: uint64_t) -> uint64_t { + x.to_be() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htole16(x: uint16_t) -> uint16_t { + x.to_le() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htole32(x: uint32_t) -> uint32_t { + x.to_le() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn htole64(x: uint64_t) -> uint64_t { + x.to_le() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn le16toh(x: uint16_t) -> uint16_t { + uint16_t::from_le(x) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn le32toh(x: uint32_t) -> uint32_t { + uint32_t::from_le(x) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn le64toh(x: uint64_t) -> uint64_t { + uint64_t::from_le(x) +} diff --git a/src/header/err/cbindgen.toml b/src/header/err/cbindgen.toml new file mode 100644 index 0000000000..a65ef1a921 --- /dev/null +++ b/src/header/err/cbindgen.toml @@ -0,0 +1,5 @@ +sys_includes = ["stdarg.h", "stdio.h", "features.h"] +include_guard = "_RELIBC_ERR_H" +language = "C" +no_includes = false +cpp_compat = true diff --git a/src/header/err/mod.rs b/src/header/err/mod.rs new file mode 100644 index 0000000000..9cc963d4b3 --- /dev/null +++ b/src/header/err/mod.rs @@ -0,0 +1,204 @@ +//! `err.h` implementation. +//! +//! See +//! +//! `err.h` is a BSD extension to the C library which provides functions for printing formatted +//! errors. Errors are printed to [`stdio::stderr`] by default or to a file set by +//! [`err_set_file`]. This family of functions is non-portable, but it is also supported by `glibc` +//! and `musl`. +//! +//! The functions come in sets of three. Each of them print the program binary name (the last path +//! segment of `argv[0]`) and an optional user message along with these differences: +//! * No suffix: Prints an error message for ERRNO based on [`strerror`] +//! * `c` suffix: Prints an error message for an arbitrary error code +//! * `x` suffix: Does not print an error code +//! +//! For example, `err` does not have a suffix so it would print the program name, the user message, +//! and an error string for ERRNO. `errc` would operate in the same way except the functions takes +//! an error code for which to print an error string. + +// Allow is intentional. Almost every line of the simple functions below are unsafe. +// unsafe_op_in_unsafe_fn only adds visual noise or a needless indentation here. +#![allow(unsafe_op_in_unsafe_fn)] + +use core::{ + ffi::{VaList as va_list, c_char, c_int}, + ptr, +}; + +use crate::{ + header::{ + stdio::{self, FILE, fprintf, fputc, fputs, vfprintf}, + stdlib::exit, + string::strerror, + }, + platform::{self, ERRNO}, +}; + +// Optional callback from user invoked on exit. +type ExitCallback = Option; +static mut on_exit: ExitCallback = None; + +// Messages from this module are written to this sink. +static mut error_sink: *mut FILE = ptr::null_mut(); + +/// Set global [`FILE`] sink to write errors and warnings. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn err_set_file(fp: *mut FILE) { + if fp.is_null() { + error_sink = stdio::stderr; + } else { + error_sink = fp; + } +} + +/// Set or remove a callback to invoke before exiting on error. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn err_set_exit(ef: ExitCallback) { + on_exit = ef; +} + +/// Print a user message then an error message for [`ERRNO`] followed by exiting with `eval`. +/// +/// The message format is `progname: fmt: strerror(ERRNO)` +/// +/// # Return +/// Does not return. Exits with `eval` as an error code. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn err(eval: c_int, fmt: *const c_char, va_list: ...) -> ! { + let code = Some(ERRNO.get()); + err_exit(eval, code, fmt, va_list) +} + +/// Print a user message then an error message for `code` before exiting with `eval` as a return. +/// +/// The message format is `progname: fmt: strerror(code)` +/// +/// # Return +/// Exits with `eval` as an error code. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn errc(eval: c_int, code: c_int, fmt: *const c_char, va_list: ...) -> ! { + err_exit(eval, Some(code), fmt, va_list) +} + +/// Print a user message then exits with `eval` as a return. +/// +/// The message format is `progname: fmt` +/// +/// # Return +/// Exits with `eval` as an error code. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn errx(eval: c_int, fmt: *const c_char, va_list: ...) -> ! { + err_exit(eval, None, fmt, va_list) +} + +/// Print a user message and then an error message for [`ERRNO`]. +/// +/// The message format is `progname: fmt: strerror(ERRNO)` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn warn(fmt: *const c_char, va_list: ...) { + let code = Some(ERRNO.get()); + display_message(code, fmt, va_list); +} + +/// Print a user message then an error message for `code`. +/// +/// The message format is `progname: fmt: strerror(code)` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn warnc(code: c_int, fmt: *const c_char, va_list: ...) { + display_message(Some(code), fmt, va_list); +} + +/// Print a user message as a warning. +/// +/// The message format is `progname: fmt` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn warnx(fmt: *const c_char, va_list: ...) { + display_message(None, fmt, va_list); +} + +/// See [`err`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn verr(eval: c_int, fmt: *const c_char, args: va_list) -> ! { + let code = Some(ERRNO.get()); + err_exit(eval, code, fmt, args); +} + +/// See [`errc`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn verrc(eval: c_int, code: c_int, fmt: *const c_char, args: va_list) -> ! { + err_exit(eval, Some(code), fmt, args) +} + +/// See [`errx`]; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn verrx(eval: c_int, fmt: *const c_char, args: va_list) -> ! { + err_exit(eval, None, fmt, args) +} + +/// See [`warn`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vwarn(fmt: *const c_char, args: va_list) { + let code = Some(ERRNO.get()); + display_message(code, fmt, args); +} + +/// See [`warnc`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vwarnc(code: c_int, fmt: *const c_char, args: va_list) { + display_message(Some(code), fmt, args); +} + +/// See [`warnx`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vwarnx(fmt: *const c_char, args: va_list) { + display_message(None, fmt, args); +} + +// Write error messages for err and warn to the currently set sink. +unsafe fn display_message(code: Option, fmt: *const c_char, args: va_list) { + // SAFETY: + // * error_sink is only null once on start but otherwise always stderr or a user set file + // * User is trusted to pass in a valid file pointer if err_set_file is used + if error_sink.is_null() { + error_sink = stdio::stderr; + } + let sink = error_sink; + + // "progname:" is always printed + // SAFETY: + // * program_invocation_short_name is never null as it is set on start + // * program_invocation_short_name is not globally mutable so the user can't mangle it + fprintf( + sink, + c"%s".as_ptr(), + platform::program_invocation_short_name, + ); + + // Print user message if any + if !fmt.is_null() { + fputs(c": ".as_ptr(), sink); + vfprintf(sink, fmt, args); + } + + // Print error message for non-x functions + if let Some(code) = code { + let message = strerror(code); + fprintf(sink, c": %s".as_ptr(), message); + } + + // Always write new line + fputc(b'\n'.into(), sink); +} + +// Write an error message as per err and then exit. +unsafe fn err_exit(eval: c_int, code: Option, fmt: *const c_char, args: va_list) -> ! { + display_message(code, fmt, args); + + if let Some(callback) = on_exit { + // errx will hit the unwrap. + callback(code.unwrap_or_else(|| ERRNO.get())); + } + + exit(eval); +} diff --git a/src/header/errno/cbindgen.toml b/src/header/errno/cbindgen.toml index 7de26bfdf8..93f418381d 100644 --- a/src/header/errno/cbindgen.toml +++ b/src/header/errno/cbindgen.toml @@ -1,4 +1,12 @@ -sys_includes = ["bits/errno.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/errno.h.html +# +# There are no spec quotations relating to includes +sys_includes = [] +after_includes = """ +#define errno (*__errno_location()) +#define program_invocation_name (*__program_invocation_name()) +#define program_invocation_short_name (*__program_invocation_short_name()) +""" include_guard = "_RELIBC_ERRNO_H" language = "C" style = "Tag" diff --git a/src/header/errno/mod.rs b/src/header/errno/mod.rs index 7a51f0b202..aceb031138 100644 --- a/src/header/errno/mod.rs +++ b/src/header/errno/mod.rs @@ -1,38 +1,47 @@ -//! errno implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/errno.h.html +//! `errno.h` implementation. +//! +//! See . -use crate::platform::{self, types::*}; +use crate::platform::{ + self, + types::{c_char, c_int}, +}; //TODO: Consider removing, provided for compatibility with newlib -#[no_mangle] -pub unsafe extern "C" fn __errno() -> *mut c_int { +#[unsafe(no_mangle)] +pub extern "C" fn __errno() -> *mut c_int { __errno_location() } -#[no_mangle] -pub unsafe extern "C" fn __errno_location() -> *mut c_int { - &mut platform::errno +/// Provide a pointer to relibc's internal `errno` variable. +/// +/// This is the basis of the C-facing macro definition of `errno`, and should +/// not be used directly. +#[unsafe(no_mangle)] +pub extern "C" fn __errno_location() -> *mut c_int { + platform::ERRNO.as_ptr() } -#[no_mangle] +/// Get the name used to invoke the program, similar to `argv[0]`. +/// +/// This provides the basis for the C-facing macro definition of +/// `program_invocation_name`, and should not be used directly. +/// +/// The `program_invocation_name` variable is a GNU extension. +#[unsafe(no_mangle)] pub unsafe extern "C" fn __program_invocation_name() -> *mut *mut c_char { - &mut platform::inner_argv[0] + &raw mut platform::program_invocation_name } -#[no_mangle] +/// Get the directory-less name used to invoke the program. +/// +/// This provides the basis for the C-facing macro definition of +/// `program_invocation_short_name`, and should not be used directly. +/// +/// The `program_invocation_short_name` variable is a GNU extension. +#[unsafe(no_mangle)] pub unsafe extern "C" fn __program_invocation_short_name() -> *mut *mut c_char { - let mut ptr = platform::inner_argv[0]; - let mut slash_ptr = ptr; - loop { - let b = *ptr as u8; - if b == 0 { - return &mut slash_ptr; - } else { - ptr = ptr.add(1); - if b == b'/' { - slash_ptr = ptr; - } - } - } + &raw mut platform::program_invocation_short_name } pub const EPERM: c_int = 1; /* Operation not permitted */ @@ -130,6 +139,7 @@ pub const ENOPROTOOPT: c_int = 92; /* Protocol not available */ pub const EPROTONOSUPPORT: c_int = 93; /* Protocol not supported */ pub const ESOCKTNOSUPPORT: c_int = 94; /* Socket type not supported */ pub const EOPNOTSUPP: c_int = 95; /* Operation not supported on transport endpoint */ +pub const ENOTSUP: c_int = EOPNOTSUPP; /* Not supported */ pub const EPFNOSUPPORT: c_int = 96; /* Protocol family not supported */ pub const EAFNOSUPPORT: c_int = 97; /* Address family not supported by protocol */ pub const EADDRINUSE: c_int = 98; /* Address already in use */ @@ -167,7 +177,8 @@ pub const EKEYREJECTED: c_int = 129; /* Key was rejected by service */ pub const EOWNERDEAD: c_int = 130; /* Owner died */ pub const ENOTRECOVERABLE: c_int = 131; /* State not recoverable */ -pub static STR_ERROR: [&'static str; 132] = [ +/// String representations for the respective `errno` values. +pub(crate) const STR_ERROR: [&str; 132] = [ "Success", "Operation not permitted", "No such file or directory", @@ -301,3 +312,33 @@ pub static STR_ERROR: [&'static str; 132] = [ "Owner died", "State not recoverable", ]; + +/// Longest error message length +pub(crate) const STRERROR_MAX: usize = { + // Number of digits of the max value of this platform's c_int + let digits = { + let mut len = 0; + let mut i = c_int::MAX; + + while i > 0 { + i /= 10; + len += 1; + } + + len + }; + + let mut longest: usize = "Unknown error: ".len() + digits + 1; + + let mut i = 0; + while i < STR_ERROR.len() { + let len = STR_ERROR[i].len() + 1; + if len > longest { + longest = len; + } + + i += 1; + } + + longest +}; diff --git a/src/header/fcntl/cbindgen.toml b/src/header/fcntl/cbindgen.toml index 40377c713a..1e54313f42 100644 --- a/src/header/fcntl/cbindgen.toml +++ b/src/header/fcntl/cbindgen.toml @@ -1,14 +1,9 @@ -sys_includes = ["stdarg.h", "sys/types.h"] +sys_includes = ["stdarg.h", "sys/stat.h", "unistd.h"] include_guard = "_RELIBC_FCNTL_H" -trailer = "#include " language = "C" style = "Tag" no_includes = true cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true diff --git a/src/header/fcntl/linux.rs b/src/header/fcntl/linux.rs index 0779a7ed50..6be617fd26 100644 --- a/src/header/fcntl/linux.rs +++ b/src/header/fcntl/linux.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const O_RDONLY: c_int = 0x0000; pub const O_WRONLY: c_int = 0x0001; @@ -6,6 +6,7 @@ pub const O_RDWR: c_int = 0x0002; pub const O_ACCMODE: c_int = 0x0003; pub const O_CREAT: c_int = 0x0040; pub const O_EXCL: c_int = 0x0080; +pub const O_NOCTTY: c_int = 0x0100; pub const O_TRUNC: c_int = 0x0200; pub const O_APPEND: c_int = 0x0400; pub const O_NONBLOCK: c_int = 0x0800; @@ -15,3 +16,16 @@ pub const O_CLOEXEC: c_int = 0x8_0000; pub const O_PATH: c_int = 0x20_0000; pub const FD_CLOEXEC: c_int = 0x8_0000; + +// Defined for compatibility +pub const O_NDELAY: c_int = O_NONBLOCK; + +// Flags for capability based "at" functions +pub const AT_FDCWD: c_int = -100; +pub const AT_SYMLINK_NOFOLLOW: c_int = 0x100; +// AT_EACCESS only used for faccessat +pub const AT_EACCESS: c_int = 0x200; +// AT_REMOVEDIR only used for unlinkat +pub const AT_REMOVEDIR: c_int = 0x200; +pub const AT_SYMLINK_FOLLOW: c_int = 0x400; +pub const AT_EMPTY_PATH: c_int = 0x1000; diff --git a/src/header/fcntl/mod.rs b/src/header/fcntl/mod.rs index 1e9975c687..4baf961139 100644 --- a/src/header/fcntl/mod.rs +++ b/src/header/fcntl/mod.rs @@ -1,12 +1,23 @@ -//! fcntl implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/fcntl.h.html +//! `fcntl.h` implementation. +//! +//! See . + +use core::num::NonZeroU64; use crate::{ c_str::CStr, - platform::{types::*, Pal, Sys}, + error::ResultExt, + header::unistd::close, + platform::{ + Pal, Sys, + types::{c_char, c_int, c_short, c_ulonglong, mode_t, off_t, pid_t}, + }, }; pub use self::sys::*; +use super::errno::EINVAL; + #[cfg(target_os = "linux")] #[path = "linux.rs"] pub mod sys; @@ -23,16 +34,29 @@ pub const F_SETFL: c_int = 4; pub const F_GETLK: c_int = 5; pub const F_SETLK: c_int = 6; pub const F_SETLKW: c_int = 7; +pub const F_OFD_GETLK: c_int = 36; +pub const F_OFD_SETLK: c_int = 37; +pub const F_OFD_SETLKW: c_int = 38; +pub const F_DUPFD_CLOEXEC: c_int = 1030; pub const F_RDLCK: c_int = 0; pub const F_WRLCK: c_int = 1; pub const F_UNLCK: c_int = 2; -#[no_mangle] +pub const F_ULOCK: c_int = 0; +pub const F_LOCK: c_int = 1; +pub const F_TLOCK: c_int = 2; +pub const F_TEST: c_int = 3; + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn creat(path: *const c_char, mode: mode_t) -> c_int { - sys_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode) + unsafe { open(path, O_WRONLY | O_CREAT | O_TRUNC, mode) } } + +/// See . #[repr(C)] +#[derive(Clone, Copy, Default)] pub struct flock { pub l_type: c_short, pub l_whence: c_short, @@ -40,16 +64,79 @@ pub struct flock { pub l_len: off_t, pub l_pid: pid_t, } -#[no_mangle] -pub extern "C" fn sys_fcntl(fildes: c_int, cmd: c_int, arg: c_int) -> c_int { - Sys::fcntl(fildes, cmd, arg) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fcntl(fildes: c_int, cmd: c_int, mut __valist: ...) -> c_int { + // c_ulonglong + let arg = match cmd { + F_DUPFD | F_SETFD | F_SETFL | F_GETLK | F_SETLK | F_SETLKW | F_OFD_GETLK | F_OFD_SETLK + | F_OFD_SETLKW | F_DUPFD_CLOEXEC => unsafe { __valist.next_arg::() }, + _ => 0, + }; + + if cmd == F_DUPFD_CLOEXEC { + let new_fd = Sys::fcntl(fildes, F_DUPFD_CLOEXEC, arg).or_minus_one_errno(); + if new_fd >= 0 { + return new_fd; + } + + let new_fd = Sys::fcntl(fildes, F_DUPFD, arg).or_minus_one_errno(); + if new_fd < 0 { + return -1; + } + if Sys::fcntl(new_fd, F_SETFD, FD_CLOEXEC as c_ulonglong).or_minus_one_errno() < 0 { + let _ = close(new_fd); + return -1; + } + return new_fd; + } + + Sys::fcntl(fildes, cmd, arg).or_minus_one_errno() } -#[no_mangle] -pub unsafe extern "C" fn sys_open(path: *const c_char, oflag: c_int, mode: mode_t) -> c_int { - let path = CStr::from_ptr(path); - Sys::open(path, oflag, mode) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open(path: *const c_char, oflag: c_int, mut __valist: ...) -> c_int { + unsafe { openat(AT_FDCWD, path, oflag, __valist) } } -#[no_mangle] -pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_fcntl(a: flock) {} +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat( + fd: c_int, + path: *const c_char, + oflag: c_int, + mut __valist: ... +) -> c_int { + let mode = if oflag & O_CREAT == O_CREAT + /* || oflag & O_TMPFILE == O_TMPFILE */ + { + unsafe { __valist.next_arg::() } + } else { + 0 + }; + + let path = unsafe { CStr::from_ptr(path) }; + Sys::openat(fd, path, oflag, mode).or_minus_one_errno() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_fcntl(_: flock) {} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_fallocate(fd: c_int, offset: off_t, length: off_t) -> c_int { + // Length can't be zero and offset must be positive. + let Ok(offset) = offset.try_into() else { + return EINVAL; + }; + let Some(length) = length.try_into().ok().and_then(NonZeroU64::new) else { + return EINVAL; + }; + + // posix_fallocate does not set errno but instead returns it. + Sys::posix_fallocate(fd, offset, length) + .err() + .map(|e| e.0) + .unwrap_or_default() +} diff --git a/src/header/fcntl/redox.rs b/src/header/fcntl/redox.rs index fc72796d01..0d54c33883 100644 --- a/src/header/fcntl/redox.rs +++ b/src/header/fcntl/redox.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const O_RDONLY: c_int = 0x0001_0000; pub const O_WRONLY: c_int = 0x0002_0000; @@ -10,6 +10,7 @@ pub const O_SHLOCK: c_int = 0x0010_0000; pub const O_EXLOCK: c_int = 0x0020_0000; pub const O_ASYNC: c_int = 0x0040_0000; pub const O_FSYNC: c_int = 0x0080_0000; +pub const O_SYNC: c_int = O_FSYNC; pub const O_CLOEXEC: c_int = 0x0100_0000; pub const O_CREAT: c_int = 0x0200_0000; pub const O_TRUNC: c_int = 0x0400_0000; @@ -21,3 +22,19 @@ pub const O_SYMLINK: c_int = 0x4000_0000; pub const O_NOFOLLOW: c_int = -0x8000_0000; pub const FD_CLOEXEC: c_int = 0x0100_0000; + +pub const O_NOCTTY: c_int = 0x00000200; + +// Defined for compatibility +pub const O_NDELAY: c_int = O_NONBLOCK; + +// Flags for capability based "at" functions +pub const AT_FDCWD: c_int = -100; +pub const AT_SYMLINK_NOFOLLOW: c_int = 0x200; +pub const AT_REMOVEDIR: c_int = 0x200; +// Used by linkat() +pub const AT_SYMLINK_FOLLOW: c_int = 0x2000; +// nonstandard extension, but likely to be in a future standard +pub const AT_EMPTY_PATH: c_int = 0x4000; +// only used for faccessat() +pub const AT_EACCESS: c_int = 0x400; diff --git a/src/header/float/cbindgen.toml b/src/header/float/cbindgen.toml index 4ca9cc2ea7..13118e74d7 100644 --- a/src/header/float/cbindgen.toml +++ b/src/header/float/cbindgen.toml @@ -1,5 +1,64 @@ -sys_includes = ["sys/types.h", "bits/float.h"] +sys_includes = ["sys/types.h"] include_guard = "_RELIBC_FLOAT_H" +after_includes = """ +#ifndef _RELIBC_BITS_FLOAT_H +#define _RELIBC_BITS_FLOAT_H + +#define FLT_ROUNDS (flt_rounds()) + +// Shamelessly copy pasted from musl: + +#define FLT_TRUE_MIN 1.40129846432481707092e-45F +#define FLT_MIN 1.17549435082228750797e-38F +#define FLT_MAX 3.40282346638528859812e+38F +#define FLT_EPSILON 1.1920928955078125e-07F + +#define FLT_MANT_DIG 24 +#define FLT_MIN_EXP (-125) +#define FLT_MAX_EXP 128 +#define FLT_HAS_SUBNORM 1 + +#define FLT_DIG 6 +#define FLT_DECIMAL_DIG 9 +#define FLT_MIN_10_EXP (-37) +#define FLT_MAX_10_EXP 38 + +#define DBL_TRUE_MIN 4.94065645841246544177e-324 +#define DBL_MIN 2.22507385850720138309e-308 +#define DBL_MAX 1.79769313486231570815e+308 +#define DBL_EPSILON 2.22044604925031308085e-16 + +#define DBL_MANT_DIG 53 +#define DBL_MIN_EXP (-1021) +#define DBL_MAX_EXP 1024 +#define DBL_HAS_SUBNORM 1 + +#define DBL_DIG 15 +#define DBL_DECIMAL_DIG 17 +#define DBL_MIN_10_EXP (-307) +#define DBL_MAX_10_EXP 308 + +#define LDBL_HAS_SUBNORM 1 +#define LDBL_DECIMAL_DIG DECIMAL_DIG + +#define FLT_EVAL_METHOD 0 +#define DECIMAL_DIG 21 + +// TODO: Support more architectures than x86_64 here: +#define LDBL_TRUE_MIN 3.6451995318824746025e-4951L +#define LDBL_MIN 3.3621031431120935063e-4932L +#define LDBL_MAX 1.1897314953572317650e+4932L +#define LDBL_EPSILON 1.0842021724855044340e-19L + +#define LDBL_MANT_DIG 64 +#define LDBL_MIN_EXP (-16381) +#define LDBL_MAX_EXP 16384 +#define LDBL_DIG 18 +#define LDBL_MIN_10_EXP (-4931) +#define LDBL_MAX_10_EXP 4932 + +#endif // _RELIBC_BITS_FLOAT_H +""" language = "C" style = "Tag" no_includes = true diff --git a/src/header/float/mod.rs b/src/header/float/mod.rs index 52f8ff0a9c..e806f0ce91 100644 --- a/src/header/float/mod.rs +++ b/src/header/float/mod.rs @@ -1,16 +1,19 @@ -//! float.h implementation for Redox, following -//! http://pubs.opengroup.org/onlinepubs/7908799/xsh/float.h.html +//! `float.h` implementation. +//! +//! See . use crate::{ - header::_fenv::{fegetround, FE_TONEAREST}, - platform::types::*, + header::_fenv::{FE_TONEAREST, fegetround}, + platform::types::c_int, }; +/// See . pub const FLT_RADIX: c_int = 2; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn flt_rounds() -> c_int { - match fegetround() { + match unsafe { fegetround() } { FE_TONEAREST => 1, _ => -1, } diff --git a/src/header/fnmatch/cbindgen.toml b/src/header/fnmatch/cbindgen.toml index c5cb5babab..f28cb9123e 100644 --- a/src/header/fnmatch/cbindgen.toml +++ b/src/header/fnmatch/cbindgen.toml @@ -1,3 +1,6 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fnmatch.h.html +# +# There are no spec quotations relating to includes include_guard = "_RELIBC_FNMATCH_H" language = "C" style = "Tag" diff --git a/src/header/fnmatch/mod.rs b/src/header/fnmatch/mod.rs index 92f1959c1c..8b09bc0759 100644 --- a/src/header/fnmatch/mod.rs +++ b/src/header/fnmatch/mod.rs @@ -1,12 +1,15 @@ -//! fnmatch implementation +//! `fnmatch.h` implementation. +//! +//! See . use alloc::{borrow::Cow, vec::Vec}; use core::slice; -use crate::platform::types::*; +use crate::platform::types::{c_char, c_int}; use posix_regex::{ - compile::{Collation, Range, Token}, PosixRegex, + compile::{Collation, Range, Token}, + tree::{Tree, TreeBuilder}, }; const ONCE: Range = Range(1, Some(1)); @@ -17,9 +20,10 @@ pub const FNM_NOESCAPE: c_int = 1; pub const FNM_PATHNAME: c_int = 2; pub const FNM_PERIOD: c_int = 4; pub const FNM_CASEFOLD: c_int = 8; +pub const FNM_IGNORECASE: c_int = FNM_CASEFOLD; // TODO: FNM_EXTMATCH -unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Vec<(Token, Range)> { +unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Tree { fn any(leading: bool, flags: c_int) -> Token { let mut list = Vec::new(); if flags & FNM_PATHNAME == FNM_PATHNAME { @@ -38,24 +42,34 @@ unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Vec<(Token, Range)> c == b'/' && flags & FNM_PATHNAME == FNM_PATHNAME } - let mut tokens = Vec::new(); let mut leading = true; + let mut need_collapsing = false; - while *pattern != 0 { + let mut builder = TreeBuilder::default(); + builder.start_internal(Token::Root, Range(1, Some(1))); + builder.start_internal(Token::Alternative, Range(1, Some(1))); + + while unsafe { *pattern != 0 } { let was_leading = leading; leading = false; - let c = *pattern; - pattern = pattern.offset(1); + let c = unsafe { *pattern }; + pattern = unsafe { pattern.offset(1) }; - tokens.push(match c { + match (c == b'*', need_collapsing) { + (true, true) => continue, + (true, false) => need_collapsing = true, + (false, _) => need_collapsing = false, + } + + let (token, range) = match c { b'\\' if flags & FNM_NOESCAPE == FNM_NOESCAPE => { - let c = *pattern; + let c = unsafe { *pattern }; if c == 0 { // Trailing backslash. Maybe error here? break; } - pattern = pattern.offset(1); + pattern = unsafe { pattern.offset(1) }; leading = is_leading(flags, c); (Token::Char(c), ONCE) } @@ -63,24 +77,24 @@ unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Vec<(Token, Range)> b'*' => (any(was_leading, flags), Range(0, None)), b'[' => { let mut list: Vec = Vec::new(); - let invert = if *pattern == b'!' { - pattern = pattern.offset(1); + let invert = if unsafe { *pattern == b'!' } { + pattern = unsafe { pattern.offset(1) }; true } else { false }; loop { - let mut c = *pattern; + let mut c = unsafe { *pattern }; if c == 0 { break; } - pattern = pattern.offset(1); + pattern = unsafe { pattern.offset(1) }; match c { b']' => break, b'\\' => { - c = *pattern; - pattern = pattern.offset(1); + c = unsafe { *pattern }; + pattern = unsafe { pattern.offset(1) }; if c == 0 { // Trailing backslash. Maybe error? break; @@ -88,9 +102,9 @@ unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Vec<(Token, Range)> } _ => (), } - if *pattern == b'-' && *pattern.offset(1) != 0 { - let end = *pattern.offset(1); - pattern = pattern.offset(2); + if unsafe { *pattern == b'-' && *pattern.offset(1) != 0 } { + let end = unsafe { *pattern.offset(1) }; + pattern = unsafe { pattern.offset(2) }; for c in c..=end { if can_push(was_leading, flags, c) { list.push(Collation::Char(c)); @@ -108,12 +122,17 @@ unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Vec<(Token, Range)> leading = is_leading(flags, c); (Token::Char(c), ONCE) } - }) + }; + builder.leaf(token, range); } - tokens + builder.leaf(Token::End, ONCE); + builder.finish_internal(); + builder.finish_internal(); + builder.finish() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] #[linkage = "weak"] // often redefined in GNU programs pub unsafe extern "C" fn fnmatch( pattern: *const c_char, @@ -121,15 +140,14 @@ pub unsafe extern "C" fn fnmatch( flags: c_int, ) -> c_int { let mut len = 0; - while *input.offset(len) != 0 { + while unsafe { *input.offset(len) != 0 } { len += 1; } - let input = slice::from_raw_parts(input as *const u8, len as usize); + let input = unsafe { slice::from_raw_parts(input.cast::(), len as usize) }; - let mut tokens = tokenize(pattern as *const u8, flags); - tokens.push((Token::End, ONCE)); + let tokens = unsafe { tokenize(pattern.cast::(), flags) }; - if PosixRegex::new(Cow::Owned(vec![tokens])) + if PosixRegex::new(Cow::Owned(tokens)) .case_insensitive(flags & FNM_CASEFOLD == FNM_CASEFOLD) .matches_exact(input) .is_some() diff --git a/src/header/getopt/mod.rs b/src/header/getopt/mod.rs index d793ab8613..1cc310a677 100644 --- a/src/header/getopt/mod.rs +++ b/src/header/getopt/mod.rs @@ -1,20 +1,27 @@ -//! getopt implementation for relibc +//! `getopt.h` implementation. +//! +//! Non-POSIX, see . use crate::{ header::{ stdio, string, unistd::{optarg, opterr, optind, optopt}, }, - platform::types::*, + platform::types::{c_char, c_int, size_t}, }; use core::ptr; +/// cbindgen:ignore static mut CURRENT_OPT: *mut c_char = ptr::null_mut(); +/// Non-POSIX, see . pub const no_argument: c_int = 0; +/// Non-POSIX, see . pub const required_argument: c_int = 1; +/// Non-POSIX, see . pub const optional_argument: c_int = 2; +/// Non-POSIX, see . #[repr(C)] pub struct option { name: *const c_char, @@ -23,8 +30,10 @@ pub struct option { val: c_int, } -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs +/// Non-POSIX, see . +/// +/// Functions the same as `getopt` but also accepts long options. +#[unsafe(no_mangle)] pub unsafe extern "C" fn getopt_long( argc: c_int, argv: *const *mut c_char, @@ -33,94 +42,109 @@ pub unsafe extern "C" fn getopt_long( longindex: *mut c_int, ) -> c_int { // if optarg is not set, we still don't want the previous value leaking - optarg = ptr::null_mut(); - - // handle reinitialization request - if optind == 0 { - optind = 1; - CURRENT_OPT = ptr::null_mut(); + unsafe { + optarg = ptr::null_mut(); } - if CURRENT_OPT.is_null() || *CURRENT_OPT == 0 { - if optind >= argc { + // handle reinitialization request + unsafe { + if optind == 0 { + optind = 1; + CURRENT_OPT = ptr::null_mut(); + } + } + + if unsafe { CURRENT_OPT.is_null() || *CURRENT_OPT == 0 } { + if unsafe { optind >= argc } { -1 } else { - let current_arg = *argv.offset(optind as isize); - if current_arg.is_null() - || *current_arg != b'-' as c_char - || *current_arg.offset(1) == 0 - { + let current_arg = unsafe { *argv.offset(optind as isize) }; + if unsafe { + current_arg.is_null() + || *current_arg != b'-' as c_char + || *current_arg.offset(1) == 0 + } { -1 - } else if string::strcmp(current_arg, c_str!("--").as_ptr()) == 0 { - optind += 1; + } else if unsafe { string::strcmp(current_arg, c"--".as_ptr()) == 0 } { + unsafe { + optind += 1; + } -1 } else { // remove the '-' - let current_arg = current_arg.offset(1); + let current_arg = unsafe { current_arg.offset(1) }; - if *current_arg == b'-' as c_char && !longopts.is_null() { - let current_arg = current_arg.offset(1); + if unsafe { *current_arg == b'-' as c_char } && !longopts.is_null() { + let current_arg = unsafe { current_arg.offset(1) }; // is a long option for i in 0.. { - let opt = &*longopts.offset(i); + let opt = unsafe { &*longopts.offset(i) }; if opt.name.is_null() { break; } let mut end = 0; while { - let c = *current_arg.offset(end); + let c = unsafe { *current_arg.offset(end) }; c != 0 && c != b'=' as c_char } { end += 1; } - if string::strncmp(current_arg, opt.name, end as size_t) == 0 { - optind += 1; - *longindex = i as c_int; + if unsafe { string::strncmp(current_arg, opt.name, end as size_t) == 0 } { + unsafe { + optind += 1; + if !longindex.is_null() { + *longindex = i as c_int; + } + } if opt.has_arg == optional_argument { - if *current_arg.offset(end) == b'=' as c_char { - optarg = current_arg.offset(end + 1); + unsafe { + if *current_arg.offset(end) == b'=' as c_char { + optarg = current_arg.offset(end + 1); + } } } else if opt.has_arg == required_argument { - if *current_arg.offset(end) == b'=' as c_char { - optarg = current_arg.offset(end + 1); - } else if optind < argc { - optarg = *argv.offset(optind as isize); - optind += 1; - } else if *optstring == b':' as c_char { - return b':' as c_int; - } else { - stdio::fputs(*argv as _, &mut *stdio::stderr); - stdio::fputs( - ": option '--\0".as_ptr() as _, - &mut *stdio::stderr, - ); - stdio::fputs(current_arg, &mut *stdio::stderr); - stdio::fputs( - "' requires an argument\n\0".as_ptr() as _, - &mut *stdio::stderr, - ); - return b'?' as c_int; + unsafe { + if *current_arg.offset(end) == b'=' as c_char { + optarg = current_arg.offset(end + 1); + } else if optind < argc { + optarg = *argv.offset(optind as isize); + optind += 1; + } else if *optstring == b':' as c_char { + return c_int::from(b':'); + } else { + stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr); + stdio::fputs( + c": option '--".as_ptr().cast(), + &raw mut *stdio::stderr, + ); + stdio::fputs(current_arg, &raw mut *stdio::stderr); + stdio::fputs( + c"' requires an argument\n".as_ptr().cast(), + &raw mut *stdio::stderr, + ); + return c_int::from(b'?'); + } } } if opt.flag.is_null() { return opt.val; } else { - *opt.flag = opt.val; + unsafe { *opt.flag = opt.val }; return 0; } } } } - parse_arg(argc, argv, current_arg, optstring) + unsafe { parse_arg(argc, argv, current_arg, optstring) } } } } else { - parse_arg(argc, argv, CURRENT_OPT, optstring) + unsafe { parse_arg(argc, argv, CURRENT_OPT, optstring) } } } @@ -130,35 +154,35 @@ unsafe fn parse_arg( current_arg: *mut c_char, optstring: *const c_char, ) -> c_int { - let update_current_opt = || { + let update_current_opt = || unsafe { CURRENT_OPT = current_arg.offset(1); if *CURRENT_OPT == 0 { optind += 1; } }; - let print_error = |desc: &[u8]| { + let print_error = |desc: &[u8]| unsafe { // NOTE: we don't use fprintf to get around the usage of va_list - stdio::fputs(*argv as _, &mut *stdio::stderr); - stdio::fputs(desc.as_ptr() as _, &mut *stdio::stderr); - stdio::fputc(*current_arg as _, &mut *stdio::stderr); - stdio::fputc(b'\n' as _, &mut *stdio::stderr); + stdio::fputs((*argv).cast_const(), &raw mut *stdio::stderr); + stdio::fputs(desc.as_ptr().cast(), &raw mut *stdio::stderr); + stdio::fputc((*current_arg).into(), &raw mut *stdio::stderr); + stdio::fputc(b'\n'.into(), &raw mut *stdio::stderr); }; - match find_option(*current_arg, optstring) { + match unsafe { find_option(*current_arg, optstring) } { Some(GetoptOption::Flag) => { update_current_opt(); - *current_arg as c_int + unsafe { c_int::from(*current_arg) } } - Some(GetoptOption::OptArg) => { - CURRENT_OPT = b"\0".as_ptr() as _; + Some(GetoptOption::OptArg) => unsafe { + CURRENT_OPT = c"".as_ptr().cast_mut(); if *current_arg.offset(1) == 0 { optind += 2; if optind > argc { CURRENT_OPT = ptr::null_mut(); - optopt = *current_arg as c_int; + optopt = c_int::from(*current_arg); let errch = if *optstring == b':' as c_char { b':' } else { @@ -168,29 +192,31 @@ unsafe fn parse_arg( b'?' }; - errch as c_int + c_int::from(errch) } else { optarg = *argv.offset(optind as isize - 1); - *current_arg as c_int + c_int::from(*current_arg) } } else { optarg = current_arg.offset(1); optind += 1; - *current_arg as c_int + c_int::from(*current_arg) } - } + }, None => { // couldn't find the given option in optstring - if opterr != 0 { + if unsafe { opterr != 0 } { print_error(b": illegal option -- \0"); } update_current_opt(); - optopt = *current_arg as c_int; - b'?' as c_int + unsafe { + optopt = c_int::from(*current_arg); + } + c_int::from(b'?') } } } @@ -203,9 +229,9 @@ enum GetoptOption { unsafe fn find_option(ch: c_char, optstring: *const c_char) -> Option { let mut i = 0; - while *optstring.offset(i) != 0 { - if *optstring.offset(i) == ch { - let result = if *optstring.offset(i + 1) == b':' as c_char { + while unsafe { *optstring.offset(i) != 0 } { + if unsafe { *optstring.offset(i) == ch } { + let result = if unsafe { *optstring.offset(i + 1) == b':' as c_char } { GetoptOption::OptArg } else { GetoptOption::Flag diff --git a/src/header/glob/cbindgen.toml b/src/header/glob/cbindgen.toml new file mode 100644 index 0000000000..d8de241ec3 --- /dev/null +++ b/src/header/glob/cbindgen.toml @@ -0,0 +1,16 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/glob.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the size_t type as described in ." +# +# size_t is defined in stddef.h (sys/types.h gets size_t by including stddef.h) +# just use stddef.h to avoid importing all of sys/types.h +sys_includes = ["stddef.h"] +include_guard = "_RELIBC_GLOB_H" +language = "C" +style = "type" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/glob/mod.rs b/src/header/glob/mod.rs new file mode 100644 index 0000000000..fcbaa7f7b0 --- /dev/null +++ b/src/header/glob/mod.rs @@ -0,0 +1,401 @@ +//! `glob.h` implementation. +//! +//! See . + +use core::ptr; + +use alloc::{boxed::Box, vec::Vec}; + +use crate::{ + c_str::{CStr, CString}, + header::{ + dirent::{closedir, opendir, readdir}, + errno::*, + fnmatch::{FNM_NOESCAPE, FNM_PERIOD, fnmatch}, + sys_stat::{S_IFDIR, S_IFMT, stat}, + }, + platform::{ + self, + types::{c_char, c_int, c_uchar, c_void, size_t}, + }, +}; + +// Cause glob() to return on error +pub const GLOB_ERR: c_int = 0x0001; +// Each pathname that is a directory that matches pattern has a slash appended +pub const GLOB_MARK: c_int = 0x0002; +// Do not sort returned pathnames +pub const GLOB_NOSORT: c_int = 0x0004; +// Add gl_offs amount of null pointers to the beginning of `gl_pathv` +pub const GLOB_DOOFFS: c_int = 0x0008; +// If pattern does not match, return a list containing only pattern +pub const GLOB_NOCHECK: c_int = 0x0010; +// Append generated pathnames to those previously obtained +pub const GLOB_APPEND: c_int = 0x0020; +// Disable backslash escaping +pub const GLOB_NOESCAPE: c_int = 0x0040; +// Allow wildcards to match '.' (GNU extension) +pub const GLOB_PERIOD: c_int = 0x0080; + +// Attempt to allocate memory failed +pub const GLOB_NOSPACE: c_int = 1; +// Scan was stopped because GLOB_ERR was set or `errfunc` returned non-zero +pub const GLOB_ABORTED: c_int = 2; +// Pattern does not match any existing pathname, and GLOB_NOCHECK was not set +pub const GLOB_NOMATCH: c_int = 3; + +/// See . +#[derive(Debug)] +#[repr(C)] +pub struct glob_t { + pub gl_pathc: size_t, // Count of paths matched by pattern (POSIX required field) + pub gl_offs: size_t, // Slots to reserve at the beginning of gl_pathv (POSIX required field) + pub gl_pathv: *mut *mut c_char, // Pointer to list of matched pathnames (POSIX required field) + + // Opaque pointer to allocation data + __opaque: *mut c_void, // Vec<*mut c_char> +} + +/// See . +#[linkage = "weak"] // GNU prefers its own glob e.g. in Make +#[unsafe(no_mangle)] +pub unsafe extern "C" fn glob( + pattern: *const c_char, + flags: c_int, + errfunc: Option c_int>, + pglob: *mut glob_t, +) -> c_int { + if flags & GLOB_APPEND != GLOB_APPEND { + unsafe { + (*pglob).gl_pathc = 0; + (*pglob).gl_pathv = ptr::null_mut(); + (*pglob).__opaque = ptr::null_mut(); + } + } + + let glob_expr = unsafe { CStr::from_ptr(pattern) }; + + if glob_expr.to_bytes() == b"" { + return GLOB_NOMATCH; + } + + let base_path = unsafe { + CStr::from_bytes_with_nul_unchecked(if glob_expr.to_bytes().first() == Some(&b'/') { + b"/\0" + } else { + b"\0" + }) + }; + + let errfunc = match errfunc { + Some(f) => f, + None => default_errfunc, + }; + + // Do the globbing + let mut results = match inner_glob(&base_path, &glob_expr, flags, errfunc) { + Ok(res) => res, + Err(e) => return e, + }; + + // Handle GLOB_NOCHECK and no matches + if results.is_empty() { + if flags & GLOB_NOCHECK == GLOB_NOCHECK { + results.push(glob_expr.to_owned_cstring()); + } else { + return GLOB_NOMATCH; + } + } + + // Handle GLOB_NOSORT + if flags & GLOB_NOSORT != GLOB_NOSORT { + results.sort(); + } + + // Set gl_pathc + if flags & GLOB_APPEND == GLOB_APPEND { + unsafe { + (*pglob).gl_pathc += results.len(); + } + } else { + unsafe { + (*pglob).gl_pathc = results.len(); + } + } + + let mut pathv: Box>; + if flags & GLOB_APPEND == GLOB_APPEND { + pathv = unsafe { Box::from_raw((*pglob).__opaque.cast()) }; + pathv.pop(); // Remove NULL from end + } else { + pathv = Box::new(Vec::new()); + if flags & GLOB_DOOFFS == GLOB_DOOFFS { + let gl_offs = unsafe { (*pglob).gl_offs }; + pathv.reserve(gl_offs); + for _ in 0..gl_offs { + pathv.push(ptr::null_mut()); + } + } + } + + pathv.reserve_exact(results.len() + 1); + pathv.extend(results.into_iter().map(|s| s.into_raw())); + + pathv.push(ptr::null_mut()); + + unsafe { + (*pglob).gl_pathv = pathv.as_ptr().cast_mut(); + (*pglob).__opaque = Box::into_raw(pathv).cast(); + } + + 0 +} + +/// See . +#[linkage = "weak"] // GNU prefers its own glob e.g. in Make +#[unsafe(no_mangle)] +pub unsafe extern "C" fn globfree(pglob: *mut glob_t) { + // Retake ownership + if unsafe { !(*pglob).__opaque.is_null() } { + let pathv: Box> = unsafe { Box::from_raw((*pglob).__opaque.cast()) }; + for (idx, path) in pathv.into_iter().enumerate() { + if unsafe { idx < (*pglob).gl_offs } { + continue; + } + if !path.is_null() { + unsafe { + drop(CString::from_raw(path)); + } + } + } + unsafe { + (*pglob).gl_pathv = ptr::null_mut(); + } + } +} + +type GlobErrorFunc = unsafe extern "C" fn(epath: *const c_char, eerrno: c_int) -> c_int; + +struct DirEntry { + name: CString, + is_dir: bool, +} + +unsafe extern "C" fn default_errfunc(epath: *const c_char, eerrno: c_int) -> c_int { + 0 +} + +fn list_dir( + path: &CStr, + errfunc: GlobErrorFunc, + abort_on_error: bool, +) -> Result, c_int> { + const DT_DIR: c_uchar = 4; // From dirent.h + const DT_LNK: c_uchar = 10; // From dirent.h + + let old_errno = platform::ERRNO.get(); + let mut results: Vec = Vec::new(); + let open_path = if path.to_bytes().is_empty() { + unsafe { &CStr::from_bytes_with_nul_unchecked(b".\0") } + } else { + path + }; + let dir = unsafe { opendir(open_path.as_ptr()) }; + + if dir.is_null() { + let new_errno = platform::ERRNO.get(); + platform::ERRNO.set(old_errno); + + if unsafe { errfunc(path.as_ptr(), new_errno) } != 0 || abort_on_error { + return Err(GLOB_ABORTED); + } + + return Ok(results); + } + + platform::ERRNO.set(0); + + loop { + let entry = unsafe { readdir(&mut *dir) }; + if entry.is_null() { + break; + } + + let name = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()).to_owned_cstring() }; + + if name.as_bytes() == b"." || name.as_bytes() == b".." { + continue; + } + + let is_dir: bool = unsafe { + if (*entry).d_type == DT_DIR { + true + } else if (*entry).d_type == DT_LNK { + // Resolve symbolic link + let mut full_path = path.to_owned_cstring().into_string().unwrap(); + if !full_path.ends_with('/') { + full_path.push('/'); + } + full_path.push_str(name.to_str().unwrap()); + full_path.push('\0'); + + let mut link_info = stat::default(); + if stat( + full_path.as_ptr().cast::(), + ptr::from_mut(&mut link_info), + ) != 0 + { + let errno = platform::ERRNO.get(); + platform::ERRNO.set(old_errno); + if errfunc(full_path.as_ptr().cast::(), errno) != 0 || abort_on_error { + return Err(GLOB_ABORTED); + } + } + link_info.st_mode & S_IFMT == S_IFDIR + } else { + false + } + }; + + results.push(DirEntry { name, is_dir }); + } + + // Check if entry == NULL because of an error + let errno = platform::ERRNO.get(); + + unsafe { closedir(Box::from_raw(dir)) }; + + // Restore the old errno + platform::ERRNO.set(old_errno); + + if errno != 0 && (unsafe { errfunc(path.as_ptr(), errno) } != 0 || abort_on_error) { + return Err(GLOB_ABORTED); + } + + Ok(results) +} + +fn inner_glob( + current_dir: &CStr, + glob_expr: &CStr, + flags: c_int, + errfunc: GlobErrorFunc, +) -> Result, c_int> { + let mut pattern: Vec = Vec::new(); + + // Remove any '/' chars at the start of the expression + let glob_expr = { + let mut expr = glob_expr.to_bytes_with_nul(); + while expr.first() == Some(&b'/') { + expr = &expr[1..]; + } + unsafe { CStr::from_bytes_with_nul_unchecked(expr) } + }; + + // Get the next section of the glob expression (up to non-escaped '/') + let glob_iter = glob_expr.to_bytes(); + let mut in_bracket = false; + let mut escaped = false; + let mut glob_consumed = 0; + + for ch in glob_iter { + // Don't consume nul + if ch == &b'\0' { + break; + } + + glob_consumed += 1; + + if ch == &b'/' && !escaped { + break; + } + + if ch == &b'[' && !escaped { + in_bracket = true; + } else if ch == &b']' { + // '\' is a normal character in brackets so doesn't escape + in_bracket = false; + } + + escaped = + ch == &b'\\' && !in_bracket && !escaped && (flags & GLOB_NOESCAPE != GLOB_NOESCAPE); + + pattern.push(*ch); + } + + // Needs to be C-string + pattern.push(b'\0'); + + let new_glob_expr = unsafe { + CStr::from_bytes_with_nul_unchecked(&glob_expr.to_bytes_with_nul()[glob_consumed..]) + }; + + // Handle special path sections + if pattern == b".\0" || pattern == b"..\0" { + let mut new_dir: Vec = Vec::new(); + new_dir.extend_from_slice(current_dir.to_bytes()); + new_dir.extend_from_slice(&pattern); + let new_dir_c = unsafe { CStr::from_bytes_with_nul_unchecked(&new_dir) }; + return inner_glob(&new_dir_c, &new_glob_expr, flags, errfunc); + } + + let mut fnmatch_flags = 0; + if flags & GLOB_NOESCAPE == GLOB_NOESCAPE { + fnmatch_flags |= FNM_NOESCAPE; + } + if flags & GLOB_PERIOD == GLOB_PERIOD { + fnmatch_flags |= FNM_PERIOD; + } + + let mut matches: Vec = Vec::new(); + + for entry in list_dir(current_dir, errfunc, flags & GLOB_ERR == GLOB_ERR)? { + // If we still have pattern to match ignore non-directories + if !new_glob_expr.to_bytes().is_empty() && !entry.is_dir { + continue; + } + + let mut path = current_dir.to_bytes().to_vec(); + + if path != b"" && !path.ends_with(b"/") { + path.push(b'/'); + } + path.extend_from_slice(entry.name.as_bytes()); + + if flags & GLOB_MARK == GLOB_MARK && new_glob_expr.to_bytes() == b"" && entry.is_dir { + path.push(b'/'); + } + + // This shouldn't ever panic, we know the vec has no nul bytes + let path = CString::new(path).unwrap(); + + if unsafe { + fnmatch( + pattern.as_ptr().cast::(), + entry.name.as_ptr(), + fnmatch_flags, + ) + } == 0 + { + if entry.is_dir && new_glob_expr.to_bytes() != b"" { + let new_matches = inner_glob(&CStr::borrow(&path), &new_glob_expr, flags, errfunc)?; + matches.extend(new_matches); + } else { + matches.push(path); + } + } + } + + // It is an error if we don't find a directory when we expect one + if matches.is_empty() && !new_glob_expr.to_bytes().is_empty() { + let mut path = current_dir.to_bytes().to_vec(); + path.extend_from_slice(&pattern); + if unsafe { errfunc(path.as_ptr().cast::(), ENOENT) } != 0 + || flags & GLOB_ERR == GLOB_ERR + { + return Err(GLOB_ABORTED); + } + } + + Ok(matches) +} diff --git a/src/header/grp/cbindgen.toml b/src/header/grp/cbindgen.toml index 4a2e9f2cf0..61102f5158 100644 --- a/src/header/grp/cbindgen.toml +++ b/src/header/grp/cbindgen.toml @@ -1,8 +1,13 @@ -sys_includes = [] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/grp.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the gid_t and size_t types as described in ." +sys_includes = ["sys/types.h"] include_guard = "_RELIBC_GRP_H" language = "C" style = "Tag" -no_includes = true +usize_is_size_t = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/grp/mod.rs b/src/header/grp/mod.rs index 6c5b0c16d4..1f5ef44a6f 100644 --- a/src/header/grp/mod.rs +++ b/src/header/grp/mod.rs @@ -1,8 +1,91 @@ -//! grp implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/grp.h.html +//! `grp.h` implementation. +//! +//! See . -use crate::platform::types::*; +use core::{ + cell::SyncUnsafeCell, + convert::TryInto, + mem::{self, MaybeUninit}, + num::ParseIntError, + ops::{Deref, DerefMut}, + pin::Pin, + ptr, slice, +}; +use alloc::{ + borrow::ToOwned, + string::{FromUtf8Error, String}, +}; + +use crate::{ + error::ResultExt, + fs::File, + header::{errno, fcntl, limits, string::strlen}, + io::{self, BufReader, Lines, prelude::*}, + platform::{ + self, Pal, Sys, + types::{c_char, c_int, c_void, gid_t, size_t}, + }, +}; + +use super::{errno::*, string::strncmp}; + +#[cfg(target_os = "linux")] +const SEPARATOR: char = ':'; + +#[cfg(target_os = "redox")] +const SEPARATOR: char = ';'; + +const GROUP_FILE: &core::ffi::CStr = c"/etc/group"; + +#[derive(Clone, Copy, Debug)] +struct DestBuffer { + ptr: *mut u8, + len: usize, +} + +// Shamelessly stolen from pwd/mod.rs +#[derive(Debug)] +enum MaybeAllocated { + Owned(Pin>), + Borrowed(DestBuffer), +} +impl Deref for MaybeAllocated { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts(dst.ptr, dst.len) + }, + } + } +} +impl DerefMut for MaybeAllocated { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts_mut(dst.ptr, dst.len) + }, + } + } +} + +static mut GROUP_BUF: Option = None; +static mut GROUP: group = group { + gr_name: ptr::null_mut(), + gr_passwd: ptr::null_mut(), + gr_gid: 0, + gr_mem: ptr::null_mut(), +}; + +static LINE_READER: SyncUnsafeCell>>> = SyncUnsafeCell::new(None); + +/// See . #[repr(C)] +#[derive(Debug)] pub struct group { pub gr_name: *mut c_char, pub gr_passwd: *mut c_char, @@ -10,56 +93,466 @@ pub struct group { pub gr_mem: *mut *mut c_char, } -// #[no_mangle] -pub extern "C" fn getgrgid(gid: gid_t) -> *mut group { - unimplemented!(); +#[derive(Debug)] +enum Error { + EOF, + SyntaxError, + BufTooSmall, + Misc(io::Error), + FromUtf8Error(FromUtf8Error), + ParseIntError(ParseIntError), + Other, } -// #[no_mangle] -pub extern "C" fn getgrnam(name: *const c_char) -> *mut group { - unimplemented!(); +#[derive(Debug)] +struct OwnedGrp { + buffer: MaybeAllocated, + reference: group, } -// #[no_mangle] -pub extern "C" fn getgrgid_r( +impl OwnedGrp { + fn into_global(self) -> *mut group { + unsafe { + GROUP_BUF = Some(self.buffer); + GROUP = self.reference; + &raw mut GROUP + } + } +} + +fn split(buf: &mut [u8]) -> Option { + let gr_gid = match buf[0..mem::size_of::()].try_into() { + Ok(buf) => gid_t::from_ne_bytes(buf), + Err(err) => return None, + }; + + // Get address of buffer for fixing up gr_mem + let buf_addr = buf.as_ptr() as usize; + + // We moved the gid to the beginning of the byte buffer so we can do this. + let mut parts = buf[mem::size_of::()..].split_mut(|&c| c == b'\0'); + let gr_name = parts.next()?.as_mut_ptr().cast::(); + let gr_passwd = parts.next()?.as_mut_ptr().cast::(); + let gr_mem = parts.next()?.as_mut_ptr().cast::(); + + // Adjust gr_mem address by buffer base address + // TODO: max group members length? + for i in 0..4096 { + unsafe { + if *gr_mem.add(i) == 0 { + // End of gr_mem pointer array + break; + } + *gr_mem.add(i) += buf_addr; + } + } + + Some(group { + gr_name, + gr_passwd, + gr_gid, + gr_mem: gr_mem.cast::<*mut c_char>(), + }) +} + +fn parse_grp(line: String, destbuf: Option) -> Result { + let buffer = line.to_owned().into_bytes(); + + let mut buffer = buffer + .into_iter() + .map(|i| if i == SEPARATOR as u8 { b'\0' } else { i }) + .chain([b'\0']) + .collect::>(); + let mut buffer = buffer.split_mut(|i| *i == b'\0'); + + let strings = { + let mut vec: Vec = Vec::new(); + + let gr_name = buffer.next().ok_or(Error::EOF)?.to_vec(); + let gr_passwd = buffer.next().ok_or(Error::EOF)?.to_vec(); + let gr_gid = String::from_utf8(buffer.next().ok_or(Error::EOF)?.to_vec()) + .map_err(Error::FromUtf8Error)? + .parse::() + .map_err(Error::ParseIntError)?; + + // Place the gid at the beginning of the byte buffer to make getting it back out again later, much faster. + + vec.extend(gr_gid.to_ne_bytes()); + vec.extend(gr_name); + vec.push(0); + vec.extend(gr_passwd); + vec.push(0); + + let members = buffer.next().ok_or(Error::EOF)?; + + // Get the offset of the members array + let member_array_start = vec.len(); + + // Push enough null pointers to fit all members + for _member in members + .split(|b| *b == b',') + .filter(|member| !member.is_empty()) + { + vec.extend(0usize.to_ne_bytes()); + } + let member_array_end = vec.len(); + // Push a null pointer to terminate the members array + vec.extend(0usize.to_ne_bytes()); + + // Fill in member names + for (i, member) in members + .split(|b| *b == b',') + .filter(|member| !member.is_empty()) + .enumerate() + { + let cur_offset = vec.len(); + + // This must be recomputed each time, because `vec` is undergoing extensions and so + // its backing memory might be reallocated and moved and its old memory deallocated. + let member_array = &mut vec[member_array_start..member_array_end]; + let member_ptr = { + const SIZEOF_PTR: usize = mem::size_of::<*mut c_void>(); + let start = i * SIZEOF_PTR; + let end = start + SIZEOF_PTR; + &mut member_array[start..end] + }; + + // Store offset to start of member, MUST BE ADJUSTED LATER BASED ON THE ADDRESS OF THE BUFFER + member_ptr.copy_from_slice(&cur_offset.to_ne_bytes()); + + vec.extend(member); + vec.push(0); + } + + vec + }; + + let mut buffer = match destbuf { + None => MaybeAllocated::Owned(Box::into_pin(strings.into_boxed_slice())), + Some(buf) => { + let mut buf = MaybeAllocated::Borrowed(buf); + + if buf.len() < strings.len() { + platform::ERRNO.set(errno::ERANGE); + return Err(Error::BufTooSmall); + } + + buf[..strings.len()].copy_from_slice(&strings); + buf + } + }; + let reference = split(&mut buffer).ok_or(Error::Other)?; + + Ok(OwnedGrp { buffer, reference }) +} + +/// MT-Unsafe race:grgid locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrgid(gid: gid_t) -> *mut group { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return ptr::null_mut(); + }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { + return ptr::null_mut(); + }; + let Ok(grp) = parse_grp(line, None) else { + return ptr::null_mut(); + }; + + if grp.reference.gr_gid == gid { + return grp.into_global(); + } + } + + ptr::null_mut() +} + +/// MT-Unsafe race:grnam locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrnam(name: *const c_char) -> *mut group { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return ptr::null_mut(); + }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { + return ptr::null_mut(); + }; + + let Ok(grp) = parse_grp(line, None) else { + return ptr::null_mut(); + }; + + // Attempt to prevent BO vulnerabilities + if unsafe { + strncmp( + grp.reference.gr_name, + name, + strlen(grp.reference.gr_name).min(strlen(name)), + ) == 0 + } { + return grp.into_global(); + } + } + + ptr::null_mut() +} + +/// MT-Safe locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrgid_r( gid: gid_t, - grp: *mut group, + result_buf: *mut group, buffer: *mut c_char, - bufsize: usize, + buflen: usize, result: *mut *mut group, ) -> c_int { - unimplemented!(); + // In case of error or the requested entry is not found. + unsafe { + *result = ptr::null_mut(); + } + + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return ENOENT; + }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { return EINVAL }; + let grp = match parse_grp( + line, + Some(DestBuffer { + ptr: buffer.cast::(), + len: buflen, + }), + ) { + Ok(grp) => grp, + Err(err) => { + return match err { + Error::BufTooSmall => ERANGE, + Error::EOF + | Error::SyntaxError + | Error::FromUtf8Error(_) + | Error::ParseIntError(_) + | Error::Other => EINVAL, + Error::Misc(io_err) => match io_err.kind() { + io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => EINVAL, + io::ErrorKind::NotFound => ENOENT, + _ => EIO, + }, + }; + } + }; + + if grp.reference.gr_gid == gid { + unsafe { + *result_buf = grp.reference; + *result = result_buf; + } + + return 0; + } + } + + // The requested entry was not found. + 0 } -// #[no_mangle] -pub extern "C" fn getgrnam_r( +/// MT-Safe locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrnam_r( name: *const c_char, - grp: *mut group, + result_buf: *mut group, buffer: *mut c_char, - bufsize: usize, + buflen: usize, result: *mut *mut group, ) -> c_int { - unimplemented!(); + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return ENOENT; + }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { return EINVAL }; + let Ok(grp) = parse_grp( + line, + Some(DestBuffer { + ptr: buffer.cast::(), + len: buflen, + }), + ) else { + return EINVAL; + }; + + if unsafe { + strncmp( + grp.reference.gr_name, + name, + strlen(grp.reference.gr_name).min(strlen(name)), + ) == 0 + } { + unsafe { + *result_buf = grp.reference; + *result = result_buf; + } + + return 0; + } + } + + ENOENT } -// #[no_mangle] -pub extern "C" fn getgrent() -> *mut group { - unimplemented!(); +/// MT-Unsafe race:grent race:grentbuf locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrent() -> *mut group { + let mut line_reader = unsafe { &mut *LINE_READER.get() }; + + if line_reader.is_none() { + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return ptr::null_mut(); + }; + *line_reader = Some(BufReader::new(db).lines()); + } + + if let Some(lines) = line_reader.deref_mut() { + let Some(line) = lines.next() else { + return ptr::null_mut(); + }; + let Ok(line) = line else { + return ptr::null_mut(); + }; + + if let Ok(grp) = parse_grp(line, None) { + grp.into_global() + } else { + ptr::null_mut() + } + } else { + ptr::null_mut() + } } -// #[no_mangle] -pub extern "C" fn endgrent() { - unimplemented!(); +/// MT-Unsafe race:grent locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn endgrent() { + unsafe { + *(&mut *LINE_READER.get()) = None; + } } -// #[no_mangle] -pub extern "C" fn setgrent() { - unimplemented!(); +/// MT-Unsafe race:grent locale +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setgrent() { + let line_reader = unsafe { &mut *LINE_READER.get() }; + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return; + }; + *line_reader = Some(BufReader::new(db).lines()); } -/* -#[no_mangle] -pub extern "C" fn func(args) -> c_int { - unimplemented!(); +/// MT-Safe locale +/// Not POSIX +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgrouplist( + user: *const c_char, + group: gid_t, + groups: *mut gid_t, + ngroups: *mut c_int, +) -> c_int { + let grps = unsafe { + slice::from_raw_parts_mut(groups.cast::>(), ngroups.read() as usize) + }; + + // FIXME: This API probably expects the group database to already exist in memory, as it + // doesn't seem to have any documented error handling. + + let Ok(user) = (unsafe { crate::c_str::CStr::from_ptr(user).to_str() }) else { + return 0; + }; + + let Ok(db) = File::open(GROUP_FILE.into(), fcntl::O_RDONLY) else { + return 0; + }; + + let mut groups_found: c_int = 0; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { + return 0; + }; + + let mut parts = line.split(SEPARATOR); + + let group_name = parts.next().unwrap_or(""); + let group_password = parts.next().unwrap_or(""); + let group_id = parts.next().unwrap_or("-1").parse::().unwrap(); + let members = parts + .next() + .unwrap_or("") + .split(",") + .map(|i| i.trim()) + .collect::>(); + + if !members.contains(&user) { + continue; + } + + if let Some(dst) = grps.get_mut(groups_found as usize) { + dst.write(group_id); + } + + groups_found = match groups_found.checked_add(1) { + Some(g) => g, + None => break, + }; + } + + unsafe { + ngroups.write(groups_found); + } + + if groups_found as usize > grps.len() { + -1 + } else { + groups_found + } +} + +/// MT-Safe locale +/// Not POSIX +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn initgroups(user: *const c_char, gid: gid_t) -> c_int { + let mut groups = [0; limits::NGROUPS_MAX]; + let mut count = groups.len() as c_int; + if unsafe { getgrouplist(user, gid, groups.as_mut_ptr(), &raw mut count) < 0 } { + return -1; + } + unsafe { setgroups(count as size_t, groups.as_ptr()) } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setgroups(size: size_t, list: *const gid_t) -> c_int { + unsafe { Sys::setgroups(size, list) } + .map(|()| 0) + .or_minus_one_errno() } -*/ diff --git a/src/header/ifaddrs/cbindgen.toml b/src/header/ifaddrs/cbindgen.toml new file mode 100644 index 0000000000..b507c6f75f --- /dev/null +++ b/src/header/ifaddrs/cbindgen.toml @@ -0,0 +1,17 @@ +# netinet/in.h brings in sys/socket.h +sys_includes = ["features.h", "netinet/in.h"] +include_guard = "_RELIBC_IFADDRS_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true +trailer = """ +#define ifa_broadaddr ifa_ifu.ifu_broadaddr +#define ifa_dstaddr ifa_ifu.ifu_dstaddr +""" + +[export.rename] +"sockaddr" = "struct sockaddr" + +[enum] +prefix_with_name = true diff --git a/src/header/ifaddrs/mod.rs b/src/header/ifaddrs/mod.rs new file mode 100644 index 0000000000..bddb69b858 --- /dev/null +++ b/src/header/ifaddrs/mod.rs @@ -0,0 +1,44 @@ +//! `ifaddrs.h` implementation. +//! +//! Non-POSIX, see . + +use crate::{ + header::{errno, stdlib, sys_socket::sockaddr}, + platform::{ + self, + types::{c_char, c_int, c_uint, c_void}, + }, +}; + +#[repr(C)] +union ifaddrs_ifa_ifu { + ifu_broadaddr: *mut sockaddr, + ifu_dstaddr: *mut sockaddr, +} + +#[repr(C)] +pub struct ifaddrs { + ifa_next: *mut ifaddrs, + ifa_name: *mut c_char, + ifa_flags: c_uint, + ifa_addr: *mut sockaddr, + ifa_netmask: *mut sockaddr, + ifa_ifu: ifaddrs_ifa_ifu, + ifa_data: *mut c_void, +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn freeifaddrs(mut ifa: *mut ifaddrs) { + while !ifa.is_null() { + let next = unsafe { (*ifa).ifa_next }; + unsafe { stdlib::free(ifa.cast()) }; + ifa = next; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int { + //TODO: implement getifaddrs + platform::ERRNO.set(errno::ENOSYS); + -1 +} diff --git a/src/header/inttypes/cbindgen.toml b/src/header/inttypes/cbindgen.toml index f923cb6804..b8c2b7e93b 100644 --- a/src/header/inttypes/cbindgen.toml +++ b/src/header/inttypes/cbindgen.toml @@ -1,6 +1,211 @@ -sys_includes = ["stdint.h", "wchar.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/inttypes.h.html +# +# Spec quotations relating to includes: +# - "The header shall include the header." +# - "wchar_t As described in ." +# +# Include stdint.h and stddef.h directly per POSIX spec. +# Do NOT include wchar.h — it creates a circular dependency: +# wchar.h → stdint.h → gnulib inttypes.h → inttypes.h → wchar.h +sys_includes = ["stdint.h", "stddef.h"] include_guard = "_RELIBC_INTTYPES_H" -trailer = "#include " +trailer = """ +#ifndef _RELIBC_BITS_INTTYPES_H +#define _RELIBC_BITS_INTTYPES_H + +#define PRId8 "hhd" +#define PRId16 "hd" +#define PRId32 "d" +#define PRId64 "ld" + +#define PRIdLEAST8 "hhd" +#define PRIdLEAST16 "hd" +#define PRIdLEAST32 "d" +#define PRIdLEAST64 "ld" + +#define PRIdFAST8 "hhd" +#define PRIdFAST16 "hd" +#define PRIdFAST32 "d" +#define PRIdFAST64 "ld" + +#define PRIi8 "hhi" +#define PRIi16 "hi" +#define PRIi32 "i" +#define PRIi64 "li" + +#define PRIiLEAST8 "hhi" +#define PRIiLEAST16 "hi" +#define PRIiLEAST32 "i" +#define PRIiLEAST64 "li" + +#define PRIiFAST8 "hhi" +#define PRIiFAST16 "hi" +#define PRIiFAST32 "i" +#define PRIiFAST64 "li" + +#define PRIo8 "hho" +#define PRIo16 "ho" +#define PRIo32 "o" +#define PRIo64 "lo" + +#define PRIoLEAST8 "hho" +#define PRIoLEAST16 "ho" +#define PRIoLEAST32 "o" +#define PRIoLEAST64 "lo" + +#define PRIoFAST8 "hho" +#define PRIoFAST16 "ho" +#define PRIoFAST32 "o" +#define PRIoFAST64 "lo" + +#define PRIu8 "hhu" +#define PRIu16 "hu" +#define PRIu32 "u" +#define PRIu64 "lu" + +#define PRIuLEAST8 "hhu" +#define PRIuLEAST16 "hu" +#define PRIuLEAST32 "u" +#define PRIuLEAST64 "lu" + +#define PRIuFAST8 "hhu" +#define PRIuFAST16 "hu" +#define PRIuFAST32 "u" +#define PRIuFAST64 "lu" + +#define PRIx8 "hhx" +#define PRIx16 "hx" +#define PRIx32 "x" +#define PRIx64 "lx" + +#define PRIxLEAST8 "hhx" +#define PRIxLEAST16 "hx" +#define PRIxLEAST32 "x" +#define PRIxLEAST64 "lx" + +#define PRIxFAST8 "hhx" +#define PRIxFAST16 "hx" +#define PRIxFAST32 "x" +#define PRIxFAST64 "lx" + +#define PRIX8 "hhX" +#define PRIX16 "hX" +#define PRIX32 "X" +#define PRIX64 "lX" + +#define PRIXLEAST8 "hhX" +#define PRIXLEAST16 "hX" +#define PRIXLEAST32 "X" +#define PRIXLEAST64 "lX" + +#define PRIXFAST8 "hhX" +#define PRIXFAST16 "hX" +#define PRIXFAST32 "X" +#define PRIXFAST64 "lX" + +#define PRIdMAX "jd" +#define PRIiMAX "ji" +#define PRIoMAX "jo" +#define PRIuMAX "ju" +#define PRIxMAX "jx" +#define PRIXMAX "jX" + +#define PRIdPTR "td" +#define PRIiPTR "ti" +#define PRIoPTR "to" +#define PRIuPTR "tu" +#define PRIxPTR "tx" +#define PRIXPTR "tX" + +#define SCNd8 PRId8 +#define SCNd16 PRId16 +#define SCNd32 PRId32 +#define SCNd64 PRId64 + +#define SCNdLEAST8 PRIdLEAST8 +#define SCNdLEAST16 PRIdLEAST16 +#define SCNdLEAST32 PRIdLEAST32 +#define SCNdLEAST64 PRIdLEAST64 + +#define SCNdFAST8 PRIdFAST8 +#define SCNdFAST16 PRIdFAST16 +#define SCNdFAST32 PRIdFAST32 +#define SCNdFAST64 PRIdFAST64 + +#define SCNi8 PRIi8 +#define SCNi16 PRIi16 +#define SCNi32 PRIi32 +#define SCNi64 PRIi64 + +#define SCNiLEAST8 PRIiLEAST8 +#define SCNiLEAST16 PRIiLEAST16 +#define SCNiLEAST32 PRIiLEAST32 +#define SCNiLEAST64 PRIiLEAST64 + +#define SCNiFAST8 PRIiFAST8 +#define SCNiFAST16 PRIiFAST16 +#define SCNiFAST32 PRIiFAST32 +#define SCNiFAST64 PRIiFAST64 + +#define SCNo8 PRIo8 +#define SCNo16 PRIo16 +#define SCNo32 PRIo32 +#define SCNo64 PRIo64 + +#define SCNoLEAST8 PRIoLEAST8 +#define SCNoLEAST16 PRIoLEAST16 +#define SCNoLEAST32 PRIoLEAST32 +#define SCNoLEAST64 PRIoLEAST64 + +#define SCNoFAST8 PRIoFAST8 +#define SCNoFAST16 PRIoFAST16 +#define SCNoFAST32 PRIoFAST32 +#define SCNoFAST64 PRIoFAST64 + +#define SCNu8 PRIu8 +#define SCNu16 PRIu16 +#define SCNu32 PRIu32 +#define SCNu64 PRIu64 + +#define SCNuLEAST8 PRIuLEAST8 +#define SCNuLEAST16 PRIuLEAST16 +#define SCNuLEAST32 PRIuLEAST32 +#define SCNuLEAST64 PRIuLEAST64 + +#define SCNuFAST8 PRIuFAST8 +#define SCNuFAST16 PRIuFAST16 +#define SCNuFAST32 PRIuFAST32 +#define SCNuFAST64 PRIuFAST64 + +#define SCNx8 PRIx8 +#define SCNx16 PRIx16 +#define SCNx32 PRIx32 +#define SCNx64 PRIx64 + +#define SCNxLEAST8 PRIxLEAST8 +#define SCNxLEAST16 PRIxLEAST16 +#define SCNxLEAST32 PRIxLEAST32 +#define SCNxLEAST64 PRIxLEAST64 + +#define SCNxFAST8 PRIxFAST8 +#define SCNxFAST16 PRIxFAST16 +#define SCNxFAST32 PRIxFAST32 +#define SCNxFAST64 PRIxFAST64 + +#define SCNdMAX PRIdMAX +#define SCNiMAX PRIiMAX +#define SCNoMAX PRIoMAX +#define SCNuMAX PRIuMAX +#define SCNxMAX PRIxMAX + +#define SCNdPTR PRIdPTR +#define SCNiPTR PRIiPTR +#define SCNoPTR PRIoPTR +#define SCNuPTR PRIuPTR +#define SCNxPTR PRIxPTR + +#endif // _RELIBC_BITS_INTTYPES_H +""" language = "C" style = "Type" no_includes = true diff --git a/src/header/inttypes/mod.rs b/src/header/inttypes/mod.rs index 52ec258c70..29eff295dd 100644 --- a/src/header/inttypes/mod.rs +++ b/src/header/inttypes/mod.rs @@ -1,21 +1,34 @@ +//! `inttypes.h` implementation. +//! +//! See . + use crate::{ - header::{ctype, errno::*, stdlib::*}, - platform::{self, types::*}, + header::{ + ctype::{self, isspace}, + errno::{EINVAL, ERANGE}, + stdlib::{convert_hex, convert_integer, convert_octal, detect_base, is_positive}, + }, + platform::{ + self, + types::{c_char, c_int, c_long, intmax_t, uintmax_t, wchar_t}, + }, }; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn imaxabs(i: intmax_t) -> intmax_t { i.abs() } -#[no_mangle] +/// See . #[repr(C)] pub struct imaxdiv_t { quot: intmax_t, rem: intmax_t, } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn imaxdiv(i: intmax_t, j: intmax_t) -> imaxdiv_t { imaxdiv_t { quot: i / j, @@ -23,7 +36,8 @@ pub extern "C" fn imaxdiv(i: intmax_t, j: intmax_t) -> imaxdiv_t { } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtoimax( s: *const c_char, endptr: *mut *mut c_char, @@ -32,15 +46,16 @@ pub unsafe extern "C" fn strtoimax( strto_impl!( intmax_t, false, - intmax_t::max_value(), - intmax_t::min_value(), + intmax_t::MAX, + intmax_t::MIN, s, endptr, base ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtoumax( s: *const c_char, endptr: *mut *mut c_char, @@ -49,30 +64,40 @@ pub unsafe extern "C" fn strtoumax( strto_impl!( uintmax_t, false, - uintmax_t::max_value(), - uintmax_t::min_value(), + uintmax_t::MAX, + uintmax_t::MIN, s, endptr, base ) } -#[allow(unused)] -// #[no_mangle] -pub extern "C" fn wcstoimax( - nptr: *const wchar_t, - endptr: *mut *mut wchar_t, +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcstoimax( + mut ptr: *const wchar_t, + end: *mut *mut wchar_t, base: c_int, ) -> intmax_t { - unimplemented!(); + skipws!(ptr); + let result = strto_impl!(intmax_t, ptr, base); + if !end.is_null() { + unsafe { *end = ptr.cast_mut() }; + } + result } -#[allow(unused)] -// #[no_mangle] -pub extern "C" fn wcstoumax( - nptr: *const wchar_t, - endptr: *mut *mut wchar_t, +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcstoumax( + mut ptr: *const wchar_t, + end: *mut *mut wchar_t, base: c_int, ) -> uintmax_t { - unimplemented!(); + skipws!(ptr); + let result = strtou_impl!(uintmax_t, ptr, base); + if !end.is_null() { + unsafe { *end = ptr.cast_mut() }; + } + result } diff --git a/src/header/langinfo/cbindgen.toml b/src/header/langinfo/cbindgen.toml new file mode 100644 index 0000000000..3b6b4819d3 --- /dev/null +++ b/src/header/langinfo/cbindgen.toml @@ -0,0 +1,10 @@ +sys_includes = ["stddef.h", "stdint.h", "features.h"] +include_guard = "_RELIBC_LANGINFO_H" +after_includes = "#include // locale_t" +language = "C" +style = "tag" +no_includes = false +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/langinfo/mod.rs b/src/header/langinfo/mod.rs new file mode 100644 index 0000000000..8f1eb6f81d --- /dev/null +++ b/src/header/langinfo/mod.rs @@ -0,0 +1,225 @@ +//! `langinfo.h` implementation. +//! +//! See . + +// TODO : involve loading locale data. Currently, the implementation only supports the "C" locale. + +use crate::header::bits_locale_t::locale_t; +use core::ffi::c_char; + +/// See . +/// +/// POSIX type for items used with `nl_langinfo` +/// In practice, this is an integer index into the string table. +pub type nl_item = i32; + +// Static string table for langinfo constants +static STRING_TABLE: [&[u8]; 81] = [ + b"UTF-8\0", // CODESET + b"%a %b %e %H:%M:%S %Y\0", // D_T_FMT + b"%m/%d/%y\0", // D_FMT + b"%H:%M:%S\0", // T_FMT + b"%I:%M:%S %p\0", // T_FMT_AMPM + b"AM\0", // AM_STR + b"PM\0", // PM_STR + b"Sunday\0", // DAY_1 + b"Monday\0", // DAY_2 + b"Tuesday\0", // DAY_3 + b"Wednesday\0", // DAY_4 + b"Thursday\0", // DAY_5 + b"Friday\0", // DAY_6 + b"Saturday\0", // DAY_7 + b"Sun\0", // ABDAY_1 + b"Mon\0", // ABDAY_2 + b"Tue\0", // ABDAY_3 + b"Wed\0", // ABDAY_4 + b"Thu\0", // ABDAY_5 + b"Fri\0", // ABDAY_6 + b"Sat\0", // ABDAY_7 + b"January\0", // MON_1 + b"February\0", // MON_2 + b"March\0", // MON_3 + b"April\0", // MON_4 + b"May\0", // MON_5 + b"June\0", // MON_6 + b"July\0", // MON_7 + b"August\0", // MON_8 + b"September\0", // MON_9 + b"October\0", // MON_10 + b"November\0", // MON_11 + b"December\0", // MON_12 + b"Jan\0", // ABMON_1 + b"Feb\0", // ABMON_2 + b"Mar\0", // ABMON_3 + b"Apr\0", // ABMON_4 + b"May\0", // ABMON_5 + b"Jun\0", // ABMON_6 + b"Jul\0", // ABMON_7 + b"Aug\0", // ABMON_8 + b"Sep\0", // ABMON_9 + b"Oct\0", // ABMON_10 + b"Nov\0", // ABMON_11 + b"Dec\0", // ABMON_12 + b"\0", // ERA + b"\0", // ERA_D_FMT + b"\0", // ERA_D_T_FMT + b"\0", // ERA_T_FMT + b"\0", // ALT_DIGITS + b".\0", // RADIXCHAR + b"\0", // THOUSEP + b"^[yY]\0", // YESEXPR + b"^[nN]\0", // NOEXPR + b"yes\0", // YESSTR + b"no\0", // NOSTR + b".\0", // CRNCYSTR + // Some languages have alternative names for + // months. For the "C" locale, we just use one. + b"January\0", // ALTMON_1 + b"February\0", // ALTMON_2 + b"March\0", // ALTMON_3 + b"April\0", // ALTMON_4 + b"May\0", // ALTMON_5 + b"June\0", // ALTMON_6 + b"July\0", // ALTMON_7 + b"August\0", // ALTMON_8 + b"September\0", // ALTMON_9 + b"October\0", // ALTMON_10 + b"November\0", // ALTMON_11 + b"December\0", // ALTMON_12 + b"Jan\0", // ABALTMON_1 + b"Feb\0", // ABALTMON_2 + b"Mar\0", // ABALTMON_3 + b"Apr\0", // ABALTMON_4 + b"May\0", // ABALTMON_5 + b"Jun\0", // ABALTMON_6 + b"Jul\0", // ABALTMON_7 + b"Aug\0", // ABALTMON_8 + b"Sep\0", // ABALTMON_9 + b"Oct\0", // ABALTMON_10 + b"Nov\0", // ABALTMON_11 + b"Dec\0", // ABALTMON_12 +]; + +// Item constants +pub const CODESET: nl_item = 0; +pub const D_T_FMT: nl_item = 1; +pub const D_FMT: nl_item = 2; +pub const T_FMT: nl_item = 3; +pub const T_FMT_AMPM: nl_item = 4; +pub const AM_STR: nl_item = 5; +pub const PM_STR: nl_item = 6; + +pub const DAY_1: nl_item = 7; +pub const DAY_2: nl_item = 8; +pub const DAY_3: nl_item = 9; +pub const DAY_4: nl_item = 10; +pub const DAY_5: nl_item = 11; +pub const DAY_6: nl_item = 12; +pub const DAY_7: nl_item = 13; + +pub const ABDAY_1: nl_item = 14; +pub const ABDAY_2: nl_item = 15; +pub const ABDAY_3: nl_item = 16; +pub const ABDAY_4: nl_item = 17; +pub const ABDAY_5: nl_item = 18; +pub const ABDAY_6: nl_item = 19; +pub const ABDAY_7: nl_item = 20; + +pub const MON_1: nl_item = 21; +pub const MON_2: nl_item = 22; +pub const MON_3: nl_item = 23; +pub const MON_4: nl_item = 24; +pub const MON_5: nl_item = 25; +pub const MON_6: nl_item = 26; +pub const MON_7: nl_item = 27; +pub const MON_8: nl_item = 28; +pub const MON_9: nl_item = 29; +pub const MON_10: nl_item = 30; +pub const MON_11: nl_item = 31; +pub const MON_12: nl_item = 32; + +pub const ABMON_1: nl_item = 33; +pub const ABMON_2: nl_item = 34; +pub const ABMON_3: nl_item = 35; +pub const ABMON_4: nl_item = 36; +pub const ABMON_5: nl_item = 37; +pub const ABMON_6: nl_item = 38; +pub const ABMON_7: nl_item = 39; +pub const ABMON_8: nl_item = 40; +pub const ABMON_9: nl_item = 41; +pub const ABMON_10: nl_item = 42; +pub const ABMON_11: nl_item = 43; +pub const ABMON_12: nl_item = 44; + +pub const ERA: nl_item = 45; +pub const ERA_D_FMT: nl_item = 46; +pub const ERA_D_T_FMT: nl_item = 47; +pub const ERA_T_FMT: nl_item = 48; +pub const ALT_DIGITS: nl_item = 49; +pub const RADIXCHAR: nl_item = 50; +pub const THOUSEP: nl_item = 51; +pub const YESEXPR: nl_item = 52; +pub const NOEXPR: nl_item = 53; +pub const YESSTR: nl_item = 54; // Legacy +pub const NOSTR: nl_item = 55; // Legacy +pub const CRNCYSTR: nl_item = 56; + +pub const ALTMON_1: nl_item = 57; +pub const ALTMON_2: nl_item = 58; +pub const ALTMON_3: nl_item = 59; +pub const ALTMON_4: nl_item = 60; +pub const ALTMON_5: nl_item = 61; +pub const ALTMON_6: nl_item = 62; +pub const ALTMON_7: nl_item = 63; +pub const ALTMON_8: nl_item = 64; +pub const ALTMON_9: nl_item = 65; +pub const ALTMON_10: nl_item = 66; +pub const ALTMON_11: nl_item = 67; +pub const ALTMON_12: nl_item = 68; + +pub const ABALTMON_1: nl_item = 69; +pub const ABALTMON_2: nl_item = 70; +pub const ABALTMON_3: nl_item = 71; +pub const ABALTMON_4: nl_item = 72; +pub const ABALTMON_5: nl_item = 73; +pub const ABALTMON_6: nl_item = 74; +pub const ABALTMON_7: nl_item = 75; +pub const ABALTMON_8: nl_item = 76; +pub const ABALTMON_9: nl_item = 77; +pub const ABALTMON_10: nl_item = 78; +pub const ABALTMON_11: nl_item = 79; +pub const ABALTMON_12: nl_item = 80; + +/// See . +/// +/// Get a string from the langinfo table +/// +/// # Safety +/// - Caller must ensure `item` is a valid `nl_item` index. +/// - Returns a pointer to a null-terminated string, or an empty string if the item is invalid. +/// - Compatibility requires mutable pointer to be returned, but it should not be mutated! +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nl_langinfo(item: nl_item) -> *mut c_char { + // Validate the item and perform the lookup + let ptr = if (item as usize) < STRING_TABLE.len() { + STRING_TABLE[item as usize].as_ptr().cast::() + } else { + // Return a pointer to an empty string if the item is invalid + c"".as_ptr().cast::() + }; + // Mutable pointer is required (unsafe!) + ptr.cast_mut() +} + +/// See . +/// +/// Get a string from the langinfo table +/// +/// # Safety +/// - Caller must ensure `item` is a valid `nl_item` index. +/// - Returns a pointer to a null-terminated string, or an empty string if the item is invalid. +/// - Compatibility requires mutable pointer to be returned, but it should not be mutated! +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nl_langinfo_l(item: nl_item, _loc: locale_t) -> *mut c_char { + unsafe { nl_langinfo(item) } +} diff --git a/src/header/libgen/cbindgen.toml b/src/header/libgen/cbindgen.toml index 50d050350e..0dfb9a44a7 100644 --- a/src/header/libgen/cbindgen.toml +++ b/src/header/libgen/cbindgen.toml @@ -1,3 +1,6 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/libgen.h.html +# +# There are no spec quotations relating to includes sys_includes = [] include_guard = "_RELIBC_LIBGEN_H" language = "C" diff --git a/src/header/libgen/mod.rs b/src/header/libgen/mod.rs index 5117282f11..a4e39c7584 100644 --- a/src/header/libgen/mod.rs +++ b/src/header/libgen/mod.rs @@ -1,47 +1,55 @@ -//! libgen implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/libgen.h.html +//! `libgen.h` implementation. +//! +//! See . use crate::platform::types::c_char; use crate::header::string::strlen; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn basename(str: *mut c_char) -> *mut c_char { - if str.is_null() || strlen(str) == 0 { - return ".\0".as_ptr() as *mut c_char; + if str.is_null() || unsafe { strlen(str) == 0 } { + return c".".as_ptr().cast_mut(); } - let mut end = strlen(str) as isize - 1; - while end >= 0 && *str.offset(end) == b'/' as c_char { + let mut end = unsafe { strlen(str) as isize - 1 }; + while end >= 0 && unsafe { *str.offset(end) == b'/' as c_char } { end -= 1; } if end == -1 { - return "/\0".as_ptr() as *mut c_char; + return c"/".as_ptr().cast_mut(); } let mut begin = end; - while begin >= 0 && *str.offset(begin) != b'/' as c_char { + while begin >= 0 && unsafe { *str.offset(begin) != b'/' as c_char } { begin -= 1; } - *str.offset(end + 1) = 0; - str.offset(begin + 1) as *mut c_char + unsafe { + *str.offset(end + 1) = 0; + str.offset(begin + 1).cast::() + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn dirname(str: *mut c_char) -> *mut c_char { - if str.is_null() || strlen(str) == 0 { - return ".\0".as_ptr() as *mut c_char; + if str.is_null() || unsafe { strlen(str) == 0 } { + return c".".as_ptr().cast_mut(); } - let mut end = strlen(str) as isize - 1; - while end > 0 && *str.offset(end) == b'/' as c_char { + let mut end = unsafe { strlen(str) as isize - 1 }; + while end > 0 && unsafe { *str.offset(end) == b'/' as c_char } { end -= 1; } - while end >= 0 && *str.offset(end) != b'/' as c_char { + while end >= 0 && unsafe { *str.offset(end) != b'/' as c_char } { end -= 1; } - while end > 0 && *str.offset(end) == b'/' as c_char { + while end > 0 && unsafe { *str.offset(end) == b'/' as c_char } { end -= 1; } if end == -1 { - return ".\0".as_ptr() as *mut c_char; + return c".".as_ptr().cast_mut(); + } + unsafe { + *str.offset(end + 1) = 0; } - *str.offset(end + 1) = 0; str } diff --git a/src/header/limits/cbindgen.toml b/src/header/limits/cbindgen.toml index df8b0b0469..bd3b52720e 100644 --- a/src/header/limits/cbindgen.toml +++ b/src/header/limits/cbindgen.toml @@ -1,6 +1,8 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/limits.h.html +# +# There are no spec quotations relating to includes sys_includes = [] include_guard = "_RELIBC_LIMITS_H" -trailer = "#include " language = "C" style = "Tag" no_includes = true diff --git a/src/header/limits/mod.rs b/src/header/limits/mod.rs index aab89f6ed3..45c37f06ca 100644 --- a/src/header/limits/mod.rs +++ b/src/header/limits/mod.rs @@ -1,3 +1,131 @@ -//! limits.h implementation for relibc +//! `limits.h` implementation. +//! +//! See . +use crate::platform::types::{ + c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, + c_ushort, ssize_t, +}; + +pub const NAME_MAX: usize = 255; +pub const PASS_MAX: usize = 128; pub const PATH_MAX: usize = 4096; +pub const NGROUPS_MAX: usize = 65536; + +pub const CHAR_BIT: u32 = 8; +pub const WORD_BIT: u32 = 32; +#[cfg(target_pointer_width = "32")] +pub const LONG_BIT: u32 = 32; +#[cfg(target_pointer_width = "64")] +pub const LONG_BIT: u32 = 64; + +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub const CHAR_MAX: c_char = 0xFF; +#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] +pub const CHAR_MAX: c_char = 0x7F; +pub const SCHAR_MAX: c_schar = 0x7F; +pub const SHRT_MAX: c_short = 0x7FFF; +pub const INT_MAX: c_int = 0x7FFF_FFFF; +#[cfg(target_pointer_width = "32")] +pub const LONG_MAX: c_long = 0x7FFF_FFFF; +#[cfg(target_pointer_width = "64")] +pub const LONG_MAX: c_long = 0x7FFF_FFFF_FFFF_FFFF; +pub const LLONG_MAX: c_longlong = 0x7FFF_FFFF_FFFF_FFFF; +#[cfg(target_pointer_width = "32")] +pub const SSIZE_MAX: ssize_t = 0x7FFF_FFFF; +#[cfg(target_pointer_width = "64")] +pub const SSIZE_MAX: ssize_t = 0x7FFF_FFFF_FFFF_FFFF; +pub const UCHAR_MAX: c_uchar = 0xFF; +pub const USHRT_MAX: c_ushort = 0xFFFF; +pub const UINT_MAX: c_uint = 0xFFFFFFFF; + +#[cfg(target_pointer_width = "32")] +pub const ULONG_MAX: c_ulong = 0xFFFF_FFFF; +#[cfg(target_pointer_width = "64")] +pub const ULONG_MAX: c_ulong = 0xFFFF_FFFF_FFFF_FFFF; +pub const ULLONG_MAX: c_ulonglong = 0xFFFF_FFFF_FFFF_FFFF; + +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub const CHAR_MIN: c_char = 0; +#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] +pub const CHAR_MIN: c_char = -0x80; +pub const SCHAR_MIN: c_schar = -SCHAR_MAX - 1; +pub const SHRT_MIN: c_short = -SHRT_MAX - 1; +pub const INT_MIN: c_int = -INT_MAX - 1; +pub const LONG_MIN: c_long = -LONG_MAX - 1; +pub const LLONG_MIN: c_longlong = -LLONG_MAX - 1; + +// TODO: 4096 for most architectures as determined by a quick grep of musl's source; need a better +// way to determine it for other archs or to hard code a value. +#[cfg(target_os = "linux")] +pub const PAGE_SIZE: usize = 4096; + +// These POSIX symbols must have these values regardless of OS +pub const _POSIX_AIO_LISTIO_MAX: c_long = 2; +pub const _POSIX_AIO_MAX: c_long = 1; +pub const _POSIX_ARG_MAX: c_long = 4096; +pub const _POSIX_CHILD_MAX: c_long = 25; +pub const _POSIX_CLOCKRES_MIN: c_long = 20000000; +pub const _POSIX_DELAYTIMER_MAX: c_long = 32; +pub const _POSIX_HOST_NAME_MAX: c_long = 255; +pub const _POSIX_LINK_MAX: c_long = 8; +pub const _POSIX_LOGIN_NAME_MAX: c_long = 9; +pub const _POSIX_MAX_CANON: c_long = 255; +pub const _POSIX_MAX_INPUT: c_long = 255; +pub const _POSIX_NAME_MAX: c_long = 14; +pub const _POSIX_NGROUPS_MAX: c_long = 8; +pub const _POSIX_OPEN_MAX: c_long = 20; +pub const _POSIX_PATH_MAX: c_long = 256; +pub const _POSIX_PIPE_BUF: c_long = 512; +pub const _POSIX_RE_DUP_MAX: c_long = 255; +pub const _POSIX_RTSIG_MAX: c_long = 8; +pub const _POSIX_SEM_NSEMS_MAX: c_long = 256; +pub const _POSIX_SEM_VALUE_MAX: c_long = 32767; +pub const _POSIX_SIGQUEUE_MAX: c_long = 32; +pub const _POSIX_SSIZE_MAX: c_long = 32767; +pub const _POSIX_STREAM_MAX: c_long = 8; +pub const _POSIX_SYMLINK_MAX: c_long = 255; +pub const _POSIX_SYMLOOP_MAX: c_long = 8; +pub const _POSIX_THREAD_DESTRUCTOR_ITERATIONS: c_long = 4; +pub const _POSIX_THREAD_KEYS_MAX: c_long = 128; +pub const _POSIX_THREAD_THREADS_MAX: c_long = 64; +pub const _POSIX_TIMER_MAX: c_long = 32; +pub const _POSIX_TTY_NAME_MAX: c_long = 9; +pub const _POSIX_TZNAME_MAX: c_long = 6; + +pub const _POSIX2_BC_BASE_MAX: c_long = 99; +pub const _POSIX2_BC_DIM_MAX: c_long = 2048; +pub const _POSIX2_BC_SCALE_MAX: c_long = 99; +pub const _POSIX2_BC_STRING_MAX: c_long = 1000; +pub const _POSIX2_CHARCLASS_NAME_MAX: c_long = 14; +pub const _POSIX2_COLL_WEIGHTS_MAX: c_long = 2; +pub const _POSIX2_EXPR_NEST_MAX: c_long = 32; +pub const _POSIX2_LINE_MAX: c_long = 2048; +pub const _POSIX2_RE_DUP_MAX: c_long = 255; + +// These symbols must be at least the POSIX values, and sysconf will return the actual value between +// the posix minimum and this maximum. +pub const BC_BASE_MAX: c_long = _POSIX2_BC_BASE_MAX; +pub const BC_DIM_MAX: c_long = _POSIX2_BC_DIM_MAX; +pub const BC_SCALE_MAX: c_long = _POSIX2_BC_SCALE_MAX; +pub const BC_STRING_MAX: c_long = _POSIX2_BC_STRING_MAX; +pub const CHARCLASS_NAME_MAX: c_long = _POSIX2_CHARCLASS_NAME_MAX; +pub const COLL_WEIGHTS_MAX: c_long = _POSIX2_COLL_WEIGHTS_MAX; +pub const EXPR_NEST_MAX: c_long = _POSIX2_EXPR_NEST_MAX; +pub const LINE_MAX: c_long = _POSIX2_LINE_MAX; +pub const RE_DUP_MAX: c_long = _POSIX2_RE_DUP_MAX; +pub const HOST_NAME_MAX: c_long = _POSIX_HOST_NAME_MAX; +pub const LOGIN_NAME_MAX: c_long = 255; +pub const GETENTROPY_MAX: c_long = 256; +pub const LINK_MAX: c_long = 127; +pub const PIPE_BUF: c_long = 4096; +pub const FILESIZEBITS: c_long = 64; +pub const MAX_CANON: c_long = _POSIX_MAX_CANON; +pub const MAX_INPUT: c_long = _POSIX_MAX_INPUT; +pub const SYMLINK_MAX: c_long = _POSIX_SYMLINK_MAX; +pub const POSIX_ALLOC_SIZE_MIN: c_long = 4096; + +pub const PTHREAD_DESTRUCTOR_ITERATIONS: c_long = _POSIX_THREAD_DESTRUCTOR_ITERATIONS; +// TODO: What should this limit be? Both glibc and musl have it as 1024 +pub const PTHREAD_KEYS_MAX: c_long = 4096 * 32; +pub const PTHREAD_STACK_MIN: c_long = 65536; diff --git a/src/header/locale/cbindgen.toml b/src/header/locale/cbindgen.toml index edb2300b9c..14c2225835 100644 --- a/src/header/locale/cbindgen.toml +++ b/src/header/locale/cbindgen.toml @@ -1,8 +1,15 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/locale.h.html +# +# Spec quotations relating to includes: +# - "The header shall define NULL (as described in ) and at least the following as macros:" +sys_includes = ["stddef.h"] include_guard = "_RELIBC_LOCALE_H" -trailer = "#include " +after_includes = """ +#include // for locale_t +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/locale/constants.rs b/src/header/locale/constants.rs new file mode 100644 index 0000000000..0652ba8dad --- /dev/null +++ b/src/header/locale/constants.rs @@ -0,0 +1,16 @@ +use crate::platform::types::c_int; + +pub const LC_COLLATE: c_int = 0; +pub const LC_CTYPE: c_int = 1; +pub const LC_MESSAGES: c_int = 2; +pub const LC_MONETARY: c_int = 3; +pub const LC_NUMERIC: c_int = 4; +pub const LC_TIME: c_int = 5; +pub const LC_ALL: c_int = 6; +pub const LC_COLLATE_MASK: c_int = 0x1; +pub const LC_CTYPE_MASK: c_int = 0x2; +pub const LC_MESSAGES_MASK: c_int = 0x4; +pub const LC_MONETARY_MASK: c_int = 0x8; +pub const LC_NUMERIC_MASK: c_int = 0x10; +pub const LC_TIME_MASK: c_int = 0x20; +pub const LC_ALL_MASK: c_int = 0b111111; diff --git a/src/header/locale/data.rs b/src/header/locale/data.rs new file mode 100644 index 0000000000..128f39a184 --- /dev/null +++ b/src/header/locale/data.rs @@ -0,0 +1,392 @@ +use core::str::FromStr; + +use alloc::{boxed::Box, ffi::CString, string::String, vec::Vec}; + +use super::constants::*; +use crate::platform::types::{c_char, c_int}; + +/// See . +/// this struct is not ordered like in the posix spec for readability +#[repr(C)] +#[derive(Clone)] +pub struct lconv { + pub decimal_point: *mut c_char, + pub thousands_sep: *mut c_char, + pub grouping: *mut c_char, + pub int_curr_symbol: *mut c_char, + pub currency_symbol: *mut c_char, + pub mon_decimal_point: *mut c_char, + pub mon_thousands_sep: *mut c_char, + pub mon_grouping: *mut c_char, + pub positive_sign: *mut c_char, + pub negative_sign: *mut c_char, + pub int_frac_digits: c_char, + pub frac_digits: c_char, + pub p_cs_precedes: c_char, + pub p_sep_by_space: c_char, + pub n_cs_precedes: c_char, + pub n_sep_by_space: c_char, + pub p_sign_posn: c_char, + pub n_sign_posn: c_char, + pub int_p_cs_precedes: c_char, + pub int_p_sep_by_space: c_char, + pub int_n_cs_precedes: c_char, + pub int_n_sep_by_space: c_char, + pub int_p_sign_posn: c_char, + pub int_n_sign_posn: c_char, +} +unsafe impl Sync for lconv {} + +/// "POSIX" or "C" default +pub(crate) const fn posix_lconv() -> lconv { + lconv { + // numeric, non-monetary + decimal_point: c".".as_ptr().cast_mut(), + thousands_sep: c"".as_ptr().cast_mut(), + grouping: c"".as_ptr().cast_mut(), + // local monetary + int_curr_symbol: c"".as_ptr().cast_mut(), + currency_symbol: c"".as_ptr().cast_mut(), + mon_decimal_point: c"".as_ptr().cast_mut(), + mon_thousands_sep: c"".as_ptr().cast_mut(), + mon_grouping: c"".as_ptr().cast_mut(), + positive_sign: c"".as_ptr().cast_mut(), + negative_sign: c"".as_ptr().cast_mut(), + // delimiters, unspecified + int_frac_digits: c_char::MAX, + frac_digits: c_char::MAX, + p_cs_precedes: c_char::MAX, + p_sep_by_space: c_char::MAX, + n_cs_precedes: c_char::MAX, + n_sep_by_space: c_char::MAX, + p_sign_posn: c_char::MAX, + n_sign_posn: c_char::MAX, + // international format + int_p_cs_precedes: c_char::MAX, + int_p_sep_by_space: c_char::MAX, + int_n_cs_precedes: c_char::MAX, + int_n_sep_by_space: c_char::MAX, + int_p_sign_posn: c_char::MAX, + int_n_sign_posn: c_char::MAX, + } +} + +#[repr(C)] +pub(crate) struct LocaleData { + pub name: CString, + pub lconv: lconv, + // Owned memory buffers + pub decimal_point: CString, + pub thousands_sep: CString, + pub grouping: Vec, + pub int_curr_symbol: CString, + pub currency_symbol: CString, + pub mon_decimal_point: CString, + pub mon_thousands_sep: CString, + pub mon_grouping: Vec, + pub positive_sign: CString, + pub negative_sign: CString, +} +unsafe impl Sync for LocaleData {} + +impl LocaleData { + pub fn new(name: CString, defs: PosixLocaleDef) -> Box { + let mut data = Box::new(LocaleData { + name, + decimal_point: Self::to_cstring(defs.decimal_point), + thousands_sep: Self::to_cstring(defs.thousands_sep), + grouping: Self::to_grouping_char(defs.grouping), + int_curr_symbol: Self::to_cstring(defs.int_curr_symbol), + currency_symbol: Self::to_cstring(defs.currency_symbol), + mon_decimal_point: Self::to_cstring(defs.mon_decimal_point), + mon_thousands_sep: Self::to_cstring(defs.mon_thousands_sep), + mon_grouping: Self::to_grouping_char(defs.mon_grouping), + positive_sign: Self::to_cstring(defs.positive_sign), + negative_sign: Self::to_cstring(defs.negative_sign), + lconv: unsafe { core::mem::zeroed() }, + }); + + data.lconv.int_frac_digits = defs.int_frac_digits.unwrap_or(c_char::MAX); + data.lconv.frac_digits = defs.frac_digits.unwrap_or(c_char::MAX); + data.lconv.p_cs_precedes = defs.p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.p_sep_by_space = defs.p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.n_cs_precedes = defs.n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.n_sep_by_space = defs.n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.p_sign_posn = defs.p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.n_sign_posn = defs.n_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_p_cs_precedes = defs.int_p_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_p_sep_by_space = defs.int_p_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_n_cs_precedes = defs.int_n_cs_precedes.unwrap_or(c_char::MAX); + data.lconv.int_n_sep_by_space = defs.int_n_sep_by_space.unwrap_or(c_char::MAX); + data.lconv.int_p_sign_posn = defs.int_p_sign_posn.unwrap_or(c_char::MAX); + data.lconv.int_n_sign_posn = defs.int_n_sign_posn.unwrap_or(c_char::MAX); + + data.update_lconv_pointers(); + data + } + + pub fn posix() -> Box { + LocaleData::new(CString::from_str("C").unwrap(), PosixLocaleDef::default()) + } + + fn update_lconv_pointers(&mut self) { + self.lconv.decimal_point = self.decimal_point.as_ptr().cast_mut(); + self.lconv.thousands_sep = self.thousands_sep.as_ptr().cast_mut(); + self.lconv.grouping = self.grouping.as_ptr().cast_mut(); + self.lconv.int_curr_symbol = self.int_curr_symbol.as_ptr().cast_mut(); + self.lconv.currency_symbol = self.currency_symbol.as_ptr().cast_mut(); + self.lconv.mon_decimal_point = self.mon_decimal_point.as_ptr().cast_mut(); + self.lconv.mon_thousands_sep = self.mon_thousands_sep.as_ptr().cast_mut(); + self.lconv.mon_grouping = self.mon_grouping.as_ptr().cast_mut(); + self.lconv.positive_sign = self.positive_sign.as_ptr().cast_mut(); + self.lconv.negative_sign = self.negative_sign.as_ptr().cast_mut(); + } + + pub fn copy_category(&mut self, other: &Self, category: c_int) { + match category { + LC_NUMERIC => { + self.decimal_point = other.decimal_point.clone(); + self.thousands_sep = other.thousands_sep.clone(); + self.grouping = other.grouping.clone(); + self.lconv.frac_digits = other.lconv.frac_digits; + } + LC_MONETARY => { + self.int_curr_symbol = other.int_curr_symbol.clone(); + self.currency_symbol = other.currency_symbol.clone(); + self.mon_decimal_point = other.mon_decimal_point.clone(); + self.mon_thousands_sep = other.mon_thousands_sep.clone(); + self.mon_grouping = other.mon_grouping.clone(); + self.positive_sign = other.positive_sign.clone(); + self.negative_sign = other.negative_sign.clone(); + self.lconv.int_frac_digits = other.lconv.int_frac_digits; + self.lconv.p_cs_precedes = other.lconv.p_cs_precedes; + self.lconv.p_sep_by_space = other.lconv.p_sep_by_space; + self.lconv.n_cs_precedes = other.lconv.n_cs_precedes; + self.lconv.n_sep_by_space = other.lconv.n_sep_by_space; + self.lconv.p_sign_posn = other.lconv.p_sign_posn; + self.lconv.n_sign_posn = other.lconv.n_sign_posn; + self.lconv.int_p_cs_precedes = other.lconv.int_p_cs_precedes; + self.lconv.int_p_sep_by_space = other.lconv.int_p_sep_by_space; + self.lconv.int_n_cs_precedes = other.lconv.int_n_cs_precedes; + self.lconv.int_n_sep_by_space = other.lconv.int_n_sep_by_space; + self.lconv.int_p_sign_posn = other.lconv.int_p_sign_posn; + self.lconv.int_n_sign_posn = other.lconv.int_n_sign_posn; + } + LC_ALL => { + *self = other.clone(); + } + _ => {} + } + + self.update_lconv_pointers(); + } + + fn to_cstring(opt: Option) -> CString { + opt.unwrap_or_else(|| CString::new("").unwrap()) + } + + fn to_grouping_char(opt: Vec>) -> Vec { + let mut v: Vec = opt.into_iter().map(Self::to_char).collect(); + v.push(0); + v + } + + fn to_char(opt: Option) -> c_char { + opt.unwrap_or(c_char::MAX) + } +} + +impl Clone for LocaleData { + fn clone(&self) -> Self { + let mut data = Self { + name: self.name.clone(), + lconv: self.lconv.clone(), + decimal_point: self.decimal_point.clone(), + thousands_sep: self.thousands_sep.clone(), + grouping: self.grouping.clone(), + int_curr_symbol: self.int_curr_symbol.clone(), + currency_symbol: self.currency_symbol.clone(), + mon_decimal_point: self.mon_decimal_point.clone(), + mon_thousands_sep: self.mon_thousands_sep.clone(), + mon_grouping: self.mon_grouping.clone(), + positive_sign: self.positive_sign.clone(), + negative_sign: self.negative_sign.clone(), + }; + data.update_lconv_pointers(); + data + } +} + +#[derive(Clone)] +pub(crate) struct GlobalLocaleData { + // names per LC_* constant + pub names: [CString; 7], + pub data: LocaleData, +} + +impl GlobalLocaleData { + pub fn new() -> Box { + let data = LocaleData::posix(); + let names = [ + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + data.name.clone(), + ]; + let mut r = Box::new(GlobalLocaleData { data: *data, names }); + r.data.update_lconv_pointers(); + r + } + + pub fn get_name(&self, category: i32) -> Option<&CString> { + self.names.get(category as usize) + } + pub fn set_name(&mut self, category: i32, name: CString) -> Option<&CString> { + if self.names.get(category as usize).is_some() { + self.names[category as usize] = name; + self.names.get(category as usize) + } else { + None + } + } +} +unsafe impl Sync for GlobalLocaleData {} + +#[derive(Default)] +pub(crate) struct PosixLocaleDef { + pub decimal_point: Option, + pub thousands_sep: Option, + pub grouping: Vec>, + pub int_curr_symbol: Option, + pub currency_symbol: Option, + pub mon_decimal_point: Option, + pub mon_thousands_sep: Option, + pub mon_grouping: Vec>, + pub positive_sign: Option, + pub negative_sign: Option, + pub int_frac_digits: Option, + pub frac_digits: Option, + pub p_cs_precedes: Option, + pub p_sep_by_space: Option, + pub n_cs_precedes: Option, + pub n_sep_by_space: Option, + pub p_sign_posn: Option, + pub n_sign_posn: Option, + pub int_p_cs_precedes: Option, + pub int_p_sep_by_space: Option, + pub int_n_cs_precedes: Option, + pub int_n_sep_by_space: Option, + pub int_p_sign_posn: Option, + pub int_n_sign_posn: Option, +} +impl PosixLocaleDef { + //! See + pub fn parse(content: &str) -> Self { + let mut locale = PosixLocaleDef::default(); + + let mut lines = content.lines(); + loop { + let Some(line) = lines.next() else { + break; + }; + + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + + let mut parts = trimmed.split_ascii_whitespace(); + let Some(key) = parts.next() else { + continue; + }; + let mut val: String = String::new(); + loop { + let Some(chunk) = parts.next() else { + break; + }; + if !chunk.ends_with('\\') { + val.push_str(chunk); + break; + } + // multiline values + val.push_str(&chunk[0..chunk.len() - 1]); + let Some(next_line) = lines.next() else { + break; + }; + parts = next_line.split_ascii_whitespace(); + } + + if key.is_empty() || val.is_empty() { + continue; + } + + match key { + "decimal_point" => locale.decimal_point = Self::parse_str(&val), + "thousands_sep" => locale.thousands_sep = Self::parse_str(&val), + "int_curr_symbol" => locale.int_curr_symbol = Self::parse_str(&val), + "currency_symbol" => locale.currency_symbol = Self::parse_str(&val), + "mon_decimal_point" => locale.mon_decimal_point = Self::parse_str(&val), + "mon_thousands_sep" => locale.mon_thousands_sep = Self::parse_str(&val), + "positive_sign" => locale.positive_sign = Self::parse_str(&val), + "negative_sign" => locale.negative_sign = Self::parse_str(&val), + "grouping" => locale.grouping = Self::parse_int_group(&val), + "mon_grouping" => locale.mon_grouping = Self::parse_int_group(&val), + "int_frac_digits" => locale.int_frac_digits = Self::parse_int(&val), + "frac_digits" => locale.frac_digits = Self::parse_int(&val), + "p_cs_precedes" => locale.p_cs_precedes = Self::parse_int(&val), + "p_sep_by_space" => locale.p_sep_by_space = Self::parse_int(&val), + "n_cs_precedes" => locale.n_cs_precedes = Self::parse_int(&val), + "n_sep_by_space" => locale.n_sep_by_space = Self::parse_int(&val), + "p_sign_posn" => locale.p_sign_posn = Self::parse_int(&val), + "n_sign_posn" => locale.n_sign_posn = Self::parse_int(&val), + "int_p_cs_precedes" => locale.int_p_cs_precedes = Self::parse_int(&val), + "int_p_sep_by_space" => locale.int_p_sep_by_space = Self::parse_int(&val), + "int_n_cs_precedes" => locale.int_n_cs_precedes = Self::parse_int(&val), + "int_n_sep_by_space" => locale.int_n_sep_by_space = Self::parse_int(&val), + "int_p_sign_posn" => locale.int_p_sign_posn = Self::parse_int(&val), + "int_n_sign_posn" => locale.int_n_sign_posn = Self::parse_int(&val), + _ => {} + } + } + locale + } + + /// parse e.g. `3;3;0` -> [ 3,3,0 ], `-1` -> [ None ] + fn parse_int_group(val: &str) -> Vec> { + val.split(';').map(Self::parse_int).collect() + } + + /// parse e.g. `-1` -> None, `1` -> Some(1) + fn parse_int(val: &str) -> Option { + let r = val.trim().parse::().ok(); + // c_char is u8 on aarch64 and riscv64 so comparison is useless + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + if r.is_some_and(|i| i < 0) { + return None; + } + r + } + + /// parse e.g. `""` + fn parse_str(val: &str) -> Option { + let mut r = String::new(); + let mut v = val.chars(); + if v.next() != Some('"') { + return None; + } + while let Some(c) = v.next() { + if c == '"' { + if v.next().is_some() { + return None; + } + break; + } + // TODO: Parse <..> + r.push(c); + } + CString::new(r).ok() + } +} diff --git a/src/header/locale/mod.rs b/src/header/locale/mod.rs index 7697c446d0..d8f9e54bd3 100644 --- a/src/header/locale/mod.rs +++ b/src/header/locale/mod.rs @@ -1,68 +1,182 @@ -//! locale implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/locale.h.html +//! `locale.h` implementation. +//! +//! See . -use core::ptr; +use alloc::{boxed::Box, ffi::CString, string::String}; +use core::{ptr, str::FromStr}; -use crate::platform::types::*; +use crate::{ + c_str::CStr, + error::{Errno, ResultExtPtrMut}, + fs::File, + header::{errno, fcntl}, + io::Read, + platform::types::{c_char, c_int}, +}; -const EMPTY_PTR: *const c_char = "\0" as *const _ as *const c_char; // Can't use &str because of the mutability static mut C_LOCALE: [c_char; 2] = [b'C' as c_char, 0]; -#[repr(C)] -#[no_mangle] -pub struct lconv { - currency_symbol: *const c_char, - decimal_point: *const c_char, - frac_digits: c_char, - grouping: *const c_char, - int_curr_symbol: *const c_char, - int_frac_digits: c_char, - mon_decimal_point: *const c_char, - mon_grouping: *const c_char, - mon_thousands_sep: *const c_char, - negative_sign: *const c_char, - n_cs_precedes: c_char, - n_sep_by_space: c_char, - n_sign_posn: c_char, - positive_sign: *const c_char, - p_cs_precedes: c_char, - p_sep_by_space: c_char, - p_sign_posn: c_char, - thousands_sep: *const c_char, -} -unsafe impl Sync for lconv {} +mod constants; +use constants::*; +mod data; +use data::*; -static CURRENT_LOCALE: lconv = lconv { - currency_symbol: EMPTY_PTR, - decimal_point: ".\0" as *const _ as *const c_char, - frac_digits: c_char::max_value(), - grouping: EMPTY_PTR, - int_curr_symbol: EMPTY_PTR, - int_frac_digits: c_char::max_value(), - mon_decimal_point: EMPTY_PTR, - mon_grouping: EMPTY_PTR, - mon_thousands_sep: EMPTY_PTR, - negative_sign: EMPTY_PTR, - n_cs_precedes: c_char::max_value(), - n_sep_by_space: c_char::max_value(), - n_sign_posn: c_char::max_value(), - positive_sign: EMPTY_PTR, - p_cs_precedes: c_char::max_value(), - p_sep_by_space: c_char::max_value(), - p_sign_posn: c_char::max_value(), - thousands_sep: EMPTY_PTR, -}; +use super::bits_locale_t::locale_t; +/// constant struct to "C" or "POSIX" locale +/// mutable because POSIX demands a mutable pointer +static mut POSIX_LOCALE: lconv = posix_lconv(); +pub const LC_GLOBAL_LOCALE: locale_t = -1isize as locale_t; +/// process-wide locale, used by setlocale() and localeconv() +static mut GLOBAL_LOCALE: *mut GlobalLocaleData = ptr::null_mut(); +/// thread-wide locale, used by uselocale() and localeconv() +#[thread_local] +pub(crate) static mut THREAD_LOCALE: *mut LocaleData = ptr::null_mut(); -#[no_mangle] -pub extern "C" fn localeconv() -> *const lconv { - &CURRENT_LOCALE as *const _ -} - -#[no_mangle] -pub unsafe extern "C" fn setlocale(_option: c_int, val: *const c_char) -> *mut c_char { - if val.is_null() { - return C_LOCALE.as_mut_ptr() as *mut c_char; +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn localeconv() -> *mut lconv { + let current = unsafe { uselocale(ptr::null_mut()) }; + if current == LC_GLOBAL_LOCALE || current.is_null() { + if !unsafe { GLOBAL_LOCALE.is_null() } { + // safety: GLOBAL_LOCALE is never set to null again + unsafe { &raw mut (*GLOBAL_LOCALE).data.lconv } + } else { + &raw mut POSIX_LOCALE + } + } else { + let current = current.cast::(); + unsafe { &raw mut (*current).lconv } } - // TODO actually implement - ptr::null_mut() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setlocale(category: c_int, locale: *const c_char) -> *mut c_char { + if unsafe { GLOBAL_LOCALE.is_null() } { + let new_global = GlobalLocaleData::new(); + unsafe { GLOBAL_LOCALE = Box::into_raw(new_global) }; + }; + let Some(global) = (unsafe { GLOBAL_LOCALE.as_mut() }) else { + return ptr::null_mut(); + }; + + if locale.is_null() { + let Some(name) = global.get_name(category) else { + return ptr::null_mut(); + }; + return name.as_ptr().cast_mut(); + } + + let name = unsafe { CStr::from_ptr(locale).to_str().unwrap_or("C") }; + + let locale_file = if name.is_empty() || name == "C" || name == "POSIX" { + // TODO: name == "" should read from LANG env + Ok(LocaleData::posix()) + } else { + load_locale_file(name) + }; + + match locale_file { + Ok(loc_ptr) => { + global.data.copy_category(&loc_ptr, category); + let Some(name) = global.set_name(category, CString::from_str(name).unwrap()) else { + return ptr::null_mut(); + }; + name.as_ptr().cast_mut() + } + Err(_) => ptr::null_mut(), + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn uselocale(newloc: locale_t) -> locale_t { + let old_loc = if unsafe { THREAD_LOCALE.is_null() } { + LC_GLOBAL_LOCALE + } else { + (unsafe { THREAD_LOCALE }) as locale_t + }; + + if !newloc.is_null() { + unsafe { + THREAD_LOCALE = if newloc == LC_GLOBAL_LOCALE { + ptr::null_mut() + } else { + newloc.cast::() + } + }; + } + + old_loc +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn newlocale(mask: c_int, locale: *const c_char, base: locale_t) -> locale_t { + let name = unsafe { CStr::from_ptr(locale) } + .to_string_lossy() + .into_owned(); + let name = name.as_str(); + let mut new_locale = if name.is_empty() || name == "C" || name == "POSIX" { + // TODO: name == "" should read from LANG env + Ok(LocaleData::posix()) + } else { + load_locale_file(name) + }; + if base != LC_GLOBAL_LOCALE { + // borrowing here + let base = base.cast_const().cast::(); + if let Ok(new_locale) = new_locale.as_mut() + && let Some(base) = unsafe { base.as_ref() } + { + // copy old values if not containing the mask + if (mask & LC_NUMERIC_MASK) == 0 { + new_locale.copy_category(base, LC_NUMERIC); + } + if (mask & LC_MONETARY_MASK) == 0 { + new_locale.copy_category(base, LC_MONETARY); + } + // TODO: other categories? + } + } + new_locale.or_errno_null_mut().cast() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn freelocale(loc: locale_t) { + if !loc.is_null() && loc != LC_GLOBAL_LOCALE { + drop(unsafe { Box::from_raw(loc.cast::()) }); + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn duplocale(loc: locale_t) -> locale_t { + if loc.is_null() { + // TODO: errno? + loc + } else if loc == LC_GLOBAL_LOCALE { + Box::into_raw(LocaleData::posix()) as locale_t + } else { + // borrowing here + let loc = loc.cast_const().cast::(); + Box::into_raw(unsafe { Box::from((*loc).clone()) }) as locale_t + } +} + +pub(crate) fn load_locale_file(name: &str) -> Result, Errno> { + let mut path = String::from("/usr/share/i18n/locales/"); + path.push_str(name); + + let path_c = CString::new(path).map_err(|_| Errno(errno::EINVAL))?; + let mut content = String::new(); + + let mut file = File::open(path_c.as_c_str().into(), fcntl::O_RDONLY)?; + file.read_to_string(&mut content) + .map_err(|_| Errno(errno::EIO))?; + + let toml = PosixLocaleDef::parse(&content); + Ok(LocaleData::new(CString::from_str(name).unwrap(), toml)) } diff --git a/include/malloc.h b/src/header/malloc/cbindgen.toml similarity index 52% rename from include/malloc.h rename to src/header/malloc/cbindgen.toml index 9826495495..18db86526d 100644 --- a/include/malloc.h +++ b/src/header/malloc/cbindgen.toml @@ -1,10 +1,8 @@ -#ifndef _MALLOC_H -#define _MALLOC_H - -#include - -// Generated from: -// `grep "malloc\|calloc\|realloc\|free\|valloc\|memalign" target/include/stdlib.h` +sys_includes = ["features.h", "stddef.h"] +include_guard = "_RELIBC_MALLOC_H" +trailer = """ +#ifndef _RELIBC_MALLOC_EXTRA_H +#define _RELIBC_MALLOC_EXTRA_H #ifdef __cplusplus extern "C" { @@ -18,7 +16,15 @@ void *realloc(void *ptr, size_t size); void *valloc(size_t size); #ifdef __cplusplus -} // extern "C" +} #endif #endif +""" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/malloc/mod.rs b/src/header/malloc/mod.rs new file mode 100644 index 0000000000..2b73052b42 --- /dev/null +++ b/src/header/malloc/mod.rs @@ -0,0 +1,48 @@ +//! `malloc.h` implementation. +//! +//! Non-POSIX, see . + +use crate::{ + header::errno::ENOMEM, + platform::{ + self, Pal, Sys, + types::{c_void, size_t}, + }, +}; +use core::ptr; + +/// See . +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pvalloc(size: size_t) -> *mut c_void { + let page_size = Sys::getpagesize(); + // Find the smallest multiple of the page size in which the requested size + // will fit. The result of the division will always be less than or equal + // to size_t::MAX - 1, and the num_pages calculation will therefore never + // overflow. + let num_pages = if size != 0 { + (size - 1) / page_size + 1 + } else { + 0 + }; + + match num_pages.checked_mul(page_size) { + Some(alloc_size) => { + let ptr = unsafe { platform::alloc_align(alloc_size, page_size) }; + if ptr.is_null() { + platform::ERRNO.set(ENOMEM); + } + ptr + } + None => { + platform::ERRNO.set(ENOMEM); + ptr::null_mut() + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn malloc_usable_size(ptr: *mut c_void) -> size_t { + unsafe { platform::alloc_usable_size(ptr) } +} diff --git a/src/header/math/cbindgen.toml b/src/header/math/cbindgen.toml new file mode 100644 index 0000000000..97345d7770 --- /dev/null +++ b/src/header/math/cbindgen.toml @@ -0,0 +1,57 @@ +# Note: changing this file requires `make clean`. +sys_includes = [] +include_guard = "_RELIBC_MATH_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true +# constants from openlibm +after_includes = """ + +#define HUGE_VALF (float)HUGE_VAL +#define HUGE_VALL (long double)HUGE_VAL +#define INFINITY HUGE_VALF +#define NAN (__nan.__uf) + +#define FP_ILOGB0 (-INT_MAX) +#define FP_ILOGBNAN INT_MAX + +#define MAXFLOAT 3.40282346638528859812e+38F + +#define M_E 2.7182818284590452354 /* e */ +#define M_LOG2E 1.4426950408889634074 /* log_2 e */ +#define M_LOG10E 0.43429448190325182765 /* log_10 e */ +#define M_LN2 0.69314718055994530942 /* log_e 2 */ +#define M_LN10 2.30258509299404568402 /* log_e 10 */ +#define M_PI 3.14159265358979323846 /* pi */ +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#define M_PI_4 0.78539816339744830962 /* pi/4 */ +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ + +#define FP_NAN 0 +#define FP_INFINITE 1 +#define FP_ZERO 2 +#define FP_SUBNORMAL 3 +#define FP_NORMAL 4 +#define fpclassify(x) __builtin_fpclassify(FP_NAN, FP_INFINITE, FP_NORMAL, FP_SUBNORMAL, FP_ZERO, x) + +#define signbit(x) __builtin_signbit(x) +#define isnan(x) __builtin_isnan(x) +#define isinf(x) __builtin_isinf_sign(x) +#define isfinite(x) __builtin_isfinite(x) +#define isnormal(x) __builtin_isnormal(x) +#define isgreater(x, y) __builtin_isgreater((x), (y)) +#define isgreaterequal(x, y) __builtin_isgreaterequal((x), (y)) +#define isless(x, y) __builtin_isless((x), (y)) +#define islessequal(x, y) __builtin_islessequal((x), (y)) +#define islessgreater(x, y) __builtin_islessgreater((x), (y)) +#define isunordered(x, y) __builtin_isunordered((x), (y)) + +""" + +[enum] +prefix_with_name = true diff --git a/src/header/math/mod.rs b/src/header/math/mod.rs new file mode 100644 index 0000000000..b529bd2633 --- /dev/null +++ b/src/header/math/mod.rs @@ -0,0 +1,867 @@ +//! `math.h` implementation. +//! +//! See . + +use crate::platform::types::{c_double, c_float, c_int, c_long, c_longlong}; + +// TODO move constants from cbindgen (openlibm) +// TODO all is* function is defined as "real-floating", so it can both f32 and f64 +// TODO cover edge cases (EDOM and ERANGE) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn acos(x: c_double) -> c_double { + libm::acos(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn acosf(x: c_float) -> c_float { + libm::acosf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn acosh(x: c_double) -> c_double { + libm::acosh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn acoshf(x: c_float) -> c_float { + libm::acoshf(x) +} + +// TODO acoshl, acosl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn asin(x: c_double) -> c_double { + libm::asin(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn asinf(x: c_float) -> c_float { + libm::asinf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn asinh(x: c_double) -> c_double { + libm::asinh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn asinhf(x: c_float) -> c_float { + libm::asinhf(x) +} + +// TODO asinhl, asinl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atan(x: c_double) -> c_double { + libm::atan(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atan2(y: c_double, x: c_double) -> c_double { + libm::atan2(y, x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atan2f(y: c_float, x: c_float) -> c_float { + libm::atan2f(y, x) +} + +// TODO atan2l (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atanf(x: c_float) -> c_float { + libm::atanf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atanh(x: c_double) -> c_double { + libm::atanh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atanhf(x: c_float) -> c_float { + libm::atanhf(x) +} + +// TODO atanhl, atanl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbrt(x: c_double) -> c_double { + libm::cbrt(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbrtf(x: c_float) -> c_float { + libm::cbrtf(x) +} + +// TODO cbrtl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ceil(x: c_double) -> c_double { + libm::ceil(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ceilf(x: c_float) -> c_float { + libm::ceilf(x) +} + +// TODO ceill (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn copysign(x: c_double, y: c_double) -> c_double { + libm::copysign(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn copysignf(x: c_float, y: c_float) -> c_float { + libm::copysignf(x, y) +} + +// TODO copysignl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cos(x: c_double) -> c_double { + libm::cos(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cosf(x: c_float) -> c_float { + libm::cosf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cosh(x: c_double) -> c_double { + libm::cosh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn coshf(x: c_float) -> c_float { + libm::coshf(x) +} + +// TODO coshl, cosl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn erf(x: c_double) -> c_double { + libm::erf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn erfc(x: c_double) -> c_double { + libm::erfc(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn erfcf(x: c_float) -> c_float { + libm::erfcf(x) +} + +// TODO erfcl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn erff(x: c_float) -> c_float { + libm::erff(x) +} + +// TODO erfl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn exp(x: c_double) -> c_double { + libm::exp(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn exp2(x: c_double) -> c_double { + libm::exp2(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn exp2f(x: c_float) -> c_float { + libm::exp2f(x) +} + +// TODO exp2l (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn expf(x: c_float) -> c_float { + libm::expf(x) +} + +// TODO expl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn expm1(x: c_double) -> c_double { + libm::expm1(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn expm1f(x: c_float) -> c_float { + libm::expm1f(x) +} + +// TODO expm1l (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fabs(x: c_double) -> c_double { + libm::fabs(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fabsf(x: c_float) -> c_float { + libm::fabsf(x) +} + +// TODO fabsl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fdim(x: c_double, y: c_double) -> c_double { + libm::fdim(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fdimf(x: c_float, y: c_float) -> c_float { + libm::fdimf(x, y) +} + +// TODO fdiml (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn floor(x: c_double) -> c_double { + libm::floor(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn floorf(x: c_float) -> c_float { + libm::floorf(x) +} + +// TODO floorl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fma(x: c_double, y: c_double, z: c_double) -> c_double { + libm::fma(x, y, z) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmaf(x: c_float, y: c_float, z: c_float) -> c_float { + libm::fmaf(x, y, z) +} + +// TODO fmal (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmax(x: c_double, y: c_double) -> c_double { + libm::fmax(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmaxf(x: c_float, y: c_float) -> c_float { + libm::fmaxf(x, y) +} + +// TODO fmaxl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmin(x: c_double, y: c_double) -> c_double { + libm::fmin(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fminf(x: c_float, y: c_float) -> c_float { + libm::fminf(x, y) +} + +// TODO fminl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmod(x: c_double, y: c_double) -> c_double { + libm::fmod(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fmodf(x: c_float, y: c_float) -> c_float { + libm::fmodf(x, y) +} + +// TODO fmodl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn frexp(num: c_double, exp: *mut c_int) -> c_double { + let (number, exponent) = libm::frexp(num); + unsafe { + *exp = exponent; + } + number +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn frexpf(num: c_float, exp: *mut c_int) -> c_float { + let (number, exponent) = libm::frexpf(num); + unsafe { + *exp = exponent; + } + number +} + +// TODO frexpl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn hypot(x: c_double, y: c_double) -> c_double { + libm::hypot(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn hypotf(x: c_float, y: c_float) -> c_float { + libm::hypotf(x, y) +} + +// TODO hypotl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ilogb(x: c_double) -> c_int { + libm::ilogb(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ilogbf(x: c_float) -> c_int { + libm::ilogbf(x) +} + +// TODO ilogbl (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn isfinite(x: c_double) -> c_int { + if x.is_finite() { 1 } else { 0 } +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn isinf(x: c_double) -> c_int { + match x { + f64::INFINITY => 1, + f64::NEG_INFINITY => -1, + _ => 0, + } +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn isnan(x: c_double) -> c_int { + if x.is_nan() { 1 } else { 0 } +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn isnormal(x: c_double) -> c_int { + if x.is_normal() { 1 } else { 0 } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn j0(x: c_double) -> c_double { + libm::j0(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn j1(x: c_double) -> c_double { + libm::j1(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn jn(n: c_int, x: c_double) -> c_double { + libm::jn(n, x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ldexp(x: c_double, exp: c_int) -> c_double { + libm::ldexp(x, exp) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ldexpf(x: c_float, exp: c_int) -> c_float { + libm::ldexpf(x, exp) +} + +// TODO ldexpl (long double) + +#[unsafe(no_mangle)] +static mut signgam: c_int = 0; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lgamma(x: c_double) -> c_double { + let (a, b) = libm::lgamma_r(x); + unsafe { signgam = b }; + a +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lgammaf(x: c_float) -> c_float { + let (a, b) = libm::lgammaf_r(x); + unsafe { signgam = b }; + a +} + +// TODO lgammal (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn llrint(x: c_double) -> c_longlong { + libm::rint(x) as _ +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn llrintf(x: c_float) -> c_longlong { + libm::rintf(x) as _ +} + +// TODO llrintl (long double to c_longlong) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn llround(x: c_double) -> c_longlong { + libm::round(x) as _ +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn llroundf(x: c_float) -> c_longlong { + libm::roundf(x) as _ +} + +// TODO llroundl (long double to c_longlong) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log(x: c_double) -> c_double { + libm::log(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log10(x: c_double) -> c_double { + libm::log10(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log10f(x: c_float) -> c_float { + libm::log10f(x) +} + +// TODO log10l (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log1p(x: c_double) -> c_double { + libm::log1p(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log1pf(x: c_float) -> c_float { + libm::log1pf(x) +} + +// TODO log1pl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log2(x: c_double) -> c_double { + libm::log2(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn log2f(x: c_float) -> c_float { + libm::log2f(x) +} + +// TODO log2l (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn logb(x: c_double) -> c_double { + // TODO: errno + libm::ilogb(x) as _ +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn logbf(x: c_float) -> c_float { + // TODO: errno + libm::ilogbf(x) as _ +} + +// TODO logbl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn logf(x: c_float) -> c_float { + libm::logf(x) +} + +// TODO logl (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn lrint(x: c_double) -> c_long { + // TODO: errno + libm::rint(x) as _ +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn lrintf(x: c_float) -> c_long { + // TODO: errno + libm::rintf(x) as _ +} + +// TODO lrintl (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn lround(x: c_double) -> c_long { + libm::round(x) as _ +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn lroundf(x: c_float) -> c_long { + libm::roundf(x) as _ +} + +// TODO lroundl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn modf(x: c_double, iptr: *mut c_double) -> c_double { + let (integral, fractional) = libm::modf(x); + unsafe { + *iptr = integral; + } + fractional +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn modff(value: c_float, iptr: *mut c_float) -> c_float { + let (integral, fractional) = libm::modff(value); + unsafe { + *iptr = integral; + } + fractional +} + +// TODO modfl (long double) + +// TODO do we want to support quiet NaN or just return 0? +// TODO nan (c_double) +// TODO nanf (c_float) +// TODO nanl (long double) + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn nearbyint(x: c_double) -> c_double { + libm::rint(x) +} + +/// See . +#[unsafe(no_mangle)] +unsafe extern "C" fn nearbyintf(x: c_float) -> c_float { + libm::rintf(x) +} + +// TODO nearbyintl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nextafter(x: c_double, y: c_double) -> c_double { + libm::nextafter(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nextafterf(x: c_float, y: c_float) -> c_float { + libm::nextafterf(x, y) +} + +// TODO nextafterl (long double) + +// TODO nexttoward (c_double) +// TODO nexttowardf (c_float) +// TODO nexttowardl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pow(x: c_double, y: c_double) -> c_double { + libm::pow(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn powf(x: c_float, y: c_float) -> c_float { + libm::powf(x, y) +} + +// TODO powl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remainder(x: c_double, y: c_double) -> c_double { + libm::remainder(x, y) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remainderf(x: c_float, y: c_float) -> c_float { + libm::remainderf(x, y) +} + +// TODO remainderl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remquo(x: c_double, y: c_double, quo: *mut c_int) -> c_double { + let (remainder, quotient) = libm::remquo(x, y); + unsafe { + *quo = quotient; + } + remainder +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn remquof(x: c_float, y: c_float, quo: *mut c_int) -> c_float { + let (remainder, quotient) = libm::remquof(x, y); + unsafe { + *quo = quotient; + } + remainder +} + +// TODO remquol (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rint(x: c_double) -> c_double { + libm::rint(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rintf(x: c_float) -> c_float { + libm::rintf(x) +} + +// TODO rintl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn round(x: c_double) -> c_double { + libm::round(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn roundf(x: c_float) -> c_float { + libm::roundf(x) +} + +// TODO roundl (long double) + +// TODO scalbln (c_double, c_long) +// TODO scalblnf (c_float, c_long) +// TODO scalblnl (long double, c_long) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn scalbn(x: c_double, n: c_int) -> c_double { + libm::scalbn(x, n) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn scalbnf(x: c_float, n: c_int) -> c_float { + libm::scalbnf(x, n) +} + +// TODO scalbnl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sin(x: c_double) -> c_double { + libm::sin(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sinf(x: c_float) -> c_float { + libm::sinf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sinh(x: c_double) -> c_double { + libm::sinh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sinhf(x: c_float) -> c_float { + libm::sinhf(x) +} + +// TODO sinhl (long double) +// TODO sinl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sqrt(x: c_double) -> c_double { + libm::sqrt(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sqrtf(x: c_float) -> c_float { + libm::sqrtf(x) +} + +// TODO sqrtl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tan(x: c_double) -> c_double { + libm::tan(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tanf(x: c_float) -> c_float { + libm::tanf(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tanh(x: c_double) -> c_double { + libm::tanh(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tanhf(x: c_float) -> c_float { + libm::tanhf(x) +} + +// TODO tanhl (long double) +// TODO tanl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tgamma(x: c_double) -> c_double { + libm::tgamma(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tgammaf(x: c_float) -> c_float { + libm::tgammaf(x) +} + +// TODO tgammal (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn trunc(x: c_double) -> c_double { + libm::trunc(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn truncf(x: c_float) -> c_float { + libm::truncf(x) +} + +// TODO truncl (long double) + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn y0(x: c_double) -> c_double { + libm::y0(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn y1(x: c_double) -> c_double { + libm::y1(x) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn yn(n: c_int, x: c_double) -> c_double { + libm::yn(n, x) +} diff --git a/src/header/mod.rs b/src/header/mod.rs index 4b788ce72f..49e5e52ce6 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -1,65 +1,155 @@ +//! POSIX header implementations. + pub mod _aio; pub mod _fenv; pub mod arpa_inet; pub mod assert; +pub mod bits_arpainet; +pub mod bits_eventfd; +pub mod bits_iovec; +#[path = "bits_locale-t/mod.rs"] +pub mod bits_locale_t; +pub mod bits_null; +pub mod bits_pthread; +#[path = "bits_safamily-t/mod.rs"] +pub mod bits_safamily_t; +#[path = "bits_sigset-t/mod.rs"] +pub mod bits_sigset_t; +#[path = "bits_size-t/mod.rs"] +pub mod bits_size_t; +#[path = "bits_socklen-t/mod.rs"] +pub mod bits_socklen_t; +pub mod bits_timespec; +#[path = "bits_wchar-t/mod.rs"] +pub mod bits_wchar_t; +// complex.h implemented in C +pub mod cpio; +pub mod crypt; pub mod ctype; +// TODO: curses.h (deprecated) +// TODO: devctl.h pub mod dirent; #[path = "dl-tls/mod.rs"] pub mod dl_tls; pub mod dlfcn; pub mod elf; +pub mod endian; +pub mod err; pub mod errno; pub mod fcntl; pub mod float; +// TODO: fmtmsg.h pub mod fnmatch; +// TODO: ftw.h pub mod getopt; +pub mod glob; pub mod grp; +// TODO: iconv.h +pub mod ifaddrs; pub mod inttypes; +// iso646.h implemented in C +pub mod langinfo; +// TODO: libintl.h pub mod libgen; pub mod limits; pub mod locale; +pub mod malloc; +// TODO unfinished, unguard feature when ready +#[cfg(feature = "math_libm")] +pub mod math; +pub mod monetary; +// TODO: mqueue.h +// TODO: ndbm.h +pub mod net_if; pub mod netdb; pub mod netinet_in; pub mod netinet_ip; pub mod netinet_tcp; +// TODO: nl_types.h +// TODO: Remove C header paths.h when cbindgen can export C/Rust strs +// pub mod paths; pub mod poll; +pub mod pthread; +pub mod pty; pub mod pwd; +// TODO: re_comp.h (deprecated) pub mod regex; +// TODO: regexp.h (deprecated) +pub mod sched; +// TODO: search.h pub mod semaphore; pub mod setjmp; pub mod sgtty; +pub mod shadow; pub mod signal; +pub mod spawn; +// TODO: stdalign.h (likely C implementation) +// stdarg.h implemented in C +// stdatomic.h implemented in C +// stdbool.h implemented in C +pub mod stddef; +// stdint.h implemented in C pub mod stdio; pub mod stdlib; +// TODO: stdnoreturn.h (likely C implementation) pub mod string; pub mod strings; +// TODO: stropts.h (deprecated) pub mod sys_auxv; pub mod sys_epoll; +pub mod sys_eventfd; pub mod sys_file; pub mod sys_ioctl; +// TODO: sys/ipc.h pub mod sys_mman; +// TODO: sys/msg.h pub mod sys_ptrace; pub mod sys_resource; pub mod sys_select; +// TODO: sys/sem.h +// TODO: sys/shm.h pub mod sys_socket; pub mod sys_stat; pub mod sys_statvfs; +#[allow(non_upper_case_globals)] +pub mod sys_syscall; pub mod sys_time; +#[deprecated] pub mod sys_timeb; //pub mod sys_times; -pub mod _wctype; pub mod arch_aarch64_user; +pub mod arch_riscv64_user; pub mod arch_x64_user; +pub mod sys_signalfd; +#[cfg(not(target_arch = "x86"))] // TODO: x86 pub mod sys_procfs; pub mod sys_random; +pub mod sys_timerfd; +pub mod sys_syslog; pub mod sys_types; +#[allow(non_camel_case_types)] +pub mod sys_types_internal; pub mod sys_uio; pub mod sys_un; pub mod sys_utsname; pub mod sys_wait; +pub mod tar; +// TODO: term.h (deprecated) pub mod termios; +// TODO: tgmath.h (likely C implementation) +// TODO: threads.h pub mod time; +// TODO: uchar.h +// TODO: ucontext.h (deprecated) +// TODO: ulimit.h (deprecated) +// TODO: unctrl.h (deprecated) pub mod unistd; +#[deprecated] pub mod utime; +pub mod utmp; +// TODO: utmpx.h +// TODO: varargs.h (deprecated) pub mod wchar; pub mod wctype; +// TODO: wordexp.h +// TODO: xti.h (deprecated) diff --git a/src/header/mod.rs.orig b/src/header/mod.rs.orig new file mode 100644 index 0000000000..c078fbe1c8 --- /dev/null +++ b/src/header/mod.rs.orig @@ -0,0 +1,150 @@ +//! POSIX header implementations. + +pub mod _aio; +pub mod _fenv; +pub mod arpa_inet; +pub mod assert; +pub mod bits_arpainet; +pub mod bits_eventfd; +pub mod bits_iovec; +#[path = "bits_locale-t/mod.rs"] +pub mod bits_locale_t; +pub mod bits_pthread; +#[path = "bits_safamily-t/mod.rs"] +pub mod bits_safamily_t; +#[path = "bits_sigset-t/mod.rs"] +pub mod bits_sigset_t; +#[path = "bits_socklen-t/mod.rs"] +pub mod bits_socklen_t; +pub mod bits_timespec; +// complex.h implemented in C +pub mod cpio; +pub mod crypt; +pub mod ctype; +// TODO: curses.h (deprecated) +// TODO: devctl.h +pub mod dirent; +#[path = "dl-tls/mod.rs"] +pub mod dl_tls; +pub mod dlfcn; +pub mod elf; +pub mod endian; +pub mod err; +pub mod errno; +pub mod fcntl; +pub mod float; +// TODO: fmtmsg.h +pub mod fnmatch; +// TODO: ftw.h +pub mod getopt; +pub mod glob; +pub mod grp; +// TODO: iconv.h +pub mod ifaddrs; +pub mod inttypes; +// iso646.h implemented in C +pub mod langinfo; +// TODO: libintl.h +pub mod libgen; +pub mod limits; +pub mod locale; +pub mod malloc; +// TODO unfinished, unguard feature when ready +#[cfg(feature = "math_libm")] +pub mod math; +pub mod monetary; +// TODO: mqueue.h +// TODO: ndbm.h +pub mod net_if; +pub mod netdb; +pub mod netinet_in; +pub mod netinet_ip; +pub mod netinet_tcp; +// TODO: nl_types.h +// TODO: Remove C header paths.h when cbindgen can export C/Rust strs +// pub mod paths; +pub mod poll; +pub mod pthread; +pub mod pty; +pub mod pwd; +// TODO: re_comp.h (deprecated) +pub mod regex; +// TODO: regexp.h (deprecated) +pub mod sched; +// TODO: search.h +pub mod semaphore; +pub mod setjmp; +pub mod sgtty; +pub mod shadow; +pub mod signal; +pub mod spawn; +// TODO: stdalign.h (likely C implementation) +// stdarg.h implemented in C +// stdatomic.h implemented in C +// stdbool.h implemented in C +// stddef.h implemented in C +// stdint.h implemented in C +pub mod stdio; +pub mod stdlib; +// TODO: stdnoreturn.h (likely C implementation) +pub mod string; +pub mod strings; +// TODO: stropts.h (deprecated) +pub mod sys_auxv; +pub mod sys_epoll; +pub mod sys_eventfd; +pub mod sys_file; +pub mod sys_ioctl; +// TODO: sys/ipc.h +pub mod sys_mman; +// TODO: sys/msg.h +pub mod sys_ptrace; +pub mod sys_resource; +pub mod sys_select; +// TODO: sys/sem.h +// TODO: sys/shm.h +pub mod sys_socket; +pub mod sys_stat; +pub mod sys_statvfs; +#[allow(non_upper_case_globals)] +pub mod sys_syscall; +pub mod sys_time; +#[deprecated] +pub mod sys_timeb; +//pub mod sys_times; +pub mod arch_aarch64_user; +pub mod arch_riscv64_user; +pub mod arch_x64_user; +pub mod sys_signalfd; +#[cfg(not(target_arch = "x86"))] // TODO: x86 +pub mod sys_procfs; +pub mod sys_random; +pub mod sys_timerfd; +pub mod sys_syslog; +pub mod sys_types; +#[allow(non_camel_case_types)] +pub mod sys_types_internal; +pub mod sys_uio; +pub mod sys_un; +pub mod sys_utsname; +pub mod sys_wait; +pub mod tar; +// TODO: term.h (deprecated) +pub mod termios; +// TODO: tgmath.h (likely C implementation) +// TODO: threads.h +pub mod time; +// TODO: uchar.h +// TODO: ucontext.h (deprecated) +// TODO: ulimit.h (deprecated) +// TODO: unctrl.h (deprecated) +pub mod unistd; +#[deprecated] +pub mod utime; +pub mod utmp; +// TODO: utmpx.h +// TODO: varargs.h (deprecated) +pub mod wchar; +pub mod wctype; +// TODO: wordexp.h +// TODO: xti.h (deprecated) diff --git a/src/header/monetary/cbindgen.toml b/src/header/monetary/cbindgen.toml new file mode 100644 index 0000000000..003cbd5026 --- /dev/null +++ b/src/header/monetary/cbindgen.toml @@ -0,0 +1,21 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/monetary.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the locale_t type as described in ." +# - "The header shall define the size_t type as described in ." +# - "The header shall define the ssize_t type as described in ." +# +# sys/types.h brings in stddef.h +sys_includes = ["sys/types.h"] +include_guard = "_RELIBC_MONETARY_H" +after_includes = """ +#include // for locale_t from locale.h +""" +language = "C" +style = "tag" +no_includes = true +cpp_compat = true +usize_is_size_t = true + +[enum] +prefix_with_name = true diff --git a/src/header/monetary/mod.rs b/src/header/monetary/mod.rs new file mode 100644 index 0000000000..973b1830a3 --- /dev/null +++ b/src/header/monetary/mod.rs @@ -0,0 +1,102 @@ +//! `monetary.h` implementation. +//! +//! See . + +// We should provide a strfmon() implementation that formats a monetary value, +// according to the current locale (TODO). + +use alloc::string::{String, ToString}; +use core::str; + +use libm::{fabs, pow, round, trunc}; + +extern crate alloc; + +mod strfmon; +#[repr(C)] +struct LocaleMonetaryInfo { + int_curr_symbol: &'static str, + currency_symbol: &'static str, + mon_decimal_point: &'static str, + mon_thousands_sep: &'static str, + mon_grouping: &'static [u8], + positive_sign: &'static str, + negative_sign: &'static str, + int_frac_digits: u8, + frac_digits: u8, + p_cs_precedes: bool, + p_sep_by_space: bool, + p_sign_posn: u8, + n_cs_precedes: bool, + n_sep_by_space: bool, + n_sign_posn: u8, +} + +const DEFAULT_MONETARY: LocaleMonetaryInfo = LocaleMonetaryInfo { + int_curr_symbol: "USD ", + currency_symbol: "$", + mon_decimal_point: ".", + mon_thousands_sep: ",", + mon_grouping: &[3, 3], + positive_sign: "", + negative_sign: "-", + int_frac_digits: 2, + frac_digits: 2, + p_cs_precedes: true, + p_sep_by_space: false, + p_sign_posn: 1, + n_cs_precedes: true, + n_sep_by_space: false, + n_sign_posn: 1, +}; + +#[derive(Default)] +struct FormatFlags { + left_justify: bool, + force_sign: bool, + space_sign: bool, + use_parens: bool, + suppress_symbol: bool, + field_width: Option, + left_precision: Option, + right_precision: Option, + international: bool, +} + +/// Formats a monetary value according to the current locale. +fn apply_grouping(int_str: &str, monetary: &LocaleMonetaryInfo) -> String { + let mut grouped = String::with_capacity(int_str.len() * 2); + let mut group_idx = 0; + let current_grouping = &monetary.mon_grouping; + let separator = monetary.mon_thousands_sep; + + for (count, c) in int_str.chars().enumerate() { + if count > 0 { + let current_group = current_grouping[group_idx.min(current_grouping.len() - 1)]; + if current_group > 0 && (count as u8).is_multiple_of(current_group) { + grouped.push_str(separator); + if group_idx + 1 < current_grouping.len() { + group_idx += 1; + } + } + } + grouped.push(c); + } + + grouped +} + +/// Safe handling of large monetary values. Returns None if the value is too large to format +fn format_value_parts(value: f64, frac_digits: usize) -> Option<(String, i64)> { + let abs_value = fabs(value); + if abs_value > (i64::MAX as f64) { + // Check if the value is too large to format + return None; + } + + let int_part = trunc(abs_value) as i64; + let scale = pow(10.0, frac_digits as f64); + let frac_part = round((abs_value - int_part as f64) * scale) as i64; + + Some((int_part.to_string(), frac_part)) +} diff --git a/src/header/monetary/strfmon.rs b/src/header/monetary/strfmon.rs new file mode 100644 index 0000000000..eae658362c --- /dev/null +++ b/src/header/monetary/strfmon.rs @@ -0,0 +1,326 @@ +use crate::platform::types::{c_char, size_t, ssize_t}; + +use super::{DEFAULT_MONETARY, FormatFlags, LocaleMonetaryInfo, apply_grouping}; +use alloc::string::{String, ToString}; +use core::{ffi::CStr, slice}; +use libm::{fabs, pow, round, trunc}; + +/// See . +/// +/// The `strfmon()` function formats a monetary value according to the format string `format` +/// and writes the result to the character array `s` of size `maxsize`. +/// The format string can contain plain characters and format specifiers. +/// +/// Returns: +/// - The number of characters written (excluding the null terminator), or -1 if +/// an error occurs (e.g., invalid input, buffer overflow) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strfmon( + s: *mut c_char, // Output buffer + maxsize: size_t, // Maximum size of the buffer + format: *const c_char, // Format string + mut args: ... // Variadic arguments for monetary values +) -> ssize_t { + // Validate input pointers and buffer size + if s.is_null() || format.is_null() || maxsize == 0 { + return -1; // Invalid input + } + + // Convert the format string from C to string + let format_str = match unsafe { CStr::from_ptr(format) }.to_str() { + Ok(s) => s, + Err(_) => return -1, // Invalid format string + }; + + // Create a mutable slice for the output buffer + let buffer = unsafe { slice::from_raw_parts_mut(s.cast::(), maxsize) }; + let mut pos = 0; + let mut format_chars = format_str.chars().peekable(); + + // Parse the format string + while let Some(c) = format_chars.next() { + // Handle plain characters (non-`%`) + if c != '%' { + if pos >= buffer.len() { + return -1; // Buffer overflow + } + buffer[pos] = c as u8; // Write the character to the buffer + pos += 1; + continue; + } + + // Handle `%%` escape sequence + if format_chars.peek() == Some(&'%') { + if pos >= buffer.len() { + return -1; // Buffer overflow + } + buffer[pos] = b'%'; // Write a literal `%` + pos += 1; + format_chars.next(); // Skip the second `%` + continue; + } + + // Parse format specifiers + let mut flags = FormatFlags::default(); + + // Parse flags (`+`, `(`, ...) + while let Some(&next_char) = format_chars.peek() { + match next_char { + '=' => flags.left_justify = true, + '+' => flags.force_sign = true, + ' ' => flags.space_sign = true, + '(' => flags.use_parens = true, + '!' => flags.suppress_symbol = true, + _ => break, // Stop when no more flags are found + } + format_chars.next(); + } + + // Parse field width + let mut num_str = String::new(); + while let Some(&c) = format_chars.peek() { + if !c.is_ascii_digit() { + break; + } + num_str.push(c); + format_chars.next(); + } + if !num_str.is_empty() { + flags.field_width = num_str.parse().ok(); // Parse as integer + } + + // Parse left precision (`#`) + if format_chars.peek() == Some(&'#') { + format_chars.next(); // Skip `#` + num_str.clear(); // Clear the string for precision + while let Some(&c) = format_chars.peek() { + if !c.is_ascii_digit() { + break; + } + num_str.push(c); + format_chars.next(); + } + flags.left_precision = num_str.parse().ok(); // Parse as integer + } + + // Parse right precision indicated by `.` + if format_chars.peek() == Some(&'.') { + format_chars.next(); // Skip `.` + num_str.clear(); // Clear the string for precision + while let Some(&c) = format_chars.peek() { + if !c.is_ascii_digit() { + break; + } + num_str.push(c); + format_chars.next(); + } + flags.right_precision = num_str.parse().ok(); // Parse as integer + } + + // Handle conversion specifiers (`i` or `n`) + match format_chars.next() { + Some('i') => { + // International formatting + flags.international = true; + let value = unsafe { args.next_arg::() }; // Get the argument as f64 + if let Some(written) = + format_monetary(&mut buffer[pos..], value, &DEFAULT_MONETARY, &flags) + { + pos += written; // Update the position + } else { + return -1; // Formatting failed + } + } + Some('n') => { + // Locale-specific formatting + let value = unsafe { args.next_arg::() }; // Get the argument as f64 + if let Some(written) = + format_monetary(&mut buffer[pos..], value, &DEFAULT_MONETARY, &flags) + { + pos += written; // Update the position + } else { + return -1; // Failed to format the value + } + } + _ => return -1, + } + } + + // Ensure there is space for the null terminator + if pos >= buffer.len() { + return -1; // Buffer overflow + } + buffer[pos] = 0; // Null-terminate the buffer + pos as ssize_t // Return the number of characters written +} + +/// Formats a monetary value into the given `buffer` using locale-specific rules +/// from `monetary` and formatting options from `flags`. +/// Returns `Some(len)` if successful, where `len` is the number of bytes written, +/// or `None` on error. +/// +/// # Parameters +/// - `buffer`: The output slice in which to write the formatted string +/// - `value`: The numeric value to format as monetary +/// - `monetary`: Locale-specific formatting rules, including symbols, separators, +/// and grouping sizes +/// - `flags`: Additional formatting options +/// +fn format_monetary( + buffer: &mut [u8], + value: f64, + monetary: &LocaleMonetaryInfo, + flags: &FormatFlags, +) -> Option { + // 1) determine sign and absolute value + let is_negative = value < 0.0; + let abs_value = fabs(value); + + // 2) figure out how many fractionals digits to use + let frac_digits = if flags.international { + flags + .right_precision + .unwrap_or(monetary.int_frac_digits as usize) + } else { + flags + .right_precision + .unwrap_or(monetary.frac_digits as usize) + }; + + // 3) split the value into integer and fractional parts + let scale = pow(10.0, frac_digits as f64); + + // Check for overflow + let max_safe_int = (i64::MAX as f64) / scale; + if abs_value >= max_safe_int { + return None; + } + + let mut int_part = trunc(abs_value) as i64; + let mut frac_part = round((abs_value - int_part as f64) * scale) as i64; + + // 4) handle carry-over if frac_part equals or exceeds scale after rounding + if frac_part >= scale as i64 { + int_part += 1; + frac_part = 0; + } + + // 5) convert the integer part to string + let mut int_str = int_part.to_string(); + + // 6) apply left precision if specified (padding with '0') + // So if left_precision is 5 and int_str is "42", it becomes "00042". + if let Some(left_prec) = flags.left_precision { + if int_str.len() > left_prec { + // The integer part is too large to fit the precision + return None; + } + // Right-align the number in a field of `left_prec` width, padded with '0' + int_str = format!("{:0>width$}", int_str, width = left_prec); + } + + // 7) build the final formatted output in a temporary String + let mut result = String::with_capacity(int_str.len() * 2 + 20); + + // 7a) determine currency symbol placement and sign rules + let (cs_precedes, sep_by_space, sign_posn) = if is_negative { + ( + monetary.n_cs_precedes, + monetary.n_sep_by_space, + monetary.n_sign_posn, + ) + } else { + ( + monetary.p_cs_precedes, + monetary.p_sep_by_space, + monetary.p_sign_posn, + ) + }; + + // 7b) determine which sign to display + // - negative sign if value is negative + // - positive_sign if user forced sign + // - space if space_sign is set and value is positive + // - empty otherwise + let sign = match (is_negative, flags.force_sign, flags.space_sign) { + (true, _, _) => monetary.negative_sign, + (false, true, _) => monetary.positive_sign, + (false, false, true) => " ", + _ => "", + }; + + // 7c) choose which currency symbol to display + // - maybe empty if suppressed + // - int_curr_symbol for international format + // - currency_symbol for local format + let symbol = if flags.suppress_symbol { + "" + } else if flags.international { + monetary.int_curr_symbol + } else { + monetary.currency_symbol + }; + + // 8) add opening parenthesis if sign position is 0 + if sign_posn == 0 { + result.push('('); + } else if sign_posn == 1 { + result.push_str(sign); + } + + // 9) add currency symbol if it precedes the amount + if cs_precedes { + result.push_str(symbol); + if sep_by_space { + result.push(' '); + } + } + + // 10) group the integer string and append it + let grouped = apply_grouping(&int_str, monetary); + result.push_str(&grouped); + + // 11) append the fractional part, if any + if frac_digits > 0 { + result.push_str(monetary.mon_decimal_point); + // Zero-pad fractional part to the specified width + result.push_str(&format!("{:0>width$}", frac_part, width = frac_digits)); + } + + // 12) if the currency symbol follows the amount, add it now + if !cs_precedes { + if sep_by_space { + result.push(' '); + } + result.push_str(symbol); + } + + // 13) if sign_posn == 0, close the parenthesis + if sign_posn == 0 { + result.push(')'); + } + + // 14) checks if the user specified a total field width + // - if the final result is shorter, we padd it + // - if `left_justify` is true, padd on the right otherwise padd on the left + if let Some(width) = flags.field_width + && result.len() < width + { + let padding = " ".repeat(width - result.len()); + result = if flags.left_justify { + result + &padding + } else { + padding + &result + }; + } + + // 15) write the final string to the buffer + if result.len() > buffer.len() { + // Not enough space in the output buffer + return None; + } + buffer[..result.len()].copy_from_slice(result.as_bytes()); + + // 16) return how many bytes we wrote + Some(result.len()) +} diff --git a/src/header/net_if/cbindgen.toml b/src/header/net_if/cbindgen.toml new file mode 100644 index 0000000000..a434e2f0dc --- /dev/null +++ b/src/header/net_if/cbindgen.toml @@ -0,0 +1,12 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/net_if.h.html +# +# There are no spec quotations relating to includes +sys_includes = [] +include_guard = "_NET_IF_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/net_if/mod.rs b/src/header/net_if/mod.rs new file mode 100644 index 0000000000..edbfedec50 --- /dev/null +++ b/src/header/net_if/mod.rs @@ -0,0 +1,119 @@ +//! `net/if.h` implementation. +//! +//! See . + +use core::ptr::null; + +use crate::{ + c_str::CStr, + platform::{ + ERRNO, + types::{c_char, c_int, c_uint}, + }, +}; + +use super::errno::ENXIO; + +/// See . +#[repr(C)] +pub struct if_nameindex { + if_index: c_uint, + if_name: *const c_char, +} + +/// See . +pub const IF_NAMESIZE: usize = 16; + +const IF_STUB_INTERFACE: *const c_char = (c"stub").as_ptr(); + +const INTERFACES: &[if_nameindex] = &[ + if_nameindex { + if_index: 1, + if_name: IF_STUB_INTERFACE, + }, + if_nameindex { + if_index: 0, + if_name: null::(), + }, +]; + +/// See . +/// +/// # Safety +/// this is a no-op: the list returned by if_nameindex() is a ref to a constant +#[unsafe(no_mangle)] +pub unsafe extern "C" fn if_freenameindex(s: *mut if_nameindex) {} + +/// See . +/// +/// # Safety +/// Returns only static lifetime references to const names, does not reuse the buf pointer. +/// Returns NULL in case of not found + ERRNO being set to ENXIO. +/// Currently only checks against inteface index 1. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn if_indextoname(idx: c_uint, buf: *mut c_char) -> *const c_char { + if idx == 1 { + return IF_STUB_INTERFACE; + } + ERRNO.set(ENXIO); + null::() +} + +/// See . +/// +/// # Safety +/// Returns a constant pointer to a pre defined const stub list +/// The end of the list is determined by an if_nameindex struct having if_index 0 and if_name NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn if_nameindex() -> *const if_nameindex { + core::ptr::from_ref::(&INTERFACES[0]) +} + +/// See . +/// +/// # Safety +/// Compares the name to a constant string and only returns an int as a result. +/// An invalid name string will return an index of 0 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn if_nametoindex(name: *const c_char) -> c_uint { + if name.is_null() { + return 0; + } + let name = unsafe { CStr::from_ptr(name).to_str().unwrap_or("") }; + if name.eq("stub") { + return 1; + } + 0 +} + +// Nonstandard, used alongside ifaddrs.h +// See https://man7.org/linux/man-pages/man7/netdevice.7.html + +pub const IFF_UP: c_int = 0x1; +pub const IFF_BROADCAST: c_int = 0x2; +pub const IFF_DEBUG: c_int = 0x4; +pub const IFF_LOOPBACK: c_int = 0x8; +pub const IFF_POINTOPOINT: c_int = 0x10; +pub const IFF_NOTRAILERS: c_int = 0x20; +pub const IFF_RUNNING: c_int = 0x40; +pub const IFF_NOARP: c_int = 0x80; +pub const IFF_PROMISC: c_int = 0x100; +pub const IFF_ALLMULTI: c_int = 0x200; +pub const IFF_MASTER: c_int = 0x400; +pub const IFF_SLAVE: c_int = 0x800; +pub const IFF_MULTICAST: c_int = 0x1000; +pub const IFF_PORTSEL: c_int = 0x2000; +pub const IFF_AUTOMEDIA: c_int = 0x4000; +pub const IFF_DYNAMIC: c_int = 0x8000; +pub const IFF_LOWER_UP: c_int = 0x10000; +pub const IFF_DORMANT: c_int = 0x20000; +pub const IFF_ECHO: c_int = 0x40000; +pub const IFF_VOLATILE: c_int = IFF_LOOPBACK + | IFF_POINTOPOINT + | IFF_BROADCAST + | IFF_ECHO + | IFF_MASTER + | IFF_SLAVE + | IFF_RUNNING + | IFF_LOWER_UP + | IFF_DORMANT; diff --git a/src/header/netdb/cbindgen.toml b/src/header/netdb/cbindgen.toml index b4332479d2..f21e68cd28 100644 --- a/src/header/netdb/cbindgen.toml +++ b/src/header/netdb/cbindgen.toml @@ -1,9 +1,18 @@ -sys_includes = ["sys/socket.h", "netinet/in.h"] +# netinet/in.h brings in sys/socket.h +sys_includes = ["inttypes.h", "netinet/in.h", "features.h"] include_guard = "_RELIBC_NETDB_H" -trailer = "#include " +trailer = """ +#ifndef _RELIBC_BITS_NETDB_H +#define _RELIBC_BITS_NETDB_H + +#define h_errno (*__h_errno_location()) +#define h_addr h_addr_list[0] /* Address, for backward compatibility.*/ + +#endif // _RELIBC_BITS_NETDB_H +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [export.rename] diff --git a/src/header/netdb/dns/mod.rs b/src/header/netdb/dns/mod.rs index e39098c6fa..9d7e44bd1c 100644 --- a/src/header/netdb/dns/mod.rs +++ b/src/header/netdb/dns/mod.rs @@ -11,46 +11,10 @@ pub use self::{answer::DnsAnswer, query::DnsQuery}; use alloc::{string::String, vec::Vec}; -use core::{slice, u16}; mod answer; mod query; -#[allow(non_camel_case_types)] -#[derive(Copy, Clone, Debug, Default)] -#[repr(packed)] -pub struct n16 { - inner: u16, -} - -impl n16 { - pub fn as_bytes(&self) -> &[u8] { - unsafe { slice::from_raw_parts((&self.inner as *const u16) as *const u8, 2) } - } - - pub fn from_bytes(bytes: &[u8]) -> Self { - n16 { - inner: unsafe { - slice::from_raw_parts(bytes.as_ptr() as *const u16, bytes.len() / 2)[0] - }, - } - } -} - -impl From for n16 { - fn from(value: u16) -> Self { - n16 { - inner: value.to_be(), - } - } -} - -impl From for u16 { - fn from(value: n16) -> Self { - u16::from_be(value.inner) - } -} - #[derive(Clone, Debug)] pub struct Dns { pub transaction_id: u16, @@ -67,13 +31,13 @@ impl Dns { ($value:expr) => { data.push($value); }; - }; + } macro_rules! push_n16 { ($value:expr) => { - data.extend_from_slice(n16::from($value).as_bytes()); + data.extend_from_slice(&u16::to_be_bytes($value)); }; - }; + } push_n16!(self.transaction_id); push_n16!(self.flags); @@ -106,17 +70,19 @@ impl Dns { } data[i - 1] }}; - }; + } macro_rules! pop_n16 { () => {{ + use core::convert::TryInto; i += 2; if i > data.len() { return Err(format!("{}: {}: pop_n16", file!(), line!())); } - u16::from(n16::from_bytes(&data[i - 2..i])) + let bytes: [u8; 2] = data[i - 2..i].try_into().unwrap(); + u16::from_be_bytes(bytes) }}; - }; + } macro_rules! pop_data { () => {{ @@ -129,7 +95,7 @@ impl Dns { data }}; - }; + } macro_rules! pop_name { () => {{ @@ -160,14 +126,14 @@ impl Dns { name }}; - }; + } let transaction_id = pop_n16!(); let flags = pop_n16!(); let queries_len = pop_n16!(); let answers_len = pop_n16!(); - pop_n16!(); - pop_n16!(); + let _ = pop_n16!(); + let _ = pop_n16!(); let mut queries = Vec::new(); for _query_i in 0..queries_len { diff --git a/src/header/netdb/host.rs b/src/header/netdb/host.rs index 1b4ba10163..9d8c6a4347 100644 --- a/src/header/netdb/host.rs +++ b/src/header/netdb/host.rs @@ -2,16 +2,17 @@ use alloc::{boxed::Box, str::SplitWhitespace, vec::Vec}; use core::{mem, ptr}; use crate::{ - c_str::CString, + error::ResultExt, header::{ arpa_inet::inet_aton, fcntl::O_RDONLY, netinet_in::in_addr, sys_socket::constants::AF_INET, unistd::SEEK_SET, }, platform::{ - rlb::{Line, RawLineBuffer}, - types::*, Pal, Sys, + rlb::{Line, RawLineBuffer}, + types::{c_char, c_int}, }, + raw_cell::RawCell, }; use super::{bytes_to_box_str, hostent}; @@ -24,104 +25,108 @@ pub static mut HOST_ENTRY: hostent = hostent { h_length: 0, h_addr_list: ptr::null_mut(), }; -pub static mut HOST_NAME: Option> = None; -pub static mut HOST_ALIASES: Option>> = None; -static mut _HOST_ALIASES: Option> = None; +pub static HOST_NAME: RawCell>> = RawCell::new(None); +pub static HOST_ALIASES: RawCell>>> = RawCell::new(None); +static _HOST_ALIASES: RawCell>> = RawCell::new(None); pub static mut HOST_ADDR: Option = None; pub static mut HOST_ADDR_LIST: [*mut c_char; 2] = [ptr::null_mut(); 2]; pub static mut _HOST_ADDR_LIST: [u8; 4] = [0u8; 4]; static mut H_POS: usize = 0; pub static mut HOST_STAYOPEN: c_int = 0; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn endhostent() { - if HOSTDB >= 0 { - Sys::close(HOSTDB); - } - HOSTDB = -1; + if unsafe { HOSTDB } >= 0 + && let Ok(()) = Sys::close(unsafe { HOSTDB }) + {} // TODO handle error + unsafe { HOSTDB = -1 }; } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn sethostent(stayopen: c_int) { - HOST_STAYOPEN = stayopen; - if HOSTDB < 0 { - HOSTDB = Sys::open(&CString::new("/etc/hosts").unwrap(), O_RDONLY, 0) - } else { - Sys::lseek(HOSTDB, 0, SEEK_SET); - } - H_POS = 0; + unsafe { HOST_STAYOPEN = stayopen }; + if unsafe { HOSTDB } < 0 { + unsafe { HOSTDB = Sys::open(c"/etc/hosts".into(), O_RDONLY, 0).or_minus_one_errno() } + } else if Sys::lseek(unsafe { HOSTDB }, 0, SEEK_SET).is_ok() { + } // TODO handle error + unsafe { H_POS = 0 }; } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn gethostent() -> *mut hostent { - if HOSTDB < 0 { - HOSTDB = Sys::open(&CString::new("/etc/hosts").unwrap(), O_RDONLY, 0); + if unsafe { HOSTDB } < 0 { + unsafe { HOSTDB = Sys::open(c"/etc/hosts".into(), O_RDONLY, 0).or_minus_one_errno() }; } - let mut rlb = RawLineBuffer::new(HOSTDB); - rlb.seek(H_POS); + let mut rlb = RawLineBuffer::new(unsafe { HOSTDB }); + rlb.seek(unsafe { H_POS }); let mut r: Box = Box::default(); - while r.is_empty() || r.split_whitespace().next() == None || r.starts_with('#') { + while r.is_empty() || r.split_whitespace().next().is_none() || r.starts_with('#') { r = match rlb.next() { Line::Some(s) => bytes_to_box_str(s), _ => { - if HOST_STAYOPEN == 0 { - endhostent(); + if unsafe { HOST_STAYOPEN } == 0 { + unsafe { endhostent() }; } return ptr::null_mut(); } }; } rlb.next(); - H_POS = rlb.line_pos(); + unsafe { H_POS = rlb.line_pos() }; let mut iter: SplitWhitespace = r.split_whitespace(); - let mut addr_vec = iter.next().unwrap().as_bytes().to_vec(); - addr_vec.push(b'\0'); - let addr_cstr = addr_vec.as_slice().as_ptr() as *const i8; + let addr_vec: Vec = iter.next().unwrap().bytes().chain(Some(b'\0')).collect(); + let addr_cstr = addr_vec.as_slice().as_ptr().cast::(); let mut addr = mem::MaybeUninit::uninit(); - inet_aton(addr_cstr, addr.as_mut_ptr()); - let addr = addr.assume_init(); + unsafe { inet_aton(addr_cstr, addr.as_mut_ptr()) }; + let addr = unsafe { addr.assume_init() }; - _HOST_ADDR_LIST = mem::transmute::(addr.s_addr); - HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()]; + unsafe { + _HOST_ADDR_LIST = addr.s_addr.to_ne_bytes(); + HOST_ADDR_LIST = [(&raw mut _HOST_ADDR_LIST).cast::(), ptr::null_mut()]; - HOST_ADDR = Some(addr); - - let mut host_name = iter.next().unwrap().as_bytes().to_vec(); - host_name.push(b'\0'); - - let mut _host_aliases: Vec> = Vec::new(); - - for s in iter { - let mut alias = s.as_bytes().to_vec(); - alias.push(b'\0'); - _host_aliases.push(alias); + HOST_ADDR = Some(addr); } - HOST_ALIASES = Some(_host_aliases); - let mut host_aliases: Vec<*mut i8> = HOST_ALIASES - .as_mut() - .unwrap() - .iter_mut() - .map(|x| x.as_mut_ptr() as *mut i8) + let host_name = iter.next().unwrap().bytes().chain(Some(b'\0')).collect(); + + let mut _host_aliases: Vec> = iter + .map(|alias| alias.bytes().chain(Some(b'\0')).collect()) .collect(); - host_aliases.push(ptr::null_mut()); - host_aliases.push(ptr::null_mut()); + unsafe { HOST_ALIASES.unsafe_set(Some(_host_aliases)) }; - HOST_NAME = Some(host_name); - - HOST_ENTRY = hostent { - h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char, - h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - h_addrtype: AF_INET, - h_length: 4, - h_addr_list: HOST_ADDR_LIST.as_mut_ptr(), + let mut host_aliases: Vec<*mut c_char> = unsafe { + HOST_ALIASES + .unsafe_mut() + .as_mut() + .unwrap() + .iter_mut() + .map(|x| x.as_mut_ptr().cast::()) + .chain([ptr::null_mut(), ptr::null_mut()]) + .collect() }; - _HOST_ALIASES = Some(host_aliases); - if HOST_STAYOPEN == 0 { - endhostent(); + + unsafe { HOST_NAME.unsafe_set(Some(host_name)) }; + + unsafe { + HOST_ENTRY = hostent { + h_name: HOST_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_ptr() + .cast::(), + h_aliases: host_aliases.as_mut_slice().as_mut_ptr(), + h_addrtype: AF_INET, + h_length: 4, + h_addr_list: (&raw mut HOST_ADDR_LIST).cast(), + }; + _HOST_ALIASES.unsafe_set(Some(host_aliases)); } - &mut HOST_ENTRY as *mut hostent + if unsafe { HOST_STAYOPEN } == 0 { + unsafe { endhostent() }; + } + (&raw mut HOST_ENTRY).cast::() } diff --git a/src/header/netdb/linux.rs b/src/header/netdb/linux.rs index f174170164..2601051164 100644 --- a/src/header/netdb/linux.rs +++ b/src/header/netdb/linux.rs @@ -1,29 +1,19 @@ use crate::{ - c_str::CString, + error::Errno, fs::File, - header::fcntl, + header::{errno, fcntl}, io::{BufRead, BufReader}, }; use alloc::string::String; -pub fn get_dns_server() -> String { - let file = match File::open(&CString::new("/etc/resolv.conf").unwrap(), fcntl::O_RDONLY) { - Ok(file) => file, - Err(_) => return String::new(), // TODO: better error handling - }; - let file = BufReader::new(file); +pub fn get_dns_server() -> Result { + let file = File::open(c"/etc/resolv.conf".into(), fcntl::O_RDONLY).map(BufReader::new)?; - for line in file.split(b'\n') { - let mut line = match line { - Ok(line) => line, - Err(_) => return String::new(), // TODO: pls handle errors - }; - if line.starts_with(b"nameserver ") { - line.drain(..11); - return String::from_utf8(line).unwrap_or_default(); + for line in file.lines().map_while(Result::ok) { + if let Some(dns) = line.strip_prefix("nameserver ") { + return Ok(dns.into()); } } - // TODO: better error handling - String::new() + Err(Errno(errno::EIO).sync()) } diff --git a/src/header/netdb/lookup.rs b/src/header/netdb/lookup.rs index 7d3c87d55c..0734eec668 100644 --- a/src/header/netdb/lookup.rs +++ b/src/header/netdb/lookup.rs @@ -1,22 +1,26 @@ -use alloc::{ - boxed::Box, - string::{String, ToString}, - vec::{IntoIter, Vec}, -}; -use core::mem; +use alloc::{boxed::Box, string::ToString, vec::Vec}; +use core::{mem, ptr}; -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + out::Out, + platform::{ + Pal, Sys, + types::{c_int, c_void}, + }, +}; use crate::header::{ - arpa_inet::htons, + bits_arpainet::htons, + bits_socklen_t::socklen_t, + bits_timespec::timespec, errno::*, - netinet_in::{in_addr, sockaddr_in, IPPROTO_UDP}, + netinet_in::{IPPROTO_UDP, in_addr, sockaddr_in}, sys_socket::{ self, constants::{AF_INET, SOCK_DGRAM}, - sockaddr, socklen_t, + sockaddr, }, - time::{self, timespec}, + time, }; use super::{ @@ -24,33 +28,24 @@ use super::{ sys::get_dns_server, }; -pub struct LookupHost(IntoIter); - -impl Iterator for LookupHost { - type Item = in_addr; - fn next(&mut self) -> Option { - self.0.next() - } -} +pub type LookupHost = Vec; pub fn lookup_host(host: &str) -> Result { - let dns_string = get_dns_server(); + if let Some(host_direct_addr) = parse_ipv4_string(host) { + // already an ip address + return Ok(vec![in_addr { + s_addr: host_direct_addr, + }]); + } - let dns_vec: Vec = dns_string - .trim() - .split('.') - .map(|octet| octet.parse::().unwrap_or(0)) - .collect(); - - if dns_vec.len() == 4 { - let mut dns_arr = [0u8; 4]; - for (i, octet) in dns_vec.iter().enumerate() { - dns_arr[i] = *octet; - } - let dns_addr = unsafe { mem::transmute::<[u8; 4], u32>(dns_arr) }; + let dns_string = get_dns_server().map_err(|e| e.0)?; + if let Some(dns_addr) = parse_ipv4_string(&dns_string) { let mut timespec = timespec::default(); - Sys::clock_gettime(time::constants::CLOCK_REALTIME, &mut timespec); + if let Ok(()) = Sys::clock_gettime( + time::constants::CLOCK_REALTIME, + Out::from_mut(&mut timespec), + ) {}; // TODO handle error let tid = (timespec.tv_nsec >> 16) as u16; let packet = Dns { @@ -76,27 +71,27 @@ pub fn lookup_host(host: &str) -> Result { sin_addr: in_addr { s_addr: dns_addr }, ..Default::default() }; - let dest_ptr = &dest as *const _ as *const sockaddr; + let dest_ptr = ptr::from_ref(&dest).cast::(); let sock = unsafe { - let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP as i32); + let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)); if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 { return Err(EIO); } if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 { - Box::from_raw(packet_data_ptr); + drop(Box::from_raw(packet_data_ptr)); return Err(EIO); } sock }; unsafe { - Box::from_raw(packet_data_ptr); + drop(Box::from_raw(packet_data_ptr)); } let i = 0 as socklen_t; - let mut buf = [0u8; 65536]; - let buf_ptr = buf.as_mut_ptr() as *mut c_void; + let mut buf = vec![0u8; 65536]; + let buf_ptr = buf.as_mut_ptr().cast::(); let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) }; if count < 0 { @@ -105,24 +100,30 @@ pub fn lookup_host(host: &str) -> Result { match Dns::parse(&buf[..count as usize]) { Ok(response) => { - let mut addrs = vec![]; - for answer in response.answers.iter() { - if answer.a_type == 0x0001 && answer.a_class == 0x0001 && answer.data.len() == 4 - { - let addr = in_addr { - s_addr: unsafe { - mem::transmute::<[u8; 4], u32>([ + let addrs: Vec<_> = response + .answers + .into_iter() + .filter_map(|answer| { + if answer.a_type == 0x0001 + && answer.a_class == 0x0001 + && answer.data.len() == 4 + { + let addr = in_addr { + s_addr: u32::from_ne_bytes([ answer.data[0], answer.data[1], answer.data[2], answer.data[3], - ]) - }, - }; - addrs.push(addr); - } - } - Ok(LookupHost(addrs.into_iter())) + ]), + }; + Some(addr) + } else { + None + } + }) + .collect(); + + Ok(addrs) } Err(_err) => Err(EINVAL), } @@ -132,44 +133,28 @@ pub fn lookup_host(host: &str) -> Result { } pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { - let dns_string = get_dns_server(); + let dns_string = get_dns_server().map_err(|e| e.0)?; - let dns_vec: Vec = dns_string - .trim() - .split('.') - .map(|octet| octet.parse::().unwrap_or(0)) - .collect(); + if let Some(dns_addr) = parse_ipv4_string(&dns_string) { + let addr: [u8; 4] = addr.s_addr.to_ne_bytes(); + // Address intentionally backwards for reverse lookup + let name = format!( + "{}.{}.{}.{}.in-addr.arpa", + addr[3], addr[2], addr[1], addr[0] + ); - let mut dns_arr = [0u8; 4]; - - for (i, octet) in dns_vec.iter().enumerate() { - dns_arr[i] = *octet; - } - - let mut addr_vec: Vec = unsafe { mem::transmute::(addr.s_addr).to_vec() }; - addr_vec.reverse(); - let mut name: Vec = vec![]; - for octet in addr_vec { - for ch in format!("{}", octet).as_bytes() { - name.push(*ch); - } - name.push(b"."[0]); - } - name.pop(); - for ch in b".IN-ADDR.ARPA" { - name.push(*ch); - } - - if dns_vec.len() == 4 { let mut timespec = timespec::default(); - Sys::clock_gettime(time::constants::CLOCK_REALTIME, &mut timespec); + if let Ok(()) = Sys::clock_gettime( + time::constants::CLOCK_REALTIME, + Out::from_mut(&mut timespec), + ) {}; // TODO handle error let tid = (timespec.tv_nsec >> 16) as u16; let packet = Dns { transaction_id: tid, flags: 0x0100, queries: vec![DnsQuery { - name: String::from_utf8(name).unwrap(), + name, q_type: 0x000C, q_class: 0x0001, }], @@ -184,16 +169,14 @@ pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { let dest = sockaddr_in { sin_family: AF_INET as u16, sin_port: htons(53), - sin_addr: in_addr { - s_addr: unsafe { mem::transmute::<[u8; 4], u32>(dns_arr) }, - }, + sin_addr: in_addr { s_addr: dns_addr }, ..Default::default() }; - let dest_ptr = &dest as *const _ as *const sockaddr; + let dest_ptr = ptr::from_ref(&dest).cast::(); let sock = unsafe { - let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP as i32); + let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)); if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 { return Err(EIO); } @@ -207,12 +190,12 @@ pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { } unsafe { - Box::from_raw(packet_data_ptr); + drop(Box::from_raw(packet_data_ptr)); } let i = mem::size_of::() as socklen_t; let mut buf = [0u8; 65536]; - let buf_ptr = buf.as_mut_ptr() as *mut c_void; + let buf_ptr = buf.as_mut_ptr().cast::(); let count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) }; if count < 0 { @@ -221,18 +204,22 @@ pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { match Dns::parse(&buf[..count as usize]) { Ok(response) => { - let mut names = vec![]; - for answer in response.answers.iter() { - if answer.a_type == 0x000C && answer.a_class == 0x0001 { - // answer.data is encoded kinda weird. - // Basically length-prefixed strings for each - // subsection of the domain. - // We need to parse this to insert periods where - // they belong (ie at the end of each string) - let data = parse_revdns_answer(&answer.data); - names.push(data); - } - } + let names = response + .answers + .into_iter() + .filter_map(|answer| { + if answer.a_type == 0x000C && answer.a_class == 0x0001 { + // answer.data is encoded kinda weird. + // Basically length-prefixed strings for each + // subsection of the domain. + // We need to parse this to insert periods where + // they belong (ie at the end of each string) + Some(parse_revdns_answer(&answer.data)) + } else { + None + } + }) + .collect(); Ok(names) } Err(_err) => Err(EINVAL), @@ -243,16 +230,142 @@ pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { } fn parse_revdns_answer(data: &[u8]) -> Vec { + if data.is_empty() || data[0] == 0 { + return vec![0]; + } + let mut cursor = 0; let mut index = 0; - let mut output = data.to_vec(); - while index < data.len() - 1 { + // First byte is a length; discard + let mut output = data[1..].to_vec(); + let length = data.len() - 1; + while index < length { let offset = data[index] as usize; + // CVE-2024-21342 + if offset > length { + return vec![0]; + } index = cursor + offset + 1; - output[index] = b'.'; + // First byte was skipped so index is one less + output[index - 1] = b'.'; cursor = index; } - //we don't want an extra period at the end - output.pop(); + // Response is NUL terminated so we must preserve that for C + match output.last_mut() { + Some(nul) => *nul = b'\0', + // XXX: Likely unreachable + None => { + debug_assert!(output.is_empty()); + output = vec![0]; + } + } output } + +pub fn parse_ipv4_string(ip_string: &str) -> Option { + let dns_vec: Vec = ip_string + .trim() + .split('.') + .map(|octet| octet.parse::().unwrap_or(0)) + .collect(); + + if dns_vec.len() != 4 { + return None; + } + + let mut dns_arr = [0u8; 4]; + + for (i, octet) in dns_vec.iter().enumerate() { + dns_arr[i] = *octet; + } + + Some(u32::from_ne_bytes(dns_arr)) +} + +#[cfg(test)] +mod tests { + use alloc::str; + use core::ffi::CStr; + + use super::parse_revdns_answer; + + // Actual response from a query + const DNS_GOOGLE: &[u8] = &[3, 100, 110, 115, 6, 103, 111, 111, 103, 108, 101, 0]; + const EXPECTED_DNS_GOOGLE: &str = "dns.google\0"; + const EXPECTED_DNS_GOOGLE_RT: &str = "dns.google"; + + // Fake response that has numbers within the name (e.g. like CDNs) + const FAKE_WITH_NUMS: &[u8] = &[ + 14, 102, 97, 107, 101, 45, 49, 48, 48, 45, 50, 45, 51, 45, 52, 8, 102, 111, 111, 98, 97, + 114, 50, 52, 4, 102, 97, 107, 101, 0, + ]; + const EXPECTED_FAKE_RESPONSE: &str = "fake-100-2-3-4.foobar24.fake\0"; + const EXPECTED_FAKE_RESPONSE_RT: &str = "fake-100-2-3-4.foobar24.fake"; + + const EMPTY_RESPONSE: &[u8] = &[0]; + const EXPECTED_EMPTY_RESPONSE: &str = "\0"; + const EXPECTED_EMPTY_RESPONSE_RT: &str = ""; + + #[test] + fn dns_response_dns_google() { + let response = parse_revdns_answer(DNS_GOOGLE); + assert_eq!( + 0, + *response.last().unwrap(), + "Response should end with a NUL byte" + ); + + let response_str = str::from_utf8(&response) + .expect("Response is valid UTF-8; parsing shouldn't change that"); + assert_eq!(EXPECTED_DNS_GOOGLE, response_str); + + let response_cstr = CStr::from_bytes_with_nul(&response) + .expect("Parsed response should have only one NUL byte"); + let response_cstr_str = response_cstr + .to_str() + .expect("Valid UTF-8 bytes to CStr to Rust str should be valid"); + assert_eq!(EXPECTED_DNS_GOOGLE_RT, response_cstr_str); + } + + #[test] + fn dns_response_fake_with_nums() { + let response = parse_revdns_answer(FAKE_WITH_NUMS); + assert_eq!( + 0, + *response.last().unwrap(), + "Response should end with a NUL byte" + ); + + let response_str = str::from_utf8(&response) + .expect("Response is valid UTF-8; parsing shouldn't change that"); + assert_eq!(EXPECTED_FAKE_RESPONSE, response_str); + + let response_cstr = CStr::from_bytes_with_nul(&response) + .expect("Parsed response should have only one NUL byte"); + let response_cstr_str = response_cstr + .to_str() + .expect("Valid UTF-8 bytes to CStr to Rust str should be valid"); + assert_eq!(EXPECTED_FAKE_RESPONSE_RT, response_cstr_str); + } + + #[test] + fn dns_response_empty() { + let response = parse_revdns_answer(EMPTY_RESPONSE); + assert_eq!( + 0, + *response.last().unwrap(), + "Response should end with a NUL byte" + ); + + let response_str = str::from_utf8(&response) + .expect("Response is valid UTF-8; parsing shouldn't change that"); + assert_eq!(EXPECTED_EMPTY_RESPONSE, response_str); + + let response_cstr = CStr::from_bytes_with_nul(&response) + .expect("Parsed response should have only one NUL byte"); + let response_cstr_str = response_cstr + .to_str() + .expect("Valid UTF-8 bytes to CStr to Rust str should be valid"); + assert_eq!(EXPECTED_EMPTY_RESPONSE_RT, response_cstr_str); + } +} diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs index 8a510171a6..1d9941cbc5 100644 --- a/src/header/netdb/mod.rs +++ b/src/header/netdb/mod.rs @@ -1,32 +1,35 @@ -//! netdb implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xns/netdb.h.html +//! `netdb.h` implementation. +//! +//! See . mod dns; -use core::{ - mem, ptr, slice, - str::{self, FromStr}, -}; +use core::{cell::Cell, fmt::Write, mem, net::Ipv4Addr, ptr, str}; -use alloc::{borrow::ToOwned, boxed::Box, str::SplitWhitespace, vec::Vec}; +use alloc::{boxed::Box, str::SplitWhitespace, string::ToString, vec::Vec}; use crate::{ c_str::{CStr, CString}, + error::ResultExt, header::{ - arpa_inet::{htons, inet_aton, ntohl}, + arpa_inet::inet_aton, + bits_arpainet::{htons, ntohl}, + bits_safamily_t::sa_family_t, + bits_socklen_t::socklen_t, errno::*, fcntl::O_RDONLY, netinet_in::{in_addr, sockaddr_in, sockaddr_in6}, stdlib::atoi, strings::strcasecmp, - sys_socket::{constants::AF_INET, sa_family_t, sockaddr, socklen_t}, + sys_socket::{constants::AF_INET, sockaddr}, unistd::SEEK_SET, }, platform::{ - self, + self, Pal, Sys, rlb::{Line, RawLineBuffer}, - types::*, - Pal, Sys, + types::{c_char, c_int, c_void, uint32_t}, }, + raw_cell::RawCell, }; #[cfg(target_os = "linux")] @@ -43,6 +46,7 @@ pub mod host; pub use self::lookup::*; pub mod lookup; +/// See . #[repr(C)] pub struct hostent { h_name: *mut c_char, @@ -52,14 +56,16 @@ pub struct hostent { h_addr_list: *mut *mut c_char, } +/// See . #[repr(C)] pub struct netent { n_name: *mut c_char, /* official name of net */ n_aliases: *mut *mut c_char, /* alias list */ n_addrtype: c_int, /* net address type */ - n_net: c_ulong, /* network # */ + n_net: uint32_t, /* network # */ } +/// See . #[repr(C)] pub struct protoent { p_name: *mut c_char, /* official protocol name */ @@ -67,6 +73,7 @@ pub struct protoent { p_proto: c_int, /* protocol # */ } +/// See . #[repr(C)] pub struct servent { s_name: *mut c_char, /* official service name */ @@ -75,6 +82,7 @@ pub struct servent { s_proto: *mut c_char, /* protocol to use */ } +/// See . #[repr(C)] #[derive(Debug)] pub struct addrinfo { @@ -82,7 +90,7 @@ pub struct addrinfo { ai_family: c_int, /* PF_xxx */ ai_socktype: c_int, /* SOCK_xxx */ ai_protocol: c_int, /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ - ai_addrlen: size_t, /* length of ai_addr */ + ai_addrlen: socklen_t, /* length of ai_addr */ ai_canonname: *mut c_char, /* canonical name for hostname */ ai_addr: *mut sockaddr, /* binary address */ ai_next: *mut addrinfo, /* next structure in linked list */ @@ -125,28 +133,32 @@ pub static mut NET_ENTRY: netent = netent { n_addrtype: 0, n_net: 0, }; -pub static mut NET_NAME: Option> = None; -pub static mut NET_ALIASES: Option>> = None; +pub static NET_NAME: RawCell>> = RawCell::new(None); +pub static NET_ALIASES: RawCell>>> = RawCell::new(None); pub static mut NET_ADDR: Option = None; static mut N_POS: usize = 0; static mut NET_STAYOPEN: c_int = 0; -#[allow(non_upper_case_globals)] -#[no_mangle] -pub static mut h_errno: c_int = 0; +#[thread_local] +pub static H_ERRNO: Cell = Cell::new(0); +const H_UNSET: c_int = 0; pub const HOST_NOT_FOUND: c_int = 1; pub const NO_DATA: c_int = 2; pub const NO_RECOVERY: c_int = 3; pub const TRY_AGAIN: c_int = 4; +// Expected length of addresses +const SOCKLEN_AF_INET4: socklen_t = 4; +const SOCKLEN_AF_INET6: socklen_t = 16; + static mut PROTODB: c_int = 0; static mut PROTO_ENTRY: protoent = protoent { p_name: ptr::null_mut(), p_aliases: ptr::null_mut(), p_proto: 0 as c_int, }; -static mut PROTO_NAME: Option> = None; -static mut PROTO_ALIASES: Option>> = None; +static PROTO_NAME: RawCell>> = RawCell::new(None); +static PROTO_ALIASES: RawCell>>> = RawCell::new(None); static mut PROTO_NUM: Option = None; static mut P_POS: usize = 0; static mut PROTO_STAYOPEN: c_int = 0; @@ -158,10 +170,10 @@ static mut SERV_ENTRY: servent = servent { s_port: 0 as c_int, s_proto: ptr::null_mut(), }; -static mut SERV_NAME: Option> = None; -static mut SERV_ALIASES: Option>> = None; +static SERV_NAME: RawCell>> = RawCell::new(None); +static SERV_ALIASES: RawCell>>> = RawCell::new(None); static mut SERV_PORT: Option = None; -static mut SERV_PROTO: Option> = None; +static SERV_PROTO: RawCell>> = RawCell::new(None); static mut S_POS: usize = 0; static mut SERV_STAYOPEN: c_int = 0; @@ -169,341 +181,423 @@ fn bytes_to_box_str(bytes: &[u8]) -> Box { Box::from(core::str::from_utf8(bytes).unwrap_or("")) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn endnetent() { - Sys::close(NETDB); - NETDB = 0; + if let Ok(()) = Sys::close(unsafe { NETDB }) {}; // TODO handle error + unsafe { NETDB = 0 }; } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn endprotoent() { - Sys::close(PROTODB); - PROTODB = 0; + if let Ok(()) = Sys::close(unsafe { PROTODB }) {}; // TODO handle error + unsafe { PROTODB = 0 }; } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn endservent() { - Sys::close(SERVDB); - SERVDB = 0; + if let Ok(()) = Sys::close(unsafe { SERVDB }) {}; // TODO handle error + unsafe { SERVDB = 0 }; } -#[no_mangle] +/// See . +/// Resolve a host name from a given network address. +/// +/// # Arguments +/// * `v` - Address to resolve as a non-null [`in_addr`] +/// * `length` - +/// * `format` - AF_INET or AF_INET6 +/// +/// # Safety +/// * `v` must be a valid pointer. +/// * `length` must correctly match the size of `v` as expected by `format` (usually 4 or 16). +/// * This function is not reentrant and may modify static data. +/// +/// # Panics +/// Panics if `v` is a null pointer. +/// +/// # Deprecation +/// Deprecated as of POSIX.1-2001 and removed in POSIX.1-2008. +/// New code should use [`getaddrinfo`] instead. +#[unsafe(no_mangle)] +#[deprecated] pub unsafe extern "C" fn gethostbyaddr( v: *const c_void, length: socklen_t, format: c_int, ) -> *mut hostent { - let addr: in_addr = *(v as *mut in_addr); + assert!( + !v.is_null(), + "`gethostbyaddr()` called with null `v` (in_addr)" + ); + // Uncomment if optional IPv6 support is added + // if length != SOCKLEN_AF_INET4 || length != SOCKLEN_AF_INET6 { + // H_ERRNO.set(NO_RECOVERY); + // return ptr::null_mut(); + // } + if length != SOCKLEN_AF_INET4 { + H_ERRNO.set(NO_RECOVERY); + return ptr::null_mut(); + } + let addr: in_addr = unsafe { (*(v as *mut in_addr)).clone() }; // check the hosts file first let mut p: *mut hostent; - sethostent(HOST_STAYOPEN); + unsafe { sethostent(HOST_STAYOPEN) }; while { - p = gethostent(); + p = unsafe { gethostent() }; !p.is_null() } { - let mut cp = (*p).h_addr_list; + let mut cp = unsafe { (*p).h_addr_list }; loop { if cp.is_null() { break; } - if (*cp).is_null() { + if (unsafe { *cp }).is_null() { break; } - let mut cp_slice: [i8; 4] = [0i8; 4]; - (*cp).copy_to(cp_slice.as_mut_ptr(), 4); - let cp_s_addr = mem::transmute::<[i8; 4], u32>(cp_slice); + let mut cp_slice: [c_char; 4] = [0; 4]; + unsafe { (*cp).copy_to(cp_slice.as_mut_ptr(), 4) }; + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + let cp_s_addr = unsafe { mem::transmute::<[c_char; 4], u32>(cp_slice) }; + #[cfg(any(target_arch = "riscv64", target_arch = "aarch64"))] + let cp_s_addr = u32::from_ne_bytes(cp_slice); if cp_s_addr == addr.s_addr { - sethostent(HOST_STAYOPEN); + unsafe { sethostent(HOST_STAYOPEN) }; return p; } - cp = cp.offset(1); + cp = unsafe { cp.offset(1) }; } } //TODO actually get aliases let mut _host_aliases: Vec> = Vec::new(); _host_aliases.push(vec![b'\0']); - let mut host_aliases: Vec<*mut i8> = Vec::new(); + let mut host_aliases: Vec<*mut c_char> = Vec::new(); host_aliases.push(ptr::null_mut()); - HOST_ALIASES = Some(_host_aliases); + unsafe { HOST_ALIASES.unsafe_set(Some(_host_aliases)) }; - match lookup_addr(addr) { - Ok(s) => { - _HOST_ADDR_LIST = mem::transmute::(addr.s_addr); - HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()]; - let host_name = s[0].to_vec(); - HOST_NAME = Some(host_name); - HOST_ENTRY = hostent { - h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char, - h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - h_addrtype: format, - h_length: length as i32, - h_addr_list: HOST_ADDR_LIST.as_mut_ptr(), + match lookup_addr(addr.clone()).map(|host_names| host_names.into_iter().next()) { + Ok(Some(host_name)) => { + unsafe { _HOST_ADDR_LIST = addr.s_addr.to_ne_bytes() }; + unsafe { + HOST_ADDR_LIST = [(&raw mut _HOST_ADDR_LIST).cast::(), ptr::null_mut()] }; - &mut HOST_ENTRY + unsafe { HOST_NAME.unsafe_set(Some(host_name)) }; + unsafe { + HOST_ENTRY = hostent { + h_name: HOST_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_ptr() + .cast::(), + h_aliases: host_aliases.as_mut_slice().as_mut_ptr(), + h_addrtype: format, + h_length: length as i32, + h_addr_list: (&raw mut HOST_ADDR_LIST).cast(), + } + }; + &raw mut HOST_ENTRY + } + // `glibc` sets errno if an address doesn't have a host name + // `musl` uses the address as the host name in said case + Ok(None) => { + H_ERRNO.set(HOST_NOT_FOUND); + ptr::null_mut() } Err(e) => { - platform::errno = e; + // TODO: Better error separation in lookup_addr + H_ERRNO.set(NO_RECOVERY); ptr::null_mut() } } } -#[no_mangle] +/// See . +/// Resolve host information by name or IP address. +/// +/// # Arguments +/// * `name` - Host name or IP address. +/// +/// # Safety +/// `name` must be a valid string. +/// This function is not reentrant and may modify static data. +/// +/// # Panics +/// Panics if `name` is a null pointer. +/// +/// # Deprecation +/// Deprecated as of POSIX.1-2001 and removed in POSIX.1-2008. +/// New code should use [`getaddrinfo`] instead. +#[unsafe(no_mangle)] +#[deprecated] pub unsafe extern "C" fn gethostbyname(name: *const c_char) -> *mut hostent { - // check if some idiot gave us an address instead of a name - let name_cstr = CStr::from_ptr(name); - let mut octets = str::from_utf8_unchecked(name_cstr.to_bytes()).split('.'); - let mut s_addr = [0u8; 4]; - let mut is_addr = true; - for item in &mut s_addr { - if let Some(n) = octets.next().and_then(|x| u8::from_str(x).ok()) { - *item = n; - } else { - is_addr = false; - } - } - if octets.next() != None { - is_addr = false; - } + let name_cstr = unsafe { + CStr::from_nullable_ptr(name).expect("gethostbyname() called with a NULL pointer") + }; + let Ok(name_str) = str::from_utf8(name_cstr.to_bytes()) else { + H_ERRNO.set(NO_RECOVERY); + return ptr::null_mut(); + }; - if is_addr { - let addr = in_addr { - s_addr: mem::transmute::<[u8; 4], u32>(s_addr), + // Addresses and hostnames are both valid, so we'll check addresses first + // The standard doesn't define what to do when called with addresses + // Some implementations just skip resolution and copy the address to h_name + if let Some(s_addr) = parse_ipv4_string(name_str) { + let addr = in_addr { s_addr }; + return unsafe { + #[allow(deprecated)] + gethostbyaddr(ptr::from_ref(&addr).cast::(), 4, AF_INET) }; - return gethostbyaddr(&addr as *const _ as *const c_void, 4, AF_INET); } // check the hosts file first let mut p: *mut hostent; - sethostent(HOST_STAYOPEN); + unsafe { sethostent(HOST_STAYOPEN) }; while { - p = gethostent(); + p = unsafe { gethostent() }; !p.is_null() } { - if strcasecmp((*p).h_name, name) == 0 { - sethostent(HOST_STAYOPEN); + if unsafe { strcasecmp((*p).h_name, name) } == 0 { + unsafe { sethostent(HOST_STAYOPEN) }; return p; } - let mut cp = (*p).h_aliases; + let mut cp = unsafe { (*p).h_aliases }; loop { if cp.is_null() { break; } - if (*cp).is_null() { + if (unsafe { *cp }).is_null() { break; } - if strcasecmp(*cp, name) == 0 { - sethostent(HOST_STAYOPEN); + if unsafe { strcasecmp(*cp, name) } == 0 { + unsafe { sethostent(HOST_STAYOPEN) }; return p; } - cp = cp.offset(1); + cp = unsafe { cp.offset(1) }; } } - let name_cstr = CStr::from_ptr(name); - - let mut host = match lookup_host(str::from_utf8_unchecked(name_cstr.to_bytes())) { + let host = match lookup_host(name_str) { Ok(lookuphost) => lookuphost, Err(e) => { - platform::errno = e; + H_ERRNO.set(NO_RECOVERY); return ptr::null_mut(); } }; - let host_addr = match host.next() { + let host_addr = match host.into_iter().next() { Some(result) => result, None => { - platform::errno = ENOENT; + H_ERRNO.set(HOST_NOT_FOUND); return ptr::null_mut(); } }; let host_name: Vec = name_cstr.to_bytes().to_vec(); - HOST_NAME = Some(host_name); - _HOST_ADDR_LIST = mem::transmute::(host_addr.s_addr); - HOST_ADDR_LIST = [_HOST_ADDR_LIST.as_mut_ptr() as *mut c_char, ptr::null_mut()]; - HOST_ADDR = Some(host_addr); + unsafe { HOST_NAME.unsafe_set(Some(host_name)) }; + unsafe { _HOST_ADDR_LIST = host_addr.s_addr.to_ne_bytes() }; + unsafe { HOST_ADDR_LIST = [(&raw mut _HOST_ADDR_LIST).cast::(), ptr::null_mut()] }; + unsafe { HOST_ADDR = Some(host_addr) }; //TODO actually get aliases let mut _host_aliases: Vec> = Vec::new(); _host_aliases.push(vec![b'\0']); - let mut host_aliases: Vec<*mut i8> = Vec::new(); + let mut host_aliases: Vec<*mut c_char> = Vec::new(); host_aliases.push(ptr::null_mut()); host_aliases.push(ptr::null_mut()); - HOST_ALIASES = Some(_host_aliases); + unsafe { HOST_ALIASES.unsafe_set(Some(_host_aliases)) }; - HOST_ENTRY = hostent { - h_name: HOST_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char, - h_aliases: host_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - h_addrtype: AF_INET, - h_length: 4, - h_addr_list: HOST_ADDR_LIST.as_mut_ptr(), + unsafe { + HOST_ENTRY = hostent { + h_name: HOST_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_ptr() + .cast::(), + h_aliases: host_aliases.as_mut_slice().as_mut_ptr(), + h_addrtype: AF_INET, + h_length: 4, + h_addr_list: (&raw mut HOST_ADDR_LIST).cast(), + } }; - sethostent(HOST_STAYOPEN); - &mut HOST_ENTRY as *mut hostent + unsafe { sethostent(HOST_STAYOPEN) }; + (&raw mut HOST_ENTRY).cast::() } +/// See . pub unsafe extern "C" fn getnetbyaddr(net: u32, net_type: c_int) -> *mut netent { unimplemented!(); } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getnetbyname(name: *const c_char) -> *mut netent { let mut n: *mut netent; - setnetent(NET_STAYOPEN); + unsafe { setnetent(NET_STAYOPEN) }; while { - n = getnetent(); + n = unsafe { getnetent() }; !n.is_null() } { - if strcasecmp((*n).n_name, name) == 0 { - setnetent(NET_STAYOPEN); + if unsafe { strcasecmp((*n).n_name, name) } == 0 { + unsafe { setnetent(NET_STAYOPEN) }; return n; } } - setnetent(NET_STAYOPEN); + unsafe { setnetent(NET_STAYOPEN) }; - platform::errno = ENOENT; - ptr::null_mut() as *mut netent + platform::ERRNO.set(ENOENT); + ptr::null_mut::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getnetent() -> *mut netent { - if NETDB == 0 { - NETDB = Sys::open(&CString::new("/etc/networks").unwrap(), O_RDONLY, 0); + // TODO: Rustify implementation + + if unsafe { NETDB } == 0 { + unsafe { NETDB = Sys::open(c"/etc/networks".into(), O_RDONLY, 0).or_minus_one_errno() }; } - let mut rlb = RawLineBuffer::new(NETDB); - rlb.seek(N_POS); + let mut rlb = RawLineBuffer::new(unsafe { NETDB }); + rlb.seek(unsafe { N_POS }); let mut r: Box = Box::default(); - while r.is_empty() || r.split_whitespace().next() == None || r.starts_with('#') { + while r.is_empty() || r.split_whitespace().next().is_none() || r.starts_with('#') { r = match rlb.next() { Line::Some(s) => bytes_to_box_str(s), _ => { - if NET_STAYOPEN == 0 { - endnetent(); + if unsafe { NET_STAYOPEN } == 0 { + unsafe { endnetent() }; } return ptr::null_mut(); } }; } rlb.next(); - N_POS = rlb.line_pos(); + unsafe { N_POS = rlb.line_pos() }; let mut iter: SplitWhitespace = r.split_whitespace(); - let mut net_name = iter.next().unwrap().as_bytes().to_vec(); - net_name.push(b'\0'); - NET_NAME = Some(net_name); + let net_name = iter.next().unwrap().bytes().chain(Some(b'\0')).collect(); + unsafe { NET_NAME.unsafe_set(Some(net_name)) }; - let mut addr_vec = iter.next().unwrap().as_bytes().to_vec(); - addr_vec.push(b'\0'); - let addr_cstr = addr_vec.as_slice().as_ptr() as *const i8; + let addr_vec: Vec = iter.next().unwrap().bytes().chain(Some(b'\0')).collect(); + let addr_cstr = addr_vec.as_slice().as_ptr().cast::(); let mut addr = mem::MaybeUninit::uninit(); - inet_aton(addr_cstr, addr.as_mut_ptr()); - let addr = addr.assume_init(); - NET_ADDR = Some(ntohl(addr.s_addr)); + unsafe { inet_aton(addr_cstr, addr.as_mut_ptr()) }; + let addr = unsafe { addr.assume_init() }; + unsafe { NET_ADDR = Some(ntohl(addr.s_addr)) }; - let mut _net_aliases: Vec> = Vec::new(); - for s in iter { - let mut alias = s.as_bytes().to_vec(); - alias.push(b'\0'); - _net_aliases.push(alias); - } - let mut net_aliases: Vec<*mut i8> = _net_aliases - .iter_mut() - .map(|x| x.as_mut_ptr() as *mut i8) + let mut _net_aliases: Vec> = iter + .map(|alias| alias.bytes().chain(Some(b'\0')).collect()) .collect(); - net_aliases.push(ptr::null_mut()); - NET_ALIASES = Some(_net_aliases); + let mut net_aliases: Vec<*mut c_char> = _net_aliases + .iter_mut() + .map(|x| x.as_mut_ptr().cast::()) + .chain(Some(ptr::null_mut())) + .collect(); + unsafe { NET_ALIASES.unsafe_set(Some(_net_aliases)) }; - NET_ENTRY = netent { - n_name: NET_NAME.as_mut().unwrap().as_mut_ptr() as *mut c_char, - n_aliases: net_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - n_addrtype: AF_INET, - n_net: NET_ADDR.unwrap() as u64, + unsafe { + NET_ENTRY = netent { + n_name: NET_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_ptr() + .cast::(), + n_aliases: net_aliases.as_mut_slice().as_mut_ptr(), + n_addrtype: AF_INET, + n_net: uint32_t::from(NET_ADDR.unwrap()), + } }; - &mut NET_ENTRY as *mut netent + (&raw mut NET_ENTRY).cast::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getprotobyname(name: *const c_char) -> *mut protoent { let mut p: *mut protoent; - setprotoent(PROTO_STAYOPEN); + unsafe { setprotoent(PROTO_STAYOPEN) }; while { - p = getprotoent(); + p = unsafe { getprotoent() }; !p.is_null() } { - if strcasecmp((*p).p_name, name) == 0 { - setprotoent(PROTO_STAYOPEN); + if unsafe { strcasecmp((*p).p_name, name) } == 0 { + unsafe { setprotoent(PROTO_STAYOPEN) }; return p; } - let mut cp = (*p).p_aliases; + let mut cp = unsafe { (*p).p_aliases }; loop { if cp.is_null() { - setprotoent(PROTO_STAYOPEN); + unsafe { setprotoent(PROTO_STAYOPEN) }; break; } - if (*cp).is_null() { - setprotoent(PROTO_STAYOPEN); + if (unsafe { *cp }).is_null() { + unsafe { setprotoent(PROTO_STAYOPEN) }; break; } - if strcasecmp(*cp, name) == 0 { - setprotoent(PROTO_STAYOPEN); + if unsafe { strcasecmp(*cp, name) } == 0 { + unsafe { setprotoent(PROTO_STAYOPEN) }; return p; } - cp = cp.offset(1); + cp = unsafe { cp.offset(1) }; } } - setprotoent(PROTO_STAYOPEN); + unsafe { setprotoent(PROTO_STAYOPEN) }; - platform::errno = ENOENT; - ptr::null_mut() as *mut protoent + platform::ERRNO.set(ENOENT); + ptr::null_mut::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getprotobynumber(number: c_int) -> *mut protoent { - setprotoent(PROTO_STAYOPEN); + unsafe { setprotoent(PROTO_STAYOPEN) }; let mut p: *mut protoent; while { - p = getprotoent(); + p = unsafe { getprotoent() }; !p.is_null() } { - if (*p).p_proto == number { - setprotoent(PROTO_STAYOPEN); + if unsafe { (*p).p_proto } == number { + unsafe { setprotoent(PROTO_STAYOPEN) }; return p; } } - setprotoent(PROTO_STAYOPEN); - platform::errno = ENOENT; - ptr::null_mut() as *mut protoent + unsafe { setprotoent(PROTO_STAYOPEN) }; + platform::ERRNO.set(ENOENT); + ptr::null_mut::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getprotoent() -> *mut protoent { - if PROTODB == 0 { - PROTODB = Sys::open(&CString::new("/etc/protocols").unwrap(), O_RDONLY, 0); + if unsafe { PROTODB } == 0 { + unsafe { PROTODB = Sys::open(c"/etc/protocols".into(), O_RDONLY, 0).or_minus_one_errno() }; } - let mut rlb = RawLineBuffer::new(PROTODB); - rlb.seek(P_POS); + let mut rlb = RawLineBuffer::new(unsafe { PROTODB }); + rlb.seek(unsafe { P_POS }); let mut r: Box = Box::default(); - while r.is_empty() || r.split_whitespace().next() == None || r.starts_with('#') { + while r.is_empty() || r.split_whitespace().next().is_none() || r.starts_with('#') { r = match rlb.next() { Line::Some(s) => bytes_to_box_str(s), _ => { - if PROTO_STAYOPEN == 0 { - endprotoent(); + if unsafe { PROTO_STAYOPEN } == 0 { + unsafe { endprotoent() }; } return ptr::null_mut(); } }; } rlb.next(); - P_POS = rlb.line_pos(); + unsafe { P_POS = rlb.line_pos() }; let mut iter: SplitWhitespace = r.split_whitespace(); @@ -512,101 +606,115 @@ pub unsafe extern "C" fn getprotoent() -> *mut protoent { let mut num = iter.next().unwrap().as_bytes().to_vec(); num.push(b'\0'); - PROTO_NUM = Some(atoi(num.as_mut_slice().as_mut_ptr() as *mut i8)); + unsafe { PROTO_NUM = Some(atoi(num.as_mut_slice().as_mut_ptr().cast::())) }; - let mut _proto_aliases: Vec> = Vec::new(); - for s in iter { - let mut alias = s.as_bytes().to_vec(); - alias.push(b'\0'); - _proto_aliases.push(alias); - } + let mut _proto_aliases: Vec> = iter + .map(|alias| alias.bytes().chain(Some(b'\0')).collect()) + .collect(); let mut proto_aliases: Vec<*mut i8> = _proto_aliases .iter_mut() - .map(|x| x.as_mut_ptr() as *mut i8) + .map(|x| x.as_mut_ptr().cast::()) + .chain(Some(ptr::null_mut())) .collect(); - proto_aliases.push(ptr::null_mut()); - PROTO_ALIASES = Some(_proto_aliases); - PROTO_NAME = Some(proto_name); + unsafe { PROTO_ALIASES.unsafe_set(Some(_proto_aliases)) }; + unsafe { PROTO_NAME.unsafe_set(Some(proto_name)) }; - PROTO_ENTRY = protoent { - p_name: PROTO_NAME.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char, - p_aliases: proto_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - p_proto: PROTO_NUM.unwrap(), + unsafe { + PROTO_ENTRY = protoent { + p_name: PROTO_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_slice() + .as_mut_ptr() + .cast::(), + p_aliases: proto_aliases + .as_mut_slice() + .as_mut_ptr() + .cast::<*mut c_char>(), + p_proto: PROTO_NUM.unwrap(), + } }; - if PROTO_STAYOPEN == 0 { - endprotoent(); + if unsafe { PROTO_STAYOPEN } == 0 { + unsafe { endprotoent() }; } - &mut PROTO_ENTRY as *mut protoent + (&raw mut PROTO_ENTRY).cast::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getservbyname(name: *const c_char, proto: *const c_char) -> *mut servent { - setservent(SERV_STAYOPEN); + unsafe { setservent(SERV_STAYOPEN) }; let mut p: *mut servent; if proto.is_null() { while { - p = getservent(); + p = unsafe { getservent() }; !p.is_null() } { - if strcasecmp((*p).s_name, name) == 0 { - setservent(SERV_STAYOPEN); + if unsafe { strcasecmp((*p).s_name, name) } == 0 { + unsafe { setservent(SERV_STAYOPEN) }; return p; } } } else { while { - p = getservent(); + p = unsafe { getservent() }; !p.is_null() } { - if strcasecmp((*p).s_name, name) == 0 && strcasecmp((*p).s_proto, proto) == 0 { - setservent(SERV_STAYOPEN); + if unsafe { strcasecmp((*p).s_name, name) } == 0 + && unsafe { strcasecmp((*p).s_proto, proto) } == 0 + { + unsafe { setservent(SERV_STAYOPEN) }; return p; } } } - setservent(SERV_STAYOPEN); - platform::errno = ENOENT; - ptr::null_mut() as *mut servent + unsafe { setservent(SERV_STAYOPEN) }; + platform::ERRNO.set(ENOENT); + ptr::null_mut::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getservbyport(port: c_int, proto: *const c_char) -> *mut servent { - setservent(SERV_STAYOPEN); + unsafe { setservent(SERV_STAYOPEN) }; let mut p: *mut servent; if proto.is_null() { while { - p = getservent(); + p = unsafe { getservent() }; !p.is_null() } { - if (*p).s_port == port { - setservent(SERV_STAYOPEN); + if unsafe { (*p).s_port } == port { + unsafe { setservent(SERV_STAYOPEN) }; return p; } } } else { while { - p = getservent(); + p = unsafe { getservent() }; !p.is_null() } { - if (*p).s_port == port && strcasecmp((*p).s_proto, proto) == 0 { - setservent(SERV_STAYOPEN); + if unsafe { (*p).s_port } == port && unsafe { strcasecmp((*p).s_proto, proto) } == 0 { + unsafe { setservent(SERV_STAYOPEN) }; return p; } } } - setservent(SERV_STAYOPEN); - platform::errno = ENOENT; + unsafe { setservent(SERV_STAYOPEN) }; + platform::ERRNO.set(ENOENT); ptr::null_mut() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getservent() -> *mut servent { - if SERVDB == 0 { - SERVDB = Sys::open(&CString::new("/etc/services").unwrap(), O_RDONLY, 0); + if unsafe { SERVDB } == 0 { + // TODO: Rustify + unsafe { SERVDB = Sys::open(c"/etc/services".into(), O_RDONLY, 0).or_minus_one_errno() }; } - let mut rlb = RawLineBuffer::new(SERVDB); - rlb.seek(S_POS); + let mut rlb = RawLineBuffer::new(unsafe { SERVDB }); + rlb.seek(unsafe { S_POS }); let r: Box = Box::default(); @@ -614,39 +722,39 @@ pub unsafe extern "C" fn getservent() -> *mut servent { let r = match rlb.next() { Line::Some(s) => bytes_to_box_str(s), _ => { - if SERV_STAYOPEN == 0 { - endservent(); + if unsafe { SERV_STAYOPEN } == 0 { + unsafe { endservent() }; } return ptr::null_mut(); } }; let mut iter = r.split_whitespace(); - let mut serv_name = match iter.next() { - Some(serv_name) => serv_name.as_bytes().to_vec(), + let serv_name = match iter.next() { + Some(serv_name) => serv_name.bytes().chain(Some(b'\0')).collect(), None => continue, }; - serv_name.push(b'\0'); let port_proto = match iter.next() { Some(port_proto) => port_proto, None => continue, }; let mut split = port_proto.split('/'); - let mut port = match split.next() { - Some(port) => port.as_bytes().to_vec(), + let mut port: Vec = match split.next() { + Some(port) => port.bytes().chain(Some(b'\0')).collect(), None => continue, }; - port.push(b'\0'); - SERV_PORT = - Some(htons(atoi(port.as_mut_slice().as_mut_ptr() as *mut i8) as u16) as u32 as i32); - let mut proto = match split.next() { - Some(proto) => proto.as_bytes().to_vec(), + unsafe { + SERV_PORT = Some(u32::from(htons( + atoi(port.as_mut_slice().as_mut_ptr().cast::()) as u16, + )) as i32) + }; + let proto = match split.next() { + Some(proto) => proto.bytes().chain(Some(b'\0')).collect(), None => continue, }; - proto.push(b'\0'); rlb.next(); - S_POS = rlb.line_pos(); + unsafe { S_POS = rlb.line_pos() }; /* *let mut _serv_aliases: Vec> = Vec::new(); @@ -668,83 +776,98 @@ pub unsafe extern "C" fn getservent() -> *mut servent { serv_aliases.push(ptr::null_mut()); serv_aliases.push(ptr::null_mut()); - SERV_ALIASES = Some(_serv_aliases); - SERV_NAME = Some(serv_name); - SERV_PROTO = Some(proto); + unsafe { SERV_ALIASES.unsafe_set(Some(_serv_aliases)) }; + unsafe { SERV_NAME.unsafe_set(Some(serv_name)) }; + unsafe { SERV_PROTO.unsafe_set(Some(proto)) }; - SERV_ENTRY = servent { - s_name: SERV_NAME.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char, - s_aliases: serv_aliases.as_mut_slice().as_mut_ptr() as *mut *mut i8, - s_port: SERV_PORT.unwrap(), - s_proto: SERV_PROTO.as_mut().unwrap().as_mut_slice().as_mut_ptr() as *mut c_char, + unsafe { + SERV_ENTRY = servent { + s_name: SERV_NAME + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_slice() + .as_mut_ptr() + .cast::(), + s_aliases: serv_aliases + .as_mut_slice() + .as_mut_ptr() + .cast::<*mut c_char>(), + s_port: SERV_PORT.unwrap(), + s_proto: SERV_PROTO + .unsafe_mut() + .as_mut() + .unwrap() + .as_mut_slice() + .as_mut_ptr() + .cast::(), + } }; - if SERV_STAYOPEN == 0 { - endservent(); + if unsafe { SERV_STAYOPEN } == 0 { + unsafe { endservent() }; } - break &mut SERV_ENTRY as *mut servent; + break (&raw mut SERV_ENTRY).cast::(); } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn setnetent(stayopen: c_int) { - NET_STAYOPEN = stayopen; - if NETDB == 0 { - NETDB = Sys::open(&CString::new("/etc/networks").unwrap(), O_RDONLY, 0) + unsafe { NET_STAYOPEN = stayopen }; + if unsafe { NETDB } == 0 { + unsafe { NETDB = Sys::open(c"/etc/networks".into(), O_RDONLY, 0).or_minus_one_errno() } } else { - Sys::lseek(NETDB, 0, SEEK_SET); - N_POS = 0; + if Sys::lseek(unsafe { NETDB }, 0, SEEK_SET).is_ok() {}; // TODO handle errror + unsafe { N_POS = 0 }; } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn setprotoent(stayopen: c_int) { - PROTO_STAYOPEN = stayopen; - if PROTODB == 0 { - PROTODB = Sys::open(&CString::new("/etc/protocols").unwrap(), O_RDONLY, 0) + unsafe { PROTO_STAYOPEN = stayopen }; + if unsafe { PROTODB } == 0 { + unsafe { PROTODB = Sys::open(c"/etc/protocols".into(), O_RDONLY, 0).or_minus_one_errno() } } else { - Sys::lseek(PROTODB, 0, SEEK_SET); - P_POS = 0; + if Sys::lseek(unsafe { PROTODB }, 0, SEEK_SET).is_ok() {}; // TODO handle error + unsafe { P_POS = 0 }; } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn setservent(stayopen: c_int) { - SERV_STAYOPEN = stayopen; - if SERVDB == 0 { - SERVDB = Sys::open(&CString::new("/etc/services").unwrap(), O_RDONLY, 0) + unsafe { SERV_STAYOPEN = stayopen }; + if unsafe { SERVDB } == 0 { + unsafe { SERVDB = Sys::open(c"/etc/services".into(), O_RDONLY, 0).or_minus_one_errno() } } else { - Sys::lseek(SERVDB, 0, SEEK_SET); - S_POS = 0; + if Sys::lseek(unsafe { SERVDB }, 0, SEEK_SET).is_ok() {}; // TODO handle error + unsafe { S_POS = 0 }; } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getaddrinfo( node: *const c_char, service: *const c_char, hints: *const addrinfo, res: *mut *mut addrinfo, ) -> c_int { - //TODO: getaddrinfo - let node_opt = if node.is_null() { + let node_opt = unsafe { CStr::from_nullable_ptr(node) }; + let service_opt = unsafe { CStr::from_nullable_ptr(service) }; + + let hints_opt = if hints.is_null() { None } else { - Some(CStr::from_ptr(node)) + Some(unsafe { &*hints }) }; - let service_opt = if service.is_null() { - None - } else { - Some(CStr::from_ptr(service)) - }; - - let hints_opt = if hints.is_null() { None } else { Some(&*hints) }; - - trace!( + log::trace!( "getaddrinfo({:?}, {:?}, {:?})", - node_opt.map(|c| str::from_utf8_unchecked(c.to_bytes())), - service_opt.map(|c| str::from_utf8_unchecked(c.to_bytes())), + node_opt.map(|c| unsafe { str::from_utf8_unchecked(c.to_bytes()) }), + service_opt.map(|c| unsafe { str::from_utf8_unchecked(c.to_bytes()) }), hints_opt ); @@ -754,71 +877,90 @@ pub unsafe extern "C" fn getaddrinfo( let ai_socktype = hints_opt.map_or(0, |hints| hints.ai_socktype); let mut ai_protocol; // = hints_opt.map_or(0, |hints| hints.ai_protocol); - *res = ptr::null_mut(); + unsafe { *res = ptr::null_mut() }; let mut port = 0; if let Some(service) = service_opt { //TODO: Support other service definitions as well as AI_NUMERICSERV - match str::from_utf8_unchecked(service.to_bytes()).parse::() { + match unsafe { str::from_utf8_unchecked(service.to_bytes()) }.parse::() { Ok(ok) => port = ok, Err(_err) => (), } } + let node = node_opt.unwrap_or_else(|| { + //TODO: Optimize by bypassing string parsing + if ai_flags & AI_PASSIVE > 0 { + c"0.0.0.0".into() + } else { + c"127.0.0.1".into() + } + }); - //TODO: Check hosts file - if let Some(node) = node_opt { - //TODO: Support AI_NUMERICHOST - let lookuphost = match lookup_host(str::from_utf8_unchecked(node.to_bytes())) { + let lookuphost = if ai_flags & AI_NUMERICHOST > 0 { + match parse_ipv4_string(unsafe { str::from_utf8_unchecked(node.to_bytes()) }) { + Some(s_addr) => vec![in_addr { s_addr }], + None => { + return EAI_NONAME; + } + } + } else { + match lookup_host(unsafe { str::from_utf8_unchecked(node.to_bytes()) }) { Ok(lookuphost) => lookuphost, Err(e) => { - platform::errno = e; + platform::ERRNO.set(e); return EAI_SYSTEM; } + } + }; + + for in_addr in lookuphost { + ai_family = AF_INET; + ai_protocol = 0; + + let ai_addr = Box::into_raw(Box::new(sockaddr_in { + sin_family: ai_family as sa_family_t, + sin_port: htons(port), + sin_addr: in_addr, + sin_zero: [0; 8], + })) + .cast::(); + + let ai_addrlen = mem::size_of::() as socklen_t; + + let ai_canonname = if ai_flags & AI_CANONNAME > 0 { + if node_opt.is_none() { + return EAI_BADFLAGS; + } + ai_flags &= !AI_CANONNAME; + node.to_owned_cstring().into_raw() + } else { + ptr::null_mut() }; - for in_addr in lookuphost { - ai_family = AF_INET; - ai_protocol = 0; - - let ai_addr = Box::into_raw(Box::new(sockaddr_in { - sin_family: ai_family as sa_family_t, - sin_port: htons(port), - sin_addr: in_addr, - sin_zero: [0; 8], - })) as *mut sockaddr; - - let ai_addrlen = mem::size_of::(); - - let ai_canonname = if ai_flags & AI_CANONNAME > 0 { - ai_flags &= !AI_CANONNAME; - node.to_owned().into_raw() - } else { - ptr::null_mut() - }; - - let addrinfo = Box::new(addrinfo { - ai_flags: 0, - ai_family, - ai_socktype, - ai_protocol, - ai_addrlen, - ai_canonname, - ai_addr, - ai_next: ptr::null_mut(), - }); - + let addrinfo = Box::new(addrinfo { + ai_flags: 0, + ai_family, + ai_socktype, + ai_protocol, + ai_addrlen, + ai_canonname, + ai_addr, + ai_next: ptr::null_mut(), + }); + unsafe { let mut indirect = res; while !(*indirect).is_null() { - indirect = &mut (**indirect).ai_next; + indirect = &raw mut (**indirect).ai_next; } - *indirect = Box::into_raw(addrinfo); + *indirect = Box::into_raw(addrinfo) } } 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getnameinfo( addr: *const sockaddr, addrlen: socklen_t, @@ -828,68 +970,189 @@ pub unsafe extern "C" fn getnameinfo( servlen: socklen_t, flags: c_int, ) -> c_int { - //TODO: getnameinfo - if addrlen as usize != mem::size_of::() { + if addr.is_null() || addrlen as usize != mem::size_of::() { return EAI_FAMILY; } - let addr = &*(addr as *const sockaddr_in); + let sa = unsafe { &*(addr.cast::()) }; - let host_opt = if host.is_null() { - None - } else { - Some(slice::from_raw_parts_mut(host, hostlen as usize)) - }; + if !serv.is_null() && servlen > 0 { + if flags & NI_NUMERICSERV != 0 { + let port_str = sa.sin_port.to_be().to_string(); + let port_bytes = port_str.as_bytes(); + if (servlen as usize) <= port_bytes.len() { + return EAI_MEMORY; // Buffer too small + } + unsafe { + ptr::copy_nonoverlapping( + port_bytes.as_ptr().cast::(), + serv, + port_bytes.len(), + ) + }; + unsafe { *serv.add(port_bytes.len()) = 0 }; + } else { + // TODO: Implement service name lookup (e.g., from /etc/services) + unsafe { *serv = 0 }; + } + } - let serv_opt = if serv.is_null() { - None - } else { - Some(slice::from_raw_parts_mut(serv, servlen as usize)) - }; + if !host.is_null() && hostlen > 0 { + if flags & NI_NUMERICHOST != 0 { + let ip_addr = Ipv4Addr::from(sa.sin_addr.s_addr.to_be()); + let ip_str = ip_addr.to_string(); + let ip_bytes = ip_str.as_bytes(); + if (hostlen as usize) <= ip_bytes.len() { + return EAI_MEMORY; // Buffer too small + } + unsafe { + ptr::copy_nonoverlapping(ip_bytes.as_ptr().cast::(), host, ip_bytes.len()) + }; + unsafe { *host.add(ip_bytes.len()) = 0 }; + } else { + match lookup_addr(sa.sin_addr.clone()).map(|host_names| host_names.into_iter().next()) { + Ok(Some(hostname)) => { + if (hostlen as usize) <= hostname.len() { + return EAI_MEMORY; // Buffer too small + } + unsafe { + ptr::copy_nonoverlapping( + hostname.as_ptr().cast::(), + host, + hostname.len(), + ); + *host.add(hostname.len()) = 0; + } + } + Ok(None) => { + if flags & NI_NAMEREQD != 0 { + return EAI_NONAME; + } + } + Err(_) => { + if flags & NI_NAMEREQD != 0 { + return EAI_NONAME; + } + let ip_addr = Ipv4Addr::from(sa.sin_addr.s_addr.to_be()); + let ip_str = ip_addr.to_string(); + let ip_bytes = ip_str.as_bytes(); + if (hostlen as usize) <= ip_bytes.len() { + return EAI_MEMORY; + } + unsafe { + ptr::copy_nonoverlapping( + ip_bytes.as_ptr().cast::(), + host, + ip_bytes.len(), + ); + *host.add(ip_bytes.len()) = 0; + } + } + } + } + } - eprintln!("getnameinfo({:p}, {}, {:#x})", addr, addrlen, flags); - - platform::errno = ENOSYS; - EAI_SYSTEM + 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn freeaddrinfo(res: *mut addrinfo) { let mut ai = res; while !ai.is_null() { - let bai = Box::from_raw(ai); + let bai = unsafe { Box::from_raw(ai) }; if !bai.ai_canonname.is_null() { - CString::from_raw(bai.ai_canonname); + drop(unsafe { CString::from_raw(bai.ai_canonname) }); } if !bai.ai_addr.is_null() { - if bai.ai_addrlen == mem::size_of::() { - Box::from_raw(bai.ai_addr as *mut sockaddr_in); - } else if bai.ai_addrlen == mem::size_of::() { - Box::from_raw(bai.ai_addr as *mut sockaddr_in6); + if bai.ai_addrlen == mem::size_of::() as socklen_t { + unsafe { drop(Box::from_raw(bai.ai_addr.cast::())) }; + } else if bai.ai_addrlen == mem::size_of::() as socklen_t { + unsafe { drop(Box::from_raw(bai.ai_addr.cast::())) }; } else { - eprintln!("freeaddrinfo: unknown ai_addrlen {}", bai.ai_addrlen); + todo_skip!(0, "freeaddrinfo: unknown ai_addrlen {}", bai.ai_addrlen); } } ai = bai.ai_next; } } -#[no_mangle] -pub extern "C" fn gai_strerror(errcode: c_int) -> *const c_char { +/// See . +#[unsafe(no_mangle)] +pub const extern "C" fn gai_strerror(errcode: c_int) -> *const c_char { match errcode { - EAI_BADFLAGS => c_str!("Invalid flags"), - EAI_NONAME => c_str!("Name does not resolve"), - EAI_AGAIN => c_str!("Try again"), - EAI_FAIL => c_str!("Non-recoverable error"), - EAI_NODATA => c_str!("Unknown error"), - EAI_FAMILY => c_str!("Unrecognized address family or invalid length"), - EAI_SOCKTYPE => c_str!("Unrecognized socket type"), - EAI_SERVICE => c_str!("Unrecognized service"), - EAI_ADDRFAMILY => c_str!("Address family for name not supported"), - EAI_MEMORY => c_str!("Out of memory"), - EAI_SYSTEM => c_str!("System error"), - EAI_OVERFLOW => c_str!("Overflow"), - _ => c_str!("Unknown error"), + EAI_BADFLAGS => c"Invalid flags", + EAI_NONAME => c"Name does not resolve", + EAI_AGAIN => c"Try again", + EAI_FAIL => c"Non-recoverable error", + EAI_NODATA => c"Unknown error", + EAI_FAMILY => c"Unrecognized address family or invalid length", + EAI_SOCKTYPE => c"Unrecognized socket type", + EAI_SERVICE => c"Unrecognized service", + EAI_ADDRFAMILY => c"Address family for name not supported", + EAI_MEMORY => c"Out of memory", + EAI_SYSTEM => c"System error", + EAI_OVERFLOW => c"Overflow", + _ => c"Unknown error", } .as_ptr() } + +/// Provide a pointer to relibc's internal [`H_ERRNO`]. +#[unsafe(no_mangle)] +#[deprecated] +pub extern "C" fn __h_errno_location() -> *mut c_int { + H_ERRNO.as_ptr() +} + +#[unsafe(no_mangle)] +#[deprecated] +pub const extern "C" fn hstrerror(errcode: c_int) -> *const c_char { + match errcode { + H_UNSET => c"Resolver error unset", + HOST_NOT_FOUND => c"Unknown hostname", + NO_DATA => c"No address for hostname", + NO_RECOVERY => c"Unknown server error", + TRY_AGAIN => c"Hostname lookup failure", + _ => c"Unknown error", + } + .as_ptr() +} + +/// Print error message associated with [`H_ERRNO`] to stderr. +/// +/// # Arguments +/// * `prefix` - An optional prefix to prepend to the error message. May be null or an empty +/// (`""`) C string. +/// +/// # Safety +/// Like [`crate::header::stdio::perror`], `prefix` should be a valid, NUL terminated C string if +/// used. The caller may safely call this function with a null pointer. +/// +/// # Deprecation +/// [`H_ERRNO`], [`hstrerror`], [`herror`], and other functions are deprecated as of +/// POSIX.1-2001 and removed as of POSIX.1-2008. These functions are provided for backwards +/// compatibility but should not be used by new code. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +#[unsafe(no_mangle)] +#[deprecated] +pub extern "C" fn herror(prefix: *const c_char) { + let code = H_ERRNO.get(); + // Safety: `hstrerror` handles every error code case and always returns a valid C string + let error = unsafe { + #[allow(deprecated)] + let msg_cstr = CStr::from_ptr(hstrerror(code)); + str::from_utf8_unchecked(msg_cstr.to_bytes()) + }; + + let mut writer = platform::FileWriter::new(2); + // Prefix is optional + match unsafe { CStr::from_nullable_ptr(prefix) } + .and_then(|prefix| str::from_utf8(prefix.to_bytes()).ok()) + { + Some(prefix) if !prefix.is_empty() => writer + .write_fmt(format_args!("{prefix}: {error}\n")) + .unwrap(), + _ => writer.write_fmt(format_args!("{error}\n")).unwrap(), + } +} diff --git a/src/header/netdb/redox.rs b/src/header/netdb/redox.rs index d52628b186..314dffcd05 100644 --- a/src/header/netdb/redox.rs +++ b/src/header/netdb/redox.rs @@ -1,9 +1,16 @@ -use crate::{c_str::CString, fs::File, header::fcntl, io::Read}; +use crate::{ + error::Errno, + fs::File, + header::{errno, fcntl}, + io::Read, +}; use alloc::string::String; -pub fn get_dns_server() -> String { +pub fn get_dns_server() -> Result { let mut string = String::new(); - let mut file = File::open(&CString::new("/etc/net/dns").unwrap(), fcntl::O_RDONLY).unwrap(); // TODO: error handling - file.read_to_string(&mut string).unwrap(); // TODO: error handling - string + let mut file = File::open(c"/etc/net/dns".into(), fcntl::O_RDONLY)?; + file.read_to_string(&mut string) + .map_err(|_| Errno(errno::EIO).sync())?; + + Ok(string) } diff --git a/src/header/netinet_in/cbindgen.toml b/src/header/netinet_in/cbindgen.toml index 2467174a3b..356053ec8b 100644 --- a/src/header/netinet_in/cbindgen.toml +++ b/src/header/netinet_in/cbindgen.toml @@ -1,13 +1,123 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/netinet_in.h.html +# +# Spec quotations relating to includes: +# - "The header shall define in_port_t Equivalent to the type uint16_t as described in ." +# - "The header shall define in_addr_t Equivalent to the type uint32_t as described in ." +# - "The header shall define the sa_family_t type as described in ." +# - "The header shall define the uint8_t and uint32_t types as described in ." +# - "Inclusion of the header may also make visible all symbols from and ." +# - "The htonl(), htons(), ntohl(), and ntohs() functions shall be available as described in ." +# - "Inclusion of the header may also make visible all symbols from ." +# +# Possible cycle between arpa/inet.h and netinet/in.h solved by: +# - including netinet/in.h in arpa/inet.h +# - splitting out functions (htonl, htons, ntohl, ntohs) into bits/arpainet.h +# +# sys/types.h needed for c_int and c_char +# bits/arpainet.h brings in inttypes.h sys_includes = ["sys/types.h", "sys/socket.h"] -include_guard = "_NETINET_IN_H" -trailer = "#include " +after_includes = """ +#include // for htonl, htons, ntohl, ntohs +""" +include_guard = "_RELIBC_NETINET_IN_H" +trailer = """ +#ifndef _RELIBC_BITS_NETINET_IN_H +#define _RELIBC_BITS_NETINET_IN_H + +extern const struct in6_addr in6addr_any; +extern const struct in6_addr 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 } } } + +// from musl { +#define IN6_IS_ADDR_UNSPECIFIED(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && ((uint32_t *) (a))[3] == 0) + +#define IN6_IS_ADDR_LOOPBACK(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && \ + ((uint8_t *) (a))[12] == 0 && ((uint8_t *) (a))[13] == 0 && \ + ((uint8_t *) (a))[14] == 0 && ((uint8_t *) (a))[15] == 1 ) + +#define IN6_IS_ADDR_MULTICAST(a) (((uint8_t *) (a))[0] == 0xff) + +#define IN6_IS_ADDR_LINKLOCAL(a) \ + ((((uint8_t *) (a))[0]) == 0xfe && (((uint8_t *) (a))[1] & 0xc0) == 0x80) + +#define IN6_IS_ADDR_SITELOCAL(a) \ + ((((uint8_t *) (a))[0]) == 0xfe && (((uint8_t *) (a))[1] & 0xc0) == 0xc0) + +#define IN6_IS_ADDR_V4MAPPED(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint8_t *) (a))[8] == 0 && ((uint8_t *) (a))[9] == 0 && \ + ((uint8_t *) (a))[10] == 0xff && ((uint8_t *) (a))[11] == 0xff) + +#define IN6_IS_ADDR_V4COMPAT(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && ((uint8_t *) (a))[15] > 1) + +#define IN6_IS_ADDR_MC_NODELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x1)) + +#define IN6_IS_ADDR_MC_LINKLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x2)) + +#define IN6_IS_ADDR_MC_SITELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x5)) + +#define IN6_IS_ADDR_MC_ORGLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0x8)) + +#define IN6_IS_ADDR_MC_GLOBAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && ((((uint8_t *) (a))[1] & 0xf) == 0xe)) + +#define __ARE_4_EQUAL(a,b) \ + (!( (0[a]-0[b]) | (1[a]-1[b]) | (2[a]-2[b]) | (3[a]-3[b]) )) +#define IN6_ARE_ADDR_EQUAL(a,b) \ + __ARE_4_EQUAL((const uint32_t *)(a), (const uint32_t *)(b)) + +#define IN_CLASSA(a) ((((in_addr_t)(a)) & 0x80000000) == 0) +#define IN_CLASSA_NET 0xff000000 +#define IN_CLASSA_NSHIFT 24 +#define IN_CLASSA_HOST (0xffffffff & ~IN_CLASSA_NET) +#define IN_CLASSA_MAX 128 +#define IN_CLASSB(a) ((((in_addr_t)(a)) & 0xc0000000) == 0x80000000) +#define IN_CLASSB_NET 0xffff0000 +#define IN_CLASSB_NSHIFT 16 +#define IN_CLASSB_HOST (0xffffffff & ~IN_CLASSB_NET) +#define IN_CLASSB_MAX 65536 +#define IN_CLASSC(a) ((((in_addr_t)(a)) & 0xe0000000) == 0xc0000000) +#define IN_CLASSC_NET 0xffffff00 +#define IN_CLASSC_NSHIFT 8 +#define IN_CLASSC_HOST (0xffffffff & ~IN_CLASSC_NET) +#define IN_CLASSD(a) ((((in_addr_t)(a)) & 0xf0000000) == 0xe0000000) +#define IN_MULTICAST(a) IN_CLASSD(a) +#define IN_EXPERIMENTAL(a) ((((in_addr_t)(a)) & 0xe0000000) == 0xe0000000) +#define IN_BADCLASS(a) ((((in_addr_t)(a)) & 0xf0000000) == 0xf0000000) +// } from musl + +#endif // _RELIBC_BITS_NETINET_IN_H +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [export] -include = ["sockaddr_in6", "sockaddr_in", "ipv6_mreq"] +include = [ + "sockaddr_in6", + "sockaddr_in", + "ipv6_mreq", + "ip_mreq", + "ip_mreq_source", + "group_req", + "group_source_req", + "in6_pktinfo", +] + +[export.rename] +"sockaddr_storage" = "struct sockaddr_storage" [enum] prefix_with_name = true diff --git a/src/header/netinet_in/mod.rs b/src/header/netinet_in/mod.rs index 0a6e24a916..9a2a9131dc 100644 --- a/src/header/netinet_in/mod.rs +++ b/src/header/netinet_in/mod.rs @@ -1,21 +1,33 @@ +//! `netinet/in.h` implementation. +//! +//! See . + #![allow(non_camel_case_types)] -use crate::{header::sys_socket::sa_family_t, platform::types::*}; +use crate::{ + header::{bits_safamily_t::sa_family_t, sys_socket::sockaddr_storage}, + platform::types::{c_char, c_int}, +}; +/// See . pub type in_addr_t = u32; +/// See . pub type in_port_t = u16; +/// See . #[repr(C)] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] pub struct in_addr { pub s_addr: in_addr_t, } +/// See . #[repr(C)] pub struct in6_addr { pub s6_addr: [u8; 16], } +/// See . #[repr(C)] #[derive(Debug, Default)] pub struct sockaddr_in { @@ -25,6 +37,7 @@ pub struct sockaddr_in { pub sin_zero: [c_char; 8], } +/// See . #[repr(C)] pub struct sockaddr_in6 { pub sin6_family: sa_family_t, @@ -34,41 +47,163 @@ pub struct sockaddr_in6 { pub sin6_scope_id: u32, } +/// See . #[repr(C)] pub struct ipv6_mreq { pub ipv6mr_multiaddr: in6_addr, pub ipv6mr_interface: u32, } +/// See . +#[repr(C)] +pub struct in6_pktinfo { + pub ipi6_addr: in6_addr, + pub ipi6_ifindex: u32, +} + +impl Clone for in6_pktinfo { + fn clone(&self) -> Self { + Self { + ipi6_addr: in6_addr { s6_addr: self.ipi6_addr.s6_addr }, + ipi6_ifindex: self.ipi6_ifindex, + } + } +} + +impl Default for in6_pktinfo { + fn default() -> Self { + Self { + ipi6_addr: in6_addr { s6_addr: [0; 16] }, + ipi6_ifindex: 0, + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_in6_pktinfo(in6_pktinfo: in6_pktinfo) {} + // Address String Lengths +/// See . pub const INET_ADDRSTRLEN: c_int = 16; +/// See . pub const INET6_ADDRSTRLEN: c_int = 46; +// IP options +/// Non-POSIX, see . +pub const IP_TOS: c_int = 1; +/// Non-POSIX, see . +pub const IP_RECVTOS: c_int = 13; + // Protocol Numbers -pub const IPPROTO_IP: u8 = 0x00; -pub const IPPROTO_ICMP: u8 = 0x01; -pub const IPPROTO_TCP: u8 = 0x06; -pub const IPPROTO_UDP: u8 = 0x11; -pub const IPPROTO_IPV6: u8 = 0x29; +/// See . +pub const IPPROTO_IP: u8 = 0; +/// See . +pub const IPPROTO_ICMP: u8 = 1; +/// Non-POSIX. +pub const IPPROTO_IGMP: u8 = 2; +/// See . +pub const IPPROTO_TCP: u8 = 6; +/// Non-POSIX. +pub const IPPROTO_PUP: u8 = 12; +/// See . +pub const IPPROTO_UDP: u8 = 17; +/// Non-POSIX. +pub const IPPROTO_IDP: u8 = 22; +/// See . +pub const IPPROTO_IPV6: u8 = 41; +/// See . pub const IPPROTO_RAW: u8 = 0xff; +/// Non-POSIX. pub const IPPROTO_MAX: u8 = 0xff; -pub const INADDR_ANY: u32 = 0; // Can't use in_addr_t alias because cbindgen :( -pub const INADDR_BROADCAST: u32 = 0xFFFF_FFFF; // Can't use core::u32::MAX because cbindgen :( -pub const INADDR_NONE: u32 = 0xFFFF_FFFF; -pub const INADDR_LOOPBACK: u32 = 0x7F00_0001; +/// Non-POSIX, see . +pub const IP_TTL: c_int = 2; +/// See . +pub const IPV6_UNICAST_HOPS: c_int = 16; +/// See . +pub const IPV6_MULTICAST_IF: c_int = 17; +/// See . +pub const IPV6_MULTICAST_HOPS: c_int = 18; +/// See . +pub const IPV6_MULTICAST_LOOP: c_int = 19; +/// Non-POSIX. +pub const IPV6_ADD_MEMBERSHIP: c_int = 20; +pub const IPV6_JOIN_GROUP: c_int = 20; +/// Non-POSIX. +pub const IPV6_DROP_MEMBERSHIP: c_int = 21; +pub const IPV6_LEAVE_GROUP: c_int = 21; +/// See . +pub const IPV6_V6ONLY: c_int = 26; +/// Non-POSIX, see . +pub const IPV6_PKTINFO: c_int = 50; +/// Non-POSIX, see . +pub const IPV6_RECVPKTINFO: c_int = 49; +/// Non-POSIX, see . +pub const IP_MULTICAST_IF: c_int = 32; +/// Non-POSIX, see . +pub const IP_MULTICAST_TTL: c_int = 33; +/// Non-POSIX, see . +pub const IP_MULTICAST_LOOP: c_int = 34; +/// Non-POSIX, see . +pub const IP_ADD_MEMBERSHIP: c_int = 35; +/// Non-POSIX, see . +pub const IP_DROP_MEMBERSHIP: c_int = 36; +/// See . +pub const INADDR_ANY: u32 = 0; // Can't use in_addr_t alias because cbindgen :( +/// See . +pub const INADDR_BROADCAST: u32 = 0xFFFF_FFFF; // Can't use core::u32::MAX because cbindgen :( +/// Non-POSIX. +pub const INADDR_NONE: u32 = 0xFFFF_FFFF; +/// Non-POSIX, see . +pub const INADDR_LOOPBACK: u32 = 0x7F00_0001; +/// Non-POSIX. pub const INADDR_UNSPEC_GROUP: u32 = 0xE000_0000; +/// Non-POSIX. pub const INADDR_ALLHOSTS_GROUP: u32 = 0xE000_0001; +/// Non-POSIX. pub const INADDR_ALLRTRS_GROUP: u32 = 0xE000_0002; +/// Non-POSIX. pub const INADDR_MAX_LOCAL_GROUP: u32 = 0xE000_00FF; -#[no_mangle] +/// Non-POSIX, see . +#[repr(C)] +pub struct ip_mreq_source { + pub imr_multiaddr: in_addr, + pub imr_interface: in_addr, + pub imr_sourceaddr: in_addr, +} + +/// Non-POSIX, see . +#[repr(C)] +pub struct ip_mreq { + pub imr_multiaddr: in_addr, + pub imr_interface: in_addr, +} + +/// Non-POSIX. +#[repr(C)] +pub struct group_req { + pub gr_interface: u32, + pub gr_group: sockaddr_storage, +} + +/// Non-POSIX. +#[repr(C)] +pub struct group_source_req { + pub gsr_interface: u32, + pub gsr_group: sockaddr_storage, + pub gsr_source: sockaddr_storage, +} + +/// See . +#[unsafe(no_mangle)] pub static in6addr_any: in6_addr = in6_addr { s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], }; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub static in6addr_loopback: in6_addr = in6_addr { s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], }; diff --git a/src/header/netinet_ip/mod.rs b/src/header/netinet_ip/mod.rs index 8b13789179..30ddb6ebf4 100644 --- a/src/header/netinet_ip/mod.rs +++ b/src/header/netinet_ip/mod.rs @@ -1 +1,3 @@ - +//! `netinet/ip.h` implementation. +//! +//! Non-POSIX. diff --git a/src/header/netinet_tcp/cbindgen.toml b/src/header/netinet_tcp/cbindgen.toml index 50e883fdcd..64807753a2 100644 --- a/src/header/netinet_tcp/cbindgen.toml +++ b/src/header/netinet_tcp/cbindgen.toml @@ -1,3 +1,6 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/netinet_tcp.h.html +# +# There are no spec quotations relating to includes sys_includes = [] include_guard = "_NETINET_TCP_H" language = "C" diff --git a/src/header/netinet_tcp/mod.rs b/src/header/netinet_tcp/mod.rs index f3694c629a..99812e14e5 100644 --- a/src/header/netinet_tcp/mod.rs +++ b/src/header/netinet_tcp/mod.rs @@ -1,3 +1,16 @@ -use crate::platform::types::*; +//! `netinet/tcp.h` implementation. +//! +//! See . +use crate::platform::types::c_int; + +/// See . pub const TCP_NODELAY: c_int = 1; +/// Non-POSIX, see . +pub const TCP_MAXSEG: c_int = 2; +/// Non-POSIX, see . +pub const TCP_KEEPIDLE: c_int = 4; +/// Non-POSIX, see . +pub const TCP_KEEPINTVL: c_int = 5; +/// Non-POSIX, see . +pub const TCP_KEEPCNT: c_int = 6; diff --git a/src/header/poll/cbindgen.toml b/src/header/poll/cbindgen.toml index ac981e6ad1..27aeec4a78 100644 --- a/src/header/poll/cbindgen.toml +++ b/src/header/poll/cbindgen.toml @@ -1,9 +1,15 @@ -sys_includes = [] +sys_includes = ["signal.h"] include_guard = "_RELIBC_POLL_H" +after_includes = """ +#include // for timespec +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] prefix_with_name = true + +[export.rename] +"timespec" = "struct timespec" diff --git a/src/header/poll/mod.rs b/src/header/poll/mod.rs index 3d46779d8d..8f42cf8366 100644 --- a/src/header/poll/mod.rs +++ b/src/header/poll/mod.rs @@ -1,14 +1,25 @@ -//! poll implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/poll.h.html +//! `poll.h` implementation. +//! +//! See . -use core::{mem, slice}; +use core::{mem, ptr, slice}; use crate::{ fs::File, - header::sys_epoll::{ - epoll_create1, epoll_ctl, epoll_data, epoll_event, epoll_wait, EPOLLERR, EPOLLHUP, EPOLLIN, - EPOLLNVAL, EPOLLOUT, EPOLLPRI, EPOLL_CLOEXEC, EPOLL_CTL_ADD, + header::{ + bits_sigset_t::sigset_t, + bits_timespec::timespec, + errno::EBADF, + sys_epoll::{ + EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLLERR, EPOLLHUP, EPOLLIN, EPOLLNVAL, EPOLLOUT, + EPOLLPRI, EPOLLRDBAND, EPOLLRDNORM, EPOLLWRBAND, EPOLLWRNORM, epoll_create1, epoll_ctl, + epoll_data, epoll_event, epoll_pwait, + }, + }, + platform::{ + self, + types::{c_int, c_short, c_ulong}, }, - platform::types::*, }; pub const POLLIN: c_short = 0x001; @@ -17,9 +28,14 @@ pub const POLLOUT: c_short = 0x004; pub const POLLERR: c_short = 0x008; pub const POLLHUP: c_short = 0x010; pub const POLLNVAL: c_short = 0x020; +pub const POLLRDNORM: c_short = 0x040; +pub const POLLRDBAND: c_short = 0x080; +pub const POLLWRNORM: c_short = 0x100; +pub const POLLWRBAND: c_short = 0x200; pub type nfds_t = c_ulong; +/// See . #[repr(C)] pub struct pollfd { pub fd: c_int, @@ -27,7 +43,7 @@ pub struct pollfd { pub revents: c_short, } -pub fn poll_epoll(fds: &mut [pollfd], timeout: c_int) -> c_int { +pub unsafe fn poll_epoll(fds: &mut [pollfd], timeout: c_int, sigmask: *const sigset_t) -> c_int { let event_map = [ (POLLIN, EPOLLIN), (POLLPRI, EPOLLPRI), @@ -35,6 +51,10 @@ pub fn poll_epoll(fds: &mut [pollfd], timeout: c_int) -> c_int { (POLLERR, EPOLLERR), (POLLHUP, EPOLLHUP), (POLLNVAL, EPOLLNVAL), + (POLLRDNORM, EPOLLRDNORM), + (POLLWRNORM, EPOLLWRNORM), + (POLLRDBAND, EPOLLRDBAND), + (POLLWRBAND, EPOLLWRBAND), ]; let ep = { @@ -45,8 +65,16 @@ pub fn poll_epoll(fds: &mut [pollfd], timeout: c_int) -> c_int { File::new(epfd) }; - for i in 0..fds.len() { - let mut pfd = &mut fds[i]; + let mut closed = 0; + for (i, fd) in fds.iter_mut().enumerate() { + let pfd = fd; + + pfd.revents = 0; + + // Ignore the entry with negative fd + if pfd.fd < 0 { + continue; + } let mut event = epoll_event { events: 0, @@ -60,15 +88,31 @@ pub fn poll_epoll(fds: &mut [pollfd], timeout: c_int) -> c_int { } } - pfd.revents = 0; - - if epoll_ctl(*ep, EPOLL_CTL_ADD, pfd.fd, &mut event) < 0 { - return -1; + if unsafe { epoll_ctl(*ep, EPOLL_CTL_ADD, pfd.fd, &raw mut event) } < 0 { + if platform::ERRNO.get() == EBADF { + pfd.revents |= POLLNVAL; + closed += 1; + } else { + return -1; + } } } + // Early exit if there are fds, and all are closed (revents = POLLNVAL) + if closed > 0 && closed == fds.len() { + return closed as i32; + } + let mut events: [epoll_event; 32] = unsafe { mem::zeroed() }; - let res = epoll_wait(*ep, events.as_mut_ptr(), events.len() as c_int, timeout); + let res = unsafe { + epoll_pwait( + *ep, + events.as_mut_ptr(), + events.len() as c_int, + timeout, + sigmask, + ) + }; if res < 0 { return -1; } @@ -94,13 +138,54 @@ pub fn poll_epoll(fds: &mut [pollfd], timeout: c_int) -> c_int { count } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int { trace_expr!( - poll_epoll(slice::from_raw_parts_mut(fds, nfds as usize), timeout), + unsafe { + poll_epoll( + slice::from_raw_parts_mut(fds, nfds as usize), + timeout, + ptr::null_mut(), + ) + }, "poll({:p}, {}, {})", fds, nfds, - timeout + timeout, + ) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ppoll( + fds: *mut pollfd, + nfds: nfds_t, + tmo_p: *const timespec, + sigmask: *const sigset_t, +) -> c_int { + let timeout = if tmo_p.is_null() { + -1 + } else { + let tmo = unsafe { &*tmo_p }; + if tmo.tv_sec > (c_int::MAX / 1000) as _ { + c_int::MAX + } else { + ((tmo.tv_sec as c_int) * 1000) + ((tmo.tv_nsec as c_int) / 1000000) + } + }; + trace_expr!( + unsafe { + poll_epoll( + slice::from_raw_parts_mut(fds, nfds as usize), + timeout, + sigmask, + ) + }, + "ppoll({:p}, {}, {:p}, {:p})", + fds, + nfds, + tmo_p, + sigmask ) } diff --git a/src/header/pthread/attr.rs b/src/header/pthread/attr.rs new file mode 100644 index 0000000000..a975f9ea75 --- /dev/null +++ b/src/header/pthread/attr.rs @@ -0,0 +1,228 @@ +use core::{ptr, sync::atomic::Ordering}; + +use super::*; + +use crate::{ + header::bits_pthread::{pthread_attr_t, pthread_t}, + pthread::{Pthread, PthreadFlags}, +}; + +impl Default for RlctAttr { + fn default() -> Self { + Self { + // Default according to POSIX. + detachstate: PTHREAD_CREATE_JOINABLE as _, + // Default according to POSIX. + inheritsched: PTHREAD_INHERIT_SCHED as _, + // TODO: Linux + // Redox uses a round-robin scheduler + schedpolicy: SCHED_RR as _, + // TODO: Linux uses this one. + scope: PTHREAD_SCOPE_SYSTEM as _, + guardsize: Sys::getpagesize(), + // TODO + stack: 0, + // TODO + stacksize: 1024 * 1024, + param: sched_param { + // TODO + sched_priority: 0, + }, + #[cfg(target_pointer_width = "32")] + _pad: [0; 12], + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> c_int { + unsafe { ptr::drop_in_place(attr) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getdetachstate( + attr: *const pthread_attr_t, + detachstate: *mut c_int, +) -> c_int { + unsafe { ptr::write(detachstate, (*attr.cast::()).detachstate.into()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getguardsize( + attr: *const pthread_attr_t, + size: *mut size_t, +) -> c_int { + unsafe { ptr::write(size, (*attr.cast::()).guardsize) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getinheritsched( + attr: *const pthread_attr_t, + inheritsched: *mut c_int, +) -> c_int { + unsafe { ptr::write(inheritsched, (*attr.cast::()).inheritsched.into()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getschedparam( + attr: *const pthread_attr_t, + param: *mut sched_param, +) -> c_int { + unsafe { param.write((*attr.cast::()).param) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getschedpolicy( + attr: *const pthread_attr_t, + policy: *mut c_int, +) -> c_int { + unsafe { ptr::write(policy, (*attr.cast::()).schedpolicy.into()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getscope( + attr: *const pthread_attr_t, + scope: *mut c_int, +) -> c_int { + unsafe { ptr::write(scope, (*attr.cast::()).scope.into()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getstack( + attr: *const pthread_attr_t, + stackaddr: *mut *mut c_void, + stacksize: *mut size_t, +) -> c_int { + unsafe { ptr::write(stackaddr, (*attr.cast::()).stack as _) }; + unsafe { ptr::write(stacksize, (*attr.cast::()).stacksize as _) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_getstacksize( + attr: *const pthread_attr_t, + stacksize: *mut size_t, +) -> c_int { + unsafe { ptr::write(stacksize, (*attr.cast::()).stacksize as _) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_init(attr: *mut pthread_attr_t) -> c_int { + unsafe { ptr::write(attr.cast::(), RlctAttr::default()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setdetachstate( + attr: *mut pthread_attr_t, + detachstate: c_int, +) -> c_int { + unsafe { + (*attr.cast::()).detachstate = detachstate as _; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setguardsize( + attr: *mut pthread_attr_t, + guardsize: c_int, +) -> c_int { + unsafe { + (*attr.cast::()).guardsize = guardsize as _; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setinheritsched( + attr: *mut pthread_attr_t, + inheritsched: c_int, +) -> c_int { + unsafe { + (*attr.cast::()).inheritsched = inheritsched as _; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setschedparam( + attr: *mut pthread_attr_t, + param: *const sched_param, +) -> c_int { + unsafe { + (*attr.cast::()).param = param.read(); + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setschedpolicy( + attr: *mut pthread_attr_t, + policy: c_int, +) -> c_int { + unsafe { + (*attr.cast::()).schedpolicy = policy as u8; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setscope(attr: *mut pthread_attr_t, scope: c_int) -> c_int { + unsafe { + (*attr.cast::()).scope = scope as u8; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setstack( + attr: *mut pthread_attr_t, + stackaddr: *mut c_void, + stacksize: size_t, +) -> c_int { + unsafe { + (*attr.cast::()).stack = stackaddr as usize; + } + unsafe { + (*attr.cast::()).stacksize = stacksize; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_attr_setstacksize( + attr: *mut pthread_attr_t, + stacksize: size_t, +) -> c_int { + unsafe { + (*attr.cast::()).stacksize = stacksize; + } + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getattr_np( + thread_ptr: pthread_t, + attr_ptr: *mut pthread_attr_t, +) -> c_int { + let thread = unsafe { &*thread_ptr.cast::() }; + let attr_ptr = attr_ptr.cast::(); + unsafe { ptr::write(attr_ptr, RlctAttr::default()) }; + let attr = unsafe { &mut *attr_ptr }; + if thread.flags.load(Ordering::Acquire) & PthreadFlags::DETACHED.bits() != 0 { + attr.detachstate = PTHREAD_CREATE_DETACHED as _; + } + attr.stack = thread.stack_base as usize; + attr.stacksize = thread.stack_size; + //TODO: more values? + 0 +} diff --git a/src/header/pthread/barrier.rs b/src/header/pthread/barrier.rs new file mode 100644 index 0000000000..dedf715f7e --- /dev/null +++ b/src/header/pthread/barrier.rs @@ -0,0 +1,104 @@ +use crate::header::errno::*; + +use core::num::NonZeroU32; + +use crate::sync::barrier::*; + +use super::*; + +pub(crate) type RlctBarrier = Barrier; + +#[derive(Clone, Copy)] +pub(crate) struct RlctBarrierAttr { + pshared: c_int, +} +impl Default for RlctBarrierAttr { + fn default() -> Self { + // pshared = PTHREAD_PROCESS_PRIVATE is default according to POSIX. + Self { + pshared: PTHREAD_PROCESS_PRIVATE, + } + } +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrier_destroy(barrier: *mut pthread_barrier_t) -> c_int { + // Behavior is undefined if any thread is currently waiting when this is called. + + // No-op, currently. + unsafe { core::ptr::drop_in_place(barrier.cast::()) }; + + 0 +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrier_init( + barrier: *mut pthread_barrier_t, + attr: *const pthread_barrierattr_t, + count: c_uint, +) -> c_int { + let attr = unsafe { attr.cast::().as_ref() } + .copied() + .unwrap_or_default(); + + let Some(count) = NonZeroU32::new(count) else { + return EINVAL; + }; + + unsafe { barrier.cast::().write(RlctBarrier::new(count)) }; + 0 +} + +fn unlikely(condition: bool) -> bool { + condition +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrier_wait(barrier: *mut pthread_barrier_t) -> c_int { + let barrier = unsafe { &*barrier.cast::() }; + + match barrier.wait() { + WaitResult::NotifiedAll => PTHREAD_BARRIER_SERIAL_THREAD, + WaitResult::Waited => 0, + } +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrierattr_init(attr: *mut pthread_barrierattr_t) -> c_int { + unsafe { core::ptr::write(attr.cast::(), RlctBarrierAttr::default()) }; + + 0 +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrierattr_setpshared( + attr: *mut pthread_barrierattr_t, + pshared: c_int, +) -> c_int { + unsafe { + (*attr.cast::()).pshared = pshared; + } + 0 +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrierattr_getpshared( + attr: *const pthread_barrierattr_t, + pshared: *mut c_int, +) -> c_int { + unsafe { core::ptr::write(pshared, (*attr.cast::()).pshared) }; + 0 +} + +// Not async-signal-safe. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_barrierattr_destroy(attr: *mut pthread_barrierattr_t) -> c_int { + unsafe { core::ptr::drop_in_place(attr) }; + 0 +} diff --git a/src/header/pthread/cbindgen.toml b/src/header/pthread/cbindgen.toml new file mode 100644 index 0000000000..65b433470f --- /dev/null +++ b/src/header/pthread/cbindgen.toml @@ -0,0 +1,14 @@ +sys_includes = ["sched.h", "time.h", "bits/pthread.h", "features.h"] +include_guard = "_RELIBC_PTHREAD_H" +language = "C" +style = "tag" +no_includes = true +cpp_compat = true + +[export.rename] +"timespec" = "struct timespec" +"sched_param" = "struct sched_param" +"cpu_set_t" = "struct cpu_set_t" + +[enum] +prefix_with_name = true diff --git a/src/header/pthread/cond.rs b/src/header/pthread/cond.rs new file mode 100644 index 0000000000..49467238da --- /dev/null +++ b/src/header/pthread/cond.rs @@ -0,0 +1,132 @@ +// Used design from https://www.remlab.net/op/futex-condvar.shtml + +use crate::header::time::CLOCK_REALTIME; + +use super::*; + +// PTHREAD_COND_INITIALIZER is defined manually in bits_pthread/cbindgen.toml + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_broadcast(cond: *mut pthread_cond_t) -> c_int { + e((unsafe { &*cond.cast::() }).broadcast()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_destroy(cond: *mut pthread_cond_t) -> c_int { + // No-op + unsafe { core::ptr::drop_in_place(cond.cast::()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_init( + cond: *mut pthread_cond_t, + attr: *const pthread_condattr_t, +) -> c_int { + let attr = unsafe { attr.cast::().as_ref() } + .copied() + .unwrap_or_default(); + + if attr.clock != CLOCK_REALTIME { + // As monotonic clock always smaller than realtime clock, this always result in instant timeout. + todo_skip!(0, "pthread_cond_init with monotonic clock"); + } + + unsafe { cond.cast::().write(RlctCond::new()) }; + + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_signal(cond: *mut pthread_cond_t) -> c_int { + e((unsafe { &*cond.cast::() }).signal()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_timedwait( + cond: *mut pthread_cond_t, + mutex: *mut pthread_mutex_t, + timeout: *const timespec, +) -> c_int { + e((unsafe { &*cond.cast::() }) + .timedwait(unsafe { &*mutex.cast::() }, unsafe { &*timeout })) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_clockwait( + cond: *mut pthread_cond_t, + mutex: *mut pthread_mutex_t, + clock_id: clockid_t, + timeout: *const timespec, +) -> c_int { + e((unsafe { &*cond.cast::() }).clockwait( + unsafe { &*mutex.cast::() }, + unsafe { &*timeout }, + clock_id, + )) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cond_wait( + cond: *mut pthread_cond_t, + mutex: *mut pthread_mutex_t, +) -> c_int { + e((unsafe { &*cond.cast::() }).wait(unsafe { &*mutex.cast::() })) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_destroy(condattr: *mut pthread_condattr_t) -> c_int { + unsafe { core::ptr::drop_in_place(condattr.cast::()) }; + // No-op + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_getclock( + condattr: *const pthread_condattr_t, + clock: *mut clockid_t, +) -> c_int { + unsafe { core::ptr::write(clock, (*condattr.cast::()).clock) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_getpshared( + condattr: *const pthread_condattr_t, + pshared: *mut c_int, +) -> c_int { + unsafe { core::ptr::write(pshared, (*condattr.cast::()).pshared) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_init(condattr: *mut pthread_condattr_t) -> c_int { + unsafe { + condattr + .cast::() + .write(RlctCondAttr::default()) + }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_setclock( + condattr: *mut pthread_condattr_t, + clock: clockid_t, +) -> c_int { + (unsafe { *condattr.cast::() }).clock = clock; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_condattr_setpshared( + condattr: *mut pthread_condattr_t, + pshared: c_int, +) -> c_int { + (unsafe { *condattr.cast::() }).pshared = pshared; + 0 +} + +pub(crate) type RlctCondAttr = crate::sync::cond::CondAttr; + +pub(crate) type RlctCond = crate::sync::cond::Cond; diff --git a/src/header/pthread/mod.rs b/src/header/pthread/mod.rs new file mode 100644 index 0000000000..6f8028960d --- /dev/null +++ b/src/header/pthread/mod.rs @@ -0,0 +1,646 @@ +//! `pthread.h` implementation. +//! +//! See . + +use alloc::collections::LinkedList; +use core::{cell::Cell, mem::size_of, ptr::NonNull}; + +#[cfg(target_os = "linux")] +use sc::syscall; + +use crate::{ + error::Errno, + header::{ + bits_timespec::timespec, + errno::{EINVAL, ERANGE}, + sched::*, + }, + platform::{ + Pal, Sys, + types::{ + c_char, c_int, c_uchar, c_uint, c_void, clockid_t, pthread_attr_t, pthread_barrier_t, + pthread_barrierattr_t, pthread_cond_t, pthread_condattr_t, pthread_key_t, + pthread_mutex_t, pthread_mutexattr_t, pthread_once_t, pthread_rwlock_t, + pthread_rwlockattr_t, pthread_spinlock_t, pthread_t, size_t, + }, + }, + pthread, +}; + +#[cfg(target_os = "linux")] +use crate::platform::sys::e_raw; + +pub fn e(result: Result<(), Errno>) -> i32 { + match result { + Ok(()) => 0, + Err(Errno(error)) => error, + } +} + +// Affinity mask is 128 bits (16 bytes) on 64-bit targets — matching +// the kernel's LogicalCpuSet = [AtomicUsize; 2]. The previous +// implementation used a single u64 (8 bytes, 64 bits) which +// silently truncated the upper half of the mask on systems with +// >64 CPUs, and caused EINVAL when the kernel's +// `buf.read_exexact::()` rejected the 8-byte write that +// didn't match the 16-byte RawMask. The native-endian byte order +// matches on all current Redox targets (little-endian x86_64 and +// aarch64, big-endian would require a swap). +const RLCT_AFFINITY_BYTES: usize = 16; +const RLCT_MAX_AFFINITY_CPUS: usize = 128; + +fn cpuset_bytes<'a>(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<&'a [u8], Errno> { + if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::()).contains(&cpusetsize) { + return Err(Errno(EINVAL)); + } + + Ok(unsafe { core::slice::from_raw_parts(cpuset.cast::(), cpusetsize) }) +} + +fn cpuset_bytes_mut<'a>(cpusetsize: size_t, cpuset: *mut cpu_set_t) -> Result<&'a mut [u8], Errno> { + if cpuset.is_null() || !(RLCT_AFFINITY_BYTES..=size_of::()).contains(&cpusetsize) { + return Err(Errno(EINVAL)); + } + + Ok(unsafe { core::slice::from_raw_parts_mut(cpuset.cast::(), cpusetsize) }) +} + +fn cpuset_to_rawmask(cpusetsize: size_t, cpuset: *const cpu_set_t) -> Result<[u64; 2], Errno> { + let bytes = cpuset_bytes(cpusetsize, cpuset)?; + let mut mask = [0_u64; 2]; + + for (byte_index, byte) in bytes.iter().copied().enumerate() { + for bit in 0..u8::BITS as usize { + if byte & (1 << bit) == 0 { + continue; + } + + let cpu = byte_index * u8::BITS as usize + bit; + if cpu >= RLCT_MAX_AFFINITY_CPUS { + return Err(Errno(EINVAL)); + } + + // cpu is in [0, 127] which fits in two u64s + // (each covers 64 bits). + if cpu < 64 { + mask[0] |= 1 << cpu; + } else { + mask[1] |= 1 << (cpu - 64); + } + } + } + + Ok(mask) +} + +fn copy_rawmask_to_cpuset( + mask: [u64; 2], + cpusetsize: size_t, + cpuset: *mut cpu_set_t, +) -> Result<(), Errno> { + let bytes = cpuset_bytes_mut(cpusetsize, cpuset)?; + + for (byte_index, byte) in bytes.iter_mut().enumerate() { + let mut value = 0_u8; + for bit in 0..u8::BITS as usize { + let cpu = byte_index * u8::BITS as usize + bit; + if cpu >= RLCT_MAX_AFFINITY_CPUS { + break; + } + let set = if cpu < 64 { + mask[0] & (1 << cpu) != 0 + } else { + mask[1] & (1 << (cpu - 64)) != 0 + }; + if set { + value |= 1 << bit; + } + } + *byte = value; + } + + Ok(()) +} + +#[cfg(target_os = "redox")] +fn redox_get_thread_affinity(thread: &crate::pthread::Pthread) -> Result<[u64; 2], Errno> { + let os_tid = unsafe { thread.os_tid.get().read() }; + let path = alloc::format!("proc:{}/sched-affinity\0", os_tid.thread_fd); + let fd = Sys::open(crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()).unwrap(), crate::header::fcntl::O_RDONLY, 0)?; + + let mut buf = [0u8; RLCT_AFFINITY_BYTES]; + let read = Sys::read(fd, &mut buf)?; + let _ = Sys::close(fd); + if read != RLCT_AFFINITY_BYTES { + return Err(Errno(EINVAL)); + } + // Native-endian conversion: buf[0..8] -> mask[0] (low 64 bits), + // buf[8..16] -> mask[1] (high 64 bits). Matches the kernel's + // LogicalCpuSet = [AtomicUsize; 2] native-endian layout. + let mut mask = [0_u64; 2]; + mask[0] = u64::from_ne_bytes(buf[0..8].try_into().unwrap()); + mask[1] = u64::from_ne_bytes(buf[8..16].try_into().unwrap()); + Ok(mask) +} + +#[cfg(target_os = "redox")] +fn redox_set_thread_affinity(thread: &crate::pthread::Pthread, mask: [u64; 2]) -> Result<(), Errno> { + let os_tid = unsafe { thread.os_tid.get().read() }; + let path = alloc::format!("proc:{}/sched-affinity\0", os_tid.thread_fd); + let fd = Sys::open(crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()).unwrap(), crate::header::fcntl::O_WRONLY, 0)?; + + // Serialize the [u64; 2] mask to 16 native-endian bytes. + let mut bytes = [0u8; 16]; + bytes[0..8].copy_from_slice(&mask[0].to_ne_bytes()); + bytes[8..16].copy_from_slice(&mask[1].to_ne_bytes()); + let written = Sys::write(fd, &bytes)?; + let _ = Sys::close(fd); + if written != RLCT_AFFINITY_BYTES { + return Err(Errno(EINVAL)); + } + Ok(()) +} + +#[derive(Clone)] +pub(crate) struct RlctAttr { + pub detachstate: c_uchar, + pub inheritsched: c_uchar, + pub schedpolicy: c_uchar, + pub scope: c_uchar, + pub guardsize: size_t, + pub stacksize: size_t, + pub stack: size_t, + pub param: sched_param, + #[cfg(target_pointer_width = "32")] + _pad: [u8; 12], +} + +pub const _POSIX_THREADS: c_int = 1; + +pub const PTHREAD_BARRIER_SERIAL_THREAD: c_int = -1; + +pub const PTHREAD_CANCEL_ASYNCHRONOUS: c_int = 0; +pub const PTHREAD_CANCEL_ENABLE: c_int = 1; +pub const PTHREAD_CANCEL_DEFERRED: c_int = 2; +pub const PTHREAD_CANCEL_DISABLE: c_int = 3; +pub const PTHREAD_CANCELED: *mut c_void = (!0_usize) as *mut c_void; + +pub const PTHREAD_CREATE_DETACHED: c_int = 0; +pub const PTHREAD_CREATE_JOINABLE: c_int = 1; + +pub const PTHREAD_EXPLICIT_SCHED: c_int = 0; +pub const PTHREAD_INHERIT_SCHED: c_int = 1; + +pub const PTHREAD_MUTEX_DEFAULT: c_int = 0; +pub const PTHREAD_MUTEX_ERRORCHECK: c_int = 1; +pub const PTHREAD_MUTEX_NORMAL: c_int = 2; +pub const PTHREAD_MUTEX_RECURSIVE: c_int = 3; + +pub const PTHREAD_MUTEX_ROBUST: c_int = 0; +pub const PTHREAD_MUTEX_STALLED: c_int = 1; + +pub const PTHREAD_PRIO_INHERIT: c_int = 0; + +pub const PTHREAD_PRIO_NONE: c_int = 0; + +pub const PTHREAD_PRIO_PROTECT: c_int = 0; + +pub const PTHREAD_PROCESS_SHARED: c_int = 0; +pub const PTHREAD_PROCESS_PRIVATE: c_int = 1; + +pub const PTHREAD_SCOPE_PROCESS: c_int = 0; +pub const PTHREAD_SCOPE_SYSTEM: c_int = 1; + +pub mod attr; +pub use self::attr::*; + +pub mod barrier; +pub use self::barrier::*; + +pub mod cond; +pub use self::cond::*; + +#[thread_local] +pub static mut fork_hooks: [LinkedList; 3] = [const { LinkedList::new() }; 3]; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_cancel(thread: pthread_t) -> c_int { + match unsafe { pthread::cancel(&*thread.cast()) } { + Ok(()) => 0, + Err(Errno(error)) => error, + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_create( + pthread: *mut pthread_t, + attr: *const pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, +) -> c_int { + let attr = unsafe { attr.cast::().as_ref() }; + + match unsafe { pthread::create(attr, start_routine, arg) } { + Ok(ptr) => { + unsafe { core::ptr::write(pthread, ptr) }; + 0 + } + Err(Errno(code)) => code, + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_detach(pthread: pthread_t) -> c_int { + match unsafe { pthread::detach(&*pthread.cast()) } { + Ok(()) => 0, + Err(Errno(errno)) => errno, + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn pthread_equal(pthread1: pthread_t, pthread2: pthread_t) -> c_int { + core::ptr::eq(pthread1, pthread2).into() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn pthread_atfork( + prepare: Option, + parent: Option, + child: Option, +) -> c_int { + if let Some(prepare) = prepare { + unsafe { + fork_hooks[0].push_back(prepare); + } + } + if let Some(parent) = parent { + unsafe { + fork_hooks[1].push_back(parent); + } + } + if let Some(child) = child { + unsafe { + fork_hooks[2].push_back(child); + } + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_exit(retval: *mut c_void) -> ! { + unsafe { pthread::exit_current_thread(pthread::Retval(retval)) } +} + +// Not in latest POSIX, mark as depreciated? +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getconcurrency() -> c_int { + // Redox and Linux threads are 1:1, not M:N. + 1 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getcpuclockid( + thread: pthread_t, + clock_out: *mut clockid_t, +) -> c_int { + match pthread::get_cpu_clkid(unsafe { &*thread.cast() }) { + Ok(clock) => { + unsafe { clock_out.write(clock) }; + 0 + } + Err(Errno(error)) => error, + } +} + +/// GNU extension. See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getaffinity_np( + thread: pthread_t, + cpusetsize: size_t, + cpuset: *mut cpu_set_t, +) -> c_int { + let thread: &crate::pthread::Pthread = unsafe { &*thread.cast() }; + + let result = { + #[cfg(target_os = "redox")] + { + redox_get_thread_affinity(thread) + .and_then(|mask| copy_rawmask_to_cpuset(mask, cpusetsize, cpuset)) + } + + #[cfg(target_os = "linux")] + { + if cpuset.is_null() { + Err(Errno(EINVAL)) + } else { + e_raw(unsafe { + syscall!( + SCHED_GETAFFINITY, + thread.os_tid.get().read().thread_id, + cpusetsize, + cpuset.cast::() + ) + }) + .map(|_| ()) + } + } + }; + + e(result) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getschedparam( + thread: pthread_t, + policy_out: *mut c_int, + param_out: *mut sched_param, +) -> c_int { + match pthread::get_sched_param(unsafe { &*thread.cast() }) { + Ok((policy, param)) => { + unsafe { policy_out.write(policy) }; + unsafe { param_out.write(param) }; + 0 + } + Err(Errno(error)) => error, + } +} + +pub mod tls; +pub use tls::*; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_join(thread: pthread_t, retval: *mut *mut c_void) -> c_int { + match unsafe { pthread::join(&*thread.cast()) } { + Ok(pthread::Retval(ret)) => { + if !retval.is_null() { + unsafe { core::ptr::write(retval, ret) }; + } + 0 + } + Err(Errno(error)) => error, + } +} + +pub mod mutex; +pub use self::mutex::*; + +pub mod once; +pub use self::once::*; + +pub mod rwlock; +pub use self::rwlock::*; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_self() -> pthread_t { + core::ptr::from_ref(unsafe { pthread::current_thread().unwrap_unchecked() }) as *mut _ +} + +/// GNU extension. See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setaffinity_np( + thread: pthread_t, + cpusetsize: size_t, + cpuset: *const cpu_set_t, +) -> c_int { + let thread: &crate::pthread::Pthread = unsafe { &*thread.cast() }; + + let result = { + #[cfg(target_os = "redox")] + { + cpuset_to_rawmask(cpusetsize, cpuset) + .and_then(|mask| redox_set_thread_affinity(thread, mask)) + } + + #[cfg(target_os = "linux")] + { + if cpuset.is_null() { + Err(Errno(EINVAL)) + } else { + e_raw(unsafe { + syscall!( + SCHED_SETAFFINITY, + thread.os_tid.get().read().thread_id, + cpusetsize, + cpuset.cast::() + ) + }) + .map(|_| ()) + } + } + }; + + e(result) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setcancelstate(state: c_int, oldstate: *mut c_int) -> c_int { + match pthread::set_cancel_state(state) { + Ok(old) => { + // POSIX doesn't imply oldstate can be NULL anywhere, but a lot of C code probably + // relies on it... + if let Some(oldstate) = NonNull::new(oldstate) { + unsafe { oldstate.write(old) }; + } + 0 + } + Err(Errno(error)) => error, + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setcanceltype(ty: c_int, oldty: *mut c_int) -> c_int { + match pthread::set_cancel_type(ty) { + Ok(old) => { + // POSIX doesn't imply oldty can be NULL anywhere, but a lot of C code probably relies + // on it... + if let Some(oldty) = NonNull::new(oldty) { + unsafe { oldty.write(old) }; + } + 0 + } + Err(Errno(error)) => error, + } +} + +// Not in latest POSIX, mark as depreciated? +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn pthread_setconcurrency(concurrency: c_int) -> c_int { + // Redox and Linux threads are 1:1, not M:N. + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setschedparam( + thread: pthread_t, + policy: c_int, + param: *const sched_param, +) -> c_int { + e(pthread::set_sched_param( + unsafe { &*thread.cast() }, + policy, + unsafe { &*param }, + )) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setschedprio(thread: pthread_t, prio: c_int) -> c_int { + e(pthread::set_sched_priority( + unsafe { &*thread.cast() }, + prio, + )) +} + +pub mod spin; +pub use self::spin::*; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_testcancel() { + unsafe { pthread::testcancel() }; +} + +/// +/// +/// Non-standard GNU extension. Prefer `sched_yield()` instead. +pub extern "C" fn pthread_yield() { + let _ = Sys::sched_yield(); +} + +// Must be the same struct as defined in the pthread_cleanup_push macro. +#[repr(C)] +pub(crate) struct CleanupLinkedListEntry { + routine: extern "C" fn(*mut c_void), + arg: *mut c_void, + prev: *const c_void, +} + +#[thread_local] +pub(crate) static CLEANUP_LL_HEAD: Cell<*const CleanupLinkedListEntry> = + Cell::new(core::ptr::null()); + +// TODO: unwind? setjmp/longjmp? + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __relibc_internal_pthread_cleanup_push(new_entry: *mut c_void) { + let new_entry = unsafe { &mut *new_entry.cast::() }; + + new_entry.prev = CLEANUP_LL_HEAD.get().cast(); + CLEANUP_LL_HEAD.set(new_entry); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __relibc_internal_pthread_cleanup_pop(execute: c_int) { + let prev_head = unsafe { CLEANUP_LL_HEAD.get().read() }; + CLEANUP_LL_HEAD.set(prev_head.prev.cast()); + + if execute != 0 { + (prev_head.routine)(prev_head.arg); + } +} + +pub(crate) unsafe fn run_destructor_stack() { + unsafe { crate::cxa::__cxa_thread_finalize() }; + + let mut ptr = CLEANUP_LL_HEAD.get(); + + while !ptr.is_null() { + let entry = unsafe { ptr.read() }; + ptr = entry.prev.cast(); + + (entry.routine)(entry.arg); + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setname_np(thread: pthread_t, name: *const c_char) -> c_int { + if name.is_null() { + return EINVAL; + } + + let cstr = unsafe { core::ffi::CStr::from_ptr(name) }; + let name_bytes = cstr.to_bytes(); + let len = name_bytes.len().min(31); + + #[cfg(target_os = "redox")] + { + let thread = unsafe { &*thread.cast::() }; + let os_tid = unsafe { thread.os_tid.get().read() }; + let path = alloc::format!("proc:{}/name\0", os_tid.thread_fd); + let fd = match Sys::open(crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()).unwrap(), crate::header::fcntl::O_WRONLY, 0) { + Ok(fd) => fd, + Err(Errno(code)) => return code, + }; + + let result = match Sys::write(fd, &name_bytes[..len]) { + Ok(written) if written == len => 0, + Ok(_) => crate::header::errno::EIO, + Err(Errno(code)) => code, + }; + let _ = Sys::close(fd); + result + } + #[cfg(not(target_os = "redox"))] + { + let _ = thread; + 0 + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getname_np( + thread: pthread_t, + name: *mut c_char, + len: size_t, +) -> c_int { + if name.is_null() { + return EINVAL; + } + if len == 0 { + return ERANGE; + } + + #[cfg(target_os = "redox")] + { + let thread = unsafe { &*thread.cast::() }; + let os_tid = unsafe { thread.os_tid.get().read() }; + let path = alloc::format!("proc:{}/name\0", os_tid.thread_fd); + let fd = match Sys::open(crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()).unwrap(), crate::header::fcntl::O_RDONLY, 0) { + Ok(fd) => fd, + Err(Errno(code)) => return code, + }; + + let mut buf = [0u8; 31]; + let result = match Sys::read(fd, &mut buf) { + Ok(read) if read < len => { + unsafe { core::ptr::copy_nonoverlapping(buf.as_ptr(), name.cast(), read) }; + unsafe { *name.add(read) = 0 }; + 0 + } + Ok(_) => ERANGE, + Err(Errno(code)) => code, + }; + let _ = Sys::close(fd); + result + } + #[cfg(not(target_os = "redox"))] + { + let _ = thread; + unsafe { *name = 0 }; + 0 + } +} diff --git a/src/header/pthread/mutex.rs b/src/header/pthread/mutex.rs new file mode 100644 index 0000000000..9f53257dbf --- /dev/null +++ b/src/header/pthread/mutex.rs @@ -0,0 +1,217 @@ +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] + +use super::*; +pub use crate::sync::pthread_mutex::RlctMutex; +use crate::{error::Errno, header::time::timespec_realtime_to_monotonic}; + +// PTHREAD_MUTEX_INITIALIZER is defined in bits_pthread/cbindgen.toml + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_consistent(mutex: *mut pthread_mutex_t) -> c_int { + e((unsafe { &*mutex.cast::() }).make_consistent()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_destroy(mutex: *mut pthread_mutex_t) -> c_int { + // No-op + unsafe { core::ptr::drop_in_place(mutex.cast::()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_getprioceiling( + mutex: *const pthread_mutex_t, + prioceiling: *mut c_int, +) -> c_int { + match (unsafe { &*mutex.cast::() }).prioceiling() { + Ok(value) => { + unsafe { prioceiling.write(value) }; + 0 + } + Err(Errno(errno)) => errno, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_init( + mutex: *mut pthread_mutex_t, + attr: *const pthread_mutexattr_t, +) -> c_int { + let attr = unsafe { attr.cast::().as_ref() } + .cloned() + .unwrap_or_default(); + + match RlctMutex::new(&attr) { + Ok(new) => { + unsafe { mutex.cast::().write(new) }; + + 0 + } + Err(Errno(errno)) => errno, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_lock(mutex: *mut pthread_mutex_t) -> c_int { + e((unsafe { &*mutex.cast::() }).lock()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_setprioceiling( + mutex: *mut pthread_mutex_t, + prioceiling: c_int, + old_prioceiling: *mut c_int, +) -> c_int { + match (unsafe { &*mutex.cast::() }).replace_prioceiling(prioceiling) { + Ok(old) => { + unsafe { old_prioceiling.write(old) }; + 0 + } + Err(Errno(errno)) => errno, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_timedlock( + mutex: *mut pthread_mutex_t, + abstime: ×pec, +) -> c_int { + let relative = match timespec_realtime_to_monotonic(abstime.clone()) { + Ok(relative) => relative, + Err(err) => return e(Err(err)), + }; + + e((unsafe { &*mutex.cast::() }).lock_with_timeout(&relative)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_trylock(mutex: *mut pthread_mutex_t) -> c_int { + e((unsafe { &*mutex.cast::() }).try_lock()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_unlock(mutex: *mut pthread_mutex_t) -> c_int { + e((unsafe { &*mutex.cast::() }).unlock()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_destroy(attr: *mut pthread_mutexattr_t) -> c_int { + // No-op + unsafe { core::ptr::drop_in_place(attr.cast::()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_getprioceiling( + attr: *const pthread_mutexattr_t, + prioceiling: &mut c_int, +) -> c_int { + *prioceiling = unsafe { &*attr.cast::() }.prioceiling; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_getprotocol( + attr: *const pthread_mutexattr_t, + protocol: &mut c_int, +) -> c_int { + *protocol = unsafe { &*attr.cast::() }.protocol; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_getpshared( + attr: *const pthread_mutexattr_t, + pshared: &mut c_int, +) -> c_int { + *pshared = unsafe { &*attr.cast::() }.pshared; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_getrobust( + attr: *const pthread_mutexattr_t, + robust: &mut c_int, +) -> c_int { + *robust = unsafe { &*attr.cast::() }.robust; + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_gettype( + attr: *const pthread_mutexattr_t, + ty: &mut c_int, +) -> c_int { + *ty = unsafe { &*attr.cast::() }.ty; + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_init(attr: *mut pthread_mutexattr_t) -> c_int { + unsafe { attr.cast::().write(RlctMutexAttr::default()) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_setprioceiling( + attr: *mut pthread_mutexattr_t, + prioceiling: c_int, +) -> c_int { + unsafe { &mut *attr.cast::() }.prioceiling = prioceiling; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_setprotocol( + attr: *mut pthread_mutexattr_t, + protocol: c_int, +) -> c_int { + unsafe { &mut *attr.cast::() }.protocol = protocol; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_setpshared( + attr: *mut pthread_mutexattr_t, + pshared: c_int, +) -> c_int { + unsafe { &mut *attr.cast::() }.pshared = pshared; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_setrobust( + attr: *mut pthread_mutexattr_t, + robust: c_int, +) -> c_int { + unsafe { &mut *attr.cast::() }.robust = robust; + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutexattr_settype( + attr: *mut pthread_mutexattr_t, + ty: c_int, +) -> c_int { + unsafe { &mut *attr.cast::() }.ty = ty; + 0 +} + +#[repr(C)] +#[derive(Clone)] +pub(crate) struct RlctMutexAttr { + pub prioceiling: c_int, + pub protocol: c_int, + pub pshared: c_int, + pub robust: c_int, + pub ty: c_int, +} + +impl Default for RlctMutexAttr { + fn default() -> Self { + Self { + robust: PTHREAD_MUTEX_STALLED, + pshared: PTHREAD_PROCESS_PRIVATE, + protocol: PTHREAD_PRIO_NONE, + // TODO + prioceiling: 0, + ty: PTHREAD_MUTEX_DEFAULT, + } + } +} diff --git a/src/header/pthread/once.rs b/src/header/pthread/once.rs new file mode 100644 index 0000000000..76348bbcf6 --- /dev/null +++ b/src/header/pthread/once.rs @@ -0,0 +1,20 @@ +use super::*; + +// PTHREAD_ONCE_INIT + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_once( + once: *mut pthread_once_t, + constructor: extern "C" fn(), +) -> c_int { + let once = unsafe { &*once.cast::() }; + + // TODO: Cancellation points + + once.call_once(|| constructor()); + + //crate::sync::once::call_once_generic(&once.inner, || constructor()); + + 0 +} +pub(crate) type RlctOnce = crate::sync::Once<()>; diff --git a/src/header/pthread/rwlock.rs b/src/header/pthread/rwlock.rs new file mode 100644 index 0000000000..916579bd86 --- /dev/null +++ b/src/header/pthread/rwlock.rs @@ -0,0 +1,156 @@ +use super::*; + +use crate::header::errno::{EBUSY, EINVAL}; + +use crate::{header::time::CLOCK_REALTIME, pthread::Pshared}; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_init( + rwlock: *mut pthread_rwlock_t, + attr: *const pthread_rwlockattr_t, +) -> c_int { + let attr = unsafe { attr.cast::().as_ref() } + .copied() + .unwrap_or_default(); + + unsafe { + rwlock + .cast::() + .write(RlctRwlock::new(attr.pshared)) + }; + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_rdlock(rwlock: *mut pthread_rwlock_t) -> c_int { + match unsafe { get(rwlock) }.acquire_read_lock(None) { + Ok(()) => 0, + Err(e) => e.0, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_clockrdlock( + rwlock: *mut pthread_rwlock_t, + clock_id: clockid_t, + abstime: *const timespec, +) -> c_int { + match unsafe { get(rwlock) }.acquire_read_lock(Some((unsafe { &*abstime }, clock_id))) { + Ok(()) => 0, + Err(e) => e.0, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_timedrdlock( + rwlock: *mut pthread_rwlock_t, + abstime: *const timespec, +) -> c_int { + match unsafe { get(rwlock) }.acquire_read_lock(Some((unsafe { &*abstime }, CLOCK_REALTIME))) { + Ok(()) => 0, + Err(e) => e.0, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_clockwrlock( + rwlock: *mut pthread_rwlock_t, + clock_id: clockid_t, + abstime: *const timespec, +) -> c_int { + match unsafe { get(rwlock) }.acquire_write_lock(Some((unsafe { &*abstime }, clock_id))) { + Ok(()) => 0, + Err(e) => e.0, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_timedwrlock( + rwlock: *mut pthread_rwlock_t, + abstime: *const timespec, +) -> c_int { + match unsafe { get(rwlock) }.acquire_write_lock(Some((unsafe { &*abstime }, CLOCK_REALTIME))) { + Ok(()) => 0, + Err(e) => e.0, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_tryrdlock(rwlock: *mut pthread_rwlock_t) -> c_int { + match unsafe { get(rwlock) }.try_acquire_read_lock() { + Ok(()) => 0, + Err(_) => EBUSY, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_trywrlock(rwlock: *mut pthread_rwlock_t) -> c_int { + match unsafe { get(rwlock) }.try_acquire_write_lock() { + Ok(()) => 0, + Err(_) => EBUSY, + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_unlock(rwlock: *mut pthread_rwlock_t) -> c_int { + unsafe { get(rwlock) }.unlock(); + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_wrlock(rwlock: *mut pthread_rwlock_t) -> c_int { + match unsafe { get(rwlock) }.acquire_write_lock(None) { + Ok(()) => 0, + Err(e) => e.0, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlockattr_init(attr: *mut pthread_rwlockattr_t) -> c_int { + unsafe { + attr.cast::() + .write(RlctRwlockAttr::default()) + }; + + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlockattr_getpshared( + attr: *const pthread_rwlockattr_t, + pshared_out: *mut c_int, +) -> c_int { + unsafe { core::ptr::write(pshared_out, (*attr.cast::()).pshared.raw()) }; + + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlockattr_setpshared( + attr: *mut pthread_rwlockattr_t, + pshared: c_int, +) -> c_int { + let Some(pshared) = Pshared::from_raw(pshared) else { + return EINVAL; + }; + + (unsafe { *attr.cast::() }).pshared = pshared; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlockattr_destroy(attr: *mut pthread_rwlockattr_t) -> c_int { + unsafe { core::ptr::drop_in_place(attr) }; + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_rwlock_destroy(rwlock: *mut pthread_rwlock_t) -> c_int { + unsafe { core::ptr::drop_in_place(rwlock) }; + + 0 +} + +pub(crate) type RlctRwlock = crate::sync::rwlock::InnerRwLock; + +#[derive(Clone, Copy, Default)] +pub(crate) struct RlctRwlockAttr { + pshared: Pshared, +} +#[inline] +unsafe fn get<'a>(ptr: *mut pthread_rwlock_t) -> &'a RlctRwlock { + unsafe { &*ptr.cast() } +} diff --git a/src/header/pthread/spin.rs b/src/header/pthread/spin.rs new file mode 100644 index 0000000000..094a9ea31b --- /dev/null +++ b/src/header/pthread/spin.rs @@ -0,0 +1,75 @@ +use core::sync::atomic::{AtomicI32 as AtomicInt, Ordering}; + +use crate::header::errno::EBUSY; + +use super::*; + +const UNLOCKED: c_int = 0; +const LOCKED: c_int = 1; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_spin_destroy(spinlock: *mut pthread_spinlock_t) -> c_int { + let _spinlock = unsafe { &mut *spinlock.cast::() }; + + // No-op + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_spin_init( + spinlock: *mut pthread_spinlock_t, + _pshared: c_int, +) -> c_int { + // TODO: pshared doesn't matter in most situations, as memory is just memory, but this may be + // different on some architectures... + + unsafe { + spinlock.cast::().write(RlctSpinlock { + inner: AtomicInt::new(UNLOCKED), + }) + }; + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_spin_lock(spinlock: *mut pthread_spinlock_t) -> c_int { + let spinlock = unsafe { &*spinlock.cast::() }; + + loop { + match spinlock.inner.compare_exchange_weak( + UNLOCKED, + LOCKED, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(_) => core::hint::spin_loop(), + } + } + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_spin_trylock(spinlock: *mut pthread_spinlock_t) -> c_int { + let spinlock = unsafe { &*spinlock.cast::() }; + + match spinlock + .inner + .compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) + { + Ok(_) => (), + Err(_) => return EBUSY, + } + + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_spin_unlock(spinlock: *mut pthread_spinlock_t) -> c_int { + let spinlock = unsafe { &*spinlock.cast::() }; + + spinlock.inner.store(UNLOCKED, Ordering::Release); + + 0 +} +pub(crate) struct RlctSpinlock { + pub inner: AtomicInt, +} diff --git a/src/header/pthread/tls.rs b/src/header/pthread/tls.rs new file mode 100644 index 0000000000..83f2826c46 --- /dev/null +++ b/src/header/pthread/tls.rs @@ -0,0 +1,130 @@ +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] + +use super::*; + +// TODO: Hashmap? +use alloc::{collections::BTreeMap, vec::Vec}; + +use core::{ + cell::RefCell, + ptr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use crate::{ + header::{errno::EINVAL, limits::PTHREAD_DESTRUCTOR_ITERATIONS}, + sync::Mutex, +}; + +type Dtor = Option; + +struct Record { + data: *mut c_void, +} + +#[thread_local] +static VALUES: RefCell> = RefCell::new(BTreeMap::new()); +static KEYS: Mutex> = Mutex::new(BTreeMap::new()); +static NEXTKEY: AtomicUsize = AtomicUsize::new(1); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_getspecific(key: pthread_key_t) -> *mut c_void { + // According to POSIX (issue 8): Calling [`pthread_getspecific`] with a key + // that has been deleted with [`pthread_key_delete`] or not obtained from + // [`pthread_key_create`] results in undefined behaviour. Therefore, we only + // do this check when debug assertions are explicitly enabled to avoid + // acquiring the global [`KEYS`] lock when it is not necessary. + debug_assert!(KEYS.lock().contains_key(&key)); + VALUES + .borrow_mut() + .get(&key) + .map(|record| record.data) + .unwrap_or(ptr::null_mut()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_setspecific(key: pthread_key_t, value: *const c_void) -> c_int { + if !KEYS.lock().contains_key(&key) { + // We don't have to return anything, but it's not less expensive to ignore it. + //println!("Invalid key for pthread_setspecific key {:#0x} value {:p}", key, value); + return EINVAL; + } + + let mut guard = VALUES.borrow_mut(); + + let record = guard.entry(key).or_insert(Record { + data: core::ptr::null_mut(), + }); + //println!("Valid key for pthread_setspecific key {:#0x} value {:p} (was {:p})", key, value, record.data); + + record.data = value.cast_mut(); + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_key_create( + key_ptr: *mut pthread_key_t, + destructor: Dtor, +) -> c_int { + let key = NEXTKEY.fetch_add(1, Ordering::SeqCst) as pthread_key_t; + + // TODO + //if key >= PTHREAD_KEYS_MAX { + //} + + KEYS.lock().insert(key, destructor); + + unsafe { key_ptr.write(key) }; + 0 +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_key_delete(key: pthread_key_t) -> c_int { + if KEYS.lock().remove(&key).is_none() || VALUES.borrow_mut().remove(&key).is_none() { + // We don't have to return anything, but it's not less expensive to ignore it. + return EINVAL; + } + + 0 +} + +pub(crate) unsafe fn run_all_destructors() { + for _ in 0..PTHREAD_DESTRUCTOR_ITERATIONS { + let mut any_run = false; + let dtors = { + let keys = KEYS.lock(); + keys.iter() + .filter_map(|(&key, &dtor)| dtor.map(|dtor| (key, dtor))) + .collect::>() + }; + + // According to POSIX (issue 8): There is no specific order in which we + // have to run the destructors. + for (key, dtor) in dtors { + let mut values = VALUES.borrow_mut(); + if let Some(record) = values.get_mut(&key) { + let val = record.data; + if val.is_null() { + continue; + } + record.data = ptr::null_mut(); + drop(values); + dtor(val); + any_run = true; + } + } + + if !any_run { + break; + } + } + + // According to POSIX (issue 8): If even after + // [`PTHREAD_DESTRUCTOR_ITERATIONS`] iterations there are still some + // non-NULL values with associated destructors, the behaviour is + // implementation-defined. We can choose to stop calling them or continue + // calling them until none are left. Both musl and glibc choose to stop + // calling them so we do the same. +} diff --git a/src/header/pty/cbindgen.toml b/src/header/pty/cbindgen.toml new file mode 100644 index 0000000000..3ecd31cfcf --- /dev/null +++ b/src/header/pty/cbindgen.toml @@ -0,0 +1,13 @@ +sys_includes = ["sys/ioctl.h", "termios.h"] +include_guard = "_RELIBC_PTY_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export.rename] +"winsize" = "struct winsize" +"termios" = "struct termios" \ No newline at end of file diff --git a/src/header/pty/linux.rs b/src/header/pty/linux.rs new file mode 100644 index 0000000000..020e336915 --- /dev/null +++ b/src/header/pty/linux.rs @@ -0,0 +1,58 @@ +use core::ptr; + +use crate::{ + header::{fcntl, sys_ioctl, unistd}, + io::{Cursor, Write}, + platform::types::{c_char, c_int, c_void}, +}; + +pub(super) unsafe fn openpty(name: &mut [u8]) -> Result<(c_int, c_int), ()> { + //TODO: wrap in auto-close struct + let master = unsafe { fcntl::open(c"/dev/ptmx".as_ptr(), fcntl::O_RDWR | fcntl::O_NOCTTY, 0) }; + if master < 0 { + return Err(()); + } + + let mut lock: c_int = 0; + if unsafe { + sys_ioctl::ioctl( + master, + sys_ioctl::TIOCSPTLCK, + ptr::from_mut::(&mut lock).cast::(), + ) + } != 0 + { + unistd::close(master); + return Err(()); + } + + let mut ptn: c_int = 0; + if unsafe { + sys_ioctl::ioctl( + master, + sys_ioctl::TIOCGPTN, + ptr::from_mut::(&mut ptn).cast::(), + ) + } != 0 + { + unistd::close(master); + return Err(()); + } + + let mut cursor = Cursor::new(name); + if let Ok(()) = write!(cursor, "/dev/pts/{}\0", ptn) {}; // TODO handle error + + let slave = unsafe { + fcntl::open( + cursor.get_ref().as_ptr().cast::(), + fcntl::O_RDWR | fcntl::O_NOCTTY, + 0, + ) + }; + if slave < 0 { + unistd::close(master); + return Err(()); + } + + Ok((master, slave)) +} diff --git a/src/header/pty/mod.rs b/src/header/pty/mod.rs new file mode 100644 index 0000000000..995aef8aec --- /dev/null +++ b/src/header/pty/mod.rs @@ -0,0 +1,136 @@ +//! `pty.h` implementation. +//! +//! Non-POSIX, see . + +use core::{mem, ptr, slice}; + +use crate::{ + header::{ + bits_sigset_t, fcntl, limits, pthread, signal, sys_ioctl, sys_wait, termios, unistd, utmp, + }, + platform::{ + self, + types::{c_char, c_int, c_void}, + }, +}; + +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +mod imp; + +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +mod imp; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openpty( + amaster: *mut c_int, + aslave: *mut c_int, + namep: *mut c_char, + termp: *const termios::termios, + winp: *const sys_ioctl::winsize, +) -> c_int { + let mut tmp_name = [0; limits::PATH_MAX]; + let name = if !namep.is_null() { + unsafe { slice::from_raw_parts_mut(namep.cast::(), limits::PATH_MAX) } + } else { + &mut tmp_name + }; + + let (master, slave) = match unsafe { imp::openpty(name) } { + Ok(ok) => ok, + Err(()) => return -1, + }; + + if !termp.is_null() { + unsafe { termios::tcsetattr(slave, termios::TCSANOW, termp) }; + } + + if !winp.is_null() { + unsafe { sys_ioctl::ioctl(slave, sys_ioctl::TIOCSWINSZ, winp as *mut c_void) }; + } + + unsafe { *amaster = master }; + unsafe { *aslave = slave }; + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn forkpty( + pm: *mut c_int, + name: *mut c_char, + tio: *const termios::termios, + ws: *const sys_ioctl::winsize, +) -> c_int { + let mut m = 0; + let mut s = 0; + let mut ec = 0; + let mut p: [c_int; 2] = [0; 2]; + let mut cs = 0; + let mut pid = -1; + let mut set = bits_sigset_t::sigset_t::default(); + let mut oldset = bits_sigset_t::sigset_t::default(); + + if unsafe { openpty(&raw mut m, &raw mut s, name, tio, ws) } < 0 { + return -1; + } + + unsafe { signal::sigfillset(&raw mut set) }; + unsafe { signal::pthread_sigmask(signal::SIG_BLOCK, &raw const set, &raw mut oldset) }; + unsafe { pthread::pthread_setcancelstate(pthread::PTHREAD_CANCEL_DISABLE, &raw mut cs) }; + + if unsafe { unistd::pipe2(p.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 { + unistd::close(s); + } else { + pid = unsafe { unistd::fork() }; + if pid == 0 { + unistd::close(m); + unistd::close(p[0]); + if unsafe { utmp::login_tty(s) } != 0 { + unsafe { + unistd::write( + p[1], + platform::ERRNO.as_ptr().cast(), + mem::size_of_val(&platform::ERRNO), + ) + }; + unistd::_exit(127); + } + unistd::close(p[1]); + unsafe { pthread::pthread_setcancelstate(cs, ptr::null_mut()) }; + unsafe { + signal::pthread_sigmask(signal::SIG_SETMASK, &raw const oldset, ptr::null_mut()) + }; + return 0; + } + + unistd::close(s); + unistd::close(p[1]); + + if unsafe { + unistd::read( + p[0], + ptr::from_mut::(&mut ec).cast::(), + mem::size_of::(), + ) + } > 0 + { + let mut status = 0; + unsafe { sys_wait::waitpid(pid, &raw mut status, 0) }; + pid = -1; + platform::ERRNO.set(ec); + } + unistd::close(p[0]); + } + if pid > 0 { + unsafe { *pm = m }; + } else { + unistd::close(m); + } + unsafe { pthread::pthread_setcancelstate(cs, ptr::null_mut()) }; + unsafe { signal::pthread_sigmask(signal::SIG_SETMASK, &raw const oldset, ptr::null_mut()) }; + pid +} diff --git a/src/header/pty/redox.rs b/src/header/pty/redox.rs new file mode 100644 index 0000000000..a4fad5c608 --- /dev/null +++ b/src/header/pty/redox.rs @@ -0,0 +1,33 @@ +use crate::{ + error::ResultExt, + header::{fcntl, unistd}, + platform::{ + Pal, Sys, + types::{c_char, c_int, ssize_t}, + }, +}; + +pub(super) unsafe fn openpty(name: &mut [u8]) -> Result<(c_int, c_int), ()> { + let master = unsafe { fcntl::open(c"/scheme/pty".as_ptr(), fcntl::O_RDWR, 0) }; + if master < 0 { + return Err(()); + } + + // TODO: better error handling + let count = Sys::fpath(master, name) + .map(|u| u as ssize_t) + .or_minus_one_errno(); + if count < 0 { + unistd::close(master); + return Err(()); + } + name[count as usize] = 0; + + let slave = unsafe { fcntl::open(name.as_ptr() as *const c_char, fcntl::O_RDWR, 0) }; + if slave < 0 { + unistd::close(master); + return Err(()); + } + + Ok((master, slave)) +} diff --git a/src/header/pwd/cbindgen.toml b/src/header/pwd/cbindgen.toml index 55d0cdf99c..92e53b6f90 100644 --- a/src/header/pwd/cbindgen.toml +++ b/src/header/pwd/cbindgen.toml @@ -1,4 +1,8 @@ -sys_includes = ["stddef.h", "sys/types.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/pwd.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the gid_t, uid_t, and size_t types as described in ." +sys_includes = ["sys/types.h"] include_guard = "_RELIBC_PWD_H" language = "C" style = "Tag" diff --git a/src/header/pwd/linux.rs b/src/header/pwd/linux.rs index 0d74036685..70e1fb1221 100644 --- a/src/header/pwd/linux.rs +++ b/src/header/pwd/linux.rs @@ -1,15 +1,15 @@ use super::{parsed, passwd}; -use crate::platform::types::*; +use crate::platform::types::c_char; pub fn split(line: &mut [u8]) -> Option { let mut parts = line.split_mut(|&c| c == b'\0'); Some(passwd { - pw_name: parts.next()?.as_mut_ptr() as *mut c_char, - pw_passwd: parts.next()?.as_mut_ptr() as *mut c_char, + pw_name: parts.next()?.as_mut_ptr().cast::(), + pw_passwd: parts.next()?.as_mut_ptr().cast::(), pw_uid: parsed(parts.next())?, pw_gid: parsed(parts.next())?, - pw_gecos: parts.next()?.as_mut_ptr() as *mut c_char, - pw_dir: parts.next()?.as_mut_ptr() as *mut c_char, - pw_shell: parts.next()?.as_mut_ptr() as *mut c_char, + pw_gecos: parts.next()?.as_mut_ptr().cast::(), + pw_dir: parts.next()?.as_mut_ptr().cast::(), + pw_shell: parts.next()?.as_mut_ptr().cast::(), }) } diff --git a/src/header/pwd/mod.rs b/src/header/pwd/mod.rs index d983e47354..e581a74ba0 100644 --- a/src/header/pwd/mod.rs +++ b/src/header/pwd/mod.rs @@ -1,4 +1,6 @@ -//! pwd implementation for relibc +//! `pwd.h` implementation. +//! +//! See . use alloc::{boxed::Box, vec::Vec}; use core::{ @@ -10,8 +12,12 @@ use core::{ use crate::{ fs::File, header::{errno, fcntl, string::strcmp}, - io::{prelude::*, BufReader, SeekFrom}, - platform::{self, types::*}, + io::{BufReader, SeekFrom, prelude::*}, + platform::{ + self, + types::{c_char, c_int, gid_t, size_t, uid_t}, + }, + raw_cell::RawCell, }; #[cfg(target_os = "linux")] @@ -24,20 +30,30 @@ use self::linux as sys; #[cfg(target_os = "redox")] use self::redox as sys; +#[cfg(target_os = "linux")] +const SEPARATOR: u8 = b':'; + +#[cfg(target_os = "redox")] +const SEPARATOR: u8 = b';'; + +/// See +/// for POSIX minimum requirements, and +/// for further +/// details. #[repr(C)] #[derive(Debug)] pub struct passwd { - pw_name: *mut c_char, - pw_passwd: *mut c_char, - pw_uid: uid_t, - pw_gid: gid_t, - pw_gecos: *mut c_char, - pw_dir: *mut c_char, - pw_shell: *mut c_char, + pub pw_name: *mut c_char, + pub pw_passwd: *mut c_char, + pub pw_uid: uid_t, + pub pw_gid: gid_t, + pub pw_gecos: *mut c_char, + pub pw_dir: *mut c_char, + pub pw_shell: *mut c_char, } static mut PASSWD_BUF: Option = None; -static mut PASSWD: passwd = passwd { +static PASSWD: RawCell = RawCell::new(passwd { pw_name: ptr::null_mut(), pw_passwd: ptr::null_mut(), pw_uid: 0, @@ -45,7 +61,7 @@ static mut PASSWD: passwd = passwd { pw_gecos: ptr::null_mut(), pw_dir: ptr::null_mut(), pw_shell: ptr::null_mut(), -}; +}); #[derive(Clone, Copy, Debug)] struct DestBuffer { @@ -91,8 +107,8 @@ impl OwnedPwd { fn into_global(self) -> *mut passwd { unsafe { PASSWD_BUF = Some(self.buffer); - PASSWD = self.reference; - &mut PASSWD + PASSWD.unsafe_set(self.reference); + PASSWD.as_mut_ptr() } } } @@ -103,7 +119,7 @@ enum Cause { Other, } -static mut READER: Option> = None; +static READER: RawCell>> = RawCell::new(None); fn parsed(buf: Option) -> Option where @@ -115,6 +131,9 @@ where string.parse().ok() } +/// See . +/// +/// Non-POSIX fn getpwent_r( reader: &mut BufReader, destination: Option, @@ -128,9 +147,9 @@ fn getpwent_r( return Err(Cause::Eof); } - // Replace all occurences of ':' with terminating NUL byte + // Replace all occurences of seperator with terminating NUL byte let mut start = 0; - while let Some(i) = memchr::memchr(b':', &buf[start..]) { + while let Some(i) = memchr::memchr(SEPARATOR, &buf[start..]) { buf[start + i] = 0; start += i + 1; } @@ -148,9 +167,7 @@ fn getpwent_r( Some(dst) => { let mut new = MaybeAllocated::Borrowed(dst); if new.len() < buf.len() { - unsafe { - platform::errno = errno::ERANGE; - } + platform::ERRNO.set(errno::ERANGE); return Err(Cause::Other); } new[..buf.len()].copy_from_slice(&buf); @@ -171,7 +188,7 @@ fn pwd_lookup(mut matches: F, destination: Option) -> Result bool, { - let file = match File::open(c_str!("/etc/passwd"), fcntl::O_RDONLY) { + let file = match File::open(c"/etc/passwd".into(), fcntl::O_RDONLY) { Ok(file) => file, Err(_) => return Err(Cause::Other), }; @@ -194,91 +211,43 @@ unsafe fn mux( ) -> c_int { match status { Ok(owned) => { - *out = owned.reference; - *result = out; + unsafe { *out = owned.reference }; + unsafe { *result = out }; 0 } Err(Cause::Eof) => { - *result = ptr::null_mut(); + unsafe { *result = ptr::null_mut() }; 0 } Err(Cause::Other) => { - *result = ptr::null_mut(); + unsafe { *result = ptr::null_mut() }; -1 } } } -#[no_mangle] -pub unsafe extern "C" fn getpwnam_r( - name: *const c_char, - out: *mut passwd, - buf: *mut c_char, - size: size_t, - result: *mut *mut passwd, -) -> c_int { - mux( - pwd_lookup( - |parts| strcmp(parts.pw_name, name) == 0, - Some(DestBuffer { - ptr: buf as *mut u8, - len: size, - }), - ), - out, - result, - ) +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn endpwent() { + unsafe { + READER.unsafe_set(None); + } } -#[no_mangle] -pub unsafe extern "C" fn getpwuid_r( - uid: uid_t, - out: *mut passwd, - buf: *mut c_char, - size: size_t, - result: *mut *mut passwd, -) -> c_int { - let slice = core::slice::from_raw_parts_mut(buf as *mut u8, size); - mux( - pwd_lookup( - |part| part.pw_uid == uid, - Some(DestBuffer { - ptr: buf as *mut u8, - len: size, - }), - ), - out, - result, - ) -} - -#[no_mangle] -pub extern "C" fn getpwnam(name: *const c_char) -> *mut passwd { - pwd_lookup(|parts| unsafe { strcmp(parts.pw_name, name) } == 0, None) - .map(|res| res.into_global()) - .unwrap_or(ptr::null_mut()) -} - -#[no_mangle] -pub extern "C" fn getpwuid(uid: uid_t) -> *mut passwd { - pwd_lookup(|parts| parts.pw_uid == uid, None) - .map(|res| res.into_global()) - .unwrap_or(ptr::null_mut()) -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getpwent() -> *mut passwd { - let reader = match unsafe { &mut READER } { + let reader = match unsafe { &mut *READER.as_mut_ptr() } { Some(reader) => reader, None => { - let file = match File::open(c_str!("/etc/passwd"), fcntl::O_RDONLY) { + let file = match File::open(c"/etc/passwd".into(), fcntl::O_RDONLY) { Ok(file) => file, Err(_) => return ptr::null_mut(), }; let reader = BufReader::new(file); unsafe { - READER = Some(reader); - READER.as_mut().unwrap() + READER.unsafe_set(Some(reader)); + READER.unsafe_mut().as_mut().unwrap() } } }; @@ -287,16 +256,75 @@ pub extern "C" fn getpwent() -> *mut passwd { .unwrap_or(ptr::null_mut()) } -#[no_mangle] -pub extern "C" fn setpwent() { - if let Some(reader) = unsafe { &mut READER } { - let _ = reader.seek(SeekFrom::Start(0)); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpwnam(name: *const c_char) -> *mut passwd { + pwd_lookup(|parts| unsafe { strcmp(parts.pw_name, name) } == 0, None) + .map(|res| res.into_global()) + .unwrap_or(ptr::null_mut()) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpwnam_r( + name: *const c_char, + out: *mut passwd, + buf: *mut c_char, + size: size_t, + result: *mut *mut passwd, +) -> c_int { + unsafe { + mux( + pwd_lookup( + |parts| strcmp(parts.pw_name, name) == 0, + Some(DestBuffer { + ptr: buf.cast::(), + len: size, + }), + ), + out, + result, + ) } } -#[no_mangle] -pub extern "C" fn endpwent() { +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn getpwuid(uid: uid_t) -> *mut passwd { + pwd_lookup(|parts| parts.pw_uid == uid, None) + .map(|res| res.into_global()) + .unwrap_or(ptr::null_mut()) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpwuid_r( + uid: uid_t, + out: *mut passwd, + buf: *mut c_char, + size: size_t, + result: *mut *mut passwd, +) -> c_int { + let slice = unsafe { core::slice::from_raw_parts_mut(buf.cast::(), size) }; unsafe { - READER = None; + mux( + pwd_lookup( + |part| part.pw_uid == uid, + Some(DestBuffer { + ptr: buf.cast::(), + len: size, + }), + ), + out, + result, + ) + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn setpwent() { + if let Some(reader) = unsafe { &mut *READER.as_mut_ptr() } { + let _ = reader.seek(SeekFrom::Start(0)); } } diff --git a/src/header/pwd/redox.rs b/src/header/pwd/redox.rs index 88b218ead9..8b3d723907 100644 --- a/src/header/pwd/redox.rs +++ b/src/header/pwd/redox.rs @@ -1,15 +1,15 @@ use super::{parsed, passwd}; -use crate::platform::types::*; +use crate::platform::types::c_char; pub fn split(line: &mut [u8]) -> Option { let mut parts = line.split_mut(|&c| c == b'\0'); Some(passwd { - pw_name: parts.next()?.as_mut_ptr() as *mut c_char, - pw_passwd: "x\0".as_ptr() as *const c_char as *mut c_char, + pw_name: parts.next()?.as_mut_ptr().cast::(), + pw_passwd: c"x".as_ptr() as *const c_char as *mut c_char, pw_uid: parsed(parts.next())?, pw_gid: parsed(parts.next())?, - pw_gecos: parts.next()?.as_mut_ptr() as *mut c_char, - pw_dir: parts.next()?.as_mut_ptr() as *mut c_char, - pw_shell: parts.next()?.as_mut_ptr() as *mut c_char, + pw_gecos: parts.next()?.as_mut_ptr().cast::(), + pw_dir: parts.next()?.as_mut_ptr().cast::(), + pw_shell: parts.next()?.as_mut_ptr().cast::(), }) } diff --git a/src/header/regex/cbindgen.toml b/src/header/regex/cbindgen.toml index 78dfc8ee0d..fa1c711c04 100644 --- a/src/header/regex/cbindgen.toml +++ b/src/header/regex/cbindgen.toml @@ -1,3 +1,7 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/regex.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the size_t type as described in ." sys_includes = ["sys/types.h"] include_guard = "_RELIBC_REGEX_H" language = "C" diff --git a/src/header/regex/mod.rs b/src/header/regex/mod.rs index 01611b2a28..1b8da49041 100644 --- a/src/header/regex/mod.rs +++ b/src/header/regex/mod.rs @@ -1,26 +1,28 @@ -//! regex.h implementation, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/regex.h.html +//! `regex.h` implementation. +//! +//! See . -use crate::{header::string::strlen, platform::types::*}; -use alloc::{borrow::Cow, vec::Vec}; -use core::{mem, ptr, slice}; -use posix_regex::{ - compile::{Error as CompileError, Range, Token}, - PosixRegex, PosixRegexBuilder, +use crate::{ + header::string::strlen, + platform::types::{c_char, c_int, c_void, size_t}, }; +use alloc::{borrow::Cow, boxed::Box}; +use core::{ptr, slice}; +use posix_regex::{PosixRegex, PosixRegexBuilder, compile::Error as CompileError, tree::Tree}; +/// See . pub type regoff_t = size_t; +/// See . #[repr(C)] pub struct regex_t { - // Can't be a normal Vec because then the struct size won't be known - // from C. + // Points to a posix_regex::Tree ptr: *mut c_void, - length: size_t, - capacity: size_t, - cflags: c_int, re_nsub: size_t, } + +/// See . #[repr(C)] pub struct regmatch_t { rm_so: regoff_t, @@ -33,6 +35,7 @@ pub const REG_NOSUB: c_int = 4; pub const REG_NEWLINE: c_int = 8; pub const REG_NOTBOL: c_int = 16; pub const REG_NOTEOL: c_int = 32; +pub const REG_MINIMAL: c_int = 64; pub const REG_NOMATCH: c_int = 1; pub const REG_BADPAT: c_int = 2; @@ -49,30 +52,27 @@ pub const REG_ERANGE: c_int = 12; pub const REG_ESPACE: c_int = 13; pub const REG_BADRPT: c_int = 14; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] #[linkage = "weak"] // redefined in GIT pub unsafe extern "C" fn regcomp(out: *mut regex_t, pat: *const c_char, cflags: c_int) -> c_int { - if cflags & REG_EXTENDED == REG_EXTENDED { - return REG_ENOSYS; - } - - let pat = slice::from_raw_parts(pat as *const u8, strlen(pat)); + let pat = unsafe { slice::from_raw_parts(pat.cast::(), strlen(pat)) }; let res = PosixRegexBuilder::new(pat) .with_default_classes() + .extended(cflags & REG_EXTENDED == REG_EXTENDED) .compile_tokens(); match res { - Ok(mut branches) => { + Ok(branches) => { let re_nsub = PosixRegex::new(Cow::Borrowed(&branches)).count_groups(); - *out = regex_t { - ptr: branches.as_mut_ptr() as *mut c_void, - length: branches.len(), - capacity: branches.capacity(), + unsafe { + *out = regex_t { + ptr: Box::into_raw(Box::new(branches)).cast::(), - cflags, - re_nsub, + cflags, + re_nsub, + } }; - mem::forget(branches); 0 } Err(CompileError::EmptyRepetition) @@ -86,17 +86,15 @@ pub unsafe extern "C" fn regcomp(out: *mut regex_t, pat: *const c_char, cflags: } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] #[linkage = "weak"] // redefined in GIT pub unsafe extern "C" fn regfree(regex: *mut regex_t) { - Vec::from_raw_parts( - (*regex).ptr as *mut Vec<(Token, Range)>, - (*regex).length, - (*regex).capacity, - ); + unsafe { drop(Box::from_raw((*regex).ptr)) }; } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] #[linkage = "weak"] // redefined in GIT pub unsafe extern "C" fn regexec( regex: *const regex_t, @@ -105,20 +103,16 @@ pub unsafe extern "C" fn regexec( pmatch: *mut regmatch_t, eflags: c_int, ) -> c_int { - if eflags & REG_EXTENDED == REG_EXTENDED { - return REG_ENOSYS; - } + let regex = unsafe { &*regex }; - let regex = &*regex; - - // Allow specifying a compiler argument to the executor and vise versa + // Allow specifying a compiler argument to the executor and viceversa // because why not? let flags = regex.cflags | eflags; - let input = slice::from_raw_parts(input as *const u8, strlen(input)); - let branches = slice::from_raw_parts(regex.ptr as *const Vec<(Token, Range)>, regex.length); + let input = unsafe { slice::from_raw_parts(input.cast::(), strlen(input)) }; + let branches = unsafe { &*(regex.ptr.cast::()) }; - let matches = PosixRegex::new(Cow::Borrowed(&branches)) + let matches = PosixRegex::new(Cow::Borrowed(branches)) .case_insensitive(flags & REG_ICASE == REG_ICASE) .newline(flags & REG_NEWLINE == REG_NEWLINE) .no_start(flags & REG_NOTBOL == REG_NOTBOL) @@ -130,21 +124,20 @@ pub unsafe extern "C" fn regexec( for i in 0..nmatch { let (start, end) = first.get(i).and_then(|&range| range).unwrap_or((!0, !0)); - *pmatch.add(i) = regmatch_t { - rm_so: start, - rm_eo: end, + unsafe { + *pmatch.add(i) = regmatch_t { + rm_so: start, + rm_eo: end, + } }; } } - if matches.is_empty() { - REG_NOMATCH - } else { - 0 - } + if matches.is_empty() { REG_NOMATCH } else { 0 } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] #[linkage = "weak"] // redefined in GIT pub extern "C" fn regerror( code: c_int, @@ -172,11 +165,7 @@ pub extern "C" fn regerror( }; unsafe { - ptr::copy_nonoverlapping( - string.as_ptr(), - out as *mut u8, - string.len().min(max as usize), - ); + ptr::copy_nonoverlapping(string.as_ptr(), out.cast::(), string.len().min(max)); } string.len() diff --git a/src/header/sched/cbindgen.toml b/src/header/sched/cbindgen.toml new file mode 100644 index 0000000000..c54d4ec488 --- /dev/null +++ b/src/header/sched/cbindgen.toml @@ -0,0 +1,36 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sched.h.html +# +# Spec quotations relating to includes: +# - "[PS] The header shall define the pid_t type as described in ." +# - "[SS|TSP] The header shall define the time_t type as described in ." +# - "The header shall define the timespec structure as described in ." +# - "Inclusion of the header may make visible all symbols from the header." +sys_includes = ["sys/types.h", "stdint.h"] +include_guard = "_RELIBC_SCHED_H" +after_includes = """ +#include // for timespec +""" +language = "C" +style = "Both" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export] +include = [ + "sched_param", + "cpu_set_t", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getparam", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_setparam", + "sched_setscheduler", + "sched_yield", +] + +[export.rename] +"timespec" = "struct timespec" diff --git a/src/header/sched/mod.rs b/src/header/sched/mod.rs new file mode 100644 index 0000000000..7219b87e14 --- /dev/null +++ b/src/header/sched/mod.rs @@ -0,0 +1,156 @@ +//! `sched.h` implementation. +//! +//! See . + +use crate::{ + error::ResultExt, + header::bits_timespec::timespec, + platform::{ + Pal, Sys, + types::{c_int, pid_t}, + }, +}; + +// TODO: There are extensions, but adding more member is breaking ABI for pthread_attr_t +/// See . +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct sched_param { + pub sched_priority: c_int, +} + +/// See . +pub const SCHED_FIFO: c_int = 0; +/// See . +pub const SCHED_RR: c_int = 1; +/// See . +pub const SCHED_OTHER: c_int = 2; + +/// See . +pub const CPU_SETSIZE: usize = 1024; + +/// Linux-compatible CPU affinity mask storage. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct cpu_set_t { + pub __bits: [u64; 16], +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_get_priority_max(policy: c_int) -> c_int { + match policy { + SCHED_FIFO | SCHED_RR => 99, + SCHED_OTHER => 0, + _ => { + crate::platform::ERRNO.set(crate::header::errno::EINVAL); + -1 + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_get_priority_min(policy: c_int) -> c_int { + match policy { + SCHED_FIFO | SCHED_RR => 1, + SCHED_OTHER => 0, + _ => { + crate::platform::ERRNO.set(crate::header::errno::EINVAL); + -1 + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sched_getparam(pid: pid_t, param: *mut sched_param) -> c_int { + if pid != 0 { + crate::platform::ERRNO.set(crate::header::errno::ESRCH); + return -1; + } + crate::platform::ERRNO.set(crate::header::errno::ENOSYS); + -1 +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_getscheduler(pid: pid_t) -> c_int { + if pid != 0 { + crate::platform::ERRNO.set(crate::header::errno::ESRCH); + return -1; + } + crate::platform::ERRNO.set(crate::header::errno::ENOSYS); + -1 +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_rr_get_interval(pid: pid_t, tp: *mut timespec) -> c_int { + if pid != 0 { + crate::platform::ERRNO.set(crate::header::errno::ESRCH); + return -1; + } + if tp.is_null() { + crate::platform::ERRNO.set(crate::header::errno::EINVAL); + return -1; + } + unsafe { + (*tp).tv_sec = 0; + (*tp).tv_nsec = 100_000_000; // 100ms default SCHED_RR quantum + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sched_setparam(pid: pid_t, _param: *const sched_param) -> c_int { + if pid != 0 { + crate::platform::ERRNO.set(crate::header::errno::ESRCH); + return -1; + } + crate::platform::ERRNO.set(crate::header::errno::ENOSYS); + -1 +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_setscheduler( + pid: pid_t, + policy: c_int, + param: *const sched_param, +) -> c_int { + if pid != 0 { + crate::platform::ERRNO.set(crate::header::errno::ESRCH); + return -1; + } + match policy { + SCHED_OTHER => { + if !param.is_null() && unsafe { (*param).sched_priority } != 0 { + crate::platform::ERRNO.set(crate::header::errno::EINVAL); + return -1; + } + SCHED_OTHER + } + SCHED_FIFO | SCHED_RR => { + crate::platform::ERRNO.set(crate::header::errno::ENOSYS); + -1 + } + _ => { + crate::platform::ERRNO.set(crate::header::errno::EINVAL); + -1 + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sched_yield() -> c_int { + Sys::sched_yield().map(|()| 0).or_minus_one_errno() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_sched_param(_: sched_param) {} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_struct_user_for_cpu_set_t(_: cpu_set_t) {} diff --git a/src/header/semaphore/cbindgen.toml b/src/header/semaphore/cbindgen.toml index 0c01194989..565d855377 100644 --- a/src/header/semaphore/cbindgen.toml +++ b/src/header/semaphore/cbindgen.toml @@ -1,9 +1,25 @@ sys_includes = [] include_guard = "_RELIBC_SEMAPHORE_H" +after_includes = """ +#include // for timespec +""" +trailer = """ +#define SEM_FAILED ((sem_t *) -1) + +/* POSIX variadic named semaphore opener. cbindgen cannot emit variadic + functions, so the prototype is declared manually here. */ +sem_t *sem_open(const char *name, int oflag, ...); +""" language = "C" -style = "Tag" +style = "Type" no_includes = true cpp_compat = true +[export] +exclude = ["sem_open"] + [enum] prefix_with_name = true + +[export.rename] +"timespec" = "struct timespec" diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs index 505d6eba6f..ec519abc4b 100644 --- a/src/header/semaphore/mod.rs +++ b/src/header/semaphore/mod.rs @@ -1,61 +1,241 @@ -use crate::platform::types::*; +//! `semaphore.h` implementation. +//! +//! See . +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use core::ptr; +use core::sync::atomic::{AtomicUsize, Ordering}; + +use crate::{ + c_str::CStr, + header::{ + bits_timespec::timespec, + errno::{EINTR, EINVAL}, + fcntl::{O_CREAT, O_EXCL, O_RDWR}, + sys_mman::{ + MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, mmap, munmap, shm_open, shm_unlink, + }, + time::{CLOCK_MONOTONIC, CLOCK_REALTIME}, + }, + platform::{ + Pal, Sys, ERRNO, + types::{c_char, c_int, c_long, c_uint, clockid_t, mode_t}, + }, + sync::Mutex, +}; + +/// See . +// TODO: Statically verify size and align #[repr(C)] -#[derive(Copy)] +#[derive(Clone, Copy)] pub union sem_t { - pub size: [c_char; 32usize], + pub size: [c_char; 4], pub align: c_long, - _bindgen_union_align: [u64; 4usize], } -impl Clone for sem_t { - fn clone(&self) -> Self { - *self +pub type RlctSempahore = crate::sync::Semaphore; +#[repr(C)] +struct NamedSemaphore { + sem: RlctSempahore, +} + +const SEM_FAILED_PTR: *mut sem_t = usize::MAX as *mut sem_t; + +struct NamedSemEntry { + ptr: *mut NamedSemaphore, + size: usize, + refs: AtomicUsize, + unlinked: bool, +} +unsafe impl Send for NamedSemEntry {} + +static NAMED_SEMS: Mutex, NamedSemEntry>>> = Mutex::new(None); + +fn with_named_sems(f: impl FnOnce(&mut BTreeMap, NamedSemEntry>) -> R) -> R { + let mut guard = NAMED_SEMS.lock(); + if guard.is_none() { + *guard = Some(BTreeMap::new()); + } + f(guard.as_mut().unwrap()) +} + +unsafe fn map_named_semaphore( + name: *const c_char, + oflag: c_int, + mode: mode_t, + value: c_uint, +) -> *mut sem_t { + let create = (oflag & O_CREAT) != 0; + let shm_flags = if create { O_CREAT | O_EXCL | O_RDWR } else { O_RDWR }; + let canonical = alloc::format!("sem.{name_cstr}", name_cstr = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("")); + let fd = unsafe { shm_open(canonical.as_ptr() as *const c_char, shm_flags, mode) }; + if fd < 0 { + return SEM_FAILED_PTR; + } + let sz = core::mem::size_of::() as usize; + if create { + if Sys::ftruncate(fd, sz as i64).is_err() { + let _ = Sys::close(fd); + return SEM_FAILED_PTR; + } + } + let ptr = unsafe { mmap(core::ptr::null_mut(), sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) }; + let _ = Sys::close(fd); + if ptr == MAP_FAILED { + return SEM_FAILED_PTR; + } + let named: &mut NamedSemaphore = unsafe { &mut *ptr.cast::() }; + if create { + unsafe { ptr::write(&mut named.sem, RlctSempahore::new(value)); } + } + &mut named.sem as *mut RlctSempahore as *mut sem_t +} + +/// See . +// #[unsafe(no_mangle)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_close(sem: *mut sem_t) -> c_int { + if sem.is_null() || sem == SEM_FAILED_PTR { + ERRNO.set(EINVAL); + return -1; + } + let named_ptr = sem as *mut NamedSemaphore; + let mut should_unmap = false; + with_named_sems(|map| { + let key = map.iter().find(|(_, e)| e.ptr == named_ptr).map(|(k, _)| k.clone()); + if let Some(key) = key { + let entry = map.get(&key).unwrap(); + let prev = entry.refs.fetch_sub(1, Ordering::Release); + if prev == 1 { should_unmap = true; map.remove(&key); } + } else { should_unmap = true; } + }); + if should_unmap { + if unsafe { munmap(named_ptr as *mut crate::platform::types::c_void, core::mem::size_of::()) } != 0 { + return -1; + } + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_destroy(sem: *mut sem_t) -> c_int { + unsafe { core::ptr::drop_in_place(sem.cast::()) }; + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_getvalue(sem: *mut sem_t, sval: *mut c_int) -> c_int { + unsafe { sval.write(get(sem).value() as c_int) }; + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_init(sem: *mut sem_t, _pshared: c_int, value: c_uint) -> c_int { + unsafe { sem.cast::().write(RlctSempahore::new(value)) }; + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_open( + name: *const c_char, + oflag: c_int, + mut __valist: ... +) -> *mut sem_t { + if name.is_null() { ERRNO.set(EINVAL); return SEM_FAILED_PTR; } + let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec(); + let create = (oflag & O_CREAT) != 0; + let excl = (oflag & O_EXCL) != 0; + let existing = with_named_sems(|map| map.get(&name_bytes).map(|e| e.ptr)); + if let Some(ptr) = existing { + if excl { ERRNO.set(crate::header::errno::EEXIST); return SEM_FAILED_PTR; } + with_named_sems(|map| { if let Some(e) = map.get(&name_bytes) { e.refs.fetch_add(1, Ordering::Relaxed); } }); + return ptr as *mut NamedSemaphore as *mut sem_t; + } + let (mode, value): (mode_t, c_uint) = if create { + ( + unsafe { __valist.next_arg::() }, + unsafe { __valist.next_arg::() }, + ) + } else { + (0, 0) + }; + let ptr = unsafe { map_named_semaphore(name, oflag, mode, value) }; + if ptr == SEM_FAILED_PTR { return SEM_FAILED_PTR; } + with_named_sems(|map| { map.insert(name_bytes, NamedSemEntry { ptr: ptr as *mut NamedSemaphore, size: core::mem::size_of::(), refs: AtomicUsize::new(1), unlinked: false }); }); + ptr +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_post(sem: *mut sem_t) -> c_int { + unsafe { get(sem) }.post(1); + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_trywait(sem: *mut sem_t) -> c_int { + unsafe { get(sem) }.try_wait(); + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int { + if name.is_null() { ERRNO.set(EINVAL); return -1; } + let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec(); + with_named_sems(|map| { if let Some(e) = map.get_mut(&name_bytes) { e.unlinked = true; } }); + unsafe { shm_unlink(name) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_wait(sem: *mut sem_t) -> c_int { + loop { + match unsafe { get(sem) }.wait(None, CLOCK_MONOTONIC) { + Ok(()) => return 0, + Err(e) if e == EINTR => continue, + Err(_) => return -1, + } } } -// #[no_mangle] -pub extern "C" fn sem_init(sem: *mut sem_t, pshared: c_int, value: c_uint) -> c_int { - unimplemented!(); + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_clockwait( + sem: *mut sem_t, + clock_id: clockid_t, + abstime: *const timespec, +) -> c_int { + loop { + match unsafe { get(sem) }.wait(Some(&unsafe { (*abstime).clone() }), clock_id) { + Ok(()) => return 0, + Err(e) if e == EINTR => continue, + Err(_) => return -1, + } + } } -// #[no_mangle] -pub extern "C" fn sem_destroy(sem: *mut sem_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sem_timedwait(sem: *mut sem_t, abstime: *const timespec) -> c_int { + loop { + match unsafe { get(sem) }.wait(Some(&unsafe { (*abstime).clone() }), CLOCK_REALTIME) { + Ok(()) => return 0, + Err(e) if e == EINTR => continue, + Err(_) => return -1, + } + } } -/* - *#[no_mangle] - *pub extern "C" fn sem_open(name: *const c_char, - * oflag: c_int, ...) -> *mut sem_t { - * unimplemented!(); - *} - */ - -// #[no_mangle] -pub extern "C" fn sem_close(sem: *mut sem_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn sem_unlink(name: *const c_char) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn sem_wait(sem: *mut sem_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn sem_trywait(sem: *mut sem_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn sem_post(sem: *mut sem_t) -> c_int { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn sem_getvalue(sem: *mut sem_t, sval: *mut c_int) -> c_int { - unimplemented!(); +unsafe fn get<'any>(sem: *mut sem_t) -> &'any RlctSempahore { + unsafe { &*sem.cast() } } diff --git a/src/header/setjmp/impl/aarch64/sigsetjmp.s b/src/header/setjmp/impl/aarch64/sigsetjmp.s new file mode 100644 index 0000000000..75910c4321 --- /dev/null +++ b/src/header/setjmp/impl/aarch64/sigsetjmp.s @@ -0,0 +1,21 @@ +.global sigsetjmp +.global __sigsetjmp +.type sigsetjmp,%function +.type __sigsetjmp,%function +sigsetjmp: +__sigsetjmp: + cbz x1,setjmp + + str x30,[x0,#176] + str x19,[x0,#176+8+8] + mov x19,x0 + + bl setjmp + + mov w1,w0 + mov x0,x19 + ldr x30,[x0,#176] + ldr x19,[x0,#176+8+8] + +.hidden __sigsetjmp_tail + b __sigsetjmp_tail diff --git a/src/header/setjmp/impl/i386/longjmp.s b/src/header/setjmp/impl/i386/longjmp.s index 772d28ddb2..a2378b9fc3 100644 --- a/src/header/setjmp/impl/i386/longjmp.s +++ b/src/header/setjmp/impl/i386/longjmp.s @@ -4,17 +4,13 @@ .type longjmp,@function _longjmp: longjmp: - mov 4(%esp),%edx - mov 8(%esp),%eax - test %eax,%eax - jnz 1f - inc %eax -1: - mov (%edx),%ebx - mov 4(%edx),%esi - mov 8(%edx),%edi - mov 12(%edx),%ebp - mov 16(%edx),%ecx - mov %ecx,%esp - mov 20(%edx),%ecx - jmp *%ecx + mov edx, [esp + 4] + mov eax, [esp + 8] + cmp eax, 1 + adc al, 0 + mov ebx, [edx] + mov esi, [edx + 4] + mov edi, [edx + 8] + mov ebp, [edx + 12] + mov esp, [edx + 16] + jmp [edx + 20] diff --git a/src/header/setjmp/impl/i386/setjmp.s b/src/header/setjmp/impl/i386/setjmp.s index 4d19cf87cb..03678fdf41 100644 --- a/src/header/setjmp/impl/i386/setjmp.s +++ b/src/header/setjmp/impl/i386/setjmp.s @@ -10,14 +10,14 @@ ___setjmp: __setjmp: _setjmp: setjmp: - mov 4(%esp), %eax - mov %ebx, (%eax) - mov %esi, 4(%eax) - mov %edi, 8(%eax) - mov %ebp, 12(%eax) - lea 4(%esp), %ecx - mov %ecx, 16(%eax) - mov (%esp), %ecx - mov %ecx, 20(%eax) - xor %eax, %eax + mov eax, [esp + 4] + mov [eax], ebx + mov [eax + 4], esi + mov [eax + 8], edi + mov [eax + 12], ebp + lea ecx, [esp + 4] + mov [eax + 16], ecx + mov ecx, [esp] + mov [eax + 20], ecx + xor eax, eax ret diff --git a/src/header/setjmp/impl/i386/sigsetjmp.s b/src/header/setjmp/impl/i386/sigsetjmp.s new file mode 100644 index 0000000000..e1be3b444f --- /dev/null +++ b/src/header/setjmp/impl/i386/sigsetjmp.s @@ -0,0 +1,26 @@ +.global sigsetjmp +.global __sigsetjmp +.type sigsetjmp,@function +.type __sigsetjmp,@function +sigsetjmp: +__sigsetjmp: + mov ecx, dword ptr [esp + 8] + jecxz 1f + + mov eax, dword ptr [esp + 4] + pop [eax + 24] + mov dword ptr [eax + 8 + 28], ebx + mov ebx, eax + +.hidden ___setjmp + call ___setjmp + + push [ebx + 24] + mov dword ptr [esp + 4], ebx + mov dword ptr [esp + 8], eax + mov ebx, dword ptr [ebx + 8 + 28] + +.hidden __sigsetjmp_tail + jmp __sigsetjmp_tail + +1: jmp ___setjmp diff --git a/src/header/setjmp/impl/riscv64/longjmp.S b/src/header/setjmp/impl/riscv64/longjmp.S new file mode 100644 index 0000000000..09b36da0a8 --- /dev/null +++ b/src/header/setjmp/impl/riscv64/longjmp.S @@ -0,0 +1,41 @@ +.attribute arch, "rv64gc" # see rust issue #80608 +.global __longjmp +.global _longjmp +.global longjmp +.type __longjmp, %function +.type _longjmp, %function +.type longjmp, %function +__longjmp: +_longjmp: +longjmp: + ld s0, 0(a0) + ld s1, 8(a0) + ld s2, 16(a0) + ld s3, 24(a0) + ld s4, 32(a0) + ld s5, 40(a0) + ld s6, 48(a0) + ld s7, 56(a0) + ld s8, 64(a0) + ld s9, 72(a0) + ld s10, 80(a0) + ld s11, 88(a0) + ld sp, 96(a0) + ld ra, 104(a0) + + fld fs0, 112(a0) + fld fs1, 120(a0) + fld fs2, 128(a0) + fld fs3, 136(a0) + fld fs4, 144(a0) + fld fs5, 152(a0) + fld fs6, 160(a0) + fld fs7, 168(a0) + fld fs8, 176(a0) + fld fs9, 184(a0) + fld fs10, 192(a0) + fld fs11, 200(a0) + + seqz a0, a1 + add a0, a0, a1 + ret diff --git a/src/header/setjmp/impl/riscv64/setjmp.S b/src/header/setjmp/impl/riscv64/setjmp.S new file mode 100644 index 0000000000..243a8f744b --- /dev/null +++ b/src/header/setjmp/impl/riscv64/setjmp.S @@ -0,0 +1,40 @@ +.attribute arch, "rv64gc" # see rust issue #80608 +.global __setjmp +.global _setjmp +.global setjmp +.type __setjmp, %function +.type _setjmp, %function +.type setjmp, %function +__setjmp: +_setjmp: +setjmp: + sd s0, 0(a0) + sd s1, 8(a0) + sd s2, 16(a0) + sd s3, 24(a0) + sd s4, 32(a0) + sd s5, 40(a0) + sd s6, 48(a0) + sd s7, 56(a0) + sd s8, 64(a0) + sd s9, 72(a0) + sd s10, 80(a0) + sd s11, 88(a0) + sd sp, 96(a0) + sd ra, 104(a0) + + fsd fs0, 112(a0) + fsd fs1, 120(a0) + fsd fs2, 128(a0) + fsd fs3, 136(a0) + fsd fs4, 144(a0) + fsd fs5, 152(a0) + fsd fs6, 160(a0) + fsd fs7, 168(a0) + fsd fs8, 176(a0) + fsd fs9, 184(a0) + fsd fs10, 192(a0) + fsd fs11, 200(a0) + + li a0, 0 + ret diff --git a/src/header/setjmp/impl/riscv64/sigsetjmp.S b/src/header/setjmp/impl/riscv64/sigsetjmp.S new file mode 100644 index 0000000000..f9bc162a0a --- /dev/null +++ b/src/header/setjmp/impl/riscv64/sigsetjmp.S @@ -0,0 +1,23 @@ +.global sigsetjmp +.global __sigsetjmp +.type sigsetjmp, %function +.type __sigsetjmp, %function +sigsetjmp: +__sigsetjmp: + bnez a1, 1f + tail setjmp +1: + + sd ra, 208(a0) + sd s0, 224(a0) + mv s0, a0 + + call setjmp + + mv a1, a0 + mv a0, s0 + ld s0, 224(a0) + ld ra, 208(a0) + +.hidden __sigsetjmp_tail + tail __sigsetjmp_tail diff --git a/src/header/setjmp/impl/x86_64/longjmp.s b/src/header/setjmp/impl/x86_64/longjmp.s index e175a4b960..c6b4e0e0f6 100644 --- a/src/header/setjmp/impl/x86_64/longjmp.s +++ b/src/header/setjmp/impl/x86_64/longjmp.s @@ -5,18 +5,18 @@ .type longjmp,@function _longjmp: longjmp: - mov %rsi,%rax /* val will be longjmp return */ - test %rax,%rax + mov rax,rsi /* val will be longjmp return */ + test rax,rax jnz 1f - inc %rax /* if val==0, val=1 per longjmp semantics */ + inc rax /* if val==0, val=1 per longjmp semantics */ 1: - mov (%rdi),%rbx /* rdi is the jmp_buf, restore regs from it */ - mov 8(%rdi),%rbp - mov 16(%rdi),%r12 - mov 24(%rdi),%r13 - mov 32(%rdi),%r14 - mov 40(%rdi),%r15 - mov 48(%rdi),%rdx /* this ends up being the stack pointer */ - mov %rdx,%rsp - mov 56(%rdi),%rdx /* this is the instruction pointer */ - jmp *%rdx /* goto saved address without altering rsp */ + mov rbx, [rdi] /* rdi is the jmp_buf, restore regs from it */ + mov rbp, [rdi + 8] + mov r12, [rdi + 16] + mov r13, [rdi + 24] + mov r14, [rdi + 32] + mov r15, [rdi + 40] + mov rdx, [rdi + 48] /* this ends up being the stack pointer */ + mov rsp, rdx + mov rdx, [rdi + 56] /* this is the instruction pointer */ + jmp rdx /* goto saved address without altering rsp */ diff --git a/src/header/setjmp/impl/x86_64/setjmp.s b/src/header/setjmp/impl/x86_64/setjmp.s index 98f58b8d65..1aa521f997 100644 --- a/src/header/setjmp/impl/x86_64/setjmp.s +++ b/src/header/setjmp/impl/x86_64/setjmp.s @@ -8,15 +8,15 @@ __setjmp: _setjmp: setjmp: - mov %rbx,(%rdi) /* rdi is jmp_buf, move registers onto it */ - mov %rbp,8(%rdi) - mov %r12,16(%rdi) - mov %r13,24(%rdi) - mov %r14,32(%rdi) - mov %r15,40(%rdi) - lea 8(%rsp),%rdx /* this is our rsp WITHOUT current ret addr */ - mov %rdx,48(%rdi) - mov (%rsp),%rdx /* save return addr ptr for new rip */ - mov %rdx,56(%rdi) - xor %rax,%rax /* always return 0 */ + mov [rdi], rbx /* rdi is jmp_buf, move registers onto it */ + mov [rdi + 8], rbp + mov [rdi + 16], r12 + mov [rdi + 24], r13 + mov [rdi + 32], r14 + mov [rdi + 40], r15 + lea rdx, [rsp + 8] /* this is our rsp WITHOUT current ret addr */ + mov [rdi + 48], rdx + mov rdx, [rsp] /* save return addr ptr for new rip */ + mov [rdi + 56], rdx + xor rax, rax /* always return 0 */ ret diff --git a/src/header/setjmp/impl/x86_64/sigsetjmp.s b/src/header/setjmp/impl/x86_64/sigsetjmp.s new file mode 100644 index 0000000000..41bd05b518 --- /dev/null +++ b/src/header/setjmp/impl/x86_64/sigsetjmp.s @@ -0,0 +1,24 @@ +.global sigsetjmp +.global __sigsetjmp +.type sigsetjmp,@function +.type __sigsetjmp,@function +sigsetjmp: +__sigsetjmp: + test esi, esi + jz 1f + + pop [rdi + 64] + mov qword ptr [rdi + 8 + 72], rbx + mov rbx, rdi + + call setjmp + + push [rbx + 64] + mov rdi, rbx + mov esi, eax + mov rbx, qword ptr [rbx + 8 + 72] + +.hidden __sigsetjmp_tail + jmp __sigsetjmp_tail + +1: jmp setjmp diff --git a/src/header/setjmp/mod.rs b/src/header/setjmp/mod.rs index 6371c9a58b..3ec61d2b0b 100644 --- a/src/header/setjmp/mod.rs +++ b/src/header/setjmp/mod.rs @@ -1,30 +1,33 @@ -//! setjmp implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/setjmp.h.html +//! `setjmp.h` implementation. +//! +//! See . + +use core::arch::global_asm; macro_rules! platform_specific { - ($($arch:expr,$ext:expr;)+) => { + ($($rust_arch:expr,$c_arch:expr,$ext:expr;)+) => { $( - #[cfg(target_arch = $arch)] - global_asm!(include_str!(concat!("impl/", $arch, "/setjmp.", $ext))); - #[cfg(target_arch = $arch)] - global_asm!(include_str!(concat!("impl/", $arch, "/longjmp.", $ext))); + #[cfg(target_arch = $rust_arch)] + global_asm!(include_str!(concat!("impl/", $c_arch, "/setjmp.", $ext))); + #[cfg(target_arch = $rust_arch)] + global_asm!(include_str!(concat!("impl/", $c_arch, "/sigsetjmp.", $ext))); + #[cfg(target_arch = $rust_arch)] + global_asm!(include_str!(concat!("impl/", $c_arch, "/longjmp.", $ext))); )+ } } platform_specific! { - "aarch64","s"; - "arm","s"; - "i386","s"; - "m68k","s"; - "microblaze","s"; - "mips","S"; - "mips64","S"; - "mipsn32","S"; - "or1k","s"; - "powerpc","S"; - "powerpc64","s"; - "s390x","s"; - "sh","S"; - "x32","s"; - "x86_64","s"; + "aarch64","aarch64", "s"; + "x86","i386","s"; + "x86_64","x86_64","s"; + "riscv64", "riscv64", "S"; +} + +//Each platform has different sizes for sigjmp_buf, currently only x86_64 is supported +unsafe extern "C" { + /// See . + pub fn setjmp(jb: *mut u64) -> i32; + /// See . + pub fn longjmp(jb: *mut u64, ret: i32); } diff --git a/src/header/sgtty/cbindgen.toml b/src/header/sgtty/cbindgen.toml index f566f920ee..e442f18f50 100644 --- a/src/header/sgtty/cbindgen.toml +++ b/src/header/sgtty/cbindgen.toml @@ -5,5 +5,8 @@ style = "Tag" no_includes = true cpp_compat = true +[export.rename] +"sgttyb" = "struct sgttyb" + [enum] prefix_with_name = true diff --git a/src/header/sgtty/mod.rs b/src/header/sgtty/mod.rs index 3883ada89a..7d63a33a94 100644 --- a/src/header/sgtty/mod.rs +++ b/src/header/sgtty/mod.rs @@ -1,9 +1,9 @@ //! sgtty implementation that won't work on redox because no ioctl -use crate::{header::sys_ioctl::*, platform::types::*}; +use crate::{header::sys_ioctl::sgttyb, platform::types::c_int}; -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn gtty(fd: c_int, out: *mut sgttyb) -> c_int { - eprintln!("unimplemented: gtty({}, {:p})", fd, out); + todo_skip!(0, "gtty({}, {:p})", fd, out); -1 } diff --git a/src/header/shadow/cbindgen.toml b/src/header/shadow/cbindgen.toml new file mode 100644 index 0000000000..8d36c91ba6 --- /dev/null +++ b/src/header/shadow/cbindgen.toml @@ -0,0 +1,12 @@ +sys_includes = ["sys/types.h", "stdint.h", "stdio.h"] +include_guard = "_RELIBC_SHADOW_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export] +include = ["group"] diff --git a/src/header/shadow/mod.rs b/src/header/shadow/mod.rs new file mode 100644 index 0000000000..23492c4e8f --- /dev/null +++ b/src/header/shadow/mod.rs @@ -0,0 +1,275 @@ +//! `shadow.h` implementation. +//! +//! Non-POSIX, see . + +use core::{ + cell::SyncUnsafeCell, + ops::{Deref, DerefMut}, + pin::Pin, + ptr, + str::FromStr, +}; + +use alloc::{boxed::Box, string::String}; + +use crate::{ + c_str::CStr, + fs::File, + header::fcntl, + io::{BufReader, Lines, prelude::*}, + platform, + platform::types::{c_char, c_int, c_long, c_ulong, size_t}, +}; + +use super::errno::*; + +#[cfg(target_os = "linux")] +const SEPARATOR: char = ':'; + +#[cfg(target_os = "redox")] +const SEPARATOR: char = ';'; + +const SHADOW_FILE: &core::ffi::CStr = c"/etc/shadow"; + +#[derive(Clone, Copy, Debug)] +struct DestBuffer { + ptr: *mut u8, + len: usize, +} + +#[derive(Debug)] +enum MaybeAllocated { + Owned(Pin>), + Borrowed(DestBuffer), +} +impl Deref for MaybeAllocated { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts(dst.ptr, dst.len) + }, + } + } +} +impl DerefMut for MaybeAllocated { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MaybeAllocated::Owned(boxed) => boxed, + MaybeAllocated::Borrowed(dst) => unsafe { + core::slice::from_raw_parts_mut(dst.ptr, dst.len) + }, + } + } +} + +static mut SHADOW_BUF: Option = None; +static mut SHADOW: spwd = spwd { + sp_namp: ptr::null_mut(), + sp_pwdp: ptr::null_mut(), + sp_lstchg: -1, + sp_min: -1, + sp_max: -1, + sp_warn: -1, + sp_inact: -1, + sp_expire: -1, + sp_flag: 0, +}; + +static LINE_READER: SyncUnsafeCell>>> = SyncUnsafeCell::new(None); + +/// Non-POSIX, see . +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct spwd { + pub sp_namp: *mut c_char, + pub sp_pwdp: *mut c_char, + pub sp_lstchg: c_long, + pub sp_min: c_long, + pub sp_max: c_long, + pub sp_warn: c_long, + pub sp_inact: c_long, + pub sp_expire: c_long, + pub sp_flag: c_ulong, +} + +#[derive(Debug)] +enum Error { + EOF, + BufTooSmall, + Syntax, +} + +#[derive(Debug)] +struct OwnedSpwd { + buffer: MaybeAllocated, + reference: spwd, +} + +impl OwnedSpwd { + fn into_global(self) -> *mut spwd { + unsafe { + SHADOW_BUF = Some(self.buffer); + SHADOW = self.reference; + &raw mut SHADOW + } + } +} +fn to_long(s: &str) -> c_long { + c_long::from_str(s).unwrap_or(-1) +} +fn to_ulong(s: &str) -> c_ulong { + c_ulong::from_str(s).unwrap_or(0) +} + +fn parse_spwd(line: String, destbuf: Option) -> Result { + let mut parts = line.split(SEPARATOR); + + let sp_namp_str = parts.next().ok_or(Error::Syntax)?; + let sp_pwdp_str = parts.next().ok_or(Error::Syntax)?; + + //TODO: these are not implemented redox-users crate + let sp_lstchg = to_long(parts.next().unwrap_or("")); + let sp_min = to_long(parts.next().unwrap_or("")); + let sp_max = to_long(parts.next().unwrap_or("")); + let sp_warn = to_long(parts.next().unwrap_or("")); + let sp_inact = to_long(parts.next().unwrap_or("")); + let sp_expire = to_long(parts.next().unwrap_or("")); + let sp_flag = to_ulong(parts.next().unwrap_or("")); + + let string_data_len = sp_namp_str.len() + 1 + sp_pwdp_str.len() + 1; + + let mut buffer = match destbuf { + Some(buf) => { + if buf.len < string_data_len { + platform::ERRNO.set(ERANGE); + return Err(Error::BufTooSmall); + } + MaybeAllocated::Borrowed(buf) + } + None => { + let vec = vec![0; string_data_len]; + MaybeAllocated::Owned(Box::into_pin(vec.into_boxed_slice())) + } + }; + + let (name_slice, rest) = buffer.split_at_mut(sp_namp_str.len() + 1); + name_slice[..sp_namp_str.len()].copy_from_slice(sp_namp_str.as_bytes()); + name_slice[sp_namp_str.len()] = 0; + + let (pwd_slice, _) = rest.split_at_mut(sp_pwdp_str.len() + 1); + pwd_slice[..sp_pwdp_str.len()].copy_from_slice(sp_pwdp_str.as_bytes()); + pwd_slice[sp_pwdp_str.len()] = 0; + + let sp_namp = name_slice.as_mut_ptr().cast::(); + let sp_pwdp = pwd_slice.as_mut_ptr().cast::(); + + let reference = spwd { + sp_namp, + sp_pwdp, + sp_lstchg, + sp_min, + sp_max, + sp_warn, + sp_inact, + sp_expire, + sp_flag, + }; + + Ok(OwnedSpwd { buffer, reference }) +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getspnam(name: *const c_char) -> *mut spwd { + let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else { + return ptr::null_mut(); + }; + let c_name = unsafe { CStr::from_ptr(name) }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { continue }; + if line.starts_with(c_name.to_str().unwrap_or("\0")) + && let Ok(pwd) = parse_spwd(line, None) + { + return pwd.into_global(); + } + } + ptr::null_mut() +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getspnam_r( + name: *const c_char, + result_buf: *mut spwd, + buffer: *mut c_char, + buflen: size_t, + result: *mut *mut spwd, +) -> c_int { + unsafe { *result = ptr::null_mut() }; + + let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) else { + return ENOENT; + }; + let c_name = unsafe { CStr::from_ptr(name).to_str().unwrap_or("\0") }; + + for line in BufReader::new(db).lines() { + let Ok(line) = line else { continue }; + if line.starts_with(c_name) { + let dest_buf = Some(DestBuffer { + ptr: buffer.cast::(), + len: buflen, + }); + return match parse_spwd(line, dest_buf) { + Ok(sp) => { + unsafe { + *result_buf = sp.reference; + *result = result_buf; + } + 0 + } + Err(Error::BufTooSmall) => ERANGE, + _ => ENOENT, + }; + } + } + ENOENT +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setspent() { + let line_reader = unsafe { &mut *LINE_READER.get() }; + if let Ok(db) = File::open(SHADOW_FILE.into(), fcntl::O_RDONLY) { + *line_reader = Some(BufReader::new(db).lines()); + } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn endspent() { + unsafe { + *LINE_READER.get() = None; + } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getspent() -> *mut spwd { + let line_reader = unsafe { &mut *LINE_READER.get() }; + if line_reader.is_none() { + unsafe { + setspent(); + } + } + if let Some(lines) = line_reader + && let Some(Ok(line)) = lines.next() + && let Ok(sp) = parse_spwd(line, None) + { + return sp.into_global(); + } + ptr::null_mut() +} diff --git a/src/header/signal/cbindgen.toml b/src/header/signal/cbindgen.toml index 46bbe11352..0e9c6824ba 100644 --- a/src/header/signal/cbindgen.toml +++ b/src/header/signal/cbindgen.toml @@ -1,14 +1,45 @@ -sys_includes = ["stdint.h", "sys/types.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the pthread_t, size_t, and uid_t types as described in ." +# - "The header shall define the timespec structure as described in ." +# - "pid_t As described in ." +# - "The header shall define the pthread_attr_t type as described in ." +# - "Inclusion of the header may make visible all symbols from the header." +sys_includes = ["sys/types.h", "stdint.h"] include_guard = "_RELIBC_SIGNAL_H" -trailer = "#include " +after_includes = """ +#include // for sigset_t +#include // for timespec from time.h + +#define SIG_DFL ((void (*)(int))0) +#define SIG_IGN ((void (*)(int))1) +#define SIG_ERR ((void (*)(int))-1) +#define SIG_HOLD ((void (*)(int))2) + +typedef struct siginfo siginfo_t; +typedef struct ucontext ucontext_t; +typedef struct mcontext mcontext_t; +typedef long sig_atomic_t; + +struct sigaction { + union { + void (*sa_handler)(int); + void (*sa_sigaction)(int, siginfo_t *, void *); + }; + int sa_flags; + void (*sa_restorer)(void); + sigset_t sa_mask; +}; +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true + +[export.rename] +"sigaction" = "struct sigaction" +"timespec" = "struct timespec" diff --git a/src/header/signal/linux.rs b/src/header/signal/linux.rs index fb8fc5d1aa..e933e3054a 100644 --- a/src/header/signal/linux.rs +++ b/src/header/signal/linux.rs @@ -1,3 +1,7 @@ +use super::{sigset_t, stack_t}; +use crate::platform::types::{c_longlong, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort}; +use core::arch::global_asm; + // Needs to be defined in assembly because it can't have a function prologue // rax is register, 15 is RT_SIGRETURN #[cfg(target_arch = "x86_64")] @@ -5,7 +9,7 @@ global_asm!( " .global __restore_rt __restore_rt: - mov $15, %rax + mov rax, 15 syscall " ); @@ -20,6 +24,16 @@ global_asm!( " ); +#[cfg(target_arch = "riscv64")] +global_asm!( + " + .global __restore_rt + __restore_rt: + li a7, 139 + ecall +" +); + pub const SIGHUP: usize = 1; pub const SIGINT: usize = 2; pub const SIGQUIT: usize = 3; @@ -56,6 +70,9 @@ pub const SIGSYS: usize = 31; pub const SIGUNUSED: usize = SIGSYS; pub const NSIG: usize = 32; +pub const SIGRTMIN: usize = 35; // TODO: decrease to 34 +pub const SIGRTMAX: usize = 64; + pub const SA_NOCLDSTOP: usize = 1; pub const SA_NOCLDWAIT: usize = 2; pub const SA_SIGINFO: usize = 4; @@ -71,3 +88,92 @@ pub const SS_DISABLE: usize = 2; // Those two should be updated from kernel headers pub const MINSIGSTKSZ: usize = 2048; pub const SIGSTKSZ: usize = 8096; + +pub const SI_QUEUE: i32 = -1; +pub const SI_USER: i32 = 0; +pub const SI_TIMER: i32 = 1; +pub const SI_ASYNCIO: i32 = 2; +pub const SI_MESGQ: i32 = 3; + +// si_code values (signal-specific) +pub const ILL_ILLOPC: i32 = 1; +pub const ILL_ILLOPN: i32 = 2; +pub const ILL_ILLADR: i32 = 3; +pub const ILL_ILLTRP: i32 = 4; +pub const ILL_PRVOPC: i32 = 5; +pub const ILL_PRVREG: i32 = 6; +pub const ILL_COPROC: i32 = 7; +pub const ILL_BADSTK: i32 = 8; + +pub const FPE_INTDIV: i32 = 1; +pub const FPE_INTOVF: i32 = 2; +pub const FPE_FLTDIV: i32 = 3; +pub const FPE_FLTOVF: i32 = 4; +pub const FPE_FLTUND: i32 = 5; +pub const FPE_FLTRES: i32 = 6; +pub const FPE_FLTINV: i32 = 7; +pub const FPE_FLTSUB: i32 = 8; + +pub const SEGV_MAPERR: i32 = 1; +pub const SEGV_ACCERR: i32 = 2; + +pub const BUS_ADRALN: i32 = 1; +pub const BUS_ADRERR: i32 = 2; +pub const BUS_OBJERR: i32 = 3; + +pub const TRAP_BRKPT: i32 = 1; +pub const TRAP_TRACE: i32 = 2; + +pub const CLD_EXITED: i32 = 1; +pub const CLD_KILLED: i32 = 2; +pub const CLD_DUMPED: i32 = 3; +pub const CLD_TRAPPED: i32 = 4; +pub const CLD_STOPPED: i32 = 5; +pub const CLD_CONTINUED: i32 = 6; + +// Mirrors the ucontext_t struct from the libc crate on Linux. + +pub(crate) type ucontext_t = ucontext; +pub(crate) type mcontext_t = mcontext; + +#[repr(C)] +pub struct ucontext { + pub uc_flags: c_ulong, + pub uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + pub uc_mcontext: mcontext_t, + pub uc_sigmask: sigset_t, + __private: [c_uchar; 512], +} + +#[repr(C)] +pub struct _libc_fpstate { + pub cwd: c_ushort, + pub swd: c_ushort, + pub ftw: c_ushort, + pub fop: c_ushort, + pub rip: c_ulonglong, + pub rdp: c_ulonglong, + pub mxcsr: c_uint, + pub mxcr_mask: c_uint, + pub _st: [_libc_fpxreg; 8], + pub _xmm: [_libc_xmmreg; 16], + __private: [c_ulonglong; 12], +} +#[repr(C)] +pub struct _libc_fpxreg { + pub significand: [c_ushort; 4], + pub exponent: c_ushort, + __private: [c_ushort; 3], +} + +#[repr(C)] +pub struct _libc_xmmreg { + pub element: [c_uint; 4], +} +#[repr(C)] +pub struct mcontext { + pub gregs: [c_longlong; 23], // TODO: greg_t? + pub fpregs: *mut _libc_fpstate, + __private: [c_ulonglong; 8], +} diff --git a/src/header/signal/mod.rs b/src/header/signal/mod.rs index 1b153c5b41..7356414308 100644 --- a/src/header/signal/mod.rs +++ b/src/header/signal/mod.rs @@ -1,16 +1,29 @@ -//! signal implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/signal.h.html +//! `signal.h` implementation. +//! +//! See . -use core::mem; +use core::{mem, ptr}; use cbitset::BitSet; use crate::{ - header::errno, - platform::{self, types::*, PalSignal, Sys}, + error::{Errno, ResultExt}, + header::{bits_sigset_t::sigset_t, bits_timespec::timespec, errno, setjmp}, + platform::{ + self, ERRNO, Pal, PalSignal, Sys, + types::{ + c_char, c_int, c_ulong, c_ulonglong, c_void, pid_t, pthread_attr_t, pthread_t, size_t, uid_t, + }, + }, }; pub use self::sys::*; +use super::{ + errno::EFAULT, + stdio::{fprintf, stderr}, +}; + #[cfg(target_os = "linux")] #[path = "linux.rs"] pub mod sys; @@ -19,18 +32,28 @@ pub mod sys; #[path = "redox.rs"] pub mod sys; -type SigSet = BitSet<[c_ulong; 1]>; +mod signalfd; +pub use self::signalfd::*; -pub const SIG_DFL: usize = 0; -pub const SIG_IGN: usize = 1; -pub const SIG_ERR: isize = -1; +type SigSet = BitSet<[u64; 1]>; + +pub(crate) const SIG_DFL: usize = 0; +pub(crate) const SIG_IGN: usize = 1; +pub(crate) const SIG_ERR: isize = -1; +pub(crate) const SIG_HOLD: isize = 2; pub const SIG_BLOCK: c_int = 0; pub const SIG_UNBLOCK: c_int = 1; pub const SIG_SETMASK: c_int = 2; +pub const SIGEV_SIGNAL: c_int = 0; +pub const SIGEV_NONE: c_int = 1; +pub const SIGEV_THREAD: c_int = 2; + +/// See . #[repr(C)] #[derive(Clone, Debug)] +/// cbindgen:ignore pub struct sigaction { pub sa_handler: Option, pub sa_flags: c_ulong, @@ -46,207 +69,454 @@ pub struct sigaltstack { pub ss_size: size_t, } -pub type sigset_t = c_ulong; +/// See . +#[repr(C)] +#[derive(Clone)] +pub struct sigevent { + pub sigev_value: sigval, + pub sigev_signo: c_int, + pub sigev_notify: c_int, + pub sigev_notify_function: Option, + pub sigev_notify_attributes: *mut pthread_attr_t, +} +// FIXME: This struct is wrong on Linux +#[repr(C)] +#[derive(Clone, Copy)] +pub struct siginfo { + pub si_signo: c_int, + pub si_errno: c_int, + pub si_code: c_int, + pub si_pid: pid_t, + pub si_uid: uid_t, + pub si_addr: *mut c_void, + pub si_status: c_int, + pub si_value: sigval, +} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_siginfo(a: siginfo) {} + +/// See . +#[derive(Clone, Copy)] +#[repr(C)] +pub union sigval { + pub sival_int: c_int, + pub sival_ptr: *mut c_void, +} + +/// See . +/// cbindgen:ignore +pub type siginfo_t = siginfo; + +/// See pub type stack_t = sigaltstack; -#[no_mangle] +unsafe extern "C" { + pub fn sigsetjmp(jb: *mut c_ulonglong, savemask: c_int) -> c_int; +} + +//NOTE for the following two functions, to see why they're implemented slightly differently from their intended behavior, read +// https://git.musl-libc.org/cgit/musl/commit/?id=583e55122e767b1586286a0d9c35e2a4027998ab +#[unsafe(no_mangle)] +unsafe extern "C" fn __sigsetjmp_tail(jb: *mut c_ulonglong, ret: c_int) -> c_int { + let set = jb.wrapping_add(9); + if ret > 0 { + unsafe { sigprocmask(SIG_SETMASK, set, ptr::null_mut()) }; + } else { + unsafe { sigprocmask(SIG_SETMASK, ptr::null_mut(), set) }; + } + ret +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn siglongjmp(jb: *mut c_ulonglong, ret: c_int) { + unsafe { setjmp::longjmp(jb, ret) }; +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn kill(pid: pid_t, sig: c_int) -> c_int { - Sys::kill(pid, sig) + Sys::kill(pid, sig).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn sigqueue(pid: pid_t, sig: c_int, val: sigval) -> c_int { + Sys::sigqueue(pid, sig, val) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn killpg(pgrp: pid_t, sig: c_int) -> c_int { - Sys::killpg(pgrp, sig) + Sys::killpg(pgrp, sig).map(|()| 0).or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn pthread_sigmask( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_kill(thread: pthread_t, sig: c_int) -> c_int { + let os_tid = { + let pthread = unsafe { &*(thread as *const crate::pthread::Pthread) }; + unsafe { pthread.os_tid.get().read() } + }; + crate::header::pthread::e(unsafe { Sys::rlct_kill(os_tid, sig as usize) }) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_sigmask( how: c_int, set: *const sigset_t, oldset: *mut sigset_t, ) -> c_int { // On Linux and Redox, pthread_sigmask and sigprocmask are equivalent - if sigprocmask(how, set, oldset) == 0 { + if unsafe { sigprocmask(how, set, oldset) } == 0 { 0 } else { //TODO: Fix race - unsafe { platform::errno } + platform::ERRNO.get() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn raise(sig: c_int) -> c_int { - Sys::raise(sig) + Sys::raise(sig).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn sigaction( sig: c_int, act: *const sigaction, oact: *mut sigaction, ) -> c_int { - let act_opt = act.as_ref().map(|act| { - let mut act_clone = act.clone(); - act_clone.sa_flags |= SA_RESTORER as c_ulong; - act_clone.sa_restorer = Some(__restore_rt); - act_clone - }); - Sys::sigaction(sig, act_opt.as_ref(), oact.as_mut()) + Sys::sigaction(sig, unsafe { act.as_ref() }, unsafe { oact.as_mut() }) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn sigaddset(set: *mut sigset_t, signo: c_int) -> c_int { - if signo <= 0 || signo as usize > NSIG { - unsafe { - platform::errno = errno::EINVAL; - } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaddset(set: *mut sigset_t, signo: c_int) -> c_int { + if signo <= 0 || signo as usize > NSIG.max(SIGRTMAX) + /* TODO */ + { + platform::ERRNO.set(errno::EINVAL); return -1; } - if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } { + if let Some(set) = unsafe { (set.cast::()).as_mut() } { set.insert(signo as usize - 1); // 0-indexed usize, please! } 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int { - if !ss.is_null() { - if (*ss).ss_flags != SS_DISABLE as c_int { - return errno::EINVAL; - } - if (*ss).ss_size < MINSIGSTKSZ { - return errno::ENOMEM; - } + unsafe { + Sys::sigaltstack(ss.as_ref(), old_ss.as_mut()) + .map(|()| 0) + .or_minus_one_errno() } - - Sys::sigaltstack(ss, old_ss) } -#[no_mangle] -pub extern "C" fn sigdelset(set: *mut sigset_t, signo: c_int) -> c_int { - if signo <= 0 || signo as usize > NSIG { - unsafe { - platform::errno = errno::EINVAL; - } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigdelset(set: *mut sigset_t, signo: c_int) -> c_int { + if signo <= 0 || signo as usize > NSIG.max(SIGRTMAX) + /* TODO */ + { + platform::ERRNO.set(errno::EINVAL); return -1; } - if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } { + if let Some(set) = unsafe { (set.cast::()).as_mut() } { set.remove(signo as usize - 1); // 0-indexed usize, please! } 0 } -#[no_mangle] -pub extern "C" fn sigemptyset(set: *mut sigset_t) -> c_int { - if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigemptyset(set: *mut sigset_t) -> c_int { + if let Some(set) = unsafe { (set.cast::()).as_mut() } { set.clear(); } 0 } -#[no_mangle] -pub extern "C" fn sigfillset(set: *mut sigset_t) -> c_int { - if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigfillset(set: *mut sigset_t) -> c_int { + if let Some(set) = unsafe { (set.cast::()).as_mut() } { set.fill(.., true); } 0 } -// #[no_mangle] -pub extern "C" fn sighold(sig: c_int) -> c_int { - unimplemented!(); +/// See . +/// +/// Present in issue 7. Removed in issue 8. +/// +/// Use of this function is unspecified in a multi-threaded process. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sighold(sig: c_int) -> c_int { + let mut pset = mem::MaybeUninit::::uninit(); + unsafe { sigemptyset(pset.as_mut_ptr()) }; + let mut set = unsafe { pset.assume_init() }; + if unsafe { sigaddset(&raw mut set, sig) } < 0 { + return -1; + } + unsafe { sigprocmask(SIG_BLOCK, &raw const set, ptr::null_mut()) } } -// #[no_mangle] +/// See . +/// +/// Present in issue 7. Removed in issue 8. +/// +/// Use of this function is unspecified in a multi-threaded process. +#[allow(clippy::missing_transmute_annotations)] +#[unsafe(no_mangle)] pub extern "C" fn sigignore(sig: c_int) -> c_int { - unimplemented!(); + let mut psa = mem::MaybeUninit::::uninit(); + unsafe { sigemptyset(&raw mut (*psa.as_mut_ptr()).sa_mask) }; + let mut sa = unsafe { psa.assume_init() }; + sa.sa_handler = unsafe { mem::transmute(SIG_IGN) }; + sa.sa_flags = 0; + unsafe { sigaction(sig, &raw const sa, ptr::null_mut()) } } -// #[no_mangle] +/// See . +/// +/// Marked obsolescent in issue 7. Removed in issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn siginterrupt(sig: c_int, flag: c_int) -> c_int { - unimplemented!(); + let mut psa = mem::MaybeUninit::::uninit(); + unsafe { sigaction(sig, ptr::null_mut(), psa.as_mut_ptr()) }; + let mut sa = unsafe { psa.assume_init() }; + if flag != 0 { + sa.sa_flags &= !SA_RESTART as c_ulong; + } else { + sa.sa_flags |= SA_RESTART as c_ulong; + } + + unsafe { sigaction(sig, &raw const sa, ptr::null_mut()) } } -#[no_mangle] -pub extern "C" fn sigismember(set: *const sigset_t, signo: c_int) -> c_int { - if signo <= 0 || signo as usize > NSIG { - unsafe { - platform::errno = errno::EINVAL; - } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigismember(set: *const sigset_t, signo: c_int) -> c_int { + if signo <= 0 || signo as usize > NSIG.max(SIGRTMAX) + /* TODO */ + { + platform::ERRNO.set(errno::EINVAL); return -1; } - if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } { - if set.contains(signo as usize - 1) { - return 1; - } + if let Some(set) = unsafe { (set as *mut SigSet).as_mut() } + && set.contains(signo as usize - 1) + { + return 1; } 0 } -extern "C" { - // Defined in assembly inside platform/x/mod.rs - fn __restore_rt(); -} - -#[no_mangle] +/// See . +#[allow(clippy::missing_transmute_annotations)] +#[unsafe(no_mangle)] pub extern "C" fn signal( sig: c_int, func: Option, ) -> Option { let sa = sigaction { sa_handler: func, - sa_flags: SA_RESTART as c_ulong, - sa_restorer: Some(__restore_rt), + sa_flags: SA_RESTART as _, + sa_restorer: None, // set by platform if applicable sa_mask: sigset_t::default(), }; let mut old_sa = mem::MaybeUninit::uninit(); - if unsafe { sigaction(sig, &sa, old_sa.as_mut_ptr()) } < 0 { + if unsafe { sigaction(sig, &raw const sa, old_sa.as_mut_ptr()) } < 0 { mem::forget(old_sa); return unsafe { mem::transmute(SIG_ERR) }; } unsafe { old_sa.assume_init() }.sa_handler } -// #[no_mangle] -pub extern "C" fn sigpause(sig: c_int) -> c_int { - unimplemented!(); +/// See . +/// +/// Present in issue 7. Removed in issue 8. +/// +/// Use of this function is unspecified in a multi-threaded process. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigpause(sig: c_int) -> c_int { + let mut pset = mem::MaybeUninit::::uninit(); + unsafe { sigprocmask(0, ptr::null_mut(), pset.as_mut_ptr()) }; + let mut set = unsafe { pset.assume_init() }; + if unsafe { sigdelset(&raw mut set, sig) } == -1 { + return -1; + } + unsafe { sigsuspend(&raw const set) } } -// #[no_mangle] -pub extern "C" fn sigpending(set: *mut sigset_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigpending(set: *mut sigset_t) -> c_int { + (|| Sys::sigpending(unsafe { set.as_mut().ok_or(Errno(EFAULT)) }?))() + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn sigprocmask(how: c_int, set: *const sigset_t, oset: *mut sigset_t) -> c_int { - Sys::sigprocmask(how, set, oset) +// TODO: Double-check this mask. +// This prevents the application from blocking the two signals SIGRTMIN - 1 and SIGRTMIN - 2 which +// are (at least meant to be) used internally for timers and pthread cancellation. On Linux this is +// 32 and 33 (same as NPTL reserves), whereas this on Redox is 33 and 34 (TODO: could this be +// changed to 32 and 33 for Redox too, since there's currently no support for "sigqueue" targeting +// specific threads). +const RLCT_SIGNAL_MASK: sigset_t = (1 << ((SIGRTMIN - 1) - 1)) | (1 << ((SIGRTMIN - 2) - 1)); + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigprocmask( + how: c_int, + set: *const sigset_t, + oset: *mut sigset_t, +) -> c_int { + (|| { + let set = unsafe { set.as_ref().map(|&block| block & !RLCT_SIGNAL_MASK) }; + let mut oset = unsafe { oset.as_mut() }; + + Sys::sigprocmask( + how, + set.as_ref(), + oset.as_deref_mut(), // as_deref_mut for lifetime reasons + )?; + + if let Some(oset) = oset { + *oset &= !RLCT_SIGNAL_MASK; + } + + Ok(0) + })() + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn sigrelse(sig: c_int) -> c_int { - unimplemented!(); +/// See . +/// +/// Present in issue 7. Removed in issue 8. +/// +/// Use of this function is unspecified in a multi-threaded process. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigrelse(sig: c_int) -> c_int { + let mut pset = mem::MaybeUninit::::uninit(); + unsafe { sigemptyset(pset.as_mut_ptr()) }; + let mut set = unsafe { pset.assume_init() }; + if unsafe { sigaddset(&raw mut set, sig) } < 0 { + return -1; + } + unsafe { sigprocmask(SIG_UNBLOCK, &raw const set, ptr::null_mut()) } } -// #[no_mangle] -pub extern "C" fn sigset(sig: c_int, func: fn(c_int)) -> fn(c_int) { - unimplemented!(); +/// See . +/// +/// Present in issue 7. Removed in issue 8. +/// +/// Use of this function is unspecified in a multi-threaded process. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigset( + sig: c_int, + func: Option, +) -> Option { + let mut old_sa = mem::MaybeUninit::uninit(); + let mut pset = mem::MaybeUninit::::uninit(); + let sig_hold: Option = unsafe { mem::transmute(SIG_HOLD) }; + let sig_err: Option = unsafe { mem::transmute(SIG_ERR) }; + unsafe { sigemptyset(pset.as_mut_ptr()) }; + let mut set = unsafe { pset.assume_init() }; + if unsafe { sigaddset(&raw mut set, sig) } < 0 { + return sig_err; + } else { + let is_equal = { + match (func, sig_hold) { + (None, None) => true, + (Some(_), None) | (None, Some(_)) => false, + (Some(f), Some(sh)) => ptr::fn_addr_eq(f, sh), + } + }; + if is_equal { + if unsafe { sigaction(sig, ptr::null_mut(), old_sa.as_mut_ptr()) } < 0 + || unsafe { sigprocmask(SIG_BLOCK, &raw const set, &raw mut set) } < 0 + { + mem::forget(old_sa); + return sig_err; + } + } else { + let mut sa = sigaction { + sa_handler: func, + sa_flags: 0, + sa_restorer: None, // set by platform if applicable + sa_mask: sigset_t::default(), + }; + unsafe { sigemptyset(&raw mut sa.sa_mask) }; + if unsafe { sigaction(sig, &raw const sa, old_sa.as_mut_ptr()) } < 0 + || unsafe { sigprocmask(SIG_UNBLOCK, &raw const set, &raw mut set) } < 0 + { + mem::forget(old_sa); + return sig_err; + } + } + } + if unsafe { sigismember(&raw const set, sig) } == 1 { + return sig_hold; + } + unsafe { old_sa.assume_init().sa_handler } } -// #[no_mangle] -pub extern "C" fn sigsuspend(sigmask: *const sigset_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigsuspend(sigmask: *const sigset_t) -> c_int { + Err(Sys::sigsuspend(unsafe { &*sigmask })).or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn sigwait(set: *const sigset_t, sig: *mut c_int) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigwait(set: *const sigset_t, sig: *mut c_int) -> c_int { + let mut pinfo = mem::MaybeUninit::::uninit(); + if unsafe { sigtimedwait(set, pinfo.as_mut_ptr(), ptr::null_mut()) } < 0 { + return -1; + } + let info = unsafe { pinfo.assume_init() }; + unsafe { (*sig) = info.si_signo }; + 0 } -pub const _signal_strings: [&str; 32] = [ +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigtimedwait( + set: *const sigset_t, + // s/siginfo_t/siginfo due to https://github.com/mozilla/cbindgen/issues/621 + sig: *mut siginfo, + // POSIX leaves behavior unspecified if this is NULL, but on both Linux and Redox, NULL is used + // to differentiate between sigtimedwait and sigwaitinfo internally + tp: *const timespec, +) -> c_int { + Sys::sigtimedwait(unsafe { &*set }, unsafe { sig.as_mut() }, unsafe { + tp.as_ref() + }) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigwaitinfo(set: *const sigset_t, sig: *mut siginfo_t) -> c_int { + unsafe { sigtimedwait(set, sig, core::ptr::null()) } +} + +pub(crate) const SIGNAL_STRINGS: [&str; 32] = [ "Unknown signal\0", "Hangup\0", "Interrupt\0", @@ -280,3 +550,38 @@ pub const _signal_strings: [&str; 32] = [ "Power failure\0", "Bad system call\0", ]; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn psignal(sig: c_int, prefix: *const c_char) { + let c_description = usize::try_from(sig) + .ok() + .and_then(|idx| SIGNAL_STRINGS.get(idx)) + .unwrap_or(&SIGNAL_STRINGS[0]) + .as_ptr(); + // fprintf can affect errno, so we save errno and restore it + let old_errno = ERRNO.get(); + // POSIX says that "prefix" shall be written if it isn't null or an empty string. + // Otherwise, only the signal description should be written + if prefix.is_null() { + unsafe { + fprintf(stderr, c"%s\n".as_ptr(), c_description); + } + } else { + unsafe { + fprintf(stderr, c"%s: %s\n".as_ptr(), prefix, c_description); + } + } + ERRNO.set(old_errno); +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn psiginfo(info: *const siginfo_t, prefix: *const c_char) { + unsafe { + psignal((*info).si_signo, prefix); + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_stupid_struct_sigevent_for_timer(_: sigevent) {} diff --git a/src/header/signal/redox.rs b/src/header/signal/redox.rs index 03e05f03e5..0159dbdc5a 100644 --- a/src/header/signal/redox.rs +++ b/src/header/signal/redox.rs @@ -1,24 +1,10 @@ -// Needs to be defined in assembly because it can't have a function prologue -// rax is register, 119 is SIGRETURN -#[cfg(target_arch = "x86_64")] -global_asm!( - " - .global __restore_rt - __restore_rt: - mov $119, %rax - int $0x80 -" -); -// x8 is register, 119 is SIGRETURN -#[cfg(target_arch = "aarch64")] -global_asm!( - " - .global __restore_rt - __restore_rt: - mov x8, #119 - svc 0 -" -); +use redox_rt::signal::SiginfoAbi; + +#[cfg(any(target_arch = "x86", target_arch = "aarch64", target_arch = "riscv64"))] +use crate::platform::types::c_uchar; +use crate::platform::types::{c_uint, c_ulong}; + +use super::{siginfo_t, sigset_t, stack_t}; pub const SIGHUP: usize = 1; pub const SIGINT: usize = 2; @@ -53,18 +39,159 @@ pub const SIGPWR: usize = 30; pub const SIGSYS: usize = 31; pub const NSIG: usize = 32; -pub const SA_NOCLDSTOP: usize = 0x00000001; -pub const SA_NOCLDWAIT: usize = 0x00000002; -pub const SA_SIGINFO: usize = 0x00000004; -pub const SA_RESTORER: usize = 0x04000000; -pub const SA_ONSTACK: usize = 0x08000000; -pub const SA_RESTART: usize = 0x10000000; -pub const SA_NODEFER: usize = 0x40000000; -pub const SA_RESETHAND: usize = 0x80000000; +pub const SIGRTMIN: usize = 35; +pub const SIGRTMAX: usize = 64; + +pub const SA_NOCLDWAIT: usize = 0x0000_0002; +pub const SA_RESTORER: usize = 0x0000_0004; // TODO: remove +pub const SA_SIGINFO: usize = 0x0200_0000; +pub const SA_ONSTACK: usize = 0x0400_0000; +pub const SA_RESTART: usize = 0x0800_0000; +pub const SA_NODEFER: usize = 0x1000_0000; +pub const SA_RESETHAND: usize = 0x2000_0000; +pub const SA_NOCLDSTOP: usize = 0x4000_0000; pub const SS_ONSTACK: usize = 0x00000001; pub const SS_DISABLE: usize = 0x00000002; -// TODO: It's just a guess based on Linux +const _: () = { + if SS_ONSTACK != redox_rt::signal::SS_ONSTACK { + panic!(); + } + if SS_DISABLE != redox_rt::signal::SS_DISABLE { + panic!(); + } + if MINSIGSTKSZ != redox_rt::signal::MIN_SIGALTSTACK_SIZE { + panic!(); + } +}; + +// should include both SigStack size, and some extra room for the libc handler pub const MINSIGSTKSZ: usize = 2048; + pub const SIGSTKSZ: usize = 8096; + +pub const SI_QUEUE: i32 = -1; +pub const SI_USER: i32 = 0; +pub const SI_TIMER: i32 = 1; +pub const SI_ASYNCIO: i32 = 2; +pub const SI_MESGQ: i32 = 3; + +// si_code values (signal-specific) +pub const ILL_ILLOPC: i32 = 1; +pub const ILL_ILLOPN: i32 = 2; +pub const ILL_ILLADR: i32 = 3; +pub const ILL_ILLTRP: i32 = 4; +pub const ILL_PRVOPC: i32 = 5; +pub const ILL_PRVREG: i32 = 6; +pub const ILL_COPROC: i32 = 7; +pub const ILL_BADSTK: i32 = 8; + +pub const FPE_INTDIV: i32 = 1; +pub const FPE_INTOVF: i32 = 2; +pub const FPE_FLTDIV: i32 = 3; +pub const FPE_FLTOVF: i32 = 4; +pub const FPE_FLTUND: i32 = 5; +pub const FPE_FLTRES: i32 = 6; +pub const FPE_FLTINV: i32 = 7; +pub const FPE_FLTSUB: i32 = 8; + +pub const SEGV_MAPERR: i32 = 1; +pub const SEGV_ACCERR: i32 = 2; + +pub const BUS_ADRALN: i32 = 1; +pub const BUS_ADRERR: i32 = 2; +pub const BUS_OBJERR: i32 = 3; + +pub const TRAP_BRKPT: i32 = 1; +pub const TRAP_TRACE: i32 = 2; + +pub const CLD_EXITED: i32 = 1; +pub const CLD_KILLED: i32 = 2; +pub const CLD_DUMPED: i32 = 3; +pub const CLD_TRAPPED: i32 = 4; +pub const CLD_STOPPED: i32 = 5; +pub const CLD_CONTINUED: i32 = 6; + +pub(crate) type ucontext_t = ucontext; +pub(crate) type mcontext_t = mcontext; + +//TODO: share definition with SigStack? +#[repr(C)] +pub struct ucontext { + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" + ))] + _pad: [c_ulong; 1], // pad from 7*8 to 64 + + #[cfg(target_arch = "x86")] + _pad: [c_ulong; 3], // pad from 9*4 to 12*4 + + pub uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + pub uc_sigmask: sigset_t, + _sival: c_ulong, + _sigcode: c_uint, + _signum: c_uint, + pub uc_mcontext: mcontext_t, +} + +#[cfg(target_arch = "x86")] +#[repr(C)] +pub struct mcontext { + _opaque: [c_uchar; 512], +} + +//TODO: share definition with ArchIntRegs? +//TODO: repr(align(16))? +#[cfg(target_arch = "x86_64")] +#[repr(C)] +pub struct mcontext { + pub ymm_upper: [[c_ulong; 2]; 16], + pub fxsave: [[c_ulong; 2]; 29], + pub r15: c_ulong, // fxsave "available" +0 + pub r14: c_ulong, // available +8 + pub r13: c_ulong, // available +16 + pub r12: c_ulong, // available +24 + pub rbp: c_ulong, // available +32 + pub rbx: c_ulong, // available +40 + pub r11: c_ulong, // outside fxsave, and so on + pub r10: c_ulong, + pub r9: c_ulong, + pub r8: c_ulong, + pub rax: c_ulong, + pub rcx: c_ulong, + pub rdx: c_ulong, + pub rsi: c_ulong, + pub rdi: c_ulong, + pub rflags: c_ulong, + pub rip: c_ulong, + pub rsp: c_ulong, +} + +#[cfg(target_arch = "aarch64")] +#[repr(C)] +pub struct mcontext { + _opaque: [c_uchar; 272], +} + +#[cfg(target_arch = "riscv64")] +#[repr(C)] +pub struct mcontext { + _opaque: [c_uchar; 520], +} + +#[unsafe(no_mangle)] +pub extern "C" fn __completely_unused_cbindgen_workaround_fn_ucontext_mcontext( + a: *const ucontext_t, + b: *const mcontext_t, +) { +} + +impl From for siginfo_t { + fn from(value: SiginfoAbi) -> Self { + unsafe { core::mem::transmute(value) } + } +} diff --git a/src/header/signal/signalfd.rs b/src/header/signal/signalfd.rs new file mode 100644 index 0000000000..790b6bdba8 --- /dev/null +++ b/src/header/signal/signalfd.rs @@ -0,0 +1,103 @@ +use core::{mem, ptr}; + +use crate::{ + error::{Errno, ResultExt}, + header::fcntl::{ + FD_CLOEXEC, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC, O_NONBLOCK, O_RDWR, fcntl, + }, + platform::{ + ERRNO, Pal, Sys, + types::{c_int, c_ulonglong}, + }, +}; + +use super::{SIG_BLOCK, sigprocmask, sigset_t}; + +pub const SFD_CLOEXEC: c_int = 0x80000; +pub const SFD_NONBLOCK: c_int = 0x800; + +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct signalfd_siginfo { + pub ssi_signo: u32, + pub ssi_errno: i32, + pub ssi_code: i32, + pub ssi_pid: u32, + pub ssi_uid: u32, + pub ssi_fd: i32, + pub ssi_tid: u32, + pub ssi_band: u32, + pub ssi_overrun: u32, + pub ssi_trapno: u32, + pub ssi_status: i32, + pub ssi_int: i32, + pub ssi_ptr: u64, + pub ssi_utime: u64, + pub ssi_stime: u64, + pub ssi_addr: u64, + pub ssi_addr_lsb: u16, + pub __pad2: u16, + pub ssi_syscall: i32, + pub ssi_call_addr: u64, + pub ssi_arch: u32, + pub __pad: [u8; 28], +} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_signalfd_siginfo(siginfo: signalfd_siginfo) {} + +fn signalfd4_inner(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> Result { + let supported = SFD_CLOEXEC | SFD_NONBLOCK; + if flags & !supported != 0 || masksize != mem::size_of::() { + return Err(Errno(crate::header::errno::EINVAL)); + } + if mask.is_null() { + return Err(Errno(crate::header::errno::EFAULT)); + } + + let new_fd = if fd == -1 { + let mut oflag = O_RDWR; + if flags & SFD_CLOEXEC == SFD_CLOEXEC { + oflag |= O_CLOEXEC; + } + if flags & SFD_NONBLOCK == SFD_NONBLOCK { + oflag |= O_NONBLOCK; + } + Sys::open(c"/scheme/event".into(), oflag, 0)? + } else { + if flags & SFD_CLOEXEC == SFD_CLOEXEC + && unsafe { fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong) } < 0 + { + return Err(Errno(ERRNO.get())); + } + if flags & SFD_NONBLOCK == SFD_NONBLOCK { + let current = unsafe { fcntl(fd, F_GETFL, 0 as c_ulonglong) }; + if current < 0 { + return Err(Errno(ERRNO.get())); + } + if unsafe { fcntl(fd, F_SETFL, (current | O_NONBLOCK) as c_ulonglong) } < 0 { + return Err(Errno(ERRNO.get())); + } + } + fd + }; + + if unsafe { sigprocmask(SIG_BLOCK, mask, ptr::null_mut()) } < 0 { + if fd == -1 { + let _ = Sys::close(new_fd); + } + return Err(Errno(ERRNO.get())); + } + + Ok(new_fd) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn signalfd4(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> c_int { + signalfd4_inner(fd, mask, masksize, flags).or_minus_one_errno() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn signalfd(fd: c_int, mask: *const sigset_t, masksize: usize) -> c_int { + unsafe { signalfd4(fd, mask, masksize, 0) } +} diff --git a/src/header/spawn/cbindgen.toml b/src/header/spawn/cbindgen.toml new file mode 100644 index 0000000000..c6b18231ea --- /dev/null +++ b/src/header/spawn/cbindgen.toml @@ -0,0 +1,21 @@ +sys_includes = ["sys/types.h", "signal.h", "sched.h"] +include_guard = "_RELIBC_SPAWN_H" +language = "C" +style = "Both" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export] +include = [ + "posix_spawn_file_actions_t", + "posix_spawnattr_t", + "POSIX_SPAWN_RESETIDS", + "POSIX_SPAWN_SETPGROUP", + "POSIX_SPAWN_SETSIGDEF", + "POSIX_SPAWN_SETSIGMASK", + "POSIX_SPAWN_SETSCHEDULER", + "POSIX_SPAWN_SETSCHEDPARAM", +] diff --git a/src/header/spawn/mod.rs b/src/header/spawn/mod.rs new file mode 100644 index 0000000000..163b765d5f --- /dev/null +++ b/src/header/spawn/mod.rs @@ -0,0 +1,727 @@ +// POSIX spawn.h implementation +// Spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/spawn.h.html +// +// posix_spawn / posix_spawnp are implemented as fork + file actions + exec. +// File actions and spawn attributes are stored in opaque buffers embedded +// directly in the public types (no heap indirection for the struct itself; +// action entries use a linked list allocated on the heap). + +use alloc::boxed::Box; +use alloc::ffi::CString; +use core::mem; +use core::ptr; + +use crate::{ + c_str::CStr, + header::{ + errno::EINVAL, + sched, + signal, + unistd, + }, + platform::{self, Pal, Sys, types::*}, +}; + +// ─── Flags ─────────────────────────────────────────────────────────────────── + +pub const POSIX_SPAWN_RESETIDS: c_short = 0x01; +pub const POSIX_SPAWN_SETPGROUP: c_short = 0x02; +pub const POSIX_SPAWN_SETSIGDEF: c_short = 0x04; +pub const POSIX_SPAWN_SETSIGMASK: c_short = 0x08; +pub const POSIX_SPAWN_SETSCHEDULER: c_short = 0x10; +pub const POSIX_SPAWN_SETSCHEDPARAM: c_short = 0x20; + +// ─── Internal types ────────────────────────────────────────────────────────── + +const ACTION_KIND_OPEN: c_int = 1; +const ACTION_KIND_CLOSE: c_int = 2; +const ACTION_KIND_DUP2: c_int = 3; + +#[repr(C)] +struct ActionNode { + kind: c_int, + fd: c_int, + newfd: c_int, + oflag: c_int, + mode: c_int, + path_ptr: *mut c_char, + next: *mut ActionNode, +} + +#[repr(C)] +struct ActionsInner { + magic: u32, + head: *mut ActionNode, + tail: *mut ActionNode, +} + +#[repr(C)] +struct AttrsInner { + magic: u32, + flags: c_short, + pgroup: pid_t, + sigdefault: c_ulonglong, + sigmask: c_ulonglong, + schedpolicy: c_int, + sched_priority: c_int, +} + +const ACTIONS_MAGIC: u32 = 0x53504641; // "SPFA" +const ATTRS_MAGIC: u32 = 0x53504154; // "SPAT" + +// ─── Public opaque types ───────────────────────────────────────────────────── +// Sized to hold their respective Inner types with room to spare. + +/// See . +#[repr(C)] +pub struct posix_spawn_file_actions_t { + __data: [u64; 8], +} + +/// See . +#[repr(C)] +pub struct posix_spawnattr_t { + __data: [u64; 16], +} + +const _: () = assert!(mem::size_of::() <= mem::size_of::()); +const _: () = assert!(mem::size_of::() <= mem::size_of::()); + +fn actions_as_inner(a: *mut posix_spawn_file_actions_t) -> *mut ActionsInner { + a.cast() +} + +fn attrs_as_inner(a: *mut posix_spawnattr_t) -> *mut AttrsInner { + a.cast() +} + +unsafe fn free_action_list(head: *mut ActionNode) { + let mut cur = head; + while !cur.is_null() { + let node = unsafe { Box::from_raw(cur) }; + if !node.path_ptr.is_null() { + drop(unsafe { CString::from_raw(node.path_ptr) }); + } + cur = node.next; + } +} + +// ─── File actions API ──────────────────────────────────────────────────────── + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_init( + actions: *mut posix_spawn_file_actions_t, +) -> c_int { + if actions.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = actions_as_inner(actions); + unsafe { + (*inner).magic = ACTIONS_MAGIC; + (*inner).head = ptr::null_mut(); + (*inner).tail = ptr::null_mut(); + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_destroy( + actions: *mut posix_spawn_file_actions_t, +) -> c_int { + if actions.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = actions_as_inner(actions); + unsafe { + if (*inner).magic != ACTIONS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + free_action_list((*inner).head); + (*inner).magic = 0; + (*inner).head = ptr::null_mut(); + (*inner).tail = ptr::null_mut(); + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_addclose( + actions: *mut posix_spawn_file_actions_t, + fd: c_int, +) -> c_int { + if actions.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = actions_as_inner(actions); + unsafe { + if (*inner).magic != ACTIONS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + } + + let node = Box::into_raw(Box::new(ActionNode { + kind: ACTION_KIND_CLOSE, + fd, + newfd: -1, + oflag: 0, + mode: 0, + path_ptr: ptr::null_mut(), + next: ptr::null_mut(), + })); + + unsafe { append_action(inner, node) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_adddup2( + actions: *mut posix_spawn_file_actions_t, + fd: c_int, + newfd: c_int, +) -> c_int { + if actions.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = actions_as_inner(actions); + unsafe { + if (*inner).magic != ACTIONS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + } + + let node = Box::into_raw(Box::new(ActionNode { + kind: ACTION_KIND_DUP2, + fd, + newfd, + oflag: 0, + mode: 0, + path_ptr: ptr::null_mut(), + next: ptr::null_mut(), + })); + + unsafe { append_action(inner, node) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn_file_actions_addopen( + actions: *mut posix_spawn_file_actions_t, + fd: c_int, + path: *const c_char, + oflag: c_int, + mode: mode_t, +) -> c_int { + if actions.is_null() || path.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = actions_as_inner(actions); + unsafe { + if (*inner).magic != ACTIONS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + } + + let path_owned = CString::new(unsafe { CStr::from_ptr(path) }.to_bytes()).unwrap(); + let path_dup = CString::into_raw(path_owned); + + let node = Box::into_raw(Box::new(ActionNode { + kind: ACTION_KIND_OPEN, + fd, + newfd: -1, + oflag, + mode: mode as c_int, + path_ptr: path_dup, + next: ptr::null_mut(), + })); + + unsafe { append_action(inner, node) } +} + +unsafe fn append_action(inner: *mut ActionsInner, node: *mut ActionNode) -> c_int { + unsafe { + if (*inner).tail.is_null() { + (*inner).head = node; + } else { + (*(*inner).tail).next = node; + } + (*inner).tail = node; + } + 0 +} + +// ─── Spawn attributes API ──────────────────────────────────────────────────── + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_init(attr: *mut posix_spawnattr_t) -> c_int { + if attr.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + ptr::write_bytes(inner as *mut u8, 0, mem::size_of::()); + (*inner).magic = ATTRS_MAGIC; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_destroy(attr: *mut posix_spawnattr_t) -> c_int { + if attr.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + (*inner).magic = 0; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getflags( + attr: *const posix_spawnattr_t, + flags: *mut c_short, +) -> c_int { + if attr.is_null() || flags.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + *flags = (*inner).flags; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setflags( + attr: *mut posix_spawnattr_t, + flags: c_short, +) -> c_int { + if attr.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + let valid = POSIX_SPAWN_RESETIDS + | POSIX_SPAWN_SETPGROUP + | POSIX_SPAWN_SETSIGDEF + | POSIX_SPAWN_SETSIGMASK + | POSIX_SPAWN_SETSCHEDULER + | POSIX_SPAWN_SETSCHEDPARAM; + if (flags & !valid) != 0 { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).flags = flags; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getpgroup( + attr: *const posix_spawnattr_t, + pgroup: *mut pid_t, +) -> c_int { + if attr.is_null() || pgroup.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + *pgroup = (*inner).pgroup; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setpgroup( + attr: *mut posix_spawnattr_t, + pgroup: pid_t, +) -> c_int { + if attr.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).pgroup = pgroup; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getsigmask( + attr: *const posix_spawnattr_t, + sigmask: *mut c_ulonglong, +) -> c_int { + if attr.is_null() || sigmask.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + *sigmask = (*inner).sigmask; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setsigmask( + attr: *mut posix_spawnattr_t, + sigmask: *const c_ulonglong, +) -> c_int { + if attr.is_null() || sigmask.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).sigmask = *sigmask; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getsigdefault( + attr: *const posix_spawnattr_t, + sigdefault: *mut c_ulonglong, +) -> c_int { + if attr.is_null() || sigdefault.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + *sigdefault = (*inner).sigdefault; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setsigdefault( + attr: *mut posix_spawnattr_t, + sigdefault: *const c_ulonglong, +) -> c_int { + if attr.is_null() || sigdefault.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).sigdefault = *sigdefault; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getschedpolicy( + attr: *const posix_spawnattr_t, + schedpolicy: *mut c_int, +) -> c_int { + if attr.is_null() || schedpolicy.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + *schedpolicy = (*inner).schedpolicy; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setschedpolicy( + attr: *mut posix_spawnattr_t, + schedpolicy: c_int, +) -> c_int { + if attr.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).schedpolicy = schedpolicy; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_getschedparam( + attr: *const posix_spawnattr_t, + schedparam: *mut sched::sched_param, +) -> c_int { + if attr.is_null() || schedparam.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr as *mut posix_spawnattr_t); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*schedparam).sched_priority = (*inner).sched_priority; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnattr_setschedparam( + attr: *mut posix_spawnattr_t, + schedparam: *const sched::sched_param, +) -> c_int { + if attr.is_null() || schedparam.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + let inner = attrs_as_inner(attr); + unsafe { + if (*inner).magic != ATTRS_MAGIC { + platform::ERRNO.set(EINVAL); + return -1; + } + (*inner).sched_priority = (*schedparam).sched_priority; + } + 0 +} + +// ─── Core spawn implementation ─────────────────────────────────────────────── + +unsafe fn do_spawn( + path: *const c_char, + search_path: bool, + actions: *const posix_spawn_file_actions_t, + attr: *const posix_spawnattr_t, + argv: *const *mut c_char, + envp: *const *mut c_char, +) -> c_int { + if path.is_null() || argv.is_null() || envp.is_null() { + platform::ERRNO.set(EINVAL); + return -1; + } + + // Validate optional actions / attrs + let actions_valid = actions.is_null() + || unsafe { (*actions_as_inner(actions as *mut _)).magic == ACTIONS_MAGIC }; + let attrs_valid = attr.is_null() + || unsafe { (*attrs_as_inner(attr as *mut _)).magic == ATTRS_MAGIC }; + + if !actions_valid || !attrs_valid { + platform::ERRNO.set(EINVAL); + return -1; + } + + // Read spawnattr flags + let flags: c_short = if attr.is_null() { + 0 + } else { + unsafe { (*attrs_as_inner(attr as *mut _)).flags } + }; + + // Fork + let pid = unsafe { unistd::fork() }; + if pid == -1 { + return -1; + } + + if pid == 0 { + // Child process: apply attributes and file actions, then exec + + if flags & POSIX_SPAWN_RESETIDS != 0 { + unistd::seteuid(unistd::getuid()); + unistd::setegid(unistd::getgid()); + } + + if flags & POSIX_SPAWN_SETPGROUP != 0 { + let pgrp = if attr.is_null() { + 0 + } else { + unsafe { (*attrs_as_inner(attr as *mut _)).pgroup } + }; + unistd::setpgid(0, pgrp); + } + + if flags & POSIX_SPAWN_SETSIGMASK != 0 && !attr.is_null() { + let mask = unsafe { (*attrs_as_inner(attr as *mut _)).sigmask }; + unsafe { + signal::sigprocmask(signal::SIG_SETMASK, &mask, ptr::null_mut()); + } + } + + if flags & POSIX_SPAWN_SETSIGDEF != 0 && !attr.is_null() { + let default_set = unsafe { (*attrs_as_inner(attr as *mut _)).sigdefault }; + let mut i = 1; + while i < 32 { + if (default_set & (1u64 << i)) != 0 { + signal::signal(i, None); + } + i += 1; + } + } + + if (flags & POSIX_SPAWN_SETSCHEDULER != 0 || flags & POSIX_SPAWN_SETSCHEDPARAM != 0) + && !attr.is_null() + { + let inner = unsafe { &*attrs_as_inner(attr as *mut _) }; + let param = sched::sched_param { + sched_priority: inner.sched_priority, + }; + if flags & POSIX_SPAWN_SETSCHEDULER != 0 { + sched::sched_setscheduler(0, inner.schedpolicy, ¶m); + } + if flags & POSIX_SPAWN_SETSCHEDPARAM != 0 { + unsafe { sched::sched_setparam(0, ¶m) }; + } + } + + if !actions.is_null() { + let inner = actions_as_inner(actions as *mut _); + let mut node = unsafe { (*inner).head }; + while !node.is_null() { + let n = unsafe { &*node }; + match n.kind { + ACTION_KIND_CLOSE => { + unistd::close(n.fd); + } + ACTION_KIND_DUP2 => { + unistd::dup2(n.fd, n.newfd); + if n.fd != n.newfd { + unistd::close(n.fd); + } + } + ACTION_KIND_OPEN => unsafe { + let path_cstr = CStr::from_ptr(n.path_ptr); + if let Ok(new_fd) = Sys::open(path_cstr, n.oflag, n.mode) { + if new_fd != n.fd { + unistd::dup2(new_fd, n.fd); + unistd::close(new_fd); + } + } + }, + _ => {} + } + node = n.next; + } + } + + if search_path { + unsafe { unistd::execvp(path, argv) }; + } else { + unsafe { unistd::execve(path, argv, envp) }; + } + + unistd::_exit(127); + } + + // ── Parent process ──────────────────────────────────────────────────────── + pid +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawn( + pid: *mut pid_t, + path: *const c_char, + actions: *const posix_spawn_file_actions_t, + attr: *const posix_spawnattr_t, + argv: *const *mut c_char, + envp: *const *mut c_char, +) -> c_int { + let child = unsafe { do_spawn(path, false, actions, attr, argv, envp) }; + if child == -1 { + return platform::ERRNO.get(); + } + if !pid.is_null() { + unsafe { *pid = child }; + } + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_spawnp( + pid: *mut pid_t, + file: *const c_char, + actions: *const posix_spawn_file_actions_t, + attr: *const posix_spawnattr_t, + argv: *const *mut c_char, + envp: *const *mut c_char, +) -> c_int { + let child = unsafe { do_spawn(file, true, actions, attr, argv, envp) }; + if child == -1 { + return platform::ERRNO.get(); + } + if !pid.is_null() { + unsafe { *pid = child }; + } + 0 +} diff --git a/src/header/stddef/cbindgen.toml b/src/header/stddef/cbindgen.toml new file mode 100644 index 0000000000..ab10db8119 --- /dev/null +++ b/src/header/stddef/cbindgen.toml @@ -0,0 +1,29 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/stddef.h.html +# +# There are no spec quotations relating to includes +include_guard = "_RELIBC_STDDEF_H" +after_includes = """ +// wchar_t should come before NULL and size_t +// according to comment in wchar cbindgen +#include +#include +#include + +#ifndef __PTRDIFF_TYPE__ +#define __PTRDIFF_TYPE__ long int +#endif +// Signed integer type of the result of subtracting two pointers. +typedef __PTRDIFF_TYPE__ ptrdiff_t; + +// Object type whose alignment is the greatest fundamental alignment. +typedef struct { long long __ll; long double __ld; } max_align_t; + +// Integer constant expression of type size_t, the value of which is the offset +// in bytes to the structure member from the beginning of its structure. +#define offsetof(type, member) __builtin_offsetof(type, member) +""" +language = "C" +no_includes = true +cpp_compat = true +[enum] +prefix_with_name = true diff --git a/src/header/stddef/mod.rs b/src/header/stddef/mod.rs new file mode 100644 index 0000000000..50fd1de6a0 --- /dev/null +++ b/src/header/stddef/mod.rs @@ -0,0 +1,5 @@ +//! `stddef.h` implementation. +//! +//! See . +//! +//! Implemented via includes, typedefs and defines in cbindgen. diff --git a/src/header/stdio/cbindgen.toml b/src/header/stdio/cbindgen.toml index 633b449850..7a6860140b 100644 --- a/src/header/stdio/cbindgen.toml +++ b/src/header/stdio/cbindgen.toml @@ -1,6 +1,25 @@ -sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h"] +sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h", "features.h"] include_guard = "_RELIBC_STDIO_H" -trailer = "#include " +trailer = """ +#ifndef _RELIBC_BITS_STDIO_H +#define _RELIBC_BITS_STDIO_H + +// XXX: this is only here because cbindgen can't handle string constants +#define P_tmpdir "/tmp" + +// TODO: figure out why this duplicate is needed to compile libiconv +typedef struct FILE FILE; + +// A typedef doesn't suffice, because libgmp uses this definition to check if +// STDIO was loaded. +#define FILE FILE +// Likewise, stdin, stdout, and stderr are expected to be macros. +#define stdin stdin +#define stdout stdout +#define stderr stderr + +#endif // _RELIBC_BITS_STDIO_H +""" language = "C" style = "Type" no_includes = true diff --git a/src/header/stdio/constants.rs b/src/header/stdio/constants.rs index 0d4346d940..ff19da78e2 100644 --- a/src/header/stdio/constants.rs +++ b/src/header/stdio/constants.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::{c_int, c_uint, int32_t, off_t}; pub const EOF: c_int = -1; pub const BUFSIZ: c_int = 1024; @@ -24,13 +24,26 @@ pub const _IOFBF: c_int = 0; pub const _IOLBF: c_int = 1; pub const _IONBF: c_int = 2; +// renameat2 +// (uint instead of int is intentional) +/// Rename but don't replace the target if it exists. +pub const RENAME_NOREPLACE: c_uint = 0x01; +/// Atomically swap two files. +#[cfg(target_os = "linux")] +pub const RENAME_EXCHANGE: c_uint = 0x02; +#[cfg(target_os = "linux")] +pub const RENAME_WHITEOUT: c_uint = 0x04; + +// /dev/tty + nul +pub const L_ctermid: usize = 9; // form of name is /XXXXXX, so 7 pub const L_tmpnam: c_int = 7; // 36^6 (26 letters + 10 digits) is larger than i32::MAX, so just set to that // for now pub const TMP_MAX: int32_t = 2_147_483_647; -// XXX: defined manually in bits/stdio.h as well because cbindgen can't handle +// XXX: defined manually in cbindgen as well because it can't handle // string constants in any form AFAICT +/// cbindgen:ignore pub const P_tmpdir: &[u8; 5] = b"/tmp\0"; #[allow(non_camel_case_types)] diff --git a/src/header/stdio/default.rs b/src/header/stdio/default.rs index 90aa709b02..36ef4bf5d7 100644 --- a/src/header/stdio/default.rs +++ b/src/header/stdio/default.rs @@ -1,16 +1,22 @@ -use super::{constants, Buffer, BUFSIZ, FILE}; +use super::{BUFSIZ, Buffer, FILE, constants}; use core::{cell::UnsafeCell, ptr}; -use crate::{fs::File, io::LineWriter, platform::types::*, sync::Mutex}; -use alloc::vec::Vec; +use crate::{fs::File, header::pthread, io::LineWriter, platform::types::c_int, sync::Once}; +use alloc::{boxed::Box, vec::Vec}; +// TODO: Change FILE to allow const fn initialization? pub struct GlobalFile(UnsafeCell); + impl GlobalFile { fn new(file: c_int, flags: c_int) -> Self { let file = File::new(file); - let writer = LineWriter::new(unsafe { file.get_ref() }); + let writer = Box::new(LineWriter::new(unsafe { file.get_ref() })); + let mutex_attr = pthread::RlctMutexAttr { + ty: pthread::PTHREAD_MUTEX_RECURSIVE, + ..Default::default() + }; GlobalFile(UnsafeCell::new(FILE { - lock: Mutex::new(()), + lock: pthread::RlctMutex::new(&mutex_attr).unwrap(), file, flags: constants::F_PERM | flags, @@ -32,20 +38,24 @@ impl GlobalFile { // statics need to be Sync unsafe impl Sync for GlobalFile {} -lazy_static! { - #[allow(non_upper_case_globals)] - pub static ref default_stdin: GlobalFile = GlobalFile::new(0, constants::F_NOWR); +// TODO: Allow const fn initialization of FILE +static DEFAULT_STDIN: Once = Once::new(); +static DEFAULT_STDOUT: Once = Once::new(); +static DEFAULT_STDERR: Once = Once::new(); - #[allow(non_upper_case_globals)] - pub static ref default_stdout: GlobalFile = GlobalFile::new(1, constants::F_NORD); - - #[allow(non_upper_case_globals)] - pub static ref default_stderr: GlobalFile = GlobalFile::new(2, constants::F_NORD); +pub fn default_stdin() -> &'static GlobalFile { + DEFAULT_STDIN.call_once(|| GlobalFile::new(0, constants::F_NOWR)) +} +pub fn default_stdout() -> &'static GlobalFile { + DEFAULT_STDOUT.call_once(|| GlobalFile::new(1, constants::F_NORD)) +} +pub fn default_stderr() -> &'static GlobalFile { + DEFAULT_STDERR.call_once(|| GlobalFile::new(2, constants::F_NORD)) } -#[no_mangle] +#[unsafe(no_mangle)] pub static mut stdin: *mut FILE = ptr::null_mut(); -#[no_mangle] +#[unsafe(no_mangle)] pub static mut stdout: *mut FILE = ptr::null_mut(); -#[no_mangle] +#[unsafe(no_mangle)] pub static mut stderr: *mut FILE = ptr::null_mut(); diff --git a/src/header/stdio/ext.rs b/src/header/stdio/ext.rs index 28bb73445a..9dfb1a2597 100644 --- a/src/header/stdio/ext.rs +++ b/src/header/stdio/ext.rs @@ -1,41 +1,55 @@ use crate::{ - header::stdio::{FILE, F_NORD, F_NOWR}, - platform::types::*, + header::stdio::{F_ERR, F_NORD, F_NOWR, FILE}, + platform::types::{c_int, size_t}, }; -#[no_mangle] +#[unsafe(no_mangle)] +pub extern "C" fn __freadahead(stream: *mut FILE) -> size_t { + let stream = unsafe { &mut *stream }.lock(); + + (stream.read_size.saturating_sub(stream.read_pos) + stream.unget.len()) as size_t +} + +#[unsafe(no_mangle)] pub extern "C" fn __fpending(stream: *mut FILE) -> size_t { let stream = unsafe { &mut *stream }.lock(); - stream.writer.inner.buf.len() as size_t + stream.writer.pending() } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn __freadable(stream: *mut FILE) -> c_int { let stream = unsafe { &mut *stream }.lock(); - (stream.flags & F_NORD == 0) as c_int + c_int::from(stream.flags & F_NORD == 0) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn __fwritable(stream: *mut FILE) -> c_int { let stream = unsafe { &mut *stream }.lock(); - (stream.flags & F_NOWR == 0) as c_int + c_int::from(stream.flags & F_NOWR == 0) +} + +#[unsafe(no_mangle)] +pub extern "C" fn __fseterr(stream: *mut FILE) { + let mut stream = unsafe { &mut *stream }.lock(); + + stream.flags |= F_ERR; } //TODO: Check last operation when read-write -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn __freading(stream: *mut FILE) -> c_int { let stream = unsafe { &mut *stream }.lock(); - (stream.flags & F_NORD == 0) as c_int + c_int::from(stream.flags & F_NORD == 0) } //TODO: Check last operation when read-write -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn __fwriting(stream: *mut FILE) -> c_int { let stream = unsafe { &mut *stream }.lock(); - (stream.flags & F_NOWR == 0) as c_int + c_int::from(stream.flags & F_NOWR == 0) } diff --git a/src/header/stdio/getdelim.rs b/src/header/stdio/getdelim.rs index 59b2aa04ed..145aa25b31 100644 --- a/src/header/stdio/getdelim.rs +++ b/src/header/stdio/getdelim.rs @@ -1,31 +1,80 @@ +// See . + use alloc::vec::Vec; -use core::ptr; +use core::{intrinsics::unlikely, ptr}; use crate::{ - header::{stdio::FILE, stdlib}, + header::{ + errno::{EINVAL, ENOMEM, EOVERFLOW}, + stdio::FILE, + stdlib, + }, io::BufRead, - platform::types::*, + platform::types::{c_char, c_int, c_void, size_t, ssize_t}, }; -#[no_mangle] -pub unsafe extern "C" fn __getline( +use crate::{ + header::stdio::{F_EOF, F_ERR, feof, ferror}, + platform::ERRNO, +}; + +/// see getdelim (getline is a special case of getdelim with delim == '\n') +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getline( lineptr: *mut *mut c_char, n: *mut size_t, stream: *mut FILE, ) -> ssize_t { - __getdelim(lineptr, n, b'\n' as c_int, stream) + unsafe { getdelim(lineptr, n, c_int::from(b'\n'), stream) } } -#[no_mangle] -pub unsafe extern "C" fn __getdelim( +// One *could* read the standard as 'getdelim sets the stream error flag on *any* error, though +// since glibc doesn't seem to do this, I won't either + +/// +/// +/// # Safety +/// - `lineptr, *lineptr, `n`, `stream` pointers must be valid and have to be aligned. +/// - `stream` has to be a valid file handle returned by fopen and likes. +/// +/// # Deviation from POSIX +/// - **EINVAL is set on stream being NULL or delim not fitting into char** (POSIX allows UB) +/// - **`*n` can contain invalid data.** The buffer size `n` is not read, instead realloc is called each time. That is in principle +/// inefficent since the buffer is reallocated in memory for every call, but if `n` is by mistake +/// bigger than the number of bytes allocated for the buffer, there can be no out-of-bounds write. +/// - On non-stream-related errors, the error indicator of the stream is *not* set. Posix states +/// "If an error occurs, the error indicator for the stream shall be set, and the function shall +/// return -1 and set errno to indicate the error." but in cases that produce EINVAL even glibc +/// doesn't seem to set the error indicator, so we also don't. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getdelim( lineptr: *mut *mut c_char, n: *mut size_t, delim: c_int, stream: *mut FILE, ) -> ssize_t { - let lineptr = &mut *lineptr; - let n = &mut *n; - let delim = delim as u8; + let (lineptr, n, stream) = if let (Some(ptr), Some(n), Some(file)) = + (unsafe { lineptr.as_mut() }, unsafe { n.as_mut() }, unsafe { + stream.as_mut() + }) { + (ptr, n, file) + } else { + ERRNO.set(EINVAL); + return -1 as ssize_t; + }; + + if unsafe { feof(stream) } != 0 || unsafe { ferror(stream) } != 0 { + return -1 as ssize_t; + } + + // POSIX specifies UB but we test anyway + // returning EINVAL in that case + let delim: u8 = if let Ok(delim) = delim.try_into() { + delim + } else { + ERRNO.set(EINVAL); + return -1; + }; //TODO: More efficient algorithm using lineptr and n instead of this vec let mut buf = Vec::new(); @@ -33,23 +82,64 @@ pub unsafe extern "C" fn __getdelim( let mut stream = (*stream).lock(); match stream.read_until(delim, &mut buf) { Ok(ok) => ok, - Err(err) => return -1, + Err(err) => { + stream.flags &= F_ERR; + return -1; + } } }; + // "[EOVERFLOW] + // The number of bytes to be written into the buffer, including the delimiter character (if encountered), would exceed {SSIZE_MAX}." + if unlikely(count > ssize_t::MAX as usize) { + ERRNO.set(EOVERFLOW); + return -1; + } + + // we reached EOF if either + // - we have no last elem (because vec is empty), or + // - the last elem doesn't match the delimiter + let eof_reached = if let Some(last) = buf.last() { + *last == delim + } else { + true + }; + + // "If the end-of-file indicator for the stream is set, or if no characters were read and the + // stream is at end-of-file, the end-of-file indicator for the stream shall be set and the + // function shall return -1." + if eof_reached { + stream.flags &= F_EOF; + if count == 0 { + return -1; + } + } + //TODO: Check errors and improve safety { - // Allocate lineptr to size of buf and set n to size of lineptr + // Allocate lineptr to size of buf plus NUL byte and set n to size of lineptr *n = count + 1; - *lineptr = stdlib::realloc(*lineptr as *mut c_void, *n) as *mut c_char; + // The advantage in always realloc'ing is that even if the user supplies a wrong n, this + // doesn't break + *lineptr = unsafe { stdlib::realloc((*lineptr).cast::(), *n) }.cast::(); + if unlikely(lineptr.is_null() && *n != 0usize) { + // memory error; realloc returns NULL on alloc'ing 0 bytes + ERRNO.set(ENOMEM); + return -1; + } // Copy buf to lineptr - ptr::copy(buf.as_ptr(), *lineptr as *mut u8, count); + unsafe { ptr::copy(buf.as_ptr(), (*lineptr).cast::(), count) }; // NUL terminate lineptr - *lineptr.offset(count as isize) = 0; + unsafe { *lineptr.add(count) = 0 }; + // TODO remove + /*eprintln!( + "[DBG]{}: {}, {:?}, {:?}, {:?}", line!(), + String::from_utf8(buf).unwrap(), count, *n, *lineptr + );*/ // Return allocated size - *n as ssize_t + count as ssize_t } } diff --git a/src/header/stdio/helpers.rs b/src/header/stdio/helpers.rs index be7aa9fa0b..d070673b0a 100644 --- a/src/header/stdio/helpers.rs +++ b/src/header/stdio/helpers.rs @@ -1,36 +1,47 @@ use alloc::boxed::Box; -use super::{constants::*, Buffer, FILE}; +use super::{ + Buffer, FILE, + constants::{BUFSIZ, F_APP, F_NORD, F_NOWR}, +}; use crate::{ + c_str::CStr, + error::Errno, fs::File, - header::{errno, fcntl::*, string::strchr}, - io::LineWriter, - platform::{self, types::*}, - sync::Mutex, + header::{ + errno::EINVAL, + fcntl::{ + F_GETFL, F_SETFD, F_SETFL, FD_CLOEXEC, O_APPEND, O_CLOEXEC, O_CREAT, O_EXCL, O_RDONLY, + O_RDWR, O_TRUNC, O_WRONLY, fcntl, + }, + pthread, + }, + io::BufWriter, + platform::types::{c_int, c_ulonglong}, }; use alloc::vec::Vec; /// Parse mode flags as a string and output a mode flags integer -pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 { - let mut flags = if !strchr(mode_str, b'+' as i32).is_null() { +pub fn parse_mode_flags(mode_str: CStr) -> i32 { + let mut flags = if mode_str.contains(b'+') { O_RDWR - } else if (*mode_str) == b'r' as i8 { + } else if mode_str.first() == b'r' { O_RDONLY } else { O_WRONLY }; - if !strchr(mode_str, b'x' as i32).is_null() { + if mode_str.contains(b'x') { flags |= O_EXCL; } - if !strchr(mode_str, b'e' as i32).is_null() { + if mode_str.contains(b'e') { flags |= O_CLOEXEC; } - if (*mode_str) != b'r' as i8 { + if mode_str.first() != b'r' { flags |= O_CREAT; } - if (*mode_str) == b'w' as i8 { + if mode_str.first() == b'w' { flags |= O_TRUNC; - } else if (*mode_str) == b'a' as i8 { + } else if mode_str.first() == b'a' { flags |= O_APPEND; } @@ -38,34 +49,38 @@ pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 { } /// Open a file with the file descriptor `fd` in the mode `mode` -pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> { - if *mode != b'r' as i8 && *mode != b'w' as i8 && *mode != b'a' as i8 { - platform::errno = errno::EINVAL; - return None; +pub fn _fdopen(fd: c_int, mode: CStr) -> Result, Errno> { + if mode.first() != b'r' && mode.first() != b'w' && mode.first() != b'a' { + return Err(Errno(EINVAL)); } let mut flags = 0; - if strchr(mode, b'+' as i32).is_null() { - flags |= if *mode == b'r' as i8 { F_NOWR } else { F_NORD }; + if !mode.contains(b'+') { + flags |= if mode.first() == b'r' { F_NOWR } else { F_NORD }; } - if !strchr(mode, b'e' as i32).is_null() { - sys_fcntl(fd, F_SETFD, FD_CLOEXEC); + if mode.contains(b'e') { + unsafe { + fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong); + } } - if *mode == 'a' as i8 { - let f = sys_fcntl(fd, F_GETFL, 0); + if mode.first() == b'a' { + let f = unsafe { fcntl(fd, F_GETFL, 0) }; if (f & O_APPEND) == 0 { - sys_fcntl(fd, F_SETFL, f | O_APPEND); + unsafe { fcntl(fd, F_SETFL, (f | O_APPEND) as c_ulonglong) }; } flags |= F_APP; } let file = File::new(fd); - let writer = LineWriter::new(file.get_ref()); - - Some(Box::into_raw(Box::new(FILE { - lock: Mutex::new(()), + let writer = Box::new(BufWriter::new(unsafe { file.get_ref() })); + let mutex_attr = pthread::RlctMutexAttr { + ty: pthread::PTHREAD_MUTEX_RECURSIVE, + ..Default::default() + }; + Ok(Box::new(FILE { + lock: pthread::RlctMutex::new(&mutex_attr).unwrap(), file, flags, @@ -78,5 +93,5 @@ pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> { pid: None, orientation: 0, - }))) + })) } diff --git a/src/header/stdio/lookaheadreader.rs b/src/header/stdio/lookaheadreader.rs deleted file mode 100644 index 7b7495b0c7..0000000000 --- a/src/header/stdio/lookaheadreader.rs +++ /dev/null @@ -1,99 +0,0 @@ -use super::{fseek_locked, ftell_locked, FILE, SEEK_SET}; -use crate::core_io::Read; -struct LookAheadBuffer { - buf: *const u8, - pos: isize, - look_ahead: isize, -} -impl LookAheadBuffer { - fn look_ahead(&mut self) -> Result, i32> { - let byte = unsafe { *self.buf.offset(self.look_ahead) }; - if byte == 0 { - Ok(None) - } else { - self.look_ahead += 1; - Ok(Some(byte)) - } - } - - fn commit(&mut self) { - self.pos = self.look_ahead; - } -} - -impl From<*const u8> for LookAheadBuffer { - fn from(buff: *const u8) -> LookAheadBuffer { - LookAheadBuffer { - buf: buff, - pos: 0, - look_ahead: 0, - } - } -} - -struct LookAheadFile<'a> { - f: &'a mut FILE, - look_ahead: i64, -} - -impl<'a> LookAheadFile<'a> { - fn look_ahead(&mut self) -> Result, i32> { - let buf = &mut [0]; - let seek = unsafe { ftell_locked(self.f) }; - unsafe { fseek_locked(self.f, self.look_ahead, SEEK_SET) }; - let ret = match self.f.read(buf) { - Ok(0) => Ok(None), - Ok(_) => Ok(Some(buf[0])), - Err(_) => Err(-1), - }; - unsafe { fseek_locked(self.f, seek, SEEK_SET) }; - self.look_ahead += 1; - ret - } - - fn commit(&mut self) { - unsafe { fseek_locked(self.f, self.look_ahead, SEEK_SET) }; - } -} - -impl<'a> From<&'a mut FILE> for LookAheadFile<'a> { - fn from(f: &'a mut FILE) -> LookAheadFile<'a> { - let look_ahead = unsafe { ftell_locked(f) } as i64; - LookAheadFile { f, look_ahead } - } -} - -enum LookAheadReaderEnum<'a> { - FILE(LookAheadFile<'a>), - // (buffer, location) - BUFFER(LookAheadBuffer), -} - -pub struct LookAheadReader<'a>(LookAheadReaderEnum<'a>); - -impl<'a> LookAheadReader<'a> { - pub fn lookahead1(&mut self) -> Result, i32> { - match &mut self.0 { - LookAheadReaderEnum::FILE(f) => f.look_ahead(), - LookAheadReaderEnum::BUFFER(b) => b.look_ahead(), - } - } - pub fn commit(&mut self) { - match &mut self.0 { - LookAheadReaderEnum::FILE(f) => f.commit(), - LookAheadReaderEnum::BUFFER(b) => b.commit(), - } - } -} - -impl<'a> From<&'a mut FILE> for LookAheadReader<'a> { - fn from(f: &'a mut FILE) -> LookAheadReader { - LookAheadReader(LookAheadReaderEnum::FILE(f.into())) - } -} - -impl<'a> From<*const u8> for LookAheadReader<'a> { - fn from(buff: *const u8) -> LookAheadReader<'a> { - LookAheadReader(LookAheadReaderEnum::BUFFER(buff.into())) - } -} diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs index f6625766dd..2a234e85a5 100644 --- a/src/header/stdio/mod.rs +++ b/src/header/stdio/mod.rs @@ -1,4 +1,6 @@ -//! stdio implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/stdio.h.html +//! `stdio.h` implementation. +//! +//! See . use alloc::{ borrow::{Borrow, BorrowMut}, @@ -9,25 +11,32 @@ use core::{ cmp, ffi::VaList as va_list, fmt::{self, Write as WriteFmt}, - i32, mem, + mem, ops::{Deref, DerefMut}, ptr, slice, str, }; use crate::{ - c_str::CStr, + c_str::{CStr, Thin}, c_vec::CVec, + error::{ResultExt, ResultExtPtrMut}, fs::File, header::{ errno::{self, STR_ERROR}, - fcntl, stdlib, - string::{self, strlen}, + fcntl, + pthread::RlctMutex, + pwd, stdlib, + string::{self, strlen, strncpy}, unistd, }, - io::{self, BufRead, LineWriter, Read, Write}, - platform::{self, errno, types::*, Pal, Sys, WriteByte}, - sync::Mutex, + io::{self, BufRead, BufWriter, LineWriter, Read, Write}, + out::Out, + platform::{ + self, ERRNO, Pal, Sys, WriteByte, + types::{c_char, c_int, c_long, c_uint, c_ulonglong, c_void, off_t, size_t}, + }, }; +use reader::Reader; pub use self::constants::*; mod constants; @@ -38,18 +47,21 @@ mod default; pub use self::getdelim::*; mod getdelim; +pub use self::open_memstream::*; +mod open_memstream; + mod ext; mod helpers; -mod lookaheadreader; -mod printf; -mod scanf; -use lookaheadreader::LookAheadReader; +pub mod printf; +pub mod reader; +pub mod scanf; static mut TMPNAM_BUF: [c_char; L_tmpnam as usize + 1] = [0; L_tmpnam as usize + 1]; enum Buffer<'a> { Borrowed(&'a mut [u8]), Owned(Vec), } + impl<'a> Deref for Buffer<'a> { type Target = [u8]; @@ -60,6 +72,7 @@ impl<'a> Deref for Buffer<'a> { } } } + impl<'a> DerefMut for Buffer<'a> { fn deref_mut(&mut self) -> &mut Self::Target { match self { @@ -69,19 +82,56 @@ impl<'a> DerefMut for Buffer<'a> { } } +pub trait Pending { + fn pending(&self) -> size_t; +} + +impl Pending for BufWriter { + fn pending(&self) -> size_t { + self.buf.len() as size_t + } +} + +impl Pending for LineWriter { + fn pending(&self) -> size_t { + self.inner.buf.len() as size_t + } +} + +pub trait Writer: Write + Pending { + fn purge(&mut self); +} + +impl Writer for BufWriter { + fn purge(&mut self) { + self.buf.clear(); + } +} + +impl Writer for LineWriter { + fn purge(&mut self) { + self.inner.buf.clear(); + } +} + /// This struct gets exposed to the C API. pub struct FILE { - lock: Mutex<()>, + lock: RlctMutex, file: File, // pub for stdio_ext pub(crate) flags: c_int, + + // TODO: Is the read_buf dropped? read_buf: Buffer<'static>, + read_pos: usize, read_size: usize, unget: Vec, // pub for stdio_ext - pub(crate) writer: LineWriter, + + // TODO: To support const fn initialization, use static dispatch (perhaps partially)? + pub(crate) writer: Box, // Optional pid for use with popen/pclose pid: Option, @@ -93,8 +143,8 @@ pub struct FILE { impl Read for FILE { fn read(&mut self, out: &mut [u8]) -> io::Result { let unget_read_size = cmp::min(out.len(), self.unget.len()); - for i in 0..unget_read_size { - out[i] = self.unget.pop().unwrap(); + for inner in out.iter_mut().take(unget_read_size) { + *inner = self.unget.pop().unwrap(); } if unget_read_size != 0 { return Ok(unget_read_size); @@ -111,6 +161,7 @@ impl Read for FILE { Ok(len) } } + impl BufRead for FILE { fn fill_buf(&mut self) -> io::Result<&[u8]> { if self.read_pos == self.read_size { @@ -133,6 +184,7 @@ impl BufRead for FILE { self.read_pos = (self.read_pos + i).min(self.read_size); } } + impl Write for FILE { fn write(&mut self, buf: &[u8]) -> io::Result { match self.writer.write(buf) { @@ -153,6 +205,7 @@ impl Write for FILE { } } } + impl WriteFmt for FILE { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_all(s.as_bytes()) @@ -160,13 +213,15 @@ impl WriteFmt for FILE { .map_err(|_| fmt::Error) } } + impl WriteByte for FILE { fn write_u8(&mut self, c: u8) -> fmt::Result { self.write_all(&[c]).map(|_| ()).map_err(|_| fmt::Error) } } + impl FILE { - pub fn lock(&mut self) -> LockGuard { + pub fn lock(&mut self) -> LockGuard<'_> { unsafe { flockfile(self); } @@ -195,21 +250,41 @@ impl FILE { x => Err(x), } } + + pub fn try_set_wide_orientation_unlocked(&mut self) -> core::result::Result<(), c_int> { + match self.try_set_orientation_unlocked(1) { + 1..=i32::MAX => Ok(()), + x => Err(x), + } + } + + pub fn purge(&mut self) { + // Purge read buffer + self.read_pos = 0; + self.read_size = 0; + // Purge unget + self.unget.clear(); + // Purge write buffer + self.writer.purge(); + } } pub struct LockGuard<'a>(&'a mut FILE); + impl<'a> Deref for LockGuard<'a> { type Target = FILE; fn deref(&self) -> &Self::Target { - &self.0 + self.0 } } + impl<'a> DerefMut for LockGuard<'a> { fn deref_mut(&mut self) -> &mut Self::Target { self.0 } } + impl<'a> Drop for LockGuard<'a> { fn drop(&mut self) { unsafe { @@ -218,89 +293,137 @@ impl<'a> Drop for LockGuard<'a> { } } +/// See . +/// /// Clears EOF and ERR indicators on a stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn clearerr(stream: *mut FILE) { - let mut stream = (*stream).lock(); + let mut stream = unsafe { (*stream).lock() }; stream.flags &= !(F_EOF | F_ERR); } -// #[no_mangle] -pub extern "C" fn ctermid(_s: *mut c_char) -> *mut c_char { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ctermid(s: *mut c_char) -> *mut c_char { + static mut TERMID: [u8; L_ctermid] = *b"/dev/tty\0"; + + if s.is_null() { + return (&raw mut TERMID).cast::(); + } + + unsafe { strncpy(s, (&raw mut TERMID).cast::(), L_ctermid) } } -// #[no_mangle] -pub extern "C" fn cuserid(_s: *mut c_char) -> *mut c_char { - unimplemented!(); +/// See +/// +/// Marked legacy in SUS Version 2. +// #[unsafe(no_mangle)] +#[deprecated] +pub unsafe extern "C" fn cuserid(s: *mut c_char) -> *mut c_char { + let mut buf: Vec = vec![0; 256]; + let mut pwd: pwd::passwd = unsafe { mem::zeroed() }; + let mut pwdbuf: *mut pwd::passwd = unsafe { mem::zeroed() }; + if !s.is_null() { + unsafe { + *s.add(0) = 0; + } + } + unsafe { + pwd::getpwuid_r( + unistd::geteuid(), + &raw mut pwd, + buf.as_mut_ptr(), + buf.len(), + &raw mut pwdbuf, + ) + }; + if pwdbuf.is_null() { + return s; + } + + if !s.is_null() { + unsafe { strncpy(s, (*pwdbuf).pw_name, unistd::L_cuserid) }; + return s; + } + + unsafe { (*pwdbuf).pw_name } } +/// See . +/// /// Close a file /// This function does not guarentee that the file buffer will be flushed or that the file /// descriptor will be closed, so if it is important that the file be written to, use `fflush()` /// prior to using this function. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fclose(stream: *mut FILE) -> c_int { - let stream = &mut *stream; - flockfile(stream); + let stream = unsafe { &mut *stream }; + unsafe { flockfile(stream) }; let mut r = stream.flush().is_err(); - let close = Sys::close(*stream.file) < 0; + // TODO: better error handling + let close = Sys::close(*stream.file).map(|()| 0).or_minus_one_errno() == -1; r = r || close; if stream.flags & constants::F_PERM == 0 { // Not one of stdin, stdout or stderr - let mut stream = Box::from_raw(stream); + let mut stream = unsafe { Box::from_raw(stream) }; // Reference files aren't closed on drop, so pretend to be a reference stream.file.reference = true; } else { - funlockfile(stream); + unsafe { funlockfile(stream) }; } - r as c_int + c_int::from(r) } +/// See . +/// /// Open a file from a file descriptor -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fdopen(fildes: c_int, mode: *const c_char) -> *mut FILE { - if let Some(f) = helpers::_fdopen(fildes, mode) { - f - } else { - ptr::null_mut() - } + helpers::_fdopen(fildes, unsafe { CStr::from_ptr(mode) }) + .map(Box::into_raw) + .or_errno_null_mut() } +/// See . +/// /// Check for EOF -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn feof(stream: *mut FILE) -> c_int { - let stream = (*stream).lock(); + let stream = unsafe { (*stream).lock() }; stream.flags & F_EOF } +/// See . +/// /// Check for ERR -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ferror(stream: *mut FILE) -> c_int { - let stream = (*stream).lock(); + let stream = unsafe { (*stream).lock() }; stream.flags & F_ERR } +/// See . +/// /// Flush output to stream, or sync read position /// Ensure the file is unlocked before calling this function, as it will attempt to lock the file /// itself. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int { if stream.is_null() { //TODO: flush all files! - if fflush(stdout) != 0 { + if unsafe { fflush(stdout) } != 0 { return EOF; } - if fflush(stderr) != 0 { + if unsafe { fflush(stderr) } != 0 { return EOF; } } else { - let mut stream = (*stream).lock(); + let mut stream = unsafe { (*stream).lock() }; if stream.flush().is_err() { return EOF; } @@ -309,37 +432,43 @@ pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int { 0 } +/// See . +/// /// Get a single char from a stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fgetc(stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return -1; } - getc_unlocked(&mut *stream) + unsafe { getc_unlocked(&raw mut *stream) } } +/// See . +/// /// Get the position of the stream and store it in pos -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int { - let off = ftello(stream); + let off = unsafe { ftello(stream) }; if off < 0 { return -1; } - *pos = off; + unsafe { *pos = off }; 0 } +/// See . +/// /// Get a string from the stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fgets( original: *mut c_char, max: c_int, stream: *mut FILE, ) -> *mut c_char { - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return ptr::null_mut(); } @@ -351,8 +480,8 @@ pub unsafe extern "C" fn fgets( if left >= 1 { let unget_read_size = cmp::min(left, stream.unget.len()); for _ in 0..unget_read_size { - *out = stream.unget.pop().unwrap() as i8; - out = out.offset(1); + unsafe { *out = stream.unget.pop().unwrap() as c_char }; + out = unsafe { out.offset(1) }; } left -= unget_read_size; } @@ -377,14 +506,14 @@ pub unsafe extern "C" fn fgets( let newline = buf[..len].iter().position(|&c| c == b'\n'); let len = newline.map(|i| i + 1).unwrap_or(len); - ptr::copy_nonoverlapping(buf.as_ptr(), out as *mut u8, len); + unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), out.cast::(), len) }; (len, newline.is_some()) }; stream.consume(read); - out = out.add(read); + out = unsafe { out.add(read) }; left -= read; if exit { @@ -394,40 +523,47 @@ pub unsafe extern "C" fn fgets( if max >= 1 { // Write the NUL byte - *out = 0; - } - if wrote { - original - } else { - ptr::null_mut() + unsafe { *out = 0 }; } + if wrote { original } else { ptr::null_mut() } } +/// See . +/// /// Get the underlying file descriptor -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fileno(stream: *mut FILE) -> c_int { - let stream = (*stream).lock(); + let stream = unsafe { (*stream).lock() }; *stream.file } +/// See . +/// /// Lock the file /// Do not call any functions other than those with the `_unlocked` postfix while the file is /// locked -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn flockfile(file: *mut FILE) { - (*file).lock.manual_lock(); + if let Err(e) = unsafe { (*file).lock.lock() } { + todo_error!(0, e, "flockfile error") + } } +/// See . +/// /// Open the file in mode `mode` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE { - let initial_mode = *mode; - if initial_mode != b'r' as i8 && initial_mode != b'w' as i8 && initial_mode != b'a' as i8 { - platform::errno = errno::EINVAL; + let initial_mode = unsafe { *mode }; + if initial_mode != b'r' as c_char + && initial_mode != b'w' as c_char + && initial_mode != b'a' as c_char + { + platform::ERRNO.set(errno::EINVAL); return ptr::null_mut(); } - let flags = helpers::parse_mode_flags(mode); + let flags = helpers::parse_mode_flags(unsafe { CStr::from_ptr(mode) }); let new_mode = if flags & fcntl::O_CREAT == fcntl::O_CREAT { 0o666 @@ -435,53 +571,71 @@ pub unsafe extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> 0 }; - let fd = fcntl::sys_open(filename, flags, new_mode); + let fd = unsafe { fcntl::open(filename, flags, new_mode) }; if fd < 0 { return ptr::null_mut(); } if flags & fcntl::O_CLOEXEC > 0 { - fcntl::sys_fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC); + unsafe { fcntl::fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as c_ulonglong) }; } - if let Some(f) = helpers::_fdopen(fd, mode) { - f - } else { - Sys::close(fd); - ptr::null_mut() + helpers::_fdopen(fd, unsafe { CStr::from_ptr(mode) }) + .map(Box::into_raw) + .inspect_err(|err| { + // TODO: guard type + if let Ok(()) = Sys::close(fd) {}; // TODO handle error + }) + .or_errno_null_mut() +} + +/// See . +/// +/// Non-POSIX. From Solaris. +/// +/// Clear the buffers of a stream +/// Ensure the file is unlocked before calling this function, as it will attempt to lock the file +/// itself. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __fpurge(stream: *mut FILE) { + if !stream.is_null() { + let mut stream = unsafe { (*stream).lock() }; + stream.purge(); } } +/// See . +/// /// Insert a character into the stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fputc(c: c_int, stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return -1; } - putc_unlocked(c, &mut *stream) + unsafe { putc_unlocked(c, &raw mut *stream) } } +/// See . +/// /// Insert a string into a stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fputs(s: *const c_char, stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return -1; } - let buf = slice::from_raw_parts(s as *mut u8, strlen(s)); + let buf = unsafe { slice::from_raw_parts(s as *mut u8, strlen(s)) }; - if stream.write_all(&buf).is_ok() { - 0 - } else { - -1 - } + if stream.write_all(buf).is_ok() { 0 } else { -1 } } +/// See . +/// /// Read `nitems` of size `size` into `ptr` from `stream` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fread( ptr: *mut c_void, size: size_t, @@ -492,12 +646,12 @@ pub unsafe extern "C" fn fread( return 0; } - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return 0; } - let buf = slice::from_raw_parts_mut(ptr as *mut u8, size as usize * nitems as usize); + let buf = unsafe { slice::from_raw_parts_mut(ptr.cast::(), size * nitems) }; let mut read = 0; while read < buf.len() { match stream.read(&mut buf[read..]) { @@ -505,67 +659,84 @@ pub unsafe extern "C" fn fread( Ok(n) => read += n, } } - (read / size as usize) as size_t + (read / size) as size_t } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn freopen( filename: *const c_char, mode: *const c_char, stream: &mut FILE, ) -> *mut FILE { - let mut flags = helpers::parse_mode_flags(mode); - flockfile(stream); + let mut flags = helpers::parse_mode_flags(unsafe { CStr::from_ptr(mode) }); + unsafe { flockfile(stream) }; let _ = stream.flush(); if filename.is_null() { // Reopen stream in new mode if flags & fcntl::O_CLOEXEC > 0 { - fcntl::sys_fcntl(*stream.file, fcntl::F_SETFD, fcntl::FD_CLOEXEC); + unsafe { + fcntl::fcntl( + *stream.file, + fcntl::F_SETFD, + fcntl::FD_CLOEXEC as c_ulonglong, + ) + }; } flags &= !(fcntl::O_CREAT | fcntl::O_EXCL | fcntl::O_CLOEXEC); - if fcntl::sys_fcntl(*stream.file, fcntl::F_SETFL, flags) < 0 { - funlockfile(stream); - fclose(stream); + if unsafe { fcntl::fcntl(*stream.file, fcntl::F_SETFL, flags as c_ulonglong) } < 0 { + unsafe { funlockfile(stream) }; + unsafe { fclose(stream) }; return ptr::null_mut(); } } else { - let new = fopen(filename, mode); + let new = unsafe { fopen(filename, mode) }; if new.is_null() { - funlockfile(stream); - fclose(stream); + unsafe { funlockfile(stream) }; + unsafe { fclose(stream) }; return ptr::null_mut(); } - let new = &mut *new; // Should be safe, new is not null + let new = unsafe { &mut *new }; // Should be safe, new is not null if *new.file == *stream.file { new.file.fd = -1; - } else if Sys::dup2(*new.file, *stream.file) < 0 - || fcntl::sys_fcntl(*stream.file, fcntl::F_SETFL, flags & fcntl::O_CLOEXEC) < 0 + } else if Sys::dup2(*new.file, *stream.file).or_minus_one_errno() == -1 + || unsafe { + fcntl::fcntl( + *stream.file, + fcntl::F_SETFL, + (flags & fcntl::O_CLOEXEC) as c_ulonglong, + ) + } < 0 { - funlockfile(stream); - fclose(new); - fclose(stream); + unsafe { funlockfile(stream) }; + unsafe { fclose(new) }; + unsafe { fclose(stream) }; return ptr::null_mut(); } stream.flags = (stream.flags & constants::F_PERM) | new.flags; - fclose(new); + unsafe { fclose(new) }; } stream.orientation = 0; - funlockfile(stream); + unsafe { funlockfile(stream) }; stream } +/// See . +/// /// Seek to an offset `offset` from `whence` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fseek(stream: *mut FILE, offset: c_long, whence: c_int) -> c_int { - fseeko(stream, offset as off_t, whence) + unsafe { fseeko(stream, offset as off_t, whence) } } +/// See . +/// /// Seek to an offset `offset` from `whence` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fseeko(stream: *mut FILE, off: off_t, whence: c_int) -> c_int { - let mut stream = (*stream).lock(); - fseek_locked(&mut *stream, off, whence) + let mut stream = unsafe { (*stream).lock() }; + unsafe { fseek_locked(&mut stream, off, whence) } } pub unsafe fn fseek_locked(stream: &mut FILE, mut off: off_t, whence: c_int) -> c_int { @@ -580,7 +751,7 @@ pub unsafe fn fseek_locked(stream: &mut FILE, mut off: off_t, whence: c_int) -> return -1; } - let err = Sys::lseek(*stream.file, off, whence); + let err = Sys::lseek(*stream.file, off, whence).or_minus_one_errno(); if err < 0 { return err as c_int; } @@ -592,51 +763,68 @@ pub unsafe fn fseek_locked(stream: &mut FILE, mut off: off_t, whence: c_int) -> 0 } +/// See . +/// /// Seek to a position `pos` in the file from the beginning of the file -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fsetpos(stream: *mut FILE, pos: *const fpos_t) -> c_int { - fseek(stream, *pos, SEEK_SET) + unsafe { fseeko(stream, *pos, SEEK_SET) } } +/// See . +/// /// Get the current position of the cursor in the file -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ftell(stream: *mut FILE) -> c_long { - ftello(stream) as c_long + unsafe { ftello(stream) as c_long } } +/// See . +/// /// Get the current position of the cursor in the file -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ftello(stream: *mut FILE) -> off_t { - let mut stream = (*stream).lock(); - ftell_locked(&mut *stream) + let mut stream = unsafe { (*stream).lock() }; + unsafe { ftell_locked(&mut stream) } } + pub unsafe extern "C" fn ftell_locked(stream: &mut FILE) -> off_t { - let pos = Sys::lseek(*stream.file, 0, SEEK_CUR); + let pos = Sys::lseek(*stream.file, 0, SEEK_CUR).or_minus_one_errno(); if pos < 0 { return -1; } + // Adjust for read buffer, ungetc, and write buffer pos - (stream.read_size - stream.read_pos) as off_t - stream.unget.len() as off_t + + stream.writer.pending() as off_t } +/// See . +/// /// Try to lock the file. Returns 0 for success, 1 for failure -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ftrylockfile(file: *mut FILE) -> c_int { - if (*file).lock.manual_try_lock().is_ok() { + if unsafe { (*file).lock.try_lock() }.is_ok() { 0 } else { 1 } } +/// See . +/// /// Unlock the file -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn funlockfile(file: *mut FILE) { - (*file).lock.manual_unlock(); + if let Err(e) = unsafe { (*file).lock.unlock() } { + todo_error!(0, e, "RELIBC: funlockfile error") + } } +/// See . +/// /// Write `nitems` of size `size` from `ptr` to `stream` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwrite( ptr: *const c_void, size: size_t, @@ -646,12 +834,12 @@ pub unsafe extern "C" fn fwrite( if size == 0 || nitems == 0 { return 0; } - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return 0; } - let buf = slice::from_raw_parts(ptr as *const u8, size as usize * nitems as usize); + let buf = unsafe { slice::from_raw_parts(ptr.cast::(), size * nitems) }; let mut written = 0; while written < buf.len() { match stream.write(&buf[written..]) { @@ -659,59 +847,80 @@ pub unsafe extern "C" fn fwrite( Ok(n) => written += n, } } - (written / size as usize) as size_t + (written / size) as size_t } +/// See . +/// /// Get a single char from a stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn getc(stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - getc_unlocked(&mut *stream) + let mut stream = unsafe { (*stream).lock() }; + unsafe { getc_unlocked(&raw mut *stream) } } +/// See . +/// /// Get a single char from `stdin` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn getchar() -> c_int { - fgetc(&mut *stdin) + unsafe { fgetc(&raw mut *stdin) } } +/// See . +/// /// Get a char from a stream without locking the stream -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn getc_unlocked(stream: *mut FILE) -> c_int { - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + if unsafe { (*stream).try_set_byte_orientation_unlocked() }.is_err() { return -1; } let mut buf = [0]; - match (*stream).read(&mut buf) { + match unsafe { (*stream).read(&mut buf) } { Ok(0) | Err(_) => EOF, - Ok(_) => buf[0] as c_int, + Ok(_) => c_int::from(buf[0]), } } +/// See . +/// /// Get a char from `stdin` without locking `stdin` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn getchar_unlocked() -> c_int { - getc_unlocked(&mut *stdin) + unsafe { getc_unlocked(&raw mut *stdin) } } +/// See . +/// +/// Marked obsolescent in issue 7. +/// `fgets` is recommended instead, which is what this implementation calls. +/// /// Get a string from `stdin` -#[no_mangle] +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn gets(s: *mut c_char) -> *mut c_char { - fgets(s, c_int::max_value(), &mut *stdin) + unsafe { fgets(s, c_int::MAX, &raw mut *stdin) } } +/// See . +/// +/// Was marked legacy and removed in issue 6. +/// /// Get an integer from `stream` -#[no_mangle] +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn getw(stream: *mut FILE) -> c_int { let mut ret: c_int = 0; - if fread( - &mut ret as *mut _ as *mut c_void, - mem::size_of_val(&ret), - 1, - stream, - ) > 0 + if unsafe { + fread( + ptr::from_mut(&mut ret).cast::(), + mem::size_of_val(&ret), + 1, + stream, + ) + } > 0 { ret } else { @@ -719,49 +928,59 @@ pub unsafe extern "C" fn getw(stream: *mut FILE) -> c_int { } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn pclose(stream: *mut FILE) -> c_int { + // TODO: rusty error handling? let pid = { - let mut stream = (*stream).lock(); + let mut stream = unsafe { (*stream).lock() }; if let Some(pid) = stream.pid.take() { pid } else { - errno = errno::ECHILD; + ERRNO.set(errno::ECHILD); return -1; } }; - fclose(stream); + unsafe { fclose(stream) }; let mut wstatus = 0; - if Sys::waitpid(pid, &mut wstatus, 0) < 0 { + if Sys::waitpid(pid, Some(Out::from_mut(&mut wstatus)), 0).or_minus_one_errno() == -1 { return -1; } wstatus } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn perror(s: *const c_char) { - let s_cstr = CStr::from_ptr(s); - let s_str = str::from_utf8_unchecked(s_cstr.to_bytes()); - - let mut w = platform::FileWriter(2); - if errno >= 0 && errno < STR_ERROR.len() as c_int { - w.write_fmt(format_args!("{}: {}\n", s_str, STR_ERROR[errno as usize])) - .unwrap(); + let err = ERRNO.get(); + let err_str = if err >= 0 && err < STR_ERROR.len() as c_int { + STR_ERROR[err as usize] } else { - w.write_fmt(format_args!("{}: Unknown error {}\n", s_str, errno)) - .unwrap(); + "Unknown error" + }; + let mut w = platform::FileWriter::new(2); + + // The prefix, `s`, is optional (empty or NULL) according to the spec + match unsafe { CStr::from_nullable_ptr(s) } + .and_then(|s_cstr| str::from_utf8(s_cstr.to_bytes()).ok()) + { + Some(s_str) if !s_str.is_empty() => w + .write_fmt(format_args!("{}: {}\n", s_str, err_str)) + .unwrap(), + _ => w.write_fmt(format_args!("{}\n", err_str)).unwrap(), } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn popen(command: *const c_char, mode: *const c_char) -> *mut FILE { //TODO: share code with system - let mode = CStr::from_ptr(mode); + let mode = unsafe { CStr::from_ptr(mode) }; let mut cloexec = false; let mut write_opt = None; @@ -771,7 +990,7 @@ pub unsafe extern "C" fn popen(command: *const c_char, mode: *const c_char) -> * b'r' if write_opt.is_none() => write_opt = Some(false), b'w' if write_opt.is_none() => write_opt = Some(true), _ => { - errno = errno::EINVAL; + ERRNO.set(errno::EINVAL); return ptr::null_mut(); } } @@ -780,174 +999,252 @@ pub unsafe extern "C" fn popen(command: *const c_char, mode: *const c_char) -> * let write = match write_opt { Some(some) => some, None => { - errno = errno::EINVAL; + ERRNO.set(errno::EINVAL); return ptr::null_mut(); } }; let mut pipes = [-1, -1]; - if unistd::pipe(pipes.as_mut_ptr()) != 0 { + if unsafe { unistd::pipe(pipes.as_mut_ptr()) } != 0 { return ptr::null_mut(); } - let child_pid = unistd::fork(); + let child_pid = unsafe { unistd::fork() }; if child_pid == 0 { let command_nonnull = if command.is_null() { - "exit 0\0".as_ptr() + c"exit 0".as_ptr() } else { - command as *const u8 + command.cast::() }; - let shell = "/bin/sh\0".as_ptr(); + let shell = c"/bin/sh".as_ptr(); - let args = [ - "sh\0".as_ptr(), - "-c\0".as_ptr(), - command_nonnull, - ptr::null(), - ]; + let args = [c"sh".as_ptr(), c"-c".as_ptr(), command_nonnull, ptr::null()]; // Setup up stdin or stdout //TODO: dup errors are ignored, should they be? { if write { - unistd::dup2(0, pipes[0]); + match unistd::dup2(pipes[0], 0) { + 0 => {} + e => unsafe { stdlib::exit(127) }, + } } else { - unistd::dup2(1, pipes[1]); + match unistd::dup2(pipes[1], 1) { + 1 => {} + e => unsafe { stdlib::exit(127) }, + } } unistd::close(pipes[0]); unistd::close(pipes[1]); } - unistd::execv(shell as *const c_char, args.as_ptr() as *const *mut c_char); + unsafe { unistd::execv(shell.cast::(), args.as_ptr().cast::<*mut c_char>()) }; - stdlib::exit(127); + unsafe { stdlib::exit(127) }; unreachable!(); } else if child_pid > 0 { - let (fd, fd_mode) = if write { + let (fd, fd_mode): (_, CStr) = if write { unistd::close(pipes[0]); - (pipes[1], if cloexec { c_str!("we") } else { c_str!("w") }) + (pipes[1], if cloexec { c"we".into() } else { c"w".into() }) } else { unistd::close(pipes[1]); - (pipes[0], if cloexec { c_str!("re") } else { c_str!("r") }) + (pipes[0], if cloexec { c"re".into() } else { c"r".into() }) }; - if let Some(f) = helpers::_fdopen(fd, fd_mode.as_ptr()) { - (*f).pid = Some(child_pid); - f - } else { - ptr::null_mut() - } + helpers::_fdopen(fd, fd_mode) + .map(|mut f| { + f.pid = Some(child_pid); + Box::into_raw(f) + }) + .or_errno_null_mut() } else { ptr::null_mut() } } +/// See . +/// /// Put a character `c` into `stream` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn putc(c: c_int, stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - putc_unlocked(c, &mut *stream) + let mut stream = unsafe { (*stream).lock() }; + unsafe { putc_unlocked(c, &raw mut *stream) } } +/// See . +/// /// Put a character `c` into `stdout` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn putchar(c: c_int) -> c_int { - fputc(c, &mut *stdout) + unsafe { fputc(c, &raw mut *stdout) } } +/// See . +/// /// Put a character `c` into `stream` without locking `stream` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn putc_unlocked(c: c_int, stream: *mut FILE) -> c_int { - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + if unsafe { (*stream).try_set_byte_orientation_unlocked() }.is_err() { return -1; } - match (*stream).write(&[c as u8]) { + match unsafe { (*stream).write(&[c as u8]) } { Ok(0) | Err(_) => EOF, Ok(_) => c, } } +/// See . +/// /// Put a character `c` into `stdout` without locking `stdout` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn putchar_unlocked(c: c_int) -> c_int { - putc_unlocked(c, stdout) + unsafe { putc_unlocked(c, stdout) } } +/// See . +/// /// Put a string `s` into `stdout` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn puts(s: *const c_char) -> c_int { - let mut stream = (&mut *stdout).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (&mut *stdout).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return -1; } - let buf = slice::from_raw_parts(s as *mut u8, strlen(s)); + let buf = unsafe { slice::from_raw_parts(s as *mut u8, strlen(s)) }; - if stream.write_all(&buf).is_err() { + if stream.write_all(buf).is_err() { return -1; } - if stream.write(&[b'\n']).is_err() { + if stream.write(b"\n").is_err() { return -1; } 0 } +/// See . +/// +/// Marked legacy in SUS Version 2. +/// /// Put an integer `w` into `stream` -#[no_mangle] +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn putw(w: c_int, stream: *mut FILE) -> c_int { - fwrite(&w as *const c_int as _, mem::size_of_val(&w), 1, stream) as i32 - 1 + (unsafe { + fwrite( + ptr::from_ref::(&w).cast(), + mem::size_of_val(&w), + 1, + stream, + ) + }) as i32 + - 1 } +/// See . +/// /// Delete file or directory `path` -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn remove(path: *const c_char) -> c_int { - let path = CStr::from_ptr(path); - let r = Sys::unlink(path); - if r == -errno::EISDIR { - Sys::rmdir(path) - } else { - r - } + let path = unsafe { CStr::from_ptr(path) }; + Sys::unlink(path) + .or_else(|_err| Sys::rmdir(path)) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn rename(oldpath: *const c_char, newpath: *const c_char) -> c_int { - let oldpath = CStr::from_ptr(oldpath); - let newpath = CStr::from_ptr(newpath); + let oldpath = unsafe { CStr::from_ptr(oldpath) }; + let newpath = unsafe { CStr::from_ptr(newpath) }; Sys::rename(oldpath, newpath) + .map(|()| 0) + .or_minus_one_errno() } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn renameat( + old_dir: c_int, + old_path: *const c_char, + new_dir: c_int, + new_path: *const c_char, +) -> c_int { + let old_path = unsafe { CStr::from_ptr(old_path) }; + let new_path = unsafe { CStr::from_ptr(new_path) }; + Sys::renameat(old_dir, old_path, new_dir, new_path) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +/// +/// Non-POSIX. Seems to be a GNU extension. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn renameat2( + old_dir: c_int, + old_path: *const c_char, + new_dir: c_int, + new_path: *const c_char, + flags: c_uint, +) -> c_int { + let old_path = unsafe { CStr::from_ptr(old_path) }; + let new_path = unsafe { CStr::from_ptr(new_path) }; + Sys::renameat2(old_dir, old_path, new_dir, new_path, flags) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +/// /// Rewind `stream` back to the beginning of it -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn rewind(stream: *mut FILE) { - fseeko(stream, 0, SEEK_SET); + unsafe { fseeko(stream, 0, SEEK_SET) }; } +/// See . +/// /// Reset `stream` to use buffer `buf`. Buffer must be `BUFSIZ` in length -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn setbuf(stream: *mut FILE, buf: *mut c_char) { - setvbuf( - stream, - buf, - if buf.is_null() { _IONBF } else { _IOFBF }, - BUFSIZ as usize, - ); + unsafe { + setvbuf( + stream, + buf, + if buf.is_null() { _IONBF } else { _IOFBF }, + BUFSIZ as usize, + ) + }; } +/// See . +/// +/// Non-POSIX. +/// +/// Set buffering of `stream` to line buffered +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setlinebuf(stream: *mut FILE) { + unsafe { setvbuf(stream, ptr::null_mut(), _IOLBF, 0) }; +} + +/// See . +/// /// Reset `stream` to use buffer `buf` of size `size` /// If this isn't the meaning of unsafe, idk what is -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn setvbuf( stream: *mut FILE, buf: *mut c_char, mode: c_int, mut size: size_t, ) -> c_int { - let mut stream = (*stream).lock(); + let mut stream = unsafe { (*stream).lock() }; // Set a buffer of size `size` if no buffer is given stream.read_buf = if buf.is_null() || size == 0 { if size == 0 { @@ -956,51 +1253,63 @@ pub unsafe extern "C" fn setvbuf( // TODO: Make it unbuffered if _IONBF // if mode == _IONBF { // } else { - Buffer::Owned(vec![0; size as usize]) + Buffer::Owned(vec![0; size]) // } } else { - Buffer::Borrowed(slice::from_raw_parts_mut(buf as *mut u8, size)) + Buffer::Borrowed(unsafe { slice::from_raw_parts_mut(buf.cast::(), size) }) }; stream.flags |= F_SVB; 0 } -#[no_mangle] +/// See . +/// +/// Marked obsolescent in issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tempnam(dir: *const c_char, pfx: *const c_char) -> *mut c_char { unsafe fn is_appropriate(pos_dir: *const c_char) -> bool { - !pos_dir.is_null() && unistd::access(pos_dir, unistd::W_OK) == 0 + !pos_dir.is_null() && unsafe { unistd::access(pos_dir, unistd::W_OK) } == 0 } // directory search order is env!(TMPDIR), dir, P_tmpdir, "/tmp" let dirname = { - let tmpdir = stdlib::getenv(b"TMPDIR\0".as_ptr() as _); - [tmpdir, dir, P_tmpdir.as_ptr() as _] + let tmpdir = unsafe { stdlib::getenv(c"TMPDIR".as_ptr().cast()) }; + [tmpdir, dir, P_tmpdir.as_ptr().cast()] .iter() .copied() - .skip_while(|&d| !is_appropriate(d)) + .skip_while(|&d| !unsafe { is_appropriate(d) }) .next() - .unwrap_or(b"/tmp\0".as_ptr() as _) + .unwrap_or(c"/tmp".as_ptr().cast()) }; - let dirname_len = string::strlen(dirname); + let dirname_len = unsafe { string::strlen(dirname) }; - let prefix_len = string::strnlen_s(pfx, 5); + let prefix_len = unsafe { string::strnlen_s(pfx, 5) }; // allocate enough for dirname "/" prefix "XXXXXX\0" let mut out_buf = - platform::alloc(dirname_len + 1 + prefix_len + L_tmpnam as usize + 1) as *mut c_char; + unsafe { platform::alloc(dirname_len + 1 + prefix_len + L_tmpnam as usize + 1) } + .cast::(); if !out_buf.is_null() { // copy the directory name and prefix into the allocated buffer - out_buf.copy_from_nonoverlapping(dirname, dirname_len); - *out_buf.add(dirname_len) = b'/' as _; - out_buf - .add(dirname_len + 1) - .copy_from_nonoverlapping(pfx, prefix_len); + unsafe { out_buf.copy_from_nonoverlapping(dirname, dirname_len) }; + unsafe { *out_buf.add(dirname_len) = b'/' as _ }; + unsafe { + out_buf + .add(dirname_len + 1) + .copy_from_nonoverlapping(pfx, prefix_len) + }; // use the same mechanism as tmpnam to get the file name - if tmpnam_inner(out_buf, dirname_len + 1 + prefix_len).is_null() { + if unsafe { + #[allow(deprecated)] + tmpnam_inner(out_buf, dirname_len + 1 + prefix_len) + } + .is_null() + { // failed to find a valid file name, so we need to free the buffer - platform::free(out_buf as _); + unsafe { platform::free(out_buf.cast()) }; out_buf = ptr::null_mut(); } } @@ -1008,63 +1317,79 @@ pub unsafe extern "C" fn tempnam(dir: *const c_char, pfx: *const c_char) -> *mut out_buf } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tmpfile() -> *mut FILE { let mut file_name = *b"/tmp/tmpfileXXXXXX\0"; - let file_name = file_name.as_mut_ptr() as *mut c_char; - let fd = stdlib::mkstemp(file_name); + let file_name = file_name.as_mut_ptr().cast::(); + let fd = unsafe { stdlib::mkstemp(file_name) }; if fd < 0 { return ptr::null_mut(); } - let fp = fdopen(fd, c_str!("w+").as_ptr()); + let fp = unsafe { fdopen(fd, c"w+".as_ptr()) }; { - let file_name = CStr::from_ptr(file_name); - Sys::unlink(file_name); + let file_name = unsafe { CStr::from_ptr(file_name) }; + if let Ok(()) = Sys::unlink(file_name) {}; // TODO handle error } - if fp.is_null() { - Sys::close(fd); - } + if fp.is_null() + && let Ok(()) = Sys::close(fd) + {}; // TODO handle error fp } -#[no_mangle] +/// See . +/// +/// Marked obsolescent in issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tmpnam(s: *mut c_char) -> *mut c_char { let buf = if s.is_null() { - TMPNAM_BUF.as_mut_ptr() + (&raw mut TMPNAM_BUF).cast() } else { s }; - *buf = b'/' as _; - tmpnam_inner(buf, 1) + unsafe { *buf = b'/' as _ }; + unsafe { + #[allow(deprecated)] + tmpnam_inner(buf, 1) + } } +#[deprecated] unsafe extern "C" fn tmpnam_inner(buf: *mut c_char, offset: usize) -> *mut c_char { const TEMPLATE: &[u8] = b"XXXXXX\0"; - buf.add(offset) - .copy_from_nonoverlapping(TEMPLATE.as_ptr() as _, TEMPLATE.len()); + unsafe { + buf.add(offset) + .copy_from_nonoverlapping(TEMPLATE.as_ptr().cast(), TEMPLATE.len()) + }; - let err = platform::errno; - stdlib::mktemp(buf); - platform::errno = err; + let err = platform::ERRNO.get(); + unsafe { + #[allow(deprecated)] + stdlib::mktemp(buf) + }; + platform::ERRNO.set(err); - if *buf == 0 { + if unsafe { *buf } == 0 { ptr::null_mut() } else { buf } } +/// See . +/// /// Push character `c` back onto `stream` so it'll be read next -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int { - let mut stream = (*stream).lock(); - if let Err(_) = (*stream).try_set_byte_orientation_unlocked() { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_byte_orientation_unlocked().is_err() { return -1; } @@ -1072,76 +1397,211 @@ pub unsafe extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int { c } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vfprintf(file: *mut FILE, format: *const c_char, ap: va_list) -> c_int { - let mut file = (*file).lock(); - if let Err(_) = file.try_set_byte_orientation_unlocked() { + let mut file = unsafe { (*file).lock() }; + if file.try_set_byte_orientation_unlocked().is_err() { return -1; } - printf::printf(&mut *file, format, ap) + unsafe { printf::printf(&mut *file, CStr::from_ptr(format), ap) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fprintf( + file: *mut FILE, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { vfprintf(file, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vdprintf(fd: c_int, format: *const c_char, ap: va_list) -> c_int { + let mut f = File::new(fd); + + // We don't want to close the file on drop; we're merely + // borrowing the file descriptor here + f.reference = true; + + unsafe { printf::printf(f, CStr::from_ptr(format), ap) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dprintf(fd: c_int, format: *const c_char, mut __valist: ...) -> c_int { + unsafe { vdprintf(fd, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vprintf(format: *const c_char, ap: va_list) -> c_int { - vfprintf(&mut *stdout, format, ap) + unsafe { vfprintf(&raw mut *stdout, format, ap) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn printf(format: *const c_char, mut __valist: ...) -> c_int { + unsafe { vfprintf(&raw mut *stdout, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vasprintf( strp: *mut *mut c_char, format: *const c_char, ap: va_list, ) -> c_int { let mut alloc_writer = CVec::new(); - let ret = printf::printf(&mut alloc_writer, format, ap); + let ret = unsafe { printf::printf(&mut alloc_writer, CStr::from_ptr(format), ap) }; alloc_writer.push(0).unwrap(); alloc_writer.shrink_to_fit().unwrap(); - *strp = alloc_writer.leak() as *mut c_char; + unsafe { *strp = alloc_writer.leak().cast::() }; ret } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn asprintf( + strp: *mut *mut c_char, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { vasprintf(strp, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vsnprintf( s: *mut c_char, n: size_t, format: *const c_char, ap: va_list, ) -> c_int { - printf::printf( - &mut platform::StringWriter(s as *mut u8, n as usize), - format, - ap, - ) + unsafe { + printf::printf( + &mut platform::StringWriter(s.cast::(), n), + CStr::from_ptr(format), + ap, + ) + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn snprintf( + s: *mut c_char, + n: size_t, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + printf::printf( + &mut platform::StringWriter(s.cast::(), n), + CStr::from_ptr(format), + __valist, + ) + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_list) -> c_int { - printf::printf(&mut platform::UnsafeStringWriter(s as *mut u8), format, ap) + unsafe { + printf::printf( + &mut platform::UnsafeStringWriter(s.cast::()), + CStr::from_ptr(format), + ap, + ) + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sprintf( + s: *mut c_char, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + printf::printf( + &mut platform::UnsafeStringWriter(s.cast::()), + CStr::from_ptr(format), + __valist, + ) + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vfscanf(file: *mut FILE, format: *const c_char, ap: va_list) -> c_int { - let ret = { - let mut file = (*file).lock(); - if let Err(_) = file.try_set_byte_orientation_unlocked() { - return -1; - } + let mut file = unsafe { (*file).lock() }; + if file.try_set_byte_orientation_unlocked().is_err() { + return -1; + } - let f: &mut FILE = &mut *file; - let reader: LookAheadReader = f.into(); - scanf::scanf(reader, format, ap) - }; - ret + let f: &mut FILE = &mut file; + let reader: Reader = f.into(); + unsafe { + let format = CStr::from_ptr(format); + scanf::scanf(reader, format.into(), ap) + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fscanf( + file: *mut FILE, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { vfscanf(file, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn vscanf(format: *const c_char, ap: va_list) -> c_int { - vfscanf(&mut *stdin, format, ap) + unsafe { vfscanf(&raw mut *stdin, format, ap) } } -#[no_mangle] -pub unsafe extern "C" fn vsscanf(s: *const c_char, format: *const c_char, ap: va_list) -> c_int { - let reader = (s as *const u8).into(); - scanf::scanf(reader, format, ap) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn scanf(format: *const c_char, mut __valist: ...) -> c_int { + unsafe { vfscanf(&raw mut *stdin, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vsscanf(s: *const c_char, format: *const c_char, ap: va_list) -> c_int { + unsafe { + let format = CStr::from_ptr(format); + let s = CStr::from_ptr(s); + scanf::scanf(s.into(), format.into(), ap) + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sscanf( + s: *const c_char, + format: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + let format = CStr::from_ptr(format); + let s = CStr::from_ptr(s); + scanf::scanf(s.into(), format.into(), __valist) + } +} + +pub unsafe fn flush_io_streams() { + let flush = |stream: *mut FILE| { + let stream = unsafe { &mut *stream }; + let _ = stream.flush(); + }; + flush(unsafe { stdout }); + flush(unsafe { stderr }); } diff --git a/src/header/stdio/open_memstream.rs b/src/header/stdio/open_memstream.rs new file mode 100644 index 0000000000..60ee508e7c --- /dev/null +++ b/src/header/stdio/open_memstream.rs @@ -0,0 +1,124 @@ +use alloc::{boxed::Box, vec, vec::Vec}; +use core::ptr; + +use super::{ + Buffer, FILE, + constants::{BUFSIZ, F_NORD}, +}; +use crate::{ + error::{Errno, ResultExtPtrMut}, + fs::File, + header::{ + errno::{EFAULT, ENOMEM}, + fcntl, pthread, stdlib, unistd, + }, + io::{self, BufWriter, Write}, + platform::{ + ERRNO, + types::{c_char, size_t}, + }, +}; + +struct MemstreamWriter { + bufp: *mut *mut c_char, + sizep: *mut size_t, + current: *mut c_char, + buffer: Vec, +} + +unsafe impl Send for MemstreamWriter {} + +impl MemstreamWriter { + fn new(bufp: *mut *mut c_char, sizep: *mut size_t) -> Self { + Self { + bufp, + sizep, + current: ptr::null_mut(), + buffer: Vec::new(), + } + } + + fn sync_output(&mut self) -> io::Result<()> { + let size = self.buffer.len(); + let alloc_size = size + .checked_add(1) + .ok_or_else(|| io::Error::from_raw_os_error(ENOMEM))?; + + let raw = if self.current.is_null() { + unsafe { stdlib::malloc(alloc_size) } + } else { + unsafe { stdlib::realloc(self.current.cast(), alloc_size) } + }; + if raw.is_null() { + return Err(io::Error::from_raw_os_error(ENOMEM)); + } + + let raw = raw.cast::(); + if size != 0 { + unsafe { ptr::copy_nonoverlapping(self.buffer.as_ptr(), raw.cast::(), size) }; + } + unsafe { + *raw.add(size) = 0; + *self.bufp = raw; + *self.sizep = size; + } + self.current = raw; + Ok(()) + } +} + +impl Write for MemstreamWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buffer + .try_reserve(buf.len()) + .map_err(|_| io::Error::from_raw_os_error(ENOMEM))?; + self.buffer.extend_from_slice(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.sync_output() + } +} + +fn create_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> Result, Errno> { + if bufp.is_null() || sizep.is_null() { + return Err(Errno(EFAULT)); + } + + unsafe { + *bufp = ptr::null_mut(); + *sizep = 0; + } + + let mut fds = [0; 2]; + if unsafe { unistd::pipe2(fds.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 { + return Err(Errno(ERRNO.get())); + } + let _ = unistd::close(fds[0]); + + let file = File::new(fds[1]); + let writer = Box::new(BufWriter::new(MemstreamWriter::new(bufp, sizep))); + let mutex_attr = pthread::RlctMutexAttr { + ty: pthread::PTHREAD_MUTEX_RECURSIVE, + ..Default::default() + }; + + Ok(Box::new(FILE { + lock: pthread::RlctMutex::new(&mutex_attr).unwrap(), + file, + flags: F_NORD, + read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]), + read_pos: 0, + read_size: 0, + unget: Vec::new(), + writer, + pid: None, + orientation: 0, + })) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> *mut FILE { + create_memstream(bufp, sizep).or_errno_null_mut() +} diff --git a/src/header/stdio/printf.rs b/src/header/stdio/printf.rs index d06570fa86..ac2c190988 100644 --- a/src/header/stdio/printf.rs +++ b/src/header/stdio/printf.rs @@ -1,16 +1,34 @@ -use crate::io::{self, Write}; +// TODO: reuse more code with the wide printf impl +use crate::{ + c_str::{self, CStr, NulStr}, + io::{self, Write}, +}; use alloc::{ collections::BTreeMap, string::{String, ToString}, vec::Vec, }; -use core::{char, cmp, f64, ffi::VaList, fmt, num::FpCategory, ops::Range, slice}; +use core::{cmp, ffi::VaList, fmt, num::FpCategory, ops::Range, slice}; use crate::{ - header::errno::EILSEQ, - platform::{self, types::*}, + header::errno::{self, EILSEQ}, + platform::{ + self, + types::{ + c_char, c_double, c_int, c_long, c_longdouble, c_longlong, c_short, c_uchar, c_uint, + c_ulong, c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, + uintmax_t, wchar_t, wint_t, + }, + }, }; +#[allow(unused_doc_comments)] +/// cbindgen:ignore +unsafe extern "C" { + pub unsafe fn relibc_ldtod(x: *const c_longdouble) -> c_double; + pub unsafe fn relibc_dtold(x: c_double, out: *mut c_longdouble); +} + // ____ _ _ _ _ // | __ ) ___ (_) | ___ _ __ _ __ | | __ _| |_ ___ _ // | _ \ / _ \| | |/ _ \ '__| '_ \| |/ _` | __/ _ (_) @@ -19,7 +37,7 @@ use crate::{ // |_| #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum IntKind { +pub(crate) enum IntKind { Byte, Short, Int, @@ -30,7 +48,7 @@ enum IntKind { Size, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum FmtKind { +pub(crate) enum FmtKind { Percent, Signed, @@ -46,25 +64,29 @@ enum FmtKind { GetWritten, } #[derive(Clone, Copy, Debug)] -enum Number { +pub(crate) enum Number { Static(usize), Index(usize), Next, } impl Number { - unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize { + pub(crate) unsafe fn resolve(self, varargs: &mut VaListCache, ap: &mut VaList) -> usize { let arg = match self { Number::Static(num) => return num, - Number::Index(i) => varargs.get(i - 1, ap, None), + Number::Index(i) => unsafe { varargs.get(i - 1, ap, None) }, Number::Next => { let i = varargs.i; varargs.i += 1; - varargs.get(i, ap, None) + unsafe { varargs.get(i, ap, None) } } }; match arg { VaArg::c_char(i) => i as usize, VaArg::c_double(i) => i as usize, + #[cfg(target_pointer_width = "32")] + VaArg::c_longdouble(_) => 0 as usize, + #[cfg(target_pointer_width = "64")] + VaArg::c_longdouble(i) => i as usize, VaArg::c_int(i) => i as usize, VaArg::c_long(i) => i as usize, VaArg::c_longlong(i) => i as usize, @@ -78,9 +100,10 @@ impl Number { } } #[derive(Clone, Copy, Debug)] -enum VaArg { +pub(crate) enum VaArg { c_char(c_char), c_double(c_double), + c_longdouble(c_longdouble), c_int(c_int), c_long(c_long), c_longlong(c_longlong), @@ -92,7 +115,7 @@ enum VaArg { wint_t(wint_t), } impl VaArg { - unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> VaArg { + pub(crate) unsafe fn arg_from(fmtkind: FmtKind, intkind: IntKind, ap: &mut VaList) -> VaArg { // Per the C standard using va_arg with a type with a size // less than that of an int for integers and double for floats // is invalid. As a result any arguments smaller than an int or @@ -104,43 +127,115 @@ impl VaArg { (FmtKind::Percent, _) => panic!("Can't call arg_from on %"), (FmtKind::Char, IntKind::Long) | (FmtKind::Char, IntKind::LongLong) => { - VaArg::wint_t(ap.arg::()) + VaArg::wint_t(unsafe { ap.next_arg::() }) } (FmtKind::Char, _) | (FmtKind::Unsigned, IntKind::Byte) - | (FmtKind::Signed, IntKind::Byte) => VaArg::c_char(ap.arg::()), + | (FmtKind::Signed, IntKind::Byte) => { + // c_int is passed but truncated to c_char + VaArg::c_char(unsafe { ap.next_arg::() } as c_char) + } (FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => { - VaArg::c_short(ap.arg::()) + // c_int is passed but truncated to c_short + VaArg::c_short(unsafe { ap.next_arg::() } as c_short) } (FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => { - VaArg::c_int(ap.arg::()) + VaArg::c_int(unsafe { ap.next_arg::() }) } (FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => { - VaArg::c_long(ap.arg::()) + VaArg::c_long(unsafe { ap.next_arg::() }) } (FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => { - VaArg::c_longlong(ap.arg::()) + VaArg::c_longlong(unsafe { ap.next_arg::() }) } (FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => { - VaArg::intmax_t(ap.arg::()) + VaArg::intmax_t(unsafe { ap.next_arg::() }) } (FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => { - VaArg::ptrdiff_t(ap.arg::()) + VaArg::ptrdiff_t(unsafe { ap.next_arg::() }) } (FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => { - VaArg::ssize_t(ap.arg::()) + VaArg::ssize_t(unsafe { ap.next_arg::() }) } + (FmtKind::AnyNotation, IntKind::LongLong) + | (FmtKind::Decimal, IntKind::LongLong) + | (FmtKind::Scientific, IntKind::LongLong) => { + VaArg::c_longdouble(unsafe { VaArg::extract_longdouble(ap) }) + } (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { - VaArg::c_double(ap.arg::()) + VaArg::c_double(unsafe { ap.next_arg::() }) } (FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => { - VaArg::pointer(ap.arg::<*const c_void>()) + VaArg::pointer(unsafe { ap.next_arg::<*const c_void>() }) } } } + #[cfg(target_arch = "x86")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + todo_skip!(0, "long double in variadic printf is not supported"); + [0, 0, 0] + } + #[cfg(target_arch = "x86_64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + // https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf (long double) + + // exactly same as core::ffi::VaListImpl but all variables exposed + #[repr(C)] + struct VaListImpl { + gp_offset: i32, + fp_offset: i32, + overflow_arg_area: *mut u8, + reg_save_area: *mut u8, + } + + let ap_impl = unsafe { + // The double deconstruct is intended + let ptr_to_struct = (ap as *mut core::ffi::VaList as *mut VaListImpl); + &mut *ptr_to_struct + }; + + let ptr = ap_impl.overflow_arg_area as *const c_longdouble; + let val = unsafe { ptr.read() }; + + ap_impl.overflow_arg_area = unsafe { ap_impl.overflow_arg_area.add(16) }; + + val + } + #[cfg(target_arch = "aarch64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + // https://c9x.me/compile/bib/abi-arm64.pdf (quad precision) + + // exactly same as core::ffi::VaListImpl but all variables exposed + #[repr(C)] + struct VaListImpl { + stack: *mut u8, + gr_top: *mut u8, + vr_top: *mut u8, + gr_offs: i32, + vr_offs: i32, + } + + let ap_impl: &mut VaListImpl = unsafe { + // The double deconstruct is intended + let ptr_to_struct = (ap as *mut core::ffi::VaList as *mut VaListImpl); + &mut *ptr_to_struct + }; + + let ptr = unsafe { ap_impl.vr_top.offset(ap_impl.vr_offs as isize) as *const c_longdouble }; + + ap_impl.vr_offs += 16; + + unsafe { ptr.read() } + } + + #[cfg(target_arch = "riscv64")] + unsafe fn extract_longdouble(ap: &mut core::ffi::VaList) -> c_longdouble { + todo_skip!(0, "long double in variadic printf is not supported"); + 0u128 + } unsafe fn transmute(&self, fmtkind: FmtKind, intkind: IntKind) -> VaArg { // At this point, there are conflicting printf arguments. An // example of this is: @@ -153,6 +248,7 @@ impl VaArg { union Untyped { c_char: c_char, c_double: c_double, + c_longdouble: c_longdouble, c_int: c_int, c_long: c_long, c_longlong: c_longlong, @@ -166,6 +262,7 @@ impl VaArg { let untyped = match *self { VaArg::c_char(i) => Untyped { c_char: i }, VaArg::c_double(i) => Untyped { c_double: i }, + VaArg::c_longdouble(i) => Untyped { c_longdouble: i }, VaArg::c_int(i) => Untyped { c_int: i }, VaArg::c_long(i) => Untyped { c_long: i }, VaArg::c_longlong(i) => Untyped { c_longlong: i }, @@ -180,51 +277,56 @@ impl VaArg { (FmtKind::Percent, _) => panic!("Can't call transmute on %"), (FmtKind::Char, IntKind::Long) | (FmtKind::Char, IntKind::LongLong) => { - VaArg::wint_t(untyped.wint_t) + VaArg::wint_t(unsafe { untyped.wint_t }) } (FmtKind::Char, _) | (FmtKind::Unsigned, IntKind::Byte) - | (FmtKind::Signed, IntKind::Byte) => VaArg::c_char(untyped.c_char), + | (FmtKind::Signed, IntKind::Byte) => VaArg::c_char(unsafe { untyped.c_char }), (FmtKind::Unsigned, IntKind::Short) | (FmtKind::Signed, IntKind::Short) => { - VaArg::c_short(untyped.c_short) + VaArg::c_short(unsafe { untyped.c_short }) } (FmtKind::Unsigned, IntKind::Int) | (FmtKind::Signed, IntKind::Int) => { - VaArg::c_int(untyped.c_int) + VaArg::c_int(unsafe { untyped.c_int }) } (FmtKind::Unsigned, IntKind::Long) | (FmtKind::Signed, IntKind::Long) => { - VaArg::c_long(untyped.c_long) + VaArg::c_long(unsafe { untyped.c_long }) } (FmtKind::Unsigned, IntKind::LongLong) | (FmtKind::Signed, IntKind::LongLong) => { - VaArg::c_longlong(untyped.c_longlong) + VaArg::c_longlong(unsafe { untyped.c_longlong }) } (FmtKind::Unsigned, IntKind::IntMax) | (FmtKind::Signed, IntKind::IntMax) => { - VaArg::intmax_t(untyped.intmax_t) + VaArg::intmax_t(unsafe { untyped.intmax_t }) } (FmtKind::Unsigned, IntKind::PtrDiff) | (FmtKind::Signed, IntKind::PtrDiff) => { - VaArg::ptrdiff_t(untyped.ptrdiff_t) + VaArg::ptrdiff_t(unsafe { untyped.ptrdiff_t }) } (FmtKind::Unsigned, IntKind::Size) | (FmtKind::Signed, IntKind::Size) => { - VaArg::ssize_t(untyped.ssize_t) + VaArg::ssize_t(unsafe { untyped.ssize_t }) } + (FmtKind::AnyNotation, IntKind::LongLong) + | (FmtKind::Decimal, IntKind::LongLong) + | (FmtKind::Scientific, IntKind::LongLong) => { + VaArg::c_longdouble(unsafe { untyped.c_longdouble }) + } (FmtKind::AnyNotation, _) | (FmtKind::Decimal, _) | (FmtKind::Scientific, _) => { - VaArg::c_double(untyped.c_double) + VaArg::c_double(unsafe { untyped.c_double }) } (FmtKind::GetWritten, _) | (FmtKind::Pointer, _) | (FmtKind::String, _) => { - VaArg::pointer(untyped.pointer) + VaArg::pointer(unsafe { untyped.pointer }) } } } } #[derive(Default)] -struct VaListCache { - args: Vec, - i: usize, +pub(crate) struct VaListCache { + pub(crate) args: Vec, + pub(crate) i: usize, } impl VaListCache { - unsafe fn get( + pub(crate) unsafe fn get( &mut self, i: usize, ap: &mut VaList, @@ -235,7 +337,7 @@ impl VaListCache { let mut arg = arg; if let Some((fmtkind, intkind)) = default { // ...but as a different type - arg = arg.transmute(fmtkind, intkind); + arg = unsafe { arg.transmute(fmtkind, intkind) }; } return arg; } @@ -246,13 +348,13 @@ impl VaListCache { // point. Reaching here means there are unused gaps in the // arguments. Ultimately we'll have to settle down with // defaulting to c_int. - self.args.push(VaArg::c_int(ap.arg::())) + self.args.push(VaArg::c_int(unsafe { ap.next_arg::() })) } // Add the value to the cache self.args.push(match default { - Some((fmtkind, intkind)) => VaArg::arg_from(fmtkind, intkind, ap), - None => VaArg::c_int(ap.arg::()), + Some((fmtkind, intkind)) => unsafe { VaArg::arg_from(fmtkind, intkind, ap) }, + None => VaArg::c_int(unsafe { ap.next_arg::() }), }); // Return the value @@ -279,10 +381,13 @@ static INF_STR_UPPER: &str = "INF"; static NAN_STR_LOWER: &str = "nan"; static NAN_STR_UPPER: &str = "NAN"; -unsafe fn pop_int_raw(format: &mut *const u8) -> Option { +fn pop_int_raw(format: &mut NulStr) -> Option { let mut int = None; - while let Some(digit) = (**format as char).to_digit(10) { - *format = format.add(1); + while let Some((digit, rest)) = format + .split_first_char() + .and_then(|(d, r)| Some((d.to_digit(10)?, r))) + { + *format = rest; if int.is_none() { int = Some(0); } @@ -291,39 +396,37 @@ unsafe fn pop_int_raw(format: &mut *const u8) -> Option { } int } -unsafe fn pop_index(format: &mut *const u8) -> Option { +fn pop_index(format: &mut NulStr) -> Option { // Peek ahead for a positional argument: let mut format2 = *format; - if let Some(i) = pop_int_raw(&mut format2) { - if *format2 == b'$' { - *format = format2.add(1); - return Some(i); - } + if let Some(i) = pop_int_raw(&mut format2) + && let Some(('$', format2)) = format2.split_first_char() + { + *format = format2; + return Some(i); } None } -unsafe fn pop_int(format: &mut *const u8) -> Option { - if **format == b'*' { - *format = format.add(1); +fn pop_int(format: &mut NulStr) -> Option { + if let Some(('*', rest)) = format.split_first_char() { + *format = rest; Some(pop_index(format).map(Number::Index).unwrap_or(Number::Next)) } else { pop_int_raw(format).map(Number::Static) } } -unsafe fn fmt_int(fmt: u8, i: I) -> String +fn fmt_int(fmt: char, i: I) -> String where - I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex, + I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex + fmt::Binary, { match fmt { - b'o' => format!("{:o}", i), - b'u' => i.to_string(), - b'x' => format!("{:x}", i), - b'X' => format!("{:X}", i), - _ => panic!( - "fmt_int should never be called with the fmt {:?}", - fmt as char - ), + 'o' => format!("{:o}", i), + 'u' => i.to_string(), + 'x' => format!("{:x}", i), + 'X' => format!("{:X}", i), + 'b' | 'B' if T::IS_THIN_NOT_WIDE => format!("{:b}", i), + _ => panic!("fmt_int should never be called with the fmt {:?}", fmt,), } } @@ -341,18 +444,23 @@ fn pad( Ok(()) } -fn abs(float: c_double) -> c_double { - // Don't ask me whe float.abs() seems absent... - if float.is_sign_negative() { - -float - } else { - float - } -} - -fn float_string(float: c_double, precision: usize, trim: bool) -> String { +fn float_string(float: c_double, precision: usize, trim: bool, alternate: bool) -> String { + // The Rust format! macro doesn't keep the dot on precision = 0 and alternate = true, + // so we have to perform a fix-up + // + // POSIX.1-2024 says "... if the precision is zero and no '#' flag is present, + // no radix character shall appear." + // + // This case is covered here. let mut string = format!("{:.p$}", float, p = precision); - if trim && string.contains('.') { + // + // Additionally, it says "For a, A, e, E, f, F, g, and G conversion specifiers, + // the result shall always contain a radix character, even if no digits follow + // the radix character." + // + if alternate && precision == 0 { + string.push('.'); + } else if trim && string.contains('.') { let truncate = { let slice = string.trim_end_matches('0'); let mut truncate = slice.len(); @@ -368,11 +476,11 @@ fn float_string(float: c_double, precision: usize, trim: bool) -> String { fn float_exp(mut float: c_double) -> (c_double, isize) { let mut exp: isize = 0; - while abs(float) >= 10.0 { + while float.abs() >= 10.0 { float /= 10.0; exp += 1; } - while f64::EPSILON < abs(float) && abs(float) < 1.0 { + while f64::EPSILON < float.abs() && float.abs() < 1.0 { float *= 10.0; exp -= 1; } @@ -381,8 +489,9 @@ fn float_exp(mut float: c_double) -> (c_double, isize) { fn fmt_float_exp( w: &mut W, - exp_fmt: u8, + exp_fmt: char, trim: bool, + alternate: bool, precision: usize, float: c_double, exp: isize, @@ -397,19 +506,19 @@ fn fmt_float_exp( exp_len += 1; } - let string = float_string(float, precision, trim); + let string = float_string(float, precision, trim, alternate); let len = string.len() + 2 + 2.max(exp_len); pad(w, !left, b' ', len..pad_space)?; let bytes = if string.starts_with('-') { - w.write_all(&[b'-'])?; + w.write_all(b"-")?; &string.as_bytes()[1..] } else { string.as_bytes() }; pad(w, !left, b'0', len..pad_zero)?; w.write_all(bytes)?; - write!(w, "{}{:+03}", exp_fmt as char, exp)?; + write!(w, "{}{:+03}", exp_fmt, exp)?; pad(w, left, b' ', len..pad_space)?; Ok(()) @@ -418,17 +527,18 @@ fn fmt_float_exp( fn fmt_float_normal( w: &mut W, trim: bool, + alternate: bool, precision: usize, float: c_double, left: bool, pad_space: usize, pad_zero: usize, ) -> io::Result { - let string = float_string(float, precision, trim); + let string = float_string(float, precision, trim, alternate); pad(w, !left, b' ', string.len()..pad_space)?; let bytes = if string.starts_with('-') { - w.write_all(&[b'-'])?; + w.write_all(b"-")?; &string.as_bytes()[1..] } else { string.as_bytes() @@ -441,12 +551,15 @@ fn fmt_float_normal( } /// Write ±infinity or ±NaN representation for any floating-point style -fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> io::Result<()> { - if float.is_sign_negative() { - w.write_all(&[b'-'])?; - } - - let nonfinite_str = match float.classify() { +fn fmt_float_nonfinite( + w: &mut W, + float: c_double, + case: FmtCase, + left: bool, + pad_space: usize, + pad_zero: usize, +) -> io::Result<()> { + let string = match float.classify() { FpCategory::Infinite => match case { FmtCase::Lower => INF_STR_LOWER, FmtCase::Upper => INF_STR_UPPER, @@ -457,163 +570,191 @@ fn fmt_float_nonfinite(w: &mut W, float: c_double, case: FmtCase) -> i }, _ => { // This function should only be called with infinite or NaN value. - panic!("this should not be possible") + panic!("fmt_float_nonfinite called with finite float") } }; - w.write_all(nonfinite_str.as_bytes())?; + // Infinity is always padded with spaces, rather than zeroes + pad(w, !left, b' ', string.len()..pad_space + pad_zero)?; + if float.is_sign_negative() { + w.write_all(b"-")?; + } + w.write_all(string.as_bytes())?; + pad(w, left, b' ', string.len()..pad_space + pad_zero)?; Ok(()) } #[derive(Clone, Copy)] -struct PrintfIter { - format: *const u8, +pub(crate) struct PrintfIter<'a, T: c_str::Kind> { + pub(crate) format: NulStr<'a, T>, } #[derive(Clone, Copy, Debug)] -struct PrintfArg { - index: Option, - alternate: bool, - zero: bool, - left: bool, - sign_reserve: bool, - sign_always: bool, - min_width: Number, - precision: Option, - intkind: IntKind, - fmt: u8, - fmtkind: FmtKind, +pub(crate) struct PrintfArg { + pub(crate) index: Option, + pub(crate) alternate: bool, + pub(crate) zero: bool, + pub(crate) left: bool, + pub(crate) sign_reserve: bool, + pub(crate) sign_always: bool, + pub(crate) min_width: Number, + pub(crate) precision: Option, + pub(crate) intkind: IntKind, + pub(crate) fmt: char, + pub(crate) fmtkind: FmtKind, } #[derive(Debug)] -enum PrintfFmt { - Plain(&'static [u8]), +pub(crate) enum PrintfFmt<'a, U> { + Plain(&'a [U]), Arg(PrintfArg), } -impl Iterator for PrintfIter { - type Item = Result; +impl<'a, T: c_str::Kind> Iterator for PrintfIter<'a, T> { + type Item = Result, ()>; + fn next(&mut self) -> Option { - unsafe { - // Send PrintfFmt::Plain until the next % - let mut len = 0; - while *self.format.add(len) != 0 && *self.format.add(len) != b'%' { - len += 1; + // Send PrintfFmt::Plain until the next % + let first_percent = match self.format.find_get_subslice_or_all(b'%') { + Err(([], _)) => return None, + Ok((chunk @ [_, ..], rest)) | Err((chunk @ [_, ..], rest)) => { + self.format = rest; + return Some(Ok(PrintfFmt::Plain(chunk))); } - if len > 0 { - let slice = slice::from_raw_parts(self.format as *const u8, len); - self.format = self.format.add(len); - return Some(Ok(PrintfFmt::Plain(slice))); + Ok(([], rest)) => rest, + }; + + // at this point the next char must be % + self.format = first_percent.split_first().expect("must be %").1; + + let mut peekahead = self.format; + let index = pop_index(&mut peekahead).inspect(|i| { + self.format = peekahead; + }); + + // Flags: + let mut alternate = false; + let mut zero = false; + let mut left = false; + let mut sign_reserve = false; + let mut sign_always = false; + + while let Some((c, rest)) = self.format.split_first_char() { + match c { + '#' => alternate = true, + '0' => zero = true, + '-' => left = true, + ' ' => sign_reserve = true, + '+' => sign_always = true, + _ => break, } - self.format = self.format.add(len); - if *self.format == 0 { - return None; - } - - // *self.format is guaranteed to be '%' at this point - self.format = self.format.add(1); - - let mut peekahead = self.format; - let index = pop_index(&mut peekahead).map(|i| { - self.format = peekahead; - i - }); - - // Flags: - let mut alternate = false; - let mut zero = false; - let mut left = false; - let mut sign_reserve = false; - let mut sign_always = false; - - loop { - match *self.format { - b'#' => alternate = true, - b'0' => zero = true, - b'-' => left = true, - b' ' => sign_reserve = true, - b'+' => sign_always = true, - _ => break, - } - self.format = self.format.add(1); - } - - // Width and precision: - let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); - let precision = if *self.format == b'.' { - self.format = self.format.add(1); - match pop_int(&mut self.format) { - int @ Some(_) => int, - None => return Some(Err(())), - } - } else { - None - }; - - // Integer size: - let mut intkind = IntKind::Int; - loop { - intkind = match *self.format { - b'h' => { - if intkind == IntKind::Short || intkind == IntKind::Byte { - IntKind::Byte - } else { - IntKind::Short - } - } - b'j' => IntKind::IntMax, - b'l' => { - if intkind == IntKind::Long || intkind == IntKind::LongLong { - IntKind::LongLong - } else { - IntKind::Long - } - } - b'q' | b'L' => IntKind::LongLong, - b't' => IntKind::PtrDiff, - b'z' => IntKind::Size, - _ => break, - }; - - self.format = self.format.add(1); - } - let fmt = *self.format; - let fmtkind = match fmt { - b'%' => FmtKind::Percent, - b'd' | b'i' => FmtKind::Signed, - b'o' | b'u' | b'x' | b'X' => FmtKind::Unsigned, - b'e' | b'E' => FmtKind::Scientific, - b'f' | b'F' => FmtKind::Decimal, - b'g' | b'G' => FmtKind::AnyNotation, - b's' => FmtKind::String, - b'c' => FmtKind::Char, - b'p' => FmtKind::Pointer, - b'n' => FmtKind::GetWritten, - _ => return Some(Err(())), - }; - self.format = self.format.add(1); - - Some(Ok(PrintfFmt::Arg(PrintfArg { - index, - alternate, - zero, - left, - sign_reserve, - sign_always, - min_width, - precision, - intkind, - fmt, - fmtkind, - }))) + self.format = rest; } + + // Width and precision: + let min_width = pop_int(&mut self.format).unwrap_or(Number::Static(0)); + let precision = if let Some(('.', rest)) = self.format.split_first_char() { + self.format = rest; + match pop_int(&mut self.format) { + int @ Some(_) => int, + None => return Some(Err(())), + } + } else { + None + }; + + // Integer size: + let mut intkind = IntKind::Int; + while let Some((byte, rest)) = self.format.split_first_char() { + intkind = match byte { + 'h' => { + if intkind == IntKind::Short || intkind == IntKind::Byte { + IntKind::Byte + } else { + IntKind::Short + } + } + 'j' => IntKind::IntMax, + 'l' => { + if intkind == IntKind::Long || intkind == IntKind::LongLong { + IntKind::LongLong + } else { + IntKind::Long + } + } + 'q' | 'L' => IntKind::LongLong, + 't' => IntKind::PtrDiff, + 'z' => IntKind::Size, + _ => break, + }; + + self.format = rest; + } + let Some((fmt, rest)) = self.format.split_first_char() else { + return Some(Err(())); + }; + self.format = rest; + let fmtkind = match fmt { + '%' => FmtKind::Percent, + 'd' | 'i' => FmtKind::Signed, + 'o' | 'u' | 'x' | 'X' => FmtKind::Unsigned, + 'b' | 'B' if T::IS_THIN_NOT_WIDE => FmtKind::Unsigned, + 'e' | 'E' => FmtKind::Scientific, + 'f' | 'F' | 'L' => FmtKind::Decimal, + 'g' | 'G' => FmtKind::AnyNotation, + 's' => FmtKind::String, + 'c' => FmtKind::Char, + 'p' => FmtKind::Pointer, + 'n' => FmtKind::GetWritten, + 'm' if T::IS_THIN_NOT_WIDE => { + // %m is technically for syslog only, but musl and glibc implement it for + // printf because it is difficult and error prone to implement a format + // specifier for just *one* function. + return Some(Ok(PrintfFmt::Plain( + T::chars_from_bytes( + errno::STR_ERROR + .get(platform::ERRNO.get() as usize) + .map(|e| e.as_bytes()) + .unwrap_or(b"unknown error"), + ) + .expect("string must be thin"), + ))); + } + _ => return Some(Err(())), + }; + // "For b, B, d, i, o, u, x, and X conversions, + // if a precision is specified, the 0 flag is ignored." + match fmt { + 'b' | 'B' | 'd' | 'i' | 'o' | 'u' | 'x' | 'X' => { + if precision.is_some() { + zero = false; + } + } + _ => (), + } + + Some(Ok(PrintfFmt::Arg(PrintfArg { + index, + alternate, + zero, + left, + sign_reserve, + sign_always, + min_width, + precision, + intkind, + fmt, + fmtkind, + }))) } } -unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> io::Result { +pub(crate) unsafe fn inner_printf( + w: impl Write, + format: NulStr, + mut ap: VaList, +) -> io::Result { let w = &mut platform::CountingWriter::new(w); - let iterator = PrintfIter { - format: format as *const u8, - }; + let iterator = PrintfIter { format }; // Pre-fetch vararg types let mut varargs = VaListCache::default(); @@ -631,7 +772,9 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> } for num in &[arg.min_width, arg.precision.unwrap_or(Number::Static(0))] { match num { - Number::Next => varargs.args.push(VaArg::c_int(ap.arg::())), + Number::Next => varargs + .args + .push(VaArg::c_int(unsafe { ap.next_arg::() })), Number::Index(i) => { positional.insert(i - 1, (FmtKind::Signed, IntKind::Int)); } @@ -644,20 +787,28 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> } None => varargs .args - .push(VaArg::arg_from(arg.fmtkind, arg.intkind, &mut ap)), + .push(unsafe { VaArg::arg_from(arg.fmtkind, arg.intkind, &mut ap) }), } } // Make sure, in order, the positional arguments exist with the specified type for (i, arg) in positional { - varargs.get(i, &mut ap, Some(arg)); + unsafe { varargs.get(i, &mut ap, Some(arg)) }; } // Main loop for section in iterator { let arg = match section { Ok(PrintfFmt::Plain(text)) => { - w.write_all(text)?; + if T::IS_THIN_NOT_WIDE { + let bytes = T::chars_to_bytes(text).expect("is thin"); + w.write_all(bytes)?; + } else { + // TODO: wcsrtombs wrapper + for c in text.iter().filter_map(|u| char::from_u32((*u).into())) { + if let Ok(()) = write!(w, "{}", c) {}; // TODO handle error + } + } continue; } Ok(PrintfFmt::Arg(arg)) => arg, @@ -668,8 +819,11 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> let mut left = arg.left; let sign_reserve = arg.sign_reserve; let sign_always = arg.sign_always; - let min_width = arg.min_width.resolve(&mut varargs, &mut ap); - let precision = arg.precision.map(|n| n.resolve(&mut varargs, &mut ap)); + let min_width = unsafe { arg.min_width.resolve(&mut varargs, &mut ap) }; + let precision = arg + .precision + .map(|n| unsafe { n.resolve(&mut varargs, &mut ap) }) + .filter(|&n| (n as c_int) >= 0); let pad_zero = if zero { min_width } else { 0 }; let signed_space = match pad_zero { 0 => min_width as isize, @@ -685,8 +839,10 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> let fmt = arg.fmt; let fmtkind = arg.fmtkind; let fmtcase = match fmt { - b'x' | b'f' | b'e' | b'g' => Some(FmtCase::Lower), - b'X' | b'F' | b'E' | b'G' => Some(FmtCase::Upper), + 'b' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Lower), + 'B' if T::IS_THIN_NOT_WIDE => Some(FmtCase::Upper), + 'x' | 'f' | 'e' | 'g' => Some(FmtCase::Lower), + 'X' | 'F' | 'E' | 'G' => Some(FmtCase::Upper), _ => None, }; @@ -701,11 +857,14 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> }); match fmtkind { - FmtKind::Percent => w.write_all(&[b'%'])?, + FmtKind::Percent => w.write_all(b"%")?, FmtKind::Signed => { - let string = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let string = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::c_char(i) => i.to_string(), VaArg::c_double(i) => panic!("this should not be possible"), + VaArg::c_longdouble(i) => panic!("this should not be possible"), VaArg::c_int(i) => i.to_string(), VaArg::c_long(i) => i.to_string(), VaArg::c_longlong(i) => i.to_string(), @@ -733,13 +892,13 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> let bytes = if positive { if sign_reserve { - w.write_all(&[b' '])?; + w.write_all(b" ")?; } else if sign_always { - w.write_all(&[b'+'])?; + w.write_all(b"+")?; } string.as_bytes() } else { - w.write_all(&[b'-'])?; + w.write_all(b"-")?; &string.as_bytes()[1..] }; pad(w, true, b'0', len..precision.unwrap_or(pad_zero))?; @@ -751,17 +910,20 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> pad(w, left, b' ', final_len..pad_space)?; } FmtKind::Unsigned => { - let string = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_char(i) => fmt_int(fmt, i as c_uchar), + let string = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { + VaArg::c_char(i) => fmt_int::<_, T>(fmt, i as c_uchar), VaArg::c_double(i) => panic!("this should not be possible"), - VaArg::c_int(i) => fmt_int(fmt, i as c_uint), - VaArg::c_long(i) => fmt_int(fmt, i as c_ulong), - VaArg::c_longlong(i) => fmt_int(fmt, i as c_ulonglong), - VaArg::c_short(i) => fmt_int(fmt, i as c_ushort), - VaArg::intmax_t(i) => fmt_int(fmt, i as uintmax_t), - VaArg::pointer(i) => fmt_int(fmt, i as usize), - VaArg::ptrdiff_t(i) => fmt_int(fmt, i as size_t), - VaArg::ssize_t(i) => fmt_int(fmt, i as size_t), + VaArg::c_longdouble(i) => panic!("this should not be possible"), + VaArg::c_int(i) => fmt_int::<_, T>(fmt, i as c_uint), + VaArg::c_long(i) => fmt_int::<_, T>(fmt, i as c_ulong), + VaArg::c_longlong(i) => fmt_int::<_, T>(fmt, i as c_ulonglong), + VaArg::c_short(i) => fmt_int::<_, T>(fmt, i as c_ushort), + VaArg::intmax_t(i) => fmt_int::<_, T>(fmt, i as uintmax_t), + VaArg::pointer(i) => fmt_int::<_, T>(fmt, i as usize), + VaArg::ptrdiff_t(i) => fmt_int::<_, T>(fmt, i as size_t), + VaArg::ssize_t(i) => fmt_int::<_, T>(fmt, i as size_t), VaArg::wint_t(_) => unreachable!("this should not be possible"), }; let zero = precision == Some(0) && string == "0"; @@ -779,8 +941,9 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> len.max(precision.unwrap_or(0)) + if alternate && string != "0" { match fmt { - b'o' if no_precision => 1, - b'x' | b'X' => 2, + 'o' if no_precision => 1, + 'x' | 'X' => 2, + 'b' | 'B' if T::IS_THIN_NOT_WIDE => 2, _ => 0, } } else { @@ -792,9 +955,11 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> if alternate && string != "0" { match fmt { - b'o' if no_precision => w.write_all(&[b'0'])?, - b'x' => w.write_all(&[b'0', b'x'])?, - b'X' => w.write_all(&[b'0', b'X'])?, + 'o' if no_precision => w.write_all(b"0")?, + 'x' => w.write_all(b"0x")?, + 'X' => w.write_all(b"0X")?, + 'b' if T::IS_THIN_NOT_WIDE => w.write_all(b"0b")?, + 'B' if T::IS_THIN_NOT_WIDE => w.write_all(b"0B")?, _ => (), } } @@ -807,8 +972,11 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> pad(w, left, b' ', final_len..pad_space)?; } FmtKind::Scientific => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let float = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) }, _ => panic!("this should not be possible"), }; if float.is_finite() { @@ -816,33 +984,42 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> let precision = precision.unwrap_or(6); fmt_float_exp( - w, fmt, false, precision, float, exp, left, pad_space, pad_zero, + w, fmt, false, alternate, precision, float, exp, left, pad_space, pad_zero, )?; } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; + fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?; } } FmtKind::Decimal => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let float = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) }, _ => panic!("this should not be possible"), }; if float.is_finite() { let precision = precision.unwrap_or(6); - fmt_float_normal(w, false, precision, float, left, pad_space, pad_zero)?; + fmt_float_normal( + w, false, alternate, precision, float, left, pad_space, pad_zero, + )?; } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; + fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?; } } FmtKind::AnyNotation => { - let float = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let float = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::c_double(i) => i, + VaArg::c_longdouble(i) => unsafe { relibc_ldtod(&raw const i) }, _ => panic!("this should not be possible"), }; if float.is_finite() { let (log, exp) = float_exp(float); - let exp_fmt = b'E' | (fmt & 32); + // TODO: .is_uppercase()? + let exp_fmt = if fmt as u32 & 32 == 32 { 'e' } else { 'E' }; let precision = precision.unwrap_or(6); let use_exp_format = exp < -4 || exp >= precision as isize; @@ -851,7 +1028,8 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> // because that's how x/floor(log10(x)) works let precision = precision.saturating_sub(1); fmt_float_exp( - w, exp_fmt, true, precision, log, exp, left, pad_space, pad_zero, + w, exp_fmt, !alternate, alternate, precision, log, exp, left, + pad_space, pad_zero, )?; } else { // Length of integral part will be the exponent of @@ -860,33 +1038,38 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> // of course be 0, 1 in length let len = 1 + cmp::max(0, exp) as usize; let precision = precision.saturating_sub(len); - fmt_float_normal(w, true, precision, float, left, pad_space, pad_zero)?; + fmt_float_normal( + w, !alternate, alternate, precision, float, left, pad_space, pad_zero, + )?; } } else { - fmt_float_nonfinite(w, float, fmtcase.unwrap())?; + fmt_float_nonfinite(w, float, fmtcase.unwrap(), left, pad_space, pad_zero)?; } } FmtKind::String => { - let ptr = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let ptr = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::pointer(p) => p, _ => panic!("this should not be possible"), - } as *const c_char; + } + .cast::(); if ptr.is_null() { w.write_all(b"(null)")?; } else { - let max = precision.unwrap_or(::core::usize::MAX); + let max = precision.unwrap_or(usize::MAX); if intkind == IntKind::Long || intkind == IntKind::LongLong { // Handle wchar_t - let mut ptr = ptr as *const wchar_t; + let mut ptr = ptr.cast::(); let mut string = String::new(); - while *ptr != 0 { - let c = match char::from_u32(*ptr as _) { + while unsafe { *ptr } != 0 { + let c = match char::from_u32(unsafe { *ptr } as _) { Some(c) => c, None => { - platform::errno = EILSEQ; + platform::ERRNO.set(EILSEQ); return Err(io::last_os_error()); } }; @@ -894,7 +1077,7 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> break; } string.push(c); - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } pad(w, !left, b' ', string.len()..pad_space)?; @@ -902,40 +1085,44 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> pad(w, left, b' ', string.len()..pad_space)?; } else { let mut len = 0; - while *ptr.add(len) != 0 && len < max { + while unsafe { *ptr.add(len) } != 0 && len < max { len += 1; } pad(w, !left, b' ', len..pad_space)?; - w.write_all(slice::from_raw_parts(ptr as *const u8, len))?; + w.write_all(unsafe { slice::from_raw_parts(ptr.cast::(), len) })?; pad(w, left, b' ', len..pad_space)?; } } } - FmtKind::Char => match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { - VaArg::c_char(c) => { - pad(w, !left, b' ', 1..pad_space)?; - w.write_all(&[c as u8])?; - pad(w, left, b' ', 1..pad_space)?; - } - VaArg::wint_t(c) => { - let c = match char::from_u32(c as _) { - Some(c) => c, - None => { - platform::errno = EILSEQ; - return Err(io::last_os_error()); - } - }; - let mut buf = [0; 4]; + FmtKind::Char => { + match unsafe { varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) } { + VaArg::c_char(c) => { + pad(w, !left, b' ', 1..pad_space)?; + w.write_all(&[c as u8])?; + pad(w, left, b' ', 1..pad_space)?; + } + VaArg::wint_t(c) => { + let c = match char::from_u32(c as _) { + Some(c) => c, + None => { + platform::ERRNO.set(EILSEQ); + return Err(io::last_os_error()); + } + }; + let mut buf = [0; 4]; - pad(w, !left, b' ', 1..pad_space)?; - w.write_all(c.encode_utf8(&mut buf).as_bytes())?; - pad(w, left, b' ', 1..pad_space)?; + pad(w, !left, b' ', 1..pad_space)?; + w.write_all(c.encode_utf8(&mut buf).as_bytes())?; + pad(w, left, b' ', 1..pad_space)?; + } + _ => unreachable!("this should not be possible"), } - _ => unreachable!("this should not be possible"), - }, + } FmtKind::Pointer => { - let ptr = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let ptr = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::pointer(p) => p, _ => panic!("this should not be possible"), }; @@ -960,20 +1147,26 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> pad(w, left, b' ', len..pad_space)?; } FmtKind::GetWritten => { - let ptr = match varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) { + let ptr = match unsafe { + varargs.get(index, &mut ap, Some((arg.fmtkind, arg.intkind))) + } { VaArg::pointer(p) => p, _ => panic!("this should not be possible"), }; match intkind { - IntKind::Byte => *(ptr as *mut c_char) = w.written as c_char, - IntKind::Short => *(ptr as *mut c_short) = w.written as c_short, - IntKind::Int => *(ptr as *mut c_int) = w.written as c_int, - IntKind::Long => *(ptr as *mut c_long) = w.written as c_long, - IntKind::LongLong => *(ptr as *mut c_longlong) = w.written as c_longlong, - IntKind::IntMax => *(ptr as *mut intmax_t) = w.written as intmax_t, - IntKind::PtrDiff => *(ptr as *mut ptrdiff_t) = w.written as ptrdiff_t, - IntKind::Size => *(ptr as *mut size_t) = w.written as size_t, + IntKind::Byte => unsafe { *(ptr as *mut c_char) = w.written as c_char }, + IntKind::Short => unsafe { *(ptr as *mut c_short) = w.written as c_short }, + IntKind::Int => unsafe { *(ptr as *mut c_int) = w.written as c_int }, + IntKind::Long => unsafe { *(ptr as *mut c_long) = w.written as c_long }, + IntKind::LongLong => unsafe { + *(ptr as *mut c_longlong) = w.written as c_longlong + }, + IntKind::IntMax => unsafe { *(ptr as *mut intmax_t) = w.written as intmax_t }, + IntKind::PtrDiff => unsafe { + *(ptr as *mut ptrdiff_t) = w.written as ptrdiff_t + }, + IntKind::Size => unsafe { *(ptr as *mut size_t) = w.written as size_t }, } } } @@ -981,6 +1174,274 @@ unsafe fn inner_printf(w: W, format: *const c_char, mut ap: VaList) -> Ok(w.written as c_int) } -pub unsafe fn printf(w: W, format: *const c_char, ap: VaList) -> c_int { - inner_printf(w, format, ap).unwrap_or(-1) +/// Implementation of `printf` formatting function, generic over a `writer` +/// +/// This implementation in currently compliant over C17 specification (lacking a few one from C23) +/// and contains extensions as well. +/// +/// # The Format Specification +/// ```text +/// %[conversion-flags][field-width][precision][length-modifier] +/// ``` +/// +///
+/// ※ : This symbol means it is not implemented yet, but it is defined in the C standard +///
+/// +/// ## Conversion Flags +/// Conversion flags are flags that modify the behavior of the [conversion +/// format]. Each one can happen only once per format specifier. They are: +/// +/// - `-`: The result of the conversion is left-justified within the field (by default it is +/// right-justified). +/// - `+`: The sign of signed conversions is always prepended to the result of the conversion (by +/// default the result is preceded by minus **only** when it is negative). +/// - ` `(space): If the result of a signed conversion does not start with a sign character, or is +/// empty, space is prepended to the result. +/// - It is ignored if `+` flag is present. +/// - `#`: Alternative form of the conversion is performed. See the documentation for each +/// [conversion format] for details. +/// - `0`: For integer and floating-point number conversions, leading zeros are used to pad the +/// field instead of space characters. +/// - For integer numbers it is ignored if the precision is explicitly specified. +/// - For other conversions using this flag results in undefined behavior. +/// - It is ignored if `-` flag is present. +/// +/// ## Field Width +/// Specifies minimum field width. This makes the result to be padded (with spaces by default, with +/// zeroes if `0` conversion flag is specified) if the converted value has fewer characters than the +/// specified width. It can take three forms: +/// +/// - `N` where N is a positive integer: Specifies the field width value of `N`. +/// - `*`: The width is specified by an extra argument of type [`int`], which has to appear before +/// the argument to be converted and the [precision] (if specified with `.*`). +/// - If the value of e extra argument is negative, it is interpreted as with `-` [conversion +/// flag], i.e. left-justified result. +/// - `*P$` where P is a positive integer: The width is specified by an extra argument of type +/// [`int`], which has to appear exactly at the position specified by `P`. +/// - This is a popular extension of the C and POSIX standards. +/// - If the value of e extra argument is negative, it is interpreted as with `-` [conversion +/// flag], i.e. left-justified result. +/// +/// ## Precision +/// Specifies the precision of the conversion. +/// +/// For integer [conversion formats], this specifies the number of digits to appear in the result. +/// +/// For float point [conversion formats], this specifies the number of digits to appear after the +/// decimal-point character. +/// +/// It can take three forms: +/// +/// - `.N` where N is a positive integer: Specifies the precision value of `N`. +/// - `.*`: The precision is specified by an extra argument of type [`int`], which has to appear +/// before the argument to be converted and after the the [field width] (if specified with `*`). +/// - If the value of the extra argument is negative, it is interpreted as if the precision were +/// omitted. +/// - `.*P$` where P is a positive integer: The precision is specified by an extra argument of type +/// [`int`], which has to appear exactly at the position specified by `P`. +/// - This is an popular extension of the C and POSIX standards. +/// - If the value of e extra argument is negative, it is interpreted as if the precision were +/// omitted. +/// +/// ## Length Modifier +/// Specifies the size of the argument. In combination with the [conversion format], it specifies +/// the type of the corresponding argument. +/// +/// - `hh`: Byte size +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `h`: Short size +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `l`: Long size +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with character conversion format (`c`) +/// - Works with string conversion format (`s`) +/// - Works with written number conversion format (`n`) +/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) (C99) +/// - `ll`: Long long size +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `j`: Maximum width +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `z`: Pointer width size +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `t`: Pointer diff width +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - Works with written number conversion format (`n`) +/// - `wN` (C23 ※): Specifies that the size should be N bits width version of the supported +/// conversion format. +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - The supported values of `N` must be the same as the widths specified in `stdint.h` +/// - `wfN` (C23 ※): Specifies that the size should be the fast N bits width version of the +/// supported conversion format. +/// - Works with integer conversion formats (`d`, `i`, `o`, `x`, `X`, `b`, `B`) +/// - The supported values of `N` must be the same as the widths specified in `stdint.h` +/// - `L`: Long double size +/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) +/// - `H` (C23 ※): _Decimal32 size +/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) +/// - `D` (C23 ※): _Decimal64 size +/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) +/// - `DD` (C23 ※): _Decimal128 size +/// - Works with float conversion formats (`f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`) +/// +/// ## Conversion Format +/// Specifies the conversion format as one of the following: +/// +/// - `%`: Writes a percent symbol. The full conversion format must be `%%`. +/// - `c`: Writes as single character +/// - Without length modifier: +/// - The argument is first converted to [`unsigned char`] +/// - With `l` length modifier: +/// - The argument is first converted to a character string as if by `%ls` with a array of 2 +/// [`wchar_t`] argument. +/// - `s`: Writes a character string +/// - The argument is a pointer to the first character +/// - The [precision] specifies the maximum number of bytes to be written. If not specified, +/// writes up to the first null character found. +/// - `d` and `i`: Writes a decimal representation of a signed integer +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - `u`: Writes the decimal representation of a unsigned integer +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - `o`: Writes the octal representation of a unsigned integer. +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - The alternative representation includes a leading `0`. +/// - The types are the same as `u` +/// - `x`: Writes the hexadecimal representation of a unsigned integer with lowercase characters. +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - The alternative representation includes a leading `0x`. +/// - The types are the same as `u` +/// - `X`: Writes the hexadecimal representation of a unsigned integer with uppercase characters. +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - The alternative representation includes a leading `0X`. +/// - The types are the same as `u`. +/// - `b` | `B` (C23): Writes the binary representation of a unsigned integer. +/// - The [precision] specifies the minimal number to appear (defaults to `1`). +/// - If the precision is zero and the value to be written is also zero, the result is no +/// characters written. +/// - The alternative representation includes a leading `0b` and `0B`, respectively. +/// - The types are the same as `u`. +/// - `f` | `F`: Writes the decimal representation of a float point number. +/// - The [precision] specifies the exact number of digits to appear after the decimal point +/// character (defaults to `6`). +/// - The alternative representation, the decimal point character is written even if no digits +/// follow it. +/// - `e` | `E`: Writes the float point number with the decimal exponential notation (\[-\]d.ddd +/// **e**±dd | \[-\]d.ddd **E**±dd) +/// - The [precision] specifies the exact number of digits to appear after the decimal point +/// character (defaults to `6`). +/// - The exponent contains at least two digits, more digits are used only if necessary. +/// - If the value is ​zero, the exponent is also ​zero​. +/// - The alternative representation: decimal point character is written even if no digits follow +/// it. +/// - `a` | `A`: Writes the float point number with the hexadecimal exponential notation (\[-\] +/// **0x**h.hhh **p**±d | \[-\] **0X**h.hhh **P**±d) +/// - The [precision] specifies the exact number of digits to appear after the hexadecimal point +/// character (defaults to `6`). +/// - If the value is ​zero, the exponent is also ​zero​. +/// - The alternative representation: decimal point character is written even if no digits follow +/// it. +/// - `g` | `G`: Writes the float point number to decimal or decimal exponent notation depending on +/// the value and the [precision]. +/// - Let `P` equal the precision if nonzero, `6` if the precision is not specified, or `1` if the +/// precision is `​0`​. Then, if a conversion with style `E` would have an exponent of `X`: +/// - If `P > X ≥ −4`, the conversion is with the format `f` and precision `P − 1 − X`. +/// - Otherwise, the conversion is with the format `e` or `E` and precision `P − 1`. +/// - Unless alternative representation is requested, the trailing zeros are removed. Also the +/// decimal point character is removed if no fractional part is left. +/// - `n`: Writes the number of characters written in the call into the argument pointer +/// - It can not contain any [conversion flag], [field width], or [precision]. +/// - `p`: Writes an implementation defined character sequence defining a pointer. +/// +/// ### Types +/// The types expected by the format string can change with the [length modifier]. +/// +/// For the `c`: +/// - Without length modifier: [`int`] +/// - With `l` length modifier: [`wint_t`] +/// +/// For the `s`: +/// - Without length modifier: pointer to [`char`] (`char*`, `const char*`) +/// - With `l` length modifier: pointer to [`wchar_t`] (`wchar_t*`, `const wchar_t*`) +/// +/// For the `d` and `i`: +/// - Without length modifier: [`int`] +/// - With `hh` length modifier: [`signed char`] +/// - With `h` length modifier: [`short`] +/// - With `l` length modifier: [`long`] +/// - With `ll` length modifier: [`long long`] +/// - With `j` length modifier: [`intmax_t`] +/// - With `z` length modifier: [`ssize_t`] +/// - With `t` length modifier: [`ptrdiff_t`] +/// +/// For the `u`, `o`, `x`, `X`, `b`, `B`: +/// - Without length modifier: [`unsigned int`] +/// - With `hh` length modifier: [`unsigned char`] +/// - With `h` length modifier: [`unsigned short`] +/// - With `l` length modifier: [`unsigned long`] +/// - With `ll` length modifier: [`unsigned long long`] +/// - With `j` length modifier: [`uintmax_t`] +/// - With `z` length modifier: [`size_t`] +/// - With `t` length modifier: [`unsigned ptrdiff_t`] +/// +/// For the `f`, `F`, `e`, `E`, `a`, `A`, `g`, `G`: +/// - Without length modifier: [`double`] +/// - With `l` length modifier: [`double`] +/// - With `L` length modifier: `long double` +/// - With `H` length modifier (C23 ※): `_Decimal32` +/// - With `D` length modifier (C23 ※): `_Decimal64` +/// - With `DD` length modifier (C23 ※): `_Decimal128` +/// +/// For the `n` +/// - Without length modifier: pointer to [`int`] (`int*`) +/// - With `hh` length modifier: pointer to [`signed char`] (`signed char*`) +/// - With `h` length modifier: pointer to [`short`] (`short*`) +/// - With `l` length modifier: pointer to [`long`] (`long*`) +/// - With `ll` length modifier: pointer to [`long long`] (`long long*`) +/// - With `j` length modifier: pointer to [`intmax_t`] (`intmax_t*`) +/// - With `z` length modifier: pointer to [`ssize_t`] (`ssize_t*`) +/// - With `t` length modifier: pointer to [`ptrdiff_t`] (`ptrdiff_t*`) +/// +/// For the `p`, it must always be a pointer to [`void`] (`void*` | `const void*`) +/// +/// [precision]: #precision +/// [field width]: #field-width +/// [length modifier]: #length-modifier +/// [conversion format]: #conversion-format +/// [`int`]: c_int +/// [`unsigned char`]: c_uchar +/// [`unsigned short`]: c_ushort +/// [`unsigned int`]: c_uint +/// [`unsigned long`]: c_ulong +/// [`unsigned long long`]: c_ulonglong +/// [`unsigned ptrdiff_t`]: ptrdiff_t +/// [`wchar_t`]: wchar_t +/// [`char`]: c_char +/// [`signed char`]: c_schar +/// [`short`]: c_short +/// [`long`]: c_long +/// [`long long`]: c_longlong +/// [`double`]: c_double +/// [`long double`]: c_longdouble +/// [`void`]: c_void +/// +/// # Safety +/// Behavior is undefined if any of the following conditions are violated: +/// - `ap` must follow the safety contract of variable arguments of C. +pub unsafe fn printf(w: impl Write, format: CStr, ap: VaList) -> c_int { + unsafe { inner_printf::(w, format, ap).unwrap_or(-1) } } diff --git a/src/header/stdio/reader.rs b/src/header/stdio/reader.rs new file mode 100644 index 0000000000..aaf6ea05e2 --- /dev/null +++ b/src/header/stdio/reader.rs @@ -0,0 +1,180 @@ +use super::{FILE, SEEK_SET, fseek_locked, ftell_locked}; +use crate::{ + c_str::{CStr, Kind, NulStr, Thin, WStr, Wide}, + header::{ + errno::EILSEQ, + stdlib::MB_CUR_MAX, + wchar::{get_char_encoded_length, mbrtowc}, + wctype::WEOF, + }, + io::Read, + platform::{ + ERRNO, + types::{c_char, off_t, wchar_t, wint_t}, + }, +}; +use core::{ + iter::Iterator, + marker::PhantomData, + ptr::{self}, +}; + +pub struct BufferReader<'a, T: Kind> { + buf: NulStr<'a, T>, +} + +impl<'a> From> for BufferReader<'a, Wide> { + fn from(buff: WStr<'a>) -> Self { + BufferReader { buf: buff } + } +} + +impl<'a> From> for BufferReader<'a, Thin> { + fn from(buff: CStr<'a>) -> Self { + BufferReader { buf: buff } + } +} + +impl<'a, T: Kind> Iterator for BufferReader<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + self.buf.split_first().map(|(c, r)| { + self.buf = r; + Ok(c) + }) + } +} + +pub struct FileReader<'a, T: Kind> { + f: &'a mut FILE, + position: off_t, + phantom: PhantomData, +} + +impl<'a, T: Kind> FileReader<'a, T> { + // Gets the wchar at the current position + #[inline] + fn get_curret_char(&mut self) -> Result, i32> { + if T::IS_THIN_NOT_WIDE { + let mut buf: [u8; 1] = [0]; + match self.f.read(&mut buf) { + Ok(0) => Ok(None), + Ok(n) => Ok(Some((T::Char::from(buf[0]), n))), + Err(_) => Err(-1), + } + } else { + let buf = &mut [0; MB_CUR_MAX as usize]; + let mut encoded_length = 0; + let mut bytes_read = 0; + + loop { + match self.f.read(&mut buf[bytes_read..bytes_read + 1]) { + Ok(0) => return Ok(None), + Ok(_) => {} + Err(_) => return Err(-1), + } + + bytes_read += 1; + + if bytes_read == 1 { + encoded_length = if let Some(el) = get_char_encoded_length(buf[0]) { + el + } else { + ERRNO.set(EILSEQ); + return Self::get_char_from_wint(WEOF).map(|c| Some((c, 0))); + }; + } + + if bytes_read >= encoded_length { + break; + } + } + + let mut wc: wchar_t = 0; + unsafe { + mbrtowc( + &raw mut wc, + buf.as_ptr().cast::(), + encoded_length, + ptr::null_mut(), + ); + } + + Self::get_char_from_wint(wc as wint_t).map(|c| Some((c, encoded_length))) + } + } + + fn get_char_from_wint(wc: wint_t) -> Result { + if let Some(wc_char) = T::chars_from_bytes(&wc.to_be_bytes()) + && wc_char.len() == 1 + { + Ok(wc_char[0]) + } else { + Err(-1) + } + } +} + +impl<'a, T: Kind> Iterator for FileReader<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + unsafe { fseek_locked(self.f, self.position, SEEK_SET) }; + + match self.get_curret_char() { + Ok(Some((wc, encoded_length))) => { + unsafe { fseek_locked(self.f, self.position, SEEK_SET) }; + self.position += encoded_length as off_t; + Some(Ok(wc)) + } + Ok(None) => None, + Err(e) => Some(Err(e)), + } + } +} + +impl<'a, T: Kind> From<&'a mut FILE> for FileReader<'a, T> { + fn from(f: &'a mut FILE) -> Self { + let position = unsafe { ftell_locked(f) } as off_t; + FileReader { + f, + position, + phantom: PhantomData::, + } + } +} + +pub enum Reader<'a, T: Kind> { + FILE(FileReader<'a, T>, PhantomData), + BUFFER(BufferReader<'a, T>), +} + +impl<'a, T: Kind> Iterator for Reader<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + match self { + Self::FILE(r, _) => r.next(), + Self::BUFFER(r) => r.next(), + } + } +} + +impl<'a, T: Kind> From<&'a mut FILE> for Reader<'a, T> { + fn from(f: &'a mut FILE) -> Self { + Self::FILE(f.into(), PhantomData::) + } +} + +impl<'a> From> for Reader<'a, Wide> { + fn from(buff: WStr<'a>) -> Self { + Self::BUFFER(buff.into()) + } +} + +impl<'a> From> for Reader<'a, Thin> { + fn from(buff: CStr<'a>) -> Self { + Self::BUFFER(buff.into()) + } +} diff --git a/src/header/stdio/scanf.rs b/src/header/stdio/scanf.rs index 8a88a31630..1adbe6e4e0 100644 --- a/src/header/stdio/scanf.rs +++ b/src/header/stdio/scanf.rs @@ -1,51 +1,56 @@ -use super::lookaheadreader::LookAheadReader; -use crate::platform::types::*; +use super::reader::Reader; +use crate::{ + c_str::Kind, + header::stdio::printf::IntKind, + platform::types::{ + c_char, c_double, c_float, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong, + c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, uintmax_t, wchar_t, + }, +}; use alloc::{string::String, vec::Vec}; -use core::ffi::VaList as va_list; +use core::{ffi::VaList as va_list, iter::Peekable}; #[derive(PartialEq, Eq)] -enum IntKind { - Byte, - Short, - Int, - Long, - LongLong, - IntMax, - PtrDiff, - Size, +enum CharKind { + Ascii, + Wide, } -/// Helper function for progressing a C string -unsafe fn next_byte(string: &mut *const c_char) -> Result { - let c = **string as u8; - *string = string.offset(1); - if c == 0 { - Err(-1) +fn next_char(lar: &mut Peekable>) -> Result { + if let Some(c) = lar.next().transpose()? { + char::try_from(c.into()).map_err(|_| -1) } else { - Ok(c) + Ok('\0') } } -unsafe fn inner_scanf( - mut r: LookAheadReader, - mut format: *const c_char, +macro_rules! wc_as_char { + ($c:ident) => { + char::try_from($c.into()).map_err(|_| -1)? + }; +} + +pub unsafe fn inner_scanf( + mut r: Reader, + format: Reader, mut ap: va_list, ) -> Result { let mut matched = 0; - let mut byte = 0; + let mut character: char = '\0'; let mut skip_read = false; let mut count = 0; + let mut format = format.peekable(); macro_rules! read { () => {{ - match r.lookahead1() { - Ok(None) => false, - Ok(Some(b)) => { - byte = b; + match r.next() { + None => false, + Some(Ok(b)) => { + character = wc_as_char!(b); count += 1; true } - Err(x) => return Err(x), + Some(Err(x)) => return Err(x), } }}; } @@ -71,38 +76,37 @@ unsafe fn inner_scanf( } } - while *format != 0 { - let mut c = next_byte(&mut format)?; + while format.peek().is_some() { + let mut c = next_char(&mut format)?; - if c == b' ' { + if c == ' ' { maybe_read!(noreset); - while (byte as char).is_whitespace() { + while (character).is_whitespace() { if !read!() { return Ok(matched); } } skip_read = true; - } else if c != b'%' { + } else if c != '%' { maybe_read!(); - if c != byte { + if c != character { return Ok(matched); } - r.commit(); } else { - c = next_byte(&mut format)?; + c = next_char(&mut format)?; let mut ignore = false; - if c == b'*' { + if c == '*' { ignore = true; - c = next_byte(&mut format)?; + c = next_char(&mut format)?; } let mut width = String::new(); - while c >= b'0' && c <= b'9' { - width.push(c as char); - c = next_byte(&mut format)?; + while c.is_ascii_digit() { + width.push(c); + c = next_char(&mut format)?; } let mut width = if width.is_empty() { None @@ -118,65 +122,72 @@ unsafe fn inner_scanf( let mut eof = false; let mut kind = IntKind::Int; + let mut c_kind = CharKind::Ascii; loop { - kind = match c { - b'h' => { + match c { + 'h' => { if kind == IntKind::Short || kind == IntKind::Byte { - IntKind::Byte + kind = IntKind::Byte; } else { - IntKind::Short + kind = IntKind::Short; } } - b'j' => IntKind::IntMax, - b'l' => { + 'j' => kind = IntKind::IntMax, + 'l' => { if kind == IntKind::Long || kind == IntKind::LongLong { - IntKind::LongLong + kind = IntKind::LongLong; } else { - IntKind::Long + kind = IntKind::Long; } } - b'q' | b'L' => IntKind::LongLong, - b't' => IntKind::PtrDiff, - b'z' => IntKind::Size, + 'q' | 'L' => kind = IntKind::LongLong, + 't' => kind = IntKind::PtrDiff, + 'z' => kind = IntKind::Size, + // If kind is Long, means we found a 'l' before finding 'c' or 's'. In this + // case the format corresponds to a wide char/string + 'c' | 's' if kind == IntKind::Long && !T::IS_THIN_NOT_WIDE => { + c_kind = CharKind::Wide; + break; + } _ => break, - }; + } - c = next_byte(&mut format)?; + c = next_char(&mut format)?; } - if c != b'n' { + if c != 'n' { maybe_read!(noreset); } match c { - b'%' => { - while (byte as char).is_whitespace() { + '%' => { + while (character).is_whitespace() { if !read!() { return Ok(matched); } } - if byte != b'%' { + if character != '%' { return Err(matched); } else if !read!() { return Ok(matched); } } - b'd' | b'i' | b'o' | b'u' | b'x' | b'X' | b'f' | b'e' | b'g' | b'E' | b'a' - | b'p' => { - while (byte as char).is_whitespace() { + + 'd' | 'i' | 'o' | 'u' | 'x' | 'X' | 'f' | 'e' | 'g' | 'E' | 'a' | 'p' => { + while character.is_whitespace() { if !read!() { return Ok(matched); } } - let pointer = c == b'p'; + let pointer = c == 'p'; // Pointers aren't automatic, but we do want to parse "0x" - let auto = c == b'i' || pointer; - let float = c == b'f' || c == b'e' || c == b'g' || c == b'E' || c == b'a'; + let auto = c == 'i' || pointer; + let float = c == 'f' || c == 'e' || c == 'g' || c == 'E' || c == 'a'; let mut radix = match c { - b'o' => 8, - b'x' | b'X' | b'p' => 16, + 'o' => 8, + 'x' | 'X' | 'p' => 16, _ => 10, }; @@ -184,16 +195,16 @@ unsafe fn inner_scanf( let mut dot = false; while width.map(|w| w > 0).unwrap_or(true) - && ((byte >= b'0' && byte <= b'7') - || (radix >= 10 && (byte >= b'8' && byte <= b'9')) - || (float && !dot && byte == b'.') + && (('0'..='7').contains(&character) + || (radix >= 10 && ('8'..='9').contains(&character)) + || (float && !dot && character == '.') || (radix == 16 - && ((byte >= b'a' && byte <= b'f') - || (byte >= b'A' && byte <= b'F')))) + && (('a'..='f').contains(&character) + || ('A'..='F').contains(&character)))) { if auto && n.is_empty() - && byte == b'0' + && character == '0' && width.map(|w| w > 0).unwrap_or(true) { if !pointer { @@ -204,7 +215,7 @@ unsafe fn inner_scanf( return Ok(matched); } if width.map(|w| w > 0).unwrap_or(true) - && (byte == b'x' || byte == b'X') + && (character == 'x' || character == 'X') { radix = 16; width = width.map(|w| w - 1); @@ -214,15 +225,14 @@ unsafe fn inner_scanf( } continue; } - if byte == b'.' { + if character == '.' { // Don't allow another dot dot = true; } - n.push(byte as char); - r.commit(); + n.push(character); width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { - return Ok(matched); + break; } } @@ -234,18 +244,18 @@ unsafe fn inner_scanf( n.parse::<$type>().map_err(|_| 0)? }; if !ignore { - *ap.arg::<*mut $type>() = n; + unsafe { *ap.next_arg::<*mut $type>() = n }; matched += 1; } }}; (c_double) => { - parse_type!(noformat c_double); + parse_type!(noformat c_double) }; (c_float) => { - parse_type!(noformat c_float); + parse_type!(noformat c_float) }; ($type:ident) => { - parse_type!($type, $type); + parse_type!($type, $type) }; ($type:ident, $final:ty) => {{ let n = if n.is_empty() { @@ -254,7 +264,7 @@ unsafe fn inner_scanf( $type::from_str_radix(&n, radix).map_err(|_| 0)? }; if !ignore { - *ap.arg::<*mut $final>() = n as $final; + unsafe { *ap.next_arg::<*mut $final>() = n as $final }; matched += 1; } }}; @@ -266,10 +276,10 @@ unsafe fn inner_scanf( } else { parse_type!(c_float); } - } else if c == b'p' { + } else if c == 'p' { parse_type!(size_t, *mut c_void); } else { - let unsigned = c == b'o' || c == b'u' || c == b'x' || c == b'X'; + let unsigned = c == 'o' || c == 'u' || c == 'x' || c == 'X'; match kind { IntKind::Byte => { @@ -325,99 +335,140 @@ unsafe fn inner_scanf( } } } - b's' => { - while (byte as char).is_whitespace() { - if !read!() { - return Ok(matched); - } + + 's' => { + macro_rules! parse_string_type { + ($type:ident) => { + while character.is_whitespace() { + if !read!() { + return Ok(matched); + } + } + + let mut ptr: Option<*mut $type> = + if ignore { None } else { Some(ap.next_arg()) }; + + while width.map(|w| w > 0).unwrap_or(true) && !character.is_whitespace() + { + if let Some(ref mut ptr) = ptr { + **ptr = character as $type; + *ptr = ptr.offset(1); + } + width = width.map(|w| w - 1); + if width.map(|w| w > 0).unwrap_or(true) && !read!() { + eof = true; + break; + } + } + + // If we read the width, we end up in the last character of was + // intended to be read, so we advance one + if let Some(0) = width { + read!(); + } + + if let Some(ptr) = ptr { + *ptr = 0; + matched += 1; + } + }; } - let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) }; - - while width.map(|w| w > 0).unwrap_or(true) && !(byte as char).is_whitespace() { - if let Some(ref mut ptr) = ptr { - **ptr = byte as c_char; - *ptr = ptr.offset(1); + if c_kind == CharKind::Ascii { + unsafe { + parse_string_type!(c_char); } - width = width.map(|w| w - 1); - if width.map(|w| w > 0).unwrap_or(true) && !read!() { - eof = true; - break; + } else { + unsafe { + parse_string_type!(wchar_t); } } - - if let Some(ptr) = ptr { - *ptr = 0; - matched += 1; - r.commit(); - } } - b'c' => { - let ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) }; - for i in 0..width.unwrap_or(1) { - if let Some(ptr) = ptr { - *ptr.add(i) = byte as c_char; - } - width = width.map(|w| w - 1); - if width.map(|w| w > 0).unwrap_or(true) && !read!() { - eof = true; - break; - } + 'c' => { + macro_rules! parse_char_type { + ($type:ident) => { + let ptr: Option<*mut $type> = if ignore { + None + } else { + Some(unsafe { ap.next_arg() }) + }; + + for i in 0..width.unwrap_or(1) { + if let Some(ptr) = ptr { + unsafe { *ptr.add(i) = character as $type }; + } + width = width.map(|w| w - 1); + if width.map(|w| w > 0).unwrap_or(true) && !read!() { + eof = true; + break; + } + } + + if ptr.is_some() { + matched += 1; + } + }; } - if ptr.is_some() { - matched += 1; - r.commit(); + if c_kind == CharKind::Ascii { + parse_char_type!(c_char); + } else { + parse_char_type!(wchar_t); } } - b'[' => { - c = next_byte(&mut format)?; + + '[' => { + c = next_char(&mut format)?; let mut matches = Vec::new(); - let invert = if c == b'^' { - c = next_byte(&mut format)?; + let invert = if c == '^' { + c = next_char(&mut format)?; true } else { false }; - let mut prev; + let mut prev: u32; loop { matches.push(c); - prev = c; - c = next_byte(&mut format)?; - if c == b'-' { - if prev == b']' { + prev = c.into(); + c = next_char(&mut format)?; + if c == '-' { + if prev as u8 == b']' { continue; } - c = next_byte(&mut format)?; - if c == b']' { - matches.push(b'-'); + c = next_char(&mut format)?; + if c == ']' { + matches.push('-'); break; } prev += 1; - while prev < c { - matches.push(prev); + while prev < c.into() { + matches.push(char::try_from(prev).map_err(|_| -1)?); prev += 1; } - } else if c == b']' { + } else if c == ']' { break; } } - let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) }; + let mut ptr: Option<*mut c_char> = if ignore { + None + } else { + Some(unsafe { ap.next_arg() }) + }; // While we haven't used up all the width, and it matches let mut data_stored = false; - while width.map(|w| w > 0).unwrap_or(true) && !invert == matches.contains(&byte) + while width.map(|w| w > 0).unwrap_or(true) + && invert != matches.contains(&character) { if let Some(ref mut ptr) = ptr { - **ptr = byte as c_char; - *ptr = ptr.offset(1); + unsafe { **ptr = character as c_char }; + *ptr = unsafe { ptr.offset(1) }; data_stored = true; } - r.commit(); // Decrease the width, and read a new character unless the width is 0 width = width.map(|w| w - 1); if width.map(|w| w > 0).unwrap_or(true) && !read!() { @@ -429,13 +480,13 @@ unsafe fn inner_scanf( } if data_stored { - *ptr.unwrap() = 0; + unsafe { *ptr.unwrap() = 0 }; matched += 1; } } - b'n' => { + 'n' => { if !ignore { - *ap.arg::<*mut c_int>() = count as c_int; + unsafe { *ap.next_arg::<*mut c_int>() = count as c_int }; } } _ => return Err(-1), @@ -445,7 +496,7 @@ unsafe fn inner_scanf( return Ok(matched); } - if width != Some(0) && c != b'n' { + if width != Some(0) && c != 'n' { // It didn't hit the width, so an extra character was read and matched. // But this character did not match so let's reuse it. skip_read = true; @@ -455,8 +506,8 @@ unsafe fn inner_scanf( Ok(matched) } -pub unsafe fn scanf(r: LookAheadReader, format: *const c_char, ap: va_list) -> c_int { - match inner_scanf(r, format, ap) { +pub unsafe fn scanf(r: Reader, format: Reader, ap: va_list) -> c_int { + match unsafe { inner_scanf(r, format, ap) } { Ok(n) => n, Err(n) => n, } diff --git a/src/header/stdlib/cbindgen.toml b/src/header/stdlib/cbindgen.toml index 7e9b9a8698..e511194878 100644 --- a/src/header/stdlib/cbindgen.toml +++ b/src/header/stdlib/cbindgen.toml @@ -1,9 +1,24 @@ -sys_includes = ["stddef.h", "alloca.h"] +sys_includes = ["stddef.h", "alloca.h", "wchar.h", "features.h"] include_guard = "_RELIBC_STDLIB_H" -trailer = "#include " +trailer = """ +#ifndef _RELIBC_STDLIB_EXTRA_H +#define _RELIBC_STDLIB_EXTRA_H + +#ifdef __cplusplus +extern "C" { +#endif + +long double strtold(const char *nptr, char **endptr); + +#ifdef __cplusplus +} +#endif + +#endif +""" language = "C" style = "Type" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/stdlib/mod.rs b/src/header/stdlib/mod.rs index c5fd5d1be1..36b4958c77 100644 --- a/src/header/stdlib/mod.rs +++ b/src/header/stdlib/mod.rs @@ -1,56 +1,88 @@ -//! stdlib implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/stdlib.h.html +//! `stdlib.h` implementation. +//! +//! See . -use core::{convert::TryFrom, intrinsics, iter, mem, ptr, slice}; +use core::{convert::TryFrom, iter, mem, ptr, slice}; use rand::{ - distributions::{Alphanumeric, Distribution, Uniform}, - prng::XorShiftRng, - rngs::JitterRng, - Rng, SeedableRng, + RngExt, SeedableRng, + distr::{Alphanumeric, Distribution, Uniform}, }; +use rand_jitter::JitterRng; +use rand_xorshift::XorShiftRng; use crate::{ c_str::CStr, + error::{Errno, ResultExt}, fs::File, header::{ ctype, errno::{self, *}, fcntl::*, limits, + stdio::flush_io_streams, string::*, + sys_ioctl::*, time::constants::CLOCK_MONOTONIC, - unistd::{self, sysconf, _SC_PAGESIZE}, + unistd::{self, _SC_PAGESIZE, sysconf}, wchar::*, }, - platform::{self, types::*, Pal, Sys}, + ld_so, + out::Out, + platform::{ + self, Pal, Sys, + types::{ + c_char, c_double, c_float, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong, + c_ushort, c_void, size_t, ssize_t, uintptr_t, wchar_t, + }, + }, + raw_cell::RawCell, + sync::Once, }; mod rand48; mod random; mod sort; +/// See . pub const EXIT_FAILURE: c_int = 1; +/// See . pub const EXIT_SUCCESS: c_int = 0; +/// See . pub const RAND_MAX: c_int = 2_147_483_647; +/// See . //Maximum number of bytes in a multibyte character for the current locale pub const MB_CUR_MAX: c_int = 4; +/// Actually specified for `limits.h`? //Maximum number of bytes in a multibyte characters for any locale pub const MB_LEN_MAX: c_int = 4; -static mut ATEXIT_FUNCS: [Option; 32] = [None; 32]; -static mut L64A_BUFFER: [c_char; 7] = [0; 7]; // up to 6 digits plus null terminator -static mut RNG: Option = None; - -lazy_static! { - static ref RNG_SAMPLER: Uniform = Uniform::new_inclusive(0, RAND_MAX); +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn getprogname() -> *const c_char { + unsafe { platform::program_invocation_short_name } } -#[no_mangle] -pub extern "C" fn _Exit(status: c_int) { +static ATEXIT_FUNCS: RawCell<[Option; 32]> = RawCell::new([None; 32]); +static AT_QUICK_EXIT_FUNCS: RawCell<[Option; 32]> = RawCell::new([None; 32]); +static L64A_BUFFER: RawCell<[c_char; 7]> = RawCell::new([0; 7]); // up to 6 digits plus null terminator +static mut RNG: Option = None; + +// TODO: This could be const fn, but the trait system won't allow that. +static RNG_SAMPLER: Once> = Once::new(); + +fn rng_sampler() -> &'static Uniform { + RNG_SAMPLER.call_once(|| Uniform::new_inclusive(0, RAND_MAX).expect("within bounds")) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn _Exit(status: c_int) -> ! { unistd::_exit(status); } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn a64l(s: *const c_char) -> c_long { // Early return upon null pointer argument if s.is_null() { @@ -62,7 +94,7 @@ pub unsafe extern "C" fn a64l(s: *const c_char) -> c_long { // Handle up to 6 input characters (excl. null terminator) for i in 0..6 { - let digit_char = *s.offset(i); + let digit_char = unsafe { *s.offset(i) }; let digit_value = match digit_char { 0 => break, // Null terminator encountered @@ -81,40 +113,57 @@ pub unsafe extern "C" fn a64l(s: *const c_char) -> c_long { _ => return 0, // Early return for anything else }; - l |= i32::from(digit_value) << 6 * i; + l |= i32::from(digit_value) << (6 * i); } c_long::from(l) } -#[no_mangle] -pub unsafe extern "C" fn abort() { - eprintln!("abort() called"); - intrinsics::abort(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn abort() -> ! { + log::error!("Abort"); + crate::header::signal::raise(crate::header::signal::sys::SIGABRT as c_int); + _Exit(128 + crate::header::signal::sys::SIGABRT as c_int); } -#[no_mangle] +#[cfg(not(target_pointer_width = "64"))] +#[unsafe(no_mangle)] +static __stack_chk_guard: uintptr_t = 0x19fcadfe; + +#[cfg(target_pointer_width = "64")] +#[unsafe(no_mangle)] +static __stack_chk_guard: uintptr_t = 0xd048c37519fcadfe; + +#[unsafe(no_mangle)] +unsafe extern "C" fn __stack_chk_fail() -> ! { + unsafe { abort() }; +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn abs(i: c_int) -> c_int { i.abs() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn aligned_alloc(alignment: size_t, size: size_t) -> *mut c_void { - if size % alignment == 0 { - /* The size-is-multiple-of-alignment requirement is the only - * difference between aligned_alloc() and memalign(). */ - memalign(alignment, size) - } else { - platform::errno = EINVAL; - ptr::null_mut() + if alignment == 0 || !size.is_multiple_of(alignment) { + platform::ERRNO.set(EINVAL); + return ptr::null_mut(); } + let mut pointer = ptr::null_mut(); + unsafe { posix_memalign(ptr::from_mut(&mut pointer), alignment, size) }; + pointer } -#[no_mangle] -pub unsafe extern "C" fn atexit(func: Option) -> c_int { - for i in 0..ATEXIT_FUNCS.len() { - if ATEXIT_FUNCS[i] == None { - ATEXIT_FUNCS[i] = func; +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn at_quick_exit(func: Option) -> c_int { + for i in 0..unsafe { AT_QUICK_EXIT_FUNCS.unsafe_ref().len() } { + if unsafe { AT_QUICK_EXIT_FUNCS.unsafe_ref() }[i].is_none() { + (unsafe { AT_QUICK_EXIT_FUNCS.unsafe_mut() })[i] = func; return 0; } } @@ -122,69 +171,81 @@ pub unsafe extern "C" fn atexit(func: Option) -> c_int { 1 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atexit(func: Option) -> c_int { + for i in 0..unsafe { ATEXIT_FUNCS.unsafe_ref().len() } { + if unsafe { ATEXIT_FUNCS.unsafe_ref() }[i].is_none() { + (unsafe { ATEXIT_FUNCS.unsafe_mut() })[i] = func; + return 0; + } + } + + 1 +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn atof(s: *const c_char) -> c_double { - strtod(s, ptr::null_mut()) + unsafe { strtod(s, ptr::null_mut()) } } macro_rules! dec_num_from_ascii { - ($s:expr, $t:ty) => { - unsafe { - let mut s = $s; - // Iterate past whitespace - while ctype::isspace(*s as c_int) != 0 { - s = s.offset(1); - } - - // Find out if there is a - sign - let neg_sign = match *s { - 0x2d => { - s = s.offset(1); - true - } - // '+' increment s and continue parsing - 0x2b => { - s = s.offset(1); - false - } - _ => false, - }; - - let mut n: $t = 0; - while ctype::isdigit(*s as c_int) != 0 { - n = 10 * n - (*s as $t - 0x30); - s = s.offset(1); - } - - if neg_sign { - n - } else { - -n - } + ($s:expr, $t:ty) => {{ + let mut s = $s; + // Iterate past whitespace + while ctype::isspace(c_int::from(unsafe { *s })) != 0 { + s = unsafe { s.offset(1) }; } - }; + + // Find out if there is a - sign + let neg_sign = match unsafe { *s } { + 0x2d => { + s = unsafe { s.offset(1) }; + true + } + // '+' increment s and continue parsing + 0x2b => { + s = unsafe { s.offset(1) }; + false + } + _ => false, + }; + + let mut n: $t = 0; + while ctype::isdigit(c_int::from(unsafe { *s })) != 0 { + n = 10 * n - (unsafe { *s } as $t - 0x30); + s = unsafe { s.offset(1) }; + } + + if neg_sign { n } else { -n } + }}; } -#[no_mangle] -pub extern "C" fn atoi(s: *const c_char) -> c_int { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atoi(s: *const c_char) -> c_int { dec_num_from_ascii!(s, c_int) } -#[no_mangle] -pub extern "C" fn atol(s: *const c_char) -> c_long { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atol(s: *const c_char) -> c_long { dec_num_from_ascii!(s, c_long) } -#[no_mangle] -pub extern "C" fn atoll(s: *const c_char) -> c_longlong { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn atoll(s: *const c_char) -> c_longlong { dec_num_from_ascii!(s, c_longlong) } unsafe extern "C" fn void_cmp(a: *const c_void, b: *const c_void) -> c_int { - *(a as *const i32) - *(b as *const i32) as c_int + (unsafe { *(a.cast::()) }) - unsafe { *(b.cast::()) } as c_int } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn bsearch( key: *const c_void, base: *const c_void, @@ -197,9 +258,9 @@ pub unsafe extern "C" fn bsearch( let cmp_fn = compar.unwrap_or(void_cmp); while len > 0 { let med = (start as size_t + (len >> 1) * width) as *const c_void; - let diff = cmp_fn(key, med); + let diff = unsafe { cmp_fn(key, med) }; if diff == 0 { - return med as *mut c_void; + return med.cast_mut(); } else if diff > 0 { start = (med as usize + width) as *const c_void; len -= 1; @@ -209,34 +270,37 @@ pub unsafe extern "C" fn bsearch( ptr::null_mut() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn calloc(nelem: size_t, elsize: size_t) -> *mut c_void { //Handle possible integer overflow in size calculation match nelem.checked_mul(elsize) { Some(size) => { /* If allocation fails here, errno setting will be handled * by malloc() */ - let ptr = malloc(size); + let ptr = unsafe { malloc(size) }; if !ptr.is_null() { - intrinsics::write_bytes(ptr as *mut u8, 0, size); + unsafe { ptr.write_bytes(0, size) }; } ptr } None => { // For overflowing multiplication, we have to set errno here - platform::errno = ENOMEM; + platform::ERRNO.set(ENOMEM); ptr::null_mut() } } } +/// See . #[repr(C)] pub struct div_t { quot: c_int, rem: c_int, } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn div(numer: c_int, denom: c_int) -> div_t { div_t { quot: numer / denom, @@ -244,13 +308,26 @@ pub extern "C" fn div(numer: c_int, denom: c_int) -> div_t { } } -#[no_mangle] -pub unsafe extern "C" fn drand48() -> c_double { - let new_xsubi_value = rand48::generator_step(&mut rand48::DEFAULT_XSUBI); - rand48::f64_from_x(new_xsubi_value) +/// See . +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] +pub extern "C" fn drand48() -> c_double { + let params = rand48::params(); + let mut xsubi = rand48::xsubi_lock(); + *xsubi = params.step(*xsubi); + xsubi.get_f64() } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `ecvt()` function was marked as legacy in the Open Group Base +/// Specifications Issue 6, and the function was removed in Issue 7. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn ecvt( value: c_double, ndigit: c_int, @@ -260,44 +337,63 @@ pub extern "C" fn ecvt( unimplemented!(); } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `xsubi` is convertible to a +/// `&mut [c_ushort; 3]`. +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] pub unsafe extern "C" fn erand48(xsubi: *mut c_ushort) -> c_double { - let new_xsubi_value = rand48::generator_step(&mut *(xsubi as *mut [c_ushort; 3])); - rand48::f64_from_x(new_xsubi_value) + let params = rand48::params(); + let xsubi_mut: &mut [c_ushort; 3] = + unsafe { slice::from_raw_parts_mut(xsubi, 3).try_into().unwrap() }; + let new_xsubi_value = params.step(xsubi_mut.into()); + *xsubi_mut = new_xsubi_value.into(); + new_xsubi_value.get_f64() } -#[no_mangle] -pub unsafe extern "C" fn exit(status: c_int) { - extern "C" { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn exit(status: c_int) -> ! { + unsafe extern "C" { static __fini_array_start: extern "C" fn(); static __fini_array_end: extern "C" fn(); - - fn pthread_terminate(); - fn _fini(); } - for i in (0..ATEXIT_FUNCS.len()).rev() { - if let Some(func) = ATEXIT_FUNCS[i] { + for i in (0..unsafe { ATEXIT_FUNCS.unsafe_ref().len() }).rev() { + if let Some(func) = unsafe { ATEXIT_FUNCS.unsafe_ref() }[i] { (func)(); } } // Look for the neighbor functions in memory until the end - let mut f = &__fini_array_end as *const _; + let mut f = unsafe { &__fini_array_end } as *const _; #[allow(clippy::op_ref)] - while f > &__fini_array_start { - f = f.offset(-1); - (*f)(); + while f > &raw const __fini_array_start { + f = unsafe { f.offset(-1) }; + (unsafe { *f })(); } - _fini(); + unsafe { ld_so::fini() }; - pthread_terminate(); + unsafe { crate::pthread::terminate_from_main_thread() }; + + unsafe { flush_io_streams() }; Sys::exit(status); } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `fcvt()` function was marked as legacy in the Open Group Base +/// Specifications Issue 6, and the function was removed in Issue 7. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn fcvt( value: c_double, ndigit: c_int, @@ -307,80 +403,134 @@ pub extern "C" fn fcvt( unimplemented!(); } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn free(ptr: *mut c_void) { - platform::free(ptr); + unsafe { platform::free(ptr) }; } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `gcvt()` function was marked as legacy in the Open Group Base +/// Specifications Issue 6, and the function was removed in Issue 7. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn gcvt(value: c_double, ndigit: c_int, buf: *mut c_char) -> *mut c_char { unimplemented!(); } unsafe fn find_env(search: *const c_char) -> Option<(usize, *mut c_char)> { - for (i, item) in platform::inner_environ.iter().enumerate() { - let mut item = *item; - if item.is_null() { - assert_eq!( - i, - platform::inner_environ.len() - 1, - "an early null pointer in environ vector" - ); - break; - } + for (i, mut item) in platform::environ_iter().enumerate() { let mut search = search; loop { - let end_of_query = *search == 0 || *search == b'=' as c_char; - assert_ne!(*item, 0, "environ has an item without value"); - if *item == b'=' as c_char || end_of_query { - if *item == b'=' as c_char && end_of_query { + let end_of_query = unsafe { *search } == 0 || unsafe { *search } == b'=' as c_char; + if unsafe { *item } == 0 { + //TODO: environ has an item without value, is this a problem? + break; + } + if unsafe { *item } == b'=' as c_char || end_of_query { + if unsafe { *item } == b'=' as c_char && end_of_query { // Both keys env here - return Some((i, item.offset(1))); + return Some((i, unsafe { item.add(1) })); } else { break; } } - if *item != *search { + if unsafe { *item } != unsafe { *search } { break; } - item = item.offset(1); - search = search.offset(1); + item = unsafe { item.add(1) }; + search = unsafe { search.add(1) }; } } + None } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getenv(name: *const c_char) -> *mut c_char { - find_env(name).map(|val| val.1).unwrap_or(ptr::null_mut()) + unsafe { find_env(name) } + .map(|val| val.1) + .unwrap_or(ptr::null_mut()) } -// #[no_mangle] -pub extern "C" fn getsubopt( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getsubopt( optionp: *mut *mut c_char, - tokens: *const *mut c_char, + keylistp: *const *mut c_char, valuep: *mut *mut c_char, ) -> c_int { - unimplemented!(); + if optionp.is_null() + || (unsafe { *optionp }).is_null() + || keylistp.is_null() + || valuep.is_null() + { + return -1; + } + + let start = unsafe { *optionp }; + let mut cursor = start; + let mut found_comma = false; + + while unsafe { *cursor } != 0 { + if unsafe { *cursor } == b',' as c_char { + unsafe { *cursor = 0 }; + unsafe { *optionp = cursor.add(1) }; + found_comma = true; + break; + } + cursor = unsafe { cursor.add(1) }; + } + + if !found_comma { + unsafe { *optionp = cursor }; + } + + let mut i = 0; + while !(unsafe { *keylistp.add(i) }).is_null() { + let token = unsafe { *keylistp.add(i) }; + let token_len = unsafe { strlen(token) }; + + if unsafe { strncmp(start, token, token_len) } == 0 { + let suffix_char = unsafe { *start.add(token_len) }; + + if suffix_char == b'=' as c_char { + unsafe { *valuep = start.add(token_len + 1) }; + return i as c_int; + } else if suffix_char == 0 { + unsafe { *valuep = ptr::null_mut() }; + return i as c_int; + } + } + i += 1; + } + + unsafe { *valuep = start }; + -1 } -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn grantpt(fildes: c_int) -> c_int { - unimplemented!(); + // No-op on Linux and Redox + 0 } -#[no_mangle] +/// See . +// Ported from musl +#[unsafe(no_mangle)] pub unsafe extern "C" fn initstate(seed: c_uint, state: *mut c_char, size: size_t) -> *mut c_char { - // Ported from musl - if size < 8 { ptr::null_mut() } else { - // TODO: lock? - let old_state = random::save_state(); - random::N = match size { + let mut random_state = random::state_lock(); + let old_state = unsafe { random_state.save() }; + random_state.n = match size { 0..=7 => unreachable!(), // ensured above 8..=31 => 0, 32..=63 => 7, @@ -389,22 +539,36 @@ pub unsafe extern "C" fn initstate(seed: c_uint, state: *mut c_char, size: size_ _ => 63, }; - random::X_PTR = (state.cast::<[u8; 4]>()).offset(1); - random::seed(seed); - random::save_state(); - // TODO: unlock? + random_state.x_ptr = unsafe { (state.cast::<[u8; 4]>()).offset(1) }; + unsafe { random_state.seed(seed) }; + unsafe { random_state.save() }; old_state.cast::<_>() } } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `xsubi` is convertible to a +/// `&mut [c_ushort; 3]`. +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] pub unsafe extern "C" fn jrand48(xsubi: *mut c_ushort) -> c_long { - let new_xsubi_value = rand48::generator_step(&mut *(xsubi as *mut [c_ushort; 3])); - rand48::i32_from_x(new_xsubi_value) + let params = rand48::params(); + let xsubi_mut: &mut [c_ushort; 3] = unsafe { slice::from_raw_parts_mut(xsubi, 3) } + .try_into() + .unwrap(); + let new_xsubi_value = params.step(xsubi_mut.into()); + *xsubi_mut = new_xsubi_value.into(); + new_xsubi_value.get_i32() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn l64a(value: c_long) -> *mut c_char { // POSIX says we should only consider the lower 32 bits of value. let value_as_i32 = value as i32; @@ -417,13 +581,13 @@ pub unsafe extern "C" fn l64a(value: c_long) -> *mut c_char { let num_output_digits = usize::try_from(6 - (value_as_i32.leading_zeros() + 4) / 6).unwrap(); // Reset buffer (and have null terminator in place for any result) - L64A_BUFFER = [0; 7]; + unsafe { L64A_BUFFER.unsafe_set([0; 7]) }; for i in 0..num_output_digits { // Conversion to c_char always succeeds for the range 0..=63 - let digit_value = c_char::try_from((value_as_i32 >> 6 * i) & 63).unwrap(); + let digit_value = c_char::try_from((value_as_i32 >> (6 * i)) & 63).unwrap(); - L64A_BUFFER[i] = match digit_value { + (unsafe { L64A_BUFFER.unsafe_mut() })[i] = match digit_value { 0..=11 => { // ./0123456789 for values 0 to 11. b'.' == 46 46 + digit_value @@ -440,35 +604,48 @@ pub unsafe extern "C" fn l64a(value: c_long) -> *mut c_char { }; } - L64A_BUFFER.as_mut_ptr() + unsafe { L64A_BUFFER.unsafe_mut().as_mut_ptr() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn labs(i: c_long) -> c_long { i.abs() } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `param` is convertible to a +/// `&mut [c_ushort; 7]`. +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] pub unsafe extern "C" fn lcong48(param: *mut c_ushort) { - // Set DEFAULT_XSUBI buffer from elements 0-2 - let xsubi_value = rand48::u48_from_ushort_arr3(&*(param as *const [c_ushort; 3])); - rand48::DEFAULT_XSUBI = rand48::ushort_arr3_from_u48(xsubi_value); + let mut xsubi = rand48::xsubi_lock(); + let mut params = rand48::params_mut(); - // Set multiplier from elements 3-5 - rand48::A = rand48::u48_from_ushort_arr3(&*(param.offset(3) as *const [c_ushort; 3])); + let param_slice = unsafe { slice::from_raw_parts(param, 7) }; - /* Set addend from element 6. Note that c_ushort may be more than 16 - * bits, thus the cast. */ - rand48::C = *param.offset(6) as u16; + let xsubi_ref: &[c_ushort; 3] = param_slice[0..3].try_into().unwrap(); + let a_ref: &[c_ushort; 3] = param_slice[3..6].try_into().unwrap(); + let c = param_slice[6]; + + *xsubi = xsubi_ref.into(); + params.set(a_ref, c); } +/// See . #[repr(C)] pub struct ldiv_t { quot: c_long, rem: c_long, } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn ldiv(numer: c_long, denom: c_long) -> ldiv_t { ldiv_t { quot: numer / denom, @@ -476,18 +653,21 @@ pub extern "C" fn ldiv(numer: c_long, denom: c_long) -> ldiv_t { } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn llabs(i: c_longlong) -> c_longlong { i.abs() } +/// See . #[repr(C)] pub struct lldiv_t { quot: c_longlong, rem: c_longlong, } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn lldiv(numer: c_longlong, denom: c_longlong) -> lldiv_t { lldiv_t { quot: numer / denom, @@ -495,40 +675,51 @@ pub extern "C" fn lldiv(numer: c_longlong, denom: c_longlong) -> lldiv_t { } } -#[no_mangle] -pub unsafe extern "C" fn lrand48() -> c_long { - let new_xsubi_value = rand48::generator_step(&mut rand48::DEFAULT_XSUBI); - rand48::u31_from_x(new_xsubi_value) +/// See . +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] +pub extern "C" fn lrand48() -> c_long { + let params = rand48::params(); + let mut xsubi = rand48::xsubi_lock(); + *xsubi = params.step(*xsubi); + xsubi.get_u31() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn malloc(size: size_t) -> *mut c_void { - let ptr = platform::alloc(size); + let ptr = unsafe { platform::alloc(size) }; if ptr.is_null() { - platform::errno = ENOMEM; + platform::ERRNO.set(ENOMEM); } ptr } -#[no_mangle] +/// Non-POSIX, see . +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn memalign(alignment: size_t, size: size_t) -> *mut c_void { if alignment.is_power_of_two() { - let ptr = platform::alloc_align(size, alignment); + let ptr = unsafe { platform::alloc_align(size, alignment) }; if ptr.is_null() { - platform::errno = ENOMEM; + platform::ERRNO.set(ENOMEM); } ptr } else { - platform::errno = EINVAL; + platform::ERRNO.set(EINVAL); ptr::null_mut() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mblen(s: *const c_char, n: size_t) -> c_int { let mut wc: wchar_t = 0; let mut state: mbstate_t = mbstate_t {}; - let result: usize = mbrtowc(&mut wc, s, n, &mut state); + let result: usize = unsafe { mbrtowc(&raw mut wc, s, n, &raw mut state) }; if result == -1isize as usize { return -1; @@ -540,16 +731,18 @@ pub unsafe extern "C" fn mblen(s: *const c_char, n: size_t) -> c_int { result as i32 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbstowcs(pwcs: *mut wchar_t, mut s: *const c_char, n: size_t) -> size_t { let mut state: mbstate_t = mbstate_t {}; - mbsrtowcs(pwcs, &mut s, n, &mut state) + unsafe { mbsrtowcs(pwcs, &raw mut s, n, &raw mut state) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbtowc(pwc: *mut wchar_t, s: *const c_char, n: size_t) -> c_int { let mut state: mbstate_t = mbstate_t {}; - mbrtowc(pwc, s, n, &mut state) as c_int + (unsafe { mbrtowc(pwc, s, n, &raw mut state) }) as c_int } fn inner_mktemp(name: *mut c_char, suffix_len: c_int, mut attempt: F) -> Option @@ -559,13 +752,13 @@ where let len = unsafe { strlen(name) as c_int }; if len < 6 || suffix_len > len - 6 { - unsafe { platform::errno = errno::EINVAL }; + platform::ERRNO.set(errno::EINVAL); return None; } for i in (len - suffix_len - 6)..(len - suffix_len) { if unsafe { *name.offset(i as isize) } != b'X' as c_char { - unsafe { platform::errno = errno::EINVAL }; + platform::ERRNO.set(errno::EINVAL); return None; } } @@ -590,16 +783,82 @@ where } } - unsafe { platform::errno = errno::EEXIST } + platform::ERRNO.set(errno::EEXIST); None } -#[no_mangle] +fn get_nstime() -> u64 { + unsafe { + let mut ts = mem::MaybeUninit::uninit(); + if Sys::clock_gettime(CLOCK_MONOTONIC, Out::from_uninit_mut(&mut ts)).is_ok() {}; // TODO what to do if Err? + ts.assume_init().tv_nsec as u64 + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkdtemp(name: *mut c_char) -> *mut c_char { + inner_mktemp(name, 0, || { + let name_c = unsafe { CStr::from_ptr(name) }; + match Sys::mkdir(name_c, 0o700) { + Ok(()) => Some(name), + Err(_) => None, + } + }) + .unwrap_or(ptr::null_mut()) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkostemp(name: *mut c_char, flags: c_int) -> c_int { + unsafe { mkostemps(name, 0, flags) } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkostemps( + name: *mut c_char, + suffix_len: c_int, + mut flags: c_int, +) -> c_int { + // TODO: Rustify impl + + flags &= !O_ACCMODE; + flags |= O_RDWR | O_CREAT | O_EXCL; + + inner_mktemp(name, suffix_len, || { + let name = unsafe { CStr::from_ptr(name) }; + let fd = Sys::open(name, flags, 0o600).or_minus_one_errno(); + + if fd >= 0 { Some(fd) } else { None } + }) + .unwrap_or(-1) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkstemp(name: *mut c_char) -> c_int { + unsafe { mkostemps(name, 0, 0) } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkstemps(name: *mut c_char, suffix_len: c_int) -> c_int { + unsafe { mkostemps(name, suffix_len, 0) } +} + +/// See . +/// +/// # Deprecation +/// The `mktemp()` function was marked as legacy in the Open Group Base +/// Specifications Issue 6, and the function was removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn mktemp(name: *mut c_char) -> *mut c_char { if inner_mktemp(name, 0, || { - let name = CStr::from_ptr(name); - if Sys::access(name, 0) != 0 && platform::errno == ENOENT { + let name = unsafe { CStr::from_ptr(name) }; + if Sys::access(name, 0) == Err(Errno(ENOENT)) { Some(()) } else { None @@ -607,61 +866,46 @@ pub unsafe extern "C" fn mktemp(name: *mut c_char) -> *mut c_char { }) .is_none() { - *name = 0; + unsafe { *name = 0 }; } name } -fn get_nstime() -> u64 { - let mut ts = mem::MaybeUninit::uninit(); - Sys::clock_gettime(CLOCK_MONOTONIC, ts.as_mut_ptr()); - unsafe { ts.assume_init() }.tv_nsec as u64 +/// See . +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] +pub extern "C" fn mrand48() -> c_long { + let params = rand48::params(); + let mut xsubi = rand48::xsubi_lock(); + *xsubi = params.step(*xsubi); + xsubi.get_i32() } -#[no_mangle] -pub extern "C" fn mkostemps(name: *mut c_char, suffix_len: c_int, mut flags: c_int) -> c_int { - flags &= !O_ACCMODE; - flags |= O_RDWR | O_CREAT | O_EXCL; - - inner_mktemp(name, suffix_len, || { - let name = unsafe { CStr::from_ptr(name) }; - let fd = Sys::open(name, flags, 0o600); - - if fd >= 0 { - Some(fd) - } else { - None - } - }) - .unwrap_or(-1) -} - -#[no_mangle] -pub extern "C" fn mkstemp(name: *mut c_char) -> c_int { - mkostemps(name, 0, 0) -} -#[no_mangle] -pub extern "C" fn mkostemp(name: *mut c_char, flags: c_int) -> c_int { - mkostemps(name, 0, flags) -} -#[no_mangle] -pub extern "C" fn mkstemps(name: *mut c_char, suffix_len: c_int) -> c_int { - mkostemps(name, suffix_len, 0) -} - -#[no_mangle] -pub unsafe extern "C" fn mrand48() -> c_long { - let new_xsubi_value = rand48::generator_step(&mut rand48::DEFAULT_XSUBI); - rand48::i32_from_x(new_xsubi_value) -} - -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `xsubi` is convertible to a +/// `&mut [c_ushort; 3]`. +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] pub unsafe extern "C" fn nrand48(xsubi: *mut c_ushort) -> c_long { - let new_xsubi_value = rand48::generator_step(&mut *(xsubi as *mut [c_ushort; 3])); - rand48::u31_from_x(new_xsubi_value) + let params = rand48::params(); + let xsubi_mut: &mut [c_ushort; 3] = unsafe { slice::from_raw_parts_mut(xsubi, 3) } + .try_into() + .unwrap(); + let new_xsubi_value = params.step(xsubi_mut.into()); + *xsubi_mut = new_xsubi_value.into(); + new_xsubi_value.get_u31() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn posix_memalign( memptr: *mut *mut c_void, alignment: size_t, @@ -669,47 +913,146 @@ pub unsafe extern "C" fn posix_memalign( ) -> c_int { const VOID_PTR_SIZE: usize = mem::size_of::<*mut c_void>(); - if alignment % VOID_PTR_SIZE == 0 && alignment.is_power_of_two() { - let ptr = platform::alloc_align(size, alignment); - *memptr = ptr; - if ptr.is_null() { - ENOMEM - } else { - 0 - } + if alignment.is_multiple_of(VOID_PTR_SIZE) && alignment.is_power_of_two() { + let ptr = unsafe { platform::alloc_align(size, alignment) }; + unsafe { *memptr = ptr }; + if ptr.is_null() { ENOMEM } else { 0 } } else { - *memptr = ptr::null_mut(); + unsafe { *memptr = ptr::null_mut() }; EINVAL } } -// #[no_mangle] -pub extern "C" fn ptsname(fildes: c_int) -> *mut c_char { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_openpt(flags: c_int) -> c_int { + #[cfg(target_os = "redox")] + let r = unsafe { open((b"/scheme/pty\0" as *const u8).cast(), O_CREAT) }; + + #[cfg(target_os = "linux")] + let r = unsafe { open((b"/dev/ptmx\0" as *const u8).cast(), flags) }; + + if r < 0 && platform::ERRNO.get() == ENOSPC { + platform::ERRNO.set(EAGAIN); + } + + r } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ptsname(fd: c_int) -> *mut c_char { + const PTS_BUFFER_LEN: usize = 9 + mem::size_of::() * 3 + 1; + static mut PTS_BUFFER: [c_char; PTS_BUFFER_LEN] = [0; PTS_BUFFER_LEN]; + if unsafe { ptsname_r(fd, (&raw mut PTS_BUFFER).cast(), PTS_BUFFER_LEN) } != 0 { + ptr::null_mut() + } else { + (&raw mut PTS_BUFFER).cast() + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ptsname_r(fd: c_int, buf: *mut c_char, buflen: size_t) -> c_int { + if buf.is_null() { + platform::ERRNO.set(EINVAL); + EINVAL + } else { + unsafe { __ptsname_r(fd, buf, buflen) } + } +} + +#[cfg(target_os = "redox")] +#[inline(always)] +unsafe fn __ptsname_r(fd: c_int, buf: *mut c_char, buflen: size_t) -> c_int { + let tty_ptr = unsafe { unistd::ttyname(fd) }; + + if !tty_ptr.is_null() { + if let Ok(name) = unsafe { CStr::from_ptr(tty_ptr) }.to_str() { + let len = name.len(); + if len > buflen { + platform::ERRNO.set(ERANGE); + return ERANGE; + } else { + // we have checked the string will fit in the buffer + // so can use strcpy safely + let s = name.as_ptr().cast(); + unsafe { + ptr::copy_nonoverlapping(s, buf, len); + } + return 0; + } + } + } + platform::ERRNO.get() +} + +#[cfg(target_os = "linux")] +#[inline(always)] +unsafe fn __ptsname_r(fd: c_int, buf: *mut c_char, buflen: size_t) -> c_int { + let mut pty = 0; + let err = platform::ERRNO.get(); + + if unsafe { ioctl(fd, TIOCGPTN, ptr::from_mut(&mut pty).cast::()) } == 0 { + let name = format!("/dev/pts/{}", pty); + let len = name.len(); + if len > buflen { + platform::ERRNO.set(ERANGE); + ERANGE + } else { + // we have checked the string will fit in the buffer + // so can use strcpy safely + let s = name.as_ptr().cast(); + unsafe { ptr::copy_nonoverlapping(s, buf, len) }; + platform::ERRNO.set(err); + 0 + } + } else { + platform::ERRNO.get() + } +} + +unsafe fn put_new_env(insert: *mut c_char) { + // XXX: Another problem is that `environ` can be set to any pointer, which means there is a + // chance of a memory leak. But we can check if it was the same as before, like musl does. + if unsafe { platform::environ } == unsafe { platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() } { + { + let our_environ = unsafe { &mut *platform::OUR_ENVIRON.as_mut_ptr() }; + *our_environ.last_mut().unwrap() = insert; + our_environ.push(core::ptr::null_mut()); + } + // Likely a no-op but is needed due to Stacked Borrows. + unsafe { platform::environ = platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() }; + } else { + { + let our_environ = unsafe { &mut *platform::OUR_ENVIRON.as_mut_ptr() }; + our_environ.clear(); + our_environ.extend(platform::environ_iter()); + our_environ.push(insert); + our_environ.push(core::ptr::null_mut()); + } + unsafe { platform::environ = platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() }; + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn putenv(insert: *mut c_char) -> c_int { assert_ne!(insert, ptr::null_mut(), "putenv(NULL)"); - if let Some((i, _)) = find_env(insert) { - //TODO: find out why this crashes: platform::free(platform::inner_environ[i] as *mut c_void); - platform::inner_environ[i] = insert; + if let Some((i, _)) = unsafe { find_env(insert) } { + // XXX: The POSIX manual states that environment variables can be *set* via the `environ` + // global variable. While we can check if a pointer belongs to our allocator, or check + // `environ` against a vector which we control, it is likely not worth the effort. + unsafe { platform::environ.add(i).write(insert) }; } else { - let i = platform::inner_environ.len() - 1; - assert_eq!( - platform::inner_environ[i], - ptr::null_mut(), - "environ did not end with null" - ); - platform::inner_environ[i] = insert; - platform::inner_environ.push(ptr::null_mut()); - platform::environ = platform::inner_environ.as_mut_ptr(); + unsafe { put_new_env(insert) }; } 0 } -#[no_mangle] -pub extern "C" fn qsort( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn qsort( base: *mut c_void, nel: size_t, width: size_t, @@ -720,106 +1063,164 @@ pub extern "C" fn qsort( if nel > 0 { // XXX: maybe try to do mergesort/timsort first and fallback to introsort if memory // allocation fails? not sure what is ideal - sort::introsort(base as *mut c_char, nel, width, comp); + unsafe { sort::introsort(base.cast::(), nel, width, comp) }; } } } -#[no_mangle] +/// See . +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn qsort_r( + base: *mut c_void, + nel: size_t, + width: size_t, + compar: Option c_int>, + arg: *mut c_void, +) { + unimplemented!(); +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn quick_exit(status: c_int) -> ! { + for i in (0..unsafe { AT_QUICK_EXIT_FUNCS.unsafe_ref() }.len()).rev() { + if let Some(func) = unsafe { AT_QUICK_EXIT_FUNCS.unsafe_ref() }[i] { + (func)(); + } + } + + Sys::exit(status); +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn rand() -> c_int { - match RNG { - Some(ref mut rng) => RNG_SAMPLER.sample(rng), - None => { - let mut rng = XorShiftRng::from_seed([1; 16]); - let ret = RNG_SAMPLER.sample(&mut rng); - RNG = Some(rng); - ret + unsafe { + match RNG { + Some(ref mut rng) => rng_sampler().sample(rng), + None => { + let mut rng = XorShiftRng::from_seed([1; 16]); + let ret = rng_sampler().sample(&mut rng); + RNG = Some(rng); + ret + } } } } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `rand_r()` function was marked as obsolescent in the Open Group Base +/// Specifications Issue 7, and the function was removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn rand_r(seed: *mut c_uint) -> c_int { if seed.is_null() { errno::EINVAL } else { // set the type explicitly so this will fail if the array size for XorShiftRng changes - let seed_arr: [u8; 16] = mem::transmute([*seed; 16 / mem::size_of::()]); + let seed_arr: [u8; 16] = unsafe { mem::transmute([*seed; 16 / mem::size_of::()]) }; let mut rng = XorShiftRng::from_seed(seed_arr); - let ret = RNG_SAMPLER.sample(&mut rng); + let ret = rng_sampler().sample(&mut rng); - *seed = ret as _; + unsafe { *seed = ret as _ }; ret } } -#[no_mangle] +/// See . +// Ported from musl +#[unsafe(no_mangle)] pub unsafe extern "C" fn random() -> c_long { - // Ported from musl + let mut random_state = random::state_lock(); let k: u32; - // TODO: lock? - random::ensure_x_ptr_init(); - if random::N == 0 { - let x_old = u32::from_ne_bytes(*random::X_PTR); + unsafe { random_state.ensure_x_ptr_init() }; + + if random_state.n == 0 { + let x_old = u32::from_ne_bytes(unsafe { *random_state.x_ptr }); let x_new = random::lcg31_step(x_old); - *random::X_PTR = x_new.to_ne_bytes(); + unsafe { *random_state.x_ptr = x_new.to_ne_bytes() }; k = x_new; } else { // The non-u32-aligned way of saying x[i] += x[j]... - let x_i_old = u32::from_ne_bytes(*random::X_PTR.add(usize::from(random::I))); - let x_j = u32::from_ne_bytes(*random::X_PTR.add(usize::from(random::J))); + let x_i_old = + u32::from_ne_bytes(unsafe { *random_state.x_ptr.add(usize::from(random_state.i)) }); + let x_j = + u32::from_ne_bytes(unsafe { *random_state.x_ptr.add(usize::from(random_state.j)) }); let x_i_new = x_i_old.wrapping_add(x_j); - *random::X_PTR.add(usize::from(random::I)) = x_i_new.to_ne_bytes(); + unsafe { *random_state.x_ptr.add(usize::from(random_state.i)) = x_i_new.to_ne_bytes() }; k = x_i_new >> 1; - random::I += 1; - if random::I == random::N { - random::I = 0; + random_state.i += 1; + if random_state.i == random_state.n { + random_state.i = 0; } - random::J += 1; - if random::J == random::N { - random::J = 0; + random_state.j += 1; + if random_state.j == random_state.n { + random_state.j = 0; } } - // TODO: unlock? /* Both branches of this function result in a "u31", which will * always fit in a c_long. */ + #[cfg(not(target_arch = "x86"))] + return c_long::from(k); + #[cfg(target_arch = "x86")] // c_long on x86 is i32 so not infallible cast c_long::try_from(k).unwrap() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: size_t) -> *mut c_void { - let new_ptr = platform::realloc(ptr, size); + let new_ptr = unsafe { platform::realloc(ptr, size) }; if new_ptr.is_null() { - platform::errno = ENOMEM; + platform::ERRNO.set(ENOMEM); } new_ptr } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn reallocarray(ptr: *mut c_void, m: size_t, n: size_t) -> *mut c_void { + //Handle possible integer overflow in size calculation + match m.checked_mul(n) { + Some(size) => unsafe { realloc(ptr, size) }, + None => { + // For overflowing multiplication, we have to set errno here + platform::ERRNO.set(ENOMEM); + ptr::null_mut() + } + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn realpath(pathname: *const c_char, resolved: *mut c_char) -> *mut c_char { let ptr = if resolved.is_null() { - malloc(limits::PATH_MAX) as *mut c_char + (unsafe { malloc(limits::PATH_MAX) }).cast::() } else { resolved }; - let out = slice::from_raw_parts_mut(ptr as *mut u8, limits::PATH_MAX); + let out = unsafe { slice::from_raw_parts_mut(ptr.cast::(), limits::PATH_MAX) }; { - let file = match File::open(&CStr::from_ptr(pathname), O_PATH | O_CLOEXEC) { + let file = match File::open(unsafe { CStr::from_ptr(pathname) }, O_PATH | O_CLOEXEC) { Ok(file) => file, Err(_) => return ptr::null_mut(), }; let len = out.len(); - let read = Sys::fpath(*file, &mut out[..len - 1]); + // TODO: better error handling + let read = Sys::fpath(*file, &mut out[..len - 1]) + .map(|read| read as ssize_t) + .or_minus_one_errno(); if read < 0 { return ptr::null_mut(); } @@ -829,137 +1230,159 @@ pub unsafe extern "C" fn realpath(pathname: *const c_char, resolved: *mut c_char ptr } -#[no_mangle] -pub unsafe extern "C" fn seed48(seed16v: *mut c_ushort) -> *mut c_ushort { - rand48::reset_a_and_c(); - - // Stash current DEFAULT_XSUBI value in SEED48_XSUBI - rand48::SEED48_XSUBI = rand48::DEFAULT_XSUBI; - - // Set DEFAULT_XSUBI from the argument provided - let xsubi_value = rand48::u48_from_ushort_arr3(&*(seed16v as *const [c_ushort; 3])); - rand48::DEFAULT_XSUBI = rand48::ushort_arr3_from_u48(xsubi_value); - - // Return the stashed value - rand48::SEED48_XSUBI.as_mut_ptr() +/// See . +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn secure_getenv(name: *const c_char) -> *mut c_char { + // Redox does not support setuid/setgid binaries, so there is no + // elevated-privilege case to guard against. Always delegate to getenv. + // If setuid support is ever added, this must check real vs effective + // uid/gid and return NULL when they differ. + unsafe { getenv(name) } } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `seed16v` is convertible to a `&[c_ushort; 3]`. +/// Additionally, the caller must ensure that the function has exclusive access +/// to the static buffer it returns; this includes avoiding simultaneous calls +/// to this function. +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn seed48(seed16v: *mut c_ushort) -> *mut c_ushort { + static RETURN_BUFFER: RawCell<[c_ushort; 3]> = RawCell::new([0; 3]); + + let mut params = rand48::params_mut(); + let mut xsubi = rand48::xsubi_lock(); + + // SAFETY: the caller is required to ensure seed16v is convertible to + // &[c_ushort; 3]. + let seed16v_ref: &[c_ushort; 3] = unsafe { slice::from_raw_parts(seed16v, 3) } + .try_into() + .unwrap(); + + // SAFETY: the caller is required to ensure exclusive access to + // RETURN_BUFFER. + let return_buffer_mut = unsafe { RETURN_BUFFER.unsafe_mut() }; + *return_buffer_mut = (*xsubi).into(); + *xsubi = seed16v_ref.into(); + params.reset(); + RETURN_BUFFER.as_mut_ptr().cast() +} + +unsafe fn copy_kv( + existing: *mut c_char, + key: *const c_char, + value: *const c_char, + key_len: usize, + value_len: usize, +) { + unsafe { core::ptr::copy_nonoverlapping(key, existing, key_len) }; + unsafe { core::ptr::write(existing.add(key_len), b'=' as c_char) }; + unsafe { core::ptr::copy_nonoverlapping(value, existing.add(key_len + 1), value_len) }; + unsafe { core::ptr::write(existing.add(key_len + 1 + value_len), 0) }; +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn setenv( - mut key: *const c_char, - mut value: *const c_char, + key: *const c_char, + value: *const c_char, overwrite: c_int, ) -> c_int { - let mut key_len = 0; - while *key.offset(key_len) != 0 { - key_len += 1; - } - let mut value_len = 0; - while *value.offset(value_len) != 0 { - value_len += 1; - } + let key_len = unsafe { strlen(key) }; + let value_len = unsafe { strlen(value) }; - let index = if let Some((i, existing)) = find_env(key) { + if let Some((i, existing)) = unsafe { find_env(key) } { if overwrite == 0 { return 0; } - let mut existing_len = 0; - while *existing.offset(existing_len) != 0 { - existing_len += 1; - } + let existing_len = unsafe { strlen(existing) }; if existing_len >= value_len { // Reuse existing element's allocation - for i in 0..=value_len { - *existing.offset(i) = *value.offset(i); - } - return 0; + unsafe { core::ptr::copy_nonoverlapping(value, existing, value_len) }; + //TODO: fill to end with zeroes + unsafe { core::ptr::write(existing.add(value_len), 0) }; } else { - i + // Reuse platform::environ slot, but allocate a new pointer. + let ptr = unsafe { platform::alloc(key_len as usize + 1 + value_len as usize + 1) } + .cast::(); + unsafe { copy_kv(ptr, key, value, key_len, value_len) }; + unsafe { platform::environ.add(i).write(ptr) }; } } else { - let i = platform::inner_environ.len() - 1; - assert_eq!( - platform::inner_environ[i], - ptr::null_mut(), - "environ did not end with null" - ); - platform::inner_environ.push(ptr::null_mut()); - platform::environ = platform::inner_environ.as_mut_ptr(); - i - }; + // Expand platform::environ and allocate a new pointer. + let ptr = unsafe { platform::alloc(key_len as usize + 1 + value_len as usize + 1) } + .cast::(); + unsafe { copy_kv(ptr, key, value, key_len, value_len) }; + unsafe { put_new_env(ptr) }; + } //platform::free(platform::inner_environ[index] as *mut c_void); - let mut ptr = platform::alloc(key_len as usize + 1 + value_len as usize) as *mut c_char; - platform::inner_environ[index] = ptr; - - while *key != 0 { - *ptr = *key; - ptr = ptr.offset(1); - key = key.offset(1); - } - *ptr = b'=' as c_char; - ptr = ptr.offset(1); - while *value != 0 { - *ptr = *value; - ptr = ptr.offset(1); - value = value.offset(1); - } - *ptr = 0; 0 } -// #[no_mangle] -pub extern "C" fn setkey(key: *const c_char) { +/// See . +/// +/// # Deprecation +/// The `setkey()` function was marked as obsolescent in the Open Group Base +/// Specifications Issue 8. +#[deprecated] +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn setkey(key: *const c_char) { unimplemented!(); } -#[no_mangle] +/// See . +// Ported from musl. The state parameter is no longer const in newer versions of POSIX. +#[unsafe(no_mangle)] pub unsafe extern "C" fn setstate(state: *mut c_char) -> *mut c_char { - /* Ported from musl. The state parameter is no longer const in newer - * versions of POSIX. */ + let mut random_state = random::state_lock(); + + let old_state = unsafe { random_state.save() }; + unsafe { random_state.load(state.cast::<_>()) }; - // TODO: lock? - let old_state = random::save_state(); - random::load_state(state.cast::<_>()); - // TODO: unlock? old_state.cast::<_>() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn srand(seed: c_uint) { - RNG = Some(XorShiftRng::from_seed([seed as u8; 16])); + unsafe { RNG = Some(XorShiftRng::from_seed([seed as u8; 16])) }; } -#[no_mangle] -pub unsafe extern "C" fn srand48(seedval: c_long) { - rand48::reset_a_and_c(); +/// See . +/// +/// # Panics +/// Panics if the function is unable to obtain a lock on the generator's global +/// state. +#[unsafe(no_mangle)] +pub extern "C" fn srand48(seedval: c_long) { + let mut params = rand48::params_mut(); + let mut xsubi = rand48::xsubi_lock(); + params.reset(); /* Set the high 32 bits of the 48-bit X_i value to the lower 32 bits * of the input argument, and the lower 16 bits to 0x330e, as * specified in POSIX. */ - let xsubi_value = (u64::from(seedval as u32) << 16) | 0x330e; - rand48::DEFAULT_XSUBI = rand48::ushort_arr3_from_u48(xsubi_value); + *xsubi = ((u64::from(seedval as u32) << 16) | 0x330e) + .try_into() + .unwrap(); } -#[no_mangle] +/// See . +// Ported from musl +#[unsafe(no_mangle)] pub unsafe extern "C" fn srandom(seed: c_uint) { - // Ported from musl + let mut random_state = random::state_lock(); - // TODO: lock? - random::seed(seed); - // TODO: unlock? -} - -#[no_mangle] -pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double { - strto_float_impl!(c_double, s, endptr) -} -#[no_mangle] -pub unsafe extern "C" fn strtof(s: *const c_char, endptr: *mut *mut c_char) -> c_float { - strto_float_impl!(c_float, s, endptr) + unsafe { random_state.seed(seed) }; } pub fn is_positive(ch: c_char) -> Option<(bool, isize)> { @@ -972,14 +1395,14 @@ pub fn is_positive(ch: c_char) -> Option<(bool, isize)> { } pub unsafe fn detect_base(s: *const c_char) -> Option<(c_int, isize)> { - let first = *s as u8; + let first = unsafe { *s } as u8; match first { 0 => None, b'0' => { - let second = *s.offset(1) as u8; + let second = unsafe { *s.offset(1) } as u8; if second == b'X' || second == b'x' { Some((16, 2)) - } else if second >= b'0' && second <= b'7' { + } else if (b'0'..=b'7').contains(&second) { Some((8, 1)) } else { // in this case, the prefix (0) is going to be the number @@ -991,8 +1414,8 @@ pub unsafe fn detect_base(s: *const c_char) -> Option<(c_int, isize)> { } pub unsafe fn convert_octal(s: *const c_char) -> Option<(c_ulong, isize, bool)> { - if *s != 0 && *s == b'0' as c_char { - if let Some((val, idx, overflow)) = convert_integer(s.offset(1), 8) { + if unsafe { *s } != 0 && unsafe { *s } == b'0' as c_char { + if let Some((val, idx, overflow)) = unsafe { convert_integer(s.offset(1), 8) } { Some((val, idx + 1, overflow)) } else { // in case the prefix is not actually a prefix @@ -1004,12 +1427,15 @@ pub unsafe fn convert_octal(s: *const c_char) -> Option<(c_ulong, isize, bool)> } pub unsafe fn convert_hex(s: *const c_char) -> Option<(c_ulong, isize, bool)> { - if (*s != 0 && *s == b'0' as c_char) - && (*s.offset(1) != 0 && (*s.offset(1) == b'x' as c_char || *s.offset(1) == b'X' as c_char)) + if (unsafe { *s } != 0 && unsafe { *s } == b'0' as c_char) + && (unsafe { *s.offset(1) } != 0 + && (unsafe { *s.offset(1) } == b'x' as c_char + || unsafe { *s.offset(1) } == b'X' as c_char)) { - convert_integer(s.offset(2), 16).map(|(val, idx, overflow)| (val, idx + 2, overflow)) + unsafe { convert_integer(s.offset(2), 16) } + .map(|(val, idx, overflow)| (val, idx + 2, overflow)) } else { - convert_integer(s, 16).map(|(val, idx, overflow)| (val, idx, overflow)) + unsafe { convert_integer(s, 16) } } } @@ -1043,7 +1469,7 @@ pub unsafe fn convert_integer(s: *const c_char, base: c_int) -> Option<(c_ulong, // `-1 as usize` is usize::MAX // `-1 as u8 as usize` is u8::MAX // It extends by the sign bit unless we cast it to unsigned first. - let val = LOOKUP_TABLE[*s.offset(idx) as u8 as usize]; + let val = LOOKUP_TABLE[unsafe { *s.offset(idx) } as u8 as usize]; if val == -1 || val as c_int >= base { break; } else { @@ -1053,8 +1479,8 @@ pub unsafe fn convert_integer(s: *const c_char, base: c_int) -> Option<(c_ulong, { num = res; } else { - platform::errno = ERANGE; - num = c_ulong::max_value(); + platform::ERRNO.set(ERANGE); + num = c_ulong::MAX; overflowed = true; } @@ -1069,83 +1495,107 @@ pub unsafe fn convert_integer(s: *const c_char, base: c_int) -> Option<(c_ulong, } } -#[no_mangle] -pub unsafe extern "C" fn strtoul( - s: *const c_char, - endptr: *mut *mut c_char, - base: c_int, -) -> c_ulong { - strto_impl!( - c_ulong, - false, - c_ulong::max_value(), - c_ulong::min_value(), - s, - endptr, - base - ) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double { + strto_float_impl!(c_double, s, endptr) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strtof(s: *const c_char, endptr: *mut *mut c_char) -> c_float { + strto_float_impl!(c_float, s, endptr) +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_long { - strto_impl!( - c_long, - true, - c_long::max_value(), - c_long::min_value(), - s, - endptr, - base - ) + strto_impl!(c_long, true, c_long::MAX, c_long::MIN, s, endptr, base) } -#[no_mangle] -pub unsafe extern "C" fn strtoull( - s: *const c_char, - endptr: *mut *mut c_char, - base: c_int, -) -> c_ulonglong { - strtoul(s, endptr, base) -} +// TODO: strtold(), when long double is available. +// Declared via cbindgen trailer section (long double strtold(...)) for +// C++ compat. Linker resolves via libredbear-qt-strtold-compat.so. -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtoll( s: *const c_char, endptr: *mut *mut c_char, base: c_int, ) -> c_longlong { - strtol(s, endptr, base) + strto_impl!( + c_longlong, + true, + c_longlong::MAX, + c_longlong::MIN, + s, + endptr, + base + ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strtoul( + s: *const c_char, + endptr: *mut *mut c_char, + base: c_int, +) -> c_ulong { + strto_impl!(c_ulong, false, c_ulong::MAX, c_ulong::MIN, s, endptr, base) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strtoull( + s: *const c_char, + endptr: *mut *mut c_char, + base: c_int, +) -> c_ulonglong { + strto_impl!( + c_ulonglong, + false, + c_ulonglong::MAX, + c_ulonglong::MIN, + s, + endptr, + base + ) +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn system(command: *const c_char) -> c_int { + // TODO: rusty error handling? //TODO: share code with popen - let child_pid = unistd::fork(); - if child_pid == 0 { - let command_nonnull = if command.is_null() { - "exit 0\0".as_ptr() + // handle shell detection on command == NULL + if command.is_null() { + let status = unsafe { system(c"exit 0".as_ptr().cast::()) }; + if status == 0 { + return 1; } else { - command as *const u8 - }; + return 0; + } + } - let shell = "/bin/sh\0".as_ptr(); + let child_pid = unsafe { unistd::fork() }; + if child_pid == 0 { + let command_nonnull = command.cast::(); - let args = [ - "sh\0".as_ptr(), - "-c\0".as_ptr(), - command_nonnull, - ptr::null(), - ]; + let shell = c"/bin/sh".as_ptr(); - unistd::execv(shell as *const c_char, args.as_ptr() as *const *mut c_char); + let args = [c"sh".as_ptr(), c"-c".as_ptr(), command_nonnull, ptr::null()]; - exit(127); + unsafe { unistd::execv(shell.cast::(), args.as_ptr().cast::<*mut c_char>()) }; + + unsafe { exit(127) }; unreachable!(); } else if child_pid > 0 { let mut wstatus = 0; - if Sys::waitpid(child_pid, &mut wstatus, 0) == !0 { + if Sys::waitpid(child_pid, Some(Out::from_mut(&mut wstatus)), 0).or_minus_one_errno() == -1 + { return -1; } @@ -1155,37 +1605,81 @@ pub unsafe extern "C" fn system(command: *const c_char) -> c_int { } } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `ttyslot()` function was marked as obsolescent in the Open Group Base +/// Specifications Issue 5, and the function was removed in Issue 6. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn ttyslot() -> c_int { unimplemented!(); } -// #[no_mangle] -pub extern "C" fn unlockpt(fildes: c_int) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unlockpt(fildes: c_int) -> c_int { + let mut u: c_int = 0; + unsafe { + ioctl( + fildes, + TIOCSPTLCK, + ptr::from_mut::(&mut u).cast::(), + ) + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn unsetenv(key: *const c_char) -> c_int { - if let Some((i, _)) = find_env(key) { - // No need to worry about updating the pointer, this does not - // reallocate in any way. And the final null is already shifted back. - platform::inner_environ.remove(i); + if let Some((i, _)) = unsafe { find_env(key) } { + if unsafe { platform::environ } + == unsafe { platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() } + { + // No need to worry about updating the pointer, this does not + // reallocate in any way. And the final null is already shifted back. + { + let our_environ = unsafe { &mut *platform::OUR_ENVIRON.as_mut_ptr() }; + our_environ.remove(i); + } + + // My UB paranoia. + unsafe { platform::environ = platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() }; + } else { + { + let our_environ = unsafe { &mut *platform::OUR_ENVIRON.as_mut_ptr() }; + our_environ.clear(); + our_environ.extend( + platform::environ_iter() + .enumerate() + .filter(|&(j, _)| j != i) + .map(|(_, v)| v), + ); + our_environ.push(core::ptr::null_mut()); + } + unsafe { platform::environ = platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() }; + } } 0 } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `valloc()` function was marked as obsolescent in the Open Group Base +/// Specifications Issue 5, and the function was removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn valloc(size: size_t) -> *mut c_void { /* sysconf(_SC_PAGESIZE) is a c_long and may in principle not * convert correctly to a size_t. */ - match size_t::try_from(sysconf(_SC_PAGESIZE)) { + match size_t::try_from(unsafe { sysconf(_SC_PAGESIZE) }) { Ok(page_size) => { /* valloc() is not supposed to be able to set errno to * EINVAL, hence no call to memalign(). */ - let ptr = platform::alloc_align(size, page_size); + let ptr = unsafe { platform::alloc_align(size, page_size) }; if ptr.is_null() { - platform::errno = ENOMEM; + platform::ERRNO.set(ENOMEM); } ptr } @@ -1196,16 +1690,18 @@ pub unsafe extern "C" fn valloc(size: size_t) -> *mut c_void { } } -#[no_mangle] -pub extern "C" fn wcstombs(s: *mut c_char, pwcs: *mut *const wchar_t, n: size_t) -> size_t { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcstombs(s: *mut c_char, mut pwcs: *const wchar_t, n: size_t) -> size_t { let mut state: mbstate_t = mbstate_t {}; - wcsrtombs(s, pwcs, n, &mut state) + unsafe { wcsrtombs(s, &raw mut pwcs, n, &raw mut state) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wctomb(s: *mut c_char, wc: wchar_t) -> c_int { let mut state: mbstate_t = mbstate_t {}; - let result: usize = wcrtomb(s, wc, &mut state); + let result: usize = unsafe { wcrtomb(s, wc, &raw mut state) }; if result == -1isize as usize { return -1; diff --git a/src/header/stdlib/rand48.rs b/src/header/stdlib/rand48.rs index 57e968ec5c..f8b3b4e35a 100644 --- a/src/header/stdlib/rand48.rs +++ b/src/header/stdlib/rand48.rs @@ -1,90 +1,141 @@ -//! Helper functions for pseudorandom number generation using LCG, see https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/drand48.html +//! Helper functions for pseudorandom number generation using LCG, see . -use crate::platform::types::*; +use crate::{ + platform::types::{c_double, c_long, c_ushort}, + sync::{ + Mutex, MutexGuard, + rwlock::{self, RwLock}, + }, +}; -/* The default element buffer for the linear congruential generator's - * sequence. Implemented using a c_ushort array for consistency between - * the drand48()/lrand48()/mrand48() and erand48()/nrand48()/jrand48() - * functions, and with SEED48_XSUBI (see below). */ -pub static mut DEFAULT_XSUBI: [c_ushort; 3] = [0; 3]; +/// A 48-bit integer, used for the 48-bit arithmetic in these functions. +#[derive(Clone, Copy, Default)] +#[repr(C)] +pub struct U48(u64); -// Used by seed48() (returns a pointer to this array). -pub static mut SEED48_XSUBI: [c_ushort; 3] = [0; 3]; - -/* Multiplier and addend, which may be set through lcong48(). Default - * values as specified in POSIX. */ -const A_DEFAULT_VALUE: u64 = 0x5deece66d; -const C_DEFAULT_VALUE: u16 = 0xb; - -pub static mut A: u64 = A_DEFAULT_VALUE; -pub static mut C: u16 = C_DEFAULT_VALUE; - -/// Used by `srand48()` and `seed48()`. -pub unsafe fn reset_a_and_c() { - A = A_DEFAULT_VALUE; - C = C_DEFAULT_VALUE; +impl From<&[c_ushort; 3]> for U48 { + fn from(value: &[c_ushort; 3]) -> Self { + /* c_ushort is u16 so we get only the lower 16 bits of each + * element, as specified by POSIX. */ + Self(u64::from(value[0]) | (u64::from(value[1]) << 16) | (u64::from(value[2]) << 32)) + } } -/// Build a 48-bit integer from a size-3 array of unsigned short. -/// -/// Pointers to c_ushort can be converted to &[c_ushort; 3] by taking -/// &*(YOUR_C_USHORT_POINTER as *const [c_ushort; 3]) -/// -/// See also this cbindgen issue for why the stdlib functions can't just -/// have an xsubi: *mut [c_ushort; 3] parameter: -/// https://github.com/eqrion/cbindgen/issues/171 -pub fn u48_from_ushort_arr3(arr: &[c_ushort; 3]) -> u64 { - /* Cast via u16 to ensure we get only the lower 16 bits of each - * element, as specified by POSIX. */ - u64::from(arr[0] as u16) | (u64::from(arr[1] as u16) << 16) | (u64::from(arr[2] as u16) << 32) +impl From<&mut [c_ushort; 3]> for U48 { + fn from(value: &mut [c_ushort; 3]) -> Self { + Self::from(&*value) + } } -/// Make a size-3 array of unsigned short from a 48-bit integer. -pub fn ushort_arr3_from_u48(value: u64) -> [c_ushort; 3] { - [ - c_ushort::from(value as u16), - c_ushort::from((value >> 16) as u16), - c_ushort::from((value >> 32) as u16), - ] +impl TryFrom for U48 { + type Error = u64; + + fn try_from(value: u64) -> Result { + if value < 0x1_0000_0000_0000 { + Ok(Self(value)) + } else { + Err(value) + } + } } -/// Advances the buffer from the input argument to the next element in -/// the linear congruential generator's sequence. -/// -/// Modifies the passed argument in-place and returns the new value as a -/// u64. -pub unsafe fn generator_step(xsubi: &mut [c_ushort; 3]) -> u64 { - let old_xsubi_value: u64 = u48_from_ushort_arr3(xsubi); - - /* The recurrence relation of the linear congruential generator, - * X_(n+1) = (a * X_n + c) % m, - * with m = 2**48. The multiplication and addition can overflow a - * u64, but we just let it wrap since we take mod 2**48 anyway. */ - let new_xsubi_value: u64 = - A.wrapping_mul(old_xsubi_value).wrapping_add(u64::from(C)) & 0xffff_ffff_ffff; - - *xsubi = ushort_arr3_from_u48(new_xsubi_value); - new_xsubi_value +impl From for u64 { + fn from(value: U48) -> Self { + value.0 + } } -/// Get a C `double` from a 48-bit integer (for `drand48()` and -/// `erand48()`). -pub fn f64_from_x(x: u64) -> c_double { - /* We set the exponent to 0, and the 48-bit integer is copied into the high - * 48 of the 52 significand bits. The value then lies in the range - * [1.0, 2.0), from which we simply subtract 1.0. */ - f64::from_bits(0x3ff0_0000_0000_0000_u64 | (x << 4)) - 1.0 +impl From for [c_ushort; 3] { + fn from(value: U48) -> Self { + [ + (value.0 as u16), + ((value.0 >> 16) as u16), + ((value.0 >> 32) as u16), + ] + } } -/// Get the high 31 bits of a 48-bit integer (for `lrand48()` and -/// `nrand48()`). -pub fn u31_from_x(x: u64) -> c_long { - (x >> 17) as c_long +impl U48 { + /// Get a C `double` in the interval [0.0, 1.0) (for `drand48()` and `erand48()`). + pub fn get_f64(self) -> c_double { + /* We set the exponent to 0, and the 48-bit integer is copied into the high + * 48 of the 52 significand bits. The value then lies in the range + * [1.0, 2.0), from which we simply subtract 1.0. */ + f64::from_bits(0x3ff0_0000_0000_0000_u64 | (self.0 << 4)) - 1.0 + } + + /// Get the high 31 bits (for `lrand48()` and `nrand48()`). + pub fn get_u31(self) -> c_long { + (self.0 >> 17).try_into().unwrap() + } + + /// Get the high 32 bits, signed (for `mrand48()` and `jrand48()`). + pub fn get_i32(self) -> c_long { + // Cast via i32 to ensure we get the sign correct + ((self.0 >> 16) as i32).into() + } } -/// Get the high 32 bits, signed, of a 48-bit integer (for `mrand48()` -/// and `jrand48()`). -pub fn i32_from_x(x: u64) -> c_long { - // Cast via i32 to ensure we get the sign correct - c_long::from((x >> 16) as i32) +/// The a and c parameters of an LCG. +#[derive(Default)] +#[repr(C)] +pub struct Params { + pub a: U48, + pub c: u16, +} + +impl Params { + pub const fn new() -> Self { + // Default values as specified in POSIX + Params { + a: U48(0x5deece66d), + c: 0xb, + } + } + + pub fn reset(&mut self) { + *self = Self::new(); + } + + /// For use in lcong48(). + pub fn set(&mut self, a: &[c_ushort; 3], c: c_ushort) { + self.a = a.into(); + self.c = c; + } + + pub fn step(&self, xsubi: U48) -> U48 { + /* The recurrence relation of the linear congruential generator, + * X_(n+1) = (a * X_n + c) % m, + * with m = 2**48. The multiplication and addition can overflow a u64, but + * we just let it wrap since we take mod 2**48 anyway. */ + (u64::from(self.a) + .wrapping_mul(u64::from(xsubi)) + .wrapping_add(u64::from(self.c)) + & 0xffff_ffff_ffff) + .try_into() + .unwrap() + } +} + +static PARAMS: RwLock = RwLock::::new(Params::new()); + +/// Immediately get the global [`Params`] lock for reading, or panic if unsuccessful. +pub fn params<'a>() -> rwlock::ReadGuard<'a, Params> { + PARAMS + .try_read() + .expect("unable to acquire LCG parameter lock") +} + +/// Immediately get the global [`Params`] lock for writing, or panic if unsuccessful. +pub fn params_mut<'a>() -> rwlock::WriteGuard<'a, Params> { + PARAMS + .try_write() + .expect("unable to acquire LCG parameter lock") +} + +/// Immediately get the global X_i lock, or panic if unsuccessful. +pub fn xsubi_lock<'a>() -> MutexGuard<'a, U48> { + static XSUBI: Mutex = Mutex::::new(U48(0)); + + XSUBI.try_lock().expect("unable to acquire LCG X_i lock") } diff --git a/src/header/stdlib/random.rs b/src/header/stdlib/random.rs index f08155924e..86aec6f3db 100644 --- a/src/header/stdlib/random.rs +++ b/src/header/stdlib/random.rs @@ -1,12 +1,15 @@ -//! Helper functions for random() and friends, see https://pubs.opengroup.org/onlinepubs/7908799/xsh/initstate.html -/* Ported from musl's implementation (src/prng/random.c). Does not - * currently implement locking, though. */ +//! Helper functions for random() and friends, see . +// Ported from musl's implementation (src/prng/random.c) -use crate::platform::types::*; -use core::{convert::TryFrom, ptr}; +use crate::{ + platform::types::c_uint, + sync::{Mutex, MutexGuard}, +}; +use core::{cell::SyncUnsafeCell, convert::TryFrom, ptr}; +// This is the default buffer for X. Not guarded by mutex as a mutable pointer to this is available to the user through initstate() and setstate(). #[rustfmt::skip] -static mut X_INIT: [u32; 32] = [ +pub static DEFAULT_X: SyncUnsafeCell<[u32; 32]> = SyncUnsafeCell::new([ 0x00000000, 0x5851f42d, 0xc0b18ccf, 0xcbb5f646, 0xc7033129, 0x30705b04, 0x20fd5db4, 0x9a8b7f78, 0x502959d8, 0xab894868, 0x6c0356a7, 0x88cdb7ff, @@ -15,30 +18,95 @@ static mut X_INIT: [u32; 32] = [ 0x40f7702c, 0x6fa72ca5, 0xaaa84157, 0x58a0df74, 0xc74a0364, 0xae533cc4, 0x04185faf, 0x6de3b115, 0x0cab8628, 0xf043bfa4, 0x398150e9, 0x37521657, -]; +]); -/* N needs to accommodate values up to 63, corresponding to the maximum - * state array size of 256 bytes. I and J must be able to accommodate - * values less than or equal to N. */ -pub static mut N: u8 = 31; -pub static mut I: u8 = 3; -pub static mut J: u8 = 0; +pub struct State { + pub x_ptr: *mut [u8; 4], + pub n: u8, + pub i: u8, + pub j: u8, +} -/* As such, random() and related functions work on u32 values, but POSIX - * allows the user to supply a custom state data array as a `char *` - * with no requirements on alignment. Thus, we must assume the worst in - * terms of alignment and convert back and forth from [u8; 4]. - * - * Also, unlike in C, we can't take the address of the initializing - * array outside of a function. */ -pub static mut X_PTR: *mut [u8; 4] = ptr::null_mut(); +// Necessary because raw pointers are not Send +unsafe impl Send for State {} -// To be called in any function that may read from X_PTR -pub unsafe fn ensure_x_ptr_init() { - if X_PTR.is_null() { - let x_u32_ptr: *mut u32 = &mut X_INIT[1]; - X_PTR = x_u32_ptr.cast::<[u8; 4]>(); +impl State { + /// To be called in any function that may read from X_PTR + pub unsafe fn ensure_x_ptr_init(&mut self) { + if self.x_ptr.is_null() { + let x_u32_ptr: *mut u32 = unsafe { DEFAULT_X.get().cast::().add(1) }; + self.x_ptr = x_u32_ptr.cast::<[u8; 4]>(); + } } + + pub unsafe fn save(&mut self) -> *mut [u8; 4] { + unsafe { self.ensure_x_ptr_init() }; + + let stash_value: u32 = + (u32::from(self.n) << 16) | (u32::from(self.i) << 8) | u32::from(self.j); + unsafe { *self.x_ptr.offset(-1) = stash_value.to_ne_bytes() }; + unsafe { self.x_ptr.offset(-1) } + } + + pub unsafe fn load(&mut self, state_ptr: *mut [u8; 4]) { + let stash_value = u32::from_ne_bytes(unsafe { *state_ptr }); + self.x_ptr = unsafe { state_ptr.offset(1) }; + + /* This calculation of n does not have a bit mask in the musl + * original, in principle resulting in a u16, but obtaining a value + * larger than 63 can probably be dismissed as pathological. */ + self.n = u8::try_from((stash_value >> 16) & 0xff).unwrap(); + + // i and j calculations are straight from musl + self.i = u8::try_from((stash_value >> 8) & 0xff).unwrap(); + self.j = u8::try_from(stash_value & 0xff).unwrap(); + } + + pub unsafe fn seed(&mut self, seed: c_uint) { + unsafe { self.ensure_x_ptr_init() }; + + let mut s = u64::from(seed); + + if self.n == 0 { + unsafe { *self.x_ptr = (s as u32).to_ne_bytes() }; + } else { + self.i = if self.n == 31 || self.n == 7 { 3 } else { 1 }; + + self.j = 0; + + for k in 0..usize::from(self.n) { + s = lcg64_step(s); + + // Conversion will always succeed (value is a 32-bit right- + // shift of a 64-bit integer). + unsafe { *self.x_ptr.add(k) = u32::try_from(s >> 32).unwrap().to_ne_bytes() }; + } + + // ensure X contains at least one odd number + unsafe { *self.x_ptr = (u32::from_ne_bytes(*self.x_ptr) | 1).to_ne_bytes() }; + } + } +} + +pub fn state_lock<'a>() -> MutexGuard<'a, State> { + static STATE: Mutex = Mutex::new(State { + /* As such, random() and related functions work on u32 values, but POSIX + * allows the user to supply a custom state data array as a `char *` + * with no requirements on alignment. Thus, we must assume the worst in + * terms of alignment and convert back and forth from [u8; 4]. + * + * Also, unlike in C, we can't take the address of the initializing + * array outside of a function. */ + x_ptr: ptr::null_mut(), + /* N needs to accommodate values up to 63, corresponding to the maximum + * state array size of 256 bytes. I and J must be able to accommodate + * values less than or equal to N. */ + n: 31, + i: 3, + j: 0, + }); + + STATE.try_lock().expect("unable to acquire PRNG lock") } pub fn lcg31_step(x: u32) -> u32 { @@ -48,50 +116,3 @@ pub fn lcg31_step(x: u32) -> u32 { pub fn lcg64_step(x: u64) -> u64 { 6364136223846793005_u64.wrapping_mul(x).wrapping_add(1_u64) } - -pub unsafe fn save_state() -> *mut [u8; 4] { - ensure_x_ptr_init(); - - let stash_value: u32 = (u32::from(N) << 16) | (u32::from(I) << 8) | u32::from(J); - *X_PTR.offset(-1) = stash_value.to_ne_bytes(); - X_PTR.offset(-1) -} - -pub unsafe fn load_state(state_ptr: *mut [u8; 4]) { - let stash_value = u32::from_ne_bytes(*state_ptr); - X_PTR = state_ptr.offset(1); - - /* This calculation of N does not have a bit mask in the musl - * original, in principle resulting in a u16, but obtaining a value - * larger than 63 can probably be dismissed as pathological. */ - N = u8::try_from((stash_value >> 16) & 0xff).unwrap(); - - // I and J calculations are straight from musl - I = u8::try_from((stash_value >> 8) & 0xff).unwrap(); - J = u8::try_from(stash_value & 0xff).unwrap(); -} - -pub unsafe fn seed(seed: c_uint) { - ensure_x_ptr_init(); - - let mut s = seed as u64; - - if N == 0 { - *X_PTR = (s as u32).to_ne_bytes(); - } else { - I = if N == 31 || N == 7 { 3 } else { 1 }; - - J = 0; - - for k in 0..usize::from(N) { - s = lcg64_step(s); - - // Conversion will always succeed (value is a 32-bit right- - // shift of a 64-bit integer). - *X_PTR.add(k) = u32::try_from(s >> 32).unwrap().to_ne_bytes(); - } - - // ensure X contains at least one odd number - *X_PTR = (u32::from_ne_bytes(*X_PTR) | 1).to_ne_bytes(); - } -} diff --git a/src/header/stdlib/sort.rs b/src/header/stdlib/sort.rs index 5a0cd5faf9..943707f0d0 100644 --- a/src/header/stdlib/sort.rs +++ b/src/header/stdlib/sort.rs @@ -1,18 +1,21 @@ -use crate::platform::types::*; +use crate::platform::types::{c_char, c_int, c_void, size_t}; -pub fn introsort( +pub unsafe fn introsort( base: *mut c_char, nel: size_t, width: size_t, comp: extern "C" fn(*const c_void, *const c_void) -> c_int, ) { + /*TODO: introsort is much faster than insertion sort, but is currently broken let maxdepth = 2 * log2(nel); introsort_helper(base, nel, width, maxdepth, comp); + */ + unsafe { insertion_sort(base, nel, width, comp) }; } // NOTE: if num is 0, the result should be considered undefined fn log2(num: size_t) -> size_t { - const IS_32_BIT: bool = size_t::max_value() as u32 as size_t == size_t::max_value(); + const IS_32_BIT: bool = size_t::MAX as u32 as size_t == size_t::MAX; let max_bits = if IS_32_BIT { 31 @@ -24,7 +27,7 @@ fn log2(num: size_t) -> size_t { max_bits - num.to_le().leading_zeros() as size_t } -fn introsort_helper( +unsafe fn introsort_helper( mut base: *mut c_char, mut nel: size_t, width: size_t, @@ -38,23 +41,23 @@ fn introsort_helper( // to introsort_helper() loop { if nel < THRESHOLD { - insertion_sort(base, nel, width, comp); + unsafe { insertion_sort(base, nel, width, comp) }; break; } else if nel > 1 { if maxdepth == 0 { - heapsort(base, nel, width, comp); + unsafe { heapsort(base, nel, width, comp) }; break; } else { - let (left, right) = partition(base, nel, width, comp); + let (left, right) = unsafe { partition(base, nel, width, comp) }; let right_base = unsafe { base.add((right + 1) * width) }; let right_nel = nel - (right + 1); maxdepth -= 1; if left < nel - right { - introsort_helper(base, left, width, maxdepth, comp); + unsafe { introsort_helper(base, left, width, maxdepth, comp) }; base = right_base; nel = right_nel; } else { - introsort_helper(right_base, right_nel, width, maxdepth, comp); + unsafe { introsort_helper(right_base, right_nel, width, maxdepth, comp) }; nel = left; } } @@ -62,7 +65,7 @@ fn introsort_helper( } } -fn insertion_sort( +unsafe fn insertion_sort( base: *mut c_char, nel: size_t, width: size_t, @@ -73,7 +76,7 @@ fn insertion_sort( let current = unsafe { base.add(j * width) }; let prev = unsafe { base.add((j + 1) * width) }; if comp(current as *const c_void, prev as *const c_void) > 0 { - swap(current, prev, width); + unsafe { swap(current, prev, width) }; } else { break; } @@ -81,24 +84,24 @@ fn insertion_sort( } } -fn heapsort( +unsafe fn heapsort( base: *mut c_char, nel: size_t, width: size_t, comp: extern "C" fn(*const c_void, *const c_void) -> c_int, ) { - heapify(base, nel, width, comp); + unsafe { heapify(base, nel, width, comp) }; let mut end = nel - 1; while end > 0 { let end_ptr = unsafe { base.add(end * width) }; - swap(end_ptr, base, width); + unsafe { swap(end_ptr, base, width) }; end -= 1; - heap_sift_down(base, 0, end, width, comp); + unsafe { heap_sift_down(base, 0, end, width, comp) }; } } -fn heapify( +unsafe fn heapify( base: *mut c_char, nel: size_t, width: size_t, @@ -108,11 +111,11 @@ fn heapify( let last_parent = (nel - 2) / 2; for start in (0..=last_parent).rev() { - heap_sift_down(base, start, nel - 1, width, comp); + unsafe { heap_sift_down(base, start, nel - 1, width, comp) }; } } -fn heap_sift_down( +unsafe fn heap_sift_down( base: *mut c_char, start: size_t, end: size_t, @@ -145,14 +148,14 @@ fn heap_sift_down( if swap_idx == root { break; } else { - swap(root_ptr, swap_ptr, width); + unsafe { swap(root_ptr, swap_ptr, width) }; root = swap_idx; } } } #[inline] -fn partition( +unsafe fn partition( base: *mut c_char, nel: size_t, width: size_t, @@ -160,7 +163,7 @@ fn partition( ) -> (size_t, size_t) { // calculate the median of the first, middle, and last elements and use it as the pivot // to do fewer comparisons, also swap the elements into their correct positions - let mut pivot = median_of_three(base, nel, width, comp); + let mut pivot = unsafe { median_of_three(base, nel, width, comp) }; let mut i = 1; let mut j = 1; @@ -175,14 +178,14 @@ fn partition( let comparison = comp(j_ptr as *const c_void, pivot_ptr as *const c_void); if comparison < 0 { - swap(i_ptr, j_ptr, width); + unsafe { swap(i_ptr, j_ptr, width) }; if i == pivot { pivot = j; } i += 1; j += 1; } else if comparison > 0 { - swap(j_ptr, n_ptr, width); + unsafe { swap(j_ptr, n_ptr, width) }; if n == pivot { pivot = j; } @@ -195,7 +198,7 @@ fn partition( (i, n) } -fn median_of_three( +unsafe fn median_of_three( base: *mut c_char, nel: size_t, width: size_t, @@ -206,12 +209,12 @@ fn median_of_three( let mid = unsafe { base.add(pivot * width) }; let last = unsafe { base.add((nel - 1) * width) }; if comp(mid as *const c_void, base as *const c_void) < 0 { - swap(mid, base, width); + unsafe { swap(mid, base, width) }; } if comp(last as *const c_void, mid as *const c_void) < 0 { - swap(mid, last, width); + unsafe { swap(mid, last, width) }; if comp(mid as *const c_void, base as *const c_void) < 0 { - swap(mid, base, width); + unsafe { swap(mid, base, width) }; } } @@ -219,15 +222,15 @@ fn median_of_three( } #[inline] -fn swap(mut ptr1: *mut c_char, mut ptr2: *mut c_char, mut width: size_t) { +unsafe fn swap(mut ptr1: *mut c_char, mut ptr2: *mut c_char, mut width: size_t) { use core::mem; const BUFSIZE: usize = 128; let mut buffer = mem::MaybeUninit::<[c_char; BUFSIZE]>::uninit(); while width > 0 { - let copy_size = BUFSIZE.min(width as usize); - let buf = buffer.as_mut_ptr() as *mut c_char; + let copy_size = BUFSIZE.min(width); + let buf = buffer.as_mut_ptr().cast::(); unsafe { buf.copy_from_nonoverlapping(ptr1, copy_size); diff --git a/src/header/string/cbindgen.toml b/src/header/string/cbindgen.toml index 9cee2c5da5..e0f04f0a33 100644 --- a/src/header/string/cbindgen.toml +++ b/src/header/string/cbindgen.toml @@ -1,9 +1,25 @@ -sys_includes = ["stddef.h", "stdint.h", "strings.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/string.h.html +# +# Spec quotations relating to includes: +# - "The header shall define NULL and size_t as described in ." +# - "The header shall define the locale_t type as described in ." +# - "Inclusion of the header may also make visible all symbols from ." +sys_includes = ["stddef.h", "features.h", "strings.h"] include_guard = "_RELIBC_STRING_H" +after_includes = """ +#include // for locale_t from locale.h + +// POSIX doesn't require importing strings.h but mesa needs it +#if defined(_BSD_SOURCE) || defined(_GNU_SOURCE) +#include +#endif +""" language = "C" style = "Tag" no_includes = true cpp_compat = true +# make size_t actually size_t instead of uintptr_t (which would require stdint.h) +usize_is_size_t = true [enum] prefix_with_name = true diff --git a/src/header/string/mod.rs b/src/header/string/mod.rs index 320f3bbebc..45621c2dcc 100644 --- a/src/header/string/mod.rs +++ b/src/header/string/mod.rs @@ -1,39 +1,58 @@ -//! string implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/string.h.html +//! `string.h` implementation. +//! +//! See . -use core::{mem, ptr, slice, usize}; +use core::{iter::once, mem, ptr, slice}; use cbitset::BitSet256; use crate::{ header::{errno::*, signal}, - platform::{self, types::*}, + iter::{NulTerminated, NulTerminatedInclusive, SrcDstPtrIter}, + platform::{ + self, + types::{c_char, c_int, c_void, size_t}, + }, + raw_cell::RawCell, }; -#[no_mangle] +use super::{bits_locale_t::locale_t, locale::THREAD_LOCALE}; + +/// See . +/// +/// # Safety +/// The caller must ensure that: +/// - `n` is not longer than the memory area pointed to by `s1`, and +/// - `n` is not longer than the memory area pointed to by `s2`. +#[unsafe(no_mangle)] pub unsafe extern "C" fn memccpy( - dest: *mut c_void, - src: *const c_void, + s1: *mut c_void, + s2: *const c_void, c: c_int, n: size_t, ) -> *mut c_void { - let to = memchr(src, c, n); + let to = unsafe { memchr(s2, c, n) }; + let dist = if to.is_null() { + n + } else { + ((to as usize) - (s2 as usize)) + 1 + }; + unsafe { memcpy(s1, s2, dist) }; if to.is_null() { - return to; + ptr::null_mut() + } else { + unsafe { s1.cast::().add(dist).cast::() } } - let dist = (to as usize) - (src as usize); - if memcpy(dest, src, dist).is_null() { - return ptr::null_mut(); - } - (dest as *mut u8).add(dist + 1) as *mut c_void } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn memchr( haystack: *const c_void, needle: c_int, len: size_t, ) -> *mut c_void { - let haystack = slice::from_raw_parts(haystack as *const u8, len as usize); + let haystack = unsafe { slice::from_raw_parts(haystack.cast::(), len) }; match memchr::memchr(needle as u8, haystack) { Some(index) => haystack[index..].as_ptr() as *mut c_void, @@ -41,79 +60,123 @@ pub unsafe extern "C" fn memchr( } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn memcmp(s1: *const c_void, s2: *const c_void, n: size_t) -> c_int { - let (div, rem) = (n / mem::size_of::(), n % mem::size_of::()); - let mut a = s1 as *const usize; - let mut b = s2 as *const usize; + let (div, rem) = (n / mem::size_of::(), n % mem::size_of::()); + let mut a = s1.cast::(); + let mut b = s2.cast::(); for _ in 0..div { - if *a != *b { + // SAFETY: `s1` and `s2` are `*const c_void`, which only guarantees byte + // alignment. Hence `a` and `b` may be unaligned. + if unsafe { a.read_unaligned() } != unsafe { b.read_unaligned() } { for i in 0..mem::size_of::() { - let c = *(a as *const u8).add(i); - let d = *(b as *const u8).add(i); + let c = unsafe { *(a.cast::()).add(i) }; + let d = unsafe { *(b.cast::()).add(i) }; if c != d { - return c as c_int - d as c_int; + return c_int::from(c) - c_int::from(d); } } unreachable!() } - a = a.offset(1); - b = b.offset(1); + a = unsafe { a.offset(1) }; + b = unsafe { b.offset(1) }; } - let mut a = a as *const u8; - let mut b = b as *const u8; + let mut a = a.cast::(); + let mut b = b.cast::(); for _ in 0..rem { - if *a != *b { - return *a as c_int - *b as c_int; + if unsafe { *a } != unsafe { *b } { + return c_int::from(unsafe { *a }) - c_int::from(unsafe { *b }); } - a = a.offset(1); - b = b.offset(1); + a = unsafe { a.offset(1) }; + b = unsafe { b.offset(1) }; } 0 } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that *either*: +/// - `n` is 0, *or* +/// - `s1` is convertible to a `&mut [MaybeUninit]` with length `n`, +/// and +/// - `s2` is convertible to a `&[MaybeUninit]` with length `n`. +#[unsafe(no_mangle)] pub unsafe extern "C" fn memcpy(s1: *mut c_void, s2: *const c_void, n: size_t) -> *mut c_void { - let mut i = 0; - while i + 7 < n { - *(s1.add(i) as *mut u64) = *(s2.add(i) as *const u64); - i += 8; - } - while i < n { - *(s1 as *mut u8).add(i) = *(s2 as *const u8).add(i); - i += 1; + for i in 0..n { + unsafe { *s1.cast::().add(i) = *s2.cast::().add(i) }; } s1 } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that: +/// - `haystack` is convertible to a `&[u8]` with length `haystacklen`, and +/// - `needle` is convertible to a `&[u8]` with length `needlelen`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memmem( + haystack: *const c_void, + haystacklen: size_t, + needle: *const c_void, + needlelen: size_t, +) -> *mut c_void { + match needlelen { + // Required to satisfy spec (would otherwise cause .windows() to panic) + 0 => haystack, + _ => { + // SAFETY: the caller is required to ensure that the provided + // pointers are valid. + let haystack_slice = + unsafe { slice::from_raw_parts(haystack.cast::(), haystacklen) }; + let needle_slice = unsafe { slice::from_raw_parts(needle.cast::(), needlelen) }; + + // At this point, .windows() will receive a nonzero `needlelen` and + // thus not panic. + match haystack_slice + .windows(needlelen) + .find(|&haystack_window| haystack_window == needle_slice) + { + Some(match_slice) => match_slice.as_ptr().cast(), + None => ptr::null(), + } + } + } + .cast_mut() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn memmove(s1: *mut c_void, s2: *const c_void, n: size_t) -> *mut c_void { - if s2 < s1 as *const c_void { + if s2 < s1.cast_const() { // copy from end let mut i = n; while i != 0 { i -= 1; - *(s1 as *mut u8).add(i) = *(s2 as *const u8).add(i); + unsafe { *s1.cast::().add(i) = *s2.cast::().add(i) }; } } else { // copy from beginning let mut i = 0; while i < n { - *(s1 as *mut u8).add(i) = *(s2 as *const u8).add(i); + unsafe { *s1.cast::().add(i) = *s2.cast::().add(i) }; i += 1; } } s1 } -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] pub unsafe extern "C" fn memrchr( haystack: *const c_void, needle: c_int, len: size_t, ) -> *mut c_void { - let haystack = slice::from_raw_parts(haystack as *const u8, len as usize); + let haystack = unsafe { slice::from_raw_parts(haystack.cast::(), len) }; match memchr::memrchr(needle as u8, haystack) { Some(index) => haystack[index..].as_ptr() as *mut c_void, @@ -121,58 +184,140 @@ pub unsafe extern "C" fn memrchr( } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn memset(s: *mut c_void, c: c_int, n: size_t) -> *mut c_void { for i in 0..n { - *(s as *mut u8).add(i) = c as u8; + unsafe { *s.cast::().add(i) = c as u8 }; } s } -#[no_mangle] -pub unsafe extern "C" fn strchr(mut s: *const c_char, c: c_int) -> *mut c_char { - let c = c as c_char; - while *s != 0 { - if *s == c { - return s as *mut c_char; - } - s = s.offset(1); - } - ptr::null_mut() -} - -#[no_mangle] -pub unsafe extern "C" fn strcmp(s1: *const c_char, s2: *const c_char) -> c_int { - strncmp(s1, s2, usize::MAX) -} - -#[no_mangle] -pub unsafe extern "C" fn strcoll(s1: *const c_char, s2: *const c_char) -> c_int { - // relibc has no locale stuff (yet) - strcmp(s1, s2) -} - -#[no_mangle] -pub unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char { - let mut i = 0; - +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stpcpy(mut s1: *mut c_char, mut s2: *const c_char) -> *mut c_char { loop { - let byte = *src.offset(i); - *dst.offset(i) = byte; + unsafe { *s1 = *s2 }; - if byte == 0 { + if unsafe { *s1 } == 0 { break; } - i += 1; + s1 = unsafe { s1.add(1) }; + s2 = unsafe { s2.add(1) }; + } + + s1 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stpncpy( + mut s1: *mut c_char, + mut s2: *const c_char, + mut n: size_t, +) -> *mut c_char { + while n > 0 { + unsafe { *s1 = *s2 }; + + if unsafe { *s1 } == 0 { + break; + } + + n -= 1; + s1 = unsafe { s1.add(1) }; + s2 = unsafe { s2.add(1) }; + } + + unsafe { memset(s1.cast(), 0, n) }; + + s1 +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcasestr(haystack: *const c_char, needle: *const c_char) -> *mut c_char { + unsafe { inner_strstr(haystack, needle, !32) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcat(s1: *mut c_char, s2: *const c_char) -> *mut c_char { + unsafe { strncat(s1, s2, usize::MAX) } +} + +/// See . +/// +/// # Safety +/// The caller is required to ensure that `s` is a valid pointer to a buffer +/// containing at least one nul value. The pointed-to buffer must not be +/// modified for the duration of the call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strchr(s: *const c_char, c: c_int) -> *mut c_char { + let c_as_c_char = c as c_char; + + // We iterate over non-mut references and thus need to coerce the + // resulting reference via a *const pointer before we can get our *mut. + // SAFETY: the caller is required to ensure that s points to a valid + // nul-terminated buffer. + let ptr: *const c_char = + match unsafe { NulTerminatedInclusive::new(s) }.find(|&&sc| sc == c_as_c_char) { + Some(sc_ref) => sc_ref, + None => ptr::null(), + }; + ptr.cast_mut() +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strchrnul(s: *const c_char, c: c_int) -> *mut c_char { + let mut s = s.cast_mut(); + loop { + if unsafe { *s } == c as _ { + break; + } + if unsafe { *s } == 0 { + break; + } + s = unsafe { s.add(1) }; + } + s +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcmp(s1: *const c_char, s2: *const c_char) -> c_int { + unsafe { strncmp(s1, s2, usize::MAX) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcoll_l(s1: *const c_char, s2: *const c_char, _loc: locale_t) -> c_int { + // relibc has no locale stuff (yet) + unsafe { strcmp(s1, s2) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcoll(s1: *const c_char, s2: *const c_char) -> c_int { + unsafe { strcoll_l(s1, s2, THREAD_LOCALE as locale_t) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char { + let src_iter = unsafe { NulTerminated::new(src).unwrap() }; + let src_dest_iter = unsafe { SrcDstPtrIter::new(src_iter.chain(once(&0)), dst) }; + for (src_item, dst_item) in src_dest_iter { + dst_item.write(*src_item); } dst } pub unsafe fn inner_strspn(s1: *const c_char, s2: *const c_char, cmp: bool) -> size_t { - let mut s1 = s1 as *const u8; - let mut s2 = s2 as *const u8; + let mut s1 = s1.cast::(); + let mut s2 = s2.cast::(); // The below logic is effectively ripped from the musl implementation. It // works by placing each byte as it's own bit in an array of numbers. Each @@ -181,200 +326,251 @@ pub unsafe fn inner_strspn(s1: *const c_char, s2: *const c_char, cmp: bool) -> s let mut set = BitSet256::new(); - while *s2 != 0 { - set.insert(*s2 as usize); - s2 = s2.offset(1); + while unsafe { *s2 } != 0 { + set.insert(unsafe { *s2 } as usize); + s2 = unsafe { s2.offset(1) }; } let mut i = 0; - while *s1 != 0 { - if set.contains(*s1 as usize) != cmp { + while unsafe { *s1 } != 0 { + if set.contains(unsafe { *s1 } as usize) != cmp { break; } i += 1; - s1 = s1.offset(1); + s1 = unsafe { s1.offset(1) }; } i } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strcspn(s1: *const c_char, s2: *const c_char) -> size_t { - inner_strspn(s1, s2, false) + unsafe { inner_strspn(s1, s2, false) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strdup(s1: *const c_char) -> *mut c_char { - strndup(s1, usize::MAX) + unsafe { strndup(s1, usize::MAX) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strerror_l(errnum: c_int, _loc: locale_t) -> *mut c_char { + use core::fmt::Write; + + static strerror_buf: RawCell<[u8; STRERROR_MAX]> = RawCell::new([0; STRERROR_MAX]); + let mut w = platform::StringWriter(unsafe { strerror_buf.unsafe_mut().as_mut_ptr() }, unsafe { + strerror_buf.unsafe_ref().len() + }); + + let _ = match STR_ERROR.get(errnum as usize) { + Some(e) => w.write_str(e), + None => w.write_fmt(format_args!("Unknown error {}", errnum)), + }; + + (unsafe { strerror_buf.unsafe_mut().as_mut_ptr() }).cast::() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strerror(errnum: c_int) -> *mut c_char { + unsafe { strerror_l(errnum, THREAD_LOCALE as locale_t) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: size_t) -> c_int { + let msg = unsafe { strerror(errnum) }; + let len = unsafe { strlen(msg) }; + + if len >= buflen { + if buflen != 0 { + unsafe { memcpy(buf.cast::(), msg as *const c_void, buflen - 1) }; + unsafe { *buf.add(buflen - 1) = 0 }; + } + return ERANGE as c_int; + } + unsafe { memcpy(buf.cast::(), msg as *const c_void, len + 1) }; + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strlcat(dst: *mut c_char, src: *const c_char, dstsize: size_t) -> size_t { + let dst_len = unsafe { strnlen(dst, dstsize) }; + let d = unsafe { dst.add(dst_len) }; + let src_len = unsafe { strlcpy(d, src, dstsize - dst_len) }; + src_len + if dst_len > dstsize { dstsize } else { dst_len } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strsep(str_: *mut *mut c_char, sep: *const c_char) -> *mut c_char { + let s = unsafe { *str_ }; + if s.is_null() { + return ptr::null_mut(); + } + let mut end = unsafe { s.add(strcspn(s, sep)) }; + if unsafe { *end } != 0 { + unsafe { *end = 0 }; + end = unsafe { end.add(1) }; + } else { + end = ptr::null_mut(); + } + unsafe { *str_ = end }; + s +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strlcpy(dst: *mut c_char, src: *const c_char, dstsize: size_t) -> size_t { + let mut i = 0; + + if dstsize != 0 { + while unsafe { *src.add(i) } != 0 && i < dstsize - 1 { + unsafe { + *dst.add(i) = *src.add(i); + } + i += 1; + } + unsafe { + *dst.add(i) = 0; + } + } + + while unsafe { *src.add(i) } != 0 { + i += 1; + } + + i as size_t +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strlen(s: *const c_char) -> size_t { + unsafe { NulTerminated::new(s) } + .map(|s| s.count()) + .unwrap_or(0) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strncat(s1: *mut c_char, s2: *const c_char, n: size_t) -> *mut c_char { + let len = unsafe { strlen(s1.cast()) }; + let mut i = 0; + while i < n { + let b = unsafe { *s2.add(i) }; + if b == 0 { + break; + } + + unsafe { *s1.add(len + i) = b }; + i += 1; + } + unsafe { *s1.add(len + i) = 0 }; + + s1 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strncmp(s1: *const c_char, s2: *const c_char, n: size_t) -> c_int { + for i in 0..n { + // These must be cast as u8 to have correct comparisons + let a = unsafe { *s1.add(i) } as u8; + let b = unsafe { *s2.add(i) } as u8; + if a != b || a == 0 { + return c_int::from(a) - c_int::from(b); + } + } + + 0 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strncpy(s1: *mut c_char, s2: *const c_char, n: size_t) -> *mut c_char { + unsafe { stpncpy(s1, s2, n) }; + s1 +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strndup(s1: *const c_char, size: size_t) -> *mut c_char { - let len = strnlen(s1, size); + let len = unsafe { strnlen(s1, size) }; // the "+ 1" is to account for the NUL byte - let buffer = platform::alloc(len + 1) as *mut c_char; + let buffer = unsafe { platform::alloc(len + 1) }.cast::(); if buffer.is_null() { - platform::errno = ENOMEM as c_int; + platform::ERRNO.set(ENOMEM as c_int); } else { //memcpy(buffer, s1, len) for i in 0..len { - *buffer.add(i) = *s1.add(i); + unsafe { *buffer.add(i) = *s1.add(i) }; } - *buffer.add(len) = 0; + unsafe { *buffer.add(len) = 0 }; } buffer } -#[no_mangle] -pub unsafe extern "C" fn strerror(errnum: c_int) -> *mut c_char { - use core::fmt::Write; - - static mut strerror_buf: [u8; 256] = [0; 256]; - - let mut w = platform::StringWriter(strerror_buf.as_mut_ptr(), strerror_buf.len()); - - if errnum >= 0 && errnum < STR_ERROR.len() as c_int { - let _ = w.write_str(STR_ERROR[errnum as usize]); - } else { - let _ = w.write_fmt(format_args!("Unknown error {}", errnum)); - } - - strerror_buf.as_mut_ptr() as *mut c_char -} - -#[no_mangle] -pub unsafe extern "C" fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: size_t) -> c_int { - let msg = strerror(errnum); - let len = strlen(msg); - - if len >= buflen { - if buflen != 0 { - memcpy(buf as *mut c_void, msg as *const c_void, buflen - 1); - *buf.add(buflen - 1) = 0; - } - return ERANGE as c_int; - } - memcpy(buf as *mut c_void, msg as *const c_void, len + 1); - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn strlen(s: *const c_char) -> size_t { - strnlen(s, usize::MAX) -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strnlen(s: *const c_char, size: size_t) -> size_t { - let mut i = 0; - while i < size { - if *s.add(i) == 0 { - break; - } - i += 1; - } - i as size_t + unsafe { NulTerminated::new(s).unwrap() }.take(size).count() } -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strnlen_s(s: *const c_char, size: size_t) -> size_t { if s.is_null() { 0 } else { - strnlen(s, size) + unsafe { strnlen(s, size) } } } -#[no_mangle] -pub unsafe extern "C" fn strcat(s1: *mut c_char, s2: *const c_char) -> *mut c_char { - strncat(s1, s2, usize::MAX) -} - -#[no_mangle] -pub unsafe extern "C" fn strncat(s1: *mut c_char, s2: *const c_char, n: size_t) -> *mut c_char { - let len = strlen(s1 as *const c_char); - let mut i = 0; - while i < n { - let b = *s2.add(i); - if b == 0 { - break; - } - - *s1.add(len + i) = b; - i += 1; - } - *s1.add(len + i) = 0; - - s1 -} - -#[no_mangle] -pub unsafe extern "C" fn strncmp(s1: *const c_char, s2: *const c_char, n: size_t) -> c_int { - let s1 = core::slice::from_raw_parts(s1 as *const c_uchar, n); - let s2 = core::slice::from_raw_parts(s2 as *const c_uchar, n); - - for (&a, &b) in s1.iter().zip(s2.iter()) { - let val = (a as c_int) - (b as c_int); - if a != b || a == 0 { - return val; - } - } - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: size_t) -> *mut c_char { - let mut i = 0; - - while *src.add(i) != 0 && i < n { - *dst.add(i) = *src.add(i); - i += 1; - } - - for i in i..n { - *dst.add(i) = 0; - } - - dst -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strpbrk(s1: *const c_char, s2: *const c_char) -> *mut c_char { - let p = s1.add(strcspn(s1, s2)); - if *p != 0 { - p as *mut c_char + let p = unsafe { s1.add(strcspn(s1, s2)) }; + if unsafe { *p } != 0 { + p.cast_mut() } else { ptr::null_mut() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strrchr(s: *const c_char, c: c_int) -> *mut c_char { - let len = strlen(s) as isize; - let c = c as i8; + let len = unsafe { strlen(s) } as isize; + let c = c as c_char; let mut i = len - 1; while i >= 0 { - if *s.offset(i) == c { - return s.offset(i) as *mut c_char; + if unsafe { *s.offset(i) } == c { + return unsafe { s.offset(i) }.cast_mut(); } i -= 1; } ptr::null_mut() } -#[no_mangle] -pub unsafe extern "C" fn strsignal(sig: c_int) -> *const c_char { - signal::_signal_strings +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strsignal(sig: c_int) -> *mut c_char { + signal::SIGNAL_STRINGS .get(sig as usize) - .unwrap_or(&signal::_signal_strings[0]) // Unknown signal message - .as_ptr() as *const c_char + .unwrap_or(&signal::SIGNAL_STRINGS[0]) // Unknown signal message + .as_ptr() as *mut c_char } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strspn(s1: *const c_char, s2: *const c_char) -> size_t { - inner_strspn(s1, s2, true) + unsafe { inner_strspn(s1, s2, true) } } unsafe fn inner_strstr( @@ -382,82 +578,93 @@ unsafe fn inner_strstr( needle: *const c_char, mask: c_char, ) -> *mut c_char { - while *haystack != 0 { + while unsafe { *haystack } != 0 { let mut i = 0; loop { - if *needle.offset(i) == 0 { + if unsafe { *needle.offset(i) } == 0 { // We reached the end of the needle, everything matches this far - return haystack as *mut c_char; + return haystack.cast_mut(); } - if *haystack.offset(i) & mask != *needle.offset(i) & mask { + if unsafe { *haystack.offset(i) } & mask != unsafe { *needle.offset(i) } & mask { break; } i += 1; } - haystack = haystack.offset(1); + haystack = unsafe { haystack.offset(1) }; } ptr::null_mut() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strstr(haystack: *const c_char, needle: *const c_char) -> *mut c_char { - inner_strstr(haystack, needle, !0) -} -#[no_mangle] -pub unsafe extern "C" fn strcasestr(haystack: *const c_char, needle: *const c_char) -> *mut c_char { - inner_strstr(haystack, needle, !32) + unsafe { inner_strstr(haystack, needle, !0) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtok(s1: *mut c_char, delimiter: *const c_char) -> *mut c_char { static mut HAYSTACK: *mut c_char = ptr::null_mut(); - strtok_r(s1, delimiter, &mut HAYSTACK) + unsafe { strtok_r(s1, delimiter, &raw mut HAYSTACK) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strtok_r( s: *mut c_char, delimiter: *const c_char, lasts: *mut *mut c_char, ) -> *mut c_char { - // Loosely based on GLIBC implementation + // musl returns null if both s and lasts are null, it sets s to lasts otherwise let mut haystack = s; if haystack.is_null() { - if (*lasts).is_null() { + if (unsafe { *lasts }).is_null() { return ptr::null_mut(); } - haystack = *lasts; + haystack = unsafe { *lasts }; } // Skip past any extra delimiter left over from previous call - haystack = haystack.add(strspn(haystack, delimiter)); - if *haystack == 0 { - *lasts = ptr::null_mut(); + haystack = unsafe { haystack.add(strspn(haystack, delimiter)) }; + if unsafe { *haystack } == 0 { + unsafe { *lasts = haystack }; return ptr::null_mut(); } // Build token by injecting null byte into delimiter let token = haystack; - haystack = strpbrk(token, delimiter); + haystack = unsafe { strpbrk(token, delimiter) }; if !haystack.is_null() { - haystack.write(0); - haystack = haystack.add(1); - *lasts = haystack; + unsafe { haystack.write(0) }; + haystack = unsafe { haystack.add(1) }; + unsafe { *lasts = haystack }; } else { - *lasts = ptr::null_mut(); + unsafe { *lasts = token.add(strlen(token)) }; } token } -#[no_mangle] -pub unsafe extern "C" fn strxfrm(s1: *mut c_char, s2: *const c_char, n: size_t) -> size_t { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strxfrm_l( + s1: *mut c_char, + s2: *const c_char, + n: size_t, + _loc: locale_t, +) -> size_t { // relibc has no locale stuff (yet) - let len = strlen(s2); + let len = unsafe { strlen(s2) }; if len < n { - strcpy(s1, s2); + unsafe { strcpy(s1, s2) }; } len } + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strxfrm(s1: *mut c_char, s2: *const c_char, n: size_t) -> size_t { + unsafe { strxfrm_l(s1, s2, n, THREAD_LOCALE as locale_t) } +} diff --git a/src/header/strings/cbindgen.toml b/src/header/strings/cbindgen.toml index 2897bd5423..4479f685d6 100644 --- a/src/header/strings/cbindgen.toml +++ b/src/header/strings/cbindgen.toml @@ -1,9 +1,22 @@ -sys_includes = ["stddef.h", "stdint.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/strings.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the locale_t type as described in ." +# - "The header shall define the size_t type as described in ." +# +# sys/types.h gets size_t from stddef.h (just include stddef.h here instead of importing all of sys/types.h) +# features.h required for deprecated annotations +sys_includes = ["features.h", "stddef.h"] include_guard = "_RELIBC_STRINGS_H" +after_includes = """ +#include // for locale_t from locale.h +""" language = "C" style = "Tag" no_includes = true cpp_compat = true +# make size_t actually size_t instead of uintptr_t (which would require stdint.h) +usize_is_size_t = true [enum] prefix_with_name = true diff --git a/src/header/strings/mod.rs b/src/header/strings/mod.rs index a902c414d4..9f33d65f54 100644 --- a/src/header/strings/mod.rs +++ b/src/header/strings/mod.rs @@ -1,28 +1,71 @@ -//! strings implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/strings.h.html +//! `strings.h` implementation. +//! +//! See . -use core::ptr; +use core::{ + arch, + iter::{once, zip}, + ptr, +}; use crate::{ header::{ctype, string}, - platform::types::*, + iter::NulTerminated, + platform::types::{c_char, c_int, c_long, c_longlong, c_void, size_t}, }; -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `bcmp()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn bcmp(first: *const c_void, second: *const c_void, n: size_t) -> c_int { - string::memcmp(first, second, n) + unsafe { string::memcmp(first, second, n) } } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `bcopy()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn bcopy(src: *const c_void, dst: *mut c_void, n: size_t) { - ptr::copy(src as *const u8, dst as *mut u8, n); + unsafe { + ptr::copy(src.cast::(), dst.cast::(), n); + } } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `bzero()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn bzero(dst: *mut c_void, n: size_t) { - ptr::write_bytes(dst as *mut u8, 0, n); + unsafe { + ptr::write_bytes(dst.cast::(), 0, n); + } } -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn explicit_bzero(s: *mut c_void, n: size_t) { + for i in 0..n { + unsafe { + *s.cast::().add(i) = 0_u8; + } + } + unsafe { + arch::asm!(""); + } +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn ffs(i: c_int) -> c_int { if i == 0 { return 0; @@ -30,36 +73,85 @@ pub extern "C" fn ffs(i: c_int) -> c_int { 1 + i.trailing_zeros() as c_int } -#[no_mangle] -pub unsafe extern "C" fn index(s: *const c_char, c: c_int) -> *mut c_char { - string::strchr(s, c) -} - -#[no_mangle] -pub unsafe extern "C" fn rindex(s: *const c_char, c: c_int) -> *mut c_char { - string::strrchr(s, c) -} - -#[no_mangle] -pub unsafe extern "C" fn strcasecmp(first: *const c_char, second: *const c_char) -> c_int { - strncasecmp(first, second, size_t::max_value()) -} - -#[no_mangle] -pub unsafe extern "C" fn strncasecmp( - mut first: *const c_char, - mut second: *const c_char, - mut n: size_t, -) -> c_int { - while n > 0 && (*first != 0 || *second != 0) { - let cmp = ctype::tolower(*first as c_int) - ctype::tolower(*second as c_int); - if cmp != 0 { - return cmp; - } - - first = first.offset(1); - second = second.offset(1); - n -= 1; +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn ffsl(i: c_long) -> c_int { + if i == 0 { + return 0; } - 0 + 1 + i.trailing_zeros() as c_int +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn ffsll(i: c_longlong) -> c_int { + if i == 0 { + return 0; + } + 1 + i.trailing_zeros() as c_int +} + +/// See . +/// +/// # Deprecation +/// The `index()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn index(s: *const c_char, c: c_int) -> *mut c_char { + unsafe { string::strchr(s, c) } +} + +/// See . +/// +/// # Deprecation +/// The `rindex()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rindex(s: *const c_char, c: c_int) -> *mut c_char { + unsafe { string::strrchr(s, c) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strcasecmp(s1: *const c_char, s2: *const c_char) -> c_int { + // SAFETY: the caller must ensure that s1 and s2 point to nul-terminated buffers. + let s1_iter = unsafe { NulTerminated::new(s1).unwrap() }.chain(once(&0)); + let s2_iter = unsafe { NulTerminated::new(s2).unwrap() }.chain(once(&0)); + + let zipped = zip(s1_iter, s2_iter); + inner_casecmp(zipped) +} + +// TODO: needs locale_t +// See . +// #[unsafe(no_mangle)] +/*pub extern "C" fn strcasecmp_l(s1: *const c_char, s2: *const c_char, locale: locale_t) -> c_int { + unimplemented!(); +}*/ + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strncasecmp(s1: *const c_char, s2: *const c_char, n: size_t) -> c_int { + // SAFETY: the caller must ensure that s1 and s2 point to nul-terminated buffers. + let s1_iter = unsafe { NulTerminated::new(s1).unwrap() }.chain(once(&0)); + let s2_iter = unsafe { NulTerminated::new(s2).unwrap() }.chain(once(&0)); + + let zipped = zip(s1_iter, s2_iter).take(n); + inner_casecmp(zipped) +} + +// TODO: needs locale_t +// See . +// #[unsafe(no_mangle)] +/*pub extern "C" fn strncasecmp_l(s1: *const c_char, s2: *const c_char, n: size_t, locale: locale_t) -> c_int { + unimplemented!(); +}*/ + +/// Given two zipped `&c_char` iterators, either find the first comparison != 0, or return 0. +fn inner_casecmp<'a>(iterator: impl Iterator) -> c_int { + let cmp_iter = iterator.map(|(&c1, &c2)| ctype::tolower(c1.into()) - ctype::tolower(c2.into())); + let mut skip_iter = cmp_iter.skip_while(|&cmp| cmp == 0); + skip_iter.next().unwrap_or(0) } diff --git a/src/header/sys_auxv/mod.rs b/src/header/sys_auxv/mod.rs index 184eba1a76..dbd26a8c75 100644 --- a/src/header/sys_auxv/mod.rs +++ b/src/header/sys_auxv/mod.rs @@ -1,36 +1,13 @@ -//! sys/auxv.h implementation +//! `sys/auxv.h` implementation. +//! +//! Non-POSIX, see . -use crate::platform::types::*; +use crate::platform::types::c_ulong; -pub const AT_NULL: usize = 0; /* End of vector */ -pub const AT_IGNORE: usize = 1; /* Entry should be ignored */ -pub const AT_EXECFD: usize = 2; /* File descriptor of program */ -pub const AT_PHDR: usize = 3; /* Program headers for program */ -pub const AT_PHENT: usize = 4; /* Size of program header entry */ -pub const AT_PHNUM: usize = 5; /* Number of program headers */ -pub const AT_PAGESZ: usize = 6; /* System page size */ -pub const AT_BASE: usize = 7; /* Base address of interpreter */ -pub const AT_FLAGS: usize = 8; /* Flags */ -pub const AT_ENTRY: usize = 9; /* Entry point of program */ -pub const AT_NOTELF: usize = 10; /* Program is not ELF */ -pub const AT_UID: usize = 11; /* Real uid */ -pub const AT_EUID: usize = 12; /* Effective uid */ -pub const AT_GID: usize = 13; /* Real gid */ -pub const AT_EGID: usize = 14; /* Effective gid */ -pub const AT_CLKTCK: usize = 17; /* Frequency of times() */ -pub const AT_PLATFORM: usize = 15; /* String identifying platform. */ -pub const AT_HWCAP: usize = 16; /* Machine-dependent hints about */ -pub const AT_FPUCW: usize = 18; /* Used FPU control word. */ -pub const AT_DCACHEBSIZE: usize = 19; /* Data cache block size. */ -pub const AT_ICACHEBSIZE: usize = 20; /* Instruction cache block size. */ -pub const AT_UCACHEBSIZE: usize = 21; /* Unified cache block size. */ -pub const AT_IGNOREPPC: usize = 22; /* Entry should be ignored. */ -pub const AT_BASE_PLATFORM: usize = 24; /* String identifying real platforms.*/ -pub const AT_RANDOM: usize = 25; /* Address of 16 random bytes. */ -pub const AT_HWCAP2: usize = 26; /* More machine-dependent hints about*/ -pub const AT_EXECFN: usize = 31; /* Filename of executable. */ +pub use crate::platform::auxv_defs::*; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getauxval(_t: c_ulong) -> c_ulong { 0 } diff --git a/src/header/sys_epoll/cbindgen.toml b/src/header/sys_epoll/cbindgen.toml index 45aac6c16e..64ce6c75b7 100644 --- a/src/header/sys_epoll/cbindgen.toml +++ b/src/header/sys_epoll/cbindgen.toml @@ -5,9 +5,5 @@ style = "Tag" no_includes = true cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true diff --git a/src/header/sys_epoll/linux.rs b/src/header/sys_epoll/linux.rs index 4f17c0c19b..89b5365017 100644 --- a/src/header/sys_epoll/linux.rs +++ b/src/header/sys_epoll/linux.rs @@ -1,18 +1,18 @@ -use crate::platform::types::*; +use crate::platform::types::{c_int, c_uint}; pub const EPOLL_CLOEXEC: c_int = 0x8_0000; pub const EPOLLIN: c_uint = 0x001; pub const EPOLLPRI: c_uint = 0x002; pub const EPOLLOUT: c_uint = 0x004; -pub const EPOLLRDNORM: c_uint = 0x040; +pub const EPOLLERR: c_uint = 0x008; +pub const EPOLLHUP: c_uint = 0x010; pub const EPOLLNVAL: c_uint = 0x020; +pub const EPOLLRDNORM: c_uint = 0x040; pub const EPOLLRDBAND: c_uint = 0x080; pub const EPOLLWRNORM: c_uint = 0x100; pub const EPOLLWRBAND: c_uint = 0x200; pub const EPOLLMSG: c_uint = 0x400; -pub const EPOLLERR: c_uint = 0x008; -pub const EPOLLHUP: c_uint = 0x010; pub const EPOLLRDHUP: c_uint = 0x2000; pub const EPOLLEXCLUSIVE: c_uint = 1 << 28; pub const EPOLLWAKEUP: c_uint = 1 << 29; diff --git a/src/header/sys_epoll/mod.rs b/src/header/sys_epoll/mod.rs index 3cae6cfa68..715d7b9d63 100644 --- a/src/header/sys_epoll/mod.rs +++ b/src/header/sys_epoll/mod.rs @@ -1,10 +1,16 @@ -//! sys/epoll.h implementation for Redox, following http://man7.org/linux/man-pages/man7/epoll.7.html +//! `sys/epoll.h` implementation. +//! +//! Non-POSIX, see . use core::ptr; use crate::{ - header::signal::sigset_t, - platform::{types::*, PalEpoll, Sys}, + error::ResultExt, + header::bits_sigset_t::sigset_t, + platform::{ + PalEpoll, Sys, + types::{c_int, c_void}, + }, }; pub use self::sys::*; @@ -21,6 +27,7 @@ pub const EPOLL_CTL_ADD: c_int = 1; pub const EPOLL_CTL_DEL: c_int = 2; pub const EPOLL_CTL_MOD: c_int = 3; +/// Non-POSIX, see . #[repr(C)] #[derive(Clone, Copy)] pub union epoll_data { @@ -35,6 +42,8 @@ impl Default for epoll_data { } } +/// Non-POSIX, see . +#[cfg(all(target_os = "redox", target_pointer_width = "64"))] #[repr(C)] #[derive(Clone, Copy, Default)] // This will match in size with syscall::Event (24 bytes on 64-bit @@ -45,24 +54,46 @@ pub struct epoll_event { // 4 automatic alignment bytes pub data: epoll_data, // 8 bytes - #[cfg(target_os = "redox")] pub _pad: u64, // 8 bytes } -#[no_mangle] +/// Non-POSIX, see . +#[cfg(not(all(target_os = "redox", target_pointer_width = "64")))] +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct epoll_event { + pub events: u32, + pub data: epoll_data, +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] pub extern "C" fn epoll_create(_size: c_int) -> c_int { epoll_create1(0) } -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] pub extern "C" fn epoll_create1(flags: c_int) -> c_int { - trace_expr!(Sys::epoll_create1(flags), "epoll_create1({:#x})", flags) + trace_expr!( + Sys::epoll_create1(flags).or_minus_one_errno(), + "epoll_create1({:#x})", + flags + ) } -#[no_mangle] -pub extern "C" fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int { +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn epoll_ctl( + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut epoll_event, +) -> c_int { trace_expr!( - Sys::epoll_ctl(epfd, op, fd, event), + unsafe { Sys::epoll_ctl(epfd, op, fd, event) } + .map(|()| 0) + .or_minus_one_errno(), "epoll_ctl({}, {}, {}, {:p})", epfd, op, @@ -71,18 +102,20 @@ pub extern "C" fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll ) } -#[no_mangle] -pub extern "C" fn epoll_wait( +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn epoll_wait( epfd: c_int, events: *mut epoll_event, maxevents: c_int, timeout: c_int, ) -> c_int { - epoll_pwait(epfd, events, maxevents, timeout, ptr::null()) + unsafe { epoll_pwait(epfd, events, maxevents, timeout, ptr::null()) } } -#[no_mangle] -pub extern "C" fn epoll_pwait( +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn epoll_pwait( epfd: c_int, events: *mut epoll_event, maxevents: c_int, @@ -90,7 +123,9 @@ pub extern "C" fn epoll_pwait( sigmask: *const sigset_t, ) -> c_int { trace_expr!( - Sys::epoll_pwait(epfd, events, maxevents, timeout, sigmask), + unsafe { Sys::epoll_pwait(epfd, events, maxevents, timeout, sigmask) } + .map(|e| e as c_int) + .or_minus_one_errno(), "epoll_pwait({}, {:p}, {}, {}, {:p})", epfd, events, diff --git a/src/header/sys_epoll/redox.rs b/src/header/sys_epoll/redox.rs index ecba6a3947..dfaf15fd6d 100644 --- a/src/header/sys_epoll/redox.rs +++ b/src/header/sys_epoll/redox.rs @@ -1,20 +1,20 @@ -use crate::platform::types::*; +use crate::platform::types::{c_int, c_uint}; pub const EPOLL_CLOEXEC: c_int = 0x0100_0000; -pub const EPOLLIN: c_uint = 1; -pub const EPOLLPRI: c_uint = 0; -pub const EPOLLOUT: c_uint = 2; -pub const EPOLLRDNORM: c_uint = 0; -pub const EPOLLNVAL: c_uint = 0; -pub const EPOLLRDBAND: c_uint = 0; -pub const EPOLLWRNORM: c_uint = 0; -pub const EPOLLWRBAND: c_uint = 0; -pub const EPOLLMSG: c_uint = 0; -pub const EPOLLERR: c_uint = 0; -pub const EPOLLHUP: c_uint = 0; -pub const EPOLLRDHUP: c_uint = 0; -pub const EPOLLEXCLUSIVE: c_uint = 0; -pub const EPOLLWAKEUP: c_uint = 0; -pub const EPOLLONESHOT: c_uint = 0; -pub const EPOLLET: c_uint = 0; +pub const EPOLLIN: c_uint = 0x001; +pub const EPOLLPRI: c_uint = 0x002; +pub const EPOLLOUT: c_uint = 0x004; +pub const EPOLLERR: c_uint = 0x008; +pub const EPOLLHUP: c_uint = 0x010; +pub const EPOLLNVAL: c_uint = 0x020; +pub const EPOLLRDNORM: c_uint = 0x040; +pub const EPOLLRDBAND: c_uint = 0x080; +pub const EPOLLWRNORM: c_uint = 0x100; +pub const EPOLLWRBAND: c_uint = 0x200; +pub const EPOLLMSG: c_uint = 0x400; +pub const EPOLLRDHUP: c_uint = 0x2000; +pub const EPOLLEXCLUSIVE: c_uint = 1 << 28; +pub const EPOLLWAKEUP: c_uint = 1 << 29; +pub const EPOLLONESHOT: c_uint = 1 << 30; +pub const EPOLLET: c_uint = 1 << 31; diff --git a/src/header/sys_eventfd/cbindgen.toml b/src/header/sys_eventfd/cbindgen.toml new file mode 100644 index 0000000000..d6750cc523 --- /dev/null +++ b/src/header/sys_eventfd/cbindgen.toml @@ -0,0 +1,12 @@ +sys_includes = ["stdint.h"] +after_includes = """ +typedef uint64_t eventfd_t; +""" +include_guard = "_SYS_EVENTFD_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/sys_eventfd/mod.rs b/src/header/sys_eventfd/mod.rs new file mode 100644 index 0000000000..1bc0926994 --- /dev/null +++ b/src/header/sys_eventfd/mod.rs @@ -0,0 +1,61 @@ +use alloc::format; +use crate::c_str::{CStr, CString}; +use crate::error::{Errno, ResultExt}; +use crate::header::fcntl::{O_CLOEXEC, O_NONBLOCK, O_RDWR}; +use crate::header::errno::EFAULT; +use crate::header::errno::EINVAL; +use crate::platform::{Pal, Sys, types::{c_int, c_uint, uint64_t}}; + +// cbindgen can't resolve re-exports across modules, so eventfd_t is +// defined here directly (the C typedef lives in after_includes in +// cbindgen.toml). bits_eventfd still has its own copy for independent +// consumers that include only . +pub type eventfd_t = uint64_t; + +pub const EFD_SEMAPHORE: c_int = 1; +pub const EFD_CLOEXEC: c_int = 0x80000; +pub const EFD_NONBLOCK: c_int = 0x800; + +#[unsafe(no_mangle)] +pub extern "C" fn eventfd(initval: c_uint, flags: c_int) -> c_int { + let supported = EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE; + if flags & !supported != 0 { + return Err::(Errno(EINVAL)).or_minus_one_errno(); + } + let sem = if flags & EFD_SEMAPHORE != 0 { 1 } else { 0 }; + let path = format!("/scheme/event/eventfd/{}/{}", initval, sem); + let cpath = match CString::new(path) { + Ok(p) => p, + Err(_) => return Err::(Errno(EINVAL)).or_minus_one_errno(), + }; + + let mut oflag = O_RDWR; + if flags & EFD_CLOEXEC == EFD_CLOEXEC { + oflag |= O_CLOEXEC; + } + if flags & EFD_NONBLOCK == EFD_NONBLOCK { + oflag |= O_NONBLOCK; + } + Sys::open(CStr::borrow(&cpath), oflag, 0).or_minus_one_errno() +} + +#[unsafe(no_mangle)] +pub extern "C" fn eventfd_read(fd: c_int, value: *mut eventfd_t) -> c_int { + if value.is_null() { + return Err::(Errno(EFAULT)).or_minus_one_errno(); + } + let mut buf = [0u8; 8]; + match Sys::read(fd, &mut buf) { + Ok(8) => { unsafe { *value = u64::from_ne_bytes(buf); } 0 } + _ => -1, + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn eventfd_write(fd: c_int, value: eventfd_t) -> c_int { + let buf = value.to_ne_bytes(); + match Sys::write(fd, &buf) { + Ok(8) => 0, + _ => -1, + } +} diff --git a/src/header/sys_eventfd/mod.rs.orig b/src/header/sys_eventfd/mod.rs.orig new file mode 100644 index 0000000000..04fa30eaa0 --- /dev/null +++ b/src/header/sys_eventfd/mod.rs.orig @@ -0,0 +1,7 @@ +use crate::platform::types::{c_int, uint64_t}; + +pub type eventfd_t = uint64_t; + +pub const EFD_SEMAPHORE: c_int = 1; +pub const EFD_CLOEXEC: c_int = 0x80000; +pub const EFD_NONBLOCK: c_int = 0x800; diff --git a/src/header/sys_file/mod.rs b/src/header/sys_file/mod.rs index 7d8e632403..810540213d 100644 --- a/src/header/sys_file/mod.rs +++ b/src/header/sys_file/mod.rs @@ -1,17 +1,23 @@ -//! sys/file.h implementation +//! `sys/file.h` implementation. +//! +//! Non-POSIX, see . -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + error::ResultExt, + platform::{Pal, Sys, types::c_int}, +}; -pub const LOCK_SH: usize = 1; -pub const LOCK_EX: usize = 2; -pub const LOCK_NB: usize = 4; -pub const LOCK_UN: usize = 8; +pub const LOCK_SH: c_int = 1; +pub const LOCK_EX: c_int = 2; +pub const LOCK_NB: c_int = 4; +pub const LOCK_UN: c_int = 8; -pub const L_SET: usize = 0; -pub const L_INCR: usize = 1; -pub const L_XTND: usize = 2; +pub const L_SET: c_int = 0; +pub const L_INCR: c_int = 1; +pub const L_XTND: c_int = 2; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn flock(fd: c_int, operation: c_int) -> c_int { - Sys::flock(fd, operation) + Sys::flock(fd, operation).map(|()| 0).or_minus_one_errno() } diff --git a/src/header/sys_ioctl/cbindgen.toml b/src/header/sys_ioctl/cbindgen.toml index fa59f7887a..64117641a7 100644 --- a/src/header/sys_ioctl/cbindgen.toml +++ b/src/header/sys_ioctl/cbindgen.toml @@ -1,13 +1,22 @@ include_guard = "_SYS_IOCTL_H" language = "C" style = "Tag" +trailer = """ +// Shamelessly copy-pasted from musl + +#define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) ) +#define _IOC_NONE 0U +#define _IOC_WRITE 1U +#define _IOC_READ 2U + +#define _IO(a,b) _IOC(_IOC_NONE,(a),(b),0) +#define _IOW(a,b,c) _IOC(_IOC_WRITE,(a),(b),sizeof(c)) +#define _IOR(a,b,c) _IOC(_IOC_READ,(a),(b),sizeof(c)) +#define _IOWR(a,b,c) _IOC(_IOC_READ|_IOC_WRITE,(a),(b),sizeof(c)) +""" no_includes = true cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true diff --git a/src/header/sys_ioctl/linux.rs b/src/header/sys_ioctl/linux.rs index 949052dbae..62d5946c0a 100644 --- a/src/header/sys_ioctl/linux.rs +++ b/src/header/sys_ioctl/linux.rs @@ -1,9 +1,15 @@ -use crate::platform::{types::*, Sys}; +use crate::{ + error::ResultExt, + platform::{ + Sys, + types::{c_int, c_ulong, c_void}, + }, +}; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> c_int { // TODO: Somehow support varargs to syscall?? - Sys::ioctl(fd, request, out) + unsafe { Sys::ioctl(fd, request, out).or_minus_one_errno() } } pub const TCGETS: c_ulong = 0x5401; diff --git a/src/header/sys_ioctl/mod.rs b/src/header/sys_ioctl/mod.rs index 54d5f29aef..2b80f7536f 100644 --- a/src/header/sys_ioctl/mod.rs +++ b/src/header/sys_ioctl/mod.rs @@ -1,6 +1,6 @@ //! ioctl implementation for linux -use crate::platform::types::*; +use crate::platform::types::{c_char, c_ushort}; // This is used from sgtty #[repr(C)] @@ -21,12 +21,20 @@ pub struct winsize { ws_ypixel: c_ushort, } -pub use self::sys::*; +impl winsize { + pub fn get_row_col(&self) -> (c_ushort, c_ushort) { + (self.ws_row, self.ws_col) + } +} #[cfg(target_os = "linux")] -#[path = "linux.rs"] -pub mod sys; +pub use self::linux::*; + +#[cfg(target_os = "linux")] +pub mod linux; #[cfg(target_os = "redox")] -#[path = "redox.rs"] -pub mod sys; +pub use self::redox::*; + +#[cfg(target_os = "redox")] +pub mod redox; diff --git a/src/header/sys_ioctl/redox.rs b/src/header/sys_ioctl/redox.rs deleted file mode 100644 index a9d3fca7b3..0000000000 --- a/src/header/sys_ioctl/redox.rs +++ /dev/null @@ -1,143 +0,0 @@ -use core::{mem, slice}; -use syscall; - -use crate::{ - header::{errno, fcntl, termios}, - platform::{self, e, types::*}, -}; - -use super::winsize; - -pub const FIONBIO: c_ulong = 0x5421; - -pub const TCGETS: c_ulong = 0x5401; -pub const TCSETS: c_ulong = 0x5402; -pub const TCSBRK: c_ulong = 0x5409; - -pub const TCXONC: c_ulong = 0x540A; - -pub const TCFLSH: c_ulong = 0x540B; - -pub const TIOCGPGRP: c_ulong = 0x540F; -pub const TIOCSPGRP: c_ulong = 0x5410; - -pub const TIOCGWINSZ: c_ulong = 0x5413; -pub const TIOCSWINSZ: c_ulong = 0x5414; - -fn dup_read(fd: c_int, name: &str, t: &mut T) -> syscall::Result { - let dup = syscall::dup(fd as usize, name.as_bytes())?; - - let size = mem::size_of::(); - - let res = syscall::read(dup, unsafe { - slice::from_raw_parts_mut(t as *mut T as *mut u8, size) - }); - - let _ = syscall::close(dup); - - res.map(|bytes| bytes / size) -} - -fn dup_write(fd: c_int, name: &str, t: &T) -> syscall::Result { - let dup = syscall::dup(fd as usize, name.as_bytes())?; - - let size = mem::size_of::(); - - let res = syscall::write(dup, unsafe { - slice::from_raw_parts(t as *const T as *const u8, size) - }); - - let _ = syscall::close(dup); - - res.map(|bytes| bytes / size) -} - -#[no_mangle] -pub unsafe extern "C" fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> c_int { - match request { - FIONBIO => { - let mut flags = fcntl::sys_fcntl(fd, fcntl::F_GETFL, 0); - if flags < 0 { - return -1; - } - flags = if *(out as *mut c_int) == 0 { - flags & !fcntl::O_NONBLOCK - } else { - flags | fcntl::O_NONBLOCK - }; - if fcntl::sys_fcntl(fd, fcntl::F_SETFL, flags) < 0 { - -1 - } else { - 0 - } - } - TCGETS => { - let termios = &mut *(out as *mut termios::termios); - if e(dup_read(fd, "termios", termios)) == !0 { - -1 - } else { - 0 - } - } - TCSETS => { - let termios = &*(out as *const termios::termios); - if e(dup_write(fd, "termios", termios)) == !0 { - -1 - } else { - 0 - } - } - TCFLSH => { - let queue = out as c_int; - if e(dup_write(fd, "flush", &queue)) == !0 { - -1 - } else { - 0 - } - } - TIOCGPGRP => { - let pgrp = &mut *(out as *mut pid_t); - if e(dup_read(fd, "pgrp", pgrp)) == !0 { - -1 - } else { - 0 - } - } - TIOCSPGRP => { - let pgrp = &*(out as *const pid_t); - if e(dup_write(fd, "pgrp", pgrp)) == !0 { - -1 - } else { - 0 - } - } - TIOCGWINSZ => { - let winsize = &mut *(out as *mut winsize); - if e(dup_read(fd, "winsize", winsize)) == !0 { - -1 - } else { - 0 - } - } - TIOCSWINSZ => { - let winsize = &*(out as *const winsize); - if e(dup_write(fd, "winsize", winsize)) == !0 { - -1 - } else { - 0 - } - } - TCSBRK => { - // TODO - 0 - } - TCXONC => { - // TODO - 0 - } - _ => { - platform::errno = errno::EINVAL; - -1 - } - } -} diff --git a/src/header/sys_ioctl/redox/drm.rs b/src/header/sys_ioctl/redox/drm.rs new file mode 100644 index 0000000000..0557178ee8 --- /dev/null +++ b/src/header/sys_ioctl/redox/drm.rs @@ -0,0 +1,135 @@ +use core::slice; + +use redox_ioctl::{IoctlData, drm::*}; + +use crate::{ + error::{Errno, Result}, + header::errno::EINVAL, + platform::types::c_int, +}; + +use super::IoctlBuffer; + +const DRM_FORMAT_ARGB8888: u32 = 0x34325241; // 'AR24' fourcc code, for ARGB8888 + +fn id_index(id: u32) -> u32 { + id & 0xFF +} + +fn conn_id(i: u32) -> u32 { + id_index(i) | (1 << 8) +} + +fn crtc_id(i: u32) -> u32 { + id_index(i) | (1 << 9) +} + +fn enc_id(i: u32) -> u32 { + id_index(i) | (1 << 10) +} + +fn fb_id(i: u32) -> u32 { + id_index(i) | (1 << 11) +} + +fn fb_handle_id(i: u32) -> u32 { + id_index(i) | (1 << 12) +} + +fn plane_id(i: u32) -> u32 { + id_index(i) | (1 << 13) +} + +unsafe fn copy_array(src: &[T], dst_ptr: *mut T, dst_len: usize) -> usize { + let dst = unsafe { slice::from_raw_parts_mut(dst_ptr, dst_len) }; + dst.copy_from_slice(&src[..src.len().min(dst_len)]); + src.len() +} + +struct Dev { + fd: c_int, +} + +impl Dev { + fn new(fd: c_int) -> Result { + //TODO: check display scheme using fpath? + Ok(Self { fd }) + } + + unsafe fn read_write_ioctl( + &self, + mut buf: IoctlBuffer, + func: u64, + ) -> Result { + let mut data = unsafe { buf.read::() }?; + let mut wire = unsafe { data.write() }; + let res = redox_rt::sys::sys_call_rw( + self.fd as usize, + &mut wire, + syscall::CallFlags::empty(), + &[func], + )?; + unsafe { data.read_from(&wire) }; + (unsafe { buf.write(data) })?; + Ok(res as c_int) + } + + unsafe fn write_ioctl(&self, buf: IoctlBuffer, func: u64) -> Result { + let data = unsafe { buf.read::() }?; + let wire = unsafe { data.write() }; + let res = redox_rt::sys::sys_call_wo( + self.fd as usize, + &wire, + syscall::CallFlags::empty(), + &[func], + )?; + Ok(res as c_int) + } +} + +pub(super) unsafe fn ioctl(fd: c_int, func: u8, buf: IoctlBuffer) -> Result { + let dev = Dev::new(fd)?; + match func { + 0x00 => unsafe { dev.read_write_ioctl::(buf, VERSION) }, + 0x0C => unsafe { dev.read_write_ioctl::(buf, GET_CAP) }, + 0x0D => unsafe { dev.write_ioctl::(buf, SET_CLIENT_CAP) }, + 0xA0 => unsafe { dev.read_write_ioctl::(buf, MODE_CARD_RES) }, + 0xA1 => unsafe { dev.read_write_ioctl::(buf, MODE_GET_CRTC) }, + 0xA2 => unsafe { dev.read_write_ioctl::(buf, MODE_SET_CRTC) }, + 0xA3 => unsafe { dev.write_ioctl::(buf, MODE_CURSOR) }, + 0xA6 => unsafe { dev.read_write_ioctl::(buf, MODE_GET_ENCODER) }, + 0xA7 => unsafe { dev.read_write_ioctl::(buf, MODE_GET_CONNECTOR) }, + 0xAA => unsafe { dev.read_write_ioctl::(buf, MODE_GET_PROPERTY) }, + 0xAB => unsafe { + dev.read_write_ioctl::(buf, MODE_SET_PROPERTY) + }, + 0xAC => unsafe { dev.read_write_ioctl::(buf, MODE_GET_PROP_BLOB) }, + 0xAD => unsafe { dev.read_write_ioctl::(buf, MODE_GET_FB) }, + 0xAE => unsafe { dev.read_write_ioctl::(buf, MODE_ADD_FB) }, + 0xAF => unsafe { dev.read_write_ioctl::(buf, MODE_RM_FB) }, + 0xB0 => unsafe { + dev.read_write_ioctl::(buf, MODE_PAGE_FLIP) + }, + 0xB1 => unsafe { dev.read_write_ioctl::(buf, MODE_DIRTYFB) }, + 0xB2 => unsafe { dev.read_write_ioctl::(buf, MODE_CREATE_DUMB) }, + 0xB3 => unsafe { dev.read_write_ioctl::(buf, MODE_MAP_DUMB) }, + 0xB4 => unsafe { dev.read_write_ioctl::(buf, MODE_DESTROY_DUMB) }, + 0xB5 => unsafe { dev.read_write_ioctl::(buf, MODE_GET_PLANE_RES) }, + 0xB6 => unsafe { dev.read_write_ioctl::(buf, MODE_GET_PLANE) }, + 0xB9 => unsafe { + dev.read_write_ioctl::(buf, MODE_OBJ_GET_PROPERTIES) + }, + 0xBB => unsafe { dev.write_ioctl::(buf, MODE_CURSOR2) }, + 0xCE => unsafe { dev.read_write_ioctl::(buf, MODE_GET_FB2) }, + _ => { + todo_skip!( + 0, + "unimplemented DRM ioctl({}, 0x{:02x}, {:?})", + fd, + func, + buf + ); + Err(Errno(EINVAL)) + } + } +} diff --git a/src/header/sys_ioctl/redox/mod.rs b/src/header/sys_ioctl/redox/mod.rs new file mode 100644 index 0000000000..8b3cf8fc23 --- /dev/null +++ b/src/header/sys_ioctl/redox/mod.rs @@ -0,0 +1,196 @@ +use core::{mem, ptr, slice}; +use redox_rt::proc::FdGuard; +use syscall; + +use crate::{ + error::{Errno, Result, ResultExt}, + header::{errno::EINVAL, fcntl, termios}, + platform::{ + Pal, Sys, + types::{c_int, c_ulong, c_ulonglong, c_void, pid_t}, + }, +}; + +use super::winsize; + +mod drm; + +pub const TCGETS: c_ulong = 0x5401; +pub const TCSETS: c_ulong = 0x5402; +pub const TCSETSW: c_ulong = 0x5403; +pub const TCSETSF: c_ulong = 0x5404; + +pub const TCSBRK: c_ulong = 0x5409; +pub const TCXONC: c_ulong = 0x540A; +pub const TCFLSH: c_ulong = 0x540B; + +pub const TIOCSCTTY: c_ulong = 0x540E; +pub const TIOCGPGRP: c_ulong = 0x540F; +pub const TIOCSPGRP: c_ulong = 0x5410; + +pub const TIOCGWINSZ: c_ulong = 0x5413; +pub const TIOCSWINSZ: c_ulong = 0x5414; + +//TODO: used by tcgetsid, not implemented yet on redox +pub const TIOCGSID: c_ulong = 0x5429; + +pub const FIONREAD: c_ulong = 0x541B; + +pub const FIONBIO: c_ulong = 0x5421; + +pub const TIOCSPTLCK: c_ulong = 0x4004_5431; +pub const TIOCGPTLCK: c_ulong = 0x8004_5439; + +pub const SIOCATMARK: c_ulong = 0x8905; + +// TODO: some of the structs passed as T have padding bytes, so casting to a byte slice is UB + +fn dup_read(fd: c_int, name: &str, t: &mut T) -> syscall::Result { + let dup = FdGuard::new(syscall::dup(fd as usize, name.as_bytes())?); + + let size = mem::size_of::(); + + let bytes = dup.read(unsafe { slice::from_raw_parts_mut(t as *mut T as *mut u8, size) })?; + + Ok(bytes / size) +} + +// FIXME: unsound +fn dup_write(fd: c_int, name: &str, t: &T) -> Result { + let dup = FdGuard::new(syscall::dup(fd as usize, name.as_bytes())?); + + let size = mem::size_of::(); + + let bytes = dup.write(unsafe { slice::from_raw_parts(t as *const T as *const u8, size) })?; + + Ok(bytes / size) +} + +#[derive(Debug)] +enum IoctlBuffer { + None, + Read(*mut c_void, usize), // read (write to userspace) + Write(*const c_void, usize), // write (read from userspace) + ReadWrite(*mut c_void, usize), +} + +impl IoctlBuffer { + unsafe fn read(&self) -> Result { + let (ptr, size) = match *self { + Self::Write(ptr, size) => (ptr, size), + Self::ReadWrite(ptr, size) => (ptr as *const c_void, size), + _ => { + return Err(Errno(EINVAL)); + } + }; + if size == mem::size_of::() { + let value = unsafe { ptr::read(ptr as *const T) }; + Ok(value) + } else { + Err(Errno(EINVAL)) + } + } + + unsafe fn write(&mut self, value: T) -> Result<()> { + let (ptr, size) = match *self { + Self::Read(ptr, size) | Self::ReadWrite(ptr, size) => (ptr, size), + _ => { + return Err(Errno(EINVAL)); + } + }; + if size == mem::size_of::() { + unsafe { ptr::write(ptr as *mut T, value) }; + Ok(()) + } else { + Err(Errno(EINVAL)) + } + } +} + +unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result { + match request { + FIONBIO => { + let mut flags = Sys::fcntl(fd, fcntl::F_GETFL, 0)?; + flags = if unsafe { *(out as *mut c_int) } == 0 { + flags & !fcntl::O_NONBLOCK + } else { + flags | fcntl::O_NONBLOCK + }; + Sys::fcntl(fd, fcntl::F_SETFL, flags as c_ulonglong)?; + } + TCGETS => { + let termios = unsafe { &mut *(out as *mut termios::termios) }; + dup_read(fd, "termios", termios)?; + } + // TODO: give these different behaviors + TCSETS | TCSETSW | TCSETSF => { + let termios = unsafe { &*(out as *const termios::termios) }; + dup_write(fd, "termios", termios)?; + } + TCFLSH => { + let queue = out as c_int; + dup_write(fd, "flush", &queue)?; + } + TIOCSCTTY => { + todo_skip!(0, "ioctl TIOCSCTTY"); + } + TIOCGPGRP => { + let pgrp = unsafe { &mut *(out as *mut pid_t) }; + dup_read(fd, "pgrp", pgrp)?; + } + TIOCSPGRP => { + let pgrp = unsafe { &*(out as *const pid_t) }; + dup_write(fd, "pgrp", pgrp)?; + } + TIOCGWINSZ => { + let winsize = unsafe { &mut *(out as *mut winsize) }; + dup_read(fd, "winsize", winsize)?; + } + TIOCSWINSZ => { + let winsize = unsafe { &*(out as *const winsize) }; + dup_write(fd, "winsize", winsize)?; + } + TIOCGPTLCK => { + todo_skip!(0, "ioctl TIOCGPTLCK"); + } + TIOCSPTLCK => { + todo_skip!(0, "ioctl TIOCSPTLCK"); + } + TCSBRK => { + todo_skip!(0, "ioctl TCSBRK"); + } + TCXONC => { + todo_skip!(0, "ioctl TCXONC"); + } + SIOCATMARK => { + todo_skip!(0, "ioctl SIOCATMARK"); + } + _ => { + // See https://docs.kernel.org/userspace-api/ioctl/ioctl-decoding.html for details + let dir = (request >> 30) & 0b11; + let size = ((request >> 16) & 0x3FFF) as usize; + let name = (((request >> 8) & 0xFF) as u8) as char; + let func = (request & 0xFF) as u8; + match name { + 'd' => { + let buf = match dir { + 0b10 => IoctlBuffer::Read(out, size), + 0b01 => IoctlBuffer::Write(out, size), + 0b11 => IoctlBuffer::ReadWrite(out, size), + _ => IoctlBuffer::None, + }; + return unsafe { drm::ioctl(fd, func, buf) }; + } + _ => { + return Err(Errno(EINVAL)); + } + } + } + } + Ok(0) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> c_int { + unsafe { ioctl_inner(fd, request, out) }.or_minus_one_errno() +} diff --git a/src/header/sys_mman/cbindgen.toml b/src/header/sys_mman/cbindgen.toml index 720c9641ff..7743c076e3 100644 --- a/src/header/sys_mman/cbindgen.toml +++ b/src/header/sys_mman/cbindgen.toml @@ -1,14 +1,21 @@ -sys_includes = ["stdint.h", "sys/types.h"] -include_guard = "_SYS_MMAN_H" -trailer = "#include " +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_mman.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the mode_t, off_t, and size_t types as described in ." +# - "The header shall define the following symbolic constants as described in " +# - "Inclusion of the header may make visible all symbols from the header." +sys_includes = ["sys/types.h", "fcntl.h"] +include_guard = "_RELIBC_SYS_MMAN_H" +after_includes = """ + +#define MAP_FAILED ((void *) -1) +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true - -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" +# make size_t actually size_t instead of uintptr_t (which would require stdint.h) +usize_is_size_t = true [enum] prefix_with_name = true diff --git a/src/header/sys_mman/linux.rs b/src/header/sys_mman/linux.rs index 1a8fb44969..5fc27ce4d7 100644 --- a/src/header/sys_mman/linux.rs +++ b/src/header/sys_mman/linux.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const PROT_READ: c_int = 0x0001; pub const PROT_WRITE: c_int = 0x0002; @@ -7,3 +7,11 @@ pub const PROT_NONE: c_int = 0x0000; pub const MAP_FIXED: c_int = 0x0010; pub const MAP_FIXED_NOREPLACE: c_int = 0x100000; +pub const MAP_POPULATE: c_int = 0x008000; +pub const MAP_HUGETLB: c_int = 0x40000; +pub const MAP_NORESERVE: c_int = 0x4000; + +pub const MADV_HUGEPAGE: c_int = 14; +pub const MADV_NOHUGEPAGE: c_int = 15; +pub const MADV_DONTDUMP: c_int = 16; +pub const MADV_DODUMP: c_int = 17; diff --git a/src/header/sys_mman/mod.rs b/src/header/sys_mman/mod.rs index de9f15ebfa..555ea94bda 100644 --- a/src/header/sys_mman/mod.rs +++ b/src/header/sys_mman/mod.rs @@ -1,7 +1,15 @@ +//! `sys/mman.h` implementation. +//! +//! See . + use crate::{ c_str::{CStr, CString}, + error::{Errno, ResultExt}, header::{fcntl, unistd}, - platform::{types::*, Pal, Sys}, + platform::{ + ERRNO, Pal, Sys, + types::{c_char, c_int, c_void, mode_t, off_t, size_t}, + }, }; pub use self::sys::*; @@ -14,27 +22,62 @@ pub mod sys; #[path = "redox.rs"] pub mod sys; +pub const MADV_NORMAL: c_int = 0; +pub const MADV_RANDOM: c_int = 1; +pub const MADV_SEQUENTIAL: c_int = 2; +pub const MADV_WILLNEED: c_int = 3; +pub const MADV_DONTNEED: c_int = 4; + pub const MAP_SHARED: c_int = 0x0001; pub const MAP_PRIVATE: c_int = 0x0002; pub const MAP_TYPE: c_int = 0x000F; pub const MAP_ANON: c_int = 0x0020; pub const MAP_ANONYMOUS: c_int = MAP_ANON; +pub const MAP_STACK: c_int = 0x20000; +/// cbindgen:ignore +pub const MAP_FAILED: *mut c_void = usize::wrapping_neg(1) as *mut c_void; + +pub const MREMAP_MAYMOVE: c_int = 1; pub const MS_ASYNC: c_int = 0x0001; pub const MS_INVALIDATE: c_int = 0x0002; pub const MS_SYNC: c_int = 0x0004; -// #[no_mangle] -pub extern "C" fn mlock(addr: *const c_void, len: usize) -> c_int { - unimplemented!(); +pub const MCL_CURRENT: c_int = 1; +pub const MCL_FUTURE: c_int = 2; + +pub const POSIX_MADV_NORMAL: c_int = 0; +pub const POSIX_MADV_RANDOM: c_int = 1; +pub const POSIX_MADV_SEQUENTIAL: c_int = 2; +pub const POSIX_MADV_WILLNEED: c_int = 3; +pub const POSIX_MADV_WONTNEED: c_int = 4; + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn madvise(addr: *mut c_void, len: size_t, flags: c_int) -> c_int { + unsafe { Sys::madvise(addr, len, flags) } + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn mlockall(flags: c_int) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mlock(addr: *const c_void, len: size_t) -> c_int { + unsafe { Sys::mlock(addr, len) } + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mlockall(flags: c_int) -> c_int { + unsafe { Sys::mlockall(flags) } + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mmap( addr: *mut c_void, len: size_t, @@ -43,42 +86,82 @@ pub unsafe extern "C" fn mmap( fildes: c_int, off: off_t, ) -> *mut c_void { - Sys::mmap(addr, len, prot, flags, fildes, off) + match unsafe { Sys::mmap(addr, len, prot, flags, fildes, off) } { + Ok(ptr) => ptr, + Err(Errno(errno)) => { + ERRNO.set(errno); + MAP_FAILED + } + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int { - Sys::mprotect(addr, len, prot) + unsafe { Sys::mprotect(addr, len, prot) } + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mremap( + old_address: *mut c_void, + old_size: size_t, + new_size: size_t, + flags: c_int, + mut __valist: ... +) -> *mut c_void { + let new_address = unsafe { __valist.next_arg::<*mut c_void>() }; + match unsafe { Sys::mremap(old_address, old_size, new_size, flags, new_address) } { + Ok(ptr) => ptr, + Err(Errno(errno)) => { + ERRNO.set(errno); + MAP_FAILED + } + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn msync(addr: *mut c_void, len: size_t, flags: c_int) -> c_int { - Sys::msync(addr, len, flags) + unsafe { Sys::msync(addr, len, flags) } + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn munlock(addr: *const c_void, len: usize) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn munlock(addr: *const c_void, len: size_t) -> c_int { + unsafe { Sys::munlock(addr, len) } + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn munlockall() -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn munlockall() -> c_int { + unsafe { Sys::munlockall() } + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn munmap(addr: *mut c_void, len: size_t) -> c_int { - Sys::munmap(addr, len) + unsafe { Sys::munmap(addr, len) } + .map(|()| 0) + .or_minus_one_errno() } #[cfg(target_os = "linux")] -static SHM_PATH: &'static [u8] = b"/dev/shm/"; +static SHM_PATH: &[u8] = b"/dev/shm/"; #[cfg(target_os = "redox")] -static SHM_PATH: &'static [u8] = b"shm:"; +static SHM_PATH: &[u8] = b"/scheme/shm/"; unsafe fn shm_path(name: *const c_char) -> CString { - let name_c = CStr::from_ptr(name); + let name_c = unsafe { CStr::from_ptr(name) }; let mut path = SHM_PATH.to_vec(); @@ -94,17 +177,19 @@ unsafe fn shm_path(name: *const c_char) -> CString { path.push(b); } - CString::from_vec_unchecked(path) + unsafe { CString::from_vec_unchecked(path) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn shm_open(name: *const c_char, oflag: c_int, mode: mode_t) -> c_int { - let path = shm_path(name); - fcntl::sys_open(path.as_ptr(), oflag, mode) + let path = unsafe { shm_path(name) }; + unsafe { fcntl::open(path.as_ptr(), oflag, mode) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn shm_unlink(name: *const c_char) -> c_int { - let path = shm_path(name); - unistd::unlink(path.as_ptr()) + let path = unsafe { shm_path(name) }; + unsafe { unistd::unlink(path.as_ptr()) } } diff --git a/src/header/sys_mman/redox.rs b/src/header/sys_mman/redox.rs index 58a3aef056..ea0ee2215d 100644 --- a/src/header/sys_mman/redox.rs +++ b/src/header/sys_mman/redox.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const PROT_NONE: c_int = 0x0000; pub const PROT_EXEC: c_int = 0x0001; @@ -7,3 +7,8 @@ pub const PROT_READ: c_int = 0x0004; pub const MAP_FIXED: c_int = 0x0004; pub const MAP_FIXED_NOREPLACE: c_int = 0x000C; +pub const MAP_POPULATE: c_int = 0x0001F; +pub const MAP_HUGETLB: c_int = 0x2E; + +pub const MADV_DONTDUMP: c_int = 5; +pub const MADV_DODUMP: c_int = 6; diff --git a/src/header/sys_procfs/mod.rs b/src/header/sys_procfs/mod.rs index a70aa9f6c1..b1534b33f2 100644 --- a/src/header/sys_procfs/mod.rs +++ b/src/header/sys_procfs/mod.rs @@ -1,8 +1,12 @@ #[cfg(target_arch = "aarch64")] -use crate::header::arch_aarch64_user::*; +use crate::header::arch_aarch64_user::{elf_fpregset_t, elf_gregset_t}; +#[cfg(target_arch = "riscv64")] +use crate::header::arch_riscv64_user::{elf_fpregset_t, elf_gregset_t}; #[cfg(target_arch = "x86_64")] -use crate::header::arch_x64_user::*; -use crate::platform::types::*; +use crate::header::arch_x64_user::{elf_fpregset_t, elf_gregset_t}; +use crate::platform::types::{ + c_char, c_int, c_long, c_short, c_uint, c_ulong, c_void, pid_t, size_t, +}; pub const ELF_PRARGSZ: size_t = 80; @@ -55,14 +59,14 @@ pub struct elf_prpsinfo { } pub type psaddr_t = *mut c_void; -pub type prgregset_t = elf_gregset_t; +pub type prgregset_t = *mut elf_gregset_t; pub type prfpregset_t = elf_fpregset_t; pub type lwpid_t = pid_t; pub type prstatus_t = elf_prstatus; pub type prpsinfo_t = elf_prpsinfo; -#[no_mangle] -pub extern "C" fn _cbindgen_only_generates_structs_if_they_are_mentioned_which_is_dumb_procfs( +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_procfs( a: psaddr_t, b: prgregset_t, c: prfpregset_t, diff --git a/src/header/sys_ptrace/cbindgen.toml b/src/header/sys_ptrace/cbindgen.toml index 11bd11f670..3ecacd0534 100644 --- a/src/header/sys_ptrace/cbindgen.toml +++ b/src/header/sys_ptrace/cbindgen.toml @@ -2,7 +2,6 @@ sys_includes = [] include_guard = "_SYS_PTRACE_H" language = "C" style = "Tag" -trailer = "#include " [enum] prefix_with_name = true diff --git a/src/header/sys_ptrace/mod.rs b/src/header/sys_ptrace/mod.rs index deb3fdedee..4c3f5ec38a 100644 --- a/src/header/sys_ptrace/mod.rs +++ b/src/header/sys_ptrace/mod.rs @@ -1,7 +1,11 @@ -//! ptrace compatibility layer for Redox OS +//! `sys/ptrace.h` implementation. +//! +//! Non-POSIX, see . -use crate::platform::{types::*, PalPtrace, Sys}; -use core::ffi::VaList; +use crate::{ + error::ResultExt, + platform::{PalPtrace, Sys, types::{c_int, c_void, pid_t}}, +}; pub const PTRACE_TRACEME: c_int = 0; pub const PTRACE_PEEKTEXT: c_int = 1; @@ -22,8 +26,11 @@ pub const PTRACE_SYSEMU: c_int = 31; pub const PTRACE_SYSEMU_SINGLESTEP: c_int = 32; // Can't use "params: ..." syntax, because... guess what? Cbingen again :( -#[no_mangle] -pub unsafe extern "C" fn sys_ptrace(request: c_int, mut params: VaList) -> c_int { - // Musl also just grabs the arguments from the varargs... - Sys::ptrace(request, params.arg(), params.arg(), params.arg()) +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ptrace(request: c_int, mut __valist: ...) -> c_int { + let pid = unsafe { __valist.next_arg::() }; + let addr = unsafe { __valist.next_arg::<*mut c_void>() }; + let data = unsafe { __valist.next_arg::<*mut c_void>() }; + unsafe { Sys::ptrace(request, pid, addr, data) }.or_minus_one_errno() } diff --git a/src/header/sys_random/mod.rs b/src/header/sys_random/mod.rs index 957f8c7753..57040d469a 100644 --- a/src/header/sys_random/mod.rs +++ b/src/header/sys_random/mod.rs @@ -1,14 +1,29 @@ +//! `sys/random.h` implementation. +//! +//! Non-POSIX, see . + use core::slice; -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + error::ResultExt, + platform::{ + Pal, Sys, + types::{c_uint, c_void, size_t, ssize_t}, + }, +}; +/// See . pub const GRND_NONBLOCK: c_uint = 1; +/// See . pub const GRND_RANDOM: c_uint = 2; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getrandom(buf: *mut c_void, buflen: size_t, flags: c_uint) -> ssize_t { Sys::getrandom( - slice::from_raw_parts_mut(buf as *mut u8, buflen as usize), + unsafe { slice::from_raw_parts_mut(buf.cast::(), buflen) }, flags, ) + .map(|read| read as ssize_t) + .or_minus_one_errno() } diff --git a/src/header/sys_resource/cbindgen.toml b/src/header/sys_resource/cbindgen.toml index 40aed298b7..42e748a803 100644 --- a/src/header/sys_resource/cbindgen.toml +++ b/src/header/sys_resource/cbindgen.toml @@ -1,15 +1,17 @@ sys_includes = ["sys/types.h", "stdint.h", "sys/time.h"] -include_guard = "_SYS_RESOURCE_H" -trailer = "#include " +include_guard = "_RELIBC_SYS_RESOURCE_H" +after_includes = """ + +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN (-1) +#define RUSAGE_BOTH (-2) +#define RUSAGE_THREAD 1 +""" language = "C" style = "Tag" no_includes = true cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true diff --git a/src/header/sys_resource/mod.rs b/src/header/sys_resource/mod.rs index 6bd21605dc..c645e8eb67 100644 --- a/src/header/sys_resource/mod.rs +++ b/src/header/sys_resource/mod.rs @@ -1,12 +1,18 @@ -//! sys/resource.h implementation for Redox, following -//! http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysresource.h.html +//! `sys/resource.h` implementation. +//! +//! See . use crate::{ - header::sys_time::timeval, - platform::{types::*, Pal, Sys}, + error::ResultExt, + header::sys_select::timeval, + out::Out, + platform::{ + Pal, Sys, + types::{c_int, c_long, id_t}, + }, }; -// Exported in bits file +// Exported from cbindgen // const RUSAGE_SELF: c_int = 0; // const RUSAGE_CHILDREN: c_int = -1; // const RUSAGE_BOTH: c_int = -2; @@ -16,22 +22,22 @@ pub const RLIM_INFINITY: u64 = 0xFFFF_FFFF_FFFF_FFFF; pub const RLIM_SAVED_CUR: u64 = RLIM_INFINITY; pub const RLIM_SAVED_MAX: u64 = RLIM_INFINITY; -pub const RLIMIT_CPU: u64 = 0; -pub const RLIMIT_FSIZE: u64 = 1; -pub const RLIMIT_DATA: u64 = 2; -pub const RLIMIT_STACK: u64 = 3; -pub const RLIMIT_CORE: u64 = 4; -pub const RLIMIT_RSS: u64 = 5; -pub const RLIMIT_NPROC: u64 = 6; -pub const RLIMIT_NOFILE: u64 = 7; -pub const RLIMIT_MEMLOCK: u64 = 8; -pub const RLIMIT_AS: u64 = 9; -pub const RLIMIT_LOCKS: u64 = 10; -pub const RLIMIT_SIGPENDING: u64 = 11; -pub const RLIMIT_MSGQUEUE: u64 = 12; -pub const RLIMIT_NICE: u64 = 13; -pub const RLIMIT_RTPRIO: u64 = 14; -pub const RLIMIT_NLIMITS: u64 = 15; +pub const RLIMIT_CPU: c_int = 0; +pub const RLIMIT_FSIZE: c_int = 1; +pub const RLIMIT_DATA: c_int = 2; +pub const RLIMIT_STACK: c_int = 3; +pub const RLIMIT_CORE: c_int = 4; +pub const RLIMIT_RSS: c_int = 5; +pub const RLIMIT_NPROC: c_int = 6; +pub const RLIMIT_NOFILE: c_int = 7; +pub const RLIMIT_MEMLOCK: c_int = 8; +pub const RLIMIT_AS: c_int = 9; +pub const RLIMIT_LOCKS: c_int = 10; +pub const RLIMIT_SIGPENDING: c_int = 11; +pub const RLIMIT_MSGQUEUE: c_int = 12; +pub const RLIMIT_NICE: c_int = 13; +pub const RLIMIT_RTPRIO: c_int = 14; +pub const RLIMIT_NLIMITS: c_int = 15; pub type rlim_t = u64; @@ -61,28 +67,58 @@ pub struct rusage { pub ru_nivcsw: c_long, } -// #[no_mangle] -// pub unsafe extern "C" fn getpriority(which: c_int, who: id_t) -> c_int { -// unimplemented!(); -// } +pub const PRIO_PROCESS: c_int = 0; +pub const PRIO_PGRP: c_int = 1; +pub const PRIO_USER: c_int = 2; -#[no_mangle] -pub unsafe extern "C" fn getrlimit(resource: c_int, rlp: *mut rlimit) -> c_int { - Sys::getrlimit(resource, rlp) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpriority(which: c_int, who: id_t) -> c_int { + let r = Sys::getpriority(which, who).or_minus_one_errno(); + if r < 0 { + return r; + } + 20 - r } -// #[no_mangle] -// pub unsafe extern "C" fn getrusage(who: c_int, r_usage: *mut rusage) -> c_int { -// // Sys::getrusage(who, r_usage) -// unimplemented!(); -// } -// -// #[no_mangle] -// pub unsafe extern "C" fn setpriority(which: c_int, who: id_t, nice: c_int) -> c_int { -// unimplemented!(); -// } -// -// #[no_mangle] -// pub unsafe extern "C" fn setrlimit(resource: c_int, rlp: *const rlimit) -> c_int { -// unimplemented!(); -// } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setpriority(which: c_int, who: id_t, nice: c_int) -> c_int { + Sys::setpriority(which, who, nice) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getrlimit(resource: c_int, rlp: *mut rlimit) -> c_int { + let Some(rlp) = (unsafe { Out::nullable(rlp) }) else { + crate::platform::ERRNO.set(crate::header::errno::EFAULT); + return -1; + }; + + Sys::getrlimit(resource, rlp) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setrlimit(resource: c_int, rlp: *const rlimit) -> c_int { + unsafe { Sys::setrlimit(resource, rlp) } + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getrusage(who: c_int, r_usage: *mut rusage) -> c_int { + let Some(r_usage) = (unsafe { Out::nullable(r_usage) }) else { + crate::platform::ERRNO.set(crate::header::errno::EFAULT); + return -1; + }; + + Sys::getrusage(who, r_usage) + .map(|()| 0) + .or_minus_one_errno() +} diff --git a/src/header/sys_select/cbindgen.toml b/src/header/sys_select/cbindgen.toml index 24d911ce0c..36bfa1fcab 100644 --- a/src/header/sys_select/cbindgen.toml +++ b/src/header/sys_select/cbindgen.toml @@ -1,4 +1,24 @@ -sys_includes = ["bits/sys/select.h", "sys/time.h"] +sys_includes = ["time.h", "signal.h"] +after_includes = """ +// from musl license MIT { +#define FD_SETSIZE 1024 + +typedef unsigned long fd_mask; + +typedef struct { + unsigned long fds_bits[FD_SETSIZE / 8 / sizeof(long)]; +} fd_set; + +#define FD_ZERO(s) do { int __i; unsigned long *__b=(s)->fds_bits; for(__i=sizeof (fd_set)/sizeof (long); __i; __i--) *__b++=0; } while(0) +#define FD_SET(d, s) ((s)->fds_bits[(d)/(8*sizeof(long))] |= (1UL<<((d)%(8*sizeof(long))))) +#define FD_CLR(d, s) ((s)->fds_bits[(d)/(8*sizeof(long))] &= ~(1UL<<((d)%(8*sizeof(long))))) +#define FD_ISSET(d, s) !!((s)->fds_bits[(d)/(8*sizeof(long))] & (1UL<<((d)%(8*sizeof(long))))) + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +#define NFDBITS (8*(int)sizeof(long)) +#endif +// } from musl license MIT +""" include_guard = "_SYS_SELECT_H" language = "C" style = "Tag" @@ -9,8 +29,5 @@ cpp_compat = true prefix_with_name = true [export] -# fd_set is also defined in C because cbindgen is incompatible with mem::size_of booo +# fd_set is also defined in C (above) because cbindgen is incompatible with mem::size_of exclude = ["FD_SETSIZE", "fd_set"] - -[export.rename] -"timeval" = "struct timeval" diff --git a/src/header/sys_select/mod.rs b/src/header/sys_select/mod.rs index 7b70fc5337..45e7a2381b 100644 --- a/src/header/sys_select/mod.rs +++ b/src/header/sys_select/mod.rs @@ -1,4 +1,6 @@ -//! sys/select.h implementation +//! `sys/select.h` implementation. +//! +//! See . use core::mem; @@ -9,24 +11,42 @@ use crate::{ header::{ errno, sys_epoll::{ - epoll_create1, epoll_ctl, epoll_data, epoll_event, epoll_wait, EPOLLERR, EPOLLIN, - EPOLLOUT, EPOLL_CLOEXEC, EPOLL_CTL_ADD, + EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLLERR, EPOLLIN, EPOLLOUT, epoll_create1, epoll_ctl, + epoll_data, epoll_event, epoll_wait, }, - sys_time::timeval, }, - platform::{self, types::*}, + platform::{ + self, + types::{c_int, suseconds_t, time_t}, + }, }; +/// See . +/// +/// Note that the `timeval` struct was specified for +/// [`sys/time.h`](crate::header::sys_time) in the Open Group Base +/// Specifications Issue 7 and prior, see +/// . +#[repr(C)] +#[derive(Default)] +pub struct timeval { + pub tv_sec: time_t, + pub tv_usec: suseconds_t, +} + // fd_set is also defined in C because cbindgen is incompatible with mem::size_of booo +/// See . pub const FD_SETSIZE: usize = 1024; -type bitset = BitSet<[c_ulong; FD_SETSIZE / (8 * mem::size_of::())]>; +type FdBitSet = BitSet<[u64; FD_SETSIZE / (8 * mem::size_of::())]>; +/// See . #[repr(C)] pub struct fd_set { - pub fds_bits: bitset, + pub fds_bits: FdBitSet, } +#[allow(clippy::needless_update)] pub fn select_epoll( nfds: c_int, readfds: Option<&mut fd_set>, @@ -35,7 +55,7 @@ pub fn select_epoll( timeout: Option<&mut timeval>, ) -> c_int { if nfds < 0 || nfds > FD_SETSIZE as i32 { - unsafe { platform::errno = errno::EINVAL }; + platform::ERRNO.set(errno::EINVAL); return -1; }; @@ -47,62 +67,62 @@ pub fn select_epoll( File::new(epfd) }; - let mut read_bitset: Option<&mut bitset> = readfds.map(|fd_set| &mut fd_set.fds_bits); - let mut write_bitset: Option<&mut bitset> = writefds.map(|fd_set| &mut fd_set.fds_bits); - let mut except_bitset: Option<&mut bitset> = exceptfds.map(|fd_set| &mut fd_set.fds_bits); + let mut read_bitset: Option<&mut FdBitSet> = readfds.map(|fd_set| &mut fd_set.fds_bits); + let mut write_bitset: Option<&mut FdBitSet> = writefds.map(|fd_set| &mut fd_set.fds_bits); + let mut except_bitset: Option<&mut FdBitSet> = exceptfds.map(|fd_set| &mut fd_set.fds_bits); // Keep track of the number of file descriptors that do not support epoll let mut not_epoll = 0; for fd in 0..nfds { let mut events = 0; - if let Some(ref fd_set) = read_bitset { - if fd_set.contains(fd as usize) { - events |= EPOLLIN; - } + if let Some(ref fd_set) = read_bitset + && fd_set.contains(fd as usize) + { + events |= EPOLLIN; } - if let Some(ref fd_set) = write_bitset { - if fd_set.contains(fd as usize) { - events |= EPOLLOUT; - } + if let Some(ref fd_set) = write_bitset + && fd_set.contains(fd as usize) + { + events |= EPOLLOUT; } - if let Some(ref fd_set) = except_bitset { - if fd_set.contains(fd as usize) { - events |= EPOLLERR; - } + if let Some(ref fd_set) = except_bitset + && fd_set.contains(fd as usize) + { + events |= EPOLLERR; } if events > 0 { let mut event = epoll_event { events, data: epoll_data { fd }, - ..Default::default() + ..Default::default() // clippy lint, _pad field on redox but not linux }; - if epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &mut event) < 0 { - if unsafe { platform::errno == errno::EPERM } { + if unsafe { epoll_ctl(*ep, EPOLL_CTL_ADD, fd, &raw mut event) } < 0 { + if platform::ERRNO.get() == errno::EPERM { not_epoll += 1; } else { return -1; } } else { - if let Some(ref mut fd_set) = read_bitset { - if fd_set.contains(fd as usize) { - fd_set.remove(fd as usize); - } + if let Some(ref mut fd_set) = read_bitset + && fd_set.contains(fd as usize) + { + fd_set.remove(fd as usize); } - if let Some(ref mut fd_set) = write_bitset { - if fd_set.contains(fd as usize) { - fd_set.remove(fd as usize); - } + if let Some(ref mut fd_set) = write_bitset + && fd_set.contains(fd as usize) + { + fd_set.remove(fd as usize); } - if let Some(ref mut fd_set) = except_bitset { - if fd_set.contains(fd as usize) { - fd_set.remove(fd as usize); - } + if let Some(ref mut fd_set) = except_bitset + && fd_set.contains(fd as usize) + { + fd_set.remove(fd as usize); } } } @@ -115,18 +135,24 @@ pub fn select_epoll( } else { match timeout { Some(timeout) => { - //TODO: Check for overflow - ((timeout.tv_sec as c_int) * 1000) + ((timeout.tv_usec as c_int) / 1000) + let sec_ms = (timeout.tv_sec as c_int).checked_mul(1000); + let usec_ms = (timeout.tv_usec as c_int) / 1000; + match sec_ms.and_then(|s| s.checked_add(usec_ms)) { + Some(s) => s as c_int, + None => c_int::MAX, + } } None => -1, } }; - let res = epoll_wait( - *ep, - events.as_mut_ptr(), - events.len() as c_int, - epoll_timeout, - ); + let res = unsafe { + epoll_wait( + *ep, + events.as_mut_ptr(), + events.len() as c_int, + epoll_timeout, + ) + }; if res < 0 { return -1; } @@ -136,30 +162,31 @@ pub fn select_epoll( let fd = unsafe { event.data.fd }; // TODO: Error status when fd does not match? if fd >= 0 && fd < FD_SETSIZE as c_int { - if event.events & EPOLLIN > 0 { - if let Some(ref mut fd_set) = read_bitset { - fd_set.insert(fd as usize); - count += 1; - } + if event.events & EPOLLIN > 0 + && let Some(ref mut fd_set) = read_bitset + { + fd_set.insert(fd as usize); + count += 1; } - if event.events & EPOLLOUT > 0 { - if let Some(ref mut fd_set) = write_bitset { - fd_set.insert(fd as usize); - count += 1; - } + if event.events & EPOLLOUT > 0 + && let Some(ref mut fd_set) = write_bitset + { + fd_set.insert(fd as usize); + count += 1; } - if event.events & EPOLLERR > 0 { - if let Some(ref mut fd_set) = except_bitset { - fd_set.insert(fd as usize); - count += 1; - } + if event.events & EPOLLERR > 0 + && let Some(ref mut fd_set) = except_bitset + { + fd_set.insert(fd as usize); + count += 1; } } } count } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn select( nfds: c_int, readfds: *mut fd_set, @@ -173,22 +200,22 @@ pub unsafe extern "C" fn select( if readfds.is_null() { None } else { - Some(&mut *readfds) + Some(unsafe { &mut *readfds }) }, if writefds.is_null() { None } else { - Some(&mut *writefds) + Some(unsafe { &mut *writefds }) }, if exceptfds.is_null() { None } else { - Some(&mut *exceptfds) + Some(unsafe { &mut *exceptfds }) }, if timeout.is_null() { None } else { - Some(&mut *timeout) + Some(unsafe { &mut *timeout }) } ), "select({}, {:p}, {:p}, {:p}, {:p})", diff --git a/src/header/sys_signalfd/cbindgen.toml b/src/header/sys_signalfd/cbindgen.toml new file mode 100644 index 0000000000..acd5ddd02d --- /dev/null +++ b/src/header/sys_signalfd/cbindgen.toml @@ -0,0 +1,24 @@ +sys_includes = ["signal.h", "stdint.h", "stddef.h"] +include_guard = "_SYS_SIGNALFD_H" +trailer = """ +#ifndef SFD_CLOEXEC +#define SFD_CLOEXEC 0x80000 +#endif + +#ifndef SFD_NONBLOCK +#define SFD_NONBLOCK 0x800 +#endif + +int signalfd(int fd, const sigset_t *mask, size_t masksize); +int signalfd4(int fd, const sigset_t *mask, size_t masksize, int flags); +""" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export.rename] +"signalfd_siginfo" = "struct signalfd_siginfo" diff --git a/src/header/sys_signalfd/mod.rs b/src/header/sys_signalfd/mod.rs new file mode 100644 index 0000000000..a7a7d85e4a --- /dev/null +++ b/src/header/sys_signalfd/mod.rs @@ -0,0 +1,14 @@ +//! `sys/signalfd.h` implementation. + +use crate::{ + header::signal::{self, signalfd_siginfo}, + platform::types::c_int, +}; + +pub const SFD_CLOEXEC: c_int = signal::SFD_CLOEXEC; +pub const SFD_NONBLOCK: c_int = signal::SFD_NONBLOCK; + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_sys_signalfd_siginfo(siginfo: signalfd_siginfo) { + let _ = siginfo; +} diff --git a/src/header/sys_socket/cbindgen.toml b/src/header/sys_socket/cbindgen.toml index 1222665d7e..4bac60ee8b 100644 --- a/src/header/sys_socket/cbindgen.toml +++ b/src/header/sys_socket/cbindgen.toml @@ -1,14 +1,45 @@ -sys_includes = ["stddef.h", "stdint.h", "sys/types.h"] -include_guard = "_SYS_SOCKET_H" -trailer = "#include " +sys_includes = ["stddef.h", "stdint.h", "sys/types.h", "sys/un.h"] +include_guard = "_RELIBC_SYS_SOCKET_H" +after_includes = """ +#include // for iovec from sys/uio.h +#include // for sa_family_t +#include // for socklen_t +""" +trailer = """ +#ifndef _RELIBC_BITS_SYS_SOCKET_H +#define _RELIBC_BITS_SYS_SOCKET_H + +struct sockaddr_storage { + sa_family_t ss_family; + char __ss_padding[128-sizeof(long)-sizeof(sa_family_t)]; + unsigned long __ss_align; +}; + +// These definitions were taken from musl, license MIT { +#define __CMSG_LEN(cmsg) (((cmsg)->cmsg_len + sizeof(long) - 1) & ~(long)(sizeof(long) - 1)) +#define __CMSG_NEXT(cmsg) ((unsigned char *)(cmsg) + __CMSG_LEN(cmsg)) +#define __MHDR_END(mhdr) ((unsigned char *)(mhdr)->msg_control + (mhdr)->msg_controllen) + +#define CMSG_DATA(cmsg) ((unsigned char *) (((struct cmsghdr *)(cmsg)) + 1)) +#define CMSG_NXTHDR(mhdr, cmsg) ((cmsg)->cmsg_len < sizeof (struct cmsghdr) || \ + __CMSG_LEN(cmsg) + sizeof(struct cmsghdr) >= __MHDR_END(mhdr) - (unsigned char *)(cmsg) \ + ? 0 : (struct cmsghdr *)__CMSG_NEXT(cmsg)) +#define CMSG_FIRSTHDR(mhdr) ((size_t) (mhdr)->msg_controllen >= sizeof (struct cmsghdr) ? (struct cmsghdr *) (mhdr)->msg_control : (struct cmsghdr *) 0) + +#define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1)) +#define CMSG_SPACE(len) (CMSG_ALIGN (len) + CMSG_ALIGN (sizeof (struct cmsghdr))) +#define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len)) +// } from musl, license MIT + +#endif // _RELIBC_BITS_SYS_SOCKET_H +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true + +[export.rename] +"iovec" = "struct iovec" diff --git a/src/header/sys_socket/constants.rs b/src/header/sys_socket/constants.rs index f9e7b9ab8f..c91ffb1af5 100644 --- a/src/header/sys_socket/constants.rs +++ b/src/header/sys_socket/constants.rs @@ -1,11 +1,13 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const SOCK_STREAM: c_int = 1; pub const SOCK_DGRAM: c_int = 2; +pub const SOCK_RAW: c_int = 3; pub const SOCK_NONBLOCK: c_int = 0o4_000; pub const SOCK_CLOEXEC: c_int = 0o2_000_000; // Other constants +pub const SOCK_RDM: c_int = 4; pub const SOCK_SEQPACKET: c_int = 5; pub const SOL_SOCKET: c_int = 1; @@ -46,18 +48,33 @@ pub const MSG_EOR: c_int = 128; pub const MSG_OOB: c_int = 1; pub const MSG_PEEK: c_int = 2; pub const MSG_TRUNC: c_int = 32; +pub const MSG_DONTWAIT: c_int = 64; pub const MSG_WAITALL: c_int = 256; +pub const MSG_CMSG_CLOEXEC: c_int = 0x40000000; + +pub const IP_ADD_SOURCE_MEMBERSHIP: c_int = 70; +pub const IP_DROP_SOURCE_MEMBERSHIP: c_int = 71; +pub const MCAST_JOIN_SOURCE_GROUP: c_int = 46; +pub const MCAST_LEAVE_SOURCE_GROUP: c_int = 47; pub const AF_INET: c_int = 2; pub const AF_INET6: c_int = 10; +pub const AF_LOCAL: c_int = AF_UNIX; pub const AF_UNIX: c_int = 1; pub const AF_UNSPEC: c_int = 0; pub const PF_INET: c_int = 2; pub const PF_INET6: c_int = 10; +pub const PF_LOCAL: c_int = PF_UNIX; pub const PF_UNIX: c_int = 1; pub const PF_UNSPEC: c_int = 0; pub const SHUT_RD: c_int = 0; pub const SHUT_RDWR: c_int = 2; pub const SHUT_WR: c_int = 1; + +pub const SCM_RIGHTS: c_int = 1; +pub const SCM_CREDENTIALS: c_int = 2; + +pub const IPPROTO_TCP: c_int = 6; +pub const TCP_NODELAY: c_int = 1; diff --git a/src/header/sys_socket/mod.rs b/src/header/sys_socket/mod.rs index 7a8c659582..1c0c6d7493 100644 --- a/src/header/sys_socket/mod.rs +++ b/src/header/sys_socket/mod.rs @@ -1,29 +1,185 @@ -//! socket implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html +//! `sys/socket.h` implementation. +//! +//! See . -use core::ptr; +use core::{mem, ptr}; -use crate::platform::{types::*, PalSocket, Sys}; +use crate::{ + error::ResultExt, + header::{bits_iovec::iovec, bits_safamily_t::sa_family_t, bits_socklen_t::socklen_t}, + platform::{ + PalSocket, Sys, + types::{ + c_char, c_int, c_long, c_uchar, c_uint, c_void, gid_t, pid_t, size_t, ssize_t, uid_t, + }, + }, +}; pub mod constants; -pub type sa_family_t = u16; -pub type socklen_t = u32; +/// See . +#[repr(C)] +#[derive(Default, CheckVsLibcCrate)] +pub struct linger { + pub l_onoff: c_int, + pub l_linger: c_int, +} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_linger(linger: linger) {} + +/// See . +#[repr(C)] +#[derive(Debug, CheckVsLibcCrate)] +pub struct msghdr { + pub msg_name: *mut c_void, + pub msg_namelen: socklen_t, + pub msg_iov: *mut iovec, + pub msg_iovlen: size_t, + pub msg_control: *mut c_void, + pub msg_controllen: size_t, + pub msg_flags: c_int, +} + +/// See . +#[repr(C)] +#[derive(Debug, CheckVsLibcCrate)] +pub struct cmsghdr { + pub cmsg_len: size_t, + pub cmsg_level: c_int, + pub cmsg_type: c_int, +} #[repr(C)] -#[derive(Default)] +#[derive(Clone, Debug)] +// FIXME: CheckVsLibcCrate +pub struct ucred { + pub pid: pid_t, + pub uid: uid_t, + pub gid: gid_t, +} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_cmsghdr(cmsghdr: cmsghdr) {} + +#[unsafe(no_mangle)] +pub extern "C" fn _cbindgen_export_ucred(ucred: ucred) {} + +/// See . +#[repr(C)] +#[derive(Default, CheckVsLibcCrate)] pub struct sockaddr { pub sa_family: sa_family_t, pub sa_data: [c_char; 14], } -#[no_mangle] +// Max size of [`sockaddr_storage`] +/// cbindgen:ignore +const _SS_MAXSIZE: usize = 128; +// Align to pointer width +/// cbindgen:ignore +const _SS_PADDING: usize = _SS_MAXSIZE - mem::size_of::() - mem::size_of::(); + +/// See . +/// Opaque storage large enough to hold any protocol specific address structure. +/// +/// ## Implementation notes +/// * The total size of this struct is 128 bytes which is based off of `musl` and `glibc` +/// * The underscore fields are implementation specific details for padding that may change +/// * [`usize`] is used because it's the width of a pointer for a given platform +/// * The order of the fields is important because the bytes in the padding will be cast to and +/// from protocol structs in C +#[repr(C)] +//#[derive(CheckVsLibcCrate)] FIXME: can't ignore private fields yet +/// cbindgen:ignore +pub struct sockaddr_storage { + pub ss_family: sa_family_t, + __ss_pad2: [u8; _SS_PADDING], + __ss_align: usize, +} + +// These must match C macros in sys_socket/cbindgen.toml { +/// cbindgen:ignore +pub unsafe extern "C" fn __CMSG_LEN(cmsg: *const cmsghdr) -> ssize_t { + ((unsafe { (*cmsg).cmsg_len as size_t } + mem::size_of::() - 1) + & !(mem::size_of::() - 1)) as ssize_t +} + +/// cbindgen:ignore +pub unsafe extern "C" fn __CMSG_NEXT(cmsg: *const cmsghdr) -> *mut c_uchar { + unsafe { (cmsg as *mut c_uchar).offset(__CMSG_LEN(cmsg)) } +} + +/// cbindgen:ignore +pub unsafe extern "C" fn __MHDR_END(mhdr: *const msghdr) -> *mut c_uchar { + unsafe { ((*mhdr).msg_control.cast::()).add((*mhdr).msg_controllen) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_DATA(cmsg: *const cmsghdr) -> *mut c_uchar { + unsafe { (cmsg as *mut c_uchar).add(CMSG_ALIGN(mem::size_of::())) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_NXTHDR(mhdr: *const msghdr, cmsg: *const cmsghdr) -> *mut cmsghdr { + if cmsg.is_null() { + return unsafe { CMSG_FIRSTHDR(mhdr) }; + }; + + unsafe { + let next = + cmsg as usize + CMSG_ALIGN((*cmsg).cmsg_len) + CMSG_ALIGN(mem::size_of::()); + let max = (*mhdr).msg_control as usize + (*mhdr).msg_controllen; + if next > max { + ptr::null_mut::() + } else { + (cmsg as usize + CMSG_ALIGN((*cmsg).cmsg_len)) as *mut cmsghdr + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_FIRSTHDR(mhdr: *const msghdr) -> *mut cmsghdr { + unsafe { + if (*mhdr).msg_controllen >= mem::size_of::() { + (*mhdr).msg_control.cast::() + } else { + ptr::null_mut::() + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_ALIGN(len: size_t) -> size_t { + (len + mem::size_of::() - 1) & !(mem::size_of::() - 1) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_SPACE(len: c_uint) -> c_uint { + (unsafe { CMSG_ALIGN(len as size_t) } + unsafe { CMSG_ALIGN(mem::size_of::()) }) + as c_uint +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CMSG_LEN(length: c_uint) -> c_uint { + (unsafe { CMSG_ALIGN(mem::size_of::()) } + length as usize) as c_uint +} +// } These must match C macros in sys_socket/cbindgen.toml + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn accept( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, ) -> c_int { trace_expr!( - Sys::accept(socket, address, address_len), + unsafe { Sys::accept(socket, address, address_len) }.or_minus_one_errno(), "accept({}, {:p}, {:p})", socket, address, @@ -31,14 +187,17 @@ pub unsafe extern "C" fn accept( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn bind( socket: c_int, address: *const sockaddr, address_len: socklen_t, ) -> c_int { trace_expr!( - Sys::bind(socket, address, address_len), + unsafe { Sys::bind(socket, address, address_len) } + .map(|()| 0) + .or_minus_one_errno(), "bind({}, {:p}, {})", socket, address, @@ -46,14 +205,15 @@ pub unsafe extern "C" fn bind( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn connect( socket: c_int, address: *const sockaddr, address_len: socklen_t, ) -> c_int { trace_expr!( - Sys::connect(socket, address, address_len), + unsafe { Sys::connect(socket, address, address_len) }.or_minus_one_errno(), "connect({}, {:p}, {})", socket, address, @@ -61,14 +221,17 @@ pub unsafe extern "C" fn connect( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getpeername( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, ) -> c_int { trace_expr!( - Sys::getpeername(socket, address, address_len), + unsafe { Sys::getpeername(socket, address, address_len) } + .map(|()| 0) + .or_minus_one_errno(), "getpeername({}, {:p}, {:p})", socket, address, @@ -76,14 +239,17 @@ pub unsafe extern "C" fn getpeername( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getsockname( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, ) -> c_int { trace_expr!( - Sys::getsockname(socket, address, address_len), + unsafe { Sys::getsockname(socket, address, address_len) } + .map(|()| 0) + .or_minus_one_errno(), "getsockname({}, {:p}, {:p})", socket, address, @@ -91,7 +257,8 @@ pub unsafe extern "C" fn getsockname( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getsockopt( socket: c_int, level: c_int, @@ -100,7 +267,9 @@ pub unsafe extern "C" fn getsockopt( option_len: *mut socklen_t, ) -> c_int { trace_expr!( - Sys::getsockopt(socket, level, option_name, option_value, option_len), + unsafe { Sys::getsockopt(socket, level, option_name, option_value, option_len) } + .map(|()| 0) + .or_minus_one_errno(), "getsockopt({}, {}, {}, {:p}, {:p})", socket, level, @@ -110,29 +279,36 @@ pub unsafe extern "C" fn getsockopt( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn listen(socket: c_int, backlog: c_int) -> c_int { Sys::listen(socket, backlog) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn recv( socket: c_int, buffer: *mut c_void, length: size_t, flags: c_int, ) -> ssize_t { - recvfrom( - socket, - buffer, - length, - flags, - ptr::null_mut(), - ptr::null_mut(), - ) + unsafe { + recvfrom( + socket, + buffer, + length, + flags, + ptr::null_mut(), + ptr::null_mut(), + ) + } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn recvfrom( socket: c_int, buffer: *mut c_void, @@ -142,7 +318,9 @@ pub unsafe extern "C" fn recvfrom( address_len: *mut socklen_t, ) -> ssize_t { trace_expr!( - Sys::recvfrom(socket, buffer, length, flags, address, address_len), + unsafe { Sys::recvfrom(socket, buffer, length, flags, address, address_len) } + .map(|r| r as ssize_t) + .or_minus_one_errno(), "recvfrom({}, {:p}, {}, {:#x}, {:p}, {:p})", socket, buffer, @@ -153,17 +331,35 @@ pub unsafe extern "C" fn recvfrom( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn recvmsg(socket: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t { + unsafe { Sys::recvmsg(socket, msg, flags) } + .map(|r| r as ssize_t) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn send( socket: c_int, message: *const c_void, length: size_t, flags: c_int, ) -> ssize_t { - sendto(socket, message, length, flags, ptr::null(), 0) + unsafe { sendto(socket, message, length, flags, ptr::null(), 0) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sendmsg(socket: c_int, msg: *const msghdr, flags: c_int) -> ssize_t { + unsafe { Sys::sendmsg(socket, msg, flags) } + .map(|w| w as ssize_t) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn sendto( socket: c_int, message: *const c_void, @@ -173,7 +369,9 @@ pub unsafe extern "C" fn sendto( dest_len: socklen_t, ) -> ssize_t { trace_expr!( - Sys::sendto(socket, message, length, flags, dest_addr, dest_len), + unsafe { Sys::sendto(socket, message, length, flags, dest_addr, dest_len) } + .map(|w| w as ssize_t) + .or_minus_one_errno(), "sendto({}, {:p}, {}, {:#x}, {:p}, {})", socket, message, @@ -184,7 +382,8 @@ pub unsafe extern "C" fn sendto( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn setsockopt( socket: c_int, level: c_int, @@ -193,7 +392,9 @@ pub unsafe extern "C" fn setsockopt( option_len: socklen_t, ) -> c_int { trace_expr!( - Sys::setsockopt(socket, level, option_name, option_value, option_len), + unsafe { Sys::setsockopt(socket, level, option_name, option_value, option_len) } + .map(|()| 0) + .or_minus_one_errno(), "setsockopt({}, {}, {}, {:p}, {})", socket, level, @@ -203,15 +404,17 @@ pub unsafe extern "C" fn setsockopt( ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn shutdown(socket: c_int, how: c_int) -> c_int { - Sys::shutdown(socket, how) + Sys::shutdown(socket, how).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn socket(domain: c_int, kind: c_int, protocol: c_int) -> c_int { trace_expr!( - Sys::socket(domain, kind, protocol), + unsafe { Sys::socket(domain, kind, protocol) }.or_minus_one_errno(), "socket({}, {}, {})", domain, kind, @@ -219,7 +422,8 @@ pub unsafe extern "C" fn socket(domain: c_int, kind: c_int, protocol: c_int) -> ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn socketpair( domain: c_int, kind: c_int, @@ -227,7 +431,11 @@ pub unsafe extern "C" fn socketpair( sv: *mut c_int, ) -> c_int { trace_expr!( - Sys::socketpair(domain, kind, protocol, &mut *(sv as *mut [c_int; 2])), + Sys::socketpair(domain, kind, protocol, unsafe { + &mut *sv.cast::<[c_int; 2]>() + }) + .map(|()| 0) + .or_minus_one_errno(), "socketpair({}, {}, {}, {:p})", domain, kind, diff --git a/src/header/sys_stat/cbindgen.toml b/src/header/sys_stat/cbindgen.toml index 345db1f4ed..61ca0da6b4 100644 --- a/src/header/sys_stat/cbindgen.toml +++ b/src/header/sys_stat/cbindgen.toml @@ -1,6 +1,26 @@ -sys_includes = ["sys/types.h", "time.h"] -include_guard = "_SYS_STAT_H" -trailer = "#include " +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_stat.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the blkcnt_t, blksize_t, dev_t, ino_t, mode_t, nlink_t, uid_t, gid_t, off_t, and time_t types as described in ." +# - "The header shall define the timespec structure as described in . Times shall be given in seconds since the Epoch." +# - "Inclusion of the header may make visible all symbols from the header." +sys_includes = ["sys/types.h"] +include_guard = "_RELIBC_SYS_STAT_H" +after_includes = """ +#include // for timespec from time.h + +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) + +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec +""" language = "C" style = "Tag" no_includes = true diff --git a/src/header/sys_stat/mod.rs b/src/header/sys_stat/mod.rs index 5a047a0755..0975a20dec 100644 --- a/src/header/sys_stat/mod.rs +++ b/src/header/sys_stat/mod.rs @@ -1,12 +1,22 @@ -//! stat implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysstat.h.html +//! `sys/stat.h` implementation. +//! +//! See . use crate::{ c_str::CStr, + error::ResultExt, header::{ + bits_timespec::timespec, fcntl::{O_NOFOLLOW, O_PATH}, - time::timespec, }, - platform::{types::*, Pal, Sys}, + out::Out, + platform::{ + Pal, Sys, + types::{ + blkcnt_t, blksize_t, c_char, c_int, c_long, dev_t, gid_t, ino_t, mode_t, nlink_t, + off_t, uid_t, + }, + }, }; pub const S_IFMT: c_int = 0o0_170_000; @@ -24,6 +34,11 @@ pub const S_IRUSR: c_int = 0o0_400; pub const S_IWUSR: c_int = 0o0_200; pub const S_IXUSR: c_int = 0o0_100; +// Defined for compatibility +pub const S_IREAD: c_int = S_IRUSR; +pub const S_IWRITE: c_int = S_IWUSR; +pub const S_IEXEC: c_int = S_IXUSR; + pub const S_IRWXG: c_int = 0o0_070; pub const S_IRGRP: c_int = 0o0_040; pub const S_IWGRP: c_int = 0o0_020; @@ -37,6 +52,10 @@ pub const S_ISUID: c_int = 0o4_000; pub const S_ISGID: c_int = 0o2_000; pub const S_ISVTX: c_int = 0o1_000; +pub const UTIME_NOW: c_long = (1 << 30) - 1; +pub const UTIME_OMIT: c_long = (1 << 30) - 2; + +/// See . #[repr(C)] #[derive(Default)] pub struct stat { @@ -61,80 +80,163 @@ pub struct stat { pub _pad: [c_char; 24], } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn chmod(path: *const c_char, mode: mode_t) -> c_int { - let path = CStr::from_ptr(path); - Sys::chmod(path, mode) + let path = unsafe { CStr::from_ptr(path) }; + Sys::chmod(path, mode).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn fchmod(fildes: c_int, mode: mode_t) -> c_int { - Sys::fchmod(fildes, mode) + Sys::fchmod(fildes, mode).map(|()| 0).or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn fstat(fildes: c_int, buf: *mut stat) -> c_int { - Sys::fstat(fildes, buf) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fchmodat( + dirfd: c_int, + path: *const c_char, + mode: mode_t, + flags: c_int, +) -> c_int { + let path = unsafe { CStr::from_nullable_ptr(path) }; + Sys::fchmodat(dirfd, path, mode, flags) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn __fxstat(_ver: c_int, fildes: c_int, buf: *mut stat) -> c_int { - fstat(fildes, buf) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstat(fildes: c_int, buf: *mut stat) -> c_int { + let buf = unsafe { Out::nonnull(buf) }; + Sys::fstat(fildes, buf).map(|()| 0).or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn futimens(fd: c_int, times: *const timespec) -> c_int { - Sys::futimens(fd, times) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstatat( + fildes: c_int, + path: *const c_char, + buf: *mut stat, + flags: c_int, +) -> c_int { + let path = unsafe { CStr::from_nullable_ptr(path) }; + let buf = unsafe { Out::nonnull(buf) }; + Sys::fstatat(fildes, path, buf, flags) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __fxstat(_ver: c_int, fildes: c_int, buf: *mut stat) -> c_int { + unsafe { fstat(fildes, buf) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn futimens(fd: c_int, times: *const timespec) -> c_int { + unsafe { Sys::futimens(fd, times) } + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat) -> c_int { - let path = CStr::from_ptr(path); - let fd = Sys::open(path, O_PATH | O_NOFOLLOW, 0); + let path = unsafe { CStr::from_ptr(path) }; + let buf = unsafe { Out::nonnull(buf) }; + + // TODO: Rustify + let fd = Sys::open(path, O_PATH | O_NOFOLLOW, 0).or_minus_one_errno(); if fd < 0 { return -1; } - let res = Sys::fstat(fd, buf); + // TODO: Rustify + let res = Sys::fstat(fd, buf).map(|()| 0).or_minus_one_errno(); - Sys::close(fd); + if let Ok(()) = Sys::close(fd) {}; // TODO handle error res } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkdirat(dirfd: c_int, path: *const c_char, mode: mode_t) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::mkdirat(dirfd, path, mode) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mkdir(path: *const c_char, mode: mode_t) -> c_int { - let path = CStr::from_ptr(path); - Sys::mkdir(path, mode) + let path = unsafe { CStr::from_ptr(path) }; + Sys::mkdir(path, mode).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkfifoat(dirfd: c_int, path: *const c_char, mode: mode_t) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::mkfifoat(dirfd, path, mode) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mkfifo(path: *const c_char, mode: mode_t) -> c_int { - let path = CStr::from_ptr(path); - Sys::mkfifo(path, mode) + let path = unsafe { CStr::from_ptr(path) }; + Sys::mkfifo(path, mode).map(|()| 0).or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn mknod(path: *const c_char, mode: mode_t, dev: dev_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mknod(path: *const c_char, mode: mode_t, dev: dev_t) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::mknod(path, mode, dev).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mknodat( + dirfd: c_int, + path: *const c_char, + mode: mode_t, + dev: dev_t, +) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::mknodat(dirfd, path, mode, dev) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn stat(file: *const c_char, buf: *mut stat) -> c_int { - let file = CStr::from_ptr(file); - let fd = Sys::open(file, O_PATH, 0); + let file = unsafe { CStr::from_ptr(file) }; + let buf = unsafe { Out::nonnull(buf) }; + + // TODO: Rustify + let fd = Sys::open(file, O_PATH, 0).or_minus_one_errno(); if fd < 0 { return -1; } - let res = Sys::fstat(fd, buf); + // TODO: Rustify + let res = Sys::fstat(fd, buf).map(|()| 0).or_minus_one_errno(); - Sys::close(fd); + if let Ok(()) = Sys::close(fd) {}; // TODO handle error res } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn umask(mask: mode_t) -> mode_t { Sys::umask(mask) } diff --git a/src/header/sys_statvfs/cbindgen.toml b/src/header/sys_statvfs/cbindgen.toml index 5ba0ede3e1..271cf9a364 100644 --- a/src/header/sys_statvfs/cbindgen.toml +++ b/src/header/sys_statvfs/cbindgen.toml @@ -1,8 +1,12 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_statvfs.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the fsblkcnt_t and fsfilcnt_t types as described in ." sys_includes = ["sys/types.h"] -include_guard = "_SYS_STATVFS_H" +include_guard = "_RELIBC_SYS_STATVFS_H" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/sys_statvfs/mod.rs b/src/header/sys_statvfs/mod.rs index 1cbd9e4ebd..866768b885 100644 --- a/src/header/sys_statvfs/mod.rs +++ b/src/header/sys_statvfs/mod.rs @@ -1,9 +1,16 @@ -//! statvfs implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysstatvfs.h.html +//! `sys_statvfs.h` implementation. +//! +//! See . use crate::{ c_str::CStr, + error::ResultExt, header::fcntl::O_PATH, - platform::{types::*, Pal, Sys}, + out::Out, + platform::{ + Pal, Sys, + types::{c_char, c_int, c_ulong, fsblkcnt_t, fsfilcnt_t}, + }, }; //pub const ST_RDONLY @@ -25,22 +32,27 @@ pub struct statvfs { pub f_namemax: c_ulong, } -#[no_mangle] -pub extern "C" fn fstatvfs(fildes: c_int, buf: *mut statvfs) -> c_int { - Sys::fstatvfs(fildes, buf) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstatvfs(fildes: c_int, buf: *mut statvfs) -> c_int { + let buf = unsafe { Out::nonnull(buf) }; + Sys::fstatvfs(fildes, buf).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn statvfs(file: *const c_char, buf: *mut statvfs) -> c_int { - let file = CStr::from_ptr(file); - let fd = Sys::open(file, O_PATH, 0); + let file = unsafe { CStr::from_ptr(file) }; + let buf = unsafe { Out::nonnull(buf) }; + // TODO: Rustify + let fd = Sys::open(file, O_PATH, 0).or_minus_one_errno(); if fd < 0 { return -1; } - let res = Sys::fstatvfs(fd, buf); + let res = Sys::fstatvfs(fd, buf).map(|()| 0).or_minus_one_errno(); - Sys::close(fd); + if let Ok(()) = Sys::close(fd) {}; // TODO handle error res } diff --git a/src/header/sys_syscall/aarch64.rs b/src/header/sys_syscall/aarch64.rs new file mode 100644 index 0000000000..369a4b045a --- /dev/null +++ b/src/header/sys_syscall/aarch64.rs @@ -0,0 +1,303 @@ +// Adapted from sc crate: https://github.com/japaric/syscall.rs/blob/master/src/platform/linux-aarch64/nr.rs +// const ([A-Z_\d]+): -> const __NR_\L$1: + +pub const __NR_accept: usize = 202; +pub const __NR_accept4: usize = 242; +pub const __NR_acct: usize = 89; +pub const __NR_add_key: usize = 217; +pub const __NR_adjtimex: usize = 171; +pub const __NR_arch_specific_syscall: usize = 244; +pub const __NR_bind: usize = 200; +pub const __NR_bpf: usize = 280; +pub const __NR_brk: usize = 214; +pub const __NR_capget: usize = 90; +pub const __NR_capset: usize = 91; +pub const __NR_chdir: usize = 49; +pub const __NR_chroot: usize = 51; +pub const __NR_clock_adjtime: usize = 266; +pub const __NR_clock_getres: usize = 114; +pub const __NR_clock_gettime: usize = 113; +pub const __NR_clock_nanosleep: usize = 115; +pub const __NR_clock_settime: usize = 112; +pub const __NR_clone: usize = 220; +pub const __NR_clone3: usize = 435; +pub const __NR_close: usize = 57; +pub const __NR_close_range: usize = 436; +pub const __NR_connect: usize = 203; +pub const __NR_copy_file_range: usize = 285; +pub const __NR_delete_module: usize = 106; +pub const __NR_dup: usize = 23; +pub const __NR_dup3: usize = 24; +pub const __NR_epoll_create1: usize = 20; +pub const __NR_epoll_ctl: usize = 21; +pub const __NR_epoll_pwait: usize = 22; +pub const __NR_epoll_pwait2: usize = 441; +pub const __NR_eventfd2: usize = 19; +pub const __NR_execve: usize = 221; +pub const __NR_execveat: usize = 281; +pub const __NR_exit: usize = 93; +pub const __NR_exit_group: usize = 94; +pub const __NR_faccessat: usize = 48; +pub const __NR_faccessat2: usize = 439; +pub const __NR_fadvise64: usize = 223; +pub const __NR_fallocate: usize = 47; +pub const __NR_fanotify_init: usize = 262; +pub const __NR_fanotify_mark: usize = 263; +pub const __NR_fchdir: usize = 50; +pub const __NR_fchmod: usize = 52; +pub const __NR_fchmodat: usize = 53; +pub const __NR_fchown: usize = 55; +pub const __NR_fchownat: usize = 54; +pub const __NR_fcntl: usize = 25; +pub const __NR_fdatasync: usize = 83; +pub const __NR_fgetxattr: usize = 10; +pub const __NR_finit_module: usize = 273; +pub const __NR_flistxattr: usize = 13; +pub const __NR_flock: usize = 32; +pub const __NR_fremovexattr: usize = 16; +pub const __NR_fsconfig: usize = 431; +pub const __NR_fsetxattr: usize = 7; +pub const __NR_fsmount: usize = 432; +pub const __NR_fsopen: usize = 430; +pub const __NR_fspick: usize = 433; +pub const __NR_fstat: usize = 80; +pub const __NR_fstatfs: usize = 44; +pub const __NR_fsync: usize = 82; +pub const __NR_ftruncate: usize = 46; +pub const __NR_futex: usize = 98; +pub const __NR_get_mempolicy: usize = 236; +pub const __NR_get_robust_list: usize = 100; +pub const __NR_getcpu: usize = 168; +pub const __NR_getcwd: usize = 17; +pub const __NR_getdents64: usize = 61; +pub const __NR_getegid: usize = 177; +pub const __NR_geteuid: usize = 175; +pub const __NR_getgid: usize = 176; +pub const __NR_getgroups: usize = 158; +pub const __NR_getitimer: usize = 102; +pub const __NR_getpeername: usize = 205; +pub const __NR_getpgid: usize = 155; +pub const __NR_getpid: usize = 172; +pub const __NR_getppid: usize = 173; +pub const __NR_getpriority: usize = 141; +pub const __NR_getrandom: usize = 278; +pub const __NR_getresgid: usize = 150; +pub const __NR_getresuid: usize = 148; +pub const __NR_getrlimit: usize = 163; +pub const __NR_getrusage: usize = 165; +pub const __NR_getsid: usize = 156; +pub const __NR_getsockname: usize = 204; +pub const __NR_getsockopt: usize = 209; +pub const __NR_gettid: usize = 178; +pub const __NR_gettimeofday: usize = 169; +pub const __NR_getuid: usize = 174; +pub const __NR_getxattr: usize = 8; +pub const __NR_init_module: usize = 105; +pub const __NR_inotify_add_watch: usize = 27; +pub const __NR_inotify_init1: usize = 26; +pub const __NR_inotify_rm_watch: usize = 28; +pub const __NR_io_cancel: usize = 3; +pub const __NR_io_destroy: usize = 1; +pub const __NR_io_getevents: usize = 4; +pub const __NR_io_pgetevents: usize = 292; +pub const __NR_io_setup: usize = 0; +pub const __NR_io_submit: usize = 2; +pub const __NR_io_uring_enter: usize = 426; +pub const __NR_io_uring_register: usize = 427; +pub const __NR_io_uring_setup: usize = 425; +pub const __NR_ioctl: usize = 29; +pub const __NR_ioprio_get: usize = 31; +pub const __NR_ioprio_set: usize = 30; +pub const __NR_kcmp: usize = 272; +pub const __NR_kexec_file_load: usize = 294; +pub const __NR_kexec_load: usize = 104; +pub const __NR_keyctl: usize = 219; +pub const __NR_kill: usize = 129; +pub const __NR_lgetxattr: usize = 9; +pub const __NR_linkat: usize = 37; +pub const __NR_listen: usize = 201; +pub const __NR_listxattr: usize = 11; +pub const __NR_llistxattr: usize = 12; +pub const __NR_lookup_dcookie: usize = 18; +pub const __NR_lremovexattr: usize = 15; +pub const __NR_lseek: usize = 62; +pub const __NR_lsetxattr: usize = 6; +pub const __NR_madvise: usize = 233; +pub const __NR_mbind: usize = 235; +pub const __NR_membarrier: usize = 283; +pub const __NR_memfd_create: usize = 279; +pub const __NR_migrate_pages: usize = 238; +pub const __NR_mincore: usize = 232; +pub const __NR_mkdirat: usize = 34; +pub const __NR_mknodat: usize = 33; +pub const __NR_mlock: usize = 228; +pub const __NR_mlock2: usize = 284; +pub const __NR_mlockall: usize = 230; +pub const __NR_mmap: usize = 222; +pub const __NR_mount: usize = 40; +pub const __NR_mount_setattr: usize = 442; +pub const __NR_move_mount: usize = 429; +pub const __NR_move_pages: usize = 239; +pub const __NR_mprotect: usize = 226; +pub const __NR_mq_getsetattr: usize = 185; +pub const __NR_mq_notify: usize = 184; +pub const __NR_mq_open: usize = 180; +pub const __NR_mq_timedreceive: usize = 183; +pub const __NR_mq_timedsend: usize = 182; +pub const __NR_mq_unlink: usize = 181; +pub const __NR_mremap: usize = 216; +pub const __NR_msgctl: usize = 187; +pub const __NR_msgget: usize = 186; +pub const __NR_msgrcv: usize = 188; +pub const __NR_msgsnd: usize = 189; +pub const __NR_msync: usize = 227; +pub const __NR_munlock: usize = 229; +pub const __NR_munlockall: usize = 231; +pub const __NR_munmap: usize = 215; +pub const __NR_name_to_handle_at: usize = 264; +pub const __NR_nanosleep: usize = 101; +pub const __NR_newfstatat: usize = 79; +pub const __NR_nfsservctl: usize = 42; +pub const __NR_open_by_handle_at: usize = 265; +pub const __NR_open_tree: usize = 428; +pub const __NR_openat: usize = 56; +pub const __NR_openat2: usize = 437; +pub const __NR_perf_event_open: usize = 241; +pub const __NR_personality: usize = 92; +pub const __NR_pidfd_getfd: usize = 438; +pub const __NR_pidfd_open: usize = 434; +pub const __NR_pidfd_send_signal: usize = 424; +pub const __NR_pipe2: usize = 59; +pub const __NR_pivot_root: usize = 41; +pub const __NR_pkey_alloc: usize = 289; +pub const __NR_pkey_free: usize = 290; +pub const __NR_pkey_mprotect: usize = 288; +pub const __NR_ppoll: usize = 73; +pub const __NR_prctl: usize = 167; +pub const __NR_pread64: usize = 67; +pub const __NR_preadv: usize = 69; +pub const __NR_preadv2: usize = 286; +pub const __NR_prlimit64: usize = 261; +pub const __NR_process_madvise: usize = 440; +pub const __NR_process_vm_readv: usize = 270; +pub const __NR_process_vm_writev: usize = 271; +pub const __NR_pselect6: usize = 72; +pub const __NR_ptrace: usize = 117; +pub const __NR_pwrite64: usize = 68; +pub const __NR_pwritev: usize = 70; +pub const __NR_pwritev2: usize = 287; +pub const __NR_quotactl: usize = 60; +pub const __NR_read: usize = 63; +pub const __NR_readahead: usize = 213; +pub const __NR_readlinkat: usize = 78; +pub const __NR_readv: usize = 65; +pub const __NR_reboot: usize = 142; +pub const __NR_recvfrom: usize = 207; +pub const __NR_recvmmsg: usize = 243; +pub const __NR_recvmsg: usize = 212; +pub const __NR_remap_file_pages: usize = 234; +pub const __NR_removexattr: usize = 14; +pub const __NR_renameat: usize = 38; +pub const __NR_renameat2: usize = 276; +pub const __NR_request_key: usize = 218; +pub const __NR_restart_syscall: usize = 128; +pub const __NR_rseq: usize = 293; +pub const __NR_rt_sigaction: usize = 134; +pub const __NR_rt_sigpending: usize = 136; +pub const __NR_rt_sigprocmask: usize = 135; +pub const __NR_rt_sigqueueinfo: usize = 138; +pub const __NR_rt_sigreturn: usize = 139; +pub const __NR_rt_sigsuspend: usize = 133; +pub const __NR_rt_sigtimedwait: usize = 137; +pub const __NR_rt_tgsigqueueinfo: usize = 240; +pub const __NR_sched_get_priority_max: usize = 125; +pub const __NR_sched_get_priority_min: usize = 126; +pub const __NR_sched_getaffinity: usize = 123; +pub const __NR_sched_getattr: usize = 275; +pub const __NR_sched_getparam: usize = 121; +pub const __NR_sched_getscheduler: usize = 120; +pub const __NR_sched_rr_get_interval: usize = 127; +pub const __NR_sched_setaffinity: usize = 122; +pub const __NR_sched_setattr: usize = 274; +pub const __NR_sched_setparam: usize = 118; +pub const __NR_sched_setscheduler: usize = 119; +pub const __NR_sched_yield: usize = 124; +pub const __NR_seccomp: usize = 277; +pub const __NR_semctl: usize = 191; +pub const __NR_semget: usize = 190; +pub const __NR_semop: usize = 193; +pub const __NR_semtimedop: usize = 192; +pub const __NR_sendfile: usize = 71; +pub const __NR_sendmmsg: usize = 269; +pub const __NR_sendmsg: usize = 211; +pub const __NR_sendto: usize = 206; +pub const __NR_set_mempolicy: usize = 237; +pub const __NR_set_robust_list: usize = 99; +pub const __NR_set_tid_address: usize = 96; +pub const __NR_setdomainname: usize = 162; +pub const __NR_setfsgid: usize = 152; +pub const __NR_setfsuid: usize = 151; +pub const __NR_setgid: usize = 144; +pub const __NR_setgroups: usize = 159; +pub const __NR_sethostname: usize = 161; +pub const __NR_setitimer: usize = 103; +pub const __NR_setns: usize = 268; +pub const __NR_setpgid: usize = 154; +pub const __NR_setpriority: usize = 140; +pub const __NR_setregid: usize = 143; +pub const __NR_setresgid: usize = 149; +pub const __NR_setresuid: usize = 147; +pub const __NR_setreuid: usize = 145; +pub const __NR_setrlimit: usize = 164; +pub const __NR_setsid: usize = 157; +pub const __NR_setsockopt: usize = 208; +pub const __NR_settimeofday: usize = 170; +pub const __NR_setuid: usize = 146; +pub const __NR_setxattr: usize = 5; +pub const __NR_shmat: usize = 196; +pub const __NR_shmctl: usize = 195; +pub const __NR_shmdt: usize = 197; +pub const __NR_shmget: usize = 194; +pub const __NR_shutdown: usize = 210; +pub const __NR_sigaltstack: usize = 132; +pub const __NR_signalfd4: usize = 74; +pub const __NR_socket: usize = 198; +pub const __NR_socketpair: usize = 199; +pub const __NR_splice: usize = 76; +pub const __NR_statfs: usize = 43; +pub const __NR_statx: usize = 291; +pub const __NR_swapoff: usize = 225; +pub const __NR_swapon: usize = 224; +pub const __NR_symlinkat: usize = 36; +pub const __NR_sync: usize = 81; +pub const __NR_sync_file_range: usize = 84; +pub const __NR_syncfs: usize = 267; +pub const __NR_syscalls: usize = 443; +pub const __NR_sysinfo: usize = 179; +pub const __NR_syslog: usize = 116; +pub const __NR_tee: usize = 77; +pub const __NR_tgkill: usize = 131; +pub const __NR_timer_create: usize = 107; +pub const __NR_timer_delete: usize = 111; +pub const __NR_timer_getoverrun: usize = 109; +pub const __NR_timer_gettime: usize = 108; +pub const __NR_timer_settime: usize = 110; +pub const __NR_timerfd_create: usize = 85; +pub const __NR_timerfd_gettime: usize = 87; +pub const __NR_timerfd_settime: usize = 86; +pub const __NR_times: usize = 153; +pub const __NR_tkill: usize = 130; +pub const __NR_truncate: usize = 45; +pub const __NR_umask: usize = 166; +pub const __NR_umount2: usize = 39; +pub const __NR_uname: usize = 160; +pub const __NR_unlinkat: usize = 35; +pub const __NR_unshare: usize = 97; +pub const __NR_userfaultfd: usize = 282; +pub const __NR_utimensat: usize = 88; +pub const __NR_vhangup: usize = 58; +pub const __NR_vmsplice: usize = 75; +pub const __NR_wait4: usize = 260; +pub const __NR_waitid: usize = 95; +pub const __NR_write: usize = 64; +pub const __NR_writev: usize = 66; diff --git a/src/header/sys_syscall/cbindgen.toml b/src/header/sys_syscall/cbindgen.toml new file mode 100644 index 0000000000..6976288d42 --- /dev/null +++ b/src/header/sys_syscall/cbindgen.toml @@ -0,0 +1,8 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_statvfs.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the fsblkcnt_t and fsfilcnt_t types as described in ." +sys_includes = ["sys/types.h"] +include_guard = "_SYS_SYSCALL_H" +language = "C" +no_includes = true diff --git a/src/header/sys_syscall/mod.rs b/src/header/sys_syscall/mod.rs new file mode 100644 index 0000000000..cb8c1350e7 --- /dev/null +++ b/src/header/sys_syscall/mod.rs @@ -0,0 +1,5 @@ +// copied from sc crate +#[cfg(all(target_os = "linux", target_arch = "aarch64"))] +pub mod aarch64; +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub mod x86_64; diff --git a/src/header/sys_syscall/x86_64.rs b/src/header/sys_syscall/x86_64.rs new file mode 100644 index 0000000000..d5fd94ae8a --- /dev/null +++ b/src/header/sys_syscall/x86_64.rs @@ -0,0 +1,356 @@ +// Adapted from sc crate: https://github.com/japaric/syscall.rs/blob/master/src/platform/linux-x86_64/nr.rs +// const ([A-Z_\d]+): -> const __NR_\L$1: + +pub const __NR_accept: usize = 43; +pub const __NR_accept4: usize = 288; +pub const __NR_access: usize = 21; +pub const __NR_acct: usize = 163; +pub const __NR_add_key: usize = 248; +pub const __NR_adjtimex: usize = 159; +pub const __NR_afs_syscall: usize = 183; +pub const __NR_alarm: usize = 37; +pub const __NR_arch_prctl: usize = 158; +pub const __NR_bind: usize = 49; +pub const __NR_bpf: usize = 321; +pub const __NR_brk: usize = 12; +pub const __NR_capget: usize = 125; +pub const __NR_capset: usize = 126; +pub const __NR_chdir: usize = 80; +pub const __NR_chmod: usize = 90; +pub const __NR_chown: usize = 92; +pub const __NR_chroot: usize = 161; +pub const __NR_clock_adjtime: usize = 305; +pub const __NR_clock_getres: usize = 229; +pub const __NR_clock_gettime: usize = 228; +pub const __NR_clock_nanosleep: usize = 230; +pub const __NR_clock_settime: usize = 227; +pub const __NR_clone: usize = 56; +pub const __NR_clone3: usize = 435; +pub const __NR_close: usize = 3; +pub const __NR_close_range: usize = 436; +pub const __NR_connect: usize = 42; +pub const __NR_copy_file_range: usize = 326; +pub const __NR_creat: usize = 85; +pub const __NR_create_module: usize = 174; +pub const __NR_delete_module: usize = 176; +pub const __NR_dup: usize = 32; +pub const __NR_dup2: usize = 33; +pub const __NR_dup3: usize = 292; +pub const __NR_epoll_create: usize = 213; +pub const __NR_epoll_create1: usize = 291; +pub const __NR_epoll_ctl: usize = 233; +pub const __NR_epoll_ctl_old: usize = 214; +pub const __NR_epoll_pwait: usize = 281; +pub const __NR_epoll_pwait2: usize = 441; +pub const __NR_epoll_wait: usize = 232; +pub const __NR_epoll_wait_old: usize = 215; +pub const __NR_eventfd: usize = 284; +pub const __NR_eventfd2: usize = 290; +pub const __NR_execve: usize = 59; +pub const __NR_execveat: usize = 322; +pub const __NR_exit: usize = 60; +pub const __NR_exit_group: usize = 231; +pub const __NR_faccessat: usize = 269; +pub const __NR_faccessat2: usize = 439; +pub const __NR_fadvise64: usize = 221; +pub const __NR_fallocate: usize = 285; +pub const __NR_fanotify_init: usize = 300; +pub const __NR_fanotify_mark: usize = 301; +pub const __NR_fchdir: usize = 81; +pub const __NR_fchmod: usize = 91; +pub const __NR_fchmodat: usize = 268; +pub const __NR_fchown: usize = 93; +pub const __NR_fchownat: usize = 260; +pub const __NR_fcntl: usize = 72; +pub const __NR_fdatasync: usize = 75; +pub const __NR_fgetxattr: usize = 193; +pub const __NR_finit_module: usize = 313; +pub const __NR_flistxattr: usize = 196; +pub const __NR_flock: usize = 73; +pub const __NR_fork: usize = 57; +pub const __NR_fremovexattr: usize = 199; +pub const __NR_fsconfig: usize = 431; +pub const __NR_fsetxattr: usize = 190; +pub const __NR_fsmount: usize = 432; +pub const __NR_fsopen: usize = 430; +pub const __NR_fspick: usize = 433; +pub const __NR_fstat: usize = 5; +pub const __NR_fstatfs: usize = 138; +pub const __NR_fsync: usize = 74; +pub const __NR_ftruncate: usize = 77; +pub const __NR_futex: usize = 202; +pub const __NR_futimesat: usize = 261; +pub const __NR_get_kernel_syms: usize = 177; +pub const __NR_get_mempolicy: usize = 239; +pub const __NR_get_robust_list: usize = 274; +pub const __NR_get_thread_area: usize = 211; +pub const __NR_getcpu: usize = 309; +pub const __NR_getcwd: usize = 79; +pub const __NR_getdents: usize = 78; +pub const __NR_getdents64: usize = 217; +pub const __NR_getegid: usize = 108; +pub const __NR_geteuid: usize = 107; +pub const __NR_getgid: usize = 104; +pub const __NR_getgroups: usize = 115; +pub const __NR_getitimer: usize = 36; +pub const __NR_getpeername: usize = 52; +pub const __NR_getpgid: usize = 121; +pub const __NR_getpgrp: usize = 111; +pub const __NR_getpid: usize = 39; +pub const __NR_getpmsg: usize = 181; +pub const __NR_getppid: usize = 110; +pub const __NR_getpriority: usize = 140; +pub const __NR_getrandom: usize = 318; +pub const __NR_getresgid: usize = 120; +pub const __NR_getresuid: usize = 118; +pub const __NR_getrlimit: usize = 97; +pub const __NR_getrusage: usize = 98; +pub const __NR_getsid: usize = 124; +pub const __NR_getsockname: usize = 51; +pub const __NR_getsockopt: usize = 55; +pub const __NR_gettid: usize = 186; +pub const __NR_gettimeofday: usize = 96; +pub const __NR_getuid: usize = 102; +pub const __NR_getxattr: usize = 191; +pub const __NR_init_module: usize = 175; +pub const __NR_inotify_add_watch: usize = 254; +pub const __NR_inotify_init: usize = 253; +pub const __NR_inotify_init1: usize = 294; +pub const __NR_inotify_rm_watch: usize = 255; +pub const __NR_io_cancel: usize = 210; +pub const __NR_io_destroy: usize = 207; +pub const __NR_io_getevents: usize = 208; +pub const __NR_io_pgetevents: usize = 333; +pub const __NR_io_setup: usize = 206; +pub const __NR_io_submit: usize = 209; +pub const __NR_io_uring_enter: usize = 426; +pub const __NR_io_uring_register: usize = 427; +pub const __NR_io_uring_setup: usize = 425; +pub const __NR_ioctl: usize = 16; +pub const __NR_ioperm: usize = 173; +pub const __NR_iopl: usize = 172; +pub const __NR_ioprio_get: usize = 252; +pub const __NR_ioprio_set: usize = 251; +pub const __NR_kcmp: usize = 312; +pub const __NR_kexec_file_load: usize = 320; +pub const __NR_kexec_load: usize = 246; +pub const __NR_keyctl: usize = 250; +pub const __NR_kill: usize = 62; +pub const __NR_lchown: usize = 94; +pub const __NR_lgetxattr: usize = 192; +pub const __NR_link: usize = 86; +pub const __NR_linkat: usize = 265; +pub const __NR_listen: usize = 50; +pub const __NR_listxattr: usize = 194; +pub const __NR_llistxattr: usize = 195; +pub const __NR_lookup_dcookie: usize = 212; +pub const __NR_lremovexattr: usize = 198; +pub const __NR_lseek: usize = 8; +pub const __NR_lsetxattr: usize = 189; +pub const __NR_lstat: usize = 6; +pub const __NR_madvise: usize = 28; +pub const __NR_mbind: usize = 237; +pub const __NR_membarrier: usize = 324; +pub const __NR_memfd_create: usize = 319; +pub const __NR_migrate_pages: usize = 256; +pub const __NR_mincore: usize = 27; +pub const __NR_mkdir: usize = 83; +pub const __NR_mkdirat: usize = 258; +pub const __NR_mknod: usize = 133; +pub const __NR_mknodat: usize = 259; +pub const __NR_mlock: usize = 149; +pub const __NR_mlock2: usize = 325; +pub const __NR_mlockall: usize = 151; +pub const __NR_mmap: usize = 9; +pub const __NR_modify_ldt: usize = 154; +pub const __NR_mount: usize = 165; +pub const __NR_mount_setattr: usize = 442; +pub const __NR_move_mount: usize = 429; +pub const __NR_move_pages: usize = 279; +pub const __NR_mprotect: usize = 10; +pub const __NR_mq_getsetattr: usize = 245; +pub const __NR_mq_notify: usize = 244; +pub const __NR_mq_open: usize = 240; +pub const __NR_mq_timedreceive: usize = 243; +pub const __NR_mq_timedsend: usize = 242; +pub const __NR_mq_unlink: usize = 241; +pub const __NR_mremap: usize = 25; +pub const __NR_msgctl: usize = 71; +pub const __NR_msgget: usize = 68; +pub const __NR_msgrcv: usize = 70; +pub const __NR_msgsnd: usize = 69; +pub const __NR_msync: usize = 26; +pub const __NR_munlock: usize = 150; +pub const __NR_munlockall: usize = 152; +pub const __NR_munmap: usize = 11; +pub const __NR_name_to_handle_at: usize = 303; +pub const __NR_nanosleep: usize = 35; +pub const __NR_newfstatat: usize = 262; +pub const __NR_nfsservctl: usize = 180; +pub const __NR_open: usize = 2; +pub const __NR_open_by_handle_at: usize = 304; +pub const __NR_open_tree: usize = 428; +pub const __NR_openat: usize = 257; +pub const __NR_openat2: usize = 437; +pub const __NR_pause: usize = 34; +pub const __NR_perf_event_open: usize = 298; +pub const __NR_personality: usize = 135; +pub const __NR_pidfd_getfd: usize = 438; +pub const __NR_pidfd_open: usize = 434; +pub const __NR_pidfd_send_signal: usize = 424; +pub const __NR_pipe: usize = 22; +pub const __NR_pipe2: usize = 293; +pub const __NR_pivot_root: usize = 155; +pub const __NR_pkey_alloc: usize = 330; +pub const __NR_pkey_free: usize = 331; +pub const __NR_pkey_mprotect: usize = 329; +pub const __NR_poll: usize = 7; +pub const __NR_ppoll: usize = 271; +pub const __NR_prctl: usize = 157; +pub const __NR_pread64: usize = 17; +pub const __NR_preadv: usize = 295; +pub const __NR_preadv2: usize = 327; +pub const __NR_prlimit64: usize = 302; +pub const __NR_process_madvise: usize = 440; +pub const __NR_process_vm_readv: usize = 310; +pub const __NR_process_vm_writev: usize = 311; +pub const __NR_pselect6: usize = 270; +pub const __NR_ptrace: usize = 101; +pub const __NR_putpmsg: usize = 182; +pub const __NR_pwrite64: usize = 18; +pub const __NR_pwritev: usize = 296; +pub const __NR_pwritev2: usize = 328; +pub const __NR_query_module: usize = 178; +pub const __NR_quotactl: usize = 179; +pub const __NR_read: usize = 0; +pub const __NR_readahead: usize = 187; +pub const __NR_readlink: usize = 89; +pub const __NR_readlinkat: usize = 267; +pub const __NR_readv: usize = 19; +pub const __NR_reboot: usize = 169; +pub const __NR_recvfrom: usize = 45; +pub const __NR_recvmmsg: usize = 299; +pub const __NR_recvmsg: usize = 47; +pub const __NR_remap_file_pages: usize = 216; +pub const __NR_removexattr: usize = 197; +pub const __NR_rename: usize = 82; +pub const __NR_renameat: usize = 264; +pub const __NR_renameat2: usize = 316; +pub const __NR_request_key: usize = 249; +pub const __NR_restart_syscall: usize = 219; +pub const __NR_rmdir: usize = 84; +pub const __NR_rseq: usize = 334; +pub const __NR_rt_sigaction: usize = 13; +pub const __NR_rt_sigpending: usize = 127; +pub const __NR_rt_sigprocmask: usize = 14; +pub const __NR_rt_sigqueueinfo: usize = 129; +pub const __NR_rt_sigreturn: usize = 15; +pub const __NR_rt_sigsuspend: usize = 130; +pub const __NR_rt_sigtimedwait: usize = 128; +pub const __NR_rt_tgsigqueueinfo: usize = 297; +pub const __NR_sched_get_priority_max: usize = 146; +pub const __NR_sched_get_priority_min: usize = 147; +pub const __NR_sched_getaffinity: usize = 204; +pub const __NR_sched_getattr: usize = 315; +pub const __NR_sched_getparam: usize = 143; +pub const __NR_sched_getscheduler: usize = 145; +pub const __NR_sched_rr_get_interval: usize = 148; +pub const __NR_sched_setaffinity: usize = 203; +pub const __NR_sched_setattr: usize = 314; +pub const __NR_sched_setparam: usize = 142; +pub const __NR_sched_setscheduler: usize = 144; +pub const __NR_sched_yield: usize = 24; +pub const __NR_seccomp: usize = 317; +pub const __NR_security: usize = 185; +pub const __NR_select: usize = 23; +pub const __NR_semctl: usize = 66; +pub const __NR_semget: usize = 64; +pub const __NR_semop: usize = 65; +pub const __NR_semtimedop: usize = 220; +pub const __NR_sendfile: usize = 40; +pub const __NR_sendmmsg: usize = 307; +pub const __NR_sendmsg: usize = 46; +pub const __NR_sendto: usize = 44; +pub const __NR_set_mempolicy: usize = 238; +pub const __NR_set_robust_list: usize = 273; +pub const __NR_set_thread_area: usize = 205; +pub const __NR_set_tid_address: usize = 218; +pub const __NR_setdomainname: usize = 171; +pub const __NR_setfsgid: usize = 123; +pub const __NR_setfsuid: usize = 122; +pub const __NR_setgid: usize = 106; +pub const __NR_setgroups: usize = 116; +pub const __NR_sethostname: usize = 170; +pub const __NR_setitimer: usize = 38; +pub const __NR_setns: usize = 308; +pub const __NR_setpgid: usize = 109; +pub const __NR_setpriority: usize = 141; +pub const __NR_setregid: usize = 114; +pub const __NR_setresgid: usize = 119; +pub const __NR_setresuid: usize = 117; +pub const __NR_setreuid: usize = 113; +pub const __NR_setrlimit: usize = 160; +pub const __NR_setsid: usize = 112; +pub const __NR_setsockopt: usize = 54; +pub const __NR_settimeofday: usize = 164; +pub const __NR_setuid: usize = 105; +pub const __NR_setxattr: usize = 188; +pub const __NR_shmat: usize = 30; +pub const __NR_shmctl: usize = 31; +pub const __NR_shmdt: usize = 67; +pub const __NR_shmget: usize = 29; +pub const __NR_shutdown: usize = 48; +pub const __NR_sigaltstack: usize = 131; +pub const __NR_signalfd: usize = 282; +pub const __NR_signalfd4: usize = 289; +pub const __NR_socket: usize = 41; +pub const __NR_socketpair: usize = 53; +pub const __NR_splice: usize = 275; +pub const __NR_stat: usize = 4; +pub const __NR_statfs: usize = 137; +pub const __NR_statx: usize = 332; +pub const __NR_swapoff: usize = 168; +pub const __NR_swapon: usize = 167; +pub const __NR_symlink: usize = 88; +pub const __NR_symlinkat: usize = 266; +pub const __NR_sync: usize = 162; +pub const __NR_sync_file_range: usize = 277; +pub const __NR_syncfs: usize = 306; +pub const __NR_sysfs: usize = 139; +pub const __NR_sysinfo: usize = 99; +pub const __NR_syslog: usize = 103; +pub const __NR_tee: usize = 276; +pub const __NR_tgkill: usize = 234; +pub const __NR_time: usize = 201; +pub const __NR_timer_create: usize = 222; +pub const __NR_timer_delete: usize = 226; +pub const __NR_timer_getoverrun: usize = 225; +pub const __NR_timer_gettime: usize = 224; +pub const __NR_timer_settime: usize = 223; +pub const __NR_timerfd_create: usize = 283; +pub const __NR_timerfd_gettime: usize = 287; +pub const __NR_timerfd_settime: usize = 286; +pub const __NR_times: usize = 100; +pub const __NR_tkill: usize = 200; +pub const __NR_truncate: usize = 76; +pub const __NR_tuxcall: usize = 184; +pub const __NR_umask: usize = 95; +pub const __NR_umount2: usize = 166; +pub const __NR_uname: usize = 63; +pub const __NR_unlink: usize = 87; +pub const __NR_unlinkat: usize = 263; +pub const __NR_unshare: usize = 272; +pub const __NR_uselib: usize = 134; +pub const __NR_userfaultfd: usize = 323; +pub const __NR_ustat: usize = 136; +pub const __NR_utime: usize = 132; +pub const __NR_utimensat: usize = 280; +pub const __NR_utimes: usize = 235; +pub const __NR_vfork: usize = 58; +pub const __NR_vhangup: usize = 153; +pub const __NR_vmsplice: usize = 278; +pub const __NR_vserver: usize = 236; +pub const __NR_wait4: usize = 61; +pub const __NR_waitid: usize = 247; +pub const __NR_write: usize = 1; +pub const __NR_writev: usize = 20; diff --git a/src/header/sys_syslog/cbindgen.toml b/src/header/sys_syslog/cbindgen.toml new file mode 100644 index 0000000000..9f62d1a556 --- /dev/null +++ b/src/header/sys_syslog/cbindgen.toml @@ -0,0 +1,11 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/syslog.h.html +# +# There are no spec quotations relating to includes +include_guard = "_RELIBC_SYS_SYSLOG_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/sys_syslog/linux.rs b/src/header/sys_syslog/linux.rs new file mode 100644 index 0000000000..6c4731d2c2 --- /dev/null +++ b/src/header/sys_syslog/linux.rs @@ -0,0 +1,74 @@ +use crate::{ + error::{Errno, Result}, + fs::File, + header::{ + sys_socket::{ + connect, + constants::{AF_UNIX, SOCK_CLOEXEC, SOCK_DGRAM}, + sockaddr, socket, + }, + sys_un::sockaddr_un, + unistd::close, + }, + io::BufWriter, + platform::ERRNO, +}; + +use super::logger::LogSink; + +/// Unix Domain Socket connected to /dev/log. +pub struct LogFile(BufWriter); + +impl LogSink for LogFile { + type Sink = BufWriter; + + fn open() -> Result + where + Self: Sized, + { + let log_addr = { + let path = c"/dev/log"; + let mut sockaddr: sockaddr_un = unsafe { core::mem::zeroed() }; + sockaddr.sun_family = AF_UNIX as _; + unsafe { + core::ptr::copy_nonoverlapping( + path.as_ptr(), + sockaddr.sun_path.as_mut_ptr(), + path.count_bytes(), + ) + }; + path + }; + let log_fd = { + let result = unsafe { socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0) }; + if result > 0 { + Ok(result) + } else { + Err(Errno(ERRNO.get())) + } + }?; + + // SAFETY: + // * connect handles invalid descriptors. + // * log_addr is a sockaddr_un so the size is correct. + if unsafe { + connect( + log_fd, + (&raw const log_addr).cast::(), + size_of::() as u32, + ) < 0 + } { + // In case close sets ERRNO. + let e = ERRNO.get(); + close(log_fd); + return Err(Errno(e)); + } + + Ok(Self(BufWriter::new(File::new(log_fd)))) + } + + #[inline(always)] + fn writer(&mut self) -> &mut Self::Sink { + &mut self.0 + } +} diff --git a/src/header/sys_syslog/logger.rs b/src/header/sys_syslog/logger.rs new file mode 100644 index 0000000000..9a9f09c0be --- /dev/null +++ b/src/header/sys_syslog/logger.rs @@ -0,0 +1,274 @@ +use alloc::{borrow::ToOwned, string::String}; +use core::{ffi::VaList, ptr::null_mut}; + +use crate::{ + c_str::CStr, + error::Result, + header::{ + stdio::{fprintf, printf::printf, stderr}, + time::time, + unistd::getpid, + }, + io::Write, + platform::{ + self, + types::{c_char, c_int}, + }, + sync::Mutex, +}; + +use bitflags::bitflags; +use chrono::{DateTime, Utc}; + +use super::{ + LOG_ALERT, LOG_AUTH, LOG_AUTHPRIV, LOG_CONS, LOG_CRIT, LOG_CRON, LOG_DAEMON, LOG_DEBUG, + LOG_EMERG, LOG_ERR, LOG_FTP, LOG_INFO, LOG_KERN, LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, + LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, LOG_LPR, LOG_MAIL, LOG_MASK, + LOG_NDELAY, LOG_NEWS, LOG_NOTICE, LOG_NOWAIT, LOG_ODELAY, LOG_PERROR, LOG_PID, LOG_SYSLOG, + LOG_UPTO, LOG_USER, LOG_UUCP, LOG_WARNING, sys::LogFile, +}; + +pub(super) static LOGGER: Mutex> = Mutex::new(LogParams::new(None)); + +pub(super) struct LogParams { + /// Identity prepended to each log message. POSIX does not specific what to do when it's empty, + /// but the program name is a common default. + ident: String, + pub opt: Config, + pub mask: Priority, + writer: Option, +} + +impl LogParams { + pub const fn new(writer: Option) -> Self { + LogParams { + ident: String::new(), + opt: Config::DelayOpen, + mask: Priority::from_bits_truncate(Priority::User.bits() | Priority::UpToDebug.bits()), + writer, + } + } + + pub fn write_log(&mut self, priority: Priority, message: CStr<'_>, ap: VaList) { + if message.is_empty() { + return; + } + if self.ident.is_empty() { + self.set_identity(None); + } + + let epoch = unsafe { time(null_mut()) }; + let currtime: DateTime = DateTime::from_timestamp(epoch, 0).unwrap_or_default(); + let currtime_s = currtime.format("%b %e %T %Y"); + let pid = self.opt.contains(Config::Pid).then(|| getpid()); + + // journald from systemd rewrites log messages from syslog into its own style. We'll + // still use the same style as other libc even though it's implementation specific. + let mut buffer = if let Some(pid) = pid { + format!( + "<{}>{} {}{}: ", + priority.bits(), + currtime_s, + self.ident, + pid + ) + .into_bytes() + } else { + format!("<{}>{} {}: ", priority.bits(), currtime_s, self.ident).into_bytes() + }; + let prefix = buffer.len(); + + // SAFETY: + // * Assumes caller passed in a valid C string; printf should handle that invariant. + // * `buffer` grows to fit the formatted string. + unsafe { printf(&mut buffer, message, ap) }; + buffer.extend(b"\n\0"); + + if self.maybe_open_logger().is_ok() + && self + .writer + .as_mut() + .map(|w| w.writer().write_all(&buffer).is_err()) + .unwrap_or(true) + { + // Try reopening the log file once and retrying as musl does. + if !self + .open_logger() + .is_ok() + .then(|| { + self.writer + .as_mut() + .map(|w| w.writer().write_all(&buffer).is_ok()) + .unwrap_or_default() + }) + .unwrap_or_default() + && self.opt.contains(Config::Console) + { + // TODO: Log error to /dev/console & Redox equivalent + } + } + if self.opt.contains(Config::PError) { + // SAFETY: + // * `ident` is a valid byte string that is NUL terminated when set. + // * `buffer` is a valid byte string that is NUL terminated above. + unsafe { + // musl and glibc only print the message rather than the prefix + message to stderr + fprintf( + stderr, + c"%s: %s".as_ptr(), + self.ident.as_ptr().cast::(), + buffer[prefix..].as_ptr().cast::(), + ); + } + } + } + + /// Set or clear log identity from a C string. + /// + /// Null or empty identities are valid as it just resets the global ident. + pub fn set_identity_cstr(&mut self, ident: Option>) { + let ident = ident + .and_then(|ident| (!ident.is_empty()).then(|| ident.to_str().ok())) + .flatten(); + self.set_identity(ident); + } + + /// Set or clear log identity. + /// + /// The log identity is prepended to each message. If unset, the program name will be used as a + /// default. + pub fn set_identity(&mut self, ident: Option<&str>) { + self.ident = ident + .and_then(|ident| { + let ident = ident.bytes().chain([0]).collect(); + // SAFETY: Already validated + Some(unsafe { String::from_utf8_unchecked(ident) }) + }) + .unwrap_or_else(|| { + unsafe { CStr::from_nullable_ptr(platform::program_invocation_short_name) } + .and_then(|name| { + let name = name.to_str().ok()?.bytes().chain([0]).collect(); + // SAFETY: Validated above + Some(unsafe { String::from_utf8_unchecked(name) }) + }) + .unwrap_or_else(|| "\0".to_owned()) + }); + } + + /// Open the internal [`LogFile`] if it's not open. + pub fn maybe_open_logger(&mut self) -> Result<()> { + if self.writer.is_none() { + self.open_logger() + } else { + Ok(()) + } + } + + /// Open or reopen the internal [`LogFile`]. + pub fn open_logger(&mut self) -> Result<()> { + L::open().map(|file| { + self.writer.replace(file); + }) + } + + /// Close the open writer to the system logger (optional). + pub fn close(&mut self) { + self.writer.take(); + } +} + +/// Operating system specific log handling. +pub(super) trait LogSink { + type Sink: Write; + + fn open() -> Result + where + Self: Sized; + + fn writer(&mut self) -> &mut Self::Sink; +} + +bitflags! { + #[derive(Clone, Copy)] + pub struct Config: c_int { + const Pid = LOG_PID; + const Console = LOG_CONS; + const DelayOpen = LOG_ODELAY; + const NoDelay = LOG_NDELAY; + const NoWait = LOG_NOWAIT; + const PError = LOG_PERROR; + } +} + +bitflags! { + /// Packed Facility-Priority bit field. + #[derive(Clone, Copy)] + pub struct Priority: c_int { + const Emerg = LOG_EMERG; + const Alert = LOG_ALERT; + const Crit = LOG_CRIT; + const Err = LOG_ERR; + const Warn = LOG_WARNING; + const Notice = LOG_NOTICE; + const Info = LOG_INFO; + const Debug = LOG_DEBUG; + + const UpToEmerg = LOG_UPTO(LOG_EMERG); + const UpToAlert = LOG_UPTO(LOG_ALERT); + const UpToCrit = LOG_UPTO(LOG_CRIT); + const UpToErr = LOG_UPTO(LOG_ERR); + const UpToWarn = LOG_UPTO(LOG_WARNING); + const UpToNotice = LOG_UPTO(LOG_NOTICE); + const UpToInfo = LOG_UPTO(LOG_INFO); + const UpToDebug = LOG_UPTO(LOG_DEBUG); + + const Kern = LOG_KERN; + const User = LOG_USER; + const Mail = LOG_MAIL; + const Daemon = LOG_DAEMON; + const Auth = LOG_AUTH; + const Syslog = LOG_SYSLOG; + const Printer = LOG_LPR; + const News = LOG_NEWS; + const UUCP = LOG_UUCP; + const CRON = LOG_CRON; + const AuthPriv = LOG_AUTHPRIV; + const FTP = LOG_FTP; + const Local0 = LOG_LOCAL0; + const Local1 = LOG_LOCAL1; + const Local2 = LOG_LOCAL2; + const Local3 = LOG_LOCAL3; + const Local4 = LOG_LOCAL4; + const Local5 = LOG_LOCAL5; + const Local6 = LOG_LOCAL6; + const Local7 = LOG_LOCAL7; + + // Internal constants for extracting facility or priority from a packed bitfield. + const FacilityMask = 0x3ff; + const PriorityMask = !Self::FacilityMask.bits(); + } +} + +impl Priority { + /// Keep facility but replace severity mask. + pub fn with_mask(self, mask: c_int) -> Option { + // Fail on invalid bits and drop invalid facility bits. + let mask = Self::from_bits(mask)? & Self::FacilityMask; + let facility = self & Self::PriorityMask; + Some(mask | facility) + } + + /// Keep mask but replace facility. + pub fn with_facility(self, facility: c_int) -> Option { + // Fail on invalid bits and drop invalid priority bits. + let facility = Self::from_bits(facility)? & Self::PriorityMask; + let mask = self & Self::FacilityMask; + Some(mask | facility) + } + + /// Returns if a message of `priority` should be retained by this mask. + pub fn should_log(self, priority: Self) -> bool { + (self & Self::FacilityMask) + .contains(Priority::from_bits_truncate(LOG_MASK(priority.bits()))) + } +} diff --git a/src/header/sys_syslog/mod.rs b/src/header/sys_syslog/mod.rs new file mode 100644 index 0000000000..fd149ecd91 --- /dev/null +++ b/src/header/sys_syslog/mod.rs @@ -0,0 +1,146 @@ +//! `syslog.h` implementation. +//! +//! See . + +// Exported as both syslog.h and sys/syslog.h. + +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +pub mod sys; + +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +pub mod sys; + +pub mod logger; + +use core::ffi::VaList; + +use crate::{ + c_str::CStr, + platform::types::{c_char, c_int}, +}; +use logger::{LOGGER, Priority}; + +/// Record the caller's PID in log messages. +pub const LOG_PID: c_int = 0x01; +/// Write to /dev/console if [`syslog`] fails. +pub const LOG_CONS: c_int = 0x02; +/// Open the log on the first call to [`syslog`] rather than opening it early. +/// This is the default behavior and setting or unsetting this option does nothing. +pub const LOG_ODELAY: c_int = 0x04; +/// Open the log file immediately. +pub const LOG_NDELAY: c_int = 0x08; +pub const LOG_NOWAIT: c_int = 0x10; +/// Print log message to stderr as well as the log. +pub const LOG_PERROR: c_int = 0x20; + +pub const LOG_KERN: c_int = 0 << 3; +pub const LOG_USER: c_int = 1 << 3; +pub const LOG_MAIL: c_int = 2 << 3; +pub const LOG_DAEMON: c_int = 3 << 3; +pub const LOG_AUTH: c_int = 4 << 3; +pub const LOG_SYSLOG: c_int = 5 << 3; +pub const LOG_LPR: c_int = 6 << 3; +pub const LOG_NEWS: c_int = 7 << 3; +pub const LOG_UUCP: c_int = 8 << 3; +pub const LOG_CRON: c_int = 9 << 3; +pub const LOG_AUTHPRIV: c_int = 10 << 3; +pub const LOG_FTP: c_int = 11 << 3; + +pub const LOG_LOCAL0: c_int = 16 << 3; +pub const LOG_LOCAL1: c_int = 17 << 3; +pub const LOG_LOCAL2: c_int = 18 << 3; +pub const LOG_LOCAL3: c_int = 19 << 3; +pub const LOG_LOCAL4: c_int = 20 << 3; +pub const LOG_LOCAL5: c_int = 21 << 3; +pub const LOG_LOCAL6: c_int = 22 << 3; +pub const LOG_LOCAL7: c_int = 23 << 3; +pub const LOG_NFACILITIES: c_int = 24; + +// Priorities +pub const LOG_EMERG: c_int = 0; +pub const LOG_ALERT: c_int = 1; +pub const LOG_CRIT: c_int = 2; +pub const LOG_ERR: c_int = 3; +pub const LOG_WARNING: c_int = 4; +pub const LOG_NOTICE: c_int = 5; +pub const LOG_INFO: c_int = 6; +pub const LOG_DEBUG: c_int = 7; + +/// Create a mask that includes all levels up to a certain priority. +#[unsafe(no_mangle)] +pub const extern "C" fn LOG_UPTO(p: c_int) -> c_int { + (1 << (p + 1)) - 1 +} + +/// Create a mask that enables a single priority. +#[unsafe(no_mangle)] +pub const extern "C" fn LOG_MASK(p: c_int) -> c_int { + 1 << p +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn setlogmask(mask: c_int) -> c_int { + let mut params = LOGGER.lock(); + let old = params.mask.bits(); + if mask != 0 + && let Some(mask) = params.mask.with_mask(mask) + { + params.mask = mask; + } + old +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openlog(ident: *const c_char, opt: c_int, facility: c_int) { + let ident = unsafe { CStr::from_nullable_ptr(ident) }; + let conf = logger::Config::from_bits_truncate(opt); + + let mut params = LOGGER.lock(); + params.set_identity_cstr(ident); + params.opt = conf; + params.mask = params.mask.with_facility(facility).unwrap_or( + params + .mask + .with_facility(Priority::User.bits()) + .expect("`User` is a valid syslog facility"), + ); + + // Ensure log is ready to write now instead of checking on the first message. + if conf.contains(logger::Config::NoDelay) + && let Ok(()) = params.open_logger() + {}; // TODO handle error +} + +/// See . +/// +/// Non-POSIX, 4.3BSD-Reno. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vsyslog(priority: c_int, message: *const c_char, ap: VaList) { + let Some(message) = (unsafe { CStr::from_nullable_ptr(message) }) else { + return; + }; + let Some(priority) = Priority::from_bits(priority) else { + return; + }; + + let mut logger = LOGGER.lock(); + if logger.mask.should_log(priority) { + logger.write_log(priority, message, ap); + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syslog(priority: c_int, message: *const c_char, mut __valist: ...) { + unsafe { vsyslog(priority, message, __valist) }; +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn closelog() { + LOGGER.lock().close(); +} diff --git a/src/header/sys_syslog/redox.rs b/src/header/sys_syslog/redox.rs new file mode 100644 index 0000000000..c396224e6a --- /dev/null +++ b/src/header/sys_syslog/redox.rs @@ -0,0 +1,20 @@ +use crate::{error::Result, fs::File, header::fcntl, io::BufWriter}; + +use super::logger::LogSink; + +/// Write logs to Redox's log scheme. +pub struct LogFile(BufWriter); + +impl LogSink for LogFile { + type Sink = BufWriter; + + #[inline(always)] + fn open() -> Result { + File::open(c"/scheme/log".into(), fcntl::O_WRONLY).map(|file| Self(BufWriter::new(file))) + } + + #[inline(always)] + fn writer(&mut self) -> &mut Self::Sink { + &mut self.0 + } +} diff --git a/src/header/sys_time/cbindgen.toml b/src/header/sys_time/cbindgen.toml index 535c6a246b..9e28d70f11 100644 --- a/src/header/sys_time/cbindgen.toml +++ b/src/header/sys_time/cbindgen.toml @@ -1,10 +1,36 @@ -sys_includes = ["sys/types.h"] +# sys/types.h brings in features.h (needed for #[deprecated] annotations) +sys_includes = ["sys/types.h", "sys/select.h"] include_guard = "_SYS_TIME_H" language = "C" -trailer = "#include " +after_includes = """ +#include // for timespec + +#define timeradd(x,y,res) (void) (\ + (res)->tv_sec = (x)->tv_sec + (y)->tv_sec + (((x)->tv_usec + (y)->tv_usec) / 1000000), \ + (res)->tv_usec = ((x)->tv_usec + (y)->tv_usec) % 1000000 \ + ) +#define timersub(x,y,res) (void) ( \ + (res)->tv_sec = (x)->tv_sec - (y)->tv_sec, \ + (res)->tv_usec = ((x)->tv_usec - (y)->tv_usec), \ + ((res)->tv_usec < 0) && ((res)->tv_sec -= 1, (res)->tv_usec += 1000000) \ + ) +#define timerclear(t) (void) ( \ + (t)->tv_sec = 0, \ + (t)->tv_usec = 0 \ + ) +#define timerisset(t) ((t)->tv_sec || (t)->tv_usec) +#define timercmp(x,y,op) ((x)->tv_sec == (y)->tv_sec ? \ + (x)->tv_usec op (y)->tv_usec \ + : \ + (x)->tv_sec op (y)->tv_sec) +""" style = "Tag" no_includes = true cpp_compat = true [enum] prefix_with_name = true + +[export.rename] +"timespec" = "struct timespec" +"timeval" = "struct timeval" diff --git a/src/header/sys_time/mod.rs b/src/header/sys_time/mod.rs index 467fbf2e5e..43b27324a9 100644 --- a/src/header/sys_time/mod.rs +++ b/src/header/sys_time/mod.rs @@ -1,28 +1,53 @@ -//! sys/time implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/systime.h.html +//! `sys/time.h` implementation. +//! +//! See . +//! +//! Note that since the Open Group Base Specifications Issue 8, the +//! [`timeval`] struct has been moved to +//! the [`sys/select.h`](crate::header::sys_select) header. use crate::{ c_str::CStr, - header::time::timespec, - platform::{types::*, Pal, PalSignal, Sys}, + error::ResultExt, + header::{bits_timespec::timespec, sys_select::timeval}, + out::Out, + platform::{ + Pal, PalSignal, Sys, + types::{c_char, c_int, c_long}, + }, }; +use core::ptr::null; +/// See . +/// +/// # Deprecation +/// The `ITIMER_REAL` symbolic constant was marked obsolescent in the Open +/// Group Base Specifications Issue 7, and removed in Issue 8. +#[deprecated] pub const ITIMER_REAL: c_int = 0; + +/// See . +/// +/// # Deprecation +/// The `ITIMER_VIRTUAL` symbolic constant was marked obsolescent in the Open +/// Group Base Specifications Issue 7, and removed in Issue 8. +#[deprecated] pub const ITIMER_VIRTUAL: c_int = 1; + +/// See . +/// +/// # Deprecation +/// The `ITIMER_PROF` symbolic constant was marked obsolescent in the Open +/// Group Base Specifications Issue 7, and removed in Issue 8. +#[deprecated] pub const ITIMER_PROF: c_int = 2; -#[repr(C)] -#[derive(Default)] -pub struct timeval { - pub tv_sec: time_t, - pub tv_usec: suseconds_t, -} -#[repr(C)] -#[derive(Default)] -pub struct timezone { - pub tz_minuteswest: c_int, - pub tz_dsttime: c_int, -} - +/// See . +/// +/// # Deprecation +/// The `itimerval` struct was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] #[repr(C)] #[derive(Default)] pub struct itimerval { @@ -30,42 +55,91 @@ pub struct itimerval { pub it_value: timeval, } +/// Non-POSIX, see . #[repr(C)] -pub struct fd_set { - pub fds_bits: [c_long; 16usize], +#[derive(Default)] +pub struct timezone { + pub tz_minuteswest: c_int, + pub tz_dsttime: c_int, } -#[no_mangle] -pub extern "C" fn getitimer(which: c_int, value: *mut itimerval) -> c_int { - Sys::getitimer(which, value) +/// See . +/// +/// # Deprecation +/// The `getitimer()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getitimer(which: c_int, value: *mut itimerval) -> c_int { + Sys::getitimer(which, unsafe { &mut *value }) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn setitimer( +/// See . +/// +/// See also +/// for further details on the `tzp` argument. +/// +/// # Deprecation +/// The `gettimeofday()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int { + Sys::gettimeofday(unsafe { Out::nonnull(tp) }, unsafe { Out::nullable(tzp) }) + .map(|()| 0) + .or_minus_one_errno() +} + +// `select()` declared in `sys/select.h`, as specified in modern POSIX + +/// See . +/// +/// # Deprecation +/// The `setitimer()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn setitimer( which: c_int, value: *const itimerval, ovalue: *mut itimerval, ) -> c_int { - Sys::setitimer(which, value, ovalue) + // TODO setitimer is unimplemented on Redox + Sys::setitimer(which, unsafe { &*value }, unsafe { ovalue.as_mut() }) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int { - Sys::gettimeofday(tp, tzp) -} - -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `utimes()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and then unmarked in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn utimes(path: *const c_char, times: *const timeval) -> c_int { - let path = CStr::from_ptr(path); - let times_spec = [ - timespec { - tv_sec: (*times.offset(0)).tv_sec, - tv_nsec: ((*times.offset(0)).tv_usec as i64) * 1000, - }, - timespec { - tv_sec: (*times.offset(1)).tv_sec, - tv_nsec: ((*times.offset(1)).tv_usec as i64) * 1000, - }, - ]; - Sys::utimens(path, times_spec.as_ptr()) + let path = unsafe { CStr::from_ptr(path) }; + // Nullptr is valid here, it means "use current time" + let times_spec = if times.is_null() { + null() + } else { + [ + timespec { + tv_sec: unsafe { (*times.offset(0)).tv_sec }, + tv_nsec: c_long::from(unsafe { (*times.offset(0)).tv_usec }) * 1000, + }, + timespec { + tv_sec: unsafe { (*times.offset(1)).tv_sec }, + tv_nsec: c_long::from(unsafe { (*times.offset(1)).tv_usec }) * 1000, + }, + ] + .as_ptr() + }; + unsafe { Sys::utimens(path, times_spec) } + .map(|()| 0) + .or_minus_one_errno() } diff --git a/src/header/sys_timeb/cbindgen.toml b/src/header/sys_timeb/cbindgen.toml index 95e95fd8fb..7da802b683 100644 --- a/src/header/sys_timeb/cbindgen.toml +++ b/src/header/sys_timeb/cbindgen.toml @@ -2,7 +2,7 @@ sys_includes = ["sys/types.h"] include_guard = "_SYS_TIMEB_H" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/sys_timeb/mod.rs b/src/header/sys_timeb/mod.rs index c1e434bcd4..8935f5b474 100644 --- a/src/header/sys_timeb/mod.rs +++ b/src/header/sys_timeb/mod.rs @@ -1,10 +1,23 @@ -//! sys/time implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/systime.h.html +//! `sys/timeb.h` implementation. +//! +//! See . +//! +//! # Deprecation +//! The `ftime()` function was marked as legacy in the Open Group Base +//! Specifications Issue 6, and the entire `sys/timeb.h` header was removed in +//! Issue 7. +use core::ptr::NonNull; + +#[allow(deprecated)] +use crate::header::sys_time::gettimeofday; use crate::{ - header::sys_time::{gettimeofday, timeval, timezone}, - platform::types::*, + header::{sys_select::timeval, sys_time::timezone}, + platform::types::{c_int, c_short, c_ushort, time_t}, }; +/// See . +#[deprecated] #[repr(C)] #[derive(Default)] pub struct timeb { @@ -14,18 +27,38 @@ pub struct timeb { pub dstflag: c_short, } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller must ensure that `tp` is convertible to a `&mut +/// MaybeUninit`. +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ftime(tp: *mut timeb) -> c_int { + // SAFETY: the caller is required to ensure that the pointer is valid. + let tp_maybe_uninit = unsafe { NonNull::new_unchecked(tp).as_uninit_mut() }; + let mut tv = timeval::default(); let mut tz = timezone::default(); - if gettimeofday(&mut tv, &mut tz) < 0 { + + // SAFETY: tv and tz are created above, and thus will coerce to valid + // pointers. + if unsafe { + #[allow(deprecated)] + gettimeofday(&raw mut tv, &raw mut tz) + } < 0 + { return -1; } - (*tp).time = tv.tv_sec; - (*tp).millitm = (tv.tv_usec / 1000) as c_ushort; - (*tp).timezone = tz.tz_minuteswest as c_short; - (*tp).dstflag = tz.tz_dsttime as c_short; + #[allow(deprecated)] + tp_maybe_uninit.write(timeb { + time: tv.tv_sec, + millitm: (tv.tv_usec / 1000) as c_ushort, + timezone: tz.tz_minuteswest as c_short, + dstflag: tz.tz_dsttime as c_short, + }); 0 } diff --git a/src/header/sys_timerfd/cbindgen.toml b/src/header/sys_timerfd/cbindgen.toml new file mode 100644 index 0000000000..3b44695d44 --- /dev/null +++ b/src/header/sys_timerfd/cbindgen.toml @@ -0,0 +1,23 @@ +sys_includes = ["time.h"] +after_includes = """ +#include // for itimerspec +""" +include_guard = "_SYS_TIMERFD_H" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true + +[export] +include = [ + "TFD_CLOEXEC", + "TFD_NONBLOCK", + "TFD_TIMER_ABSTIME", + "TFD_TIMER_CANCEL_ON_SET", +] + +[export.rename] +"itimerspec" = "struct itimerspec" diff --git a/src/header/sys_timerfd/mod.rs b/src/header/sys_timerfd/mod.rs new file mode 100644 index 0000000000..916066fc78 --- /dev/null +++ b/src/header/sys_timerfd/mod.rs @@ -0,0 +1,89 @@ +//! `sys/timerfd.h` implementation. +//! +//! Non-POSIX, see . +use alloc::{collections::BTreeMap, format}; +use core::{mem, slice, sync::atomic::{AtomicBool, Ordering}}; +use crate::{ + c_str::{CStr, CString}, + error::{Errno, ResultExt}, + header::{ + bits_timespec::timespec, + errno::{EBADF, EFAULT, EINVAL, EIO}, + fcntl::{O_CLOEXEC, O_NONBLOCK, O_RDWR}, + }, + out::Out, + platform::{Pal, Sys, types::{c_int, clockid_t}}, + sync::Mutex, +}; +pub use crate::header::time::itimerspec; + +pub const TFD_CLOEXEC: c_int = 0x80000; +pub const TFD_NONBLOCK: c_int = 0x800; +pub const TFD_TIMER_ABSTIME: c_int = 0x1; +pub const TFD_TIMER_CANCEL_ON_SET: c_int = 0x2; + +const NSEC_PER_SEC: i64 = 1_000_000_000; + +struct TimerState { clockid: clockid_t } + +static MAP_INIT: AtomicBool = AtomicBool::new(false); +static TIMERFD_STATE: Mutex>> = Mutex::new(None); + +fn with_map(f: impl FnOnce(&mut BTreeMap) -> R) -> R { + let mut guard = TIMERFD_STATE.lock(); + if !MAP_INIT.load(Ordering::Acquire) { *guard = Some(BTreeMap::new()); MAP_INIT.store(true, Ordering::Release); } + f(guard.as_mut().unwrap()) +} +fn add_timespec(a: ×pec, b: ×pec) -> timespec { + let total_nsec = a.tv_nsec as i64 + b.tv_nsec as i64; + let mut sec = a.tv_sec + b.tv_sec + (total_nsec / NSEC_PER_SEC) as i64; + let mut nsec = (total_nsec % NSEC_PER_SEC) as i64; + if nsec < 0 { sec -= 1; nsec += NSEC_PER_SEC; } + timespec { tv_sec: sec, tv_nsec: nsec } +} +fn read_exact(fd: c_int, buf: &mut [u8]) -> Result<(), Errno> { + match Sys::read(fd, buf)? { n if n == buf.len() => Ok(()), _ => Err(Errno(EIO)) } +} +fn write_exact(fd: c_int, buf: &[u8]) -> Result<(), Errno> { + match Sys::write(fd, buf)? { n if n == buf.len() => Ok(()), _ => Err(Errno(EIO)) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn timerfd_create(clockid: clockid_t, flags: c_int) -> c_int { + if flags & !(TFD_CLOEXEC | TFD_NONBLOCK) != 0 { return Err::(Errno(EINVAL)).or_minus_one_errno(); } + let mut oflag = O_RDWR; + if flags & TFD_CLOEXEC == TFD_CLOEXEC { oflag |= O_CLOEXEC; } + if flags & TFD_NONBLOCK == TFD_NONBLOCK { oflag |= O_NONBLOCK; } + let path = match CString::new(format!("/scheme/time/{clockid}")) { Ok(p) => p, Err(_) => return Err::(Errno(EINVAL)).or_minus_one_errno() }; + let fd = match Sys::open(CStr::borrow(&path), oflag, 0) { Ok(fd) => fd, Err(Errno(e)) => return Err::(Errno(e)).or_minus_one_errno() }; + with_map(|map| { map.insert(fd, TimerState { clockid }); }); + fd +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timerfd_settime(fd: c_int, flags: c_int, new: *const itimerspec, old: *mut itimerspec) -> c_int { + if flags & !(TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET) != 0 { return Err::(Errno(EINVAL)).or_minus_one_errno(); } + if new.is_null() { return Err::(Errno(EFAULT)).or_minus_one_errno(); } + if !old.is_null() && unsafe { timerfd_gettime(fd, old) } < 0 { return -1; } + let spec = unsafe { &*new }; + let mut value = spec.it_value.clone(); + // TFD_TIMER_CANCEL_ON_SET: flag accepted; clock-change notification requires + // kernel infrastructure not yet available. This is a bounded compatibility surface. + if flags & TFD_TIMER_ABSTIME == 0 { + let clockid = with_map(|map| map.get(&fd).map_or(0, |s| s.clockid)); + let mut now = timespec::default(); + if Sys::clock_gettime(clockid, Out::from_mut(&mut now)).is_err() { return Err::(Errno(EBADF)).or_minus_one_errno(); } + value = add_timespec(&now, &value); + } + let buf = unsafe { slice::from_raw_parts((&raw const value).cast::(), mem::size_of::()) }; + write_exact(fd, buf).map(|()| 0).or_minus_one_errno() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timerfd_gettime(fd: c_int, curr: *mut itimerspec) -> c_int { + if curr.is_null() { return Err::(Errno(EFAULT)).or_minus_one_errno(); } + let curr = unsafe { &mut *curr }; + curr.it_interval = timespec::default(); + let buf = unsafe { slice::from_raw_parts_mut((&raw mut curr.it_value).cast::(), mem::size_of::()) }; + read_exact(fd, buf).map(|()| 0).or_minus_one_errno() +} diff --git a/src/header/sys_times/cbindgen.toml b/src/header/sys_times/cbindgen.toml index fe95544def..a07e798828 100644 --- a/src/header/sys_times/cbindgen.toml +++ b/src/header/sys_times/cbindgen.toml @@ -2,7 +2,7 @@ sys_includes = ["sys/types.h"] include_guard = "_SYS_TIMES_H" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/sys_times/mod.rs b/src/header/sys_times/mod.rs index a045bcb65d..8149f352bf 100644 --- a/src/header/sys_times/mod.rs +++ b/src/header/sys_times/mod.rs @@ -1,9 +1,11 @@ -//! sys/times.h implementation +//! `sys/times.h` implementation. +//! +//! See . -use platform; -use platform::types::*; -use platform::{Pal, Sys}; +use crate::platform; +use platform::{Pal, Sys, types::clock_t}; +/// See . #[repr(C)] pub struct tms { tms_utime: clock_t, @@ -12,7 +14,8 @@ pub struct tms { tms_cstime: clock_t, } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn times(out: *mut tms) -> clock_t { Sys::times(out) } diff --git a/src/header/sys_types/cbindgen.toml b/src/header/sys_types/cbindgen.toml index f9f83e156c..f0a2d8c655 100644 --- a/src/header/sys_types/cbindgen.toml +++ b/src/header/sys_types/cbindgen.toml @@ -1,11 +1,19 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_types.h.html +# +# There are no spec quotations relating to includes +# +# - sys/types_internal.h are fundamental C types (which includes stddef.h) +# - bits/pthread.h is separate for convenience +# - features.h saves importing separately for any header that imports sys/types.h +# # no default C includes - they cause recursive dependencies and do weird stuff no_includes = true sys_includes = [ # Import most necessary, internal types first - "sys/types_internal.h", + "sys/types/internal.h", - "stddef.h", - "sys/select.h" + "bits/pthread.h", + "features.h", ] include_guard = "_SYS_TYPES_H" diff --git a/src/header/sys_types/mod.rs b/src/header/sys_types/mod.rs index c348094fca..601f262aac 100644 --- a/src/header/sys_types/mod.rs +++ b/src/header/sys_types/mod.rs @@ -1,2 +1,8 @@ -//! sys/types.h -use crate::platform::types::*; +//! `sys/types.h` implementation. +//! +//! See . +//! +//! Note that the `useconds_t` type provided in the `sys/types.h` header was +//! removed in the Open Group Base Specifications Issue 7, see +//! +//! for the old specification. diff --git a/src/header/sys_types_internal/cbindgen.toml b/src/header/sys_types_internal/cbindgen.toml new file mode 100644 index 0000000000..9bf86018ec --- /dev/null +++ b/src/header/sys_types_internal/cbindgen.toml @@ -0,0 +1,45 @@ +sys_includes = ["stddef.h"] +# TODO: figure out how to export void* type +after_includes = """ + +typedef void* timer_t; +""" +include_guard = "_RELIBC_SYS_TYPES_INTERNAL_H" +language = "C" +style = "Type" +no_includes = true + +[export] +include = [ + "blksize_t", + "dev_t", + "ino_t", + "reclen_t", + "gid_t", + "uid_t", + "mode_t", + "nlink_t", + "off_t", + "pid_t", + "id_t", + "ssize_t", + "time_t", + "useconds_t", + "suseconds_t", + "clock_t", + "clockid_t", + "blkcnt_t", + "fsblkcnt_t", + "fsfilcnt_t", + "u_char", + "uchar", + "u_short", + "ushort", + "u_int", + "uint", + "u_long", + "ulong", + "quad_t", + "u_quad_t", + "caddr_t" +] diff --git a/src/header/sys_types_internal/mod.rs b/src/header/sys_types_internal/mod.rs new file mode 100644 index 0000000000..4c4cde7e9d --- /dev/null +++ b/src/header/sys_types_internal/mod.rs @@ -0,0 +1,74 @@ +//! `sys/types.h` implementation. +//! +//! See . +//! +//! Note that the `useconds_t` type provided in the `sys/types.h` header was +//! removed in the Open Group Base Specifications Issue 7, see +//! +//! for the old specification. + +use crate::platform::types::{ + c_char, c_int, c_long, c_longlong, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort, +}; + +/// Used for block sizes. +pub type blksize_t = c_long; +/// Used for device IDs. +pub type dev_t = c_ulonglong; +/// Used for serial numbers. +pub type ino_t = c_ulonglong; +/// Used for directory entry lengths. +pub type reclen_t = c_ushort; +/// Used for group IDs. +pub type gid_t = c_int; +/// Used for user IDs. +pub type uid_t = c_int; +/// Used for some file attributes. +pub type mode_t = c_int; +/// Used for link counts. +pub type nlink_t = c_ulong; +/// Used for file sizes. +pub type off_t = c_longlong; +/// Used for process IDs and process group IDs. +pub type pid_t = c_int; +/// Used as a general identifier; can be used to contain at least a pid_t, uid_t, or gid_t. +pub type id_t = c_uint; +/// Used for a count of bytes or an error indication. +pub type ssize_t = c_long; +/// Used for time in seconds. +pub type time_t = c_longlong; +pub type useconds_t = c_uint; + +#[cfg(target_os = "linux")] +/// Used for time in microseconds. +pub type suseconds_t = c_long; +#[cfg(not(target_os = "linux"))] +/// Used for time in microseconds. +pub type suseconds_t = c_int; + +/// Used for system times in clock ticks or CLOCKS_PER_SEC. +pub type clock_t = c_long; +/// Used for clock ID type in the clock and timer functions. +pub type clockid_t = c_int; + +// timer_t in cbindgen after_includes (how to export void* type?) + +/// Used for file block counts. +pub type blkcnt_t = c_longlong; + +/// Used for file system block counts. +pub type fsblkcnt_t = c_ulong; +/// Used for file system file counts. +pub type fsfilcnt_t = c_ulong; + +pub type u_char = c_uchar; +pub type uchar = c_uchar; +pub type u_short = c_ushort; +pub type ushort = c_ushort; +pub type u_int = c_uint; +pub type uint = c_uint; +pub type u_long = c_ulong; +pub type ulong = c_ulong; +pub type quad_t = c_longlong; +pub type u_quad_t = c_ulonglong; +pub type caddr_t = *mut c_char; diff --git a/src/header/sys_uio/cbindgen.toml b/src/header/sys_uio/cbindgen.toml index 78bb648d46..d87e2a18c9 100644 --- a/src/header/sys_uio/cbindgen.toml +++ b/src/header/sys_uio/cbindgen.toml @@ -1,9 +1,18 @@ -sys_includes = ["sys/types.h"] -include_guard = "_SYS_UIO_H" +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_uio.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the ssize_t and size_t types as described in ." +# +# bits/iovec.h brings in sys/types.h +sys_includes = ["bits/iovec.h"] +include_guard = "_RELIBC_SYS_UIO_H" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] prefix_with_name = true + +[export.rename] +"iovec" = "struct iovec" diff --git a/src/header/sys_uio/mod.rs b/src/header/sys_uio/mod.rs index f956740e73..1e3976ae76 100644 --- a/src/header/sys_uio/mod.rs +++ b/src/header/sys_uio/mod.rs @@ -1,70 +1,92 @@ -//! sys/uio implementation for Redox, following http://pubs.opengroup.org/onlinepubs/007904875/basedefs/sys/uio.h.html +//! `sys/uio.h` implementation. +//! +//! See . -use alloc::vec::Vec; use core::slice; use crate::{ - header::{errno, unistd}, - platform::{self, types::*}, + header::{ + bits_iovec::{gather, iovec, scatter}, + errno, unistd, + }, + platform::{ + self, + types::{c_int, c_void, off_t, ssize_t}, + }, }; pub const IOV_MAX: c_int = 1024; -#[repr(C)] -pub struct iovec { - iov_base: *mut c_void, - iov_len: size_t, -} - -impl iovec { - unsafe fn to_slice(&self) -> &mut [u8] { - slice::from_raw_parts_mut(self.iov_base as *mut u8, self.iov_len as usize) - } -} - -unsafe fn gather(iovs: &[iovec]) -> Vec { - let mut vec = Vec::new(); - for iov in iovs.iter() { - vec.extend_from_slice(iov.to_slice()); - } - vec -} - -unsafe fn scatter(iovs: &[iovec], vec: Vec) { - let mut i = 0; - for iov in iovs.iter() { - let slice = iov.to_slice(); - slice.copy_from_slice(&vec[i..]); - i += slice.len(); - } -} - -#[no_mangle] -pub unsafe extern "C" fn readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { - if iovcnt < 0 || iovcnt > IOV_MAX { - platform::errno = errno::EINVAL; +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn preadv( + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + offset: off_t, +) -> ssize_t { + if !(0..=IOV_MAX).contains(&iovcnt) { + platform::ERRNO.set(errno::EINVAL); return -1; } - let iovs = slice::from_raw_parts(iov, iovcnt as usize); - let mut vec = gather(iovs); + let iovs = unsafe { slice::from_raw_parts(iov, iovcnt as usize) }; + let mut vec = unsafe { gather(iovs) }; - let ret = unistd::read(fd, vec.as_mut_ptr() as *mut c_void, vec.len()); + let ret = unsafe { unistd::pread(fd, vec.as_mut_ptr().cast::(), vec.len(), offset) }; - scatter(iovs, vec); + unsafe { scatter(iovs, vec) }; ret } -#[no_mangle] -pub unsafe extern "C" fn writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { - if iovcnt < 0 || iovcnt > IOV_MAX { - platform::errno = errno::EINVAL; +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pwritev( + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + offset: off_t, +) -> ssize_t { + if !(0..=IOV_MAX).contains(&iovcnt) { + platform::ERRNO.set(errno::EINVAL); return -1; } - let iovs = slice::from_raw_parts(iov, iovcnt as usize); - let vec = gather(iovs); + let iovs = unsafe { slice::from_raw_parts(iov, iovcnt as usize) }; + let vec = unsafe { gather(iovs) }; - unistd::write(fd, vec.as_ptr() as *const c_void, vec.len()) + unsafe { unistd::pwrite(fd, vec.as_ptr().cast::(), vec.len(), offset) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { + if !(0..=IOV_MAX).contains(&iovcnt) { + platform::ERRNO.set(errno::EINVAL); + return -1; + } + + let iovs = unsafe { slice::from_raw_parts(iov, iovcnt as usize) }; + let mut vec = unsafe { gather(iovs) }; + + let ret = unsafe { unistd::read(fd, vec.as_mut_ptr().cast::(), vec.len()) }; + + unsafe { scatter(iovs, vec) }; + + ret +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { + if !(0..=IOV_MAX).contains(&iovcnt) { + platform::ERRNO.set(errno::EINVAL); + return -1; + } + + let iovs = unsafe { slice::from_raw_parts(iov, iovcnt as usize) }; + let vec = unsafe { gather(iovs) }; + + unsafe { unistd::write(fd, vec.as_ptr().cast::(), vec.len()) } } diff --git a/src/header/sys_un/cbindgen.toml b/src/header/sys_un/cbindgen.toml index a0f6054d94..d5ebca0687 100644 --- a/src/header/sys_un/cbindgen.toml +++ b/src/header/sys_un/cbindgen.toml @@ -1,5 +1,12 @@ -sys_includes = ["sys/socket.h"] -include_guard = "_SYS_UN_H" +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the sa_family_t type as described in ." +sys_includes = [] +after_includes = """ +#include // for sa_family_t from sys/socket.h +""" +include_guard = "_RELIBC_SYS_UN_H" language = "C" style = "Tag" no_includes = true diff --git a/src/header/sys_un/mod.rs b/src/header/sys_un/mod.rs index 63d26c8ff7..2a5ab35a3f 100644 --- a/src/header/sys_un/mod.rs +++ b/src/header/sys_un/mod.rs @@ -1,5 +1,10 @@ -use crate::{header::sys_socket::sa_family_t, platform::types::*}; +//! `sys/un.h` implementation. +//! +//! See . +use crate::{header::bits_safamily_t::sa_family_t, platform::types::c_char}; + +/// See . #[repr(C)] pub struct sockaddr_un { pub sun_family: sa_family_t, @@ -8,9 +13,9 @@ pub struct sockaddr_un { impl sockaddr_un { pub fn path_offset(&self) -> usize { - let base = self as *const _ as usize; - let path = &self.sun_path as *const _ as usize; - trace!("base: {:#X}, path: {:#X}", base, path); + let base = core::ptr::from_ref(self) as usize; + let path = &raw const self.sun_path as usize; + log::trace!("base: {:#X}, path: {:#X}", base, path); path - base } } diff --git a/src/header/sys_utsname/cbindgen.toml b/src/header/sys_utsname/cbindgen.toml index 5d91813fe4..4a5ff660d4 100644 --- a/src/header/sys_utsname/cbindgen.toml +++ b/src/header/sys_utsname/cbindgen.toml @@ -1,4 +1,7 @@ -include_guard = "_SYS_UTSNAME_H" +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_utsname.h.html +# +# There are no spec quotations relating to includes +include_guard = "_RELIBC_SYS_UTSNAME_H" language = "C" style = "Tag" no_includes = true diff --git a/src/header/sys_utsname/mod.rs b/src/header/sys_utsname/mod.rs index 821868ba54..49653883e1 100644 --- a/src/header/sys_utsname/mod.rs +++ b/src/header/sys_utsname/mod.rs @@ -1,10 +1,21 @@ -//! sys/utsname implementation, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/sysutsname.h.html +//! `sys/utsname.h` implementation. +//! +//! See . -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + error::ResultExt, + out::Out, + platform::{ + Pal, Sys, + types::{c_char, c_int}, + }, +}; pub const UTSLENGTH: usize = 65; +/// See . #[repr(C)] +#[derive(Clone, Debug, OutProject)] pub struct utsname { pub sysname: [c_char; UTSLENGTH], pub nodename: [c_char; UTSLENGTH], @@ -14,7 +25,10 @@ pub struct utsname { pub domainname: [c_char; UTSLENGTH], } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn uname(uts: *mut utsname) -> c_int { - Sys::uname(uts) + Sys::uname(unsafe { Out::nonnull(uts) }) + .map(|()| 0) + .or_minus_one_errno() } diff --git a/src/header/sys_wait/cbindgen.toml b/src/header/sys_wait/cbindgen.toml index 7b39e2b8a9..e213c2bbe7 100644 --- a/src/header/sys_wait/cbindgen.toml +++ b/src/header/sys_wait/cbindgen.toml @@ -1,9 +1,19 @@ -sys_includes = ["sys/types.h", "sys/resource.h"] -include_guard = "_SYS_WAIT_H" -trailer = "#include " +sys_includes = ["sys/types.h", "sys/resource.h", "signal.h", "stdint.h"] +include_guard = "_RELIBC_SYS_WAIT_H" +after_includes = """ + +#define WEXITSTATUS(s) (((s) >> 8) & 0xff) +#define WTERMSIG(s) ((s) & 0x7f) +#define WSTOPSIG(s) WEXITSTATUS(s) +#define WCOREDUMP(s) (((s) & 0x80) != 0) +#define WIFEXITED(s) (((s) & 0x7f) == 0) +#define WIFSTOPPED(s) (((s) & 0xff) == 0x7f) +#define WIFSIGNALED(s) (((((s) & 0x7f) + 1U) & 0x7f) >= 2) // Ends with 1111111 or 10000000 +#define WIFCONTINUED(s) ((s) == 0xffff) +""" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/sys_wait/mod.rs b/src/header/sys_wait/mod.rs index 3628353977..58d769250e 100644 --- a/src/header/sys_wait/mod.rs +++ b/src/header/sys_wait/mod.rs @@ -1,8 +1,19 @@ -//! sys/wait.h implementation for Redox, following -//! http://pubs.opengroup.org/onlinepubs/7908799/xsh/syswait.h.html +//! `sys/wait.h` implementation. +//! +//! See . -//use header::sys_resource::rusage; -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + error::ResultExt, + header::signal::siginfo_t, + out::Out, + platform::{ + ERRNO, Pal, Sys, + types::{c_int, c_uint, pid_t}, + }, +}; + +pub type idtype_t = c_int; +pub type id_t = c_uint; pub const WNOHANG: c_int = 1; pub const WUNTRACED: c_int = 2; @@ -17,35 +28,146 @@ pub const __WALL: c_int = 0x4000_0000; #[allow(overflowing_literals)] pub const __WCLONE: c_int = 0x8000_0000; -#[no_mangle] +pub const P_ALL: idtype_t = 0; +pub const P_PID: idtype_t = 1; +pub const P_PGID: idtype_t = 2; + +pub const CLD_EXITED: c_int = 1; +pub const CLD_KILLED: c_int = 2; +pub const CLD_DUMPED: c_int = 3; +pub const CLD_TRAPPED: c_int = 4; +pub const CLD_STOPPED: c_int = 5; +pub const CLD_CONTINUED: c_int = 6; + +fn wexitstatus(status: c_int) -> c_int { + (status >> 8) & 0xff +} + +fn wtermsig(status: c_int) -> c_int { + status & 0x7f +} + +fn wstopsig(status: c_int) -> c_int { + wexitstatus(status) +} + +fn wcoredump(status: c_int) -> bool { + (status & 0x80) != 0 +} + +fn wifexited(status: c_int) -> bool { + (status & 0x7f) == 0 +} + +fn wifstopped(status: c_int) -> bool { + (status & 0xff) == 0x7f +} + +fn wifcontinued(status: c_int) -> bool { + status == 0xffff +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wait(stat_loc: *mut c_int) -> pid_t { - waitpid(!0, stat_loc, 0) + unsafe { waitpid(!0, stat_loc, 0) } } -// #[no_mangle] -// pub unsafe extern "C" fn wait3( -// stat_loc: *mut c_int, -// options: c_int, -// resource_usage: *mut rusage, -// ) -> pid_t { -// unimplemented!(); -// } +fn map_waitid_target(idtype: idtype_t, id: id_t) -> Option { + match idtype { + P_ALL => Some(-1), + P_PID => Some(id as pid_t), + P_PGID => Some(if id == 0 { 0 } else { -(id as pid_t) }), + _ => None, + } +} -/* - * TODO: implement idtype_t, id_t, and siginfo_t - * - * #[no_mangle] - * pub unsafe extern "C" fn waitid( - * idtype: idtype_t, - * id: id_t, - * infop: siginfo_t, - * options: c_int - * ) -> c_int { - * unimplemented!(); - * } - */ +fn map_waitid_options(options: c_int) -> Option { + let interest = options & (WEXITED | WSTOPPED | WCONTINUED); + if interest == 0 { + return None; + } -#[no_mangle] + let mut waitpid_options = 0; + if options & WNOHANG != 0 { + waitpid_options |= WNOHANG; + } + if options & WSTOPPED != 0 { + waitpid_options |= WUNTRACED; + } + if options & WCONTINUED != 0 { + waitpid_options |= WCONTINUED; + } + if options & WNOWAIT != 0 { + waitpid_options |= WNOWAIT; + } + + Some(waitpid_options) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn waitid( + idtype: idtype_t, + id: id_t, + infop: *mut siginfo_t, + options: c_int, +) -> c_int { + if infop.is_null() { + ERRNO.set(crate::header::errno::EFAULT); + return -1; + } + + let Some(pid_target) = map_waitid_target(idtype, id) else { + ERRNO.set(crate::header::errno::EINVAL); + return -1; + }; + let Some(waitpid_options) = map_waitid_options(options) else { + ERRNO.set(crate::header::errno::EINVAL); + return -1; + }; + + let mut status = 0; + let pid = Sys::waitpid(pid_target, Some(Out::from_mut(&mut status)), waitpid_options) + .or_minus_one_errno(); + if pid < 0 { + return -1; + } + + unsafe { + *infop = core::mem::zeroed(); + } + if pid == 0 { + return 0; + } + + unsafe { + (*infop).si_pid = pid; + (*infop).si_signo = crate::header::signal::SIGCHLD as c_int; + (*infop).si_errno = 0; + if wifexited(status) { + (*infop).si_code = CLD_EXITED; + (*infop).si_status = wexitstatus(status); + } else if wifstopped(status) { + (*infop).si_code = CLD_STOPPED; + (*infop).si_status = wstopsig(status); + } else if wifcontinued(status) { + (*infop).si_code = CLD_CONTINUED; + (*infop).si_status = crate::header::signal::SIGCONT as c_int; + } else { + (*infop).si_status = wtermsig(status); + (*infop).si_code = if wcoredump(status) { + CLD_DUMPED + } else { + CLD_KILLED + }; + } + } + + 0 +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn waitpid(pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t { - Sys::waitpid(pid, stat_loc, options) + Sys::waitpid(pid, unsafe { Out::nullable(stat_loc) }, options).or_minus_one_errno() } diff --git a/src/header/tar/cbindgen.toml b/src/header/tar/cbindgen.toml new file mode 100644 index 0000000000..ca3ee9617c --- /dev/null +++ b/src/header/tar/cbindgen.toml @@ -0,0 +1,17 @@ +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/tar.h.html +# +# There are no spec quotations relating to includes +include_guard = "_RELIBC_TAR_H" +# cbindgen cannot export Rust &str +after_includes = """ + +#define TMAGIC "ustar" +#define TVERSION "00" +""" +language = "C" +style = "Tag" +no_includes = true +cpp_compat = true + +[enum] +prefix_with_name = true diff --git a/src/header/tar/mod.rs b/src/header/tar/mod.rs new file mode 100644 index 0000000000..ee7c595482 --- /dev/null +++ b/src/header/tar/mod.rs @@ -0,0 +1,150 @@ +//! `tar.h` implementation. +//! +//! See . + +use core::slice; + +/// Block size for tar archives (512 bytes). +pub const BLOCKSIZE: usize = 512; + +/// Default record size for tar archives (10KB, consisting of 20 blocks). +pub const RECORDSIZE: usize = BLOCKSIZE * 20; // 10KB (default for tar archives) + +/// Field lengths in tar headers +pub const NAME_SIZE: usize = 100; // File name +pub const MODE_SIZE: usize = 8; // File mode +pub const UID_SIZE: usize = 8; // Owner's numeric user ID +pub const GID_SIZE: usize = 8; // Group's numeric user ID +pub const SIZE_SIZE: usize = 12; // File size in bytes +pub const MTIME_SIZE: usize = 12; // Modification time +pub const CHKSUM_SIZE: usize = 8; // Checksum +pub const LINKNAME_SIZE: usize = 100; // Name of linked file +pub const MAGIC_SIZE: usize = 6; // Magic string size +pub const VERSION_SIZE: usize = 2; // Version string size +pub const UNAME_SIZE: usize = 32; // Owner user name +pub const GNAME_SIZE: usize = 32; // Owner group name +pub const DEVMAJOR_SIZE: usize = 8; // Major device number +pub const DEVMINOR_SIZE: usize = 8; // Minor device number +pub const PREFIX_SIZE: usize = 155; // Prefix for file name +pub const HEADER_SIZE: usize = 512; // Total header size + +/// Bits used in the mode field - value in octal +pub const TSUID: u16 = 0o4000; // Set user ID on execution +pub const TSGID: u16 = 0o2000; // Set group ID on execution +pub const TSVTX: u16 = 0o1000; // Sticky bit +pub const TUREAD: u16 = 0o0400; // Read permission, owner +pub const TUWRITE: u16 = 0o0200; // Write permission, owner +pub const TUEXEC: u16 = 0o0100; // Execute/search permission, owner +pub const TGREAD: u16 = 0o0040; // Read permission, group +pub const TGWRITE: u16 = 0o0020; // Write permission, group +pub const TGEXEC: u16 = 0o0010; // Execute/search permission, group +pub const TOREAD: u16 = 0o0004; // Read permission, others +pub const TOWRITE: u16 = 0o0002; // Write permission, others +pub const TOEXEC: u16 = 0o0001; // Execute/search permission, others + +/// Values used in typeflag field +pub const REGTYPE: u8 = b'0'; // Regular file +pub const AREGTYPE: u8 = b'\0'; // Regular file (old format) +pub const LNKTYPE: u8 = b'1'; // Link +pub const SYMTYPE: u8 = b'2'; // Symbolic link +pub const CHRTYPE: u8 = b'3'; // Character special +pub const BLKTYPE: u8 = b'4'; // Block special +pub const DIRTYPE: u8 = b'5'; // Directory +pub const FIFOTYPE: u8 = b'6'; // FIFO special +pub const CONTTYPE: u8 = b'7'; // Contiguous file + +/// tar format magic and version +/// cbindgen:ignore +pub const TMAGIC: &str = "ustar"; // Magic string : ustar and a null +pub const TMAGLEN: usize = 6; // Length of the magic string +/// cbindgen:ignore +pub const TVERSION: &str = "00"; // Version string +pub const TVERSLEN: usize = 2; // Length of the version string + +/// Reserved for future standards +pub const XHDRTYPE: u8 = b'x'; // Extended header referring to the next file in the archive +pub const XGLTYPE: u8 = b'g'; // Global extended header + +// Reserved values for GNU tar extensions +// pub const GNUTYPE_DUMPDIR: u8 = b'D'; // Directory dump +// pub const GNUTYPE_MULTIVOL: u8 = b'M'; // Multi-volume file +// pub const GNUTYPE_LONGNAME: u8 = b'L'; // Long file name +// pub const GNUTYPE_LONGLINK: u8 = b'K'; // Long link name +// pub const GNUTYPE_SPARSE: u8 = b'S'; // Sparse file + +/// Represents a tar archive header following the POSIX ustar format. +/// +/// The header contains metadata about a file in a tar archive, including +/// its name, size, permissions, and other attributes. All text fields are +/// null-terminated. +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct TarHeader { + // Byte offset - usage + pub name: [u8; NAME_SIZE], // 0 - File name + pub mode: [u8; MODE_SIZE], // 100 - Permissions + pub uid: [u8; UID_SIZE], // 108 - User ID + pub gid: [u8; GID_SIZE], // 116 - Group ID + pub size: [u8; SIZE_SIZE], // 124 - File size in bytes + pub mtime: [u8; MTIME_SIZE], // 136 - Modification time + pub chksum: [u8; CHKSUM_SIZE], // 148 - Header checksum + pub typeflag: u8, // 156 - File type + pub linkname: [u8; LINKNAME_SIZE], // 157 - Linked file name + pub magic: [u8; MAGIC_SIZE], // 257 - UStar magic + pub version: [u8; VERSION_SIZE], // 263 - UStar version + pub uname: [u8; UNAME_SIZE], // 265 - Owner user name + pub gname: [u8; GNAME_SIZE], // 297 - Owner group name + pub devmajor: [u8; DEVMAJOR_SIZE], // 329 - Major device number + pub devminor: [u8; DEVMINOR_SIZE], // 337 - Minor device number + pub prefix: [u8; PREFIX_SIZE], // 345 - Prefix for file name + pub padding: [u8; 12], // 500 - Padding to make 512 bytes +} + +impl Default for TarHeader { + fn default() -> Self { + let mut header = Self { + name: [0; NAME_SIZE], + mode: [0; MODE_SIZE], + uid: [0; UID_SIZE], + gid: [0; GID_SIZE], + size: [0; SIZE_SIZE], + mtime: [0; MTIME_SIZE], + chksum: [0; CHKSUM_SIZE], + typeflag: AREGTYPE, + linkname: [0; LINKNAME_SIZE], + magic: [0; MAGIC_SIZE], + version: [0; VERSION_SIZE], + uname: [0; UNAME_SIZE], + gname: [0; GNAME_SIZE], + devmajor: [0; DEVMAJOR_SIZE], + devminor: [0; DEVMINOR_SIZE], + prefix: [0; PREFIX_SIZE], + padding: [0; 12], + }; + + // Set default magic ("ustar") and version ("00") + let magic_bytes = TMAGIC.as_bytes(); // "ustar" + header.magic[..magic_bytes.len()].copy_from_slice(magic_bytes); + // tar specification often expects "ustar\0" + if MAGIC_SIZE >= 6 && TMAGIC.len() < MAGIC_SIZE { + header.magic[TMAGIC.len()] = 0; + } + + let version_bytes = TVERSION.as_bytes(); // "00" + header.version[..version_bytes.len()].copy_from_slice(version_bytes); + + header + } +} + +impl TarHeader { + /// Calculates the checksum of the tar header as required by the specification. + /// Before computing, the checksum field is treated as if it contained all spaces (0x20). + pub fn calculate_checksum(&self) -> usize { + let mut header_copy = *self; + header_copy.chksum.fill(b' '); + let bytes = + unsafe { slice::from_raw_parts((&raw const header_copy).cast::(), HEADER_SIZE) }; + bytes.iter().map(|&b| b as usize).sum() + } +} diff --git a/src/header/termios/cbindgen.toml b/src/header/termios/cbindgen.toml index 03a82a153a..85d08f0d90 100644 --- a/src/header/termios/cbindgen.toml +++ b/src/header/termios/cbindgen.toml @@ -1,13 +1,16 @@ -sys_includes = ["stdint.h"] +# POSIX header spec: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/termios.h.html +# +# Spec quotations relating to includes: +# - "The header shall define the pid_t type as described in ." +sys_includes = ["sys/types.h"] include_guard = "_RELIBC_TERMIOS_H" language = "C" style = "Tag" no_includes = true cpp_compat = true -[defines] -"target_os=linux" = "__linux__" -"target_os=redox" = "__redox__" - [enum] prefix_with_name = true + +[export.rename] +"winsize" = "struct winsize" diff --git a/src/header/termios/linux.rs b/src/header/termios/linux.rs index 1bbfbf3934..2659af5e65 100644 --- a/src/header/termios/linux.rs +++ b/src/header/termios/linux.rs @@ -111,3 +111,7 @@ pub const NOFLSH: usize = 0o000_200; pub const TOSTOP: usize = 0o000_400; pub const IEXTEN: usize = 0o100_000; /* } c_lflag */ + +// POSIX extensions +/// Sentinel value to disable a control char. +pub const _POSIX_VDISABLE: u8 = 0; diff --git a/src/header/termios/mod.rs b/src/header/termios/mod.rs index f5d1705785..5bd8de3318 100644 --- a/src/header/termios/mod.rs +++ b/src/header/termios/mod.rs @@ -1,8 +1,16 @@ -//! termios implementation, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/termios.h.html +//! `termios.h` implementation. +//! +//! See . use crate::{ - header::{errno, sys_ioctl}, - platform::{self, types::*}, + header::{ + errno, + sys_ioctl::{self, winsize}, + }, + platform::{ + self, + types::{c_int, c_uchar, c_uint, c_ulong, c_void, pid_t}, + }, }; pub use self::sys::*; @@ -15,109 +23,226 @@ pub mod sys; #[path = "redox.rs"] pub mod sys; -pub type cc_t = u8; -pub type speed_t = u32; -pub type tcflag_t = u32; +pub type cc_t = c_uchar; +pub type speed_t = c_uint; +pub type tcflag_t = c_uint; -pub const TCOOFF: usize = 0; -pub const TCOON: usize = 1; -pub const TCIOFF: usize = 2; -pub const TCION: usize = 3; +pub const TCOOFF: c_int = 0; +pub const TCOON: c_int = 1; +pub const TCIOFF: c_int = 2; +pub const TCION: c_int = 3; -pub const TCIFLUSH: usize = 0; -pub const TCOFLUSH: usize = 1; -pub const TCIOFLUSH: usize = 2; +pub const TCIFLUSH: c_int = 0; +pub const TCOFLUSH: c_int = 1; +pub const TCIOFLUSH: c_int = 2; -pub const TCSANOW: usize = 0; -pub const TCSADRAIN: usize = 1; -pub const TCSAFLUSH: usize = 2; +pub const TCSANOW: c_int = 0; +pub const TCSADRAIN: c_int = 1; +pub const TCSAFLUSH: c_int = 2; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cbindgen_export_winsize(winsize: winsize) {} + +/// See . +#[cfg(target_os = "linux")] #[repr(C)] -#[derive(Default)] +#[derive(Default, Clone)] pub struct termios { - c_iflag: tcflag_t, - c_oflag: tcflag_t, - c_cflag: tcflag_t, - c_lflag: tcflag_t, - c_line: cc_t, - c_cc: [cc_t; NCCS], - __c_ispeed: speed_t, - __c_ospeed: speed_t, + pub c_iflag: tcflag_t, + pub c_oflag: tcflag_t, + pub c_cflag: tcflag_t, + pub c_lflag: tcflag_t, + pub c_line: cc_t, + pub c_cc: [cc_t; NCCS], + pub __c_ispeed: speed_t, + pub __c_ospeed: speed_t, } -#[no_mangle] +// Must match structure in redox_termios +/// See . +#[cfg(target_os = "redox")] +#[repr(C)] +#[derive(Default, Clone)] +pub struct termios { + pub c_iflag: tcflag_t, + pub c_oflag: tcflag_t, + pub c_cflag: tcflag_t, + pub c_lflag: tcflag_t, + pub c_cc: [cc_t; NCCS], +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tcgetattr(fd: c_int, out: *mut termios) -> c_int { - sys_ioctl::ioctl(fd, sys_ioctl::TCGETS, out as *mut c_void) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCGETS, out.cast::()) } } -#[no_mangle] -pub unsafe extern "C" fn tcsetattr(fd: c_int, act: c_int, value: *mut termios) -> c_int { - if act < 0 || act > 2 { - platform::errno = errno::EINVAL; +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tcsetattr(fd: c_int, act: c_int, value: *const termios) -> c_int { + if !(0..=2).contains(&act) { + platform::ERRNO.set(errno::EINVAL); return -1; } // This is safe because ioctl shouldn't modify the value - sys_ioctl::ioctl(fd, sys_ioctl::TCSETS + act as c_ulong, value as *mut c_void) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCSETS + act as c_ulong, value as *mut c_void) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tcgetsid(fd: c_int) -> pid_t { + let mut sid = 0; + if unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCGSID, (&raw mut sid).cast::()) } < 0 { + return -1; + } + sid +} + +/// See . +#[cfg(target_os = "linux")] +#[unsafe(no_mangle)] pub unsafe extern "C" fn cfgetispeed(termios_p: *const termios) -> speed_t { - (*termios_p).__c_ispeed + unsafe { (*termios_p).__c_ispeed } } -#[no_mangle] +/// See . +#[cfg(target_os = "redox")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfgetispeed(termios_p: *const termios) -> speed_t { + //TODO + 0 +} + +/// See . +#[cfg(target_os = "linux")] +#[unsafe(no_mangle)] pub unsafe extern "C" fn cfgetospeed(termios_p: *const termios) -> speed_t { - (*termios_p).__c_ospeed + unsafe { (*termios_p).__c_ospeed } } -#[no_mangle] +/// See . +#[cfg(target_os = "redox")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfgetospeed(termios_p: *const termios) -> speed_t { + //TODO + 0 +} + +/// See . +#[cfg(target_os = "linux")] +#[unsafe(no_mangle)] pub unsafe extern "C" fn cfsetispeed(termios_p: *mut termios, speed: speed_t) -> c_int { match speed as usize { B0..=B38400 | B57600..=B4000000 => { - (*termios_p).__c_ispeed = speed; + unsafe { (*termios_p).__c_ispeed = speed }; 0 } _ => { - platform::errno = errno::EINVAL; + platform::ERRNO.set(errno::EINVAL); -1 } } } -#[no_mangle] +/// See . +#[cfg(target_os = "redox")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfsetispeed(termios_p: *mut termios, speed: speed_t) -> c_int { + //TODO + platform::ERRNO.set(errno::EINVAL); + -1 +} + +/// See . +#[cfg(target_os = "linux")] +#[unsafe(no_mangle)] pub unsafe extern "C" fn cfsetospeed(termios_p: *mut termios, speed: speed_t) -> c_int { match speed as usize { B0..=B38400 | B57600..=B4000000 => { - (*termios_p).__c_ospeed = speed; + unsafe { (*termios_p).__c_ospeed = speed }; 0 } _ => { - platform::errno = errno::EINVAL; + platform::ERRNO.set(errno::EINVAL); -1 } } } -#[no_mangle] +/// See . +#[cfg(target_os = "redox")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfsetospeed(termios_p: *mut termios, speed: speed_t) -> c_int { + //TODO + platform::ERRNO.set(errno::EINVAL); + -1 +} + +/// Non-POSIX, 4.4 BSD extension +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfsetspeed(termios_p: *mut termios, speed: speed_t) -> c_int { + let r = unsafe { cfsetispeed(termios_p, speed) }; + if r < 0 { + return r; + } + unsafe { cfsetospeed(termios_p, speed) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tcflush(fd: c_int, queue: c_int) -> c_int { - sys_ioctl::ioctl(fd, sys_ioctl::TCFLSH, queue as *mut c_void) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCFLSH, queue as *mut c_void) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tcdrain(fd: c_int) -> c_int { - sys_ioctl::ioctl(fd, sys_ioctl::TCSBRK, 1 as *mut _) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCSBRK, core::ptr::dangling_mut()) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tcsendbreak(fd: c_int, _dur: c_int) -> c_int { // non-zero duration is ignored by musl due to it being // implementation-defined. we do the same. - sys_ioctl::ioctl(fd, sys_ioctl::TCSBRK, 0 as *mut _) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCSBRK, core::ptr::null_mut()) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tcgetwinsize(fd: c_int, sws: *mut winsize) -> c_int { + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCGWINSZ, sws.cast()) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tcsetwinsize(fd: c_int, sws: *const winsize) -> c_int { + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCSWINSZ, sws.cast_mut().cast()) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn tcflow(fd: c_int, action: c_int) -> c_int { // non-zero duration is ignored by musl due to it being // implementation-defined. we do the same. - sys_ioctl::ioctl(fd, sys_ioctl::TCXONC, action as *mut _) + unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TCXONC, action as *mut _) } +} + +/// Non-POSIX, BSD extension +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn cfmakeraw(termios_p: *mut termios) { + unsafe { + (*termios_p).c_iflag &= + !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) as u32; + (*termios_p).c_oflag &= !OPOST as u32; + (*termios_p).c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN) as u32; + (*termios_p).c_cflag &= !(CSIZE | PARENB) as u32; + (*termios_p).c_cflag |= CS8 as u32; + (*termios_p).c_cc[VMIN] = 1; + (*termios_p).c_cc[VTIME] = 0; + } } diff --git a/src/header/termios/redox.rs b/src/header/termios/redox.rs index 2b52146ced..526e59aa51 100644 --- a/src/header/termios/redox.rs +++ b/src/header/termios/redox.rs @@ -31,6 +31,7 @@ pub const IGNCR: usize = 0o000_200; pub const ICRNL: usize = 0o000_400; pub const IXON: usize = 0o001_000; pub const IXOFF: usize = 0o002_000; +pub const IXANY: usize = 0o004_000; /* } c_iflag */ /* c_oflag { */ @@ -104,3 +105,7 @@ pub const NOFLSH: usize = 0x8000_0000; pub const TOSTOP: usize = 0x0040_0000; pub const IEXTEN: usize = 0x0000_0400; /* } c_lflag */ + +// POSIX extensions +/// Sentinel value to disable a control char. +pub const _POSIX_VDISABLE: u8 = 0; diff --git a/src/header/threads/cbindgen.toml b/src/header/threads/cbindgen.toml new file mode 100644 index 0000000000..3f90606676 --- /dev/null +++ b/src/header/threads/cbindgen.toml @@ -0,0 +1,17 @@ +sys_includes = ["stddef.h", "pthread.h", "time.h"] +include_guard = "_RELIBC_THREADS_H" +language = "C" +style = "Type" +no_includes = true +cpp_compat = true + +[export] +include = [ + "thrd_t", + "mtx_t", + "cnd_t", + "thrd_start_t", +] + +[enum] +prefix_with_name = true diff --git a/src/header/threads/mod.rs b/src/header/threads/mod.rs new file mode 100644 index 0000000000..3e19666e83 --- /dev/null +++ b/src/header/threads/mod.rs @@ -0,0 +1,36 @@ +//! `threads.h` implementation — C11 threads type definitions and constants. +//! +//! Full C11 threads API (thrd_create, mtx_lock, cnd_wait, etc.) requires +//! a deeper pthread integration layer; this module provides the type +//! definitions and constants for C11 header compatibility. + +use crate::platform::types::c_int; + +pub type thrd_start_t = Option c_int>; + +pub const thrd_success: c_int = 0; +pub const thrd_nomem: c_int = -1; +pub const thrd_timedout: c_int = -2; +pub const thrd_busy: c_int = -3; +pub const thrd_error: c_int = -4; + +pub const mtx_plain: c_int = 0; +pub const mtx_timed: c_int = 1; + +// Opaque types; sizes match relibc's pthread backing types +// (pthread_t = *mut c_void = 8 bytes, pthread_mutex_t = 12 bytes, +// pthread_cond_t = 8 bytes) +#[repr(C)] +pub struct thrd_t { _priv: *mut core::ffi::c_void } +#[repr(C)] +pub struct mtx_t { _priv: [u8; 12] } +#[repr(C)] +pub struct cnd_t { _priv: [u8; 8] } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn thrd_yield() {} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn thrd_exit(_ret: *mut c_int) -> ! { + loop {} +} diff --git a/src/header/time/cbindgen.toml b/src/header/time/cbindgen.toml index 0b7e7399bd..d2476bd3b3 100644 --- a/src/header/time/cbindgen.toml +++ b/src/header/time/cbindgen.toml @@ -1,13 +1,17 @@ -sys_includes = ["sys/types.h", "stdint.h", "stddef.h"] +sys_includes = ["sys/types.h", "stdint.h", "stddef.h", "features.h"] include_guard = "_RELIBC_TIME_H" language = "C" style = "Tag" no_includes = true cpp_compat = true +after_includes = """ +#include // for timespec +struct sigevent; +""" [enum] prefix_with_name = true -[defines] -"target_os = linux" = "__linux__" -"target_os = redox" = "__redox__" +[export.rename] +"timespec" = "struct timespec" +"sigevent" = "struct sigevent" diff --git a/src/header/time/constants.rs b/src/header/time/constants.rs index 8e8bd80449..2b3d3ba645 100644 --- a/src/header/time/constants.rs +++ b/src/header/time/constants.rs @@ -1,6 +1,4 @@ -use crate::platform::types::*; - -pub use self::sys::*; +use crate::platform::types::{c_char, c_int, c_long, clockid_t}; #[cfg(target_os = "linux")] #[path = "linux.rs"] @@ -10,7 +8,9 @@ pub mod sys; #[path = "redox.rs"] pub mod sys; -pub(crate) const UTC: *const c_char = b"UTC\0" as *const u8 as *const c_char; +pub use self::sys::*; + +pub(crate) const UTC: *const c_char = c"UTC".as_ptr().cast(); pub(crate) const DAY_NAMES: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; pub(crate) const MON_NAMES: [&str; 12] = [ @@ -20,3 +20,14 @@ pub(crate) const MON_NAMES: [&str; 12] = [ pub const CLOCK_PROCESS_CPUTIME_ID: clockid_t = 2; // Can't be time_t because cbindgen UGH pub const CLOCKS_PER_SEC: c_long = 1_000_000; + +pub const TIMER_ABSTIME: c_int = 1; + +// Constants for timespec_get and timespec_getres which are C23 analogues to +// clock_gettime/getres. +// The values are offset by one for simplicity since zero represents an error. + +/// `TIME_UTC` returns the time since the Unix epoch. +pub const TIME_UTC: c_int = CLOCK_REALTIME + 1; +/// `TIME_MONOTONIC` returns the time from the monotonically increasing clock. +pub const TIME_MONOTONIC: c_int = CLOCK_MONOTONIC + 1; diff --git a/src/header/time/linux.rs b/src/header/time/linux.rs index a5ee7903d4..c3d67cfd8c 100644 --- a/src/header/time/linux.rs +++ b/src/header/time/linux.rs @@ -1,8 +1,7 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const CLOCK_REALTIME: c_int = 0; pub const CLOCK_MONOTONIC: c_int = 1; -pub const CLOCK_PROCESS_CPUTIME_ID: c_int = 2; pub const CLOCK_THREAD_CPUTIME_ID: c_int = 3; pub const CLOCK_MONOTONIC_RAW: c_int = 4; pub const CLOCK_REALTIME_COARSE: c_int = 5; diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs index 89eadd6a0f..1fe2ba0c40 100644 --- a/src/header/time/mod.rs +++ b/src/header/time/mod.rs @@ -1,91 +1,183 @@ -//! time implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html - -use core::convert::{TryFrom, TryInto}; +//! `time.h` implementation. +//! +//! See . use crate::{ - header::errno::{EIO, EOVERFLOW}, - platform::{self, types::*, Pal, Sys}, + c_str::{CStr, CString}, + error::{Errno, ResultExt}, + header::{ + bits_timespec::timespec, + errno::{EFAULT, EINVAL, ENOMEM, EOVERFLOW, ETIMEDOUT}, + signal::sigevent, + stdlib::getenv, + unistd::readlink, + }, + out::Out, + platform::{ + self, Pal, Sys, + types::{ + c_char, c_double, c_int, c_long, clock_t, clockid_t, pid_t, size_t, time_t, timer_t, + }, + }, + raw_cell::RawCell, + sync::{Mutex, MutexGuard}, }; +use alloc::collections::BTreeSet; +use chrono::{ + DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, Offset, TimeZone, Timelike, Utc, + offset::MappedLocalTime, +}; +use chrono_tz::{OffsetComponents, OffsetName, Tz}; +use core::{cell::OnceCell, convert::TryFrom, mem, ptr}; pub use self::constants::*; pub mod constants; -mod strftime; -#[repr(C)] -#[derive(Default)] -pub struct timespec { - pub tv_sec: time_t, - pub tv_nsec: c_long, -} +mod strftime; +mod strptime; +pub use strptime::strptime; + +const YEARS_PER_ERA: time_t = 400; +const DAYS_PER_ERA: time_t = 146097; +const SECS_PER_DAY: time_t = 24 * 60 * 60; +pub(crate) const NANOSECONDS: c_long = 1_000_000_000; +const UTC_STR: &core::ffi::CStr = c"UTC"; #[cfg(target_os = "redox")] impl<'a> From<&'a timespec> for syscall::TimeSpec { fn from(tp: ×pec) -> Self { Self { - tv_sec: tp.tv_sec, - tv_nsec: tp.tv_nsec as i32, + tv_sec: tp.tv_sec as _, + tv_nsec: tp.tv_nsec as _, } } } +#[cfg(target_os = "redox")] +impl<'a> From<&'a syscall::TimeSpec> for timespec { + fn from(tp: &syscall::TimeSpec) -> Self { + Self { + tv_sec: tp.tv_sec as _, + tv_nsec: tp.tv_nsec as _, + } + } +} + +/// timer_t internal data, ABI unstable +#[repr(C)] +#[derive(Clone)] +#[cfg(target_os = "redox")] +pub(crate) struct timer_internal_t { + pub clockid: clockid_t, + pub timerfd: usize, + pub eventfd: usize, + pub evp: sigevent, + pub thread: platform::types::pthread_t, + pub caller_thread: crate::pthread::OsTid, + // relibc handles it_interval, not the kernel + pub next_wake_time: itimerspec, + // When non-zero, timer_routine delivers SIGALRM via kill(process_pid, sig) + // instead of rlct_kill (thread-specific). Used by alarm(). + pub process_pid: platform::types::pid_t, +} + +/// See . #[repr(C)] pub struct tm { - pub tm_sec: c_int, - pub tm_min: c_int, - pub tm_hour: c_int, - pub tm_mday: c_int, - pub tm_mon: c_int, - pub tm_year: c_int, - pub tm_wday: c_int, - pub tm_yday: c_int, - pub tm_isdst: c_int, - pub tm_gmtoff: c_long, - pub tm_zone: *const c_char, + pub tm_sec: c_int, // 0 - 60 + pub tm_min: c_int, // 0 - 59 + pub tm_hour: c_int, // 0 - 23 + pub tm_mday: c_int, // 1 - 31 + pub tm_mon: c_int, // 0 - 11 + pub tm_year: c_int, // years since 1900 + pub tm_wday: c_int, // 0 - 6 (Sunday - Saturday) + pub tm_yday: c_int, // 0 - 365 + pub tm_isdst: c_int, // >0 if DST, 0 if not, <0 if unknown + pub tm_gmtoff: c_long, // offset from UTC in seconds + pub tm_zone: *const c_char, // timezone abbreviation } unsafe impl Sync for tm {} // The C Standard says that localtime and gmtime return the same pointer. -static mut TM: tm = tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_gmtoff: 0, - tm_zone: UTC, -}; +static GMTIME_LOCALTIME_RETURN_TM: RawCell = RawCell::new(blank_tm()); // The C Standard says that ctime and asctime return the same pointer. static mut ASCTIME: [c_char; 26] = [0; 26]; +#[repr(transparent)] +pub struct TzName([*mut c_char; 2]); + +unsafe impl Sync for TzName {} + +// Name storage for the `tm_zone` field. +static TIMEZONE_NAMES: Mutex>> = Mutex::new(OnceCell::new()); + +// relibc functions should hold `TIMEZONE_LOCK` when accessing `daylight`, +// `timezone`, and `tzname`. However, it cannot guard those variables against +// user access (see `tzset()` specs for details). +static TIMEZONE_LOCK: Mutex<(Option, Option)> = Mutex::new((None, None)); + +// Should only be accessed by relibc when `TIMEZONE_LOCK` is held +/// See . +#[allow(non_upper_case_globals)] +#[unsafe(no_mangle)] +pub static mut daylight: c_int = 0; + +// Should only be accessed by relibc when `TIMEZONE_LOCK` is held +/// See . +#[allow(non_upper_case_globals)] +#[unsafe(no_mangle)] +pub static mut timezone: c_long = 0; + +// Should only be accessed by relibc when `TIMEZONE_LOCK` is held +/// See . +#[allow(non_upper_case_globals)] +#[unsafe(no_mangle)] +pub static mut tzname: TzName = TzName([ptr::null_mut(); 2]); + +#[allow(non_upper_case_globals)] +#[unsafe(no_mangle)] +pub static mut getdate_err: c_int = 0; + +/// See . #[repr(C)] +#[derive(Clone, Default)] pub struct itimerspec { pub it_interval: timespec, pub it_value: timespec, } -pub struct sigevent; - -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `asctime()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn asctime(timeptr: *const tm) -> *mut c_char { - asctime_r(timeptr, ASCTIME.as_mut_ptr().cast()) + unsafe { + #[allow(deprecated)] + asctime_r(timeptr, (&raw mut ASCTIME).cast()) + } } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `asctime_r()` was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn asctime_r(tm: *const tm, buf: *mut c_char) -> *mut c_char { - let tm_sec = (*tm).tm_sec; - let tm_min = (*tm).tm_min; - let tm_hour = (*tm).tm_hour; - let tm_mday = (*tm).tm_mday; - let tm_mon = (*tm).tm_mon; - let tm_year = (*tm).tm_year; - let tm_wday = (*tm).tm_wday; + let tm_sec = unsafe { (*tm).tm_sec }; + let tm_min = unsafe { (*tm).tm_min }; + let tm_hour = unsafe { (*tm).tm_hour }; + let tm_mday = unsafe { (*tm).tm_mday }; + let tm_mon = unsafe { (*tm).tm_mon }; + let tm_year = unsafe { (*tm).tm_year }; + let tm_wday = unsafe { (*tm).tm_wday }; /* Panic when we run into undefined behavior. * @@ -117,16 +209,16 @@ pub unsafe extern "C" fn asctime_r(tm: *const tm, buf: *mut c_char) -> *mut c_ch * message for all fields. */ const OUT_OF_RANGE_MESSAGE: &str = "tm member out of range"; - assert!(0 <= tm_sec && tm_sec <= 99, OUT_OF_RANGE_MESSAGE); - assert!(0 <= tm_min && tm_min <= 99, OUT_OF_RANGE_MESSAGE); - assert!(0 <= tm_hour && tm_hour <= 99, OUT_OF_RANGE_MESSAGE); - assert!(-99 <= tm_mday && tm_mday <= 999, OUT_OF_RANGE_MESSAGE); - assert!(0 <= tm_mon && tm_mon <= 11, OUT_OF_RANGE_MESSAGE); + assert!((0..=99).contains(&tm_sec), "{OUT_OF_RANGE_MESSAGE}"); + assert!((0..=99).contains(&tm_min), "{OUT_OF_RANGE_MESSAGE}"); + assert!((0..=99).contains(&tm_hour), "{OUT_OF_RANGE_MESSAGE}"); + assert!((-99..=999).contains(&tm_mday), "{OUT_OF_RANGE_MESSAGE}"); + assert!((0..=11).contains(&tm_mon), "{OUT_OF_RANGE_MESSAGE}"); assert!( - -999 - 1900 <= tm_year && tm_year <= 9999 - 1900, - OUT_OF_RANGE_MESSAGE + (-999 - 1900..=9999 - 1900).contains(&tm_year), + "{OUT_OF_RANGE_MESSAGE}" ); - assert!(0 <= tm_wday && tm_wday <= 6, OUT_OF_RANGE_MESSAGE); + assert!((0..=6).contains(&tm_wday), "{OUT_OF_RANGE_MESSAGE}"); // At this point, we can safely use the values as given. let write_result = core::fmt::write( @@ -148,354 +240,690 @@ pub unsafe extern "C" fn asctime_r(tm: *const tm, buf: *mut c_char) -> *mut c_ch Err(_) => { /* asctime()/asctime_r() or the equivalent sprintf() call * have no defined errno setting */ - core::ptr::null_mut() + ptr::null_mut() } } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn clock() -> clock_t { - let mut ts = core::mem::MaybeUninit::::uninit(); + let mut ts = mem::MaybeUninit::::uninit(); - if clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts.as_mut_ptr()) != 0 { + if unsafe { clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ts.as_mut_ptr()) } != 0 { return -1; } let ts = unsafe { ts.assume_init() }; - if ts.tv_sec > time_t::max_value() / CLOCKS_PER_SEC - || ts.tv_nsec / (1_000_000_000 / CLOCKS_PER_SEC) - > time_t::max_value() - CLOCKS_PER_SEC * ts.tv_sec - { - return -1; + let clocks = + ts.tv_sec * CLOCKS_PER_SEC as i64 + (ts.tv_nsec / (1_000_000_000 / CLOCKS_PER_SEC)) as i64; + clock_t::try_from(clocks).unwrap_or(-1) +} + +/// See . +// #[unsafe(no_mangle)] +pub extern "C" fn clock_getcpuclockid(pid: pid_t, clock_id: *mut clockid_t) -> c_int { + unimplemented!(); +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_getres(clock_id: clockid_t, res: *mut timespec) -> c_int { + Sys::clock_getres(clock_id, unsafe { Out::nullable(res) }) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_gettime(clock_id: clockid_t, tp: *mut timespec) -> c_int { + Sys::clock_gettime(clock_id, unsafe { Out::nonnull(tp) }) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +// #[unsafe(no_mangle)] +pub extern "C" fn clock_nanosleep( + clock_id: clockid_t, + flags: c_int, + rqtp: *const timespec, + rmtp: *mut timespec, +) -> c_int { + match clock_id { + CLOCK_REALTIME | CLOCK_MONOTONIC => { + if flags == TIMER_ABSTIME { + return EINVAL; + } + match unsafe { Sys::nanosleep(rqtp, rmtp) } { + Ok(()) => 0, + Err(Errno(ETIMEDOUT)) => ETIMEDOUT, + Err(Errno(e)) => e, + } + } + _ => EINVAL, } - - ts.tv_sec * CLOCKS_PER_SEC + ts.tv_nsec / (1_000_000_000 / CLOCKS_PER_SEC) } -// #[no_mangle] -pub extern "C" fn clock_getres(clock_id: clockid_t, res: *mut timespec) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_settime(clock_id: clockid_t, tp: *const timespec) -> c_int { + unsafe { Sys::clock_settime(clock_id, tp) } + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn clock_gettime(clock_id: clockid_t, tp: *mut timespec) -> c_int { - Sys::clock_gettime(clock_id, tp) -} - -// #[no_mangle] -pub extern "C" fn clock_settime(clock_id: clockid_t, tp: *const timespec) -> c_int { - unimplemented!(); -} - -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `ctime()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ctime(clock: *const time_t) -> *mut c_char { - asctime(localtime(clock)) + unsafe { + #[allow(deprecated)] + asctime(localtime(clock)) + } } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `ctime_r()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn ctime_r(clock: *const time_t, buf: *mut c_char) -> *mut c_char { // Using MaybeUninit seems to cause a panic during the build process - let mut tm1 = tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_gmtoff: 0, - tm_zone: core::ptr::null_mut(), - }; - localtime_r(clock, &mut tm1); - asctime_r(&tm1, buf) + let mut tm1 = blank_tm(); + unsafe { localtime_r(clock, &raw mut tm1) }; + unsafe { + #[allow(deprecated)] + asctime_r(&raw const tm1, buf) + } } -#[no_mangle] -pub extern "C" fn difftime(time1: time_t, time0: time_t) -> c_double { - (time1 - time0) as c_double +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn difftime(time1: time_t, time0: time_t) -> c_double { + (time1 - time0) as _ } -// #[no_mangle] -pub extern "C" fn getdate(string: *const c_char) -> tm { +/// See . +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn getdate(string: *const c_char) -> *const tm { unimplemented!(); } -#[no_mangle] +/// See . +/// +/// # Safety +/// The caller is required to ensure that: +/// * `timer` is a valid pointer +/// * the function has exclusive access to the static `tm` structure it +/// returns. This includes avoiding simultaneous calls to this function as +/// well as to [`localtime()`]. +#[unsafe(no_mangle)] pub unsafe extern "C" fn gmtime(timer: *const time_t) -> *mut tm { - gmtime_r(timer, &mut TM) + // SAFETY: the caller is required to uphold the safety requirements for + // `gmtime_r()` in addition to exclusive access to + // `GMTIME_LOCALTIME_RETURN_TM`. + unsafe { gmtime_r(timer, GMTIME_LOCALTIME_RETURN_TM.as_mut_ptr()) } } -const MONTH_DAYS: [[c_int; 12]; 2] = [ - // Non-leap years: - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - // Leap years: - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], -]; +/// See . +/// +/// # Safety +/// The caller is required to ensure that: +/// * `timer` is a valid pointer +/// * `result` is convertible to an [`Out`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gmtime_r(timer: *const time_t, result: *mut tm) -> *mut tm { + // SAFETY: the caller is required to ensure that `timer` is a valid pointer. + let timer_val = unsafe { *timer }; -#[inline(always)] -fn leap_year(year: c_int) -> bool { - year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) + // SAFETY: the caller is required to ensure that `result` is convertible + // to an `Out`. + let result_out = unsafe { Out::nonnull(result) }; + + let _ = get_localtime(timer_val, result_out); + result } -#[no_mangle] -pub unsafe extern "C" fn gmtime_r(clock: *const time_t, result: *mut tm) -> *mut tm { - /* For the details of the algorithm used here, see - * http://howardhinnant.github.io/date_algorithms.html#civil_from_days - * Note that we need 0-based months here, though. - * Overall, this implementation should generate correct results as - * long as the tm_year value will fit in a c_int. */ - const SECS_PER_DAY: time_t = 24 * 60 * 60; - const DAYS_PER_ERA: time_t = 146097; +/// See . +/// +/// # Safety +/// The caller is required to ensure that: +/// * `timer` is a valid pointer +/// * the function has exclusive access to the static `tm` structure it +/// returns. This implies avoiding simultaneous calls to this function as +/// well as to [`gmtime()`] +/// * the variables [`daylight`], [`timezone`] and [`tzname`] are not accessed +/// by user code for the duration of the call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn localtime(timer: *const time_t) -> *mut tm { + // SAFETY: the caller is required to uphold the safety requirements for + // `localtime_r()` in addition to exclusive access to + // `GMTIME_LOCALTIME_RETURN_TM`. + unsafe { localtime_r(timer, GMTIME_LOCALTIME_RETURN_TM.as_mut_ptr()) } +} - let unix_secs = *clock; +/// See . +/// +/// # Safety +/// The caller is required to ensure that: +/// * `timer` is a valid pointer +/// * `result` is convertible to an [`Out`] +/// * the variables [`daylight`], [`timezone`] and [`tzname`] are not accessed +/// by user code for the duration of the call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn localtime_r(timer: *const time_t, result: *mut tm) -> *mut tm { + // SAFETY: the caller is required to ensure that `timer` is a valid pointer. + let timer_val = unsafe { *timer }; - /* Day number here is possibly negative, remainder will always be - * nonnegative when using Euclidean division */ - let unix_days: time_t = unix_secs.div_euclid(SECS_PER_DAY); + // SAFETY: the caller is required to ensure that `result` is convertible + // to an `Out`. + let result_out = unsafe { Out::nonnull(result) }; - /* In range [0, 86399]. Needs a u32 since this is larger (at least - * theoretically) than the guaranteed range of c_int */ - let secs_of_day: u32 = unix_secs.rem_euclid(SECS_PER_DAY).try_into().unwrap(); + let mut lock = TIMEZONE_LOCK.lock(); - /* Shift origin from 1970-01-01 to 0000-03-01 and find out where we - * are in terms of 400-year eras since then */ - let days_since_origin = unix_days + 719468; - let era = days_since_origin.div_euclid(DAYS_PER_ERA); - let day_of_era = days_since_origin.rem_euclid(DAYS_PER_ERA); - let year_of_era = - (day_of_era - day_of_era / 1460 + day_of_era / 36524 - day_of_era / 146096) / 365; + // SAFETY: the caller is required to ensure that `daylight`, `timezone` + // and `tzname` are not accessed by user code. + unsafe { clear_timezone(&mut lock) }; + if let (Some(std_time), dst_time) = get_localtime(timer_val, result_out) { + // SAFETY: the caller is required to ensure that `daylight`, + // `timezone` and `tzname` are not accessed by user code. + unsafe { set_timezone(&mut lock, &std_time, dst_time) }; + } + result +} - /* "transformed" here refers to dates in a calendar where years - * start on March 1 */ - let year_transformed = year_of_era + 400 * era; // retain large range, don't convert to c_int yet - let day_of_year_transformed: c_int = (day_of_era - - (365 * year_of_era + year_of_era / 4 - year_of_era / 100)) - .try_into() - .unwrap(); - let month_transformed: c_int = (5 * day_of_year_transformed + 2) / 153; +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mktime(timeptr: *mut tm) -> time_t { + let mut lock = TIMEZONE_LOCK.lock(); + unsafe { clear_timezone(&mut lock) }; - // Convert back to calendar with year starting on January 1 - let month: c_int = (month_transformed + 2) % 12; // adapted to 0-based months - let year: time_t = if month < 2 { - year_transformed + 1 - } else { - year_transformed + let year = unsafe { (*timeptr).tm_year } + 1900; + let month = (unsafe { (*timeptr).tm_mon } + 1) as _; + let day = unsafe { (*timeptr).tm_mday } as _; + let hour = unsafe { (*timeptr).tm_hour } as _; + let minute = unsafe { (*timeptr).tm_min } as _; + let second = unsafe { (*timeptr).tm_sec } as _; + + let naive_local = match NaiveDate::from_ymd_opt(year, month, day) + .and_then(|date| date.and_hms_opt(hour, minute, second)) + { + Some(datetime) => datetime, + None => { + platform::ERRNO.set(EOVERFLOW); + return -1; + } }; - /* Subtract 1900 *before* converting down to c_int in order to - * maximize the range of input timestamps that will succeed */ - match c_int::try_from(year - 1900) { - Ok(year_less_1900) => { - let mday: c_int = (day_of_year_transformed - (153 * month_transformed + 2) / 5 + 1) - .try_into() - .unwrap(); - - /* 1970-01-01 was a Thursday. Again, Euclidean division is - * used to ensure a nonnegative remainder (range [0, 6]). */ - let wday: c_int = ((unix_days + 4).rem_euclid(7)).try_into().unwrap(); - - /* Yes, duplicated code for now (to work on non-c_int-values - * so that we are not constrained by the subtraction of - * 1900) */ - let is_leap_year: bool = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); - - /* For dates that are March 1 or later, we can use day-of- - * year in the transformed calendar. For January and - * February, that value is sensitive to whether the previous - * year is a leap year. Therefore, we use the already - * computed date for those two months. */ - let yday: c_int = match month { - 0 => mday - 1, // January - 1 => 31 + mday - 1, // February - _ => day_of_year_transformed + if is_leap_year { 60 } else { 59 }, - }; - - let hour: c_int = (secs_of_day / (60 * 60)).try_into().unwrap(); - let min: c_int = ((secs_of_day / 60) % 60).try_into().unwrap(); - let sec: c_int = (secs_of_day % 60).try_into().unwrap(); - - *result = tm { - tm_sec: sec, - tm_min: min, - tm_hour: hour, - tm_mday: mday, - tm_mon: month, - tm_year: year_less_1900, - tm_wday: wday, - tm_yday: yday, - tm_isdst: 0, - tm_gmtoff: 0, - tm_zone: UTC, - }; - - result - } - Err(_) => { - platform::errno = EOVERFLOW; - core::ptr::null_mut() + let offset = get_offset(unsafe { (*timeptr).tm_gmtoff }).unwrap(); + let tz = time_zone(); + // Create DateTime + let datetime = match offset.from_local_datetime(&naive_local) { + MappedLocalTime::Single(datetime) => datetime, + _ => { + platform::ERRNO.set(EOVERFLOW); + return -1; } + }; + + // Convert to UTC and get timestamp + let tz_datetime = datetime.with_timezone(&tz); + let timestamp = tz_datetime.timestamp(); + + unsafe { ptr::write(timeptr, datetime_to_tm(&tz_datetime)) }; + + // Convert UTC time to local time + let (std_time, dst_time) = match tz.timestamp_opt(timestamp, 0) { + MappedLocalTime::Single(t) => (t, None), + // This variant contains the two possible results, in the order (earliest, latest). + MappedLocalTime::Ambiguous(t1, t2) => (t2, Some(t1)), + MappedLocalTime::None => return timestamp, + }; + { + unsafe { set_timezone(&mut lock, &std_time, dst_time) }; } + + timestamp } -#[no_mangle] -pub unsafe extern "C" fn localtime(clock: *const time_t) -> *mut tm { - localtime_r(clock, &mut TM) +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int { + unsafe { Sys::nanosleep(rqtp, rmtp) } + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub unsafe extern "C" fn localtime_r(clock: *const time_t, t: *mut tm) -> *mut tm { - // TODO: Change tm_isdst, tm_gmtoff, tm_zone - gmtime_r(clock, t) -} - -#[no_mangle] -pub unsafe extern "C" fn mktime(t: *mut tm) -> time_t { - let mut year = (*t).tm_year + 1900; - let mut month = (*t).tm_mon; - let mut day = (*t).tm_mday as i64 - 1; - - let leap = if leap_year(year) { 1 } else { 0 }; - - if year < 1970 { - day = MONTH_DAYS[if leap_year(year) { 1 } else { 0 }][(*t).tm_mon as usize] as i64 - day; - - while year < 1969 { - year += 1; - day += if leap_year(year) { 366 } else { 365 }; - } - - while month < 11 { - month += 1; - day += MONTH_DAYS[leap][month as usize] as i64; - } - - -(day * (60 * 60 * 24) - - (((*t).tm_hour as i64) * (60 * 60) + ((*t).tm_min as i64) * 60 + (*t).tm_sec as i64)) - } else { - while year > 1970 { - year -= 1; - day += if leap_year(year) { 366 } else { 365 }; - } - - while month > 0 { - month -= 1; - day += MONTH_DAYS[leap][month as usize] as i64; - } - - (day * (60 * 60 * 24) - + ((*t).tm_hour as i64) * (60 * 60) - + ((*t).tm_min as i64) * 60 - + (*t).tm_sec as i64) - } -} - -#[no_mangle] -pub extern "C" fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int { - Sys::nanosleep(rqtp, rmtp) -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn strftime( s: *mut c_char, maxsize: size_t, format: *const c_char, timeptr: *const tm, ) -> size_t { - let ret = strftime::strftime( - &mut platform::StringWriter(s as *mut u8, maxsize), - format, - timeptr, - ); - if ret < maxsize { - ret - } else { - 0 - } + let ret = unsafe { + strftime::strftime( + &mut platform::StringWriter(s.cast::(), maxsize), + format, + timeptr, + ) + }; + if ret < maxsize { ret } else { 0 } } -// #[no_mangle] -pub extern "C" fn strptime(buf: *const c_char, format: *const c_char, tm: *mut tm) -> *mut c_char { +// See . +// TODO: needs locale_t +// #[unsafe(no_mangle)] +/*pub extern "C" fn strftime_l(s: *mut char, maxsize: size_t, format: *const c_char, timeptr: *const tm, locale: locale_t) -> size_t { unimplemented!(); -} +}*/ -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn time(tloc: *mut time_t) -> time_t { let mut ts = timespec::default(); - Sys::clock_gettime(CLOCK_REALTIME, &mut ts); + if Sys::clock_gettime(CLOCK_REALTIME, Out::from_mut(&mut ts)).is_ok() {}; // TODO what to do if Err? if !tloc.is_null() { - *tloc = ts.tv_sec + unsafe { *tloc = ts.tv_sec } }; ts.tv_sec } -#[no_mangle] -pub unsafe extern "C" fn timelocal(tm: *mut tm) -> time_t { - //TODO: timezone - timegm(tm) -} - -#[no_mangle] +/// Non-POSIX, see . +#[unsafe(no_mangle)] pub unsafe extern "C" fn timegm(tm: *mut tm) -> time_t { - let mut y = (*tm).tm_year as time_t + 1900; - let mut m = (*tm).tm_mon as time_t + 1; - if m <= 2 { - y -= 1; - m += 12; + let tm_val = unsafe { &mut *tm }; + let dt = match convert_tm_generic(&Utc, tm_val) { + Some(dt) => dt, + None => return -1, + }; + + unsafe { + (*tm).tm_wday = dt.weekday().num_days_from_sunday() as _; + (*tm).tm_yday = dt.ordinal0() as _; // day of year starting at 0 + (*tm).tm_isdst = 0; // UTC does not use DST + (*tm).tm_gmtoff = 0; // UTC offset is zero + (*tm).tm_zone = UTC_STR.as_ptr().cast::(); } - let d = (*tm).tm_mday as time_t; - let h = (*tm).tm_hour as time_t; - let mi = (*tm).tm_min as time_t; - let s = (*tm).tm_sec as time_t; - (365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400 - + 3600 * h - + 60 * mi - + s + + dt.timestamp() } -// #[no_mangle] -pub extern "C" fn timer_create( +/// Non-POSIX, see . +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timelocal(tm: *mut tm) -> time_t { + let tm_val = unsafe { &mut *tm }; + let tz = time_zone(); + let dt = match convert_tm_generic(&tz, tm_val) { + Some(dt) => dt, + None => return -1, + }; + + let tz_name = CString::new(tz.name()).unwrap(); + unsafe { + (*tm).tm_wday = dt.weekday().num_days_from_sunday() as _; + (*tm).tm_yday = dt.ordinal0() as _; // day of year starting at 0 + (*tm).tm_isdst = dt.offset().dst_offset().num_hours() as _; + (*tm).tm_gmtoff = dt.offset().fix().local_minus_utc().into(); + (*tm).tm_zone = tz_name.into_raw().cast(); + } + + dt.timestamp() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timer_create( clock_id: clockid_t, evp: *mut sigevent, timerid: *mut timer_t, ) -> c_int { + if evp.is_null() || timerid.is_null() { + return Err(Errno(EFAULT)).or_minus_one_errno(); + } + let (evp, timerid) = unsafe { (&*evp, Out::nonnull(timerid)) }; + Sys::timer_create(clock_id, evp, timerid) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timer_delete(timerid: timer_t) -> c_int { + if timerid.is_null() { + return Err(Errno(EFAULT)).or_minus_one_errno(); + } + Sys::timer_delete(timerid).map(|()| 0).or_minus_one_errno() +} + +/// See . +// #[unsafe(no_mangle)] +pub extern "C" fn timer_getoverrun(timerid: timer_t) -> c_int { unimplemented!(); } -// #[no_mangle] -pub extern "C" fn timer_delete(timerid: timer_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timer_gettime(timerid: timer_t, value: *mut itimerspec) -> c_int { + if timerid.is_null() || value.is_null() { + return Err(Errno(EFAULT)).or_minus_one_errno(); + } + let value = unsafe { Out::nonnull(value) }; + Sys::timer_gettime(timerid, value) + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn tzset() { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn timer_settime( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timer_settime( timerid: timer_t, flags: c_int, value: *const itimerspec, ovalue: *mut itimerspec, ) -> c_int { - unimplemented!(); + if timerid.is_null() || value.is_null() { + return Err(Errno(EFAULT)).or_minus_one_errno(); + } + let (value, ovalue) = unsafe { (&*value, Out::nullable(ovalue)) }; + Sys::timer_settime(timerid, flags, value, ovalue) + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn timer_gettime(timerid: timer_t, value: *mut itimerspec) -> c_int { - unimplemented!(); +/// ISO C equivalent to [`Sys::clock_gettime`]. +/// +/// The main differences are that this function: +/// * returns `0` on error and `base` on success +/// * only mandates TIME_UTC as a base +/// +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timespec_get(tp: *mut timespec, base: c_int) -> c_int { + let tp = unsafe { Out::nonnull(tp) }; + Sys::clock_gettime(base - 1, tp).map(|()| base).unwrap_or(0) } -// #[no_mangle] -pub extern "C" fn timer_getoverrun(timerid: timer_t) -> c_int { - unimplemented!(); +/// ISO C equivalent to [`Sys::clock_getres`]. +/// +/// The main differences are that this function: +/// * returns `0` on error and `base` on success +/// * only mandates TIME_UTC as a base +#[unsafe(no_mangle)] +pub unsafe extern "C" fn timespec_getres(res: *mut timespec, base: c_int) -> c_int { + let res = unsafe { Out::nullable(res) }; + Sys::clock_getres(base - 1, res).map(|()| base).unwrap_or(0) } -/* -#[no_mangle] -pub extern "C" fn func(args) -> c_int { - unimplemented!(); +/// See . +/// +/// # Safety +/// The caller must ensure that [`daylight`], [`timezone`] and [`tzname`] are +/// not accessed by user code for the duration of the call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn tzset() { + let mut lock = TIMEZONE_LOCK.lock(); + // SAFETY: the caller is required to ensure that `daylight`, `timezone` + // and `tzname` are not accessed by user code. + unsafe { clear_timezone(&mut lock) }; + + let tz = time_zone(); + let datetime = now(); + let (std_time, dst_time) = match tz.from_local_datetime(&datetime) { + MappedLocalTime::Single(t) => (t, None), + // This variant contains the two possible results, in the order (earliest, latest). + MappedLocalTime::Ambiguous(t1, t2) => (t2, Some(t1)), + MappedLocalTime::None => return, + }; + + // SAFETY: the caller is required to ensure that `daylight`, `timezone` + // and `tzname` are not accessed by user code. + unsafe { set_timezone(&mut lock, &std_time, dst_time) } +} + +fn convert_tm_generic(tz: &Tz, tm_val: &tm) -> Option> { + // Adjust fields: tm_year is years since 1900; tm_mon is 0-indexed. + let year = tm_val.tm_year + 1900; + let month = tm_val.tm_mon + 1; // convert to 1-indexed + let day = tm_val.tm_mday; + let hour = tm_val.tm_hour; + let minute = tm_val.tm_min; + let second = tm_val.tm_sec; + + match tz.with_ymd_and_hms( + year, + month as u32, + day as u32, + hour as u32, + minute as u32, + second as u32, + ) { + MappedLocalTime::Single(dt) => Some(dt), + MappedLocalTime::Ambiguous(dt1, _dt2) => Some(dt1), // choose the earliest value + _ => None, + } +} + +/// # Safety +/// The caller must ensure that `daylight`, `timezone` and `tzname` are not +/// accessed by user code for the duration of the call (relibc functions are +/// required to hold `TIMEZONE_LOCK` when accessing these). +unsafe fn clear_timezone(guard: &mut MutexGuard<'_, (Option, Option)>) { + guard.0 = None; + guard.1 = None; + + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let daylight_mut = unsafe { &mut *(&raw mut daylight) }; + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let timezone_mut = unsafe { &mut *(&raw mut timezone) }; + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let tzname_mut = unsafe { &mut *(&raw mut tzname) }; + + (*tzname_mut).0[0] = ptr::null_mut(); + (*tzname_mut).0[1] = ptr::null_mut(); + *timezone_mut = 0; + *daylight_mut = 0; +} + +#[inline(always)] +fn get_system_time_zone<'a>() -> Option<&'a str> { + // Resolve the symlink for localtime + const BSIZE: size_t = 100; + let mut buffer: [u8; BSIZE] = [0; BSIZE]; + + #[cfg(not(target_os = "redox"))] + let (localtime, prefix) = (c"/etc/localtime", "/usr/share/zoneinfo/"); + + #[cfg(target_os = "redox")] + let (localtime, prefix) = (c"/etc/localtime", "/usr/share/zoneinfo/"); + + if unsafe { readlink(localtime.as_ptr().cast(), buffer.as_mut_ptr().cast(), BSIZE) } == -1 { + return None; + } + + let path = unsafe { CStr::from_ptr(buffer.as_mut_ptr().cast()) }; + + if let Ok(tz_name) = path.to_str() + && let Some(stripped) = tz_name.strip_prefix(prefix) + { + return Some(stripped); + } + + None +} + +fn get_current_time_zone<'a>() -> &'a str { + // Check the `TZ` environment variable + let tz_env = unsafe { getenv(c"TZ".as_ptr().cast()) }; + if !tz_env.is_null() + && let Ok(tz) = unsafe { CStr::from_ptr(tz_env) }.to_str() + { + return tz; + } + + // Fallback to the system's default time zone + if let Some(tz) = get_system_time_zone() { + return tz; + } + + // If all else fails, use UTC + "UTC" +} + +#[inline(always)] +fn time_zone() -> Tz { + get_current_time_zone().parse().unwrap_or(Tz::UTC) +} + +#[inline(always)] +fn now() -> NaiveDateTime { + let mut now = timespec::default(); + if Sys::clock_gettime(CLOCK_REALTIME, Out::from_mut(&mut now)).is_ok() {}; // TODO what to do if Err? + DateTime::from_timestamp(now.tv_sec, now.tv_nsec as _) + .unwrap_or_default() + .naive_local() +} + +#[inline(always)] +fn get_localtime( + timer: time_t, + mut result: Out, +) -> (Option>, Option>) { + let tz = time_zone(); + + // Convert UTC time to local time + let (std_time, dst_time) = match tz.timestamp_opt(timer, 0) { + MappedLocalTime::Single(t) => (Some(t), None), + // This variant contains the two possible results, in the order (earliest, latest). + MappedLocalTime::Ambiguous(t1, t2) => (Some(t2), Some(t1)), + MappedLocalTime::None => return (None, None), + }; + + let localtime = datetime_to_tm(&std_time.unwrap()); + result.write(localtime); + (std_time, dst_time) +} + +fn datetime_to_tm(local_time: &DateTime) -> tm { + let tz = local_time.timezone().name(); + let tz = tz.strip_prefix("Etc/").unwrap_or(tz); + + let mut t = blank_tm(); + // Populate the `tm` structure + t.tm_sec = local_time.second() as _; + t.tm_min = local_time.minute() as _; + t.tm_hour = local_time.hour() as _; + t.tm_mday = local_time.day() as _; + t.tm_mon = local_time.month0() as _; // 0-based month + t.tm_year = (local_time.year() - 1900) as _; // Years since 1900 + t.tm_wday = local_time.weekday().num_days_from_sunday() as _; + t.tm_yday = local_time.ordinal0() as _; // 0-based day of year + + let offset = local_time.offset(); + t.tm_isdst = offset.dst_offset().num_hours() as _; + // Get the UTC offset in seconds + t.tm_gmtoff = offset.fix().local_minus_utc().into(); + + let tm_zone = { + let mut timezone_names = TIMEZONE_NAMES.lock(); + timezone_names.get_or_init(BTreeSet::new); + let cstr = CString::new(tz).unwrap(); + timezone_names.get_mut().unwrap().insert(cstr.clone()); + timezone_names.get().unwrap().get(&cstr).unwrap().as_ptr() + }; + + t.tm_zone = tm_zone.cast(); + t +} + +/// # Safety +/// The caller must ensure that `daylight`, `timezone` and `tzname` are not +/// accessed by user code for the duration of the call (relibc functions are +/// required to hold `TIMEZONE_LOCK` when accessing these). +unsafe fn set_timezone( + guard: &mut MutexGuard<'_, (Option, Option)>, + std: &DateTime, + dst: Option>, +) { + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let daylight_mut = unsafe { &mut *(&raw mut daylight) }; + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let timezone_mut = unsafe { &mut *(&raw mut timezone) }; + // SAFETY: the caller is required to ensure access exclusively for the + // holder of `TIMEZONE_LOCK`. + let tzname_mut = unsafe { &mut *(&raw mut tzname) }; + + let ut_offset = std.offset(); + + guard.0 = Some(CString::new(ut_offset.abbreviation().expect("Wrong timezone")).unwrap()); + tzname_mut.0[0] = guard.0.as_ref().unwrap().as_ptr().cast_mut(); + + match dst { + Some(dst) => { + guard.1 = + Some(CString::new(dst.offset().abbreviation().expect("Wrong timezone")).unwrap()); + tzname_mut.0[1] = guard.1.as_ref().unwrap().as_ptr().cast_mut(); + *daylight_mut = 1; + } + None => { + guard.1 = None; + tzname_mut.0[1] = guard.0.as_ref().unwrap().as_ptr().cast_mut(); + *daylight_mut = 0; + } + } + + *timezone_mut = -c_long::from(ut_offset.fix().local_minus_utc()); +} + +#[inline(always)] +pub const fn get_offset(off: c_long) -> Option { + if off < 0 { + FixedOffset::west_opt(off as _) + } else { + FixedOffset::east_opt(off as _) + } +} + +const fn blank_tm() -> tm { + tm { + tm_year: 0, + tm_mon: 0, + tm_mday: 0, + tm_hour: 0, + tm_min: 0, + tm_sec: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: -1, + tm_gmtoff: 0, + tm_zone: ptr::null_mut(), + } +} + +pub(crate) fn timespec_realtime_to_monotonic(abstime: timespec) -> Result { + let mut realtime = timespec::default(); + unsafe { clock_gettime(CLOCK_REALTIME, &raw mut realtime) }; + let mut monotonic = timespec::default(); + unsafe { clock_gettime(CLOCK_MONOTONIC, &raw mut monotonic) }; + let Some(delta) = timespec::subtract(abstime, realtime) else { + return Err(Errno(ETIMEDOUT)); + }; + let Some(relative) = timespec::add(monotonic, delta) else { + return Err(Errno(ENOMEM)); + }; + Ok(relative) } -*/ diff --git a/src/header/time/redox.rs b/src/header/time/redox.rs index df1e3cdfd1..b19d228a7b 100644 --- a/src/header/time/redox.rs +++ b/src/header/time/redox.rs @@ -1,4 +1,4 @@ -use crate::platform::types::*; +use crate::platform::types::c_int; pub const CLOCK_REALTIME: c_int = 1; pub const CLOCK_MONOTONIC: c_int = 4; diff --git a/src/header/time/strftime.rs b/src/header/time/strftime.rs index 23d9fb5caa..49ab8e48fc 100644 --- a/src/header/time/strftime.rs +++ b/src/header/time/strftime.rs @@ -1,10 +1,52 @@ +// `strftime` implementation. +// +// See . + use alloc::string::String; -use crate::platform::{self, types::*, WriteByte}; - use super::tm; +use crate::{ + c_str::CStr, + platform::{ + self, WriteByte, + types::{c_char, c_int, size_t}, + }, +}; +// We use the langinfo constants +use crate::header::langinfo::{ + ABDAY_1, + ABMON_1, + AM_STR, + DAY_1, + MON_1, + PM_STR, + // TODO : other constants if needed + nl_item, + nl_langinfo, +}; + +/// A helper that calls `nl_langinfo(item)` and converts the returned pointer +/// into a `&str`. If it fails or is null, returns an empty string "". +unsafe fn langinfo_to_str(item: nl_item) -> &'static str { + use core::ffi::CStr; + + let ptr = unsafe { nl_langinfo(item) }; + if ptr.is_null() { + return ""; + } + unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or_default() +} + +/// See . +/// +/// Formats time data according to the given `format` string. +/// +/// Use `langinfo` for locale-based day/month names, +/// but still hard-codes other aspects of the "C" locale (like numeric/date +/// formats) and ignores `%E` / `%O` variations. pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const tm) -> size_t { + /// Helper that actually parses the format string and writes output. pub unsafe fn inner_strftime( w: &mut W, mut format: *const c_char, @@ -22,11 +64,11 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const } }}; (recurse $fmt:expr) => {{ - let mut fmt = String::with_capacity($fmt.len() + 1); - fmt.push_str($fmt); - fmt.push('\0'); + let mut tmp = String::with_capacity($fmt.len() + 1); + tmp.push_str($fmt); + tmp.push('\0'); - if !inner_strftime(w, fmt.as_ptr() as *mut c_char, t) { + if unsafe { !inner_strftime(w, tmp.as_ptr() as *mut c_char, t) } { return false; } }}; @@ -36,106 +78,227 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const } }}; ($fmt:expr, $($args:expr),+) => {{ - // Would use write!() if I could get the length written + // Could use write!() if we didn't need the exact count if write!(w, $fmt, $($args),+).is_err() { return false; } }}; } - const WDAYS: [&str; 7] = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ]; - const MONTHS: [&str; 12] = [ - "January", - "Febuary", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - while *format != 0 { - if *format as u8 != b'%' { - w!(byte * format as u8); - format = format.offset(1); + while unsafe { *format } != 0 { + // If the character isn't '%', just copy it out. + if unsafe { *format } as u8 != b'%' { + w!(byte unsafe { *format } as u8); + format = unsafe { format.offset(1) }; continue; } - format = format.offset(1); + // Skip '%' + format = unsafe { format.offset(1) }; - if *format as u8 == b'E' || *format as u8 == b'O' { - // Ignore because these do nothing without locale - format = format.offset(1); + // POSIX says '%E' and '%O' can modify numeric formats for locales, + // but we ignore them in this minimal "C" locale approach. + if unsafe { *format } as u8 == b'E' || unsafe { *format } as u8 == b'O' { + format = unsafe { format.offset(1) }; } - match *format as u8 { + match unsafe { *format } as u8 { + // Literal '%' b'%' => w!(byte b'%'), + + // Newline and tab expansions b'n' => w!(byte b'\n'), b't' => w!(byte b'\t'), - b'a' => w!(&WDAYS[(*t).tm_wday as usize][..3]), - b'A' => w!(WDAYS[(*t).tm_wday as usize]), - b'b' | b'h' => w!(&MONTHS[(*t).tm_mon as usize][..3]), - b'B' => w!(MONTHS[(*t).tm_mon as usize]), + + // Abbreviated weekday name: %a + b'a' => { + // `ABDAY_1 + tm_wday` is the correct langinfo ID for abbreviated weekdays + let s = unsafe { langinfo_to_str(ABDAY_1 + (*t).tm_wday) }; + w!(s); + } + + // Full weekday name: %A + b'A' => { + // `DAY_1 + tm_wday` is the correct langinfo ID for full weekdays + let s = unsafe { langinfo_to_str(DAY_1 + (*t).tm_wday) }; + w!(s); + } + + // Abbreviated month name: %b or %h + b'b' | b'h' => { + let s = unsafe { langinfo_to_str(ABMON_1 + (*t).tm_mon) }; + w!(s); + } + + // Full month name: %B + b'B' => { + let s = unsafe { langinfo_to_str(MON_1 + (*t).tm_mon) }; + w!(s); + } + + // Century: %C b'C' => { - let mut year = (*t).tm_year / 100; - // Round up - if (*t).tm_year % 100 != 0 { + let mut year = unsafe { (*t).tm_year } / 100; + if unsafe { (*t).tm_year } % 100 != 0 { year += 1; } w!("{:02}", year + 19); } - b'd' => w!("{:02}", (*t).tm_mday), + + // Day of month: %d + b'd' => w!("{:02}", unsafe { (*t).tm_mday }), + + // %D => same as %m/%d/%y b'D' => w!(recurse "%m/%d/%y"), - b'e' => w!("{:2}", (*t).tm_mday), + + // Day of month, space-padded: %e + b'e' => w!("{:2}", unsafe { (*t).tm_mday }), + + // ISO 8601 date: %F => %Y-%m-%d b'F' => w!(recurse "%Y-%m-%d"), - b'H' => w!("{:02}", (*t).tm_hour), - b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1), - b'j' => w!("{:03}", (*t).tm_yday), - b'k' => w!("{:2}", (*t).tm_hour), - b'l' => w!("{:2}", ((*t).tm_hour + 12 - 1) % 12 + 1), - b'm' => w!("{:02}", (*t).tm_mon + 1), - b'M' => w!("{:02}", (*t).tm_min), - b'p' => w!(if (*t).tm_hour < 12 { "AM" } else { "PM" }), - b'P' => w!(if (*t).tm_hour < 12 { "am" } else { "pm" }), + + // Hour (00-23): %H + b'H' => w!("{:02}", unsafe { (*t).tm_hour }), + + // Hour (01-12): %I + b'I' => w!("{:02}", (unsafe { (*t).tm_hour } + 12 - 1) % 12 + 1), + + // Day of year: %j + b'j' => w!("{:03}", unsafe { (*t).tm_yday } + 1), + + // etc. + b'k' => w!("{:2}", unsafe { (*t).tm_hour }), + b'l' => w!("{:2}", (unsafe { (*t).tm_hour } + 12 - 1) % 12 + 1), + b'm' => w!("{:02}", unsafe { (*t).tm_mon } + 1), + b'M' => w!("{:02}", unsafe { (*t).tm_min }), + + // AM/PM (uppercase): %p + b'p' => { + // Get "AM" / "PM" from langinfo + if unsafe { (*t).tm_hour } < 12 { + w!(unsafe { langinfo_to_str(AM_STR) }); + } else { + w!(unsafe { langinfo_to_str(PM_STR) }); + } + } + + // am/pm (lowercase): %P + b'P' => { + // Convert the AM_STR / PM_STR to lowercase + if unsafe { (*t).tm_hour } < 12 { + let am = unsafe { langinfo_to_str(AM_STR) }.to_ascii_lowercase(); + w!(&am); + } else { + let pm = unsafe { langinfo_to_str(PM_STR) }.to_ascii_lowercase(); + w!(&pm); + } + } + + // 12-hour clock with seconds + AM/PM: %r => %I:%M:%S %p b'r' => w!(recurse "%I:%M:%S %p"), + + // 24-hour clock without seconds: %R => %H:%M b'R' => w!(recurse "%H:%M"), - // Nothing is modified in mktime, but the C standard of course requires a mutable pointer ._. - b's' => w!("{}", super::mktime(t as *mut tm)), - b'S' => w!("{:02}", (*t).tm_sec), + + // Seconds since the Epoch: %s => calls mktime() to convert tm to time_t + b's' => w!("{}", unsafe { super::mktime(t.cast_mut()) }), + + // Seconds (00-60): %S (unchanged) + b'S' => w!("{:02}", unsafe { (*t).tm_sec }), + + // 24-hour clock with seconds: %T => %H:%M:%S b'T' => w!(recurse "%H:%M:%S"), - b'u' => w!("{}", ((*t).tm_wday + 7 - 1) % 7 + 1), - b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7), - b'w' => w!("{}", (*t).tm_wday), - b'W' => w!("{}", ((*t).tm_yday + 7 - ((*t).tm_wday + 6) % 7) / 7), - b'y' => w!("{:02}", (*t).tm_year % 100), - b'Y' => w!("{}", (*t).tm_year + 1900), - b'z' => w!("+0000"), // TODO - b'Z' => w!("UTC"), // TODO + + // Weekday (1-7, Monday=1): %u + b'u' => w!("{}", (unsafe { (*t).tm_wday } + 7 - 1) % 7 + 1), + + // Sunday-based week of year: %U + b'U' => w!( + "{}", + (unsafe { (*t).tm_yday } + 7 - unsafe { (*t).tm_wday }) / 7 + ), + + // ISO-8601 week of year + b'V' => w!("{}", week_of_year(unsafe { &*t })), + + // Weekday (0-6, Sunday=0): %w + b'w' => w!("{}", unsafe { (*t).tm_wday }), + + // Monday-based week of year: %W + b'W' => w!( + "{}", + (unsafe { (*t).tm_yday } + 7 - (unsafe { (*t).tm_wday } + 6) % 7) / 7 + ), + + // Last two digits of year: %y + b'y' => w!("{:02}", unsafe { (*t).tm_year } % 100), + + // Full year: %Y + b'Y' => w!("{}", unsafe { (*t).tm_year } + 1900), + + // Timezone offset: %z + b'z' => { + let offset = unsafe { (*t).tm_gmtoff }; + let (sign, offset) = if offset < 0 { + ('-', -offset) + } else { + ('+', offset) + }; + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); + w!("{}{:02}{:02}", sign, hour, min) + } + + // Timezone name: %Z + b'Z' => w!( + "{}", + unsafe { CStr::from_ptr((*t).tm_zone) }.to_str().unwrap() + ), + + // Date+time+TZ: %+ b'+' => w!(recurse "%a %b %d %T %Z %Y"), + + // Unrecognized format specifier => fail _ => return false, } - format = format.offset(1); + // Move past the format specifier + format = unsafe { format.offset(1) }; } true } - let mut w = platform::CountingWriter::new(w); - if !inner_strftime(&mut w, format, t) { + // Wrap the writer in a CountingWriter to return how many bytes were written. + let mut cw = platform::CountingWriter::new(w); + if unsafe { !inner_strftime(&mut cw, format, t) } { return 0; } - - w.written + cw.written +} + +/// Calculate number of weeks in a year as defined by ISO 8601 +/// +/// ## Source +/// https://en.wikipedia.org/wiki/ISO_week_date +fn weeks_per_year(year: c_int) -> c_int { + let year = f64::from(year); + let p_y = (year + (year / 4.) - (year / 100.) + (year / 400.)) as c_int % 7; + if p_y == 4 { 53 } else { 52 } +} + +/// Calculate the week of the year accounting for leap weeks (ISO 8601) +/// +/// ## Source +/// https://en.wikipedia.org/wiki/ISO_week_date +fn week_of_year(time: &tm) -> c_int { + let week = (10 + time.tm_yday - time.tm_wday) / 7; + + if week <= 1 { + weeks_per_year(time.tm_year - 1) + } else if week > weeks_per_year(time.tm_year) { + 1 + } else { + week + } } diff --git a/src/header/time/strptime.rs b/src/header/time/strptime.rs new file mode 100644 index 0000000000..f49c5b54b6 --- /dev/null +++ b/src/header/time/strptime.rs @@ -0,0 +1,593 @@ +// `strptime` implementation. +// +// See . + +use crate::header::time::tm; +use alloc::string::String; +use core::{ + ffi::{CStr, c_char, c_int, c_void}, + ptr, + ptr::NonNull, + str, +}; + +/// For convenience, we define some helper constants for the C-locale. +const SHORT_DAYS: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const LONG_DAYS: [&str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; +const SHORT_MONTHS: [&str; 12] = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", +]; +const LONG_MONTHS: [&str; 12] = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strptime( + buf: *const c_char, + format: *const c_char, + tm: *mut tm, +) -> *mut c_char { + // Validate inputs + let buf_ptr = if let Some(ptr) = NonNull::new(buf.cast::().cast_mut()) { + ptr + } else { + return ptr::null_mut(); + }; + // + let fmt_ptr = if let Some(ptr) = NonNull::new(format.cast::().cast_mut()) { + ptr + } else { + return ptr::null_mut(); + }; + + let tm_ptr = if let Some(ptr) = NonNull::new(tm) { + ptr + } else { + return ptr::null_mut(); + }; + + // Convert raw pointers into slices/strings. + let input_str = unsafe { + if buf.is_null() { + return ptr::null_mut(); + } + match CStr::from_ptr(buf).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), // Not a valid UTF-8 + } + }; + + let fmt_str = unsafe { + if format.is_null() { + return ptr::null_mut(); + } + match CStr::from_ptr(format).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), // Not a valid UTF-8 + } + }; + + // We parse the format specifiers in a loop + let mut fmt_chars = fmt_str.chars().peekable(); + let mut index_in_input = 0; + + while let Some(fc) = fmt_chars.next() { + if fc != '%' { + // If it's a normal character, we expect it to match exactly in input + if input_str.len() <= index_in_input { + return ptr::null_mut(); // input ended too soon + } + let in_char = input_str.as_bytes()[index_in_input] as char; + if in_char != fc { + // mismatch + return ptr::null_mut(); + } + index_in_input += 1; + continue; + } + + // If we see '%', read the next character + let Some(spec) = fmt_chars.next() else { + // format string ended abruptly after '%' + return ptr::null_mut(); + }; + + // POSIX says `%E` or `%O` are modified specifiers for locale. + // We will skip them if they appear (like strftime does) and read the next char. + let final_spec = if spec == 'E' || spec == 'O' { + match fmt_chars.next() { + Some(ch) => ch, + None => return ptr::null_mut(), + } + } else { + spec + }; + + // Handle known specifiers + match final_spec { + // Whitespace: %n or %t + 'n' | 't' => { + // Skip over any whitespace in the input + while index_in_input < input_str.len() + && input_str.as_bytes()[index_in_input].is_ascii_whitespace() + { + index_in_input += 1; + } + } + + // Literal % => "%%" + '%' => { + if index_in_input >= input_str.len() + || input_str.as_bytes()[index_in_input] as char != '%' + { + return ptr::null_mut(); + } + index_in_input += 1; + } + + // Day of Month: %d / %e + 'd' | 'e' => { + // parse a 2-digit day (with or without leading zero) + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + unsafe { + (*tm).tm_mday = val as c_int; + // Day of month is limited to [1,31] according to the standard + if (*tm).tm_mday < 1 || (*tm).tm_mday > 31 { + return ptr::null_mut(); + } + } + index_in_input += len; + } + + // Month: %m + 'm' => { + // parse a 2-digit month + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // tm_mon is 0-based (0 = Jan, 1 = Feb,...) + unsafe { + (*tm).tm_mon = (val as c_int) - 1; + if (*tm).tm_mon < 0 || (*tm).tm_mon > 11 { + return ptr::null_mut(); + } + } + index_in_input += len; + } + + // Year without century: %y + 'y' => { + // parse a 2-digit year + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + // According to POSIX, %y in strptime is [00,99], and the "year" is 1900..1999 for [00..99], + // but the standard says: "values in [69..99] refer to 1969..1999, [00..68] => 2000..2068" + let fullyear = if val >= 69 { val + 1900 } else { val + 2000 }; + unsafe { + (*tm).tm_year = (fullyear - 1900) as c_int; + } + index_in_input += len; + } + + // Year with century: %Y + 'Y' => { + // parse up to 4-digit (or more) year + // We allow more than 4 digits if needed + let (val, len) = match parse_int(&input_str[index_in_input..], 4, true) { + Some(v) => v, + None => return ptr::null_mut(), + }; + unsafe { + (*tm).tm_year = (val as c_int) - 1900; + } + index_in_input += len; + } + + // Hour (00..23): %H + 'H' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 23 { + return ptr::null_mut(); + } + unsafe { + (*tm).tm_hour = val as c_int; + } + index_in_input += len; + } + + // Hour (01..12): %I + 'I' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if !(1..=12).contains(&val) { + return ptr::null_mut(); + } + unsafe { + (*tm).tm_hour = val as c_int; + } + // We’ll interpret AM/PM with %p if it appears later + index_in_input += len; + } + + // Minute (00..59): %M + 'M' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 59 { + return ptr::null_mut(); + } + unsafe { + (*tm).tm_min = val as c_int; + } + index_in_input += len; + } + + // Seconds (00..60): %S + 'S' => { + let (val, len) = match parse_int(&input_str[index_in_input..], 2, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if val > 60 { + return ptr::null_mut(); + } + unsafe { + (*tm).tm_sec = val as c_int; + } + index_in_input += len; + } + + // AM/PM: %p + 'p' => { + // Parse either "AM" or "PM" (no case-sensitive) + // We'll read up to 2 or 3 letters from input ("AM", "PM") + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_am_pm(leftover) { + Some((is_pm, used)) => { + if unsafe { (*tm).tm_hour } == 12 { + // 12 AM => 00:xx, 12 PM => 12:xx + unsafe { + (*tm).tm_hour = if is_pm { 12 } else { 0 }; + } + } else { + // 1..11 AM => 1..11, 1..11 PM => 13..23 + if is_pm { + unsafe { + (*tm).tm_hour += 12; + } + } + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + // Weekday Name: %a/%A + 'a' => { + // Abbreviated day name (Sun..Sat) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, true) { + Some((wday, used)) => { + unsafe { + (*tm).tm_wday = wday as c_int; + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'A' => { + // Full day name (Sunday..Saturday) + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_weekday(leftover, false) { + Some((wday, used)) => { + unsafe { + (*tm).tm_wday = wday as c_int; + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + // Month Name: %b/%B/%h + 'b' | 'h' => { + // Abbreviated month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, true) { + Some((mon, used)) => { + unsafe { + (*tm).tm_mon = mon as c_int; + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + 'B' => { + // Full month name + let leftover = &input_str[index_in_input..]; + let parsed_len = match parse_month(leftover, false) { + Some((mon, used)) => { + unsafe { + (*tm).tm_mon = mon as c_int; + } + used + } + None => return ptr::null_mut(), + }; + index_in_input += parsed_len; + } + + // Day of year: %j + 'j' => { + // parse 3-digit day of year [001..366] + let (val, len) = match parse_int(&input_str[index_in_input..], 3, false) { + Some(v) => v, + None => return ptr::null_mut(), + }; + if !(1..=366).contains(&val) { + return ptr::null_mut(); + } + // store in tm_yday + unsafe { + (*tm).tm_yday = (val - 1) as c_int; + } + index_in_input += len; + } + + // Date shortcuts: %D, %F, etc. + 'D' => { + // Equivalent to "%m/%d/%y" + // We can do a mini strptime recursion or manually parse + // For simplicity, we'll do it inline here + let subfmt = "%m/%d/%y"; + let used = + match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + 'F' => { + // Equivalent to "%Y-%m-%d" + let subfmt = "%Y-%m-%d"; + let used = + match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + 'T' => { + // Equivalent to %H:%M:%S + let subfmt = "%H:%M:%S"; + let used = + match unsafe { apply_subformat(&input_str[index_in_input..], subfmt, tm) } { + Some(v) => v, + None => return ptr::null_mut(), + }; + index_in_input += used; + } + + // TODO : not implemented: %x, %X, %c, %r, %R, etc. + // Hint : if you want to implement these, do similarly to %D / %F (or parse manually) + 'x' | 'X' | 'c' | 'r' | 'R' => { + // Return NULL if we don’t want to accept them : + return ptr::null_mut(); + } + + // Timezone: %Z or %z + 'Z' | 'z' => { + // Full/abbrev time zone name or numeric offset + // Implementation omitted. Real support is quite complicated. + return ptr::null_mut(); + } + + _ => { + // We do not recognize this specifier + return ptr::null_mut(); + } + } + } + + // If we got here, parsing was successful. Return pointer to the + // next unparsed character in `buf`. + let ret_ptr = unsafe { buf.add(index_in_input) }; + ret_ptr.cast_mut() +} + +// Helper / Parsing Logic + +/// Parse an integer from the beginning of `input_str`. +/// +/// - `width` is the maximum number of digits to parse +/// - `allow_variable_width` indicates if we can parse fewer digits +/// (e.g., `%Y` can have more than 4 digits, but also might parse "2023" or "12345"). +fn parse_int(input: &str, width: usize, allow_variable: bool) -> Option<(i32, usize)> { + let mut val = 0i32; + let chars = input.chars(); + let mut count = 0; + + for c in chars { + if !c.is_ascii_digit() { + break; + } + + // Check for integer overflow + val = val + .checked_mul(10)? + .checked_add(i32::from(c as u8 - b'0'))?; + + count += 1; + if count == width && !allow_variable { + break; + } + } + + if count == 0 { None } else { Some((val, count)) } +} + +/// Handle AM/PM. Returns (is_pm, length_consumed). +/// Accepts "AM", "am", "PM", "pm" case-insensitively. +fn parse_am_pm(s: &str) -> Option<(bool, usize)> { + let trimmed = s.trim_start(); + // Amount of whitespace skipped; can be 0 + let diff = s.len() - trimmed.len(); + let s = trimmed.get(0..2)?; + + if s.eq_ignore_ascii_case("AM") { + return Some((false, diff + 2)); + } + if s.eq_ignore_ascii_case("PM") { + return Some((true, diff + 2)); + } + None +} + +/// Parse a weekday name from `s`. +/// - if `abbrev == true`, match short forms: "Mont".."Sun" +/// - otherwise, match "Monday".."Sunday" +/// +/// Return (weekday_index, length_consumed). +fn parse_weekday(s: &str, abbrev: bool) -> Option<(usize, usize)> { + let list = if abbrev { &SHORT_DAYS } else { &LONG_DAYS }; + for (i, name) in list.iter().enumerate() { + if s.len() >= name.len() + && s.get(0..name.len()) + .is_some_and(|sub| sub.eq_ignore_ascii_case(name)) + { + return Some((i, name.len())); + } + } + None +} + +/// Parse a month name from `s`. +/// - If `abbrev == true`, match short forms: "Jan".."Dec" +/// - Otherwise, match "January".."December" +/// +/// Return (month_index, length_consumed). +fn parse_month(s: &str, abbrev: bool) -> Option<(usize, usize)> { + let list = if abbrev { &SHORT_MONTHS } else { &LONG_MONTHS }; + for (i, name) in list.iter().enumerate() { + if s.len() >= name.len() + && s.get(0..name.len()) + .is_some_and(|sub| sub.eq_ignore_ascii_case(name)) + { + return Some((i, name.len())); + } + } + None +} + +/// Apply a small subformat (like "%m/%d/%y" or "%Y-%m-%d") to `input`. +/// Return how many characters of `input` were consumed or None on error. +unsafe fn apply_subformat(input: &str, subfmt: &str, tm: *mut tm) -> Option { + // We'll do a temporary strptime call on a substring. + // Then we see how many chars it consumed. If that call fails, we return None. + // Otherwise, we return the count. + + // Convert `input` to a null-terminated buffer temporarily + let mut tmpbuf = String::with_capacity(input.len() + 1); + tmpbuf.push_str(input); + tmpbuf.push('\0'); + + let mut tmpfmt = String::with_capacity(subfmt.len() + 1); + tmpfmt.push_str(subfmt); + tmpfmt.push('\0'); + + // We need a copy of the tm, so if partial parse fails, we don't override. + let old_tm = unsafe { ptr::read(tm) }; // backup + + let consumed_ptr = unsafe { + strptime( + tmpbuf.as_ptr().cast::(), + tmpfmt.as_ptr().cast::(), + tm, + ) + }; + + if consumed_ptr.is_null() { + // revert + unsafe { + *tm = old_tm; + } + return None; + } + + // consumed_ptr - tmpbuf.as_ptr() => # of bytes consumed + let diff = (consumed_ptr as usize) - (tmpbuf.as_ptr() as usize); + Some(diff) +} + +#[cfg(test)] +mod tests { + use super::parse_am_pm; + + #[test] + fn am_pm_parser_works() { + let am = "am"; + let am_expected = Some((false, 2)); + assert_eq!(am_expected, parse_am_pm(am)); + + let pm = "pm"; + let pm_expected = Some((true, 2)); + assert_eq!(pm_expected, parse_am_pm(pm)); + + let am_caps = "AM"; + assert_eq!(am_expected, parse_am_pm(am_caps)); + + let pm_caps = "PM"; + assert_eq!(pm_expected, parse_am_pm(pm_caps)); + + let am_weird = "aM"; + assert_eq!(am_expected, parse_am_pm(am_weird)); + + let am_prefix = " \tam"; + let am_prefix_expected = Some((false, 11)); + assert_eq!(am_prefix_expected, parse_am_pm(am_prefix)); + + let pm_spaces = " pm "; + let pm_spaces_expected = Some((true, 10)); + assert_eq!(pm_spaces_expected, parse_am_pm(pm_spaces)); + } +} diff --git a/src/header/unistd/brk.rs b/src/header/unistd/brk.rs index ab97d49a75..65d6544171 100644 --- a/src/header/unistd/brk.rs +++ b/src/header/unistd/brk.rs @@ -1,42 +1,58 @@ use core::ptr; use crate::{ + error::ResultExtPtrMut, header::errno::ENOMEM, - platform::{self, types::*, Pal, Sys}, + platform::{ + self, Pal, Sys, + types::{c_int, c_void, intptr_t}, + }, }; static mut BRK: *mut c_void = ptr::null_mut(); -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `brk()` function was marked legacy in the System Interface & Headers +/// Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn brk(addr: *mut c_void) -> c_int { - BRK = Sys::brk(addr); + unsafe { BRK = Sys::brk(addr).or_errno_null_mut() }; - if BRK < addr { - platform::errno = ENOMEM; + if unsafe { BRK } < addr { + platform::ERRNO.set(ENOMEM); return -1; } 0 } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `sbrk()` function was marked legacy in the System Interface & Headers +/// Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] pub unsafe extern "C" fn sbrk(incr: intptr_t) -> *mut c_void { - if BRK.is_null() { - BRK = Sys::brk(ptr::null_mut()); + if unsafe { BRK }.is_null() { + unsafe { BRK = Sys::brk(ptr::null_mut()).or_errno_null_mut() }; } - let old_brk = BRK; + let old_brk = unsafe { BRK }; if incr != 0 { - let addr = old_brk.offset(incr); + let addr = unsafe { old_brk.offset(incr) }; - BRK = Sys::brk(addr); + unsafe { BRK = Sys::brk(addr).or_errno_null_mut() }; - if BRK < addr { - platform::errno = ENOMEM; + if unsafe { BRK } < addr { + platform::ERRNO.set(ENOMEM); return -1isize as *mut c_void; } } - old_brk as *mut c_void + old_brk.cast::() } diff --git a/src/header/unistd/cbindgen.toml b/src/header/unistd/cbindgen.toml index 610d6d21fe..187a0d64f0 100644 --- a/src/header/unistd/cbindgen.toml +++ b/src/header/unistd/cbindgen.toml @@ -1,6 +1,22 @@ -sys_includes = ["stddef.h", "stdint.h", "sys/types.h"] +sys_includes = ["stddef.h", "stdint.h", "sys/types.h", "features.h", "fcntl.h"] include_guard = "_RELIBC_UNISTD_H" -trailer = "#include \n#include " +after_includes = """ + +#define _POSIX_VERSION 200809L +#define _POSIX_BARRIERS 202405L +#define _POSIX_MONOTONIC_CLOCK 200112L +#define _POSIX_REALTIME_SIGNALS 202405L +#define _POSIX_SEMAPHORES 200112L +#define _POSIX_SHELL 1 +#define _POSIX_SHARED_MEMORY_OBJECTS 200112L +#define _POSIX_THREADS 202405L +#define _POSIX_THREAD_ATTR_STACKADDR 202405L +#define _POSIX_THREAD_ATTR_STACKSIZE 202405L +#define _POSIX_TIMEOUTS 202405L +#define _POSIX_TIMERS 202405L +#define _XOPEN_SHM 1 +#define _XOPEN_VERSION 700L +""" language = "C" style = "Tag" no_includes = true @@ -8,7 +24,3 @@ cpp_compat = true [enum] prefix_with_name = true - -[defines] -"target_os = linux" = "__linux__" -"target_os = redox" = "__redox__" diff --git a/src/header/unistd/getopt.rs b/src/header/unistd/getopt.rs index e0b82c569f..5240e429fa 100644 --- a/src/header/unistd/getopt.rs +++ b/src/header/unistd/getopt.rs @@ -1,35 +1,40 @@ -//! getopt implementation for Redox, following http://pubs.opengroup.org/onlinepubs/009695399/functions/getopt.html +//! `getopt` implementation. +//! +//! See . use core::ptr; -use crate::{header::getopt, platform::types::*}; +use crate::{ + header::getopt, + platform::types::{c_char, c_int}, +}; +/// See . #[allow(non_upper_case_globals)] -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs +#[unsafe(no_mangle)] pub static mut optarg: *mut c_char = ptr::null_mut(); +/// See . #[allow(non_upper_case_globals)] -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs -pub static mut optind: c_int = 1; - -#[allow(non_upper_case_globals)] -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs +#[unsafe(no_mangle)] pub static mut opterr: c_int = 1; +/// See . #[allow(non_upper_case_globals)] -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs +#[unsafe(no_mangle)] +pub static mut optind: c_int = 1; + +/// See . +#[allow(non_upper_case_globals)] +#[unsafe(no_mangle)] pub static mut optopt: c_int = -1; -#[no_mangle] -#[linkage = "weak"] // often redefined in GNU programs +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getopt( argc: c_int, argv: *const *mut c_char, optstring: *const c_char, ) -> c_int { - getopt::getopt_long(argc, argv, optstring, ptr::null(), ptr::null_mut()) + unsafe { getopt::getopt_long(argc, argv, optstring, ptr::null(), ptr::null_mut()) } } diff --git a/src/header/unistd/getpass.rs b/src/header/unistd/getpass.rs new file mode 100644 index 0000000000..8d7a55431b --- /dev/null +++ b/src/header/unistd/getpass.rs @@ -0,0 +1,71 @@ +use core::ptr; + +use crate::{ + c_str::CStr, + fs::File, + header::{ + fcntl::{O_CLOEXEC, O_RDWR}, + limits::PASS_MAX, + termios, + }, + io::{self, Read, Write}, + raw_cell::RawCell, +}; + +use crate::platform::types::c_char; + +fn getpass_rs(prompt: CStr, passbuff: &mut [u8]) -> Result<*mut c_char, io::Error> { + let mut f = File::open(c"/dev/tty".into(), O_RDWR | O_CLOEXEC)?; + + let mut term = termios::termios::default(); + + unsafe { + termios::tcgetattr(f.fd, &raw mut term); + } + + let old_term = term.clone(); + + term.c_iflag &= !(termios::IGNCR | termios::INLCR) as u32; + term.c_iflag |= termios::ICRNL as u32; + term.c_lflag &= !(termios::ECHO | termios::ISIG) as u32; + term.c_lflag |= termios::ICANON as u32; + + unsafe { + termios::tcsetattr(f.fd, termios::TCSAFLUSH, &raw const term); + } + + f.write(prompt.to_bytes())?; + f.flush()?; + + let mut len = f.read(passbuff)?; + + if len > 0 && passbuff[len - 1] == b'\n' || passbuff.len() == len { + len -= 1; + } + + passbuff[len] = 0; + + unsafe { + termios::tcsetattr(f.fd, termios::TCSAFLUSH, &raw const old_term); + } + + f.write(b"\n")?; + f.flush()?; + + Ok(passbuff.as_mut_ptr().cast::()) +} + +/// See . +/// +/// # Deprecation +/// The `getpass()` function was marked legacy in the Open Group System +/// Interface & Headers Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpass(prompt: *const c_char) -> *mut c_char { + static PASSBUFF: RawCell<[u8; PASS_MAX]> = RawCell::new([0; PASS_MAX]); + + unsafe { + getpass_rs(CStr::from_ptr(prompt), &mut *PASSBUFF.as_mut_ptr()).unwrap_or(ptr::null_mut()) + } +} diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index 449128ab43..89607cc6ae 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -1,22 +1,63 @@ -//! unistd implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html +//! `unistd.h` implementation. +//! +//! See . -use core::{convert::TryFrom, mem, ptr, slice}; +use core::{ + convert::TryFrom, + ffi::VaList, + mem::{self, MaybeUninit}, + ptr, slice, +}; +#[allow(deprecated)] +use crate::platform::types::useconds_t; use crate::{ c_str::CStr, + error::{Errno, ResultExt}, header::{ - errno, fcntl, limits, stdlib::getenv, sys_ioctl, sys_time, sys_utsname, termios, - time::timespec, + bits_sigset_t::sigset_t, + bits_timespec::timespec, + crypt::{crypt_data, crypt_r}, + errno::{self, ENAMETOOLONG}, + fcntl, limits, + signal::{sigprocmask, sigsuspend}, + stdlib::getenv, + sys_ioctl, sys_resource, + sys_select::timeval, + sys_time, sys_utsname, termios, + }, + out::Out, + platform::{ + self, ERRNO, Pal, PalSignal, Sys, + types::{ + c_char, c_int, c_long, c_short, c_uint, c_ulonglong, c_void, gid_t, off_t, pid_t, + size_t, ssize_t, suseconds_t, time_t, uid_t, + }, }, - platform::{self, types::*, Pal, Sys}, }; -use alloc::collections::LinkedList; pub use self::{brk::*, getopt::*, pathconf::*, sysconf::*}; +pub use crate::header::pthread::fork_hooks; + +// Inclusion of ctermid() prototype marked as obsolescent since Issue 7, cf. +// . +// cuserid() marked legacy in Issue 5. +#[deprecated] +pub use crate::header::stdio::ctermid; +#[allow(deprecated)] +pub use crate::header::stdio::cuserid; + +use super::{ + errno::{E2BIG, EINVAL, ENOMEM}, + stdio::snprintf, +}; mod brk; mod getopt; +mod getpass; mod pathconf; +#[cfg(target_os = "linux")] +pub mod syscall; mod sysconf; pub const F_OK: c_int = 0; @@ -37,219 +78,412 @@ pub const STDIN_FILENO: c_int = 0; pub const STDOUT_FILENO: c_int = 1; pub const STDERR_FILENO: c_int = 2; -#[thread_local] -pub static mut fork_hooks_static: Option<[LinkedList; 3]> = None; +pub const L_cuserid: usize = 9; -unsafe fn init_fork_hooks<'a>() -> &'a mut [LinkedList; 3] { - // Transmute the lifetime so we can return here. Should be safe as - // long as one does not access the original fork_hooks. - mem::transmute( - fork_hooks_static - .get_or_insert_with(|| [LinkedList::new(), LinkedList::new(), LinkedList::new()]), - ) +// confstr constants +// These are copied from Rust's libc and match musl as well. +pub const _CS_PATH: c_int = 0; +pub const _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS: c_int = 1; +pub const _CS_POSIX_V5_WIDTH_RESTRICTED_ENVS: c_int = 4; +pub const _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS: c_int = 5; +pub const _CS_POSIX_V6_ILP32_OFF32_CFLAGS: c_int = 1116; +pub const _CS_POSIX_V6_ILP32_OFF32_LDFLAGS: c_int = 1117; +pub const _CS_POSIX_V6_ILP32_OFF32_LIBS: c_int = 1118; +pub const _CS_POSIX_V6_ILP32_OFF32_LINTFLAGS: c_int = 1119; +pub const _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS: c_int = 1120; +pub const _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS: c_int = 1121; +pub const _CS_POSIX_V6_ILP32_OFFBIG_LIBS: c_int = 1122; +pub const _CS_POSIX_V6_ILP32_OFFBIG_LINTFLAGS: c_int = 1123; +pub const _CS_POSIX_V6_LP64_OFF64_CFLAGS: c_int = 1124; +pub const _CS_POSIX_V6_LP64_OFF64_LDFLAGS: c_int = 1125; +pub const _CS_POSIX_V6_LP64_OFF64_LIBS: c_int = 1126; +pub const _CS_POSIX_V6_LP64_OFF64_LINTFLAGS: c_int = 1127; +pub const _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS: c_int = 1128; +pub const _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS: c_int = 1129; +pub const _CS_POSIX_V6_LPBIG_OFFBIG_LIBS: c_int = 1130; +pub const _CS_POSIX_V6_LPBIG_OFFBIG_LINTFLAGS: c_int = 1131; +pub const _CS_POSIX_V7_ILP32_OFF32_CFLAGS: c_int = 1132; +pub const _CS_POSIX_V7_ILP32_OFF32_LDFLAGS: c_int = 1133; +pub const _CS_POSIX_V7_ILP32_OFF32_LIBS: c_int = 1134; +pub const _CS_POSIX_V7_ILP32_OFF32_LINTFLAGS: c_int = 1135; +pub const _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS: c_int = 1136; +pub const _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS: c_int = 1137; +pub const _CS_POSIX_V7_ILP32_OFFBIG_LIBS: c_int = 1138; +pub const _CS_POSIX_V7_ILP32_OFFBIG_LINTFLAGS: c_int = 1139; +pub const _CS_POSIX_V7_LP64_OFF64_CFLAGS: c_int = 1140; +pub const _CS_POSIX_V7_LP64_OFF64_LDFLAGS: c_int = 1141; +pub const _CS_POSIX_V7_LP64_OFF64_LIBS: c_int = 1142; +pub const _CS_POSIX_V7_LP64_OFF64_LINTFLAGS: c_int = 1143; +pub const _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS: c_int = 1144; +pub const _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS: c_int = 1145; +pub const _CS_POSIX_V7_LPBIG_OFFBIG_LIBS: c_int = 1146; +pub const _CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS: c_int = 1147; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _Fork() -> pid_t { + unsafe { Sys::fork() }.or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn _exit(status: c_int) { +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn _exit(status: c_int) -> ! { Sys::exit(status) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn access(path: *const c_char, mode: c_int) -> c_int { - let path = CStr::from_ptr(path); - Sys::access(path, mode) + let path = unsafe { CStr::from_ptr(path) }; + Sys::access(path, mode).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn faccessat( + fd: c_int, + path: *const c_char, + mode: c_int, + flags: c_int, +) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::faccessat(fd, path, mode, flags) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn alarm(seconds: c_uint) -> c_uint { - let mut timer = sys_time::itimerval { - it_value: sys_time::timeval { - tv_sec: seconds as time_t, - tv_usec: 0, - }, - ..Default::default() - }; - let errno_backup = unsafe { platform::errno }; - let secs = if sys_time::setitimer(sys_time::ITIMER_REAL, &timer, &mut timer) < 0 { - 0 - } else { - timer.it_value.tv_sec as c_uint + if timer.it_value.tv_usec > 0 { 1 } else { 0 } - }; - unsafe { - platform::errno = errno_backup; + Sys::alarm(seconds) +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chdir(path: *const c_char) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::chdir(path).map(|()| 0).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chown(path: *const c_char, owner: uid_t, group: gid_t) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::chown(path, owner, group) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +/// +/// # Deprecation +/// The `chroot()` function was marked legacy in the System Interface & Headers +/// Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chroot(path: *const c_char) -> c_int { + // TODO: Implement + platform::ERRNO.set(crate::header::errno::EPERM); + + -1 +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn close(fildes: c_int) -> c_int { + Sys::close(fildes).map(|()| 0).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn confstr(name: c_int, buf: *mut c_char, len: size_t) -> size_t { + // confstr returns the number of bytes required to hold the string INCLUDING the NUL + // terminator. This is different from other C functions hence the + 1. + match name { + _CS_PATH => { + let posix2_path = c"/usr/bin"; + unsafe { snprintf(buf, len, c"%s".as_ptr(), posix2_path.as_ptr()) + 1 } + .try_into() + .unwrap_or_default() + } + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS + | _CS_POSIX_V5_WIDTH_RESTRICTED_ENVS + | _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS + | _CS_POSIX_V6_LP64_OFF64_LIBS..=_CS_POSIX_V7_LPBIG_OFFBIG_LINTFLAGS => 1, + _ => { + platform::ERRNO.set(errno::EINVAL); + 0 + } + } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char { + let mut data = crypt_data::new(); + unsafe { crypt_r(key, salt, &raw mut data) } +} + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub extern "C" fn daemon(nochdir: c_int, noclose: c_int) -> c_int { + if nochdir == 0 && Sys::chdir(c"/".into()).map(|()| 0).or_minus_one_errno() < 0 { + return -1; } - secs + if noclose == 0 { + let fd = Sys::open(c"/dev/null".into(), fcntl::O_RDWR, 0).or_minus_one_errno(); + if fd < 0 { + return -1; + } + if dup2(fd, 0) < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0 { + close(fd); + return -1; + } + if fd > 2 { + close(fd); + } + } + + match unsafe { fork() } { + 0 => {} + -1 => return -1, + _ => _exit(0), + } + + if setsid() < 0 { + return -1; + } + + 0 } -#[no_mangle] -pub unsafe extern "C" fn chdir(path: *const c_char) -> c_int { - let path = CStr::from_ptr(path); - Sys::chdir(path) -} - -// #[no_mangle] -pub extern "C" fn chroot(path: *const c_char) -> c_int { - unimplemented!(); -} - -#[no_mangle] -pub unsafe extern "C" fn chown(path: *const c_char, owner: uid_t, group: gid_t) -> c_int { - let path = CStr::from_ptr(path); - Sys::chown(path, owner, group) -} - -#[no_mangle] -pub extern "C" fn close(fildes: c_int) -> c_int { - Sys::close(fildes) -} - -// #[no_mangle] -pub extern "C" fn confstr(name: c_int, buf: *mut c_char, len: size_t) -> size_t { - unimplemented!(); -} - -// #[no_mangle] -pub extern "C" fn crypt(key: *const c_char, salt: *const c_char) -> *mut c_char { - unimplemented!(); -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn dup(fildes: c_int) -> c_int { - Sys::dup(fildes) + Sys::dup(fildes).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn dup2(fildes: c_int, fildes2: c_int) -> c_int { - Sys::dup2(fildes, fildes2) + Sys::dup2(fildes, fildes2).or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn encrypt(block: [c_char; 64], edflag: c_int) { - unimplemented!(); +/// See . +// #[unsafe(no_mangle)] +pub extern "C" fn dup3(fildes: c_int, fildes2: c_int, flag: c_int) -> c_int { + // dup3 requires fildes != fildes2 (unlike dup2 which is a no-op in that case) + if fildes == fildes2 { + ERRNO.set(EINVAL); + return -1; + } + match Sys::dup2(fildes, fildes2) { + Ok(newfd) => { + if flag & fcntl::O_CLOEXEC != 0 { + let _ = Sys::fcntl(newfd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as c_ulonglong); + } + newfd + } + Err(Errno(e)) => { ERRNO.set(e); -1 } + } } -// #[no_mangle] -// pub extern "C" fn execl(path: *const c_char, args: *const *mut c_char) -> c_int { -// unimplemented!(); -// } +// See . +// +// # Deprecation +// The `encrypt()` function was marked obsolescent in the Open Group Base Specifications Issue 8. +//#[deprecated] +// #[unsafe(no_mangle)] +//pub extern "C" fn encrypt(block: [c_char; 64], edflag: c_int) { +// unimplemented!(); +//} -// #[no_mangle] -// pub extern "C" fn execle( -// path: *const c_char, -// args: *const *mut c_char, -// envp: *const *mut c_char, -// ) -> c_int { -// unimplemented!(); -// } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn execl( + path: *const c_char, + arg0: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + with_argv(__valist, arg0, |args, _remaining_va| { + execv(path, args.as_ptr().cast()) + }) + } +} -// #[no_mangle] -// pub extern "C" fn execlp(file: *const c_char, args: *const *mut c_char) -> c_int { -// unimplemented!(); -// } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn execle( + path: *const c_char, + arg0: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + with_argv(__valist, arg0, |args, mut remaining_va| { + let envp = remaining_va.next_arg::<*const *mut c_char>(); + execve(path, args.as_ptr().cast(), envp) + }) + } +} -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn execlp( + file: *const c_char, + arg0: *const c_char, + mut __valist: ... +) -> c_int { + unsafe { + with_argv(__valist, arg0, |args, _remaining_va| { + execvp(file, args.as_ptr().cast()) + }) + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn execv(path: *const c_char, argv: *const *mut c_char) -> c_int { - execve(path, argv, platform::environ) + unsafe { execve(path, argv, platform::environ) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn execve( path: *const c_char, argv: *const *mut c_char, envp: *const *mut c_char, ) -> c_int { - let path = CStr::from_ptr(path); - Sys::execve(path, argv, envp) + let path = unsafe { CStr::from_ptr(path) }; + unsafe { Sys::execve(path, argv, envp) } + .map(|()| unreachable!()) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fexecve( + fd: c_int, + argv: *const *mut c_char, + envp: *const *mut c_char, +) -> c_int { + unsafe { Sys::fexecve(fd, argv, envp) } + .map(|()| unreachable!()) + .or_minus_one_errno() } -#[cfg(target_os = "linux")] const PATH_SEPARATOR: u8 = b':'; -#[cfg(target_os = "redox")] -const PATH_SEPARATOR: u8 = b';'; - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn execvp(file: *const c_char, argv: *const *mut c_char) -> c_int { - let file = CStr::from_ptr(file); + let file = unsafe { CStr::from_ptr(file) }; if file.to_bytes().contains(&b'/') || (cfg!(target_os = "redox") && file.to_bytes().contains(&b':')) { - execv(file.as_ptr(), argv) + unsafe { execv(file.as_ptr(), argv) } } else { let mut error = errno::ENOENT; - let path_env = getenv(c_str!("PATH\0").as_ptr()); + let path_env = unsafe { getenv(c"PATH".as_ptr()) }; if !path_env.is_null() { - let path_env = CStr::from_ptr(path_env); + let path_env = unsafe { CStr::from_ptr(path_env) }; for path in path_env.to_bytes().split(|&b| b == PATH_SEPARATOR) { - let mut program = path.to_vec(); + let file = file.to_bytes(); + let length = file.len() + path.len() + 2; + let mut program = alloc::vec::Vec::with_capacity(length); + program.extend_from_slice(path); program.push(b'/'); - program.extend_from_slice(file.to_bytes()); + program.extend_from_slice(file); program.push(b'\0'); let program_c = CStr::from_bytes_with_nul(&program).unwrap(); - execv(program_c.as_ptr(), argv); + unsafe { execv(program_c.as_ptr(), argv) }; - match platform::errno { + match platform::ERRNO.get() { errno::ENOENT => (), other => error = other, } } } - platform::errno = error; + platform::ERRNO.set(error); -1 } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn fchdir(fildes: c_int) -> c_int { + Sys::fchdir(fildes).map(|()| 0).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> c_int { Sys::fchown(fildes, owner, group) + .map(|()| 0) + .or_minus_one_errno() +} +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fchownat( + fd: c_int, + path: *const c_char, + owner: uid_t, + group: gid_t, + flags: c_int, +) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::fchownat(fd, path, owner, group, flags) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn fchdir(fildes: c_int) -> c_int { - Sys::fchdir(fildes) -} - -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn fdatasync(fildes: c_int) -> c_int { - unimplemented!(); + Sys::fdatasync(fildes).map(|()| 0).or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn fork() -> pid_t { - let fork_hooks = unsafe { init_fork_hooks() }; - for prepare in &fork_hooks[0] { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fork() -> pid_t { + for prepare in unsafe { &fork_hooks[0] } { prepare(); } - let pid = Sys::fork(); + let pid = unsafe { Sys::fork() }.or_minus_one_errno(); if pid == 0 { - for child in &fork_hooks[2] { + for child in unsafe { &fork_hooks[2] } { child(); } } else if pid != -1 { - for parent in &fork_hooks[1] { + for parent in unsafe { &fork_hooks[1] } { parent(); } } pid } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn fsync(fildes: c_int) -> c_int { - Sys::fsync(fildes) + Sys::fsync(fildes).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn ftruncate(fildes: c_int, length: off_t) -> c_int { Sys::ftruncate(fildes, length) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn getcwd(mut buf: *mut c_char, mut size: size_t) -> *mut c_char { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getcwd(mut buf: *mut c_char, mut size: size_t) -> *mut c_char { let alloc = buf.is_null(); let mut stack_buf = [0; limits::PATH_MAX]; if alloc { @@ -257,10 +491,13 @@ pub extern "C" fn getcwd(mut buf: *mut c_char, mut size: size_t) -> *mut c_char size = stack_buf.len(); } - let ret = Sys::getcwd(buf, size); - if ret.is_null() { - return ptr::null_mut(); - } + let ret = match Sys::getcwd(unsafe { Out::from_raw_parts(buf.cast(), size) }) { + Ok(()) => buf, + Err(Errno(errno)) => { + ERRNO.set(errno); + return ptr::null_mut(); + } + }; if alloc { let len = stack_buf @@ -268,10 +505,10 @@ pub extern "C" fn getcwd(mut buf: *mut c_char, mut size: size_t) -> *mut c_char .position(|b| *b == 0) .expect("no nul-byte in getcwd string") + 1; - let heap_buf = unsafe { platform::alloc(len) as *mut c_char }; - for i in 0..len { + let heap_buf = unsafe { platform::alloc(len).cast::() }; + for (i, inner) in stack_buf.iter().enumerate().take(len) { unsafe { - *heap_buf.add(i) = stack_buf[i]; + *heap_buf.add(i) = *inner; } } heap_buf @@ -280,261 +517,413 @@ pub extern "C" fn getcwd(mut buf: *mut c_char, mut size: size_t) -> *mut c_char } } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `getdtablesize()` function was marked legacy in the System Interface & +/// Headers Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn getdtablesize() -> c_int { - unimplemented!(); + let mut lim = mem::MaybeUninit::::uninit(); + let r = unsafe { + sys_resource::getrlimit( + sys_resource::RLIMIT_NOFILE as c_int, + lim.as_mut_ptr().cast::(), + ) + }; + if r == 0 { + let cur = unsafe { lim.assume_init() }.rlim_cur; + return match cur { + c if c < i32::MAX as u64 => c as i32, + _ => i32::MAX, + }; + } + -1 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getegid() -> gid_t { Sys::getegid() } -#[no_mangle] +/// See . +// #[unsafe(no_mangle)] +pub unsafe extern "C" fn getentropy(buffer: *mut c_void, length: size_t) -> c_int { + // POSIX limits getentropy to 256 bytes per call + const GETENTROPY_MAX: size_t = 256; + + if length > GETENTROPY_MAX { + ERRNO.set(EINVAL); + return -1; + } + + let path = unsafe { CStr::from_ptr(c"/scheme/rand".as_ptr().cast()) }; + let fd = match Sys::open(path, fcntl::O_RDONLY, 0) { + Ok(fd) => fd, + Err(Errno(e)) => { + ERRNO.set(e); + return -1; + } + }; + + let buf = unsafe { slice::from_raw_parts_mut(buffer.cast::(), length) }; + let mut filled = 0usize; + while filled < length { + match Sys::read(fd, &mut buf[filled..]) { + Ok(0) => break, + Ok(n) => filled += n, + Err(Errno(e)) => { + let _ = Sys::close(fd); + ERRNO.set(e); + return -1; + } + } + } + let _ = Sys::close(fd); + if filled < length { ERRNO.set(errno::EIO); -1 } else { 0 } +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn geteuid() -> uid_t { Sys::geteuid() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getgid() -> gid_t { Sys::getgid() } -// #[no_mangle] -pub extern "C" fn getgroups(gidsetsize: c_int, grouplist: *mut gid_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getgroups(size: c_int, list: *mut gid_t) -> c_int { + (|| { + let size = usize::try_from(size) + // fails for negative size, but EINVAL required if size != 0 && size < actual size, + // where the actual number of entries in the group list is obviously nonnegative + .map_err(|_| Errno(EINVAL))?; + + let list = unsafe { Out::from_raw_parts(list, size) }; + + Sys::getgroups(list) + })() + .or_minus_one_errno() } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn gethostid() -> c_long { unimplemented!(); } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn gethostname(mut name: *mut c_char, mut len: size_t) -> c_int { let mut uts = mem::MaybeUninit::::uninit(); - let err = Sys::uname(uts.as_mut_ptr()); + // TODO + let err = Sys::uname(Out::from_uninit_mut(&mut uts)) + .map(|()| 0) + .or_minus_one_errno(); if err < 0 { - mem::forget(uts); return err; } - for c in uts.assume_init().nodename.iter() { + for c in unsafe { uts.assume_init() }.nodename.iter() { if len == 0 { break; } len -= 1; - *name = *c; + unsafe { *name = *c }; - if *name == 0 { + if unsafe { *name } == 0 { // We do want to copy the zero also, so we check this after the copying. break; } - name = name.offset(1); + name = unsafe { name.offset(1) }; } 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getlogin() -> *mut c_char { - static mut LOGIN: [c_char; 256] = [0; 256]; - if getlogin_r(LOGIN.as_mut_ptr(), LOGIN.len()) == 0 { - LOGIN.as_mut_ptr() + const LOGIN_LEN: usize = limits::LOGIN_NAME_MAX as usize; + static mut LOGIN: [c_char; LOGIN_LEN] = [0; LOGIN_LEN]; + if getlogin_r((&raw mut LOGIN).cast(), LOGIN_LEN) == 0 { + (&raw mut LOGIN).cast() } else { ptr::null_mut() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getlogin_r(name: *mut c_char, namesize: size_t) -> c_int { //TODO: Determine correct getlogin result on Redox - unsafe { platform::errno = errno::ENOENT }; + platform::ERRNO.set(errno::ENOENT); -1 } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `getpagesize()` function was marked legacy in the System Interface & +/// Headers Issue 5, and removed in Issue 6. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn getpagesize() -> c_int { - match c_int::try_from(sysconf(_SC_PAGESIZE)) { - Ok(page_size) => page_size, - Err(_) => { - /* Behavior not specified by POSIX for this case. The -1 - * value mimics sysconf()'s behavior, though. - * - * As specified for the limits.h header, the minimum - * acceptable value for {PAGESIZE} is 1. The -1 value thus - * cannot be mistaken for an acceptable value. - * - * POSIX does not specify any possible errors for this - * function, hence no errno setting. */ - -1 - } - } + // Panic if we can't uphold the required behavior (no errors are specified for this function) + Sys::getpagesize() + .try_into() + .expect("page size not representable as type `int`") } -// #[no_mangle] -pub extern "C" fn getpass(prompt: *const c_char) -> *mut c_char { - unimplemented!(); -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getpgid(pid: pid_t) -> pid_t { - Sys::getpgid(pid) + Sys::getpgid(pid).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getpgrp() -> pid_t { - Sys::getpgid(Sys::getpid()) + Sys::getpgid(Sys::getpid()).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getpid() -> pid_t { Sys::getpid() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getppid() -> pid_t { Sys::getppid() } -// #[no_mangle] -pub extern "C" fn getsid(pid: pid_t) -> pid_t { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getresgid(rgid: *mut gid_t, egid: *mut gid_t, sgid: *mut gid_t) -> c_int { + Sys::getresgid( + unsafe { Out::nullable(rgid) }, + unsafe { Out::nullable(egid) }, + unsafe { Out::nullable(sgid) }, + ) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getresuid(ruid: *mut uid_t, euid: *mut uid_t, suid: *mut uid_t) -> c_int { + Sys::getresuid( + unsafe { Out::nullable(ruid) }, + unsafe { Out::nullable(euid) }, + unsafe { Out::nullable(suid) }, + ) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn getsid(pid: pid_t) -> pid_t { + Sys::getsid(pid).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn getuid() -> uid_t { Sys::getuid() } -#[no_mangle] -pub extern "C" fn getwd(path_name: *mut c_char) -> *mut c_char { - getcwd(path_name, limits::PATH_MAX) +/// See . +/// +/// # Deprecation +/// The `getwd()` function was marked legacy in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getwd(path_name: *mut c_char) -> *mut c_char { + unsafe { getcwd(path_name, limits::PATH_MAX) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn isatty(fd: c_int) -> c_int { let mut t = termios::termios::default(); - if unsafe { termios::tcgetattr(fd, &mut t as *mut termios::termios) == 0 } { + if unsafe { termios::tcgetattr(fd, &raw mut t) == 0 } { 1 } else { 0 } } -// #[no_mangle] -pub extern "C" fn lchown(path: *const c_char, owner: uid_t, group: gid_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lchown(path: *const c_char, owner: uid_t, group: gid_t) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::lchown(path, owner, group) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn link(path1: *const c_char, path2: *const c_char) -> c_int { - let path1 = CStr::from_ptr(path1); - let path2 = CStr::from_ptr(path2); - Sys::link(path1, path2) + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::link(path1, path2).map(|()| 0).or_minus_one_errno() } -// #[no_mangle] -pub extern "C" fn lockf(fildes: c_int, function: c_int, size: off_t) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn linkat( + fd1: c_int, + path1: *const c_char, + fd2: c_int, + path2: *const c_char, + flags: c_int, +) -> c_int { + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::linkat(fd1, path1, fd2, path2, flags) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lockf(fildes: c_int, function: c_int, size: off_t) -> c_int { + let mut fl = fcntl::flock { + l_type: fcntl::F_WRLCK as c_short, + l_whence: SEEK_CUR as c_short, + l_start: 0, + l_len: size, + l_pid: -1, + }; + + match function { + fcntl::F_TEST => { + fl.l_type = fcntl::F_RDLCK as c_short; + if unsafe { fcntl::fcntl(fildes, fcntl::F_GETLK, &raw mut fl as c_ulonglong) } < 0 { + return -1; + } + if fl.l_type == fcntl::F_UNLCK as c_short || fl.l_pid == getpid() { + return 0; + } + platform::ERRNO.set(errno::EACCES); + -1 + } + fcntl::F_ULOCK => { + fl.l_type = fcntl::F_UNLCK as c_short; + unsafe { fcntl::fcntl(fildes, fcntl::F_SETLK, &raw mut fl as c_ulonglong) } + } + fcntl::F_TLOCK => unsafe { + fcntl::fcntl(fildes, fcntl::F_SETLK, &raw mut fl as c_ulonglong) + }, + fcntl::F_LOCK => unsafe { + fcntl::fcntl(fildes, fcntl::F_SETLKW, &raw mut fl as c_ulonglong) + }, + _ => { + platform::ERRNO.set(errno::EINVAL); + -1 + } + } +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> off_t { - Sys::lseek(fildes, offset, whence) + Sys::lseek(fildes, offset, whence).or_minus_one_errno() } -// #[no_mangle] +/// See . +// #[unsafe(no_mangle)] pub extern "C" fn nice(incr: c_int) -> c_int { unimplemented!(); } -// #[no_mangle] -pub extern "C" fn pause() -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pause() -> c_int { + let mut pset = mem::MaybeUninit::::uninit(); + unsafe { sigprocmask(0, ptr::null_mut(), pset.as_mut_ptr()) }; + let set = unsafe { pset.assume_init() }; + unsafe { sigsuspend(&raw const set) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn pipe(fildes: *mut c_int) -> c_int { - pipe2(fildes, 0) + unsafe { pipe2(fildes, 0) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn pipe2(fildes: *mut c_int, flags: c_int) -> c_int { - Sys::pipe2(slice::from_raw_parts_mut(fildes, 2), flags) + Sys::pipe2(unsafe { Out::nonnull(fildes.cast::<[c_int; 2]>()) }, flags) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn pread(fildes: c_int, buf: *mut c_void, nbyte: size_t, offset: off_t) -> ssize_t { - //TODO: better pread using system calls - - let previous = lseek(fildes, offset, SEEK_SET); - if previous == -1 { - return -1; - } - - let res = read(fildes, buf, nbyte); - if res < 0 { - return res; - } - - if lseek(fildes, previous, SEEK_SET) == -1 { - return -1; - } - - res +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn posix_close(fildes: c_int, flag: c_int) -> c_int { + // Since we do not define `POSIX_CLOSE_RESTART`, this function is + // equivalent to `close`. In the future when we move file descriptors + // to userspace, it would only make sense to define `POSIX_CLOSE_RESTART` + // if `close` is not atomic. + close(fildes) } -#[no_mangle] -pub extern "C" fn pthread_atfork( - prepare: Option, - parent: Option, - child: Option, -) -> c_int { - let fork_hooks = unsafe { init_fork_hooks() }; - if let Some(prepare) = prepare { - fork_hooks[0].push_back(prepare); - } - if let Some(parent) = parent { - fork_hooks[1].push_back(parent); - } - if let Some(child) = child { - fork_hooks[2].push_back(child); - } - 0 +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pread( + fildes: c_int, + buf: *mut c_void, + nbyte: size_t, + offset: off_t, +) -> ssize_t { + Sys::pread( + fildes, + unsafe { slice::from_raw_parts_mut(buf.cast::(), nbyte) }, + offset, + ) + .map(|read| read as ssize_t) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn pwrite( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pwrite( fildes: c_int, buf: *const c_void, nbyte: size_t, offset: off_t, ) -> ssize_t { - //TODO: better pwrite using system calls - - let previous = lseek(fildes, offset, SEEK_SET); - if previous == -1 { - return -1; - } - - let res = write(fildes, buf, nbyte); - if res < 0 { - return res; - } - - if lseek(fildes, previous, SEEK_SET) == -1 { - return -1; - } - - res + Sys::pwrite( + fildes, + unsafe { slice::from_raw_parts(buf.cast::(), nbyte) }, + offset, + ) + .map(|read| read as ssize_t) + .or_minus_one_errno() } -#[no_mangle] -pub extern "C" fn read(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssize_t { - let buf = unsafe { slice::from_raw_parts_mut(buf as *mut u8, nbyte as usize) }; +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn read(fildes: c_int, buf: *mut c_void, nbyte: size_t) -> ssize_t { + let buf = unsafe { slice::from_raw_parts_mut(buf.cast::(), nbyte) }; trace_expr!( - Sys::read(fildes, buf), + Sys::read(fildes, buf) + .map(|read| read as ssize_t) + .or_minus_one_errno(), "read({}, {:p}, {})", fildes, buf, @@ -542,77 +931,159 @@ pub extern "C" fn read(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssiz ) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn readlink( path: *const c_char, buf: *mut c_char, bufsize: size_t, ) -> ssize_t { - let path = CStr::from_ptr(path); - let buf = slice::from_raw_parts_mut(buf as *mut u8, bufsize as usize); + let path = unsafe { CStr::from_ptr(path) }; + let buf = unsafe { slice::from_raw_parts_mut(buf.cast::(), bufsize) }; Sys::readlink(path, buf) + .map(|read| read as ssize_t) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn readlinkat( + dirfd: c_int, + pathname: *const c_char, + buf: *mut c_char, + len: size_t, +) -> ssize_t { + let pathname = unsafe { CStr::from_ptr(pathname) }; + let buf = unsafe { slice::from_raw_parts_mut(buf.cast(), len) }; + Sys::readlinkat(dirfd, pathname, buf) + .map(|read| { + read.try_into() + .map_err(|_| Errno(ENAMETOOLONG)) + .or_minus_one_errno() + }) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn rmdir(path: *const c_char) -> c_int { - let path = CStr::from_ptr(path); - Sys::rmdir(path) + let path = unsafe { CStr::from_ptr(path) }; + Sys::rmdir(path).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn setegid(gid: gid_t) -> c_int { + Sys::setresgid(-1, gid, -1).map(|()| 0).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn seteuid(uid: uid_t) -> c_int { + Sys::setresuid(-1, uid, -1).map(|()| 0).or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setgid(gid: gid_t) -> c_int { - Sys::setregid(gid, gid) + Sys::setresgid(gid, gid, -1) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setpgid(pid: pid_t, pgid: pid_t) -> c_int { - Sys::setpgid(pid, pgid) + Sys::setpgid(pid, pgid).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `setpgrp()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 7, and removed in Issue 8. +#[deprecated] +#[unsafe(no_mangle)] pub extern "C" fn setpgrp() -> pid_t { setpgid(0, 0) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setregid(rgid: gid_t, egid: gid_t) -> c_int { - Sys::setregid(rgid, egid) + Sys::setresgid(rgid, egid, -1) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) -> c_int { + Sys::setresgid(rgid, egid, sgid) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) -> c_int { + Sys::setresuid(ruid, euid, suid) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setreuid(ruid: uid_t, euid: uid_t) -> c_int { - Sys::setreuid(ruid, euid) + Sys::setresuid(ruid, euid, -1) + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setsid() -> pid_t { - unimplemented!(); + Sys::setsid().or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn setuid(uid: uid_t) -> c_int { - Sys::setreuid(uid, uid) + Sys::setresuid(uid, uid, -1) + .map(|()| 0) + .or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn sleep(seconds: c_uint) -> c_uint { let rqtp = timespec { - tv_sec: seconds as i64, + tv_sec: time_t::from(seconds), tv_nsec: 0, }; - let rmtp = ptr::null_mut(); - Sys::nanosleep(&rqtp, rmtp); - 0 + let mut rmtp = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + + // If sleep() returns because the requested time has elapsed, the value returned shall be 0. + // If sleep() returns due to delivery of a signal, the return value shall be the "unslept" amount + // (the requested time minus the time actually slept) in seconds. + match unsafe { Sys::nanosleep(&raw const rqtp, &raw mut rmtp) } { + Err(Errno(EINTR)) => rmtp.tv_sec as c_uint, + r => 0, + } } -#[no_mangle] -pub extern "C" fn swab(src: *const c_void, dest: *mut c_void, nbytes: ssize_t) { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn swab(src: *const c_void, dest: *mut c_void, nbytes: ssize_t) { if nbytes <= 0 { return; } let number_of_swaps = nbytes / 2; let mut offset = 0; - for i in 0..number_of_swaps { + for _ in 0..number_of_swaps { unsafe { src.offset(offset).copy_to(dest.offset(offset + 1), 1); src.offset(offset + 1).copy_to(dest.offset(offset), 1); @@ -621,124 +1092,232 @@ pub extern "C" fn swab(src: *const c_void, dest: *mut c_void, nbytes: ssize_t) { } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn symlink(path1: *const c_char, path2: *const c_char) -> c_int { - let path1 = CStr::from_ptr(path1); - let path2 = CStr::from_ptr(path2); - Sys::symlink(path1, path2) + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::symlink(path1, path2).map(|()| 0).or_minus_one_errno() } -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn symlinkat(path1: *const c_char, fd: c_int, path2: *const c_char) -> c_int { + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::symlinkat(path1, fd, path2) + .map(|()| 0) + .or_minus_one_errno() +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn sync() { - unimplemented!(); + if let Ok(()) = Sys::sync() {}; // TODO handle error } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn tcgetpgrp(fd: c_int) -> pid_t { let mut pgrp = 0; - if unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCGPGRP, &mut pgrp as *mut pid_t as _) } < 0 { + if unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCGPGRP, (&raw mut pgrp).cast()) } < 0 { return -1; } pgrp } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn tcsetpgrp(fd: c_int, pgrp: pid_t) -> c_int { - if unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCSPGRP, &pgrp as *const pid_t as _) } < 0 { + if unsafe { sys_ioctl::ioctl(fd, sys_ioctl::TIOCSPGRP, &raw const pgrp as _) } < 0 { return -1; } pgrp } -#[no_mangle] -pub extern "C" fn truncate(path: *const c_char, length: off_t) -> c_int { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn truncate(path: *const c_char, length: off_t) -> c_int { let file = unsafe { CStr::from_ptr(path) }; - let fd = Sys::open(file, fcntl::O_WRONLY, 0); + // TODO: Rustify + let fd = Sys::open(file, fcntl::O_WRONLY, 0).or_minus_one_errno(); if fd < 0 { return -1; } let res = ftruncate(fd, length); - Sys::close(fd); + if let Ok(()) = Sys::close(fd) {}; // TODO handle error res } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn ttyname(fildes: c_int) -> *mut c_char { - static mut TTYNAME: [c_char; 4096] = [0; 4096]; - if ttyname_r(fildes, TTYNAME.as_mut_ptr(), TTYNAME.len()) == 0 { - TTYNAME.as_mut_ptr() + const TTYNAME_LEN: usize = 4096; + static mut TTYNAME: [c_char; TTYNAME_LEN] = [0; TTYNAME_LEN]; + if ttyname_r(fildes, (&raw mut TTYNAME).cast(), TTYNAME_LEN) == 0 { + (&raw mut TTYNAME).cast() } else { ptr::null_mut() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn ttyname_r(fildes: c_int, name: *mut c_char, namesize: size_t) -> c_int { - let name = unsafe { slice::from_raw_parts_mut(name as *mut u8, namesize) }; + let name = unsafe { slice::from_raw_parts_mut(name.cast::(), namesize) }; if name.is_empty() { return errno::ERANGE; } - let len = Sys::fpath(fildes, &mut name[..namesize - 1]); + let len = Sys::fpath(fildes, &mut name[..namesize - 1]) + .map(|read| read as ssize_t) + .or_minus_one_errno(); if len < 0 { - return unsafe { -platform::errno }; + return -platform::ERRNO.get(); } name[len as usize] = 0; 0 } -#[no_mangle] +/// See . +/// +/// # Deprecation +/// The `ualarm()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] pub extern "C" fn ualarm(usecs: useconds_t, interval: useconds_t) -> useconds_t { + // TODO setitimer is unimplemented on Redox and obsolete let mut timer = sys_time::itimerval { - it_value: sys_time::timeval { + it_value: timeval { tv_sec: 0, tv_usec: usecs as suseconds_t, }, - it_interval: sys_time::timeval { + it_interval: timeval { tv_sec: 0, tv_usec: interval as suseconds_t, }, }; - let errno_backup = unsafe { platform::errno }; - let ret = if sys_time::setitimer(sys_time::ITIMER_REAL, &timer, &mut timer) < 0 { - 0 - } else { - timer.it_value.tv_sec as useconds_t * 1_000_000 + timer.it_value.tv_usec as useconds_t - }; - unsafe { - platform::errno = errno_backup; - } + let errno_backup = platform::ERRNO.get(); + let ret = + if unsafe { sys_time::setitimer(sys_time::ITIMER_REAL, &raw const timer, &raw mut timer) } + < 0 + { + 0 + } else { + timer.it_value.tv_sec as useconds_t * 1_000_000 + timer.it_value.tv_usec as useconds_t + }; + platform::ERRNO.set(errno_backup); ret } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn unlink(path: *const c_char) -> c_int { - let path = CStr::from_ptr(path); - Sys::unlink(path) + let path = unsafe { CStr::from_ptr(path) }; + Sys::unlink(path).map(|()| 0).or_minus_one_errno() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unlinkat(fd: c_int, path: *const c_char, flags: c_int) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::unlinkat(fd, path, flags) + .map(|()| 0) + .or_minus_one_errno() +} +/// See . +/// +/// # Deprecation +/// The `usleep()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] pub extern "C" fn usleep(useconds: useconds_t) -> c_int { + #[cfg(not(target_arch = "x86"))] + let tv_nsec = c_long::from((useconds % 1_000_000) * 1000); + #[cfg(target_arch = "x86")] + let tv_nsec = ((useconds % 1_000_000) * 1000) as c_long; let rqtp = timespec { - tv_sec: (useconds / 1_000_000) as i64, - tv_nsec: ((useconds % 1_000_000) * 1000) as i64, + tv_sec: time_t::from(useconds / 1_000_000), + tv_nsec, }; let rmtp = ptr::null_mut(); - Sys::nanosleep(&rqtp, rmtp) + unsafe { Sys::nanosleep(&raw const rqtp, rmtp) } + .map(|()| 0) + .or_minus_one_errno() } -// #[no_mangle] +/// See . +/// +/// # Deprecation +/// The `vfork()` function was marked obsolescent in the Open Group Base +/// Specifications Issue 6, and removed in Issue 7. +#[deprecated] +// #[unsafe(no_mangle)] pub extern "C" fn vfork() -> pid_t { unimplemented!(); } -#[no_mangle] -pub extern "C" fn write(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssize_t { - let buf = unsafe { slice::from_raw_parts(buf as *const u8, nbyte as usize) }; - Sys::write(fildes, buf) +unsafe fn with_argv( + mut va: VaList<'_>, + arg0: *const c_char, + f: impl FnOnce(&[*const c_char], VaList<'_>) -> c_int, +) -> c_int { + let argc = 1 + { + let mut copy = va.clone(); + core::iter::from_fn(|| Some(unsafe { copy.next_arg::<*const c_char>() })) + .position(|p| p.is_null()) + .unwrap() + }; + + let mut stack: [MaybeUninit<*const c_char>; 32] = [MaybeUninit::uninit(); 32]; + + let out = if argc < 32 { + stack.as_mut_slice() + } else if argc < 4096 { + // TODO: Use ARG_MAX, not this hardcoded constant + let ptr = + unsafe { crate::header::stdlib::malloc((argc + 1) * mem::size_of::<*const c_char>()) }; + if ptr.is_null() { + platform::ERRNO.set(ENOMEM); + return -1; + } + unsafe { slice::from_raw_parts_mut(ptr.cast::>(), argc + 1) } + } else { + platform::ERRNO.set(E2BIG); + return -1; + }; + out[0].write(arg0); + + for inner in out.iter_mut().take(argc).skip(1) { + (*inner).write(unsafe { va.next_arg::<*const c_char>() }); + } + out[argc].write(core::ptr::null()); + // NULL + unsafe { va.next_arg::<*const c_char>() }; + + f(unsafe { (&*out).assume_init_ref() }, va); + + // f only returns if it fails + if argc >= 32 { + unsafe { crate::header::stdlib::free(out.as_mut_ptr().cast()) }; + } + -1 +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write(fildes: c_int, buf: *const c_void, nbyte: size_t) -> ssize_t { + let buf = unsafe { slice::from_raw_parts(buf.cast::(), nbyte) }; + Sys::write(fildes, buf) + .map(|bytes| bytes as ssize_t) + .or_minus_one_errno() } diff --git a/src/header/unistd/pathconf.rs b/src/header/unistd/pathconf.rs index 2dd6ee36c5..74a90cead7 100644 --- a/src/header/unistd/pathconf.rs +++ b/src/header/unistd/pathconf.rs @@ -1,6 +1,16 @@ use crate::{ - header::errno, - platform::{self, types::*}, + header::{ + errno, + limits::{ + FILESIZEBITS, LINK_MAX, MAX_CANON, MAX_INPUT, NAME_MAX, PATH_MAX, PIPE_BUF, + POSIX_ALLOC_SIZE_MIN, SYMLINK_MAX, + }, + termios::_POSIX_VDISABLE, + }, + platform::{ + self, + types::{c_char, c_int, c_long}, + }, }; pub const _PC_LINK_MAX: c_int = 0; @@ -11,6 +21,7 @@ pub const _PC_PATH_MAX: c_int = 4; pub const _PC_PIPE_BUF: c_int = 5; pub const _PC_CHOWN_RESTRICTED: c_int = 6; pub const _PC_NO_TRUNC: c_int = 7; +/// Check if file (terminal) supports disabling control chars (CC) pub const _PC_VDISABLE: c_int = 8; pub const _PC_SYNC_IO: c_int = 9; pub const _PC_ASYNC_IO: c_int = 10; @@ -28,42 +39,42 @@ pub const _PC_2_SYMLINKS: c_int = 20; fn pc(name: c_int) -> c_long { // Settings from musl, some adjusted match name { - _PC_LINK_MAX => 127, - _PC_MAX_CANON => 255, - _PC_MAX_INPUT => 255, - _PC_NAME_MAX => 255, - _PC_PATH_MAX => 4096, - _PC_PIPE_BUF => 4096, + _PC_LINK_MAX => LINK_MAX.try_into().unwrap_or(-1), + _PC_MAX_CANON => MAX_CANON.try_into().unwrap_or(-1), + _PC_MAX_INPUT => MAX_INPUT.try_into().unwrap_or(-1), + _PC_NAME_MAX => NAME_MAX.try_into().unwrap_or(-1), + _PC_PATH_MAX => PATH_MAX.try_into().unwrap_or(-1), + _PC_PIPE_BUF => PIPE_BUF.try_into().unwrap_or(-1), _PC_CHOWN_RESTRICTED => 1, _PC_NO_TRUNC => 1, - _PC_VDISABLE => 0, + _PC_VDISABLE => _POSIX_VDISABLE.into(), _PC_SYNC_IO => 1, _PC_ASYNC_IO => -1, _PC_PRIO_IO => -1, _PC_SOCK_MAXBUF => -1, - _PC_FILESIZEBITS => 64, + _PC_FILESIZEBITS => FILESIZEBITS.into(), _PC_REC_INCR_XFER_SIZE => -1, _PC_REC_MAX_XFER_SIZE => -1, _PC_REC_MIN_XFER_SIZE => 4096, _PC_REC_XFER_ALIGN => 4096, - _PC_ALLOC_SIZE_MIN => 4096, - _PC_SYMLINK_MAX => -1, + _PC_ALLOC_SIZE_MIN => POSIX_ALLOC_SIZE_MIN.try_into().unwrap_or(-1), + _PC_SYMLINK_MAX => SYMLINK_MAX.try_into().unwrap_or(-1), _PC_2_SYMLINKS => 1, _ => { - unsafe { - platform::errno = errno::EINVAL; - } + platform::ERRNO.set(errno::EINVAL); -1 } } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn fpathconf(_fildes: c_int, name: c_int) -> c_long { pc(name) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn pathconf(_path: *const c_char, name: c_int) -> c_long { pc(name) } diff --git a/src/header/unistd/syscall.rs b/src/header/unistd/syscall.rs new file mode 100644 index 0000000000..f97e649543 --- /dev/null +++ b/src/header/unistd/syscall.rs @@ -0,0 +1,14 @@ +use crate::platform::types::c_long; + +/// Non-POSIX, see . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall(sysno: c_long, mut args: ...) -> c_long { + let a1 = unsafe { args.next_arg::() }; + let a2 = unsafe { args.next_arg::() }; + let a3 = unsafe { args.next_arg::() }; + let a4 = unsafe { args.next_arg::() }; + let a5 = unsafe { args.next_arg::() }; + let a6 = unsafe { args.next_arg::() }; + + (unsafe { sc::syscall6(sysno as usize, a1, a2, a3, a4, a5, a6) }) as c_long +} diff --git a/src/header/unistd/sysconf.rs b/src/header/unistd/sysconf.rs index 103ab07db4..dd61586ff5 100644 --- a/src/header/unistd/sysconf.rs +++ b/src/header/unistd/sysconf.rs @@ -1,56 +1,18 @@ -use crate::{ - header::errno, - platform::{self, types::*}, -}; +/// sysconf.h: . -// POSIX.1 { -pub const _SC_ARG_MAX: c_int = 0; -pub const _SC_CHILD_MAX: c_int = 1; -pub const _SC_CLK_TCK: c_int = 2; -pub const _SC_NGROUPS_MAX: c_int = 3; -pub const _SC_OPEN_MAX: c_int = 4; -pub const _SC_STREAM_MAX: c_int = 5; -pub const _SC_TZNAME_MAX: c_int = 6; -// ... -pub const _SC_VERSION: c_int = 29; -pub const _SC_PAGESIZE: c_int = 30; -pub const _SC_PAGE_SIZE: c_int = 30; -// ... -pub const _SC_RE_DUP_MAX: c_int = 44; -// ... -pub const _SC_GETPW_R_SIZE_MAX: c_int = 70; -pub const _SC_LOGIN_NAME_MAX: c_int = 71; -pub const _SC_TTY_NAME_MAX: c_int = 72; -// ... -pub const _SC_SYMLOOP_MAX: c_int = 173; -// ... -pub const _SC_HOST_NAME_MAX: c_int = 180; -// } POSIX.1 +#[cfg(target_os = "redox")] +#[path = "sysconf/redox.rs"] +mod sys; -#[no_mangle] -pub extern "C" fn sysconf(name: c_int) -> c_long { - //TODO: Real values - match name { - _SC_ARG_MAX => 4096, - _SC_CHILD_MAX => 65536, - _SC_CLK_TCK => 100, - _SC_NGROUPS_MAX => 65536, - _SC_OPEN_MAX => 1024, - _SC_STREAM_MAX => 16, - _SC_TZNAME_MAX => -1, - _SC_VERSION => 200809, - _SC_PAGESIZE => 4096, - _SC_RE_DUP_MAX => 32767, - _SC_GETPW_R_SIZE_MAX => -1, - _SC_LOGIN_NAME_MAX => 256, - _SC_TTY_NAME_MAX => 32, - _SC_SYMLOOP_MAX => -1, - _SC_HOST_NAME_MAX => 64, - _ => { - unsafe { - platform::errno = errno::EINVAL; - } - -1 - } - } +#[cfg(target_os = "linux")] +#[path = "sysconf/linux.rs"] +mod sys; + +pub use sys::*; + +use core::ffi::{c_int, c_long}; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sysconf(name: c_int) -> c_long { + sysconf_impl(name) } diff --git a/src/header/unistd/sysconf/linux.rs b/src/header/unistd/sysconf/linux.rs new file mode 100644 index 0000000000..8ec01d2dc8 --- /dev/null +++ b/src/header/unistd/sysconf/linux.rs @@ -0,0 +1,390 @@ +/// Linux sysconf implementation. +/// Constants borrowed from musl. +use core::{ + ffi::{c_int, c_long}, + mem::size_of, +}; + +use crate::{ + header::{errno, limits::*, signal}, + platform, +}; + +pub const _XOPEN_IOV_MAX: c_long = 16; +pub const _XOPEN_NAME_MAX: c_long = 255; +pub const _XOPEN_PATH_MAX: c_long = 1024; + +pub const _SC_ARG_MAX: c_int = 0; +pub const _SC_CHILD_MAX: c_int = 1; +pub const _SC_CLK_TCK: c_int = 2; +pub const _SC_NGROUPS_MAX: c_int = 3; +pub const _SC_OPEN_MAX: c_int = 4; +pub const _SC_STREAM_MAX: c_int = 5; +pub const _SC_TZNAME_MAX: c_int = 6; +pub const _SC_JOB_CONTROL: c_int = 7; +pub const _SC_SAVED_IDS: c_int = 8; +pub const _SC_REALTIME_SIGNALS: c_int = 9; +pub const _SC_PRIORITY_SCHEDULING: c_int = 10; +pub const _SC_TIMERS: c_int = 11; +pub const _SC_ASYNCHRONOUS_IO: c_int = 12; +pub const _SC_PRIORITIZED_IO: c_int = 13; +pub const _SC_SYNCHRONIZED_IO: c_int = 14; +pub const _SC_FSYNC: c_int = 15; +pub const _SC_MAPPED_FILES: c_int = 16; +pub const _SC_MEMLOCK: c_int = 17; +pub const _SC_MEMLOCK_RANGE: c_int = 18; +pub const _SC_MEMORY_PROTECTION: c_int = 19; +pub const _SC_MESSAGE_PASSING: c_int = 20; +pub const _SC_SEMAPHORES: c_int = 21; +pub const _SC_SHARED_MEMORY_OBJECTS: c_int = 22; +pub const _SC_AIO_LISTIO_MAX: c_int = 23; +pub const _SC_AIO_MAX: c_int = 24; +pub const _SC_AIO_PRIO_DELTA_MAX: c_int = 25; +pub const _SC_DELAYTIMER_MAX: c_int = 26; +pub const _SC_MQ_OPEN_MAX: c_int = 27; +pub const _SC_MQ_PRIO_MAX: c_int = 28; +pub const _SC_VERSION: c_int = 29; +pub const _SC_PAGE_SIZE: c_int = 30; +pub const _SC_PAGESIZE: c_int = 30; +pub const _SC_RTSIG_MAX: c_int = 31; +pub const _SC_SEM_NSEMS_MAX: c_int = 32; +pub const _SC_SEM_VALUE_MAX: c_int = 33; +pub const _SC_SIGQUEUE_MAX: c_int = 34; +pub const _SC_TIMER_MAX: c_int = 35; +pub const _SC_BC_BASE_MAX: c_int = 36; +pub const _SC_BC_DIM_MAX: c_int = 37; +pub const _SC_BC_SCALE_MAX: c_int = 38; +pub const _SC_BC_STRING_MAX: c_int = 39; +pub const _SC_COLL_WEIGHTS_MAX: c_int = 40; +pub const _SC_EXPR_NEST_MAX: c_int = 42; +pub const _SC_LINE_MAX: c_int = 43; +pub const _SC_RE_DUP_MAX: c_int = 44; +pub const _SC_2_VERSION: c_int = 46; +pub const _SC_2_C_BIND: c_int = 47; +pub const _SC_2_C_DEV: c_int = 48; +pub const _SC_2_FORT_DEV: c_int = 49; +pub const _SC_2_FORT_RUN: c_int = 50; +pub const _SC_2_SW_DEV: c_int = 51; +pub const _SC_2_LOCALEDEF: c_int = 52; +pub const _SC_UIO_MAXIOV: c_int = 60; +pub const _SC_IOV_MAX: c_int = 60; +pub const _SC_THREADS: c_int = 67; +pub const _SC_THREAD_SAFE_FUNCTIONS: c_int = 68; +pub const _SC_GETGR_R_SIZE_MAX: c_int = 69; +pub const _SC_GETPW_R_SIZE_MAX: c_int = 70; +pub const _SC_LOGIN_NAME_MAX: c_int = 71; +pub const _SC_TTY_NAME_MAX: c_int = 72; +pub const _SC_THREAD_DESTRUCTOR_ITERATIONS: c_int = 73; +pub const _SC_THREAD_KEYS_MAX: c_int = 74; +pub const _SC_THREAD_STACK_MIN: c_int = 75; +pub const _SC_THREAD_THREADS_MAX: c_int = 76; +pub const _SC_THREAD_ATTR_STACKADDR: c_int = 77; +pub const _SC_THREAD_ATTR_STACKSIZE: c_int = 78; +pub const _SC_THREAD_PRIORITY_SCHEDULING: c_int = 79; +pub const _SC_THREAD_PRIO_INHERIT: c_int = 80; +pub const _SC_THREAD_PRIO_PROTECT: c_int = 81; +pub const _SC_THREAD_PROCESS_SHARED: c_int = 82; +pub const _SC_NPROCESSORS_CONF: c_int = 83; +pub const _SC_NPROCESSORS_ONLN: c_int = 84; +pub const _SC_PHYS_PAGES: c_int = 85; +pub const _SC_AVPHYS_PAGES: c_int = 86; +pub const _SC_ATEXIT_MAX: c_int = 87; +pub const _SC_PASS_MAX: c_int = 88; +pub const _SC_XOPEN_VERSION: c_int = 89; +pub const _SC_XOPEN_XCU_VERSION: c_int = 90; +pub const _SC_XOPEN_UNIX: c_int = 91; +pub const _SC_XOPEN_CRYPT: c_int = 92; +pub const _SC_XOPEN_ENH_I18N: c_int = 93; +pub const _SC_XOPEN_SHM: c_int = 94; +pub const _SC_2_CHAR_TERM: c_int = 95; +pub const _SC_2_UPE: c_int = 97; +pub const _SC_XOPEN_XPG2: c_int = 98; +pub const _SC_XOPEN_XPG3: c_int = 99; +pub const _SC_XOPEN_XPG4: c_int = 100; +pub const _SC_NZERO: c_int = 109; +pub const _SC_XBS5_ILP32_OFF32: c_int = 125; +pub const _SC_XBS5_ILP32_OFFBIG: c_int = 126; +pub const _SC_XBS5_LP64_OFF64: c_int = 127; +pub const _SC_XBS5_LPBIG_OFFBIG: c_int = 128; +pub const _SC_XOPEN_LEGACY: c_int = 129; +pub const _SC_XOPEN_REALTIME: c_int = 130; +pub const _SC_XOPEN_REALTIME_THREADS: c_int = 131; +pub const _SC_ADVISORY_INFO: c_int = 132; +pub const _SC_BARRIERS: c_int = 133; +pub const _SC_CLOCK_SELECTION: c_int = 137; +pub const _SC_CPUTIME: c_int = 138; +pub const _SC_THREAD_CPUTIME: c_int = 139; +pub const _SC_MONOTONIC_CLOCK: c_int = 149; +pub const _SC_READER_WRITER_LOCKS: c_int = 153; +pub const _SC_SPIN_LOCKS: c_int = 154; +pub const _SC_REGEXP: c_int = 155; +pub const _SC_SHELL: c_int = 157; +pub const _SC_SPAWN: c_int = 159; +pub const _SC_SPORADIC_SERVER: c_int = 160; +pub const _SC_THREAD_SPORADIC_SERVER: c_int = 161; +pub const _SC_TIMEOUTS: c_int = 164; +pub const _SC_TYPED_MEMORY_OBJECTS: c_int = 165; +pub const _SC_2_PBS: c_int = 168; +pub const _SC_2_PBS_ACCOUNTING: c_int = 169; +pub const _SC_2_PBS_LOCATE: c_int = 170; +pub const _SC_2_PBS_MESSAGE: c_int = 171; +pub const _SC_2_PBS_TRACK: c_int = 172; +pub const _SC_SYMLOOP_MAX: c_int = 173; +pub const _SC_STREAMS: c_int = 174; +pub const _SC_2_PBS_CHECKPOINT: c_int = 175; +pub const _SC_V6_ILP32_OFF32: c_int = 176; +pub const _SC_V6_ILP32_OFFBIG: c_int = 177; +pub const _SC_V6_LP64_OFF64: c_int = 178; +pub const _SC_V6_LPBIG_OFFBIG: c_int = 179; +pub const _SC_HOST_NAME_MAX: c_int = 180; +pub const _SC_TRACE: c_int = 181; +pub const _SC_TRACE_EVENT_FILTER: c_int = 182; +pub const _SC_TRACE_INHERIT: c_int = 183; +pub const _SC_TRACE_LOG: c_int = 184; + +pub const _SC_IPV6: c_int = 235; +pub const _SC_RAW_SOCKETS: c_int = 236; +pub const _SC_V7_ILP32_OFF32: c_int = 237; +pub const _SC_V7_ILP32_OFFBIG: c_int = 238; +pub const _SC_V7_LP64_OFF64: c_int = 239; +pub const _SC_V7_LPBIG_OFFBIG: c_int = 240; +pub const _SC_SS_REPL_MAX: c_int = 241; +pub const _SC_TRACE_EVENT_NAME_MAX: c_int = 242; +pub const _SC_TRACE_NAME_MAX: c_int = 243; +pub const _SC_TRACE_SYS_MAX: c_int = 244; +pub const _SC_TRACE_USER_EVENT_MAX: c_int = 245; +pub const _SC_XOPEN_STREAMS: c_int = 246; +pub const _SC_THREAD_ROBUST_PRIO_INHERIT: c_int = 247; +pub const _SC_THREAD_ROBUST_PRIO_PROTECT: c_int = 248; +pub const _SC_MINSIGSTKSZ: c_int = 249; +pub const _SC_SIGSTKSZ: c_int = 250; + +// Defined in unistd.h but we defined it in C +const _POSIX_VERSION: c_long = 200809; +const _XOPEN_VERSION: c_long = 700; + +pub(super) fn sysconf_impl(name: c_int) -> c_long { + // Values from musl which we can assume is correct. + match name { + _SC_CLK_TCK => 100, + _SC_CHILD_MAX => { + let mut lim = core::mem::MaybeUninit::::uninit(); + let r = unsafe { + crate::header::sys_resource::getrlimit( + crate::header::sys_resource::RLIMIT_NPROC as c_int, + lim.as_mut_ptr().cast::(), + ) + }; + if r == 0 { + let cur = unsafe { lim.assume_init() }.rlim_cur; + if cur == crate::header::sys_resource::RLIM_INFINITY { -1 } else if cur > c_long::MAX as u64 { c_long::MAX } else { cur as c_long } + } else { -1 } + } + _SC_NGROUPS_MAX => NGROUPS_MAX as c_long, + _SC_OPEN_MAX => { + let mut lim = core::mem::MaybeUninit::::uninit(); + let r = unsafe { + crate::header::sys_resource::getrlimit( + crate::header::sys_resource::RLIMIT_NOFILE as c_int, + lim.as_mut_ptr().cast::(), + ) + }; + if r == 0 { + let cur = unsafe { lim.assume_init() }.rlim_cur; + if cur == crate::header::sys_resource::RLIM_INFINITY { -1 } else if cur > c_long::MAX as u64 { c_long::MAX } else { cur as c_long } + } else { -1 } + } + _SC_STREAM_MAX => -1, + // TODO: limits.h + _SC_TZNAME_MAX => -1, + _SC_JOB_CONTROL => 1, + _SC_SAVED_IDS => 1, + _SC_REALTIME_SIGNALS => _POSIX_VERSION, + _SC_PRIORITY_SCHEDULING => -1, + _SC_TIMERS => _POSIX_VERSION, + _SC_ASYNCHRONOUS_IO => _POSIX_VERSION, + _SC_PRIORITIZED_IO => -1, + _SC_SYNCHRONIZED_IO => -1, + _SC_FSYNC => _POSIX_VERSION, + _SC_MAPPED_FILES => _POSIX_VERSION, + _SC_MEMLOCK => _POSIX_VERSION, + _SC_MEMLOCK_RANGE => _POSIX_VERSION, + _SC_MEMORY_PROTECTION => _POSIX_VERSION, + _SC_MESSAGE_PASSING => _POSIX_VERSION, + _SC_SEMAPHORES => _POSIX_VERSION, + _SC_SHARED_MEMORY_OBJECTS => _POSIX_VERSION, + _SC_AIO_LISTIO_MAX => -1, + _SC_AIO_MAX => -1, + _SC_AIO_PRIO_DELTA_MAX => 0, + // TODO: limits.h? + _SC_DELAYTIMER_MAX => -1, + _SC_MQ_OPEN_MAX => -1, + // TODO: limits.h? + _SC_MQ_PRIO_MAX => -1, + _SC_VERSION => _POSIX_VERSION, + _SC_PAGE_SIZE => PAGE_SIZE.try_into().unwrap_or(-1), + _SC_RTSIG_MAX => (signal::SIGRTMAX - signal::SIGRTMIN) + .try_into() + .unwrap_or(-1), + // TODO: limits.h + _SC_SEM_NSEMS_MAX => -1, + // TODO: limits.h + _SC_SEM_VALUE_MAX => -1, + _SC_SIGQUEUE_MAX => -1, + _SC_TIMER_MAX => -1, + _SC_BC_BASE_MAX => BC_BASE_MAX, + _SC_BC_DIM_MAX => BC_DIM_MAX, + _SC_BC_SCALE_MAX => BC_SCALE_MAX, + _SC_BC_STRING_MAX => BC_STRING_MAX, + _SC_COLL_WEIGHTS_MAX => COLL_WEIGHTS_MAX, + _SC_EXPR_NEST_MAX => EXPR_NEST_MAX, + _SC_LINE_MAX => LINE_MAX, + _SC_RE_DUP_MAX => RE_DUP_MAX, + _SC_2_VERSION => _POSIX_VERSION, + _SC_2_C_BIND => _POSIX_VERSION, + _SC_2_C_DEV => -1, + _SC_2_FORT_DEV => -1, + _SC_2_FORT_RUN => -1, + _SC_2_SW_DEV => -1, + _SC_2_LOCALEDEF => -1, + _SC_IOV_MAX => _XOPEN_IOV_MAX, + _SC_THREADS => _POSIX_VERSION, + _SC_THREAD_SAFE_FUNCTIONS => _POSIX_VERSION, + _SC_GETGR_R_SIZE_MAX => -1, + _SC_GETPW_R_SIZE_MAX => -1, + _SC_LOGIN_NAME_MAX => 256, + // TODO: limits.h + _SC_TTY_NAME_MAX => -1, + // TODO: limits.h + _SC_THREAD_DESTRUCTOR_ITERATIONS => -1, + // TODO: limits.h + _SC_THREAD_KEYS_MAX => -1, + // TODO: limits.h + _SC_THREAD_STACK_MIN => -1, + _SC_THREAD_THREADS_MAX => -1, + _SC_THREAD_ATTR_STACKADDR => _POSIX_VERSION, + _SC_THREAD_ATTR_STACKSIZE => _POSIX_VERSION, + _SC_THREAD_PRIORITY_SCHEDULING => _POSIX_VERSION, + _SC_THREAD_PRIO_INHERIT => -1, + _SC_THREAD_PRIO_PROTECT => -1, + _SC_THREAD_PROCESS_SHARED => _POSIX_VERSION, + // TODO: Use getaffinity syscall on Linux + _SC_NPROCESSORS_CONF => -1, + _SC_NPROCESSORS_ONLN => -1, + // TODO: sysinfo + _SC_PHYS_PAGES => -1, + _SC_AVPHYS_PAGES => -1, + _SC_ATEXIT_MAX => -1, + _SC_PASS_MAX => -1, + _SC_XOPEN_VERSION => _XOPEN_VERSION, + _SC_XOPEN_XCU_VERSION => _XOPEN_VERSION, + _SC_XOPEN_UNIX => 1, + _SC_XOPEN_CRYPT => -1, + _SC_XOPEN_ENH_I18N => 1, + _SC_XOPEN_SHM => 1, + _SC_2_CHAR_TERM => -1, + _SC_2_UPE => -1, + _SC_XOPEN_XPG2 => -1, + _SC_XOPEN_XPG3 => -1, + _SC_XOPEN_XPG4 => -1, + // TODO: ? + _SC_NZERO => -1, + _SC_XBS5_ILP32_OFF32 => -1, + _SC_XBS5_ILP32_OFFBIG => { + if size_of::() == 4 { + 1 + } else { + -1 + } + } + _SC_XBS5_LP64_OFF64 => { + if size_of::() == 8 { + 1 + } else { + -1 + } + } + _SC_XBS5_LPBIG_OFFBIG => -1, + _SC_XOPEN_LEGACY => -1, + _SC_XOPEN_REALTIME => -1, + _SC_XOPEN_REALTIME_THREADS => -1, + _SC_ADVISORY_INFO => _POSIX_VERSION, + _SC_BARRIERS => _POSIX_VERSION, + _SC_CLOCK_SELECTION => _POSIX_VERSION, + _SC_CPUTIME => _POSIX_VERSION, + _SC_THREAD_CPUTIME => _POSIX_VERSION, + _SC_MONOTONIC_CLOCK => _POSIX_VERSION, + _SC_READER_WRITER_LOCKS => _POSIX_VERSION, + _SC_SPIN_LOCKS => _POSIX_VERSION, + _SC_REGEXP => 1, + _SC_SHELL => 1, + _SC_SPAWN => _POSIX_VERSION, + _SC_SPORADIC_SERVER => -1, + _SC_THREAD_SPORADIC_SERVER => -1, + _SC_TIMEOUTS => _POSIX_VERSION, + _SC_TYPED_MEMORY_OBJECTS => -1, + _SC_2_PBS => -1, + _SC_2_PBS_ACCOUNTING => -1, + _SC_2_PBS_LOCATE => -1, + _SC_2_PBS_MESSAGE => -1, + _SC_2_PBS_TRACK => -1, + // TODO: SYMLOOP_MAX in paths.h + _SC_SYMLOOP_MAX => -1, + _SC_STREAMS => 0, + _SC_2_PBS_CHECKPOINT => -1, + _SC_V6_ILP32_OFF32 => -1, + _SC_V6_ILP32_OFFBIG => { + if size_of::() == 4 { + 1 + } else { + -1 + } + } + _SC_V6_LP64_OFF64 => { + if size_of::() == 8 { + 1 + } else { + -1 + } + } + _SC_V6_LPBIG_OFFBIG => -1, + _SC_HOST_NAME_MAX => HOST_NAME_MAX.try_into().unwrap_or(-1), + _SC_TRACE => -1, + _SC_TRACE_EVENT_FILTER => -1, + _SC_TRACE_INHERIT => -1, + _SC_TRACE_LOG => -1, + _SC_IPV6 => _POSIX_VERSION, + _SC_RAW_SOCKETS => _POSIX_VERSION, + _SC_V7_ILP32_OFF32 => -1, + _SC_V7_ILP32_OFFBIG => { + if size_of::() == 4 { + 1 + } else { + -1 + } + } + _SC_V7_LP64_OFF64 => { + if size_of::() == 8 { + 1 + } else { + -1 + } + } + _SC_V7_LPBIG_OFFBIG => -1, + _SC_SS_REPL_MAX => -1, + _SC_TRACE_EVENT_NAME_MAX => -1, + _SC_TRACE_NAME_MAX => -1, + _SC_TRACE_SYS_MAX => -1, + _SC_TRACE_USER_EVENT_MAX => -1, + _SC_XOPEN_STREAMS => 0, + _SC_THREAD_ROBUST_PRIO_INHERIT => -1, + _SC_THREAD_ROBUST_PRIO_PROTECT => -1, + // TODO: Working getauxval + _SC_MINSIGSTKSZ => -1, + _SC_SIGSTKSZ => -1, + _ => { + platform::ERRNO.set(errno::EINVAL); + -1 + } + } +} diff --git a/src/header/unistd/sysconf/redox.rs b/src/header/unistd/sysconf/redox.rs new file mode 100644 index 0000000000..3d7f96dc28 --- /dev/null +++ b/src/header/unistd/sysconf/redox.rs @@ -0,0 +1,147 @@ +use core::convert::TryInto; + +use alloc::string::String; + +use crate::{ + error::Errno, + fs::File, + header::{errno, fcntl, limits, sys_resource, sys_statvfs}, + io::Read, + out::Out, + platform::{ + self, Pal, Sys, + types::{c_int, c_long}, + }, +}; + +// POSIX.1 { +pub const _SC_ARG_MAX: c_int = 0; +pub const _SC_CHILD_MAX: c_int = 1; +pub const _SC_CLK_TCK: c_int = 2; +pub const _SC_NGROUPS_MAX: c_int = 3; +pub const _SC_OPEN_MAX: c_int = 4; +pub const _SC_STREAM_MAX: c_int = 5; +pub const _SC_TZNAME_MAX: c_int = 6; +// ... +pub const _SC_TIMERS: c_int = 11; +// ... +pub const _SC_SHARED_MEMORY_OBJECTS: c_int = 22; +// ... +pub const _SC_VERSION: c_int = 29; +pub const _SC_PAGESIZE: c_int = 30; +pub const _SC_PAGE_SIZE: c_int = 30; +// ... +pub const _SC_RE_DUP_MAX: c_int = 44; + +pub const _SC_NPROCESSORS_CONF: c_int = 57; +pub const _SC_NPROCESSORS_ONLN: c_int = 58; +pub const _SC_PHYS_PAGES: c_int = 59; +pub const _SC_AVPHYS_PAGES: c_int = 60; + +// ... +pub const _SC_THREADS: c_int = 67; +pub const _SC_GETGR_R_SIZE_MAX: c_int = 69; +pub const _SC_GETPW_R_SIZE_MAX: c_int = 70; +pub const _SC_LOGIN_NAME_MAX: c_int = 71; +pub const _SC_TTY_NAME_MAX: c_int = 72; +// ... +pub const _SC_THREAD_ATTR_STACKADDR: c_int = 77; +pub const _SC_THREAD_ATTR_STACKSIZE: c_int = 78; +// ... +pub const _SC_MONOTONIC_CLOCK: c_int = 149; +pub const _SC_SEMAPHORES: c_int = 21; +// ... +pub const _SC_BARRIERS: c_int = 133; +// ... +pub const _SC_SHELL: c_int = 157; +// ... +pub const _SC_TIMEOUTS: c_int = 164; +// ... +pub const _SC_SYMLOOP_MAX: c_int = 173; +// ... +pub const _SC_HOST_NAME_MAX: c_int = 180; +// ... +pub const _SC_SIGQUEUE_MAX: c_int = 190; +pub const _SC_REALTIME_SIGNALS: c_int = 191; +// } POSIX.1 + +fn resource_limit_sysconf(resource: c_int) -> c_long { + let mut lim = core::mem::MaybeUninit::::uninit(); + let r = unsafe { sys_resource::getrlimit(resource, lim.as_mut_ptr()) }; + if r != 0 { + return -1; + } + + let cur = unsafe { lim.assume_init() }.rlim_cur; + if cur == sys_resource::RLIM_INFINITY { + -1 + } else if cur > c_long::MAX as u64 { + c_long::MAX + } else { + cur as c_long + } +} + +pub(super) fn sysconf_impl(name: c_int) -> c_long { + //TODO: Real values + match name { + _SC_ARG_MAX => 4096, + _SC_CHILD_MAX => resource_limit_sysconf(sys_resource::RLIMIT_NPROC as c_int), + _SC_CLK_TCK => 100, + _SC_NGROUPS_MAX => limits::NGROUPS_MAX as c_long, + _SC_OPEN_MAX => resource_limit_sysconf(sys_resource::RLIMIT_NOFILE as c_int), + _SC_STREAM_MAX => 16, + _SC_TZNAME_MAX => -1, + _SC_VERSION => 200809, + _SC_PAGESIZE => Sys::getpagesize().try_into().unwrap_or(-1), + _SC_RE_DUP_MAX => 32767, + _SC_GETGR_R_SIZE_MAX => -1, + _SC_GETPW_R_SIZE_MAX => -1, + _SC_LOGIN_NAME_MAX => 256, + _SC_TTY_NAME_MAX => 32, + _SC_MONOTONIC_CLOCK => 200112, + _SC_SEMAPHORES => 200112, + _SC_BARRIERS => 202405, + _SC_SHELL => 1, + _SC_SHARED_MEMORY_OBJECTS => 200112, + _SC_THREADS => 202405, + _SC_THREAD_ATTR_STACKADDR => 202405, + _SC_THREAD_ATTR_STACKSIZE => 202405, + _SC_TIMEOUTS => 202405, + _SC_TIMERS => 202405, + _SC_SYMLOOP_MAX => -1, + _SC_HOST_NAME_MAX => limits::HOST_NAME_MAX.try_into().unwrap_or(-1), + _SC_NPROCESSORS_CONF => get_cpu_count().unwrap_or(None).unwrap_or(1), + _SC_NPROCESSORS_ONLN => get_cpu_count().unwrap_or(None).unwrap_or(1), + _SC_PHYS_PAGES => get_mem_stat().map(|s| s.f_blocks as c_long).unwrap_or(-1), + _SC_AVPHYS_PAGES => get_mem_stat().map(|s| s.f_bfree as c_long).unwrap_or(-1), + _SC_SIGQUEUE_MAX => 32, + _SC_REALTIME_SIGNALS => 202405, + _ => { + platform::ERRNO.set(errno::EINVAL); + -1 + } + } +} + +pub fn get_cpu_count() -> Result, Errno> { + let mut string = String::new(); + let mut file = File::open(c"/scheme/sys/cpu".into(), fcntl::O_RDONLY)?; + file.read_to_string(&mut string) + .map_err(|_| Errno(errno::EIO).sync())?; + + Ok(string + .lines() + .find(|line| line.starts_with("CPUs:")) + .and_then(|line| line.split(':').nth(1)) + .and_then(|num_str| num_str.trim().parse::().ok())) +} + +pub fn get_mem_stat() -> Result { + let fd = Sys::open(c"/scheme/memory".into(), fcntl::O_PATH, 0)?; + let mut buf = sys_statvfs::statvfs::default(); + let res = Sys::fstatvfs(fd, Out::from_mut(&mut buf)); + if let Ok(()) = Sys::close(fd) {}; // TODO handle error + let _ = res?; + return Ok(buf); +} diff --git a/src/header/utime/cbindgen.toml b/src/header/utime/cbindgen.toml index e4e125542a..1a9357eaaa 100644 --- a/src/header/utime/cbindgen.toml +++ b/src/header/utime/cbindgen.toml @@ -1,8 +1,8 @@ -sys_includes = [] +sys_includes = ["sys/types.h", "features.h"] include_guard = "_RELIBC_UTIME_H" language = "C" style = "Tag" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/utime/mod.rs b/src/header/utime/mod.rs index 5098cec7f5..88e10e1062 100644 --- a/src/header/utime/mod.rs +++ b/src/header/utime/mod.rs @@ -1,11 +1,22 @@ -//! utime implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/utime.h.html +//! `utime.h` implementation. +//! +//! See . +//! +//! The `utime.h` header was marked obsolescent in the Open Group Base +//! Specifications Issue 7, and removed in Issue 8. use crate::{ c_str::CStr, - header::time::timespec, - platform::{types::*, Pal, Sys}, + error::ResultExt, + header::bits_timespec::timespec, + platform::{ + Pal, Sys, + types::{c_char, c_int, time_t}, + }, }; +/// See . +#[deprecated] #[repr(C)] #[derive(Clone)] pub struct utimbuf { @@ -13,18 +24,24 @@ pub struct utimbuf { pub modtime: time_t, } -#[no_mangle] +/// See . +#[deprecated] +#[allow(deprecated)] +#[unsafe(no_mangle)] pub unsafe extern "C" fn utime(filename: *const c_char, times: *const utimbuf) -> c_int { - let filename = CStr::from_ptr(filename); + let filename_cstr = unsafe { CStr::from_ptr(filename) }; + let times_ref = unsafe { &*times }; let times_spec = [ timespec { - tv_sec: (*times).actime, + tv_sec: times_ref.actime, tv_nsec: 0, }, timespec { - tv_sec: (*times).modtime, + tv_sec: times_ref.modtime, tv_nsec: 0, }, ]; - Sys::utimens(filename, times_spec.as_ptr()) + unsafe { Sys::utimens(filename_cstr, times_spec.as_ptr()) } + .map(|()| 0) + .or_minus_one_errno() } diff --git a/src/header/_wctype/cbindgen.toml b/src/header/utmp/cbindgen.toml similarity index 76% rename from src/header/_wctype/cbindgen.toml rename to src/header/utmp/cbindgen.toml index 9477e2aa38..386681d572 100644 --- a/src/header/_wctype/cbindgen.toml +++ b/src/header/utmp/cbindgen.toml @@ -1,5 +1,5 @@ sys_includes = [] -include_guard = "_RELIBC_WCTYPE_H" +include_guard = "_RELIBC_UTMP_H" language = "C" style = "Tag" no_includes = true diff --git a/src/header/utmp/mod.rs b/src/header/utmp/mod.rs new file mode 100644 index 0000000000..8512050b29 --- /dev/null +++ b/src/header/utmp/mod.rs @@ -0,0 +1,40 @@ +//! `utmp.h` implementation. +//! +//! Non-POSIX, see . + +use crate::{ + header::{sys_ioctl, unistd}, + platform::types::{c_int, c_void}, +}; + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn login_tty(fd: c_int) -> c_int { + // Create a new session + unistd::setsid(); + + // Set controlling terminal + let mut arg: c_int = 0; + if unsafe { + sys_ioctl::ioctl( + fd, + sys_ioctl::TIOCSCTTY, + core::ptr::from_mut::(&mut arg).cast::(), + ) + } != 0 + { + return -1; + } + + // Overwrite stdio + unistd::dup2(fd, 0); + unistd::dup2(fd, 1); + unistd::dup2(fd, 2); + + // Close if needed + if fd > 2 { + unistd::close(fd); + } + + 0 +} diff --git a/src/header/wchar/cbindgen.toml b/src/header/wchar/cbindgen.toml index 1a2836a9e7..3ff662b0a3 100644 --- a/src/header/wchar/cbindgen.toml +++ b/src/header/wchar/cbindgen.toml @@ -1,10 +1,43 @@ -sys_includes = ["stddef.h", "stdint.h", "time.h", "stdio.h" ] +# wint_t defined before stddef.h to avoid conflict with GCC's __need_wint_t. +# wchar_t provided by bits/wchar-t.h → stddef.h (no local redefinition needed). +# mbstate_t defined before transitive includes to break circular chains: +# wchar.h → stdio.h → inttypes.h → wchar.h (gnulib-based: m4, bison, flex). +after_includes = """ +#include // for uint32_t, WCHAR_MIN, WCHAR_MAX + +#ifndef _WINT_T +#define _WINT_T + #ifndef __WINT_TYPE__ + #define __WINT_TYPE__ uint32_t + #endif + typedef __WINT_TYPE__ wint_t; +#endif // _WINT_T + +#ifndef _RELIBC_MBSTATE_T +#define _RELIBC_MBSTATE_T +typedef struct {} mbstate_t; +#endif + +// wchar_t comes from stddef.h → bits/wchar-t.h +#define __need_size_t +#define __need_NULL +#include +#include // for FILE +#include // for struct tm + +#define WEOF (0xffffffffu) +""" include_guard = "_RELIBC_WCHAR_H" -header = "#include " language = "C" style = "Type" no_includes = true cpp_compat = true +[export] +exclude = ["mbstate_t"] + +[export.rename] +"tm" = "struct tm" + [enum] prefix_with_name = true diff --git a/src/header/wchar/mod.rs b/src/header/wchar/mod.rs index cde6a5e85b..5ef3ca862e 100644 --- a/src/header/wchar/mod.rs +++ b/src/header/wchar/mod.rs @@ -1,20 +1,44 @@ -//! wchar implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/wchar.h.html +//! `wchar.h` implementation. +//! +//! See . -use core::{char, ffi::VaList as va_list, mem, ptr, slice, usize}; +use core::{char, ffi::VaList as va_list, mem, ptr, slice}; use crate::{ + c_str::{WStr, Wide}, header::{ - ctype::isspace, errno::ERANGE, stdio::*, stdlib::MB_CUR_MAX, string, time::*, wctype::*, + ctype::isspace, + errno::{EILSEQ, ENOMEM, ERANGE}, + stdio::*, + stdlib::{MB_CUR_MAX, MB_LEN_MAX, malloc}, + string, + time::*, + wchar::reader::Reader, + wctype::*, + }, + iter::{NulTerminated, NulTerminatedInclusive}, + platform::{ + self, ERRNO, + types::{ + c_char, c_double, c_int, c_long, c_longlong, c_uchar, c_ulong, c_ulonglong, c_void, + size_t, wchar_t, wint_t, + }, }, - platform::{self, types::*}, }; mod utf8; +mod wprintf; +mod wscanf; + +pub use utf8::get_char_encoded_length; + +/// See . #[repr(C)] #[derive(Clone, Copy)] pub struct mbstate_t; -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn btowc(c: c_int) -> wint_t { //Check for EOF if c == EOF { @@ -25,103 +49,173 @@ pub unsafe extern "C" fn btowc(c: c_int) -> wint_t { let c = uc as c_char; let mut ps: mbstate_t = mbstate_t; let mut wc: wchar_t = 0; - let saved_errno = platform::errno; - let status = mbrtowc(&mut wc, &c as *const c_char, 1, &mut ps); - if status == usize::max_value() || status == usize::max_value() - 1 { - platform::errno = saved_errno; + let saved_errno = platform::ERRNO.get(); + let status = unsafe { mbrtowc(&raw mut wc, ptr::from_ref::(&c), 1, &raw mut ps) }; + if status == usize::MAX || status == usize::MAX - 1 { + platform::ERRNO.set(saved_errno); return WEOF; } wc as wint_t } -#[no_mangle] -pub unsafe extern "C" fn fgetwc(stream: *mut FILE) -> wint_t { - //TODO: Real multibyte - btowc(fgetc(stream)) -} +// not in POSIX. +pub unsafe fn fgetwc_unlocked(stream: *mut FILE) -> wint_t { + // TODO: Process locale + let mut buf: [c_uchar; MB_CUR_MAX as usize] = [0; MB_CUR_MAX as usize]; + let mut encoded_length = 0; + let mut bytes_read = 0; + let mut wc: wchar_t = 0; -#[no_mangle] -pub unsafe extern "C" fn fgetws(ws: *mut wchar_t, n: c_int, stream: *mut FILE) -> *mut wchar_t { - //TODO: lock - let mut i = 0; - while ((i + 1) as c_int) < n { - let wc = fgetwc(stream); - if wc == WEOF { - return ptr::null_mut(); + loop { + unsafe { + let ret = getc_unlocked(stream); + if ret == EOF { + return WEOF; + } + *buf.as_mut_ptr().add(bytes_read) = ret as c_uchar; + } + + bytes_read += 1; + + if bytes_read == 1 { + encoded_length = if let Some(el) = get_char_encoded_length(buf[0]) { + el + } else { + unsafe { + (*stream).flags |= F_ERR; + } + ERRNO.set(EILSEQ); + return WEOF; + }; + } + + if bytes_read >= encoded_length { + break; } - *ws.add(i) = wc as wchar_t; - i += 1; } - while (i as c_int) < n { - *ws.add(i) = 0; - i += 1; - } - ws + + unsafe { + mbrtowc( + &raw mut wc, + buf.as_ptr().cast::(), + encoded_length, + ptr::null_mut(), + ) + }; + + wc as wint_t } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fgetwc(stream: *mut FILE) -> wint_t { + let mut stream = unsafe { (*stream).lock() }; + unsafe { fgetwc_unlocked(&raw mut *stream) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fgetws(ws: *mut wchar_t, n: c_int, stream: *mut FILE) -> *mut wchar_t { + let mut i = 0; + let mut stream = unsafe { (*stream).lock() }; + while ((i + 1) as c_int) < n { + let wc = unsafe { fgetwc_unlocked(&raw mut *stream) }; + if wc == WEOF { + break; + } + unsafe { *ws.add(i) = wc as wchar_t }; + i += 1; + if wc as wchar_t == '\n' as wchar_t { + break; + } + } + // NUL-terminate result + unsafe { *ws.add(i) = 0 }; + if i == 0 || unsafe { ferror(&raw mut *stream) != 0 } { + core::ptr::null_mut() + } else { + ws + } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn fputwc(wc: wchar_t, stream: *mut FILE) -> wint_t { //Convert wchar_t to multibytes first static mut INTERNAL: mbstate_t = mbstate_t; let mut bytes: [c_char; MB_CUR_MAX as usize] = [0; MB_CUR_MAX as usize]; - let amount = wcrtomb(bytes.as_mut_ptr(), wc, &mut INTERNAL); + let amount = unsafe { wcrtomb(bytes.as_mut_ptr(), wc, &raw mut INTERNAL) }; - for i in 0..amount { - fputc(bytes[i] as c_int, &mut *stream); + for b in bytes.iter().take(amount) { + unsafe { fputc(c_int::from(*b), &raw mut *stream) }; } wc as wint_t } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn fputws(ws: *const wchar_t, stream: *mut FILE) -> c_int { let mut i = 0; loop { - let wc = *ws.add(i); + let wc = unsafe { *ws.add(i) }; if wc == 0 { return 0; } - if fputwc(wc, stream) == WEOF { + if unsafe { fputwc(wc, stream) } == WEOF { return -1; } i += 1; } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn fwide(stream: *mut FILE, mode: c_int) -> c_int { - (*stream).try_set_orientation(mode) + unsafe { (*stream).try_set_orientation(mode) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwscanf( + stream: *mut FILE, + format: *const wchar_t, + mut __valist: ... +) -> c_int { + unsafe { vfwscanf(stream, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getwc(stream: *mut FILE) -> wint_t { - fgetwc(stream) + unsafe { fgetwc(stream) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn getwchar() -> wint_t { - fgetwc(stdin) + unsafe { fgetwc(stdin) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbsinit(ps: *const mbstate_t) -> c_int { //Add a check for the state maybe - if ps.is_null() { - 1 - } else { - 0 - } + if ps.is_null() { 1 } else { 0 } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbrlen(s: *const c_char, n: size_t, ps: *mut mbstate_t) -> size_t { static mut INTERNAL: mbstate_t = mbstate_t; - mbrtowc(ptr::null_mut(), s, n, &mut INTERNAL) + unsafe { mbrtowc(ptr::null_mut(), s, n, &raw mut INTERNAL) } } -//Only works for UTF8 at the moment -#[no_mangle] +/// See . +/// +/// Only works for UTF8 at the moment. +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbrtowc( pwc: *mut wchar_t, s: *const c_char, @@ -131,19 +225,20 @@ pub unsafe extern "C" fn mbrtowc( static mut INTERNAL: mbstate_t = mbstate_t; if ps.is_null() { - let ps = &mut INTERNAL; + let ps = &raw mut INTERNAL; } if s.is_null() { let xs: [c_char; 1] = [0]; - utf8::mbrtowc(pwc, &xs[0] as *const c_char, 1, ps) + unsafe { utf8::mbrtowc(pwc, ptr::from_ref::(&xs[0]), 1, ps) } } else { - utf8::mbrtowc(pwc, s, n, ps) + unsafe { utf8::mbrtowc(pwc, s, n, ps) } } } -//Convert a multibyte string to a wide string with a limited amount of bytes -//Required for in POSIX.1-2008 -#[no_mangle] +/// See . +/// +/// Convert a multibyte string to a wide string with a limited amount of bytes. +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbsnrtowcs( dst_ptr: *mut wchar_t, src_ptr: *mut *const c_char, @@ -154,34 +249,34 @@ pub unsafe extern "C" fn mbsnrtowcs( static mut INTERNAL: mbstate_t = mbstate_t; if ps.is_null() { - let ps = &mut INTERNAL; + let ps = &raw mut INTERNAL; } - let mut src = *src_ptr; + let mut src = unsafe { *src_ptr }; let mut dst_offset: usize = 0; let mut src_offset: usize = 0; while (dst_ptr.is_null() || dst_offset < dst_len) && src_offset < src_len { - let ps_copy = *ps; + let ps_copy = unsafe { *ps }; let mut wc: wchar_t = 0; - let amount = mbrtowc(&mut wc, src.add(src_offset), src_len - src_offset, ps); + let amount = unsafe { mbrtowc(&raw mut wc, src.add(src_offset), src_len - src_offset, ps) }; // Stop in the event a decoding error occured. if amount == -1isize as usize { - *src_ptr = src.add(src_offset); + unsafe { *src_ptr = src.add(src_offset) }; return 1isize as usize; } // Stop decoding early in the event we encountered a partial character. if amount == -2isize as usize { - *ps = ps_copy; + unsafe { *ps = ps_copy }; break; } // Store the decoded wide character in the destination buffer. if !dst_ptr.is_null() { - *dst_ptr.add(dst_offset) = wc; + unsafe { *dst_ptr.add(dst_offset) = wc }; } // Stop decoding after decoding a null character and return a NULL @@ -197,73 +292,166 @@ pub unsafe extern "C" fn mbsnrtowcs( src_offset += amount; } - *src_ptr = src.add(src_offset); + unsafe { *src_ptr = src.add(src_offset) }; dst_offset } -//Convert a multibyte string to a wide string -#[no_mangle] +/// See . +/// +/// Convert a multibyte string to a wide string. +#[unsafe(no_mangle)] pub unsafe extern "C" fn mbsrtowcs( dst: *mut wchar_t, src: *mut *const c_char, len: size_t, ps: *mut mbstate_t, ) -> size_t { - mbsnrtowcs(dst, src, size_t::max_value(), len, ps) + unsafe { mbsnrtowcs(dst, src, size_t::MAX, len, ps) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn putwc(wc: wchar_t, stream: *mut FILE) -> wint_t { - fputwc(wc, &mut *stream) + unsafe { fputwc(wc, &raw mut *stream) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn putwchar(wc: wchar_t) -> wint_t { - fputwc(wc, &mut *stdout) + unsafe { fputwc(wc, &raw mut *stdout) } } -// #[no_mangle] -pub extern "C" fn swprintf( - s: *mut wchar_t, - n: size_t, +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vswscanf( + s: *const wchar_t, format: *const wchar_t, - ap: va_list, + __valist: va_list, ) -> c_int { - unimplemented!(); + unsafe { + let format = WStr::from_ptr(format); + let s = WStr::from_ptr(s); + wscanf::scanf(s.into(), format.into(), __valist) + } } -// #[no_mangle] -pub extern "C" fn swscanf(s: *const wchar_t, format: *const wchar_t, ap: va_list) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn swscanf( + s: *const wchar_t, + format: *const wchar_t, + mut __valist: ... +) -> c_int { + unsafe { vswscanf(s, format, __valist) } } -// #[no_mangle] -pub extern "C" fn ungetwc(wc: wint_t, stream: *mut FILE) -> wint_t { - unimplemented!(); +/// See . +/// +/// Push wide character `wc` back onto `stream` so it'll be read next +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ungetwc(wc: wint_t, stream: &mut FILE) -> wint_t { + if wc == WEOF { + return wc; + } + static mut INTERNAL: mbstate_t = mbstate_t; + let mut bytes: [c_char; MB_CUR_MAX as usize] = [0; MB_CUR_MAX as usize]; + + let amount = unsafe { wcrtomb(bytes.as_mut_ptr(), wc as wchar_t, &raw mut INTERNAL) }; + if amount == usize::MAX { + return WEOF; + } + + /* + We might have unget multiple bytes for a single wchar, eg, `ç` is [195, 167]. + We need to unget them in reversed, so they are pused as [..., 167, 195, ...] + When we do fgetwc, we pop from the Vec, getting the write order of bytes [195, 167]. + If we called ungetc in the non-reversed order, we would get [167, 195] + */ + for i in 0..amount { + unsafe { ungetc(c_int::from(bytes[amount - 1 - i]), &raw mut *stream) }; + } + + wc } -// #[no_mangle] -pub extern "C" fn vfwprintf(stream: *mut FILE, format: *const wchar_t, arg: va_list) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vfwprintf( + stream: *mut FILE, + format: *const wchar_t, + arg: va_list, +) -> c_int { + let mut stream = unsafe { (*stream).lock() }; + if (*stream).try_set_wide_orientation_unlocked().is_err() { + return -1; + } + + unsafe { wprintf::wprintf(&mut *stream, WStr::from_ptr(format), arg) } } -// #[no_mangle] -pub extern "C" fn vwprintf(format: *const wchar_t, arg: va_list) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fwprintf( + stream: *mut FILE, + format: *const wchar_t, + mut __valist: ... +) -> c_int { + unsafe { vfwprintf(stream, format, __valist) } } -// #[no_mangle] -pub extern "C" fn vswprintf( +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vwprintf(format: *const wchar_t, arg: va_list) -> c_int { + unsafe { vfwprintf(&raw mut *stdout, format, arg) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wprintf(format: *const wchar_t, mut __valist: ...) -> c_int { + unsafe { vfwprintf(&raw mut *stdout, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vswprintf( s: *mut wchar_t, n: size_t, format: *const wchar_t, arg: va_list, ) -> c_int { - unimplemented!(); + //TODO: implement vswprintf. This is not as simple as wprintf, since the output is not UTF-8 + // but instead is a wchar array. + todo_skip!(0, "vswprintf not implemented"); + -1 } -//widechar to multibyte -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn swprintf( + s: *mut wchar_t, + n: size_t, + format: *const wchar_t, + mut __valist: ... +) -> c_int { + unsafe { vswprintf(s, n, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcpcpy(d: *mut wchar_t, s: *const wchar_t) -> *mut wchar_t { + unsafe { (wcscpy(d, s)).add(wcslen(s)) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcpncpy(d: *mut wchar_t, s: *const wchar_t, n: size_t) -> *mut wchar_t { + unsafe { (wcsncpy(d, s, n)).add(wcsnlen(s, n)) } +} + +/// See . +/// +/// widechar to multibyte. +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcrtomb(s: *mut c_char, wc: wchar_t, ps: *mut mbstate_t) -> size_t { let mut buffer: [c_char; MB_CUR_MAX as usize] = [0; MB_CUR_MAX as usize]; let (s_cpy, wc_cpy) = if s.is_null() { @@ -272,44 +460,85 @@ pub unsafe extern "C" fn wcrtomb(s: *mut c_char, wc: wchar_t, ps: *mut mbstate_t (s, wc) }; - utf8::wcrtomb(s_cpy, wc_cpy, ps) + unsafe { utf8::wcrtomb(s_cpy, wc_cpy, ps) } } -#[no_mangle] -pub unsafe extern "C" fn wcscat(ws1: *mut wchar_t, ws2: *const wchar_t) -> *mut wchar_t { - wcsncat(ws1, ws2, usize::MAX) -} +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcsdup(s: *const wchar_t) -> *mut wchar_t { + let l = unsafe { wcslen(s) }; -#[no_mangle] -pub unsafe extern "C" fn wcschr(ws: *const wchar_t, wc: wchar_t) -> *mut wchar_t { - let mut i = 0; - loop { - if *ws.add(i) == wc { - return ws.add(i) as *mut wchar_t; - } else if *ws.add(i) == 0 { - return ptr::null_mut(); - } - i += 1; + let d = unsafe { malloc((l + 1) * mem::size_of::()) }.cast::(); + + if d.is_null() { + ERRNO.set(ENOMEM); + return ptr::null_mut(); } + + unsafe { wmemcpy(d, s, l + 1) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcsrtombs( + s: *mut c_char, + ws: *mut *const wchar_t, + n: size_t, + mut st: *mut mbstate_t, +) -> size_t { + let mut mbs = mbstate_t {}; + if st.is_null() { + st = &raw mut mbs; + } + unsafe { wcsnrtombs(s, ws, size_t::MAX, n, st) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcscat(ws1: *mut wchar_t, ws2: *const wchar_t) -> *mut wchar_t { + unsafe { wcsncat(ws1, ws2, usize::MAX) } +} + +/// See . +/// +/// # Safety +/// The caller is required to ensure that `ws` is a valid pointer to a buffer +/// containing at least one nul value. The pointed-to buffer must not be +/// modified for the duration of the call. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcschr(ws: *const wchar_t, wc: wchar_t) -> *mut wchar_t { + // We iterate over non-mut references and thus need to coerce the + // resulting reference via a *const pointer before we can get our *mut. + // SAFETY: the caller is required to ensure that ws points to a valid + // nul-terminated buffer. + let ptr: *const wchar_t = + match unsafe { NulTerminatedInclusive::new(ws) }.find(|&&wsc| wsc == wc) { + Some(wsc_ref) => wsc_ref, + None => ptr::null(), + }; + ptr.cast_mut() +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcscmp(ws1: *const wchar_t, ws2: *const wchar_t) -> c_int { - wcsncmp(ws1, ws2, usize::MAX) + unsafe { wcsncmp(ws1, ws2, usize::MAX) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcscoll(ws1: *const wchar_t, ws2: *const wchar_t) -> c_int { //TODO: locale comparison - wcscmp(ws1, ws2) + unsafe { wcscmp(ws1, ws2) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcscpy(ws1: *mut wchar_t, ws2: *const wchar_t) -> *mut wchar_t { let mut i = 0; loop { - let wc = *ws2.add(i); - *ws1.add(i) = wc; + let wc = unsafe { *ws2.add(i) }; + unsafe { *ws1.add(i) = wc }; i += 1; if wc == 0 { return ws1; @@ -319,65 +548,65 @@ pub unsafe extern "C" fn wcscpy(ws1: *mut wchar_t, ws2: *const wchar_t) -> *mut unsafe fn inner_wcsspn(mut wcs: *const wchar_t, set: *const wchar_t, reject: bool) -> size_t { let mut count = 0; - while (*wcs) != 0 && wcschr(set, *wcs).is_null() == reject { - wcs = wcs.add(1); + while unsafe { *wcs } != 0 && unsafe { wcschr(set, *wcs).is_null() } == reject { + wcs = unsafe { wcs.add(1) }; count += 1; } count } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcscspn(wcs: *const wchar_t, set: *const wchar_t) -> size_t { - inner_wcsspn(wcs, set, true) + unsafe { inner_wcsspn(wcs, set, true) } } -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn wcsftime( wcs: *mut wchar_t, maxsize: size_t, format: *const wchar_t, - timptr: *mut tm, + timptr: *const tm, ) -> size_t { - unimplemented!(); + todo_skip!(0, "wcsftime is not implemented"); + 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcslen(ws: *const wchar_t) -> size_t { - let mut i = 0; - loop { - if *ws.add(i) == 0 { - return i; - } - i += 1; - } + unsafe { NulTerminated::new(ws).unwrap() }.count() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsncat( ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t, ) -> *mut wchar_t { - let len = wcslen(ws1); - let dest = ws1.add(len); + let len = unsafe { wcslen(ws1) }; + let dest = unsafe { ws1.add(len) }; let mut i = 0; while i < n { - let wc = *ws2.add(i); + let wc = unsafe { *ws2.add(i) }; if wc == 0 { break; } - *dest.add(i) = wc; + unsafe { *dest.add(i) = wc }; i += 1; } - *dest.add(i) = 0; + unsafe { *dest.add(i) = 0 }; ws1 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsncmp(ws1: *const wchar_t, ws2: *const wchar_t, n: size_t) -> c_int { for i in 0..n { - let wc1 = *ws1.add(i); - let wc2 = *ws2.add(i); + let wc1 = unsafe { *ws1.add(i) }; + let wc2 = unsafe { *ws2.add(i) }; if wc1 != wc2 { return wc1 - wc2; } else if wc1 == 0 { @@ -387,7 +616,8 @@ pub unsafe extern "C" fn wcsncmp(ws1: *const wchar_t, ws2: *const wchar_t, n: si 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsncpy( ws1: *mut wchar_t, ws2: *const wchar_t, @@ -395,76 +625,136 @@ pub unsafe extern "C" fn wcsncpy( ) -> *mut wchar_t { let mut i = 0; while i < n { - let wc = *ws2.add(i); - *ws1.add(i) = wc; + let wc = unsafe { *ws2.add(i) }; + unsafe { *ws1.add(i) = wc }; i += 1; if wc == 0 { break; } } while i < n { - *ws1.add(i) = 0; + unsafe { *ws1.add(i) = 0 }; i += 1; } ws1 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcsnlen(mut s: *const wchar_t, maxlen: size_t) -> size_t { + let mut len = 0; + + while len < maxlen { + if unsafe { *s } == 0 { + break; + } + + len += 1; + s = unsafe { s.offset(1) }; + } + + len +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcsnrtombs( + mut dest: *mut c_char, + src: *mut *const wchar_t, + nwc: size_t, + len: size_t, + mut ps: *mut mbstate_t, +) -> size_t { + let mut written = 0; + let mut read = 0; + let mut buf: [c_char; MB_LEN_MAX as usize] = [0; MB_LEN_MAX as usize]; + let mut mbs = mbstate_t {}; + + if ps.is_null() { + ps = &raw mut mbs; + } + + while read < nwc { + buf.fill(0); + + let ret = unsafe { wcrtomb(buf.as_mut_ptr(), **src, ps) }; + + if ret == size_t::MAX { + ERRNO.set(EILSEQ); + return size_t::MAX; + } + + if !dest.is_null() && len < written + ret { + return written; + } + + if !dest.is_null() { + unsafe { ptr::copy_nonoverlapping(buf.as_ptr(), dest, ret) }; + dest = unsafe { dest.add(ret) }; + } + + if unsafe { **src } == '\0' as wchar_t { + unsafe { *src = ptr::null() }; + return written; + } + + unsafe { *src = (*src).add(1) }; + read += 1; + written += ret; + } + written +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcspbrk(mut wcs: *const wchar_t, set: *const wchar_t) -> *mut wchar_t { - wcs = wcs.add(wcscspn(wcs, set)); - if *wcs == 0 { + wcs = unsafe { wcs.add(wcscspn(wcs, set)) }; + if unsafe { *wcs } == 0 { ptr::null_mut() } else { // Once again, C wants us to transmute a const pointer to a // mutable one... - wcs as *mut _ + wcs.cast_mut() } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsrchr(ws1: *const wchar_t, wc: wchar_t) -> *mut wchar_t { - let mut last_matching_wc = 0 as *const wchar_t; + let mut last_matching_wc = ptr::null::(); let mut i = 0; - while *ws1.add(i) != 0 { - if *ws1.add(i) == wc { - last_matching_wc = ws1.add(i); + while unsafe { *ws1.add(i) } != 0 { + if unsafe { *ws1.add(i) } == wc { + last_matching_wc = unsafe { ws1.add(i) }; } i += 1; } - last_matching_wc as *mut wchar_t + last_matching_wc.cast_mut() } -// #[no_mangle] -pub extern "C" fn wcsrtombs( - dst: *mut c_char, - src: *mut *const wchar_t, - len: size_t, - ps: *mut mbstate_t, -) -> size_t { - unimplemented!(); -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsspn(wcs: *const wchar_t, set: *const wchar_t) -> size_t { - inner_wcsspn(wcs, set, false) + unsafe { inner_wcsspn(wcs, set, false) } } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcsstr(ws1: *const wchar_t, ws2: *const wchar_t) -> *mut wchar_t { // Get length of ws2, not including null terminator - let ws2_len = wcslen(ws2); + let ws2_len = unsafe { wcslen(ws2) }; // The standard says that we must return ws1 if ws2 has length 0 if ws2_len == 0 { - ws1 as *mut wchar_t + ws1.cast_mut() } else { - let ws1_len = wcslen(ws1); + let ws1_len = unsafe { wcslen(ws1) }; // Construct slices without null terminator - let ws1_slice = slice::from_raw_parts(ws1, ws1_len); - let ws2_slice = slice::from_raw_parts(ws2, ws2_len); + let ws1_slice = unsafe { slice::from_raw_parts(ws1, ws1_len) }; + let ws2_slice = unsafe { slice::from_raw_parts(ws2, ws2_len) }; /* Sliding ws2-sized window iterator on ws1. The iterator * returns None if ws2 is longer than ws1. */ @@ -473,61 +763,56 @@ pub unsafe extern "C" fn wcsstr(ws1: *const wchar_t, ws2: *const wchar_t) -> *mu /* Find the first offset into ws1 where the window is equal to * the ws2 contents. Return null pointer if no match is found. */ match ws1_windows.position(|ws1_window| ws1_window == ws2_slice) { - Some(pos) => ws1.add(pos) as *mut wchar_t, + Some(pos) => unsafe { ws1.add(pos).cast_mut() }, None => ptr::null_mut(), } } } -macro_rules! skipws { - ($ptr:expr) => { - while isspace(*$ptr) != 0 { - $ptr = $ptr.add(1); - } - }; -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcstod(mut ptr: *const wchar_t, end: *mut *mut wchar_t) -> c_double { const RADIX: u32 = 10; skipws!(ptr); - let negative = *ptr == '-' as wchar_t; + let negative = unsafe { *ptr } == '-' as wchar_t; if negative { - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } let mut result: c_double = 0.0; - while let Some(digit) = char::from_u32(*ptr as _).and_then(|c| c.to_digit(RADIX)) { + while let Some(digit) = char::from_u32(unsafe { *ptr } as _).and_then(|c| c.to_digit(RADIX)) { result *= 10.0; if negative { - result -= digit as c_double; + result -= c_double::from(digit); } else { - result += digit as c_double; + result += c_double::from(digit); } - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } - if *ptr == '.' as wchar_t { - ptr = ptr.add(1); + if unsafe { *ptr } == '.' as wchar_t { + ptr = unsafe { ptr.add(1) }; let mut scale = 1.0; - while let Some(digit) = char::from_u32(*ptr as _).and_then(|c| c.to_digit(RADIX)) { + while let Some(digit) = char::from_u32(unsafe { *ptr } as _).and_then(|c| c.to_digit(RADIX)) + { scale /= 10.0; if negative { - result -= digit as c_double * scale; + result -= c_double::from(digit) * scale; } else { - result += digit as c_double * scale; + result += c_double::from(digit) * scale; } - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } } if !end.is_null() { - *end = ptr as *mut _; + unsafe { *end = ptr.cast_mut() }; } result } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcstok( mut wcs: *mut wchar_t, delim: *const wchar_t, @@ -535,76 +820,35 @@ pub unsafe extern "C" fn wcstok( ) -> *mut wchar_t { // Choose starting position if wcs.is_null() { - if (*state).is_null() { + if (unsafe { *state }).is_null() { // There was no next token return ptr::null_mut(); } - wcs = *state; + wcs = unsafe { *state }; } // Advance past any delimiters - wcs = wcs.add(wcsspn(wcs, delim)); + wcs = unsafe { wcs.add(wcsspn(wcs, delim)) }; // Check end - if *wcs == 0 { - *state = ptr::null_mut(); + if unsafe { *wcs } == 0 { + unsafe { *state = ptr::null_mut() }; return ptr::null_mut(); } // Advance *to* any delimiters - let end = wcspbrk(wcs, delim); + let end = unsafe { wcspbrk(wcs, delim) }; if end.is_null() { - *state = ptr::null_mut(); + unsafe { *state = ptr::null_mut() }; } else { - *end = 0; - *state = end.add(1); + unsafe { *end = 0 }; + unsafe { *state = end.add(1) }; } wcs } -macro_rules! strtou_impl { - ($type:ident, $ptr:expr, $base:expr) => { - strtou_impl!($type, $ptr, $base, false) - }; - ($type:ident, $ptr:expr, $base:expr, $negative:expr) => {{ - if $base == 16 && *$ptr == '0' as wchar_t && *$ptr.add(1) | 0x20 == 'x' as wchar_t { - $ptr = $ptr.add(2); - } - - let mut result: $type = 0; - while let Some(digit) = char::from_u32(*$ptr as u32).and_then(|c| c.to_digit($base as u32)) - { - let new = result.checked_mul($base as $type).and_then(|result| { - if $negative { - result.checked_sub(digit as $type) - } else { - result.checked_add(digit as $type) - } - }); - result = match new { - Some(new) => new, - None => { - platform::errno = ERANGE; - return !0; - } - }; - - $ptr = $ptr.add(1); - } - result - }}; -} -macro_rules! strto_impl { - ($type:ident, $ptr:expr, $base:expr) => {{ - let negative = *$ptr == '-' as wchar_t; - if negative { - $ptr = $ptr.add(1); - } - strtou_impl!($type, $ptr, $base, negative) - }}; -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcstol( mut ptr: *const wchar_t, end: *mut *mut wchar_t, @@ -613,12 +857,28 @@ pub unsafe extern "C" fn wcstol( skipws!(ptr); let result = strto_impl!(c_long, ptr, base); if !end.is_null() { - *end = ptr as *mut _; + unsafe { *end = ptr.cast_mut() }; } result } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcstoll( + mut ptr: *const wchar_t, + end: *mut *mut wchar_t, + base: c_int, +) -> c_longlong { + skipws!(ptr); + let result = strto_impl!(c_longlong, ptr, base); + if !end.is_null() { + unsafe { *end = ptr.cast_mut() }; + } + result +} + +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wcstoul( mut ptr: *const wchar_t, end: *mut *mut wchar_t, @@ -627,55 +887,92 @@ pub unsafe extern "C" fn wcstoul( skipws!(ptr); let result = strtou_impl!(c_ulong, ptr, base); if !end.is_null() { - *end = ptr as *mut _; + unsafe { *end = ptr.cast_mut() }; } result } -// #[no_mangle] -pub extern "C" fn wcswcs(ws1: *const wchar_t, ws2: *const wchar_t) -> *mut wchar_t { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcstoull( + mut ptr: *const wchar_t, + end: *mut *mut wchar_t, + base: c_int, +) -> c_ulonglong { + skipws!(ptr); + let result = strtou_impl!(c_ulonglong, ptr, base); + if !end.is_null() { + unsafe { *end = ptr.cast_mut() }; + } + result } -// #[no_mangle] -pub extern "C" fn wcswidth(pwcs: *const wchar_t, n: size_t) -> c_int { - unimplemented!(); +/// See . +/// +/// Marked legacy in issue 6. +/// Encouraged to use `wcsstr` instead, which this implementation simply forwards to. +#[deprecated] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcswcs(ws1: *const wchar_t, ws2: *const wchar_t) -> *mut wchar_t { + unsafe { wcsstr(ws1, ws2) } } -// #[no_mangle] +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcswidth(pwcs: *const wchar_t, n: size_t) -> c_int { + let mut total_width = 0; + for i in 0..n { + let wc_width = wcwidth(unsafe { *pwcs.add(i) }); + if wc_width < 0 { + return -1; + } + total_width += wc_width; + } + total_width +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn wcsxfrm(ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t) -> size_t { - unimplemented!(); + todo_skip!(0, "wcsxfrm is not implemented"); + 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn wctob(c: wint_t) -> c_int { - if c <= 0x7F { - c as c_int - } else { - EOF + if c <= 0x7F { c as c_int } else { EOF } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn wcwidth(wc: wchar_t) -> c_int { + match char::from_u32(wc as u32) { + Some(c) => match unicode_width::UnicodeWidthChar::width(c) { + Some(width) => width as c_int, + None => -1, + }, + None => -1, } } -// #[no_mangle] -pub extern "C" fn wcwidth(wc: wchar_t) -> c_int { - unimplemented!(); -} - -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wmemchr(ws: *const wchar_t, wc: wchar_t, n: size_t) -> *mut wchar_t { for i in 0..n { - if *ws.add(i) == wc { - return ws.add(i) as *mut wchar_t; + if unsafe { *ws.add(i) } == wc { + return unsafe { ws.add(i) }.cast_mut(); } } ptr::null_mut() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wmemcmp(ws1: *const wchar_t, ws2: *const wchar_t, n: size_t) -> c_int { for i in 0..n { - let wc1 = *ws1.add(i); - let wc2 = *ws2.add(i); + let wc1 = unsafe { *ws1.add(i) }; + let wc2 = unsafe { *ws2.add(i) }; if wc1 != wc2 { return wc1 - wc2; } @@ -683,52 +980,85 @@ pub unsafe extern "C" fn wmemcmp(ws1: *const wchar_t, ws2: *const wchar_t, n: si 0 } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wmemcpy( ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t, ) -> *mut wchar_t { - string::memcpy( - ws1 as *mut c_void, - ws2 as *const c_void, - n * mem::size_of::(), - ) as *mut wchar_t + (unsafe { + string::memcpy( + ws1.cast::(), + ws2.cast::(), + n * mem::size_of::(), + ) + }) + .cast::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wmemmove( ws1: *mut wchar_t, ws2: *const wchar_t, n: size_t, ) -> *mut wchar_t { - string::memmove( - ws1 as *mut c_void, - ws2 as *const c_void, - n * mem::size_of::(), - ) as *mut wchar_t + (unsafe { + string::memmove( + ws1.cast::(), + ws2.cast::(), + n * mem::size_of::(), + ) + }) + .cast::() } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub unsafe extern "C" fn wmemset(ws: *mut wchar_t, wc: wchar_t, n: size_t) -> *mut wchar_t { for i in 0..n { - *ws.add(i) = wc; + unsafe { *ws.add(i) = wc }; } ws } -// #[no_mangle] -pub extern "C" fn wprintf(format: *const wchar_t, ap: va_list) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vfwscanf( + stream: *mut FILE, + format: *const wchar_t, + __valist: va_list, +) -> c_int { + let mut file = unsafe { (*stream).lock() }; + if file.try_set_byte_orientation_unlocked().is_err() { + return -1; + } + + let f: &mut FILE = &mut file; + let reader: Reader = f.into(); + + unsafe { + let format = WStr::from_ptr(format); + wscanf::scanf(reader, format.into(), __valist) + } } -// #[no_mangle] -pub extern "C" fn wscanf(format: *const wchar_t, ap: va_list) -> c_int { - unimplemented!(); +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn vwscanf(format: *const wchar_t, __valist: va_list) -> c_int { + unsafe { vfwscanf(stdin, format, __valist) } } -#[no_mangle] -pub extern "C" fn wcscasecmp(mut s1: *const wchar_t, mut s2: *const wchar_t) -> c_int { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wscanf(format: *const wchar_t, mut __valist: ...) -> c_int { + unsafe { vfwscanf(stdin, format, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcscasecmp(mut s1: *const wchar_t, mut s2: *const wchar_t) -> c_int { unsafe { while *s1 != 0 && *s2 != 0 { if towlower(*s1 as wint_t) != towlower(*s2 as wint_t) { @@ -738,12 +1068,17 @@ pub extern "C" fn wcscasecmp(mut s1: *const wchar_t, mut s2: *const wchar_t) -> s2 = s2.add(1); } let result = towlower(*s1 as wint_t).wrapping_sub(towlower(*s2 as wint_t)); - return result as c_int; + result as c_int } } -#[no_mangle] -pub extern "C" fn wcsncasecmp(mut s1: *const wchar_t, mut s2: *const wchar_t, n: size_t) -> c_int { +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wcsncasecmp( + mut s1: *const wchar_t, + mut s2: *const wchar_t, + n: size_t, +) -> c_int { if n == 0 { return 0; } @@ -755,6 +1090,6 @@ pub extern "C" fn wcsncasecmp(mut s1: *const wchar_t, mut s2: *const wchar_t, n: s1 = s1.add(1); s2 = s2.add(1); } - return 0; + 0 } } diff --git a/src/header/wchar/utf8.rs b/src/header/wchar/utf8.rs index e13c0ba5d7..dc0da1f94a 100644 --- a/src/header/wchar/utf8.rs +++ b/src/header/wchar/utf8.rs @@ -1,31 +1,64 @@ //UTF implementation parts for wchar.h. //Partially ported from the Sortix libc -use core::{char, slice, str, usize}; +use core::{char, slice, str}; use crate::{ header::errno, - platform::{self, types::*}, + platform::{ + self, + types::{c_char, wchar_t}, + }, }; use super::mbstate_t; +// Based on +// https://github.com/rust-lang/rust/blob/f24ce9b/library/core/src/str/validations.rs#L232-L257, +// because apparently somebody removed the `pub use` statement from `core::str`. + +// https://tools.ietf.org/html/rfc3629 +static UTF8_CHAR_WIDTH: [u8; 256] = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, // 0x1F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, // 0x3F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, // 0x5F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, // 0x7F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 0x9F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, // 0xBF + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, // 0xDF + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xEF + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xFF +]; + +// Given a first byte, determines how many bytes are in this UTF-8 character. +#[inline] +fn utf8_char_width(b: u8) -> usize { + UTF8_CHAR_WIDTH[usize::from(b)].into() +} + //It's guaranteed that we don't have any nullpointers here pub unsafe fn mbrtowc(pwc: *mut wchar_t, s: *const c_char, n: usize, ps: *mut mbstate_t) -> usize { - let size = str::utf8_char_width(*s as u8); + let size = utf8_char_width(unsafe { *s } as u8); if size > n { - platform::errno = errno::EILSEQ; + platform::ERRNO.set(errno::EILSEQ); return -2isize as usize; } if size == 0 { - platform::errno = errno::EILSEQ; + platform::ERRNO.set(errno::EILSEQ); return -1isize as usize; } - let slice = slice::from_raw_parts(s as *const u8, size); + let slice = unsafe { slice::from_raw_parts(s.cast::(), size) }; let decoded = str::from_utf8(slice); if decoded.is_err() { - platform::errno = errno::EILSEQ; + platform::ERRNO.set(errno::EILSEQ); return -1isize as usize; } @@ -34,14 +67,10 @@ pub unsafe fn mbrtowc(pwc: *mut wchar_t, s: *const c_char, n: usize, ps: *mut mb let result: wchar_t = wc.chars().next().unwrap() as wchar_t; if !pwc.is_null() { - *pwc = result; + unsafe { *pwc = result }; } - if result != 0 { - size - } else { - 0 - } + if result != 0 { size } else { 0 } } //It's guaranteed that we don't have any nullpointers here @@ -49,15 +78,30 @@ pub unsafe fn wcrtomb(s: *mut c_char, wc: wchar_t, ps: *mut mbstate_t) -> usize let dc = char::from_u32(wc as u32); if dc.is_none() { - platform::errno = errno::EILSEQ; + platform::ERRNO.set(errno::EILSEQ); return -1isize as usize; } let c = dc.unwrap(); let size = c.len_utf8(); - let slice = slice::from_raw_parts_mut(s as *mut u8, size); + let slice = unsafe { slice::from_raw_parts_mut(s.cast::(), size) }; c.encode_utf8(slice); size } + +/// Gets the encoded length of a character. It is used to recognize wide characters +pub fn get_char_encoded_length(first_byte: u8) -> Option { + if first_byte >> 7 == 0 { + Some(1) + } else if first_byte >> 5 == 6 { + Some(2) + } else if first_byte >> 4 == 0xe { + Some(3) + } else if first_byte >> 3 == 0x1e { + Some(4) + } else { + None + } +} diff --git a/src/header/wchar/wprintf.rs b/src/header/wchar/wprintf.rs new file mode 100644 index 0000000000..6a951a8dae --- /dev/null +++ b/src/header/wchar/wprintf.rs @@ -0,0 +1,12 @@ +// TODO: reuse more code with the thin printf impl +use crate::{ + c_str::{self, WStr}, + header::stdio::printf::inner_printf, + io::Write, + platform::types::c_int, +}; +use core::ffi::VaList; + +pub unsafe fn wprintf(w: impl Write, format: WStr, ap: VaList) -> c_int { + unsafe { inner_printf::(w, format, ap).unwrap_or(-1) } +} diff --git a/src/header/wchar/wscanf.rs b/src/header/wchar/wscanf.rs new file mode 100644 index 0000000000..e7797ed1b4 --- /dev/null +++ b/src/header/wchar/wscanf.rs @@ -0,0 +1,13 @@ +use crate::{ + c_str::{self, WStr}, + header::stdio::{reader::Reader, scanf::inner_scanf}, + platform::types::c_int, +}; +use core::ffi::VaList as va_list; + +pub unsafe fn scanf(r: Reader<'_, c_str::Wide>, format: WStr, ap: va_list) -> c_int { + match unsafe { inner_scanf::(r, format.into(), ap) } { + Ok(n) => n, + Err(n) => n, + } +} diff --git a/src/header/wctype/alpha.rs b/src/header/wctype/alpha.rs new file mode 100644 index 0000000000..02c398b0e3 --- /dev/null +++ b/src/header/wctype/alpha.rs @@ -0,0 +1,193 @@ +// From musl src/ctype/alpha.h +// Licensed under the MIT license +// Copyright 2005-2020 Rich Felker, et al. + +use crate::platform::types::c_uchar; + +pub fn is(wc: usize) -> c_uchar { + if wc < 0x20000 { + return (table[(table[wc >> 8] as usize) * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1; + } + if wc < 0x2fffe { + return 1; + } + 0 +} + +const table: [c_uchar; 3904] = [ + 18, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 17, 34, 35, 36, 17, 37, 38, + 39, 40, 41, 42, 43, 44, 17, 45, 46, 47, 16, 16, 48, 16, 16, 16, 16, 16, 16, 16, 49, 50, 51, 16, + 52, 53, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 54, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 55, 17, 17, 17, 17, 56, 17, 57, 58, + 59, 60, 61, 62, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 63, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 64, 65, 17, 66, 67, 68, 69, 70, 71, 72, 73, 74, 17, 75, + 76, 77, 78, 79, 80, 81, 16, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 16, 94, 95, 96, 16, + 17, 17, 17, 97, 98, 99, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 100, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 101, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 17, 17, 102, 103, 16, 16, 104, 105, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 106, 17, 17, 107, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 108, 109, 16, 16, 16, 16, 16, 16, 16, 16, 16, 110, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 111, 112, 113, 114, 16, 16, + 16, 16, 16, 16, 16, 16, 115, 116, 117, 16, 16, 16, 16, 16, 118, 119, 16, 16, 16, 16, 120, 16, + 16, 121, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 7, 254, 255, 255, 7, + 0, 0, 0, 0, 0, 4, 32, 4, 255, 255, 127, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 195, + 255, 3, 0, 31, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 223, 188, 64, 215, 255, + 255, 251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 191, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3, 252, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, 127, 2, + 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 255, 191, 182, 0, 255, 255, 255, 135, 7, 0, 0, 0, 255, + 7, 255, 255, 255, 255, 255, 255, 255, 254, 255, 195, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 239, 31, 254, 225, 255, 159, 0, 0, 255, 255, 255, 255, 255, 255, 0, 224, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 7, + 48, 4, 255, 255, 255, 252, 255, 31, 0, 0, 255, 255, 255, 1, 255, 7, 0, 0, 0, 0, 0, 0, 255, 255, + 223, 63, 0, 0, 240, 255, 248, 3, 255, 255, 255, 255, 255, 255, 255, 255, 255, 239, 255, 223, + 225, 255, 207, 255, 254, 255, 239, 159, 249, 255, 255, 253, 197, 227, 159, 89, 128, 176, 207, + 255, 3, 16, 238, 135, 249, 255, 255, 253, 109, 195, 135, 25, 2, 94, 192, 255, 63, 0, 238, 191, + 251, 255, 255, 253, 237, 227, 191, 27, 1, 0, 207, 255, 0, 30, 238, 159, 249, 255, 255, 253, + 237, 227, 159, 25, 192, 176, 207, 255, 2, 0, 236, 199, 61, 214, 24, 199, 255, 195, 199, 29, + 129, 0, 192, 255, 0, 0, 239, 223, 253, 255, 255, 253, 255, 227, 223, 29, 96, 7, 207, 255, 0, 0, + 239, 223, 253, 255, 255, 253, 239, 227, 223, 29, 96, 64, 207, 255, 6, 0, 239, 223, 253, 255, + 255, 255, 255, 231, 223, 93, 240, 128, 207, 255, 0, 252, 236, 255, 127, 252, 255, 255, 251, 47, + 127, 128, 95, 255, 192, 255, 12, 0, 254, 255, 255, 255, 255, 127, 255, 7, 63, 32, 255, 3, 0, 0, + 0, 0, 214, 247, 255, 255, 175, 255, 255, 59, 95, 32, 255, 243, 0, 0, 0, 0, 1, 0, 0, 0, 255, 3, + 0, 0, 255, 254, 255, 255, 255, 31, 254, 255, 3, 255, 255, 254, 255, 255, 255, 31, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 127, 249, 255, 3, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 63, 255, 255, 255, 255, 191, 32, 255, 255, 255, 255, 255, 247, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 61, 127, 61, 255, 255, 255, 255, 255, 61, 255, 255, 255, 255, 61, 127, + 61, 255, 127, 255, 255, 255, 255, 255, 255, 255, 61, 255, 255, 255, 255, 255, 255, 255, 255, 7, + 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 63, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 159, 255, 255, 254, 255, 255, 7, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 199, 255, 1, 255, 223, 15, 0, 255, 255, 15, 0, 255, 255, 15, 0, 255, 223, 13, 0, 255, + 255, 255, 255, 255, 255, 207, 255, 255, 1, 128, 16, 255, 3, 0, 0, 0, 0, 255, 3, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 1, 255, 255, 255, 255, 255, 7, 255, 255, 255, 255, 255, + 255, 255, 255, 63, 0, 255, 255, 255, 127, 255, 15, 255, 1, 192, 255, 255, 255, 255, 63, 31, 0, + 255, 255, 255, 255, 255, 15, 255, 255, 255, 3, 255, 3, 0, 0, 0, 0, 255, 255, 255, 15, 255, 255, + 255, 255, 255, 255, 255, 127, 254, 255, 31, 0, 255, 3, 255, 3, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 255, 255, 239, 255, 239, 15, 255, 3, 0, 0, 0, 0, 255, 255, 255, 255, + 255, 243, 255, 255, 255, 255, 255, 255, 191, 255, 3, 0, 255, 255, 255, 255, 255, 255, 127, 0, + 255, 227, 255, 255, 255, 255, 255, 63, 255, 1, 255, 255, 255, 255, 255, 231, 0, 0, 0, 0, 0, + 222, 111, 4, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 128, 255, 31, 0, 255, 255, 63, 63, 255, + 255, 255, 255, 63, 63, 255, 170, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 223, 95, 220, + 31, 207, 15, 255, 31, 220, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 0, 255, 31, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 252, 47, 62, 80, 189, 255, 243, 224, 67, 0, 0, 255, + 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 255, 255, 255, 255, 255, 3, 0, 0, 255, 255, 255, + 255, 255, 127, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 31, 120, 12, 0, 255, 255, 255, 255, 191, 32, 255, 255, 255, 255, + 255, 255, 255, 128, 0, 0, 255, 255, 127, 0, 127, 127, 127, 127, 127, 127, 127, 127, 255, 255, + 255, 255, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 224, 0, 0, 0, 254, 3, 62, 31, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 127, 224, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 247, 224, 255, 255, 255, 255, + 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 255, 255, 255, 7, 0, 0, + 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 63, 255, 31, 255, + 255, 255, 15, 0, 0, 255, 255, 255, 255, 255, 127, 240, 143, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 128, 255, 252, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, 255, 255, 255, 124, 0, 0, 0, 0, 0, 128, 255, + 191, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 47, 0, 255, 3, 0, 0, 252, 232, 255, 255, 255, 255, 255, 7, 255, 255, 255, 255, + 7, 0, 255, 255, 255, 31, 255, 255, 255, 255, 255, 255, 247, 255, 0, 128, 255, 3, 255, 255, 255, + 127, 255, 255, 255, 255, 255, 255, 127, 0, 255, 63, 255, 3, 255, 255, 127, 252, 255, 255, 255, + 255, 255, 255, 255, 127, 5, 0, 0, 56, 255, 255, 60, 0, 126, 126, 126, 0, 127, 127, 255, 255, + 255, 255, 255, 247, 255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 7, 255, 3, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 15, 0, 255, 255, 127, 248, 255, 255, 255, 255, 255, 15, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 3, 0, 0, 0, 0, 127, 0, 248, 224, 255, 253, 127, 95, 219, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 3, 0, 0, 0, 248, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 252, + 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 223, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 31, 0, 0, + 255, 3, 254, 255, 255, 7, 254, 255, 255, 7, 192, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 127, 252, 252, 252, 28, 0, 0, 0, 0, 255, 239, 255, 255, 127, 255, 255, 183, 255, 63, 255, + 63, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 31, 255, 255, + 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 224, 255, 255, 255, 7, 255, 255, + 255, 255, 255, 7, 255, 255, 255, 63, 255, 255, 255, 255, 15, 255, 62, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 255, + 3, 255, 255, 255, 255, 15, 255, 255, 255, 255, 15, 255, 255, 255, 255, 255, 0, 255, 255, 255, + 255, 255, 255, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, + 255, 255, 127, 0, 255, 255, 63, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 63, 253, 255, 255, 255, 255, 191, 145, 255, 255, 63, 0, 255, 255, 127, 0, 255, 255, 255, + 127, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 55, 0, 255, 255, 63, 0, 255, 255, 255, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 192, 0, 0, 0, 0, 0, 0, 0, 0, 111, 240, 239, 254, + 255, 255, 63, 0, 0, 0, 0, 0, 255, 255, 255, 31, 255, 255, 255, 31, 0, 0, 0, 0, 255, 254, 255, + 255, 31, 0, 0, 0, 255, 255, 255, 255, 255, 255, 63, 0, 255, 255, 63, 0, 255, 255, 7, 0, 255, + 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, + 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 7, 0, 255, 255, 255, 255, 255, 255, 7, 0, 255, + 255, 255, 255, 255, 0, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 31, 128, 0, 255, 255, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 255, 127, 0, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, 0, 192, 255, 0, + 0, 252, 255, 255, 255, 255, 255, 255, 1, 0, 0, 255, 255, 255, 1, 255, 3, 255, 255, 255, 255, + 255, 255, 199, 255, 112, 0, 255, 255, 255, 255, 71, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 30, 0, 255, 23, 0, 0, 0, 0, 255, 255, 251, 255, 255, 255, 159, 64, 0, 0, 0, 0, 0, 0, 0, 0, 127, + 189, 255, 191, 255, 1, 255, 255, 255, 255, 255, 255, 255, 1, 255, 3, 239, 159, 249, 255, 255, + 253, 237, 227, 159, 25, 129, 224, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 187, 7, 255, 131, 0, 0, 0, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 179, 0, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 255, 255, 255, 255, 255, 63, 127, 0, 0, 0, 63, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, + 127, 17, 0, 255, 3, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 63, 1, 255, 3, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 231, 255, 7, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 3, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 252, 255, 255, 255, 255, 255, 252, 26, 0, 0, 0, 255, 255, 255, 255, 255, 255, + 231, 127, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 32, 0, 0, 0, 0, 255, 255, 255, + 255, 255, 255, 255, 1, 255, 253, 255, 255, 255, 255, 127, 127, 1, 0, 255, 3, 0, 0, 252, 255, + 255, 255, 252, 255, 255, 254, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 251, 255, 255, 255, 255, + 127, 180, 203, 0, 255, 3, 191, 253, 255, 255, 255, 127, 123, 1, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 255, 127, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 127, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 127, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, + 255, 255, 1, 255, 255, 255, 127, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 63, + 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 15, 0, 255, 3, 248, 255, 255, 224, 255, 255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 135, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, + 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 7, 0, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 7, 0, 240, 0, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 7, 255, 31, 255, 1, 255, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 223, 255, 255, 255, 255, 255, 255, 255, 255, 223, + 100, 222, 255, 235, 239, 255, 255, 255, 255, 255, 255, 255, 191, 231, 223, 223, 255, 255, 255, + 123, 95, 252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 253, 255, 255, 247, 255, 255, 255, 247, + 255, 255, 223, 255, 255, 255, 223, 255, 255, 127, 255, 255, 255, 127, 255, 255, 255, 253, 255, + 255, 255, 253, 255, 255, 247, 207, 255, 255, 255, 255, 255, 255, 127, 255, 255, 249, 219, 7, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, + 255, 31, 128, 63, 255, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, + 15, 255, 3, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, + 255, 255, 143, 8, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 239, 255, + 255, 255, 150, 254, 247, 10, 132, 234, 150, 170, 150, 247, 247, 94, 255, 251, 255, 15, 238, + 251, 255, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 3, 255, 255, 255, 3, + 255, 255, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; diff --git a/src/header/wctype/casecmp.rs b/src/header/wctype/casecmp.rs index a15e9292f6..4c25080911 100644 --- a/src/header/wctype/casecmp.rs +++ b/src/header/wctype/casecmp.rs @@ -1,4 +1,9 @@ -use crate::platform::types::*; +// From musl src/ctype/casemap.h +// Licensed under the MIT license +// Copyright 2005-2020 Rich Felker, et al. + +use crate::platform::types::{c_int, c_uchar, c_uint, wint_t}; + const tab: [c_uchar; 2666] = [ 7, 8, 9, 10, 11, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 13, 6, 6, 14, 6, 6, 6, 6, 6, 6, 6, 6, 15, 16, 17, 18, 6, 19, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 20, 21, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, @@ -100,6 +105,7 @@ const tab: [c_uchar; 2666] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; + const rules: [c_int; 240] = [ 0x0, 0x2001, -0x2000, 0x1dbf00, 0x2e700, 0x7900, 0x2402, 0x101, -0x100, 0x0, 0x201, -0x200, -0xc6ff, -0xe800, -0x78ff, -0x12c00, 0xc300, 0xd201, 0xce01, 0xcd01, 0x4f01, 0xca01, 0xcb01, @@ -125,6 +131,7 @@ const rules: [c_int; 240] = [ -0x2000, 0x0, 0x2801, -0x2800, 0x0, 0x4001, -0x4000, 0x0, 0x2001, -0x2000, 0x0, 0x2001, -0x2000, 0x0, 0x2201, -0x2200, ]; + const rulebases: [c_uchar; 512] = [ 0, 6, 39, 81, 111, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 131, 142, 146, 151, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 180, 196, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -144,6 +151,7 @@ const rulebases: [c_uchar; 512] = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; + const exceptions: [[c_uchar; 2]; 200] = [ [48, 12], [49, 13], @@ -357,14 +365,14 @@ pub fn casemap(mut c: u32, dir: i32) -> wint_t { let x = c / 3; let y = c % 3; /* lookup entry in two-level base-6 table */ - let mut v: c_uint = tab[(tab[b as usize] as u32 * 86 + x) as usize] as c_uint; + let mut v: c_uint = c_uint::from(tab[(u32::from(tab[b as usize]) * 86 + x) as usize]); let mt: [c_uint; 3] = [2048, 342, 57]; - v = (v * mt[y as usize] >> 11) % 6; + v = ((v * mt[y as usize]) >> 11) % 6; /* use the bit vector out of the tables as an index into * a block-specific set of rules and decode the rule into * a type and a case-mapping delta. */ - let mut r: c_int = rules[(rulebases[b as usize] as c_uint + v) as usize]; + let mut r: c_int = rules[(c_uint::from(rulebases[b as usize]) + v) as usize]; let mut rt: c_uint = (r & 255) as c_uint; let mut rd: c_int = r >> 8; @@ -380,7 +388,7 @@ pub fn casemap(mut c: u32, dir: i32) -> wint_t { let mut xn: c_uint = (rd & 0xff) as c_uint; let mut xb: c_uint = rd as c_uint >> 8; while xn != 0 { - let attempt: c_uint = exceptions[(xb + xn / 2) as usize][0] as c_uint; + let attempt: c_uint = c_uint::from(exceptions[(xb + xn / 2) as usize][0]); if attempt == c { r = rules[exceptions[(xb + xn / 2) as usize][1] as usize]; rt = r as c_uint & 255; @@ -402,5 +410,5 @@ pub fn casemap(mut c: u32, dir: i32) -> wint_t { xn -= xn / 2; } } - return c0; + c0 } diff --git a/src/header/wctype/cbindgen.toml b/src/header/wctype/cbindgen.toml index 032cfcb0ab..cd89867605 100644 --- a/src/header/wctype/cbindgen.toml +++ b/src/header/wctype/cbindgen.toml @@ -1,9 +1,8 @@ -sys_includes = ["stddef.h", "stdint.h", "time.h", "stdio.h" ] +sys_includes = ["wchar.h" ] include_guard = "_RELIBC_WCTYPE_H" -header = "#include " language = "C" style = "Type" -no_includes = true +no_includes = false cpp_compat = true [enum] diff --git a/src/header/wctype/mod.rs b/src/header/wctype/mod.rs index b4b7b0eaa9..f9aac7454b 100644 --- a/src/header/wctype/mod.rs +++ b/src/header/wctype/mod.rs @@ -1,17 +1,232 @@ -//! wchar implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/wchar.h.html +//! `wctype.h` implementation. +//! +//! See . -use crate::platform::types::*; +// TODO: *_l functions +use self::casecmp::casemap; +use crate::{ + c_str::CStr, + header::ctype, + platform::types::{c_char, c_int, wint_t}, +}; + +mod alpha; mod casecmp; -use casecmp::casemap; +mod punct; + +pub type wctype_t = u32; +pub type wctrans_t = *const i32; + pub const WEOF: wint_t = 0xFFFF_FFFFu32; -#[no_mangle] +pub const WCTYPE_ALNUM: wctype_t = 1; +pub const WCTYPE_ALPHA: wctype_t = 2; +pub const WCTYPE_BLANK: wctype_t = 3; +pub const WCTYPE_CNTRL: wctype_t = 4; +pub const WCTYPE_DIGIT: wctype_t = 5; +pub const WCTYPE_GRAPH: wctype_t = 6; +pub const WCTYPE_LOWER: wctype_t = 7; +pub const WCTYPE_PRINT: wctype_t = 8; +pub const WCTYPE_PUNCT: wctype_t = 9; +pub const WCTYPE_SPACE: wctype_t = 10; +pub const WCTYPE_UPPER: wctype_t = 11; +pub const WCTYPE_XDIGIT: wctype_t = 12; + +const WCTRANSUP: wctrans_t = 1 as wctrans_t; +const WCTRANSLW: wctrans_t = 2 as wctrans_t; + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswalnum(wc: wint_t) -> c_int { + c_int::from(iswdigit(wc) != 0 || iswalpha(wc) != 0) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswalpha(wc: wint_t) -> c_int { + c_int::from(alpha::is(wc as usize)) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswblank(wc: wint_t) -> c_int { + ctype::isblank(wc as c_int) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswcntrl(wc: wint_t) -> c_int { + c_int::from( + wc < 32 + || wc.wrapping_sub(0x7f) < 33 + || wc.wrapping_sub(0x2028) < 2 + || wc.wrapping_sub(0xfff9) < 3, + ) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswctype(wc: wint_t, desc: wctype_t) -> c_int { + match desc { + WCTYPE_ALNUM => iswalnum(wc), + WCTYPE_ALPHA => iswalpha(wc), + WCTYPE_BLANK => iswblank(wc), + WCTYPE_CNTRL => iswcntrl(wc), + WCTYPE_DIGIT => iswdigit(wc), + WCTYPE_GRAPH => iswgraph(wc), + WCTYPE_LOWER => iswlower(wc), + WCTYPE_PRINT => iswprint(wc), + WCTYPE_PUNCT => iswpunct(wc), + WCTYPE_SPACE => iswspace(wc), + WCTYPE_UPPER => iswupper(wc), + WCTYPE_XDIGIT => iswxdigit(wc), + _ => 0, + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswdigit(wc: wint_t) -> c_int { + c_int::from(wc.wrapping_sub('0' as wint_t) < 10) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswgraph(wc: wint_t) -> c_int { + c_int::from(iswspace(wc) == 0 && iswprint(wc) != 0) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswlower(wc: wint_t) -> c_int { + c_int::from(towupper(wc) != wc) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswprint(wc: wint_t) -> c_int { + if wc < 0xff { + c_int::from(((wc + 1) & 0x7f) >= 0x21) + } else if wc < 0x2028 + || wc.wrapping_sub(0x202a) < 0xd800 - 0x202a + || wc.wrapping_sub(0xe000) < 0xfff9 - 0xe000 + { + 1 + } else if wc.wrapping_sub(0xfffc) > 0x10ffff - 0xfffc || (wc & 0xfffe) == 0xfffe { + 0 + } else { + 1 + } +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswpunct(wc: wint_t) -> c_int { + c_int::from(punct::is(wc as usize)) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswspace(wc: wint_t) -> c_int { + c_int::from( + [ + ' ' as wint_t, + '\t' as wint_t, + '\n' as wint_t, + '\r' as wint_t, + 11, + 12, + 0x0085, + 0x2000, + 0x2001, + 0x2002, + 0x2003, + 0x2004, + 0x2005, + 0x2006, + 0x2008, + 0x2009, + 0x200a, + 0x2028, + 0x2029, + 0x205f, + 0x3000, + ] + .contains(&wc), + ) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswupper(wc: wint_t) -> c_int { + c_int::from(towlower(wc) != wc) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn iswxdigit(wc: wint_t) -> c_int { + c_int::from(wc.wrapping_sub('0' as wint_t) < 10 || (wc | 32).wrapping_sub('a' as wint_t) < 6) +} + +/// See . +#[unsafe(no_mangle)] +pub extern "C" fn towctrans(wc: wint_t, trans: wctrans_t) -> wint_t { + match trans { + WCTRANSUP => towupper(wc), + WCTRANSLW => towlower(wc), + _ => wc, + } +} + +/// See . +#[unsafe(no_mangle)] pub extern "C" fn towlower(wc: wint_t) -> wint_t { casemap(wc, 0) } -#[no_mangle] +/// See . +#[unsafe(no_mangle)] pub extern "C" fn towupper(wc: wint_t) -> wint_t { casemap(wc, 1) } + +/// See . +/// +/// # Safety +/// The caller must ensure that `class` is convertible to a slice reference, up +/// to and including a terminating nul. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wctrans(class: *const c_char) -> wctrans_t { + let class_cstr = unsafe { CStr::from_ptr(class) }; + match class_cstr.to_bytes() { + b"toupper" => WCTRANSUP, + b"tolower" => WCTRANSLW, + _ => 0 as wctrans_t, + } +} + +/// See . +/// +/// # Safety +/// The caller must ensure that `name` is convertible to a slice reference, up +/// to and including a terminating nul. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wctype(name: *const c_char) -> wctype_t { + let name_cstr = unsafe { CStr::from_ptr(name) }; + match name_cstr.to_bytes() { + b"alnum" => WCTYPE_ALNUM, + b"alpha" => WCTYPE_ALPHA, + b"blank" => WCTYPE_BLANK, + b"cntrl" => WCTYPE_CNTRL, + b"digit" => WCTYPE_DIGIT, + b"graph" => WCTYPE_GRAPH, + b"lower" => WCTYPE_LOWER, + b"print" => WCTYPE_PRINT, + b"punct" => WCTYPE_PUNCT, + b"space" => WCTYPE_SPACE, + b"upper" => WCTYPE_UPPER, + b"xdigit" => WCTYPE_XDIGIT, + _ => 0, + } +} diff --git a/src/header/wctype/punct.rs b/src/header/wctype/punct.rs new file mode 100644 index 0000000000..2bfb1fc65a --- /dev/null +++ b/src/header/wctype/punct.rs @@ -0,0 +1,166 @@ +// From musl src/ctype/punct.h +// Licensed under the MIT license +// Copyright 2005-2020 Rich Felker, et al. + +use crate::platform::types::c_uchar; + +pub fn is(wc: usize) -> c_uchar { + if wc < 0x20000 { + return (table[(table[wc >> 8] as usize) * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1; + } + 0 +} + +const table: [c_uchar; 4000] = [ + 18, 16, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 16, 16, 34, 35, 16, 36, 37, + 38, 39, 40, 41, 42, 43, 16, 44, 45, 46, 17, 17, 47, 17, 17, 17, 17, 17, 17, 48, 49, 50, 51, 52, + 53, 54, 55, 17, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 56, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 57, 16, 58, 59, + 60, 61, 62, 63, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 64, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 65, 16, 16, 66, 16, 67, 68, 69, 16, 70, 71, 72, 16, 73, 16, 16, + 74, 75, 76, 77, 78, 16, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 16, 92, 93, 94, 95, + 16, 16, 16, 16, 96, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 97, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 98, 99, 16, 16, 100, 101, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 102, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 103, 104, 105, 106, 16, 16, 107, 108, 17, 17, 109, 16, + 16, 16, 16, 16, 16, 110, 111, 16, 16, 16, 16, 16, 112, 113, 16, 16, 114, 115, 116, 16, 117, + 118, 119, 17, 17, 17, 120, 121, 122, 123, 124, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 254, 255, 0, 252, 1, 0, 0, 248, 1, 0, 0, + 120, 0, 0, 0, 0, 255, 251, 223, 251, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 252, 255, 224, 175, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 223, 255, 255, 255, 255, 255, 32, 64, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 230, 254, 255, 255, + 255, 0, 64, 73, 0, 0, 0, 0, 0, 24, 0, 255, 255, 0, 216, 0, 0, 0, 0, 0, 0, 0, 1, 0, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 224, 1, 30, 0, 96, 255, 191, 0, 0, 0, 0, 0, 0, 255, 7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 207, 227, 0, 0, 0, 3, 0, 32, 255, 127, 0, + 0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 7, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 32, 30, 0, 48, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 32, 0, 0, 0, 0, 252, 111, 0, 0, 0, + 0, 0, 0, 0, 16, 0, 32, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 32, 0, 0, 0, 0, 3, 224, + 0, 0, 0, 0, 0, 0, 0, 16, 0, 32, 0, 0, 0, 0, 253, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, + 255, 7, 16, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 128, 255, 16, 0, 0, 0, 0, 0, 0, 16, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 160, 0, 127, 0, 0, 255, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 128, 0, 128, 192, 223, 0, 12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 31, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 0, 252, 255, 255, 0, 0, 0, 0, 0, 0, + 0, 0, 252, 0, 0, 0, 0, 0, 0, 192, 255, 223, 255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 6, 0, + 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 224, 255, 255, 255, 31, 0, 0, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 1, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, + 16, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 254, 127, 47, 0, 0, + 255, 3, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 196, 255, 255, 255, 255, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 224, 159, 0, 0, 0, 0, 127, + 63, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 16, 0, 0, 252, 255, 255, 255, + 31, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 64, 0, 12, 240, 0, 0, 0, 0, 0, 0, 128, 248, 0, 0, 0, + 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 255, 255, 33, 144, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 127, 0, 224, 251, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 3, 224, 0, 224, 0, 224, + 0, 96, 128, 248, 255, 255, 255, 252, 255, 255, 255, 255, 255, 127, 223, 255, 241, 127, 255, + 127, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 1, 0, 123, 3, 208, 193, 175, 66, 0, + 12, 31, 188, 255, 255, 0, 0, 0, 0, 0, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 255, 7, 0, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 63, 0, 0, 0, 0, 0, 0, 252, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 207, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 224, 135, 3, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 128, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 255, 255, 255, 251, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 63, 0, 0, 0, 255, 15, 30, 255, 255, 255, 1, 252, 193, 224, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 15, 0, 0, 0, 255, + 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 192, + 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 15, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 255, 0, 255, 255, 127, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 15, 255, 3, 0, 0, 0, 0, 0, 0, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 192, 0, 0, 255, 255, 3, 23, 0, 0, 0, 0, 0, 248, 0, 0, 0, 0, 8, 128, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 0, 255, 63, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 0, 128, 3, 0, + 0, 0, 0, 0, 0, 0, 128, 2, 0, 0, 192, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 255, 255, 255, 3, 255, 255, + 255, 255, 255, 255, 247, 255, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, + 254, 255, 0, 252, 1, 0, 0, 248, 1, 0, 0, 248, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 127, 127, 0, 48, 135, 255, 255, 255, 255, 255, 143, 255, 0, 0, 0, 0, 0, 0, 224, 255, 255, 127, + 255, 15, 1, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 128, 255, 0, 0, 128, 255, 0, 0, 0, 0, 128, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, + 192, 143, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 255, 255, 252, 255, + 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 135, 255, 1, 255, 1, 0, 0, 0, 224, 0, 0, 0, 224, 0, 0, + 0, 0, 0, 1, 0, 0, 96, 248, 127, 0, 0, 0, 0, 0, 0, 0, 0, 254, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, + 0, 30, 0, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 127, 0, 0, 0, 192, 255, + 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 192, 63, 252, 255, 63, 0, 0, 128, 3, 0, 0, 0, 0, 0, 0, 254, 3, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 24, 0, 15, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 225, 63, 0, 232, 254, 255, + 31, 0, 0, 0, 0, 0, 0, 0, 96, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 32, 0, 0, 192, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 248, 0, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, + 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 14, 0, 0, 0, 255, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 128, 255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 223, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 62, 0, 0, + 252, 255, 31, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 3, 128, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, 0, 255, 255, 48, 0, 0, 248, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 7, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 15, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 255, 255, + 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 15, 0, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 127, 0, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 8, 0, 0, 0, + 8, 0, 0, 32, 0, 0, 0, 32, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 8, 0, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 15, 0, 248, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 128, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 112, 7, 0, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 255, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 254, 255, 255, 255, 255, 255, 255, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 255, 255, 255, 255, 255, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 15, 0, 255, 127, 254, 255, 254, 255, 254, 255, 255, 255, 63, 0, 255, 31, 255, 255, + 255, 255, 0, 0, 0, 252, 0, 0, 0, 28, 0, 0, 0, 252, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 192, + 255, 255, 255, 7, 0, 255, 255, 255, 255, 255, 15, 255, 1, 3, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 0, 255, 31, 255, 7, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 1, 255, 15, 0, 0, 255, 15, 255, 255, 255, 255, 255, 255, + 255, 0, 255, 3, 255, 255, 255, 255, 255, 0, 255, 255, 255, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 239, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 123, 252, 255, 255, 255, + 255, 231, 199, 255, 255, 255, 231, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 15, 0, 255, 63, 15, 7, 7, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; diff --git a/src/io.rs b/src/io.rs deleted file mode 100644 index d65ab2dddd..0000000000 --- a/src/io.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub use core_io::*; - -use crate::platform; - -pub fn last_os_error() -> Error { - let errno = unsafe { platform::errno }; - Error::from_raw_os_error(errno) -} diff --git a/src/io/buffered.rs b/src/io/buffered.rs new file mode 100644 index 0000000000..a25273185c --- /dev/null +++ b/src/io/buffered.rs @@ -0,0 +1,1123 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Buffering wrappers for I/O traits + +use core::{cmp, fmt}; + +use crate::io::{ + self, DEFAULT_BUF_SIZE, Error, ErrorKind, Initializer, SeekFrom, Write, prelude::*, +}; + +/// The `BufReader` struct adds buffering to any reader. +/// +/// It can be excessively inefficient to work directly with a [`Read`] instance. +/// For example, every call to [`read`][`TcpStream::read`] on [`TcpStream`] +/// results in a system call. A `BufReader` performs large, infrequent reads on +/// the underlying [`Read`] and maintains an in-memory buffer of the results. +/// +/// `BufReader` can improve the speed of programs that make *small* and +/// *repeated* read calls to the same file or network socket. It does not +/// help when reading very large amounts at once, or reading just one or a few +/// times. It also provides no advantage when reading from a source that is +/// already in memory, like a `Vec`. +/// +/// [`Read`]: ../../std/io/trait.Read.html +/// [`TcpStream::read`]: ../../std/net/struct.TcpStream.html#method.read +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html +/// +/// # Examples +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::io::BufReader; +/// use std::fs::File; +/// +/// fn main() -> std::io::Result<()> { +/// let f = File::open("log.txt")?; +/// let mut reader = BufReader::new(f); +/// +/// let mut line = String::new(); +/// let len = reader.read_line(&mut line)?; +/// println!("First line is {} bytes long", len); +/// Ok(()) +/// } +/// ``` +pub struct BufReader { + inner: R, + buf: Box<[u8]>, + pos: usize, + cap: usize, +} + +impl BufReader { + /// Creates a new `BufReader` with a default buffer capacity. The default is currently 8 KB, + /// but may change in the future. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufReader; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("log.txt")?; + /// let reader = BufReader::new(f); + /// Ok(()) + /// } + /// ``` + pub fn new(inner: R) -> BufReader { + BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) + } + + /// Creates a new `BufReader` with the specified buffer capacity. + /// + /// # Examples + /// + /// Creating a buffer with ten bytes of capacity: + /// + /// ```no_run + /// use std::io::BufReader; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f = File::open("log.txt")?; + /// let reader = BufReader::with_capacity(10, f); + /// Ok(()) + /// } + /// ``` + #[allow(clippy::uninit_vec)] // buffer initialized after set_len + pub fn with_capacity(cap: usize, inner: R) -> BufReader { + unsafe { + let mut buffer = Vec::with_capacity(cap); + buffer.set_len(cap); + inner.initializer().initialize(&mut buffer); + BufReader { + inner, + buf: buffer.into_boxed_slice(), + pos: 0, + cap: 0, + } + } + } + + /// Gets a reference to the underlying reader. + /// + /// It is inadvisable to directly read from the underlying reader. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufReader; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let f1 = File::open("log.txt")?; + /// let reader = BufReader::new(f1); + /// + /// let f2 = reader.get_ref(); + /// Ok(()) + /// } + /// ``` + pub fn get_ref(&self) -> &R { + &self.inner + } +} + +impl BufReader { + /// Seeks relative to the current position. If the new position lies within the buffer, + /// the buffer will not be flushed, allowing for more efficient seeks. + /// This method does not return the location of the underlying reader, so the caller + /// must track this information themselves if it is required. + pub fn seek_relative(&mut self, offset: i64) -> io::Result<()> { + let pos = self.pos as u64; + if offset < 0 { + if let Some(new_pos) = pos.checked_sub((-offset) as u64) { + self.pos = new_pos as usize; + return Ok(()); + } + } else if let Some(new_pos) = pos.checked_add(offset as u64) + && new_pos <= self.cap as u64 + { + self.pos = new_pos as usize; + return Ok(()); + } + self.seek(SeekFrom::Current(offset)).map(|_| ()) + } +} + +impl Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // If we don't have any buffered data and we're doing a massive read + // (larger than our internal buffer), bypass our internal buffer + // entirely. + if self.pos == self.cap && buf.len() >= self.buf.len() { + return self.inner.read(buf); + } + let nread = { + let mut rem = self.fill_buf()?; + rem.read(buf)? + }; + self.consume(nread); + Ok(nread) + } + + // we can't skip unconditionally because of the large buffer case in read. + unsafe fn initializer(&self) -> Initializer { + unsafe { self.inner.initializer() } + } +} + +impl BufRead for BufReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + // If we've reached the end of our internal buffer then we need to fetch + // some more data from the underlying reader. + // Branch using `>=` instead of the more correct `==` + // to tell the compiler that the pos..cap slice is always valid. + if self.pos >= self.cap { + debug_assert!(self.pos == self.cap); + self.cap = self.inner.read(&mut self.buf)?; + self.pos = 0; + } + Ok(&self.buf[self.pos..self.cap]) + } + + fn consume(&mut self, amt: usize) { + self.pos = cmp::min(self.pos + amt, self.cap); + } +} + +impl Seek for BufReader { + /// Seek to an offset, in bytes, in the underlying reader. + /// + /// The position used for seeking with `SeekFrom::Current(_)` is the + /// position the underlying reader would be at if the `BufReader` had no + /// internal buffer. + /// + /// Seeking always discards the internal buffer, even if the seek position + /// would otherwise fall within it. This guarantees that calling + /// `.into_inner()` immediately after a seek yields the underlying reader + /// at the same position. + /// + /// To seek without discarding the internal buffer, use [`seek_relative`](crate::io::BufReader::seek_relative). + /// + /// See [`std::io::Seek`](https://doc.rust-lang.org/std/io/trait.Seek.html) for more details. + /// + /// Note: In the edge case where you're seeking with `SeekFrom::Current(n)` + /// where `n` minus the internal buffer length overflows an `i64`, two + /// seeks will be performed instead of one. If the second seek returns + /// `Err`, the underlying reader will be left at the same position it would + /// have if you called `seek` with `SeekFrom::Current(0)`. + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let result: u64; + if let SeekFrom::Current(n) = pos { + let remainder = (self.cap - self.pos) as i64; + // it should be safe to assume that remainder fits within an i64 as the alternative + // means we managed to allocate 8 exbibytes and that's absurd. + // But it's not out of the realm of possibility for some weird underlying reader to + // support seeking by i64::min_value() so we need to handle underflow when subtracting + // remainder. + if let Some(offset) = n.checked_sub(remainder) { + result = self.inner.seek(SeekFrom::Current(offset))?; + } else { + // seek backwards by our remainder, and then by the offset + self.inner.seek(SeekFrom::Current(-remainder))?; + self.pos = self.cap; // empty the buffer + result = self.inner.seek(SeekFrom::Current(n))?; + } + } else { + // Seeking with Start/End doesn't care about our buffer length. + result = self.inner.seek(pos)?; + } + self.pos = self.cap; // empty the buffer + Ok(result) + } +} + +/// Wraps a writer and buffers its output. +/// +/// It can be excessively inefficient to work directly with something that +/// implements [`Write`]. For example, every call to +/// [`write`][`Tcpstream::write`] on [`TcpStream`] results in a system call. A +/// `BufWriter` keeps an in-memory buffer of data and writes it to an underlying +/// writer in large, infrequent batches. +/// +/// `BufWriter` can improve the speed of programs that make *small* and +/// *repeated* write calls to the same file or network socket. It does not +/// help when writing very large amounts at once, or writing just one or a few +/// times. It also provides no advantage when writing to a destination that is +/// in memory, like a `Vec`. +/// +/// When the `BufWriter` is dropped, the contents of its buffer will be written +/// out. However, any errors that happen in the process of flushing the buffer +/// when the writer is dropped will be ignored. Code that wishes to handle such +/// errors must manually call [`flush`] before the writer is dropped. +/// +/// # Examples +/// +/// Let's write the numbers one through ten to a [`TcpStream`]: +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::net::TcpStream; +/// +/// let mut stream = TcpStream::connect("127.0.0.1:34254").unwrap(); +/// +/// for i in 0..10 { +/// stream.write(&[i+1]).unwrap(); +/// } +/// ``` +/// +/// Because we're not buffering, we write each one in turn, incurring the +/// overhead of a system call per byte written. We can fix this with a +/// `BufWriter`: +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::io::BufWriter; +/// use std::net::TcpStream; +/// +/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); +/// +/// for i in 0..10 { +/// stream.write(&[i+1]).unwrap(); +/// } +/// ``` +/// +/// By wrapping the stream with a `BufWriter`, these ten writes are all grouped +/// together by the buffer, and will all be written out in one system call when +/// the `stream` is dropped. +/// +/// [`Write`]: ../../std/io/trait.Write.html +/// [`Tcpstream::write`]: ../../std/net/struct.TcpStream.html#method.write +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html +/// [`flush`]: #method.flush +pub struct BufWriter { + inner: Option, + pub buf: Vec, + // #30888: If the inner writer panics in a call to write, we don't want to + // write the buffered data a second time in BufWriter's destructor. This + // flag tells the Drop impl if it should skip the flush. + panicked: bool, +} + +/// An error returned by `into_inner` which combines an error that +/// happened while writing out the buffer, and the buffered writer object +/// which may be used to recover from the condition. +/// +/// # Examples +/// +/// ```no_run +/// use std::io::BufWriter; +/// use std::net::TcpStream; +/// +/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); +/// +/// // do stuff with the stream +/// +/// // we want to get our `TcpStream` back, so let's try: +/// +/// let stream = match stream.into_inner() { +/// Ok(s) => s, +/// Err(e) => { +/// // Here, e is an IntoInnerError +/// panic!("An error occurred"); +/// } +/// }; +/// ``` +#[derive(Debug)] +pub struct IntoInnerError(W, Error); + +impl BufWriter { + /// Creates a new `BufWriter` with a default buffer capacity. The default is currently 8 KB, + /// but may change in the future. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// ``` + pub fn new(inner: W) -> BufWriter { + BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) + } + + /// Creates a new `BufWriter` with the specified buffer capacity. + /// + /// # Examples + /// + /// Creating a buffer with a buffer of a hundred bytes. + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let stream = TcpStream::connect("127.0.0.1:34254").unwrap(); + /// let mut buffer = BufWriter::with_capacity(100, stream); + /// ``` + pub fn with_capacity(cap: usize, inner: W) -> BufWriter { + BufWriter { + inner: Some(inner), + buf: Vec::with_capacity(cap), + panicked: false, + } + } + + /// Gets a reference to the underlying writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // we can use reference just like buffer + /// let reference = buffer.get_ref(); + /// ``` + pub fn get_ref(&self) -> &W { + self.inner.as_ref().unwrap() + } + + fn flush_buf(&mut self) -> io::Result<()> { + let mut written = 0; + let len = self.buf.len(); + let mut ret = Ok(()); + while written < len { + self.panicked = true; + let r = self.inner.as_mut().unwrap().write(&self.buf[written..]); + self.panicked = false; + + match r { + Ok(0) => { + ret = Err(Error::new( + ErrorKind::WriteZero, + "failed to write the buffered data", + )); + break; + } + Ok(n) => written += n, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => { + ret = Err(e); + break; + } + } + } + if written > 0 { + self.buf.drain(..written); + } + ret + } + + /// Gets a mutable reference to the underlying writer. + /// + /// It is inadvisable to directly write to the underlying writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // we can use reference just like buffer + /// let reference = buffer.get_mut(); + /// ``` + pub fn get_mut(&mut self) -> &mut W { + self.inner.as_mut().unwrap() + } + + /// Unwraps this `BufWriter`, returning the underlying writer. + /// + /// The buffer is written out before returning the writer. + /// + /// # Errors + /// + /// An `Err` will be returned if an error occurs while flushing the buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // unwrap the TcpStream and flush the buffer + /// let stream = buffer.into_inner().unwrap(); + /// ``` + pub fn into_inner(mut self) -> Result>> { + match self.flush_buf() { + Err(e) => Err(IntoInnerError(self, e)), + Ok(()) => Ok(self.inner.take().unwrap()), + } + } +} + +impl Write for BufWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + if self.buf.len() + buf.len() > self.buf.capacity() { + self.flush_buf()?; + } + if buf.len() >= self.buf.capacity() { + self.panicked = true; + let r = self.inner.as_mut().unwrap().write(buf); + self.panicked = false; + r + } else { + Write::write(&mut self.buf, buf) + } + } + fn flush(&mut self) -> io::Result<()> { + self.flush_buf().and_then(|()| self.get_mut().flush()) + } +} + +impl fmt::Debug for BufWriter +where + W: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("BufWriter") + .field("writer", &self.inner.as_ref().unwrap()) + .field( + "buffer", + &format_args!("{}/{}", self.buf.len(), self.buf.capacity()), + ) + .finish() + } +} + +impl Seek for BufWriter { + /// Seek to the offset, in bytes, in the underlying writer. + /// + /// Seeking always writes out the internal buffer before seeking. + fn seek(&mut self, pos: SeekFrom) -> io::Result { + self.flush_buf().and_then(|_| self.get_mut().seek(pos)) + } +} + +/// Wraps a writer and buffers output to it, flushing whenever a newline +/// (`0x0a`, `'\n'`) is detected. +/// +/// The [`BufWriter`][bufwriter] struct wraps a writer and buffers its output. +/// But it only does this batched write when it goes out of scope, or when the +/// internal buffer is full. Sometimes, you'd prefer to write each line as it's +/// completed, rather than the entire buffer at once. Enter `LineWriter`. It +/// does exactly that. +/// +/// Like [`BufWriter`], a `LineWriter`’s buffer will also be flushed when the +/// `LineWriter` goes out of scope or when its internal buffer is full. +/// +/// [bufwriter]: struct.BufWriter.html +/// +/// If there's still a partial line in the buffer when the `LineWriter` is +/// dropped, it will flush those contents. +/// +/// # Examples +/// +/// We can use `LineWriter` to write one line at a time, significantly +/// reducing the number of actual writes to the file. +/// +/// ```no_run +/// use std::fs::{self, File}; +/// use std::io::prelude::*; +/// use std::io::LineWriter; +/// +/// fn main() -> std::io::Result<()> { +/// let road_not_taken = b"I shall be telling this with a sigh +/// Somewhere ages and ages hence: +/// Two roads diverged in a wood, and I - +/// I took the one less traveled by, +/// And that has made all the difference."; +/// +/// let file = File::create("poem.txt")?; +/// let mut file = LineWriter::new(file); +/// +/// file.write_all(b"I shall be telling this with a sigh")?; +/// +/// // No bytes are written until a newline is encountered (or +/// // the internal buffer is filled). +/// assert_eq!(fs::read_to_string("poem.txt")?, ""); +/// file.write_all(b"\n")?; +/// assert_eq!( +/// fs::read_to_string("poem.txt")?, +/// "I shall be telling this with a sigh\n", +/// ); +/// +/// // Write the rest of the poem. +/// file.write_all(b"Somewhere ages and ages hence: +/// Two roads diverged in a wood, and I - +/// I took the one less traveled by, +/// And that has made all the difference.")?; +/// +/// // The last line of the poem doesn't end in a newline, so +/// // we have to flush or drop the `LineWriter` to finish +/// // writing. +/// file.flush()?; +/// +/// // Confirm the whole poem was written. +/// assert_eq!(fs::read("poem.txt")?, &road_not_taken[..]); +/// Ok(()) +/// } +/// ``` +pub struct LineWriter { + pub inner: BufWriter, + need_flush: bool, +} + +impl LineWriter { + /// Creates a new `LineWriter`. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::File; + /// use std::io::LineWriter; + /// + /// fn main() -> std::io::Result<()> { + /// let file = File::create("poem.txt")?; + /// let file = LineWriter::new(file); + /// Ok(()) + /// } + /// ``` + pub fn new(inner: W) -> LineWriter { + // Lines typically aren't that long, don't use a giant buffer + LineWriter::with_capacity(1024, inner) + } + + /// Creates a new `LineWriter` with a specified capacity for the internal + /// buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::File; + /// use std::io::LineWriter; + /// + /// fn main() -> std::io::Result<()> { + /// let file = File::create("poem.txt")?; + /// let file = LineWriter::with_capacity(100, file); + /// Ok(()) + /// } + /// ``` + pub fn with_capacity(cap: usize, inner: W) -> LineWriter { + LineWriter { + inner: BufWriter::with_capacity(cap, inner), + need_flush: false, + } + } + + /// Gets a reference to the underlying writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::File; + /// use std::io::LineWriter; + /// + /// fn main() -> std::io::Result<()> { + /// let file = File::create("poem.txt")?; + /// let file = LineWriter::new(file); + /// + /// let reference = file.get_ref(); + /// Ok(()) + /// } + /// ``` + pub fn get_ref(&self) -> &W { + self.inner.get_ref() + } + + /// Gets a mutable reference to the underlying writer. + /// + /// Caution must be taken when calling methods on the mutable reference + /// returned as extra writes could corrupt the output stream. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::File; + /// use std::io::LineWriter; + /// + /// fn main() -> std::io::Result<()> { + /// let file = File::create("poem.txt")?; + /// let mut file = LineWriter::new(file); + /// + /// // we can use reference just like file + /// let reference = file.get_mut(); + /// Ok(()) + /// } + /// ``` + pub fn get_mut(&mut self) -> &mut W { + self.inner.get_mut() + } +} + +impl Write for LineWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + if self.need_flush { + self.flush()?; + } + + // Find the last newline character in the buffer provided. If found then + // we're going to write all the data up to that point and then flush, + // otherwise we just write the whole block to the underlying writer. + let i = match memchr::memrchr(b'\n', buf) { + Some(i) => i, + None => return self.inner.write(buf), + }; + + // Ok, we're going to write a partial amount of the data given first + // followed by flushing the newline. After we've successfully written + // some data then we *must* report that we wrote that data, so future + // errors are ignored. We set our internal `need_flush` flag, though, in + // case flushing fails and we need to try it first next time. + let n = self.inner.write(&buf[..i + 1])?; + self.need_flush = true; + if self.flush().is_err() || n != i + 1 { + return Ok(n); + } + + // At this point we successfully wrote `i + 1` bytes and flushed it out, + // meaning that the entire line is now flushed out on the screen. While + // we can attempt to finish writing the rest of the data provided. + // Remember though that we ignore errors here as we've successfully + // written data, so we need to report that. + match self.inner.write(&buf[i + 1..]) { + Ok(i) => Ok(n + i), + Err(_) => Ok(n), + } + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush()?; + self.need_flush = false; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use alloc::string::String; + + use crate::io::{self, BufReader, BufWriter, LineWriter, SeekFrom, prelude::*}; + use test; + // use crate::sync::atomic::{AtomicUsize, Ordering}; + + /// A dummy reader intended at testing short-reads propagation. + pub struct ShortReader { + lengths: Vec, + } + + impl Read for ShortReader { + fn read(&mut self, _: &mut [u8]) -> io::Result { + if self.lengths.is_empty() { + Ok(0) + } else { + Ok(self.lengths.remove(0)) + } + } + } + + #[test] + fn test_buffered_reader() { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let mut reader = BufReader::with_capacity(2, inner); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(nread.unwrap(), 3); + let b: &[_] = &[5, 6, 7]; + assert_eq!(buf, b); + + let mut buf = [0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(nread.unwrap(), 2); + let b: &[_] = &[0, 1]; + assert_eq!(buf, b); + + let mut buf = [0]; + let nread = reader.read(&mut buf); + assert_eq!(nread.unwrap(), 1); + let b: &[_] = &[2]; + assert_eq!(buf, b); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(nread.unwrap(), 1); + let b: &[_] = &[3, 0, 0]; + assert_eq!(buf, b); + + let nread = reader.read(&mut buf); + assert_eq!(nread.unwrap(), 1); + let b: &[_] = &[4, 0, 0]; + assert_eq!(buf, b); + + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn test_buffered_reader_seek() { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let mut reader = BufReader::with_capacity(2, io::Cursor::new(inner)); + + assert_eq!(reader.seek(SeekFrom::Start(3)).ok(), Some(3)); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..])); + assert_eq!(reader.seek(SeekFrom::Current(0)).ok(), Some(3)); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..])); + assert_eq!(reader.seek(SeekFrom::Current(1)).ok(), Some(4)); + assert_eq!(reader.fill_buf().ok(), Some(&[1, 2][..])); + reader.consume(1); + assert_eq!(reader.seek(SeekFrom::Current(-2)).ok(), Some(3)); + } + + #[test] + fn test_buffered_reader_seek_relative() { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let mut reader = BufReader::with_capacity(2, io::Cursor::new(inner)); + + assert!(reader.seek_relative(3).is_ok()); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..])); + assert!(reader.seek_relative(0).is_ok()); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..])); + assert!(reader.seek_relative(1).is_ok()); + assert_eq!(reader.fill_buf().ok(), Some(&[1][..])); + assert!(reader.seek_relative(-1).is_ok()); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1][..])); + assert!(reader.seek_relative(2).is_ok()); + assert_eq!(reader.fill_buf().ok(), Some(&[2, 3][..])); + } + + #[test] + fn test_buffered_reader_seek_underflow() { + // gimmick reader that yields its position modulo 256 for each byte + struct PositionReader { + pos: u64, + } + impl Read for PositionReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = buf.len(); + for x in buf { + *x = self.pos as u8; + self.pos = self.pos.wrapping_add(1); + } + Ok(len) + } + } + impl Seek for PositionReader { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match pos { + SeekFrom::Start(n) => { + self.pos = n; + } + SeekFrom::Current(n) => { + self.pos = self.pos.wrapping_add(n as u64); + } + SeekFrom::End(n) => { + self.pos = u64::max_value().wrapping_add(n as u64); + } + } + Ok(self.pos) + } + } + + let mut reader = BufReader::with_capacity(5, PositionReader { pos: 0 }); + assert_eq!(reader.fill_buf().ok(), Some(&[0, 1, 2, 3, 4][..])); + assert_eq!( + reader.seek(SeekFrom::End(-5)).ok(), + Some(u64::max_value() - 5) + ); + assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5)); + // the following seek will require two underlying seeks + let expected = 9223372036854775802; + assert_eq!( + reader.seek(SeekFrom::Current(i64::min_value())).ok(), + Some(expected) + ); + assert_eq!(reader.fill_buf().ok().map(|s| s.len()), Some(5)); + // seeking to 0 should empty the buffer. + assert_eq!(reader.seek(SeekFrom::Current(0)).ok(), Some(expected)); + assert_eq!(reader.get_ref().pos, expected); + } + + #[test] + fn test_buffered_writer() { + let inner = Vec::new(); + let mut writer = BufWriter::with_capacity(2, inner); + + writer.write(&[0, 1]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[2]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[3]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[4]).unwrap(); + writer.write(&[5]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[6]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5]); + + writer.write(&[7, 8]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); + + writer.write(&[9, 10, 11]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + } + + #[test] + fn test_buffered_writer_inner_flushes() { + let mut w = BufWriter::with_capacity(3, Vec::new()); + w.write(&[0, 1]).unwrap(); + assert_eq!(*w.get_ref(), []); + let w = w.into_inner().unwrap(); + assert_eq!(w, [0, 1]); + } + + #[test] + fn test_buffered_writer_seek() { + let mut w = BufWriter::with_capacity(3, io::Cursor::new(Vec::new())); + w.write_all(&[0, 1, 2, 3, 4, 5]).unwrap(); + w.write_all(&[6, 7]).unwrap(); + assert_eq!(w.seek(SeekFrom::Current(0)).ok(), Some(8)); + assert_eq!(&w.get_ref().get_ref()[..], &[0, 1, 2, 3, 4, 5, 6, 7][..]); + assert_eq!(w.seek(SeekFrom::Start(2)).ok(), Some(2)); + w.write_all(&[8, 9]).unwrap(); + assert_eq!( + &w.into_inner().unwrap().into_inner()[..], + &[0, 1, 8, 9, 4, 5, 6, 7] + ); + } + + #[test] + fn test_read_until() { + let inner: &[u8] = &[0, 1, 2, 1, 0]; + let mut reader = BufReader::with_capacity(2, inner); + let mut v = Vec::new(); + reader.read_until(0, &mut v).unwrap(); + assert_eq!(v, [0]); + v.truncate(0); + reader.read_until(2, &mut v).unwrap(); + assert_eq!(v, [1, 2]); + v.truncate(0); + reader.read_until(1, &mut v).unwrap(); + assert_eq!(v, [1]); + v.truncate(0); + reader.read_until(8, &mut v).unwrap(); + assert_eq!(v, [0]); + v.truncate(0); + reader.read_until(9, &mut v).unwrap(); + assert_eq!(v, []); + } + + #[test] + fn test_line_buffer_fail_flush() { + // Issue #32085 + struct FailFlushWriter<'a>(&'a mut Vec); + + impl<'a> Write for FailFlushWriter<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.extend_from_slice(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Err(io::Error::new(io::ErrorKind::Other, "flush failed")) + } + } + + let mut buf = Vec::new(); + { + let mut writer = LineWriter::new(FailFlushWriter(&mut buf)); + let to_write = b"abc\ndef"; + if let Ok(written) = writer.write(to_write) { + assert!(written < to_write.len(), "didn't flush on new line"); + // PASS + return; + } + } + assert!(buf.is_empty(), "write returned an error but wrote data"); + } + + #[test] + fn test_line_buffer() { + let mut writer = LineWriter::new(Vec::new()); + writer.write(&[0]).unwrap(); + assert_eq!(*writer.get_ref(), []); + writer.write(&[1]).unwrap(); + assert_eq!(*writer.get_ref(), []); + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + writer.write(&[0, b'\n', 1, b'\n', 2]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n']); + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2]); + writer.write(&[3, b'\n']).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2, 3, b'\n']); + } + + #[test] + fn test_read_line() { + let in_buf: &[u8] = b"a\nb\nc"; + let mut reader = BufReader::with_capacity(2, in_buf); + let mut s = String::new(); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "a\n"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "b\n"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "c"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, ""); + } + + // #[test] + // fn test_lines() { + // let in_buf: &[u8] = b"a\nb\nc"; + // let reader = BufReader::with_capacity(2, in_buf); + // let mut it = reader.lines(); + // assert_eq!(it.next().unwrap().unwrap(), "a".to_string()); + // assert_eq!(it.next().unwrap().unwrap(), "b".to_string()); + // assert_eq!(it.next().unwrap().unwrap(), "c".to_string()); + // assert!(it.next().is_none()); + // } + + #[test] + fn test_short_reads() { + let inner = ShortReader { + lengths: vec![0, 1, 2, 0, 1, 0], + }; + let mut reader = BufReader::new(inner); + let mut buf = [0, 0]; + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.read(&mut buf).unwrap(), 2); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + #[should_panic] + fn dont_panic_in_drop_on_panicked_flush() { + struct FailFlushWriter; + + impl Write for FailFlushWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Err(io::Error::last_os_error()) + } + } + + let writer = FailFlushWriter; + let _writer = BufWriter::new(writer); + + // If writer panics *again* due to the flush error then the process will + // abort. + panic!(); + } + + // #[test] + // #[cfg_attr(target_os = "emscripten", ignore)] + // fn panic_in_write_doesnt_flush_in_drop() { + // static WRITES: AtomicUsize = AtomicUsize::new(0); + + // struct PanicWriter; + + // impl Write for PanicWriter { + // fn write(&mut self, _: &[u8]) -> io::Result { + // WRITES.fetch_add(1, Ordering::SeqCst); + // panic!(); + // } + // fn flush(&mut self) -> io::Result<()> { Ok(()) } + // } + + // thread::spawn(|| { + // let mut writer = BufWriter::new(PanicWriter); + // let _ = writer.write(b"hello world"); + // let _ = writer.flush(); + // }).join().unwrap_err(); + + // assert_eq!(WRITES.load(Ordering::SeqCst), 1); + // } + + // #[bench] + // fn bench_buffered_reader(b: &mut test::Bencher) { + // b.iter(|| { + // BufReader::new(io::empty()) + // }); + // } + + // #[bench] + // fn bench_buffered_writer(b: &mut test::Bencher) { + // b.iter(|| { + // BufWriter::new(io::sink()) + // }); + // } + + struct AcceptOneThenFail { + written: bool, + flushed: bool, + } + + impl Write for AcceptOneThenFail { + fn write(&mut self, data: &[u8]) -> io::Result { + if !self.written { + assert_eq!(data, b"a\nb\n"); + self.written = true; + Ok(data.len()) + } else { + Err(io::Error::new(io::ErrorKind::NotFound, "test")) + } + } + + fn flush(&mut self) -> io::Result<()> { + assert!(self.written); + assert!(!self.flushed); + self.flushed = true; + Err(io::Error::new(io::ErrorKind::Other, "test")) + } + } + + #[test] + fn erroneous_flush_retried() { + let a = AcceptOneThenFail { + written: false, + flushed: false, + }; + + let mut l = LineWriter::new(a); + assert_eq!(l.write(b"a\nb\na").unwrap(), 4); + assert!(l.get_ref().written); + assert!(l.get_ref().flushed); + l.get_mut().flushed = false; + + assert_eq!(l.write(b"a").unwrap_err().kind(), io::ErrorKind::Other) + } +} diff --git a/src/io/cursor.rs b/src/io/cursor.rs new file mode 100644 index 0000000000..fd5500137c --- /dev/null +++ b/src/io/cursor.rs @@ -0,0 +1,724 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::cmp; + +use crate::io::{self, Error, ErrorKind, Initializer, SeekFrom, prelude::*}; + +/// A `Cursor` wraps an in-memory buffer and provides it with a +/// [`Seek`] implementation. +/// +/// `Cursor`s are used with in-memory buffers, anything implementing +/// `AsRef<[u8]>`, to allow them to implement [`Read`] and/or [`Write`], +/// allowing these buffers to be used anywhere you might use a reader or writer +/// that does actual I/O. +/// +/// The standard library implements some I/O traits on various types which +/// are commonly used as a buffer, like `Cursor<`[`Vec`]`>` and +/// `Cursor<`[`&[u8]`][bytes]`>`. +/// +/// # Examples +/// +/// We may want to write bytes to a [`File`] in our production +/// code, but use an in-memory buffer in our tests. We can do this with +/// `Cursor`: +/// +/// [`Seek`]: trait.Seek.html +/// [`Read`]: ../../std/io/trait.Read.html +/// [`Write`]: ../../std/io/trait.Write.html +/// [`Vec`]: ../../std/vec/struct.Vec.html +/// [bytes]: ../../std/primitive.slice.html +/// [`File`]: ../fs/struct.File.html +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::io::{self, SeekFrom}; +/// use std::fs::File; +/// +/// // a library function we've written +/// fn write_ten_bytes_at_end(writer: &mut W) -> io::Result<()> { +/// writer.seek(SeekFrom::End(-10))?; +/// +/// for i in 0..10 { +/// writer.write(&[i])?; +/// } +/// +/// // all went well +/// Ok(()) +/// } +/// +/// # fn foo() -> io::Result<()> { +/// // Here's some code that uses this library function. +/// // +/// // We might want to use a BufReader here for efficiency, but let's +/// // keep this example focused. +/// let mut file = File::create("foo.txt")?; +/// +/// write_ten_bytes_at_end(&mut file)?; +/// # Ok(()) +/// # } +/// +/// // now let's write a test +/// #[test] +/// fn test_writes_bytes() { +/// // setting up a real File is much slower than an in-memory buffer, +/// // let's use a cursor instead +/// use std::io::Cursor; +/// let mut buff = Cursor::new(vec![0; 15]); +/// +/// write_ten_bytes_at_end(&mut buff).unwrap(); +/// +/// assert_eq!(&buff.get_ref()[5..15], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + /// Creates a new cursor wrapping the provided underlying in-memory buffer. + /// + /// Cursor initial position is `0` even if underlying buffer (e.g. `Vec`) + /// is not empty. So writing to cursor starts with overwriting `Vec` + /// content, not with appending to it. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// + /// let buff = Cursor::new(Vec::new()); + /// # fn force_inference(_: &Cursor>) {} + /// # force_inference(&buff); + /// ``` + pub fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner } + } + + /// Consumes this cursor, returning the underlying value. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// + /// let buff = Cursor::new(Vec::new()); + /// # fn force_inference(_: &Cursor>) {} + /// # force_inference(&buff); + /// + /// let vec = buff.into_inner(); + /// ``` + pub fn into_inner(self) -> T { + self.inner + } + + /// Gets a reference to the underlying value in this cursor. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// + /// let buff = Cursor::new(Vec::new()); + /// # fn force_inference(_: &Cursor>) {} + /// # force_inference(&buff); + /// + /// let reference = buff.get_ref(); + /// ``` + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets a mutable reference to the underlying value in this cursor. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying value as it may corrupt this cursor's position. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// + /// let mut buff = Cursor::new(Vec::new()); + /// # fn force_inference(_: &Cursor>) {} + /// # force_inference(&buff); + /// + /// let reference = buff.get_mut(); + /// ``` + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + /// Returns the current position of this cursor. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// use std::io::prelude::*; + /// use std::io::SeekFrom; + /// + /// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]); + /// + /// assert_eq!(buff.position(), 0); + /// + /// buff.seek(SeekFrom::Current(2)).unwrap(); + /// assert_eq!(buff.position(), 2); + /// + /// buff.seek(SeekFrom::Current(-1)).unwrap(); + /// assert_eq!(buff.position(), 1); + /// ``` + pub fn position(&self) -> u64 { + self.pos + } + + /// Sets the position of this cursor. + /// + /// # Examples + /// + /// ``` + /// use std::io::Cursor; + /// + /// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]); + /// + /// assert_eq!(buff.position(), 0); + /// + /// buff.set_position(2); + /// assert_eq!(buff.position(), 2); + /// + /// buff.set_position(4); + /// assert_eq!(buff.position(), 4); + /// ``` + pub fn set_position(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl io::Seek for Cursor +where + T: AsRef<[u8]>, +{ + fn seek(&mut self, style: SeekFrom) -> io::Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.pos = n; + return Ok(n); + } + SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), + SeekFrom::Current(n) => (self.pos, n), + }; + let new_pos = if offset >= 0 { + base_pos.checked_add(offset as u64) + } else { + base_pos.checked_sub((offset.wrapping_neg()) as u64) + }; + match new_pos { + Some(n) => { + self.pos = n; + Ok(self.pos) + } + None => Err(Error::new( + ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + )), + } + } +} + +impl Read for Cursor +where + T: AsRef<[u8]>, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let n = Read::read(&mut self.get_buf()?, buf)?; + self.pos += n as u64; + Ok(n) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + let n = buf.len(); + Read::read_exact(&mut self.get_buf()?, buf)?; + self.pos += n as u64; + Ok(()) + } + + #[inline] + unsafe fn initializer(&self) -> Initializer { + unsafe { Initializer::nop() } + } +} + +impl Cursor +where + T: AsRef<[u8]>, +{ + fn get_buf(&mut self) -> io::Result<&[u8]> { + let amt = cmp::min(self.pos, self.inner.as_ref().len() as u64); + Ok(&self.inner.as_ref()[(amt as usize)..]) + } +} + +impl BufRead for Cursor +where + T: AsRef<[u8]>, +{ + fn fill_buf(&mut self) -> io::Result<&[u8]> { + self.get_buf() + } + fn consume(&mut self, amt: usize) { + self.pos += amt as u64; + } +} + +// Non-resizing write implementation +fn slice_write(pos_mut: &mut u64, slice: &mut [u8], buf: &[u8]) -> io::Result { + let pos = cmp::min(*pos_mut, slice.len() as u64); + let amt = (&mut slice[(pos as usize)..]).write(buf)?; + *pos_mut += amt as u64; + Ok(amt) +} + +// Resizing write implementation +fn vec_write(pos_mut: &mut u64, vec: &mut Vec, buf: &[u8]) -> io::Result { + let pos: usize = (*pos_mut).try_into().map_err(|_| { + Error::new( + ErrorKind::InvalidInput, + "cursor position exceeds maximum possible vector length", + ) + })?; + // Make sure the internal buffer is as least as big as where we + // currently are + let len = vec.len(); + if len < pos { + // use `resize` so that the zero filling is as efficient as possible + vec.resize(pos, 0); + } + // Figure out what bytes will be used to overwrite what's currently + // there (left), and what will be appended on the end (right) + { + let space = vec.len() - pos; + let (left, right) = buf.split_at(cmp::min(space, buf.len())); + vec[pos..pos + left.len()].copy_from_slice(left); + vec.extend_from_slice(right); + } + + // Bump us forward + *pos_mut = (pos + buf.len()) as u64; + Ok(buf.len()) +} + +impl Write for Cursor<&mut [u8]> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + slice_write(&mut self.pos, self.inner, buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Write for Cursor<&mut Vec> { + fn write(&mut self, buf: &[u8]) -> io::Result { + vec_write(&mut self.pos, self.inner, buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Write for Cursor> { + fn write(&mut self, buf: &[u8]) -> io::Result { + vec_write(&mut self.pos, &mut self.inner, buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Write for Cursor<::alloc::boxed::Box<[u8]>> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + slice_write(&mut self.pos, &mut self.inner, buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::io::{Cursor, SeekFrom, prelude::*}; + + #[test] + fn test_vec_writer() { + let mut writer = Vec::new(); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(writer, b); + } + + #[test] + fn test_mem_writer() { + let mut writer = Cursor::new(Vec::new()); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[..], b); + } + + #[test] + fn test_mem_mut_writer() { + let mut vec = Vec::new(); + let mut writer = Cursor::new(&mut vec); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[..], b); + } + + #[test] + fn test_box_slice_writer() { + let mut writer = Cursor::new(vec![0u8; 9].into_boxed_slice()); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + assert_eq!(writer.position(), 8); + assert_eq!(writer.write(&[]).unwrap(), 0); + assert_eq!(writer.position(), 8); + + assert_eq!(writer.write(&[8, 9]).unwrap(), 1); + assert_eq!(writer.write(&[10]).unwrap(), 0); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8]; + assert_eq!(&**writer.get_ref(), b); + } + + #[test] + fn test_buf_writer() { + let mut buf = [0 as u8; 9]; + { + let mut writer = Cursor::new(&mut buf[..]); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + assert_eq!(writer.position(), 8); + assert_eq!(writer.write(&[]).unwrap(), 0); + assert_eq!(writer.position(), 8); + + assert_eq!(writer.write(&[8, 9]).unwrap(), 1); + assert_eq!(writer.write(&[10]).unwrap(), 0); + } + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8]; + assert_eq!(buf, b); + } + + #[test] + fn test_buf_writer_seek() { + let mut buf = [0 as u8; 8]; + { + let mut writer = Cursor::new(&mut buf[..]); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[1]).unwrap(), 1); + assert_eq!(writer.position(), 1); + + assert_eq!(writer.seek(SeekFrom::Start(2)).unwrap(), 2); + assert_eq!(writer.position(), 2); + assert_eq!(writer.write(&[2]).unwrap(), 1); + assert_eq!(writer.position(), 3); + + assert_eq!(writer.seek(SeekFrom::Current(-2)).unwrap(), 1); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[3]).unwrap(), 1); + assert_eq!(writer.position(), 2); + + assert_eq!(writer.seek(SeekFrom::End(-1)).unwrap(), 7); + assert_eq!(writer.position(), 7); + assert_eq!(writer.write(&[4]).unwrap(), 1); + assert_eq!(writer.position(), 8); + } + let b: &[_] = &[1, 3, 2, 0, 0, 0, 0, 4]; + assert_eq!(buf, b); + } + + #[test] + fn test_buf_writer_error() { + let mut buf = [0 as u8; 2]; + let mut writer = Cursor::new(&mut buf[..]); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.write(&[0, 0]).unwrap(), 1); + assert_eq!(writer.write(&[0, 0]).unwrap(), 0); + } + + #[test] + fn test_mem_reader() { + let mut reader = Cursor::new(vec![0, 1, 2, 3, 4, 5, 6, 7]); + let mut buf = []; + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.position(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.position(), 1); + let b: &[_] = &[0]; + assert_eq!(buf, b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf).unwrap(), 4); + assert_eq!(reader.position(), 5); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf, b); + assert_eq!(reader.read(&mut buf).unwrap(), 3); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn test_boxed_slice_reader() { + let mut reader = Cursor::new(vec![0, 1, 2, 3, 4, 5, 6, 7].into_boxed_slice()); + let mut buf = []; + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.position(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.position(), 1); + let b: &[_] = &[0]; + assert_eq!(buf, b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf).unwrap(), 4); + assert_eq!(reader.position(), 5); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf, b); + assert_eq!(reader.read(&mut buf).unwrap(), 3); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn read_to_end() { + let mut reader = Cursor::new(vec![0, 1, 2, 3, 4, 5, 6, 7]); + let mut v = Vec::new(); + reader.read_to_end(&mut v).unwrap(); + assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7]); + } + + #[test] + fn test_slice_reader() { + let in_buf = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let reader = &mut &in_buf[..]; + let mut buf = []; + assert_eq!(reader.read(&mut buf).unwrap(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.len(), 7); + let b: &[_] = &[0]; + assert_eq!(&buf[..], b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf).unwrap(), 4); + assert_eq!(reader.len(), 3); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(&buf[..], b); + assert_eq!(reader.read(&mut buf).unwrap(), 3); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn test_read_exact() { + let in_buf = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let reader = &mut &in_buf[..]; + let mut buf = []; + assert!(reader.read_exact(&mut buf).is_ok()); + let mut buf = [8]; + assert!(reader.read_exact(&mut buf).is_ok()); + assert_eq!(buf[0], 0); + assert_eq!(reader.len(), 7); + let mut buf = [0, 0, 0, 0, 0, 0, 0]; + assert!(reader.read_exact(&mut buf).is_ok()); + assert_eq!(buf, [1, 2, 3, 4, 5, 6, 7]); + assert_eq!(reader.len(), 0); + let mut buf = [0]; + assert!(reader.read_exact(&mut buf).is_err()); + } + + #[test] + fn test_buf_reader() { + let in_buf = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let mut reader = Cursor::new(&in_buf[..]); + let mut buf = []; + assert_eq!(reader.read(&mut buf).unwrap(), 0); + assert_eq!(reader.position(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf).unwrap(), 1); + assert_eq!(reader.position(), 1); + let b: &[_] = &[0]; + assert_eq!(buf, b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf).unwrap(), 4); + assert_eq!(reader.position(), 5); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf, b); + assert_eq!(reader.read(&mut buf).unwrap(), 3); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf).unwrap(), 0); + } + + #[test] + fn seek_past_end() { + let buf = [0xff]; + let mut r = Cursor::new(&buf[..]); + assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10); + assert_eq!(r.read(&mut [0]).unwrap(), 0); + + let mut r = Cursor::new(vec![10]); + assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10); + assert_eq!(r.read(&mut [0]).unwrap(), 0); + + let mut buf = [0]; + let mut r = Cursor::new(&mut buf[..]); + assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10); + assert_eq!(r.write(&[3]).unwrap(), 0); + + let mut r = Cursor::new(vec![10].into_boxed_slice()); + assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10); + assert_eq!(r.write(&[3]).unwrap(), 0); + } + + #[test] + fn seek_past_i64() { + let buf = [0xff]; + let mut r = Cursor::new(&buf[..]); + assert_eq!(r.seek(SeekFrom::Start(6)).unwrap(), 6); + assert_eq!( + r.seek(SeekFrom::Current(0x7ffffffffffffff0)).unwrap(), + 0x7ffffffffffffff6 + ); + assert_eq!(r.seek(SeekFrom::Current(0x10)).unwrap(), 0x8000000000000006); + assert_eq!(r.seek(SeekFrom::Current(0)).unwrap(), 0x8000000000000006); + assert!(r.seek(SeekFrom::Current(0x7ffffffffffffffd)).is_err()); + assert_eq!(r.seek(SeekFrom::Current(-0x8000000000000000)).unwrap(), 6); + + let mut r = Cursor::new(vec![10]); + assert_eq!(r.seek(SeekFrom::Start(6)).unwrap(), 6); + assert_eq!( + r.seek(SeekFrom::Current(0x7ffffffffffffff0)).unwrap(), + 0x7ffffffffffffff6 + ); + assert_eq!(r.seek(SeekFrom::Current(0x10)).unwrap(), 0x8000000000000006); + assert_eq!(r.seek(SeekFrom::Current(0)).unwrap(), 0x8000000000000006); + assert!(r.seek(SeekFrom::Current(0x7ffffffffffffffd)).is_err()); + assert_eq!(r.seek(SeekFrom::Current(-0x8000000000000000)).unwrap(), 6); + + let mut buf = [0]; + let mut r = Cursor::new(&mut buf[..]); + assert_eq!(r.seek(SeekFrom::Start(6)).unwrap(), 6); + assert_eq!( + r.seek(SeekFrom::Current(0x7ffffffffffffff0)).unwrap(), + 0x7ffffffffffffff6 + ); + assert_eq!(r.seek(SeekFrom::Current(0x10)).unwrap(), 0x8000000000000006); + assert_eq!(r.seek(SeekFrom::Current(0)).unwrap(), 0x8000000000000006); + assert!(r.seek(SeekFrom::Current(0x7ffffffffffffffd)).is_err()); + assert_eq!(r.seek(SeekFrom::Current(-0x8000000000000000)).unwrap(), 6); + + let mut r = Cursor::new(vec![10].into_boxed_slice()); + assert_eq!(r.seek(SeekFrom::Start(6)).unwrap(), 6); + assert_eq!( + r.seek(SeekFrom::Current(0x7ffffffffffffff0)).unwrap(), + 0x7ffffffffffffff6 + ); + assert_eq!(r.seek(SeekFrom::Current(0x10)).unwrap(), 0x8000000000000006); + assert_eq!(r.seek(SeekFrom::Current(0)).unwrap(), 0x8000000000000006); + assert!(r.seek(SeekFrom::Current(0x7ffffffffffffffd)).is_err()); + assert_eq!(r.seek(SeekFrom::Current(-0x8000000000000000)).unwrap(), 6); + } + + #[test] + fn seek_before_0() { + let buf = [0xff]; + let mut r = Cursor::new(&buf[..]); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + + let mut r = Cursor::new(vec![10]); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + + let mut buf = [0]; + let mut r = Cursor::new(&mut buf[..]); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + + let mut r = Cursor::new(vec![10].into_boxed_slice()); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + } + + #[test] + fn test_seekable_mem_writer() { + let mut writer = Cursor::new(Vec::::new()); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[0]).unwrap(), 1); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(writer.write(&[4, 5, 6, 7]).unwrap(), 4); + assert_eq!(writer.position(), 8); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[..], b); + + assert_eq!(writer.seek(SeekFrom::Start(0)).unwrap(), 0); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[3, 4]).unwrap(), 2); + let b: &[_] = &[3, 4, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[..], b); + + assert_eq!(writer.seek(SeekFrom::Current(1)).unwrap(), 3); + assert_eq!(writer.write(&[0, 1]).unwrap(), 2); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 7]; + assert_eq!(&writer.get_ref()[..], b); + + assert_eq!(writer.seek(SeekFrom::End(-1)).unwrap(), 7); + assert_eq!(writer.write(&[1, 2]).unwrap(), 2); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 1, 2]; + assert_eq!(&writer.get_ref()[..], b); + + assert_eq!(writer.seek(SeekFrom::End(1)).unwrap(), 10); + assert_eq!(writer.write(&[1]).unwrap(), 1); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 1, 2, 0, 1]; + assert_eq!(&writer.get_ref()[..], b); + } + + #[test] + fn vec_seek_past_end() { + let mut r = Cursor::new(Vec::new()); + assert_eq!(r.seek(SeekFrom::Start(10)).unwrap(), 10); + assert_eq!(r.write(&[3]).unwrap(), 1); + } + + #[test] + fn vec_seek_before_0() { + let mut r = Cursor::new(Vec::new()); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + } + + #[test] + #[cfg(target_pointer_width = "32")] + fn vec_seek_and_write_past_usize_max() { + let mut c = Cursor::new(Vec::new()); + c.set_position(::max_value() as u64 + 1); + assert!(c.write_all(&[1, 2, 3]).is_err()); + } +} diff --git a/src/io/error.rs b/src/io/error.rs new file mode 100644 index 0000000000..203f628a88 --- /dev/null +++ b/src/io/error.rs @@ -0,0 +1,313 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::{boxed::Box, string::String}; +use core::{fmt, result, str}; + +use crate::platform::types::c_int; + +/// A specialized [`Result`](../result/enum.Result.html) type for I/O +/// operations. +/// +/// This type is broadly used across [`std::io`] for any operation which may +/// produce an error. +/// +/// This typedef is generally used to avoid writing out [`io::Error`] directly and +/// is otherwise a direct mapping to [`Result`]. +/// +/// While usual Rust style is to import types directly, aliases of [`Result`] +/// often are not, to make it easier to distinguish between them. [`Result`] is +/// generally assumed to be [`std::result::Result`][`Result`], and so users of this alias +/// will generally use `io::Result` instead of shadowing the prelude's import +/// of [`std::result::Result`][`Result`]. +/// +/// [`std::io`]: ../io/index.html +/// [`io::Error`]: ../io/struct.Error.html +/// [`Result`]: ../result/enum.Result.html +/// +/// # Examples +/// +/// A convenience function that bubbles an `io::Result` to its caller: +/// +/// ``` +/// use std::io; +/// +/// fn get_string() -> io::Result { +/// let mut buffer = String::new(); +/// +/// io::stdin().read_line(&mut buffer)?; +/// +/// Ok(buffer) +/// } +/// ``` +pub type Result = result::Result; + +/// The error type for I/O operations of the [`Read`], [`Write`], [`Seek`], and +/// associated traits. +/// +/// Errors mostly originate from the underlying OS, but custom instances of +/// `Error` can be created with crafted error messages and a particular value of +/// [`ErrorKind`]. +/// +/// [`Read`]: ../io/trait.Read.html +/// [`Write`]: ../io/trait.Write.html +/// [`Seek`]: ../io/trait.Seek.html +/// [`ErrorKind`]: enum.ErrorKind.html +pub struct Error { + repr: Repr, +} + +// TODO? +impl Error { + pub fn raw_os_error(&self) -> Option { + if let Repr::Os(os) = self.repr { + Some(os) + } else { + None + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.repr, f) + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self.repr { + Repr::Os(code) => { + write!(fmt, "os error {}", code) + } + Repr::Custom(ref c) => c.error.fmt(fmt), + Repr::Simple(kind) => write!(fmt, "{}", kind.as_str()), + } + } +} + +impl Error { + /// Creates a new I/O error from a known kind of error as well as an + /// arbitrary error payload. + /// + /// This function is used to generically create I/O errors which do not + /// originate from the OS itself. The `error` argument is an arbitrary + /// payload which will be contained in this `Error`. + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// // errors can be created from strings + /// let custom_error = Error::new(ErrorKind::Other, "oh no!"); + /// + /// // errors can also be created from other errors + /// let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error); + /// ``` + pub fn new(kind: ErrorKind, error: E) -> Error + where + E: Into, + { + Self::_new(kind, error.into()) + } + + fn _new(kind: ErrorKind, error: String) -> Error { + Error { + repr: Repr::Custom(Box::new(Custom { kind, error })), + } + } + + /// Creates a new instance of an `Error` from a particular OS error code. + /// + /// # Examples + /// + /// On Linux: + /// + /// ``` + /// # if cfg!(target_os = "linux") { + /// use std::io; + /// + /// let error = io::Error::from_raw_os_error(22); + /// assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + /// # } + /// ``` + /// + /// On Windows: + /// + /// ``` + /// # if cfg!(windows) { + /// use std::io; + /// + /// let error = io::Error::from_raw_os_error(10022); + /// assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + /// # } + /// ``` + pub fn from_raw_os_error(code: i32) -> Error { + Error { + repr: Repr::Os(code), + } + } + + /// Returns the corresponding `ErrorKind` for this error. + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// fn print_error(err: Error) { + /// println!("{:?}", err.kind()); + /// } + /// + /// fn main() { + /// // Will print "No inner error". + /// print_error(Error::last_os_error()); + /// // Will print "Inner error: ...". + /// print_error(Error::new(ErrorKind::AddrInUse, "oh no!")); + /// } + /// ``` + pub fn kind(&self) -> ErrorKind { + match self.repr { + Repr::Os(_code) => ErrorKind::Other, + Repr::Custom(ref c) => c.kind, + Repr::Simple(kind) => kind, + } + } + + pub fn last_os_error() -> Error { + let errno = crate::platform::ERRNO.get(); + Error::from_raw_os_error(errno) + } +} + +enum Repr { + Os(i32), + Simple(ErrorKind), + Custom(Box), +} + +impl fmt::Debug for Repr { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::Os(code) => fmt.debug_struct("Os").field("code", &code).finish(), + Repr::Custom(ref c) => fmt::Debug::fmt(&c, fmt), + Repr::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(), + } + } +} + +#[derive(Debug)] +struct Custom { + kind: ErrorKind, + error: String, +} + +/// A list specifying general categories of I/O error. +/// +/// This list is intended to grow over time and it is not recommended to +/// exhaustively match against it. +/// +/// It is used with the [`io::Error`] type. +/// +/// [`io::Error`]: struct.Error.html +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[non_exhaustive] +pub enum ErrorKind { + /// An entity was not found, often a file. + NotFound, + /// The operation lacked the necessary privileges to complete. + PermissionDenied, + /// The connection was refused by the remote server. + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// The connection was aborted (terminated) by the remote server. + ConnectionAborted, + /// The network operation failed because it was not connected yet. + NotConnected, + /// A socket address could not be bound because the address is already in + /// use elsewhere. + AddrInUse, + /// A nonexistent interface was requested or the requested address was not + /// local. + AddrNotAvailable, + /// The operation failed because a pipe was closed. + BrokenPipe, + /// An entity already exists, often a file. + AlreadyExists, + /// The operation needs to block to complete, but the blocking operation was + /// requested to not occur. + WouldBlock, + /// A parameter was incorrect. + InvalidInput, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: #variant.InvalidInput + InvalidData, + /// The I/O operation's timeout expired, causing it to be canceled. + TimedOut, + /// An error returned when an operation could not be completed because a + /// call to [`write`] returned [`Ok(0)`]. + /// + /// This typically means that an operation could only succeed if it wrote a + /// particular number of bytes but only a smaller number of bytes could be + /// written. + /// + /// [`write`]: ../../std/io/trait.Write.html#tymethod.write + /// [`Ok(0)`]: ../../std/io/type.Result.html + WriteZero, + /// This operation was interrupted. + /// + /// Interrupted operations can typically be retried. + Interrupted, + /// Any I/O error not part of this list. + Other, + + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a + /// particular number of bytes but only a smaller number of bytes could be + /// read. + UnexpectedEof, +} + +impl ErrorKind { + fn as_str(&self) -> &'static str { + match *self { + ErrorKind::NotFound => "entity not found", + ErrorKind::PermissionDenied => "permission denied", + ErrorKind::ConnectionRefused => "connection refused", + ErrorKind::ConnectionReset => "connection reset", + ErrorKind::ConnectionAborted => "connection aborted", + ErrorKind::NotConnected => "not connected", + ErrorKind::AddrInUse => "address in use", + ErrorKind::AddrNotAvailable => "address not available", + ErrorKind::BrokenPipe => "broken pipe", + ErrorKind::AlreadyExists => "entity already exists", + ErrorKind::WouldBlock => "operation would block", + ErrorKind::InvalidInput => "invalid input parameter", + ErrorKind::InvalidData => "invalid data", + ErrorKind::TimedOut => "timed out", + ErrorKind::WriteZero => "write zero", + ErrorKind::Interrupted => "operation interrupted", + ErrorKind::Other => "other os error", + ErrorKind::UnexpectedEof => "unexpected end of file", + } + } +} diff --git a/src/io/impls.rs b/src/io/impls.rs new file mode 100644 index 0000000000..8b74284081 --- /dev/null +++ b/src/io/impls.rs @@ -0,0 +1,298 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use alloc::string::String; +use core::{cmp, fmt, mem}; + +use crate::io::{self, Error, ErrorKind, Initializer, Seek, SeekFrom, Write, prelude::*}; + +impl Read for &mut R { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + + #[inline] + unsafe fn initializer(&self) -> Initializer { + unsafe { (**self).initializer() } + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + (**self).read_to_end(buf) + } + + #[inline] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + (**self).read_to_string(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + (**self).read_exact(buf) + } +} + +impl Write for &mut W { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} +impl Seek for &mut S { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> io::Result { + (**self).seek(pos) + } +} + +impl BufRead for &mut B { + #[inline] + fn fill_buf(&mut self) -> io::Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } + + #[inline] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + (**self).read_until(byte, buf) + } + + #[inline] + fn read_line(&mut self, buf: &mut String) -> io::Result { + (**self).read_line(buf) + } +} + +impl Read for Box { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (**self).read(buf) + } + + #[inline] + unsafe fn initializer(&self) -> Initializer { + unsafe { (**self).initializer() } + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + (**self).read_to_end(buf) + } + + #[inline] + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + (**self).read_to_string(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + (**self).read_exact(buf) + } +} + +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + (**self).write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> { + (**self).write_fmt(fmt) + } +} + +impl Seek for Box { + #[inline] + fn seek(&mut self, pos: SeekFrom) -> io::Result { + (**self).seek(pos) + } +} + +impl BufRead for Box { + #[inline] + fn fill_buf(&mut self) -> io::Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } + + #[inline] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + (**self).read_until(byte, buf) + } + + #[inline] + fn read_line(&mut self, buf: &mut String) -> io::Result { + (**self).read_line(buf) + } +} + +// ============================================================================= +// In-memory buffer implementations + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let amt = cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } + + #[inline] + unsafe fn initializer(&self) -> Initializer { + unsafe { Initializer::nop() } + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + if buf.len() > self.len() { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )); + } + let (a, b) = self.split_at(buf.len()); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if buf.len() == 1 { + buf[0] = a[0]; + } else { + buf.copy_from_slice(a); + } + + *self = b; + Ok(()) + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + buf.extend_from_slice(self); + let len = self.len(); + *self = &self[len..]; + Ok(len) + } +} + +impl BufRead for &[u8] { + #[inline] + fn fill_buf(&mut self) -> io::Result<&[u8]> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, data: &[u8]) -> io::Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&data[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn write_all(&mut self, data: &[u8]) -> io::Result<()> { + if self.write(data)? == data.len() { + Ok(()) + } else { + Err(Error::new( + ErrorKind::WriteZero, + "failed to write whole buffer", + )) + } + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Write is implemented for `Vec` by appending to the vector. +/// The vector will grow as needed. +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + self.extend_from_slice(buf); + Ok(()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs new file mode 100644 index 0000000000..d4f09e1d2d --- /dev/null +++ b/src/io/mod.rs @@ -0,0 +1,2266 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Traits, helpers, and type definitions for core I/O functionality. +//! +//! The `std::io` module contains a number of common things you'll need +//! when doing input and output. The most core part of this module is +//! the [`Read`] and [`Write`] traits, which provide the +//! most general interface for reading and writing input and output. +//! +//! # Read and Write +//! +//! Because they are traits, [`Read`] and [`Write`] are implemented by a number +//! of other types, and you can implement them for your types too. As such, +//! you'll see a few different types of I/O throughout the documentation in +//! this module: [`File`]s, [`TcpStream`]s, and sometimes even [`Vec`]s. For +//! example, [`Read`] adds a [`read`][`Read::read`] method, which we can use on +//! [`File`]s: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! use std::fs::File; +//! +//! fn main() -> io::Result<()> { +//! let mut f = File::open("foo.txt")?; +//! let mut buffer = [0; 10]; +//! +//! // read up to 10 bytes +//! f.read(&mut buffer)?; +//! +//! println!("The bytes: {:?}", buffer); +//! Ok(()) +//! } +//! ``` +//! +//! [`Read`] and [`Write`] are so important, implementors of the two traits have a +//! nickname: readers and writers. So you'll sometimes see 'a reader' instead +//! of 'a type that implements the [`Read`] trait'. Much easier! +//! +//! ## Seek and BufRead +//! +//! Beyond that, there are two important traits that are provided: [`Seek`] +//! and [`BufRead`]. Both of these build on top of a reader to control +//! how the reading happens. [`Seek`] lets you control where the next byte is +//! coming from: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! use std::io::SeekFrom; +//! use std::fs::File; +//! +//! fn main() -> io::Result<()> { +//! let mut f = File::open("foo.txt")?; +//! let mut buffer = [0; 10]; +//! +//! // skip to the last 10 bytes of the file +//! f.seek(SeekFrom::End(-10))?; +//! +//! // read up to 10 bytes +//! f.read(&mut buffer)?; +//! +//! println!("The bytes: {:?}", buffer); +//! Ok(()) +//! } +//! ``` +//! +//! [`BufRead`] uses an internal buffer to provide a number of other ways to read, but +//! to show it off, we'll need to talk about buffers in general. Keep reading! +//! +//! ## BufReader and BufWriter +//! +//! Byte-based interfaces are unwieldy and can be inefficient, as we'd need to be +//! making near-constant calls to the operating system. To help with this, +//! `std::io` comes with two structs, [`BufReader`] and [`BufWriter`], which wrap +//! readers and writers. The wrapper uses a buffer, reducing the number of +//! calls and providing nicer methods for accessing exactly what you want. +//! +//! For example, [`BufReader`] works with the [`BufRead`] trait to add extra +//! methods to any reader: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! use std::io::BufReader; +//! use std::fs::File; +//! +//! fn main() -> io::Result<()> { +//! let f = File::open("foo.txt")?; +//! let mut reader = BufReader::new(f); +//! let mut buffer = String::new(); +//! +//! // read a line into buffer +//! reader.read_line(&mut buffer)?; +//! +//! println!("{}", buffer); +//! Ok(()) +//! } +//! ``` +//! +//! [`BufWriter`] doesn't add any new ways of writing; it just buffers every call +//! to [`write`][`Write::write`]: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! use std::io::BufWriter; +//! use std::fs::File; +//! +//! fn main() -> io::Result<()> { +//! let f = File::create("foo.txt")?; +//! { +//! let mut writer = BufWriter::new(f); +//! +//! // write a byte to the buffer +//! writer.write(&[42])?; +//! +//! } // the buffer is flushed once writer goes out of scope +//! +//! Ok(()) +//! } +//! ``` +//! +//! ## Standard input and output +//! +//! A very common source of input is standard input: +//! +//! ```no_run +//! use std::io; +//! +//! fn main() -> io::Result<()> { +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input)?; +//! +//! println!("You typed: {}", input.trim()); +//! Ok(()) +//! } +//! ``` +//! +//! Note that you cannot use the [`?` operator] in functions that do not return +//! a [`Result`][`Result`]. Instead, you can call [`.unwrap()`] +//! or `match` on the return value to catch any possible errors: +//! +//! ```no_run +//! use std::io; +//! +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input).unwrap(); +//! ``` +//! +//! And a very common source of output is standard output: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! +//! fn main() -> io::Result<()> { +//! io::stdout().write(&[42])?; +//! Ok(()) +//! } +//! ``` +//! +//! Of course, using [`io::stdout`] directly is less common than something like +//! [`println!`]. +//! +//! ## Iterator types +//! +//! A large number of the structures provided by `std::io` are for various +//! ways of iterating over I/O. For example, [`Lines`] is used to split over +//! lines: +//! +//! ```no_run +//! use std::io; +//! use std::io::prelude::*; +//! use std::io::BufReader; +//! use std::fs::File; +//! +//! fn main() -> io::Result<()> { +//! let f = File::open("foo.txt")?; +//! let reader = BufReader::new(f); +//! +//! for line in reader.lines() { +//! println!("{}", line?); +//! } +//! Ok(()) +//! } +//! ``` +//! +//! ## Functions +//! +//! There are a number of [functions][functions-list] that offer access to various +//! features. For example, we can use three of these functions to copy everything +//! from standard input to standard output: +//! +//! ```no_run +//! use std::io; +//! +//! fn main() -> io::Result<()> { +//! io::copy(&mut io::stdin(), &mut io::stdout())?; +//! Ok(()) +//! } +//! ``` +//! +//! [functions-list]: #functions-1 +//! +//! ## io::Result +//! +//! Last, but certainly not least, is [`io::Result`]. This type is used +//! as the return type of many `std::io` functions that can cause an error, and +//! can be returned from your own functions as well. Many of the examples in this +//! module use the [`?` operator]: +//! +//! ``` +//! use std::io; +//! +//! fn read_input() -> io::Result<()> { +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input)?; +//! +//! println!("You typed: {}", input.trim()); +//! +//! Ok(()) +//! } +//! ``` +//! +//! The return type of `read_input()`, [`io::Result<()>`][`io::Result`], is a very +//! common type for functions which don't have a 'real' return value, but do want to +//! return errors if they happen. In this case, the only purpose of this function is +//! to read the line and print it, so we use `()`. +//! +//! ## Platform-specific behavior +//! +//! Many I/O functions throughout the standard library are documented to indicate +//! what various library or syscalls they are delegated to. This is done to help +//! applications both understand what's happening under the hood as well as investigate +//! any possibly unclear semantics. Note, however, that this is informative, not a binding +//! contract. The implementation of many of these functions are subject to change over +//! time and may call fewer or more syscalls/library functions. +//! +//! [`Read`]: trait.Read.html +//! [`Write`]: trait.Write.html +//! [`Seek`]: trait.Seek.html +//! [`BufRead`]: trait.BufRead.html +//! [`File`]: ../fs/struct.File.html +//! [`TcpStream`]: ../net/struct.TcpStream.html +//! [`Vec`]: ../vec/struct.Vec.html +//! [`BufReader`]: struct.BufReader.html +//! [`BufWriter`]: struct.BufWriter.html +//! [`Write::write`]: trait.Write.html#tymethod.write +//! [`io::stdout`]: fn.stdout.html +//! [`println!`]: ../macro.println.html +//! [`Lines`]: struct.Lines.html +//! [`io::Result`]: type.Result.html +//! [`?` operator]: ../../book/first-edition/syntax-index.html +//! [`Read::read`]: trait.Read.html#tymethod.read +//! [`Result`]: ../result/enum.Result.html +//! [`.unwrap()`]: ../result/enum.Result.html#method.unwrap + +pub mod buffered; +pub mod cursor; +pub mod error; +mod impls; +pub mod prelude; + +use crate::out::Out; + +pub use self::{buffered::*, cursor::*, error::*}; + +use self::prelude::*; + +use alloc::string::String; +use core::{cmp, fmt, ptr, str}; + +const DEFAULT_BUF_SIZE: usize = 8 * 1024; + +#[inline] +pub fn last_os_error() -> Error { + Error::last_os_error() +} + +struct Guard<'a> { + buf: &'a mut Vec, + len: usize, +} + +impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + unsafe { + self.buf.set_len(self.len); + } + } +} + +// A few methods below (read_to_string, read_line) will append data into a +// `String` buffer, but we need to be pretty careful when doing this. The +// implementation will just call `.as_mut_vec()` and then delegate to a +// byte-oriented reading method, but we must ensure that when returning we never +// leave `buf` in a state such that it contains invalid UTF-8 in its bounds. +// +// To this end, we use an RAII guard (to protect against panics) which updates +// the length of the string when it is dropped. This guard initially truncates +// the string to the prior length and only after we've validated that the +// new contents are valid UTF-8 do we allow it to set a longer length. +// +// The unsafety in this function is twofold: +// +// 1. We're looking at the raw bytes of `buf`, so we take on the burden of UTF-8 +// checks. +// 2. We're passing a raw buffer to the function `f`, and it is expected that +// the function only *appends* bytes to the buffer. We'll get undefined +// behavior if existing bytes are overwritten to have non-UTF-8 data. +fn append_to_string(buf: &mut String, f: F) -> Result +where + F: FnOnce(&mut Vec) -> Result, +{ + unsafe { + let mut g = Guard { + len: buf.len(), + buf: buf.as_mut_vec(), + }; + let ret = f(g.buf); + if str::from_utf8(&g.buf[g.len..]).is_err() { + ret.and_then(|_| { + Err(Error::new( + ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + )) + }) + } else { + g.len = g.buf.len(); + ret + } + } +} + +// This uses an adaptive system to extend the vector when it fills. We want to +// avoid paying to allocate and zero a huge chunk of memory if the reader only +// has 4 bytes while still making large reads if the reader does have a ton +// of data to return. Simply tacking on an extra DEFAULT_BUF_SIZE space every +// time is 4,500 times (!) slower than a default reservation size of 32 if the +// reader has a very small amount of data to return. +// +// Because we're extending the buffer with uninitialized data for trusted +// readers, we need to make sure to truncate that if any of this panics. +fn read_to_end(r: &mut R, buf: &mut Vec) -> Result { + read_to_end_with_reservation(r, buf, 32) +} + +fn read_to_end_with_reservation( + r: &mut R, + buf: &mut Vec, + reservation_size: usize, +) -> Result { + let start_len = buf.len(); + let mut g = Guard { + len: buf.len(), + buf, + }; + let ret; + loop { + if g.len == g.buf.len() { + unsafe { + g.buf.reserve(reservation_size); + let capacity = g.buf.capacity(); + g.buf.set_len(capacity); + r.initializer().initialize(&mut g.buf[g.len..]); + } + } + + match r.read(&mut g.buf[g.len..]) { + Ok(0) => { + ret = Ok(g.len - start_len); + break; + } + Ok(n) => g.len += n, + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => { + ret = Err(e); + break; + } + } + } + + ret +} + +fn read_until(r: &mut R, delim: u8, buf: &mut Vec) -> Result { + let mut read = 0; + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + match memchr::memchr(delim, available) { + Some(i) => { + buf.extend_from_slice(&available[..i + 1]); + (true, i + 1) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + } + }; + r.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } +} + +/// The `Read` trait allows for reading bytes from a source. +/// +/// Implementors of the `Read` trait are called 'readers'. +/// +/// Readers are defined by one required method, [`read()`]. Each call to [`read()`] +/// will attempt to pull bytes from this source into a provided buffer. A +/// number of other methods are implemented in terms of [`read()`], giving +/// implementors a number of ways to read bytes while only needing to implement +/// a single method. +/// +/// Readers are intended to be composable with one another. Many implementors +/// throughout [`std::io`] take and provide types which implement the `Read` +/// trait. +/// +/// Please note that each call to [`read()`] may involve a system call, and +/// therefore, using something that implements [`BufRead`], such as +/// [`BufReader`], will be more efficient. +/// +/// # Examples +/// +/// [`File`]s implement `Read`: +/// +/// ```no_run +/// use std::io; +/// use std::io::prelude::*; +/// use std::fs::File; +/// +/// fn main() -> io::Result<()> { +/// let mut f = File::open("foo.txt")?; +/// let mut buffer = [0; 10]; +/// +/// // read up to 10 bytes +/// f.read(&mut buffer)?; +/// +/// let mut buffer = vec![0; 10]; +/// // read the whole file +/// f.read_to_end(&mut buffer)?; +/// +/// // read into a String, so that you don't need to do the conversion. +/// let mut buffer = String::new(); +/// f.read_to_string(&mut buffer)?; +/// +/// // and more! See the other methods for more details. +/// Ok(()) +/// } +/// ``` +/// +/// Read from [`&str`] because [`&[u8]`][slice] implements `Read`: +/// +/// ```no_run +/// # use std::io; +/// use std::io::prelude::*; +/// +/// fn main() -> io::Result<()> { +/// let mut b = "This string will be read".as_bytes(); +/// let mut buffer = [0; 10]; +/// +/// // read up to 10 bytes +/// b.read(&mut buffer)?; +/// +/// // etc... it works exactly as a File does! +/// Ok(()) +/// } +/// ``` +/// +/// [`read()`]: trait.Read.html#tymethod.read +/// [`std::io`]: ../../std/io/index.html +/// [`File`]: ../fs/struct.File.html +/// [`BufRead`]: trait.BufRead.html +/// [`BufReader`]: struct.BufReader.html +/// [`&str`]: ../../std/primitive.str.html +/// [slice]: ../../std/primitive.slice.html +pub trait Read { + /// Pull some bytes from this source into the specified buffer, returning + /// how many bytes were read. + /// + /// This function does not provide any guarantees about whether it blocks + /// waiting for data, but if an object needs to block for a read but cannot + /// it will typically signal this via an [`Err`] return value. + /// + /// If the return value of this method is [`Ok(n)`], then it must be + /// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value indicates + /// that the buffer `buf` has been filled in with `n` bytes of data from this + /// source. If `n` is `0`, then it can indicate one of two scenarios: + /// + /// 1. This reader has reached its "end of file" and will likely no longer + /// be able to produce bytes. Note that this does not mean that the + /// reader will *always* no longer be able to produce bytes. + /// 2. The buffer specified was 0 bytes in length. + /// + /// No guarantees are provided about the contents of `buf` when this + /// function is called, implementations cannot rely on any property of the + /// contents of `buf` being true. It is recommended that implementations + /// only write data to `buf` instead of reading its contents. + /// + /// # Errors + /// + /// If this function encounters any form of I/O or other error, an error + /// variant will be returned. If an error is returned then it must be + /// guaranteed that no bytes were read. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read + /// operation should be retried if there is nothing else to do. + /// + /// # Examples + /// + /// [`File`]s implement `Read`: + /// + /// [`Err`]: ../../std/result/enum.Result.html#variant.Err + /// [`Ok(n)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`File`]: ../fs/struct.File.html + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = [0; 10]; + /// + /// // read up to 10 bytes + /// f.read(&mut buffer[..])?; + /// Ok(()) + /// } + /// ``` + fn read(&mut self, buf: &mut [u8]) -> Result; + + fn read_out(&mut self, mut out: Out<[u8]>) -> Result { + // XXX: Technically incorrect but hopefully not UB + let slice: &mut [u8] = unsafe { &mut *out.as_mut_ptr() }; + self.read(slice) + } + + /// Determines if this `Read`er can work with buffers of uninitialized + /// memory. + /// + /// The default implementation returns an initializer which will zero + /// buffers. + /// + /// If a `Read`er guarantees that it can work properly with uninitialized + /// memory, it should call [`Initializer::nop()`]. See the documentation for + /// [`Initializer`] for details. + /// + /// The behavior of this method must be independent of the state of the + /// `Read`er - the method only takes `&self` so that it can be used through + /// trait objects. + /// + /// # Safety + /// + /// This method is unsafe because a `Read`er could otherwise return a + /// non-zeroing `Initializer` from another `Read` type without an `unsafe` + /// block. + /// + /// [`Initializer::nop()`]: ../../std/io/struct.Initializer.html#method.nop + /// [`Initializer`]: ../../std/io/struct.Initializer.html + #[inline] + unsafe fn initializer(&self) -> Initializer { + Initializer::zeroing() + } + + /// Read all bytes until EOF in this source, placing them into `buf`. + /// + /// All bytes read from this source will be appended to the specified buffer + /// `buf`. This function will continuously call [`read()`] to append more data to + /// `buf` until [`read()`] returns either [`Ok(0)`] or an error of + /// non-[`ErrorKind::Interrupted`] kind. + /// + /// If successful, this function will return the total number of bytes read. + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`ErrorKind::Interrupted`] then the error is ignored and the operation + /// will continue. + /// + /// If any other read error is encountered then this function immediately + /// returns. Any bytes which have already been read will be appended to + /// `buf`. + /// + /// # Examples + /// + /// [`File`]s implement `Read`: + /// + /// [`read()`]: trait.Read.html#tymethod.read + /// [`Ok(0)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`File`]: ../fs/struct.File.html + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = Vec::new(); + /// + /// // read the whole file + /// f.read_to_end(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + /// + /// (See also the [`std::fs::read`] convenience function for reading from a + /// file.) + /// + /// [`std::fs::read`]: ../fs/fn.read.html + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + read_to_end(self, buf) + } + + /// Read all bytes until EOF in this source, appending them to `buf`. + /// + /// If successful, this function returns the number of bytes which were read + /// and appended to `buf`. + /// + /// # Errors + /// + /// If the data in this stream is *not* valid UTF-8 then an error is + /// returned and `buf` is unchanged. + /// + /// See [`read_to_end`][readtoend] for other error semantics. + /// + /// [readtoend]: #method.read_to_end + /// + /// # Examples + /// + /// [`File`][file]s implement `Read`: + /// + /// [file]: ../fs/struct.File.html + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = String::new(); + /// + /// f.read_to_string(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + /// + /// (See also the [`std::fs::read_to_string`] convenience function for + /// reading from a file.) + /// + /// [`std::fs::read_to_string`]: ../fs/fn.read_to_string.html + fn read_to_string(&mut self, buf: &mut String) -> Result { + // Note that we do *not* call `.read_to_end()` here. We are passing + // `&mut Vec` (the raw contents of `buf`) into the `read_to_end` + // method to fill it up. An arbitrary implementation could overwrite the + // entire contents of the vector, not just append to it (which is what + // we are expecting). + // + // To prevent extraneously checking the UTF-8-ness of the entire buffer + // we pass it to our hardcoded `read_to_end` implementation which we + // know is guaranteed to only read data into the end of the buffer. + append_to_string(buf, |b| read_to_end(self, b)) + } + + /// Read the exact number of bytes required to fill `buf`. + /// + /// This function reads as many bytes as necessary to completely fill the + /// specified buffer `buf`. + /// + /// No guarantees are provided about the contents of `buf` when this + /// function is called, implementations cannot rely on any property of the + /// contents of `buf` being true. It is recommended that implementations + /// only write data to `buf` instead of reading its contents. + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`ErrorKind::Interrupted`] then the error is ignored and the operation + /// will continue. + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + /// + /// # Examples + /// + /// [`File`]s implement `Read`: + /// + /// [`File`]: ../fs/struct.File.html + /// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`ErrorKind::UnexpectedEof`]: ../../std/io/enum.ErrorKind.html#variant.UnexpectedEof + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = [0; 10]; + /// + /// // read exactly 10 bytes + /// f.read_exact(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + } + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + Err(Error::new( + ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )) + } else { + Ok(()) + } + } + + /// Creates a "by reference" adaptor for this instance of `Read`. + /// + /// The returned adaptor also implements `Read` and will simply borrow this + /// current reader. + /// + /// # Examples + /// + /// [`File`][file]s implement `Read`: + /// + /// [file]: ../fs/struct.File.html + /// + /// ```no_run + /// use std::io; + /// use std::io::Read; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = Vec::new(); + /// let mut other_buffer = Vec::new(); + /// + /// { + /// let reference = f.by_ref(); + /// + /// // read at most 5 bytes + /// reference.take(5).read_to_end(&mut buffer)?; + /// + /// } // drop our &mut reference so we can use f again + /// + /// // original file still usable, read the rest + /// f.read_to_end(&mut other_buffer)?; + /// Ok(()) + /// } + /// ``` + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } + + /// Transforms this `Read` instance to an [`Iterator`] over its bytes. + /// + /// The returned type implements [`Iterator`] where the `Item` is + /// [`Result`]`<`[`u8`]`, `[`io::Error`]`>`. + /// The yielded item is [`Ok`] if a byte was successfully read and [`Err`] + /// otherwise. EOF is mapped to returning [`None`] from this iterator. + /// + /// # Examples + /// + /// [`File`][file]s implement `Read`: + /// + /// [file]: ../fs/struct.File.html + /// [`Iterator`]: ../../std/iter/trait.Iterator.html + /// [`Result`]: ../../std/result/enum.Result.html + /// [`io::Error`]: ../../std/io/struct.Error.html + /// [`u8`]: ../../std/primitive.u8.html + /// [`Ok`]: ../../std/result/enum.Result.html#variant.Ok + /// [`Err`]: ../../std/result/enum.Result.html#variant.Err + /// [`None`]: ../../std/option/enum.Option.html#variant.None + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// + /// for byte in f.bytes() { + /// println!("{}", byte.unwrap()); + /// } + /// Ok(()) + /// } + /// ``` + fn bytes(self) -> Bytes + where + Self: Sized, + { + Bytes { inner: self } + } + + /// Creates an adaptor which will chain this stream with another. + /// + /// The returned `Read` instance will first read all bytes from this object + /// until EOF is encountered. Afterwards the output is equivalent to the + /// output of `next`. + /// + /// # Examples + /// + /// [`File`][file]s implement `Read`: + /// + /// [file]: ../fs/struct.File.html + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f1 = File::open("foo.txt")?; + /// let mut f2 = File::open("bar.txt")?; + /// + /// let mut handle = f1.chain(f2); + /// let mut buffer = String::new(); + /// + /// // read the value into a String. We could use any Read method here, + /// // this is just one example. + /// handle.read_to_string(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + fn chain(self, next: R) -> Chain + where + Self: Sized, + { + Chain { + first: self, + second: next, + done_first: false, + } + } + + /// Creates an adaptor which will read at most `limit` bytes from it. + /// + /// This function returns a new instance of `Read` which will read at most + /// `limit` bytes, after which it will always return EOF ([`Ok(0)`]). Any + /// read errors will not count towards the number of bytes read and future + /// calls to [`read()`] may succeed. + /// + /// # Examples + /// + /// [`File`]s implement `Read`: + /// + /// [`File`]: ../fs/struct.File.html + /// [`Ok(0)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`read()`]: trait.Read.html#tymethod.read + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut f = File::open("foo.txt")?; + /// let mut buffer = [0; 5]; + /// + /// // read at most five bytes + /// let mut handle = f.take(5); + /// + /// handle.read(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + fn take(self, limit: u64) -> Take + where + Self: Sized, + { + Take { inner: self, limit } + } +} + +fn read_one_byte(reader: &mut dyn Read) -> Option> { + let mut buf = [0]; + loop { + return match reader.read(&mut buf) { + Ok(0) => None, + Ok(..) => Some(Ok(buf[0])), + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => Some(Err(e)), + }; + } +} + +/// A `BufRead` is a type of `Read`er which has an internal buffer, allowing it +/// to perform extra ways of reading. +/// +/// For example, reading line-by-line is inefficient without using a buffer, so +/// if you want to read by line, you'll need `BufRead`, which includes a +/// [`read_line`] method as well as a [`lines`] iterator. +/// +/// # Examples +/// +/// A locked standard input implements `BufRead`: +/// +/// ```no_run +/// use std::io; +/// use std::io::prelude::*; +/// +/// let stdin = io::stdin(); +/// for line in stdin.lock().lines() { +/// println!("{}", line.unwrap()); +/// } +/// ``` +/// +/// If you have something that implements [`Read`], you can use the [`BufReader` +/// type][`BufReader`] to turn it into a `BufRead`. +/// +/// For example, [`File`] implements [`Read`], but not `BufRead`. +/// [`BufReader`] to the rescue! +/// +/// [`BufReader`]: struct.BufReader.html +/// [`File`]: ../fs/struct.File.html +/// [`read_line`]: #method.read_line +/// [`lines`]: #method.lines +/// [`Read`]: trait.Read.html +/// +/// ```no_run +/// use std::io::{self, BufReader}; +/// use std::io::prelude::*; +/// use std::fs::File; +/// +/// fn main() -> io::Result<()> { +/// let f = File::open("foo.txt")?; +/// let f = BufReader::new(f); +/// +/// for line in f.lines() { +/// println!("{}", line.unwrap()); +/// } +/// +/// Ok(()) +/// } +/// ``` +/// +pub trait BufRead: Read { + /// Returns the contents of the internal buffer, filling it with more data + /// from the inner reader if it is empty. + /// + /// This function is a lower-level call. It needs to be paired with the + /// [`consume`] method to function properly. When calling this + /// method, none of the contents will be "read" in the sense that later + /// calling `read` may return the same contents. As such, [`consume`] must + /// be called with the number of bytes that are consumed from this buffer to + /// ensure that the bytes are never returned twice. + /// + /// [`consume`]: #tymethod.consume + /// + /// An empty buffer returned indicates that the stream has reached EOF. + /// + /// # Errors + /// + /// This function will return an I/O error if the underlying reader was + /// read, but returned an error. + /// + /// # Examples + /// + /// A locked standard input implements `BufRead`: + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// + /// let stdin = io::stdin(); + /// let mut stdin = stdin.lock(); + /// + /// // we can't have two `&mut` references to `stdin`, so use a block + /// // to end the borrow early. + /// let length = { + /// let buffer = stdin.fill_buf().unwrap(); + /// + /// // work with buffer + /// println!("{:?}", buffer); + /// + /// buffer.len() + /// }; + /// + /// // ensure the bytes we worked with aren't returned again later + /// stdin.consume(length); + /// ``` + fn fill_buf(&mut self) -> Result<&[u8]>; + + /// Tells this buffer that `amt` bytes have been consumed from the buffer, + /// so they should no longer be returned in calls to `read`. + /// + /// This function is a lower-level call. It needs to be paired with the + /// [`fill_buf`] method to function properly. This function does + /// not perform any I/O, it simply informs this object that some amount of + /// its buffer, returned from [`fill_buf`], has been consumed and should + /// no longer be returned. As such, this function may do odd things if + /// [`fill_buf`] isn't called before calling it. + /// + /// The `amt` must be `<=` the number of bytes in the buffer returned by + /// [`fill_buf`]. + /// + /// # Examples + /// + /// Since `consume()` is meant to be used with [`fill_buf`], + /// that method's example includes an example of `consume()`. + /// + /// [`fill_buf`]: #tymethod.fill_buf + fn consume(&mut self, amt: usize); + + /// Read all bytes into `buf` until the delimiter `byte` or EOF is reached. + /// + /// This function will read bytes from the underlying stream until the + /// delimiter or EOF is found. Once found, all bytes up to, and including, + /// the delimiter (if found) will be appended to `buf`. + /// + /// If successful, this function will return the total number of bytes read. + /// + /// # Errors + /// + /// This function will ignore all instances of [`ErrorKind::Interrupted`] and + /// will otherwise return any errors returned by [`fill_buf`]. + /// + /// If an I/O error is encountered then all bytes read so far will be + /// present in `buf` and its length will have been adjusted appropriately. + /// + /// [`fill_buf`]: #tymethod.fill_buf + /// [`ErrorKind::Interrupted`]: enum.ErrorKind.html#variant.Interrupted + /// + /// # Examples + /// + /// [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + /// this example, we use [`Cursor`] to read all the bytes in a byte slice + /// in hyphen delimited segments: + /// + /// [`Cursor`]: struct.Cursor.html + /// + /// ``` + /// use std::io::{self, BufRead}; + /// + /// let mut cursor = io::Cursor::new(b"lorem-ipsum"); + /// let mut buf = vec![]; + /// + /// // cursor is at 'l' + /// let num_bytes = cursor.read_until(b'-', &mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 6); + /// assert_eq!(buf, b"lorem-"); + /// buf.clear(); + /// + /// // cursor is at 'i' + /// let num_bytes = cursor.read_until(b'-', &mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 5); + /// assert_eq!(buf, b"ipsum"); + /// buf.clear(); + /// + /// // cursor is at EOF + /// let num_bytes = cursor.read_until(b'-', &mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 0); + /// assert_eq!(buf, b""); + /// ``` + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> Result { + read_until(self, byte, buf) + } + + /// Read all bytes until a newline (the 0xA byte) is reached, and append + /// them to the provided buffer. + /// + /// This function will read bytes from the underlying stream until the + /// newline delimiter (the 0xA byte) or EOF is found. Once found, all bytes + /// up to, and including, the delimiter (if found) will be appended to + /// `buf`. + /// + /// If successful, this function will return the total number of bytes read. + /// + /// An empty buffer returned indicates that the stream has reached EOF. + /// + /// # Errors + /// + /// This function has the same error semantics as [`read_until`] and will + /// also return an error if the read bytes are not valid UTF-8. If an I/O + /// error is encountered then `buf` may contain some bytes already read in + /// the event that all data read so far was valid UTF-8. + /// + /// [`read_until`]: #method.read_until + /// + /// # Examples + /// + /// [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + /// this example, we use [`Cursor`] to read all the lines in a byte slice: + /// + /// [`Cursor`]: struct.Cursor.html + /// + /// ``` + /// use std::io::{self, BufRead}; + /// + /// let mut cursor = io::Cursor::new(b"foo\nbar"); + /// let mut buf = String::new(); + /// + /// // cursor is at 'f' + /// let num_bytes = cursor.read_line(&mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 4); + /// assert_eq!(buf, "foo\n"); + /// buf.clear(); + /// + /// // cursor is at 'b' + /// let num_bytes = cursor.read_line(&mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 3); + /// assert_eq!(buf, "bar"); + /// buf.clear(); + /// + /// // cursor is at EOF + /// let num_bytes = cursor.read_line(&mut buf) + /// .expect("reading from cursor won't fail"); + /// assert_eq!(num_bytes, 0); + /// assert_eq!(buf, ""); + /// ``` + fn read_line(&mut self, buf: &mut String) -> Result { + // Note that we are not calling the `.read_until` method here, but + // rather our hardcoded implementation. For more details as to why, see + // the comments in `read_to_end`. + append_to_string(buf, |b| read_until(self, b'\n', b)) + } + + /// Returns an iterator over the contents of this reader split on the byte + /// `byte`. + /// + /// The iterator returned from this function will return instances of + /// [`io::Result`]`<`[`Vec`]`>`. Each vector returned will *not* have + /// the delimiter byte at the end. + /// + /// This function will yield errors whenever [`read_until`] would have + /// also yielded an error. + /// + /// [`io::Result`]: type.Result.html + /// [`Vec`]: ../vec/struct.Vec.html + /// [`read_until`]: #method.read_until + /// + /// # Examples + /// + /// [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + /// this example, we use [`Cursor`] to iterate over all hyphen delimited + /// segments in a byte slice + /// + /// [`Cursor`]: struct.Cursor.html + /// + /// ``` + /// use std::io::{self, BufRead}; + /// + /// let cursor = io::Cursor::new(b"lorem-ipsum-dolor"); + /// + /// let mut split_iter = cursor.split(b'-').map(|l| l.unwrap()); + /// assert_eq!(split_iter.next(), Some(b"lorem".to_vec())); + /// assert_eq!(split_iter.next(), Some(b"ipsum".to_vec())); + /// assert_eq!(split_iter.next(), Some(b"dolor".to_vec())); + /// assert_eq!(split_iter.next(), None); + /// ``` + fn split(self, byte: u8) -> Split + where + Self: Sized, + { + Split { + buf: self, + delim: byte, + } + } + + /// Returns an iterator over the lines of this reader. + /// + /// The iterator returned from this function will yield instances of + /// [`io::Result`]`<`[`String`]`>`. Each string returned will *not* have a newline + /// byte (the 0xA byte) or CRLF (0xD, 0xA bytes) at the end. + /// + /// [`io::Result`]: type.Result.html + /// [`String`]: ../string/struct.String.html + /// + /// # Examples + /// + /// [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + /// this example, we use [`Cursor`] to iterate over all the lines in a byte + /// slice. + /// + /// [`Cursor`]: struct.Cursor.html + /// + /// ``` + /// use std::io::{self, BufRead}; + /// + /// let cursor = io::Cursor::new(b"lorem\nipsum\r\ndolor"); + /// + /// let mut lines_iter = cursor.lines().map(|l| l.unwrap()); + /// assert_eq!(lines_iter.next(), Some(String::from("lorem"))); + /// assert_eq!(lines_iter.next(), Some(String::from("ipsum"))); + /// assert_eq!(lines_iter.next(), Some(String::from("dolor"))); + /// assert_eq!(lines_iter.next(), None); + /// ``` + /// + /// # Errors + /// + /// Each line of the iterator has the same error semantics as [`BufRead::read_line`]. + /// + /// [`BufRead::read_line`]: trait.BufRead.html#method.read_line + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { buf: self } + } +} + +/// A type used to conditionally initialize buffers passed to `Read` methods. +#[derive(Debug)] +pub struct Initializer(bool); + +impl Initializer { + /// Returns a new `Initializer` which will zero out buffers. + #[inline] + pub fn zeroing() -> Initializer { + Initializer(true) + } + + /// Returns a new `Initializer` which will not zero out buffers. + /// + /// # Safety + /// + /// This may only be called by `Read`ers which guarantee that they will not + /// read from buffers passed to `Read` methods, and that the return value of + /// the method accurately reflects the number of bytes that have been + /// written to the head of the buffer. + #[inline] + pub unsafe fn nop() -> Initializer { + Initializer(false) + } + + /// Indicates if a buffer should be initialized. + #[inline] + pub fn should_initialize(&self) -> bool { + self.0 + } + + /// Initializes a buffer if necessary. + #[inline] + pub fn initialize(&self, buf: &mut [u8]) { + if self.should_initialize() { + unsafe { ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) } + } + } +} + +/// Adaptor to chain together two readers. +/// +/// This struct is generally created by calling [`chain`] on a reader. +/// Please see the documentation of [`chain`] for more details. +/// +/// [`chain`]: trait.Read.html#method.chain +pub struct Chain { + first: T, + second: U, + done_first: bool, +} + +impl Chain { + /// Consumes the `Chain`, returning the wrapped readers. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut foo_file = File::open("foo.txt")?; + /// let mut bar_file = File::open("bar.txt")?; + /// + /// let chain = foo_file.chain(bar_file); + /// let (foo_file, bar_file) = chain.into_inner(); + /// Ok(()) + /// } + /// ``` + pub fn into_inner(self) -> (T, U) { + (self.first, self.second) + } + + /// Gets references to the underlying readers in this `Chain`. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut foo_file = File::open("foo.txt")?; + /// let mut bar_file = File::open("bar.txt")?; + /// + /// let chain = foo_file.chain(bar_file); + /// let (foo_file, bar_file) = chain.get_ref(); + /// Ok(()) + /// } + /// ``` + pub fn get_ref(&self) -> (&T, &U) { + (&self.first, &self.second) + } + + /// Gets mutable references to the underlying readers in this `Chain`. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying readers as doing so may corrupt the internal state of this + /// `Chain`. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut foo_file = File::open("foo.txt")?; + /// let mut bar_file = File::open("bar.txt")?; + /// + /// let mut chain = foo_file.chain(bar_file); + /// let (foo_file, bar_file) = chain.get_mut(); + /// Ok(()) + /// } + /// ``` + pub fn get_mut(&mut self) -> (&mut T, &mut U) { + (&mut self.first, &mut self.second) + } +} + +impl Read for Chain { + fn read(&mut self, buf: &mut [u8]) -> Result { + if !self.done_first { + match self.first.read(buf)? { + 0 if !buf.is_empty() => { + self.done_first = true; + } + n => return Ok(n), + } + } + self.second.read(buf) + } + + unsafe fn initializer(&self) -> Initializer { + let initializer = unsafe { self.first.initializer() }; + if initializer.should_initialize() { + initializer + } else { + unsafe { self.second.initializer() } + } + } +} + +impl BufRead for Chain { + fn fill_buf(&mut self) -> Result<&[u8]> { + if !self.done_first { + match self.first.fill_buf()? { + [] => { + self.done_first = true; + } + buf => return Ok(buf), + } + } + self.second.fill_buf() + } + + fn consume(&mut self, amt: usize) { + if !self.done_first { + self.first.consume(amt) + } else { + self.second.consume(amt) + } + } +} + +/// Reader adaptor which limits the bytes read from an underlying reader. +/// +/// This struct is generally created by calling [`take`] on a reader. +/// Please see the documentation of [`take`] for more details. +/// +/// [`take`]: trait.Read.html#method.take +#[derive(Debug)] +pub struct Take { + inner: T, + limit: u64, +} + +impl Take { + /// Returns the number of bytes that can be read before this instance will + /// return EOF. + /// + /// # Note + /// + /// This instance may reach `EOF` after reading fewer bytes than indicated by + /// this method if the underlying [`Read`] instance reaches EOF. + /// + /// [`Read`]: ../../std/io/trait.Read.html + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let f = File::open("foo.txt")?; + /// + /// // read at most five bytes + /// let handle = f.take(5); + /// + /// println!("limit: {}", handle.limit()); + /// Ok(()) + /// } + /// ``` + pub fn limit(&self) -> u64 { + self.limit + } + + /// Sets the number of bytes that can be read before this instance will + /// return EOF. This is the same as constructing a new `Take` instance, so + /// the amount of bytes read and the previous limit value don't matter when + /// calling this method. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let f = File::open("foo.txt")?; + /// + /// // read at most five bytes + /// let mut handle = f.take(5); + /// handle.set_limit(10); + /// + /// assert_eq!(handle.limit(), 10); + /// Ok(()) + /// } + /// ``` + pub fn set_limit(&mut self, limit: u64) { + self.limit = limit; + } + + /// Consumes the `Take`, returning the wrapped reader. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt")?; + /// + /// let mut buffer = [0; 5]; + /// let mut handle = file.take(5); + /// handle.read(&mut buffer)?; + /// + /// let file = handle.into_inner(); + /// Ok(()) + /// } + /// ``` + pub fn into_inner(self) -> T { + self.inner + } + + /// Gets a reference to the underlying reader. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt")?; + /// + /// let mut buffer = [0; 5]; + /// let mut handle = file.take(5); + /// handle.read(&mut buffer)?; + /// + /// let file = handle.get_ref(); + /// Ok(()) + /// } + /// ``` + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets a mutable reference to the underlying reader. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying reader as doing so may corrupt the internal limit of this + /// `Take`. + /// + /// # Examples + /// + /// ```no_run + /// use std::io; + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt")?; + /// + /// let mut buffer = [0; 5]; + /// let mut handle = file.take(5); + /// handle.read(&mut buffer)?; + /// + /// let file = handle.get_mut(); + /// Ok(()) + /// } + /// ``` + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Read for Take { + fn read(&mut self, buf: &mut [u8]) -> Result { + // Don't call into inner reader at all at EOF because it may still block + if self.limit == 0 { + return Ok(0); + } + + let max = cmp::min(buf.len() as u64, self.limit) as usize; + let n = self.inner.read(&mut buf[..max])?; + self.limit -= n as u64; + Ok(n) + } + + unsafe fn initializer(&self) -> Initializer { + unsafe { self.inner.initializer() } + } + + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + let reservation_size = cmp::min(self.limit, 32) as usize; + + read_to_end_with_reservation(self, buf, reservation_size) + } +} + +impl BufRead for Take { + fn fill_buf(&mut self) -> Result<&[u8]> { + // Don't call into inner reader at all at EOF because it may still block + if self.limit == 0 { + return Ok(&[]); + } + + let buf = self.inner.fill_buf()?; + let cap = cmp::min(buf.len() as u64, self.limit) as usize; + Ok(&buf[..cap]) + } + + fn consume(&mut self, amt: usize) { + // Don't let callers reset the limit by passing an overlarge value + let amt = cmp::min(amt as u64, self.limit) as usize; + self.limit -= amt as u64; + self.inner.consume(amt); + } +} + +/// A trait for objects which are byte-oriented sinks. +/// +/// Implementors of the `Write` trait are sometimes called 'writers'. +/// +/// Writers are defined by two required methods, [`write`] and [`flush`]: +/// +/// * The [`write`] method will attempt to write some data into the object, +/// returning how many bytes were successfully written. +/// +/// * The [`flush`] method is useful for adaptors and explicit buffers +/// themselves for ensuring that all buffered data has been pushed out to the +/// 'true sink'. +/// +/// Writers are intended to be composable with one another. Many implementors +/// throughout [`std::io`] take and provide types which implement the `Write` +/// trait. +/// +/// [`write`]: #tymethod.write +/// [`flush`]: #tymethod.flush +/// [`std::io`]: index.html +/// +/// # Examples +/// +/// ```no_run +/// use std::io::prelude::*; +/// use std::fs::File; +/// +/// fn main() -> std::io::Result<()> { +/// let mut buffer = File::create("foo.txt")?; +/// +/// buffer.write(b"some bytes")?; +/// Ok(()) +/// } +/// ``` +pub trait Write { + /// Write a buffer into this object, returning how many bytes were written. + /// + /// This function will attempt to write the entire contents of `buf`, but + /// the entire write may not succeed, or the write may also generate an + /// error. A call to `write` represents *at most one* attempt to write to + /// any wrapped object. + /// + /// Calls to `write` are not guaranteed to block waiting for data to be + /// written, and a write which would otherwise block can be indicated through + /// an [`Err`] variant. + /// + /// If the return value is [`Ok(n)`] then it must be guaranteed that + /// `0 <= n <= buf.len()`. A return value of `0` typically means that the + /// underlying object is no longer able to accept bytes and will likely not + /// be able to in the future as well, or that the buffer provided is empty. + /// + /// # Errors + /// + /// Each call to `write` may generate an I/O error indicating that the + /// operation could not be completed. If an error is returned then no bytes + /// in the buffer were written to this writer. + /// + /// It is **not** considered an error if the entire buffer could not be + /// written to this writer. + /// + /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the + /// write operation should be retried if there is nothing else to do. + /// + /// [`Err`]: ../../std/result/enum.Result.html#variant.Err + /// [`Ok(n)`]: ../../std/result/enum.Result.html#variant.Ok + /// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Writes some prefix of the byte string, not necessarily all of it. + /// buffer.write(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + /// + /// # Errors + /// + /// It is considered an error if not all bytes could be written due to + /// I/O errors or EOF being reached. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::io::BufWriter; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = BufWriter::new(File::create("foo.txt")?); + /// + /// buffer.write(b"some bytes")?; + /// buffer.flush()?; + /// Ok(()) + /// } + /// ``` + fn flush(&mut self) -> Result<()>; + + /// Attempts to write an entire buffer into this write. + /// + /// This method will continuously call [`write`] until there is no more data + /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`ErrorKind::Interrupted`] kind that [`write`] returns. + /// + /// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`write`]: #tymethod.write + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// buffer.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(Error::new( + ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + Ok(n) => buf = &buf[n..], + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Writes a formatted string into this writer, returning any error + /// encountered. + /// + /// This method is primarily used to interface with the + /// [`format_args!`][formatargs] macro, but it is rare that this should + /// explicitly be called. The [`write!`][write] macro should be favored to + /// invoke this method instead. + /// + /// [formatargs]: ../macro.format_args.html + /// [write]: ../macro.write.html + /// + /// This function internally uses the [`write_all`][writeall] method on + /// this trait and hence will continuously write data so long as no errors + /// are received. This also means that partial writes are not indicated in + /// this signature. + /// + /// [writeall]: #method.write_all + /// + /// # Errors + /// + /// This function will return any I/O error reported while formatting. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::prelude::*; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // this call + /// write!(buffer, "{:.*}", 2, 1.234567)?; + /// // turns into this: + /// buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?; + /// Ok(()) + /// } + /// ``` + fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()> { + // Create a shim which translates a Write to a fmt::Write and saves + // off I/O errors. instead of discarding them + struct Adaptor<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl<'a, T: Write + ?Sized> fmt::Write for Adaptor<'a, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adaptor { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => { + // check if the error came from the underlying `Write` or not + if output.error.is_err() { + output.error + } else { + Err(Error::new(ErrorKind::Other, "formatter error")) + } + } + } + } + + /// Creates a "by reference" adaptor for this instance of `Write`. + /// + /// The returned adaptor also implements `Write` and will simply borrow this + /// current writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::Write; + /// use std::fs::File; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// let reference = buffer.by_ref(); + /// + /// // we can use reference just like our original buffer + /// reference.write_all(b"some bytes")?; + /// Ok(()) + /// } + /// ``` + fn by_ref(&mut self) -> &mut Self + where + Self: Sized, + { + self + } +} + +/// The `Seek` trait provides a cursor which can be moved within a stream of +/// bytes. +/// +/// The stream typically has a fixed size, allowing seeking relative to either +/// end or the current offset. +/// +/// # Examples +/// +/// [`File`][file]s implement `Seek`: +/// +/// [file]: ../fs/struct.File.html +/// +/// ```no_run +/// use std::io; +/// use std::io::prelude::*; +/// use std::fs::File; +/// use std::io::SeekFrom; +/// +/// fn main() -> io::Result<()> { +/// let mut f = File::open("foo.txt")?; +/// +/// // move the cursor 42 bytes from the start of the file +/// f.seek(SeekFrom::Start(42))?; +/// Ok(()) +/// } +/// ``` +pub trait Seek { + /// Seek to an offset, in bytes, in a stream. + /// + /// A seek beyond the end of a stream is allowed, but behavior is defined + /// by the implementation. + /// + /// If the seek operation completed successfully, + /// this method returns the new position from the start of the stream. + /// That position can be used later with [`SeekFrom::Start`]. + /// + /// # Errors + /// + /// Seeking to a negative offset is considered an error. + /// + /// [`SeekFrom::Start`]: enum.SeekFrom.html#variant.Start + fn seek(&mut self, pos: SeekFrom) -> Result; +} + +/// Enumeration of possible methods to seek within an I/O object. +/// +/// It is used by the [`Seek`] trait. +/// +/// [`Seek`]: trait.Seek.html +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum SeekFrom { + /// Set the offset to the provided number of bytes. + Start(u64), + + /// Set the offset to the size of this object plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but it's an error to + /// seek before byte 0. + End(i64), + + /// Set the offset to the current position plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but it's an error to + /// seek before byte 0. + Current(i64), +} + +/// An iterator over `u8` values of a reader. +/// +/// This struct is generally created by calling [`bytes`] on a reader. +/// Please see the documentation of [`bytes`] for more details. +/// +/// [`bytes`]: trait.Read.html#method.bytes +#[derive(Debug)] +pub struct Bytes { + inner: R, +} + +impl Iterator for Bytes { + type Item = Result; + + fn next(&mut self) -> Option> { + read_one_byte(&mut self.inner) + } +} + +/// An iterator over the contents of an instance of `BufRead` split on a +/// particular byte. +/// +/// This struct is generally created by calling [`split`][split] on a +/// `BufRead`. Please see the documentation of `split()` for more details. +/// +/// [split]: trait.BufRead.html#method.split +#[derive(Debug)] +pub struct Split { + buf: B, + delim: u8, +} + +impl Iterator for Split { + type Item = Result>; + + fn next(&mut self) -> Option>> { + let mut buf = Vec::new(); + match self.buf.read_until(self.delim, &mut buf) { + Ok(0) => None, + Ok(_n) => { + if buf[buf.len() - 1] == self.delim { + buf.pop(); + } + Some(Ok(buf)) + } + Err(e) => Some(Err(e)), + } + } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// This struct is generally created by calling [`lines`][lines] on a +/// `BufRead`. Please see the documentation of `lines()` for more details. +/// +/// [lines]: trait.BufRead.html#method.lines +#[derive(Debug)] +pub struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = Result; + + fn next(&mut self) -> Option> { + let mut buf = String::new(); + match self.buf.read_line(&mut buf) { + Ok(0) => None, + Ok(_n) => { + if buf.ends_with("\n") { + buf.pop(); + if buf.ends_with("\r") { + buf.pop(); + } + } + Some(Ok(buf)) + } + Err(e) => Some(Err(e)), + } + } +} + +#[cfg(test)] +mod tests { + use alloc::string::String; + + use crate::io::{self, cursor::Cursor, prelude::*}; + + #[test] + #[cfg_attr(target_os = "emscripten", ignore)] + fn read_until() { + let mut buf = Cursor::new(&b"12"[..]); + let mut v = Vec::new(); + assert_eq!(buf.read_until(b'3', &mut v).unwrap(), 2); + assert_eq!(v, b"12"); + + let mut buf = Cursor::new(&b"1233"[..]); + let mut v = Vec::new(); + assert_eq!(buf.read_until(b'3', &mut v).unwrap(), 3); + assert_eq!(v, b"123"); + v.truncate(0); + assert_eq!(buf.read_until(b'3', &mut v).unwrap(), 1); + assert_eq!(v, b"3"); + v.truncate(0); + assert_eq!(buf.read_until(b'3', &mut v).unwrap(), 0); + assert_eq!(v, []); + } + + #[test] + fn split() { + let buf = Cursor::new(&b"12"[..]); + let mut s = buf.split(b'3'); + assert_eq!(s.next().unwrap().unwrap(), vec![b'1', b'2']); + assert!(s.next().is_none()); + + let buf = Cursor::new(&b"1233"[..]); + let mut s = buf.split(b'3'); + assert_eq!(s.next().unwrap().unwrap(), vec![b'1', b'2']); + assert_eq!(s.next().unwrap().unwrap(), vec![]); + assert!(s.next().is_none()); + } + + #[test] + fn read_line() { + let mut buf = Cursor::new(&b"12"[..]); + let mut v = String::new(); + assert_eq!(buf.read_line(&mut v).unwrap(), 2); + assert_eq!(v, "12"); + + let mut buf = Cursor::new(&b"12\n\n"[..]); + let mut v = String::new(); + assert_eq!(buf.read_line(&mut v).unwrap(), 3); + assert_eq!(v, "12\n"); + v.truncate(0); + assert_eq!(buf.read_line(&mut v).unwrap(), 1); + assert_eq!(v, "\n"); + v.truncate(0); + assert_eq!(buf.read_line(&mut v).unwrap(), 0); + assert_eq!(v, ""); + } + + // #[test] + // fn lines() { + // let buf = Cursor::new(&b"12\r"[..]); + // let mut s = buf.lines(); + // assert_eq!(s.next().unwrap().unwrap(), "12\r".to_string()); + // assert!(s.next().is_none()); + + // let buf = Cursor::new(&b"12\r\n\n"[..]); + // let mut s = buf.lines(); + // assert_eq!(s.next().unwrap().unwrap(), "12".to_string()); + // assert_eq!(s.next().unwrap().unwrap(), "".to_string()); + // assert!(s.next().is_none()); + // } + + #[test] + fn read_to_end() { + let mut c = Cursor::new(&b""[..]); + let mut v = Vec::new(); + assert_eq!(c.read_to_end(&mut v).unwrap(), 0); + assert_eq!(v, []); + + let mut c = Cursor::new(&b"1"[..]); + let mut v = Vec::new(); + assert_eq!(c.read_to_end(&mut v).unwrap(), 1); + assert_eq!(v, b"1"); + + let cap = 1024 * 1024; + let data = (0..cap).map(|i| (i / 3) as u8).collect::>(); + let mut v = Vec::new(); + let (a, b) = data.split_at(data.len() / 2); + assert_eq!(Cursor::new(a).read_to_end(&mut v).unwrap(), a.len()); + assert_eq!(Cursor::new(b).read_to_end(&mut v).unwrap(), b.len()); + assert_eq!(v, data); + } + + #[test] + fn read_to_string() { + let mut c = Cursor::new(&b""[..]); + let mut v = String::new(); + assert_eq!(c.read_to_string(&mut v).unwrap(), 0); + assert_eq!(v, ""); + + let mut c = Cursor::new(&b"1"[..]); + let mut v = String::new(); + assert_eq!(c.read_to_string(&mut v).unwrap(), 1); + assert_eq!(v, "1"); + + let mut c = Cursor::new(&b"\xff"[..]); + let mut v = String::new(); + assert!(c.read_to_string(&mut v).is_err()); + } + + #[test] + fn read_exact() { + let mut buf = [0; 4]; + + let mut c = Cursor::new(&b""[..]); + assert_eq!( + c.read_exact(&mut buf).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + + let mut c = Cursor::new(&b"123"[..]).chain(Cursor::new(&b"456789"[..])); + c.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"1234"); + c.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"5678"); + assert_eq!( + c.read_exact(&mut buf).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + } + + #[test] + fn read_exact_slice() { + let mut buf = [0; 4]; + + let mut c = &b""[..]; + assert_eq!( + c.read_exact(&mut buf).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + + let mut c = &b"123"[..]; + assert_eq!( + c.read_exact(&mut buf).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + // make sure the optimized (early returning) method is being used + assert_eq!(&buf, &[0; 4]); + + let mut c = &b"1234"[..]; + c.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"1234"); + + let mut c = &b"56789"[..]; + c.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"5678"); + assert_eq!(c, b"9"); + } + + #[test] + fn take_eof() { + struct R; + + impl Read for R { + fn read(&mut self, _: &mut [u8]) -> io::Result { + Err(io::Error::new(io::ErrorKind::Other, "")) + } + } + impl BufRead for R { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + Err(io::Error::new(io::ErrorKind::Other, "")) + } + fn consume(&mut self, _amt: usize) {} + } + + let mut buf = [0; 1]; + assert_eq!(0, R.take(0).read(&mut buf).unwrap()); + assert_eq!(b"", R.take(0).fill_buf().unwrap()); + } + + fn cmp_bufread(mut br1: Br1, mut br2: Br2, exp: &[u8]) { + let mut cat = Vec::new(); + loop { + let consume = { + let buf1 = br1.fill_buf().unwrap(); + let buf2 = br2.fill_buf().unwrap(); + let minlen = if buf1.len() < buf2.len() { + buf1.len() + } else { + buf2.len() + }; + assert_eq!(buf1[..minlen], buf2[..minlen]); + cat.extend_from_slice(&buf1[..minlen]); + minlen + }; + if consume == 0 { + break; + } + br1.consume(consume); + br2.consume(consume); + } + assert_eq!(br1.fill_buf().unwrap().len(), 0); + assert_eq!(br2.fill_buf().unwrap().len(), 0); + assert_eq!(&cat[..], &exp[..]) + } + + #[test] + fn chain_bufread() { + let testdata = b"ABCDEFGHIJKL"; + let chain1 = (&testdata[..3]) + .chain(&testdata[3..6]) + .chain(&testdata[6..9]) + .chain(&testdata[9..]); + let chain2 = (&testdata[..4]) + .chain(&testdata[4..8]) + .chain(&testdata[8..]); + cmp_bufread(chain1, chain2, &testdata[..]); + } + + #[test] + fn chain_zero_length_read_is_not_eof() { + let a = b"A"; + let b = b"B"; + let mut s = String::new(); + let mut chain = (&a[..]).chain(&b[..]); + chain.read(&mut []).unwrap(); + chain.read_to_string(&mut s).unwrap(); + assert_eq!("AB", s); + } + + // #[bench] + // #[cfg_attr(target_os = "emscripten", ignore)] + // fn bench_read_to_end(b: &mut test::Bencher) { + // b.iter(|| { + // let mut lr = repeat(1).take(10000000); + // let mut vec = Vec::with_capacity(1024); + // super::read_to_end(&mut lr, &mut vec) + // }); + // } +} diff --git a/src/io/prelude.rs b/src/io/prelude.rs new file mode 100644 index 0000000000..d0240613dd --- /dev/null +++ b/src/io/prelude.rs @@ -0,0 +1,23 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The I/O Prelude +//! +//! The purpose of this module is to alleviate imports of many common I/O traits +//! by adding a glob import to the top of I/O heavy modules: +//! +//! ``` +//! # #![allow(unused_imports)] +//! use std::io::prelude::*; +//! ``` + +pub use super::{BufRead, Read, Seek, Write}; + +pub use alloc::{boxed::Box, vec::Vec}; diff --git a/src/iter.rs b/src/iter.rs new file mode 100644 index 0000000000..369d2dadc8 --- /dev/null +++ b/src/iter.rs @@ -0,0 +1,206 @@ +//! Utilities to help use Rust iterators on C strings. + +use core::{ + iter::{FusedIterator, Iterator}, + marker::PhantomData, + mem::MaybeUninit, + ptr::NonNull, +}; + +use crate::platform::types::*; + +/// A minimal alternative to the `Zero` trait from num-traits, for use in +/// `NulTerminated`. +/// +/// May be replaced with the one from num-traits at a later time if so +/// desired. +pub unsafe trait Zero { + fn is_zero(&self) -> bool; +} + +unsafe impl Zero for c_char { + fn is_zero(&self) -> bool { + self == &0 + } +} + +unsafe impl Zero for wchar_t { + fn is_zero(&self) -> bool { + self == &0 + } +} + +/// An iterator over a nul-terminated buffer. +/// +/// This is intended to allow safe, ergonomic iteration over C-style byte and +/// wide strings without first having to read through the string and construct +/// a slice. Assuming the safety requirements are upheld when constructing the +/// iterator, it allows for string iteration in safe Rust. +pub struct NulTerminated<'a, T: Zero> { + ptr: NonNull, + phantom: PhantomData<&'a T>, +} + +impl<'a, T: Zero> Iterator for NulTerminated<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // SAFETY: the caller is required to ensure a valid pointer to a + // 0-terminated buffer is provided, and the zero-check below ensures + // that iteration and pointer increments will stop in time. + let val_ref = unsafe { self.ptr.as_ref() }; + if val_ref.is_zero() { + None + } else { + // SAFETY: the caller is required to provide a 0-terminated + // buffer, and this point will only be reached if the next element + // is at most the terminating 0. + self.ptr = unsafe { self.ptr.add(1) }; + Some(val_ref) + } + } +} + +impl<'a, T: Zero> NulTerminated<'a, T> { + /// Constructs a new iterator, starting at `ptr`, yielding elements of + /// type `&T` up to (but not including) the terminating nul. + /// + /// The iterator returns `None` after the terminating nul has been + /// encountered. + /// + /// # Safety + /// The provided pointer must be a valid pointer to a buffer of contiguous + /// elements of type `T`, and the value 0 must be present within the + /// buffer at or after `ptr` (not necessarily at the end). The buffer must + /// not be written to for the lifetime of the iterator. + pub unsafe fn new(ptr: *const T) -> Option { + Some(NulTerminated { + // NonNull can only wrap only *mut pointers... + ptr: NonNull::new(ptr.cast_mut())?, + phantom: PhantomData, + }) + } +} + +// Once the terminating nul has been encountered, the pointer will not advance +// further and the iterator will thus keep returning None. +impl<'a, T: Zero> FusedIterator for NulTerminated<'a, T> {} + +/// An iterator over a nul-terminated buffer, including the terminating nul. +/// +/// Similar to [`NulTerminated`], but includes the terminating nul. +pub struct NulTerminatedInclusive<'a, T: Zero> { + ptr_opt: Option>, + phantom: PhantomData<&'a T>, +} + +impl<'a, T: Zero> Iterator for NulTerminatedInclusive<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if let Some(old_ptr) = self.ptr_opt { + // SAFETY: the caller is required to ensure a valid pointer to a + // 0-terminated buffer is provided, and the zero-check below + // ensures that iteration and pointer increments will stop in + // time. + let val_ref = unsafe { old_ptr.as_ref() }; + self.ptr_opt = if val_ref.is_zero() { + None + } else { + // SAFETY: if a terminating nul value has been encountered, + // this will not be called + Some(unsafe { old_ptr.add(1) }) + }; + Some(val_ref) + } else { + None + } + } +} + +impl<'a, T: Zero> NulTerminatedInclusive<'a, T> { + /// Constructs a new iterator, starting at `ptr`, yielding elements of + /// type `&T` up to and including the terminating nul. + /// + /// The iterator returns `None` after the terminating nul has been + /// encountered. + /// + /// # Safety + /// The provided pointer must be a valid pointer to a buffer of contiguous + /// elements of type `T`, and the value 0 must be present within the + /// buffer at or after `ptr` (not necessarily at the end). The buffer must + /// not be written to for the lifetime of the iterator. + pub unsafe fn new(ptr: *const T) -> Self { + NulTerminatedInclusive { + // NonNull can only wrap only *mut pointers... + ptr_opt: NonNull::new(ptr.cast_mut()), + phantom: PhantomData, + } + } +} + +// Once the terminating nul has been encountered, the internal Option will be +// set to None, ensuring that we will keep returning None. +impl<'a, T: Zero> FusedIterator for NulTerminatedInclusive<'a, T> {} + +/// A zipped iterator mapping an input iterator to an "out" pointer. +/// +/// This is intended to allow safe, iterative writing to an "out pointer". +/// Special care needs to be taken to avoid creating references past the end +/// of the output buffer, thus the output is zipped with an "input" iterator +/// to ensure up-front control of the range of memory on which we create +/// references. +pub struct SrcDstPtrIter<'a, I: Iterator, U: Copy> { + src_iter: I, + dst_ptr: *mut U, + phantom: PhantomData<&'a mut U>, +} + +impl<'a, I: Iterator, U: Copy> Iterator for SrcDstPtrIter<'a, I, U> { + type Item = (I::Item, &'a mut MaybeUninit); + + fn next(&mut self) -> Option { + if let Some(src_item) = self.src_iter.next() { + let old_dst_ptr = self.dst_ptr; + + // SAFETY: due to the caller requirements on `I` upon + // construction, the new pointer here may be either valid to turn + // into a reference or "one past the end". The latter is okay as + // long as it is only represented as a raw pointer. + self.dst_ptr = unsafe { self.dst_ptr.add(1) }; + + // SAFETY: self.dst_ptr may point "one past the end", but the + // caller is required upon construction to ensure that `I` does + // not over-iterate, and thus old_dst_ptr is always okay to + // dereference. + let out_mut_ref = unsafe { old_dst_ptr.as_uninit_mut() }.unwrap(); + + Some((src_item, out_mut_ref)) + } else { + None + } + } +} + +impl<'a, I: Iterator, U: Copy> SrcDstPtrIter<'a, I, U> { + /// Constructs a new iterator of "zipped" input and output. + /// + /// The caller must provide an "input" iterator `I` and an "out pointer" + /// `ptr`. Assuming `I` has item type `T`, the new iterator will have + /// `type Item = (T, &mut MaybeUninit)`. + /// + /// # Safety + /// `ptr` must be a valid pointer to a writable buffer of contiguous (but + /// possibly uninitialized) elements of type `U`. The caller must ensure + /// that `I` does not return `Some` any more times than there are elements + /// in the output buffer. The caller must ensure that the iterator has + /// exclusive access to that buffer for the entire lifetime of the + /// iterator. + pub unsafe fn new(iter: I, ptr: *mut U) -> Self { + SrcDstPtrIter { + src_iter: iter, + dst_ptr: ptr, + phantom: PhantomData, + } + } +} diff --git a/src/ld_so/access.rs b/src/ld_so/access.rs index 7cf0d201d7..267e9e897f 100644 --- a/src/ld_so/access.rs +++ b/src/ld_so/access.rs @@ -1,62 +1,10 @@ -// Wrapper over the access syscall that doesn't touch errno variable, -// Do not use outside of ld_so - -#[cfg(target_os = "redox")] -use crate::header::unistd::{F_OK, R_OK, W_OK, X_OK}; use crate::{ c_str::{CStr, CString}, - platform::types::*, + error::Errno, + platform::{Pal, Sys, types::*}, }; -pub fn accessible(path: &str, mode: c_int) -> c_int { - let path_c = CString::new(path.as_bytes()).unwrap(); /*.map_err(|err| { - Error::Malformed(format!("invalid path '{}': {}", path, err)) - })?;*/ - unsafe { access(path_c.as_ptr(), mode) } -} - -#[cfg(target_os = "linux")] -unsafe fn access(path: *const c_char, mode: c_int) -> c_int { - let path = CStr::from_ptr(path); - syscall!(ACCESS, (path).as_ptr(), mode) as c_int -} - -// Wrapper over the systemcall, Do not use outside of ld_so -#[cfg(target_os = "redox")] -unsafe fn access(path: *const c_char, mode: c_int) -> c_int { - let path = CStr::from_ptr(path).to_bytes(); - let fd = match syscall::open(path, syscall::O_CLOEXEC) { - Ok(fd) => fd, - _ => return -1, - }; - if mode == F_OK { - return 0; - } - let mut stat = syscall::Stat::default(); - if syscall::fstat(fd, &mut stat).is_err() { - return -1; - } - let uid = match syscall::getuid() { - Ok(uid) => uid, - Err(_) => return -1, - }; - let gid = match syscall::getgid() { - Ok(gid) => gid, - Err(_) => return -1, - }; - - let perms = if stat.st_uid as usize == uid { - stat.st_mode >> (3 * 2 & 0o7) - } else if stat.st_gid as usize == gid { - stat.st_mode >> (3 * 1 & 0o7) - } else { - stat.st_mode & 0o7 - }; - if (mode & R_OK == R_OK && perms & 0o4 != 0o4) - || (mode & W_OK == W_OK && perms & 0o2 != 0o2) - || (mode & X_OK == X_OK && perms & 0o1 != 0o1) - { - return -1; - } - 0 +pub fn accessible(path: &str, mode: c_int) -> Result<(), Errno> { + let path_c = CString::new(path.as_bytes()).unwrap(); + unsafe { Sys::access(CStr::from_ptr(path_c.as_ptr()), mode) } } diff --git a/src/ld_so/callbacks.rs b/src/ld_so/callbacks.rs index f0e851cbc7..5b322ef440 100644 --- a/src/ld_so/callbacks.rs +++ b/src/ld_so/callbacks.rs @@ -1,53 +1,39 @@ -use super::linker::{Linker, Symbol, DSO}; +use super::linker::{Linker, ObjectHandle, Resolve, Result, ScopeKind}; +use crate::platform::types::c_void; use alloc::boxed::Box; -use goblin::error::Result; pub struct LinkerCallbacks { - pub unload: Box, - pub load_library: Box) -> Result>, - pub link: - Box, Option, Option) -> Result>>, - pub get_sym: Box) -> Option>, - pub run_init: Box) -> Result<()>>, - pub run_fini: Box) -> Result<()>>, + pub unload: Box, + pub load_library: + Box, Resolve, ScopeKind, bool) -> Result>, + pub get_sym: Box, &str) -> Option<*mut c_void>>, } impl LinkerCallbacks { + #[allow(clippy::new_without_default)] pub fn new() -> LinkerCallbacks { LinkerCallbacks { unload: Box::new(unload), load_library: Box::new(load_library), - link: Box::new(link), get_sym: Box::new(get_sym), - run_init: Box::new(run_init), - run_fini: Box::new(run_fini), } } } -fn unload(linker: &mut Linker, libspace: usize) { - linker.unload(libspace) +fn unload(linker: &mut Linker, handle: ObjectHandle) { + linker.unload(handle) } -fn load_library(linker: &mut Linker, name: Option<&str>) -> Result { - linker.load_library(name) -} - -fn link( +fn load_library( linker: &mut Linker, - primary_opt: Option<&str>, - dso: Option, - libspace: Option, -) -> Result> { - linker.link(primary_opt, dso, libspace) + name: Option<&str>, + resolve: Resolve, + scope: ScopeKind, + noload: bool, +) -> Result { + linker.load_library(name, resolve, scope, noload) } -fn get_sym(linker: &Linker, name: &str, libspace: Option) -> Option { - linker.get_sym(name, libspace) -} -fn run_init(linker: &Linker, libspace: Option) -> Result<()> { - linker.run_init(libspace) -} -fn run_fini(linker: &Linker, libspace: Option) -> Result<()> { - linker.run_fini(libspace) +fn get_sym(linker: &Linker, handle: Option, name: &str) -> Option<*mut c_void> { + linker.get_sym(handle, name) } diff --git a/src/ld_so/debug.rs b/src/ld_so/debug.rs index d6838e08a7..784fb339e3 100644 --- a/src/ld_so/debug.rs +++ b/src/ld_so/debug.rs @@ -1,14 +1,15 @@ use crate::{c_str::CString, platform::types::*}; use alloc::boxed::Box; +use core::ptr; #[repr(C)] pub enum RTLDState { /// Mapping change is complete. - RT_CONSISTENT, + RtConsistent, /// Beginning to add a new object. - RT_ADD, + RtAdd, /// Beginning to remove an object mapping. - RT_DELETE, + RtDelete, } /// Data structure for sharing debugging information from the @@ -38,9 +39,9 @@ pub struct RTLDDebug { impl RTLDDebug { const NEW: Self = RTLDDebug { r_version: 1, - r_map: 0 as *mut LinkMap, + r_map: ptr::null_mut::(), r_brk: _dl_debug_state, - state: RTLDState::RT_CONSISTENT, + state: RTLDState::RtConsistent, r_ldbase: 0, }; @@ -50,7 +51,6 @@ impl RTLDDebug { } else { unsafe { (*self.r_map).add_object(l_addr, name, l_ld) }; } - return; } pub fn insert_first(&mut self, l_addr: usize, name: &str, l_ld: usize) { if self.r_map.is_null() { @@ -60,10 +60,18 @@ impl RTLDDebug { self.r_map = LinkMap::new_with_args(l_addr, name, l_ld); unsafe { (*self.r_map).link(&mut *tmp) }; } - return; } } +/// SAFETY: safe as long as caller wraps the instance in a mutex, +/// or similar structure that guarantees exclusive mutable access. +/// Separate instances must not contain pointers to the same LinkMap instance. +unsafe impl Send for RTLDDebug {} +/// SAFETY: safe as long as caller wraps the instance in a mutex, +/// or similar structure that guarantees exclusive mutable access. +/// Separate instances must not contain pointers to the same LinkMap instance. +unsafe impl Sync for RTLDDebug {} + #[repr(C)] struct LinkMap { /* These members are part of the protocol with the debugger. @@ -83,16 +91,16 @@ impl LinkMap { fn new() -> *mut Self { let map = Box::new(LinkMap { l_addr: 0, - l_name: 0 as *const c_char, + l_name: ptr::null(), l_ld: 0, - l_next: 0 as *mut LinkMap, - l_prev: 0 as *mut LinkMap, + l_next: ptr::null_mut(), + l_prev: ptr::null_mut(), }); Box::into_raw(map) } fn link(&mut self, map: &mut LinkMap) { - map.l_prev = self as *mut LinkMap; - self.l_next = map as *mut LinkMap; + map.l_prev = ptr::from_mut::(self); + self.l_next = ptr::from_mut::(map); } fn new_with_args(l_addr: usize, name: &str, l_ld: usize) -> *mut Self { let map = LinkMap::new(); @@ -100,7 +108,7 @@ impl LinkMap { (*map).l_addr = l_addr; (*map).l_ld = l_ld; let c_name = CString::new(name).unwrap(); - (*map).l_name = c_name.into_raw() as *const c_char; + (*map).l_name = c_name.into_raw().cast_const(); } map } @@ -113,7 +121,7 @@ impl LinkMap { } unsafe { (*node).l_prev = last; - (*last).l_next = node; + last.l_next = node; } } } @@ -123,8 +131,8 @@ impl LinkMap { * break point there */ #[linkage = "weak"] -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn _dl_debug_state() {} -#[no_mangle] -pub static mut _r_debug: RTLDDebug = RTLDDebug::NEW; +#[unsafe(no_mangle)] +pub static _r_debug: spin::Mutex = spin::Mutex::new(RTLDDebug::NEW); diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs new file mode 100644 index 0000000000..afb4c7c1e6 --- /dev/null +++ b/src/ld_so/dso.rs @@ -0,0 +1,1310 @@ +//! See: +//! * +//! * + +use object::{ + NativeEndian, Object, StringTable, SymbolIndex, elf, + read::elf::{ + Dyn as _, GnuHashTable, HashTable as SysVHashTable, ProgramHeader as _, Rel as _, + Rela as _, Sym as _, Version, VersionTable, + }, +}; + +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] +use super::tcb::Tcb; +use super::{ + debug::{_r_debug, RTLDDebug}, + linker::{__plt_resolve_trampoline, GLOBAL_SCOPE, Resolve, Scope, Symbol}, + tcb::Master, +}; +use crate::{ + header::{dl_tls::__tls_get_addr, sys_mman}, + platform::{Pal, Sys, types::c_void}, +}; +use alloc::{ + boxed::Box, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] +use core::mem::offset_of; +use core::{ + ffi::c_char, + mem::size_of, + ptr::{self, NonNull}, + slice, + sync::atomic::{AtomicBool, Ordering}, +}; + +pub const CHAR_BITS: usize = c_char::BITS as usize; +pub type Relr = usize; + +#[cfg(target_pointer_width = "32")] +mod shim { + use object::{NativeEndian, elf::*, read::elf::ElfFile32}; + pub type Dyn = Dyn32; + pub type Rel = Rel32; + pub type Rela = Rela32; + pub type Sym = Sym32; + pub type FileHeader = FileHeader32; + pub type ProgramHeader = ProgramHeader32; + pub type ElfFile<'a> = ElfFile32<'a, NativeEndian>; +} + +#[cfg(target_pointer_width = "64")] +mod shim { + use object::{NativeEndian, elf::*, read::elf::ElfFile64}; + pub type Dyn = Dyn64; + pub type Rel = Rel64; + pub type Rela = Rela64; + pub type Sym = Sym64; + pub type FileHeader = FileHeader64; + pub type ProgramHeader = ProgramHeader64; + pub type ElfFile<'a> = ElfFile64<'a, NativeEndian>; +} + +pub use shim::*; + +// TODO: missing from the `object` crate +pub const DT_RELRSZ: u32 = 35; +pub const DT_RELR: u32 = 36; +pub const DT_RELRENT: u32 = 37; + +/// Undefined Symbol Index +pub const STN_UNDEF: SymbolIndex = SymbolIndex(0); + +enum HashTable<'a> { + Gnu(GnuHashTable<'a, FileHeader>), + Sysv(SysVHashTable<'a, FileHeader>), +} + +impl<'a> HashTable<'a> { + /// Use the hash table to find the symbol table entry with the given name, hash, and version. + #[inline] + pub fn find( + &self, + name: &str, + version: Option<&Version<'_>>, + symbols: &'a [Sym], + strings: StringTable<'a>, + versions: &VersionTable<'a, FileHeader>, + ) -> Option<(SymbolIndex, &'a Sym)> { + let name = name.as_bytes(); + + match self { + Self::Gnu(hash_table) => { + let hash = elf::gnu_hash(name); + hash_table.find( + NativeEndian, + name, + hash, + version, + symbols, + strings, + versions, + ) + } + + Self::Sysv(hash_table) => { + let hash = elf::hash(name); + hash_table.find( + NativeEndian, + name, + hash, + version, + symbols, + strings, + versions, + ) + } + } + } + + fn symbol_table_length(&self) -> usize { + match self { + Self::Gnu(hash_table) => hash_table + .symbol_table_length(NativeEndian) + .expect("empty GNU symbol hash table") + as usize, + Self::Sysv(hash_table) => hash_table.symbol_table_length() as usize, + } + } +} + +type InitFn = unsafe extern "C" fn(); + +pub(super) struct Dynamic<'data> { + runpath: Option, + got: Option>, + needed: Vec<&'data str>, + pub(super) jmprel: usize, + hash_table: HashTable<'data>, + pub(super) dynstrtab: StringTable<'data>, + soname: Option<&'data str>, + init_array: &'data [unsafe extern "C" fn()], + fini_array: &'data [unsafe extern "C" fn()], + rela: &'data [Rela], + relr: &'data [Relr], + rel: &'data [Rel], + symbols: &'data [Sym], + explicit_addend: bool, + pltrelsz: usize, +} + +impl<'data> Dynamic<'data> { + pub fn symbol(&self, index: SymbolIndex) -> Option<&'data Sym> { + // Symbol table entry for index 0 is reserved. + assert!(index != SymbolIndex(0)); + self.symbols.get(index.0) + } + + fn symbol_name(&self, index: SymbolIndex) -> Option<&'data str> { + let sym = self.symbol(index)?; + let name = sym.name(NativeEndian, self.dynstrtab).ok()?; + Some(core::str::from_utf8(name).expect("non UTF-8 ELF symbol name")) + } + + fn static_relocations(&self) -> impl Iterator + '_ { + self.rela + .iter() + .map(Relocation::from) + .chain(self.rel.iter().map(Relocation::from)) + } +} + +unsafe impl Send for Dynamic<'_> {} +unsafe impl Sync for Dynamic<'_> {} + +#[derive(Debug)] +pub(super) struct Relocation { + pub(super) offset: usize, + pub(super) addend: Option, + pub(super) sym: SymbolIndex, + pub(super) kind: RelocationKind, +} + +#[cfg(target_pointer_width = "32")] +impl From<&Rela> for Relocation { + fn from(reloc: &Rela) -> Self { + Self { + offset: reloc.r_offset(NativeEndian) as usize, + addend: Some(reloc.r_addend(NativeEndian) as usize), + sym: SymbolIndex(reloc.r_sym(NativeEndian) as usize), + kind: RelocationKind::new(reloc.r_type(NativeEndian)), + } + } +} + +#[cfg(target_pointer_width = "64")] +impl From<&Rela> for Relocation { + fn from(reloc: &Rela) -> Self { + let is_mips64el = cfg!(all(target_arch = "mips64", target_endian = "little")); + Self { + offset: reloc.r_offset(NativeEndian) as usize, + addend: Some(reloc.r_addend(NativeEndian) as usize), + sym: SymbolIndex(reloc.r_sym(NativeEndian, is_mips64el) as usize), + kind: RelocationKind::new(reloc.r_type(NativeEndian, is_mips64el)), + } + } +} + +impl From<&Rel> for Relocation { + fn from(reloc: &Rel) -> Self { + Self { + offset: reloc.r_offset(NativeEndian) as usize, + addend: None, + sym: SymbolIndex(reloc.r_sym(NativeEndian) as usize), + kind: RelocationKind::new(reloc.r_type(NativeEndian)), + } + } +} + +// This is matched up to REL_* constants used by musl for ease of comparison +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RelocationKind { + COPY, + DTPMOD, + DTPOFF, + GOT, + IRELATIVE, + OFFSET, + PLT, + RELATIVE, + SYMBOLIC, + TLSDESC, + TPOFF, + UNKNOWN(u32), +} + +impl RelocationKind { + #[cfg(target_arch = "aarch64")] + pub fn new(kind: u32) -> Self { + //WARNING: Only use R_AARCH64_* constants here! + match kind { + elf::R_AARCH64_COPY => Self::COPY, + elf::R_AARCH64_TLS_DTPMOD => Self::DTPMOD, + elf::R_AARCH64_TLS_DTPREL => Self::DTPOFF, + elf::R_AARCH64_GLOB_DAT => Self::GOT, + elf::R_AARCH64_IRELATIVE => Self::IRELATIVE, + elf::R_AARCH64_JUMP_SLOT => Self::PLT, + elf::R_AARCH64_RELATIVE => Self::RELATIVE, + elf::R_AARCH64_ABS64 => Self::SYMBOLIC, + elf::R_AARCH64_TLSDESC => Self::TLSDESC, + elf::R_AARCH64_TLS_TPREL => Self::TPOFF, + _ => Self::UNKNOWN(kind), + } + } + + #[cfg(target_arch = "riscv64")] + pub fn new(kind: u32) -> Self { + //WARNING: Only use R_RISCV_* constants here! + match kind { + elf::R_RISCV_COPY => Self::COPY, + elf::R_RISCV_TLS_DTPMOD64 => Self::DTPMOD, + elf::R_RISCV_TLS_DTPREL64 => Self::DTPOFF, + elf::R_RISCV_IRELATIVE => Self::IRELATIVE, + elf::R_RISCV_JUMP_SLOT => Self::PLT, + elf::R_RISCV_RELATIVE => Self::RELATIVE, + elf::R_RISCV_64 => Self::SYMBOLIC, + //TODO: not defined, should be 12: elf::R_RISCV_TLSDESC => Self::TLSDESC, + elf::R_RISCV_TLS_TPREL64 => Self::TPOFF, + _ => Self::UNKNOWN(kind), + } + } + + #[cfg(target_arch = "x86")] + pub fn new(kind: u32) -> Self { + //WARNING: Only use R_386_* constants here! + match kind { + elf::R_386_COPY => Self::COPY, + elf::R_386_TLS_DTPMOD32 => Self::DTPMOD, + elf::R_386_TLS_DTPOFF32 => Self::DTPOFF, + elf::R_386_GLOB_DAT => Self::GOT, + elf::R_386_IRELATIVE => Self::IRELATIVE, + elf::R_386_JMP_SLOT => Self::PLT, + elf::R_386_PC32 => Self::OFFSET, + elf::R_386_RELATIVE => Self::RELATIVE, + elf::R_386_32 => Self::SYMBOLIC, + elf::R_386_TLS_DESC => Self::TLSDESC, + elf::R_386_TLS_TPOFF => Self::TPOFF, + _ => Self::UNKNOWN(kind), + } + } + + #[cfg(target_arch = "x86_64")] + pub fn new(kind: u32) -> Self { + //WARNING: Only use R_X86_64_* constants here! + match kind { + elf::R_X86_64_COPY => Self::COPY, + elf::R_X86_64_DTPMOD64 => Self::DTPMOD, + elf::R_X86_64_DTPOFF64 => Self::DTPOFF, + elf::R_X86_64_GLOB_DAT => Self::GOT, + elf::R_X86_64_IRELATIVE => Self::IRELATIVE, + elf::R_X86_64_JUMP_SLOT => Self::PLT, + elf::R_X86_64_RELATIVE => Self::RELATIVE, + elf::R_X86_64_64 => Self::SYMBOLIC, + elf::R_X86_64_TLSDESC => Self::TLSDESC, + elf::R_X86_64_TPOFF64 => Self::TPOFF, + _ => Self::UNKNOWN(kind), + } + } +} + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum SymbolBinding { + /// Global symbols are visible to all object files being combined. One + /// file's definition of a global symbol will satisfy another file's + /// undefined reference to the same global symbol. + Global = elf::STB_GLOBAL, + /// Weak symbols resemble global symbols, but their definitions have lower + /// precedence. + Weak = elf::STB_WEAK, +} + +impl SymbolBinding { + #[inline] + pub fn is_global(&self) -> bool { + matches!(self, Self::Global) + } +} + +/// Use to represent a library as well as all the symbols that is loaded withen it. +pub struct DSO { + pub name: String, + pub id: usize, + pub dlopened: bool, + pub entry_point: usize, + /// Loaded library in-memory data + pub mmap: &'static [u8], + pub tls_module_id: usize, + pub tls_offset: usize, + + pub(super) dynamic: Dynamic<'static>, + + pub scope: spin::Once, + /// Position Independent Executable. + pub pie: bool, + + /// Whether this DSO *and* its dependencies have been successfully loaded. + is_ready: AtomicBool, +} + +impl DSO { + pub fn new( + path: &str, + data: &[u8], + base_addr: Option, + dlopened: bool, + id: usize, + tls_module_id: usize, + tls_offset: usize, + ) -> Result<(DSO, Option, Vec), String> { + let elf = ElfFile::parse(data).map_err(|err| err.to_string())?; + let (mmap, tcb_master, dynamic) = + DSO::mmap_and_copy(path, &elf, data, base_addr, tls_offset)?; + + let name = match dynamic.soname { + Some(soname) => soname.to_string(), + _ => basename(path), + }; + let tls_offset = match tcb_master { + Some(ref master) => master.offset, + _ => 0, + }; + let entry_point = if is_pie_enabled(&elf) { + mmap.as_ptr() as usize + elf.entry() as usize + } else { + elf.entry() as usize + }; + + let dso = DSO { + name, + id, + dlopened, + entry_point, + mmap, + tls_module_id: if tcb_master.is_some() { + tls_module_id + } else { + 0 + }, + tls_offset, + + pie: is_pie_enabled(&elf), + dynamic, + scope: spin::Once::new(), + is_ready: AtomicBool::new(false), + }; + + Ok((dso, tcb_master, elf.elf_program_headers().to_vec())) + } + + #[inline] + pub fn mark_ready(&self) { + self.is_ready.store(true, Ordering::SeqCst); + } + + #[inline] + pub fn scope(&self) -> &Scope { + self.scope.get().expect("scope not initialized") + } + + /// Global Offset Table + #[inline] + pub fn got(&self) -> Option> { + self.dynamic.got + } + + #[inline] + pub fn runpath(&self) -> Option<&String> { + self.dynamic.runpath.as_ref() + } + + #[inline] + pub fn dependencies(&self) -> &[&str] { + &self.dynamic.needed + } + + pub fn get_sym<'a>(&self, name: &'a str) -> Option<(Symbol<'a>, SymbolBinding)> { + let (_, sym) = self.dynamic.hash_table.find( + name, + None, + self.dynamic.symbols, + self.dynamic.dynstrtab, + &VersionTable::default(), + )?; + + if sym.st_shndx(NativeEndian) == elf::SHN_UNDEF { + return None; + } + + Some(( + Symbol { + name, + base: if self.pie { + self.mmap.as_ptr() as usize + } else { + 0 + }, + value: sym.st_value(NativeEndian) as usize, + size: sym.st_size(NativeEndian) as usize, + sym_type: sym.st_type(), + }, + // TODO(andypython): move this into [`Symbol`] + match sym.st_bind() { + elf::STB_GLOBAL => SymbolBinding::Global, + elf::STB_WEAK => SymbolBinding::Weak, + bind => unreachable!("get_sym bind {bind}"), + }, + )) + } + + pub fn run_init(&self) { + for f in self.dynamic.init_array { + unsafe { f() } + } + } + + pub fn run_fini(&self) { + for f in self.dynamic.fini_array.iter().rev() { + unsafe { f() } + } + } + + fn mmap_and_copy<'a>( + path: &str, + elf: &ElfFile<'a>, + data: &'a [u8], + base_addr: Option, + tls_offset: usize, + ) -> Result<(&'static [u8], Option, Dynamic<'static>), String> { + let endian = elf.endian(); + log::trace!("# {}", path); + // data for struct LinkMap + let mut l_ld = 0; + // Calculate virtual memory bounds + let bounds = { + let mut bounds_opt: Option<(usize, usize)> = None; + for ph in elf.elf_program_headers() { + let voff = ph.p_vaddr(endian) % ph.p_align(endian); + let vaddr = (ph.p_vaddr(endian) - voff) as usize; + let vsize = ((ph.p_memsz(endian) + voff) as usize) + .next_multiple_of(ph.p_align(endian) as usize); + + match ph.p_type(endian) { + elf::PT_DYNAMIC => { + l_ld = ph.p_vaddr(endian); + } + elf::PT_LOAD => { + log::trace!(" load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph); + if let Some(ref mut bounds) = bounds_opt { + if vaddr < bounds.0 { + bounds.0 = vaddr; + } + if vaddr + vsize > bounds.1 { + bounds.1 = vaddr + vsize; + } + } else { + bounds_opt = Some((vaddr, vaddr + vsize)); + } + } + _ => (), + } + } + bounds_opt.ok_or_else(|| "Unable to find PT_LOAD section".to_string())? + }; + log::trace!(" bounds {:#x}, {:#x}", bounds.0, bounds.1); + // Allocate memory + let mmap = unsafe { + if let Some(addr) = base_addr { + let size = if is_pie_enabled(elf) { + bounds.1 + } else { + bounds.1 - bounds.0 + }; + _r_debug + .lock() + .insert_first(addr + bounds.0, path, addr + l_ld as usize); + slice::from_raw_parts_mut((addr + bounds.0) as *mut u8, size) + } else { + let (start, end) = bounds; + let size = end - start; + let mut flags = sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE; + if start != 0 { + flags |= sys_mman::MAP_FIXED_NOREPLACE; + } + log::trace!(" mmap({:#x}, {:x}, {:x})", start, size, flags); + let ptr = Sys::mmap( + start as *mut c_void, + size, + //TODO: Make it possible to not specify PROT_EXEC on Redox + sys_mman::PROT_READ | sys_mman::PROT_WRITE, + flags, + -1, + 0, + ) + .map_err(|e| format!("failed to map {}. errno: {}", path, e.0))?; + + if !(start as *mut c_void).is_null() { + assert_eq!( + ptr, start as *mut c_void, + "mmap must always map on the destination we requested" + ); + } + log::trace!(" = {:p}", ptr); + _r_debug + .lock() + .insert(ptr as usize, path, ptr as usize + l_ld as usize); + slice::from_raw_parts_mut(ptr.cast::(), size) + } + }; + + let skip_load_segment_copy = base_addr.is_some(); + let mut tcb_master = None; + + // Copy data + let mut dynamic = None; + for ph in elf.elf_program_headers() { + match ph.p_type(endian) { + elf::PT_LOAD => { + if skip_load_segment_copy { + continue; + } + let obj_data = { + let (offset, size) = ph.file_range(endian); + let offset = offset as usize; + let range = offset..(offset + size as usize); + match data.get(range.clone()) { + Some(some) => some, + None => return Err(format!("failed to read {:x?}", range)), + } + }; + + let mmap_data = { + let range = if is_pie_enabled(elf) { + let addr = ph.p_vaddr(endian) as usize; + addr..addr + obj_data.len() + } else { + let addr = ph.p_vaddr(endian) as usize - mmap.as_ptr() as usize; + addr..addr + obj_data.len() + }; + match mmap.get_mut(range.clone()) { + Some(some) => some, + None => { + return Err(format!("failed to write {:x?}", range)); + } + } + }; + let _voff = ph.p_vaddr(endian) % ph.p_align(endian); + let _vsize = ((ph.p_memsz(endian) + _voff) as usize) + .next_multiple_of(ph.p_align(endian) as usize); + log::trace!( + " copy {:#x}, {:#x}: {:#x}, {:#x}", + ph.p_vaddr(endian) - _voff, + _vsize, + _voff, + obj_data.len() + ); + mmap_data.copy_from_slice(obj_data); + } + elf::PT_TLS => { + let ptr = unsafe { + if is_pie_enabled(elf) { + mmap.as_ptr().add(ph.p_vaddr(endian) as usize) + } else { + ph.p_vaddr(endian) as *const u8 + } + }; + tcb_master = Some(Master { + ptr, + image_size: ph.p_filesz(endian) as usize, + segment_size: ph.p_memsz(endian) as usize, + offset: tls_offset + ph.p_memsz(endian) as usize, + }); + log::trace!(" tcb master {:x?}", tcb_master); + } + + elf::PT_DYNAMIC => { + let entries = ph + .dynamic(endian, data) + .map_err(|err| err.to_string())? + .ok_or_else(|| "Unable to parse PT_DYNAMIC section".to_string())?; + dynamic = Some((ph, entries)); + } + _ => (), + } + } + + let dynamic = dynamic.ok_or_else(|| "Unable to find PT_DYNAMIC section".to_string())?; + + let (parsed_dynamic, debug) = Self::parse_dynamic(path, mmap, is_pie_enabled(elf), dynamic) + .map_err(|e| e.to_string())?; + + if let Some(i) = debug { + // FIXME: cleanup + let (ph, _) = dynamic; + let vaddr = ph.p_vaddr(endian) as usize; + let bytes: [u8; size_of::() / 2] = + ((&raw const _r_debug).cast::<*const RTLDDebug>() as usize).to_ne_bytes(); + let start = if is_pie_enabled(elf) { + vaddr + i * size_of::() + size_of::() / 2 + } else { + vaddr + i * size_of::() + size_of::() / 2 + - mmap.as_ptr().cast_mut() as usize + }; + unsafe { + ptr::copy_nonoverlapping( + bytes.as_ptr(), + mmap.as_ptr().cast_mut().add(start), + bytes.len(), + ); + } + } + + Ok((mmap, tcb_master, parsed_dynamic)) + } + + fn parse_dynamic<'a>( + path: &str, + mmap: &'a [u8], + is_pie: bool, + (_, entries): (&ProgramHeader, &[Dyn]), + ) -> object::Result<(Dynamic<'a>, Option)> { + let mut runpath = None; + let mut got = None; + let mut needed = vec![]; + let mut jmprel = None; + let mut soname = None; + let mut hash_table = None; + let mut explicit_addend = None; + let mut pltrelsz = None; + let mut debug = None; + let mut symtab_ptr = None; + let (mut rel_ptr, mut rel_len) = (None, None); + let (mut relr_ptr, mut relr_len) = (None, None); + let (mut strtab_offset, mut strtab_size) = (None, None); + let (mut init_array_ptr, mut init_array_len) = (None, None); + let (mut fini_array_ptr, mut fini_array_len) = (None, None); + let (mut rela_offset, mut rela_len) = (None, None); + + for (i, entry) in entries.iter().enumerate() { + let val = entry.d_val(NativeEndian); + let relative_idx = val as usize - if is_pie { 0 } else { mmap.as_ptr() as usize }; + let ptr = (val as usize + if is_pie { mmap.as_ptr() as usize } else { 0 }) as *const u8; + let tag = entry.d_tag(NativeEndian) as u32; + + match tag { + elf::DT_DEBUG => debug = Some(i), + + // {Gnu,SysV}HashTable::parse() + // + // > The header does not contain a length field, and so all of + // > `data` will be used as the hash table values. It does not + // > matter if this is longer than needed... + elf::DT_GNU_HASH => { + let value = GnuHashTable::parse(NativeEndian, &mmap[relative_idx..])?; + hash_table = Some(HashTable::Gnu(value)); + } + + // XXX: Both GNU_HASH and HASH may be present, we give priority + // to GNU_HASH as it is significantly faster. + elf::DT_HASH if hash_table.is_none() => { + let value = SysVHashTable::parse(NativeEndian, &mmap[relative_idx..])?; + hash_table = Some(HashTable::Sysv(value)); + } + + elf::DT_PLTGOT => { + got = Some(NonNull::new(ptr as *mut usize).expect("DT_PLTGOT is NULL")); + } + + elf::DT_NEEDED => needed.push(entry), + elf::DT_JMPREL => jmprel = Some(ptr as usize), + elf::DT_RUNPATH => runpath = Some(entry), // FIXME(andypython): rpath + elf::DT_STRTAB => strtab_offset = Some(relative_idx), + elf::DT_STRSZ => strtab_size = Some(val), + elf::DT_SONAME => soname = Some(entry), + + elf::DT_RELA => rela_offset = Some(ptr.cast::()), + elf::DT_RELASZ => rela_len = Some(val as usize / size_of::()), + elf::DT_RELAENT => { + assert_eq!(val, size_of::() as _) + } + + elf::DT_REL => rel_ptr = Some(ptr.cast::()), + elf::DT_RELSZ => rel_len = Some(val as usize / size_of::()), + elf::DT_RELENT => { + assert_eq!(val, size_of::() as _) + } + + DT_RELR => relr_ptr = Some(ptr.cast::()), + DT_RELRSZ => relr_len = Some(val as usize / size_of::()), + DT_RELRENT => { + assert_eq!(val, size_of::() as _) + } + + elf::DT_PLTREL => { + let val = val as u32; + if val == elf::DT_RELA { + explicit_addend = Some(true); + } else { + assert_eq!(val, elf::DT_REL); + explicit_addend = Some(false); + } + } + elf::DT_PLTRELSZ => pltrelsz = Some(val as usize), + + elf::DT_INIT_ARRAY if val != 0 => init_array_ptr = Some(ptr.cast::()), + elf::DT_INIT_ARRAYSZ => init_array_len = Some(val as usize / size_of::()), + + elf::DT_FINI_ARRAY if val != 0 => fini_array_ptr = Some(ptr.cast::()), + elf::DT_FINI_ARRAYSZ => fini_array_len = Some(val as usize / size_of::()), + + elf::DT_SYMTAB => symtab_ptr = Some(ptr.cast::()), + elf::DT_SYMENT => { + assert_eq!(val as usize, size_of::()); + } + + _ => {} + } + } + + let strtab_offset = strtab_offset.expect("mandatory DT_STRTAB not present"); + let strtab_size = strtab_size.expect("mandatory DT_STRSZ not present"); + + let dynstrtab = StringTable::new( + &*mmap, + strtab_offset as u64, + strtab_offset as u64 + strtab_size as u64, + ); + + let get_str = |entry: &Dyn| { + entry + .string(NativeEndian, dynstrtab) + .map(|bytes| core::str::from_utf8(bytes).expect("non utf-8 elf symbol name")) + }; + + unsafe fn get_array<'a, T>(ptr: Option<*const T>, len: Option) -> &'a [T] { + if let Some(ptr) = ptr { + let len = len.expect("dynamic entry was present without it's corresponding size"); + unsafe { core::slice::from_raw_parts(ptr, len) } + } else { + assert!(len.is_none()); + &[] + } + } + + let needed = needed + .into_iter() + .map(get_str) + .collect::>>()?; + + let base = dirname(path); + + let runpath = runpath + .map(get_str) + .transpose()? + .map(|value| value.replace("$ORIGIN", &base)); + + let soname = soname.map(get_str).transpose()?; + + let jmprel = jmprel.unwrap_or_default(); + let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present"); + + let init_array = unsafe { get_array(init_array_ptr, init_array_len) }; + let fini_array = unsafe { get_array(fini_array_ptr, fini_array_len) }; + let rela = unsafe { get_array(rela_offset, rela_len) }; + let relr = unsafe { get_array(relr_ptr, relr_len) }; + let rel = unsafe { get_array(rel_ptr, rel_len) }; + + Ok(( + Dynamic { + symbols: unsafe { get_array(symtab_ptr, Some(hash_table.symbol_table_length())) }, + runpath, + got, + needed, + jmprel, + soname, + hash_table, + dynstrtab, + init_array, + fini_array, + rela, + rel, + relr, + explicit_addend: explicit_addend.unwrap_or_default(), + pltrelsz: pltrelsz.unwrap_or_default(), + }, + debug, + )) + } + + /// `TLSDESC` relocation being an extension to the original TLS ABI spec can + /// be present in either `.rela.plt` (handled in [`Self::static_relocate`]) + /// or `.rela.dyn` (handled in [`Self::lazy_relocate`]) due to the lack of a + /// standard unfortunately. + /// + /// # Panics + /// + /// Panics if `reloc.kind` is not `RelocationKind::TLSDESC`. + fn do_tlsdesc_reloc(&self, reloc: Relocation, ptr: *mut usize, global_scope: &Scope) { + assert!(reloc.kind == RelocationKind::TLSDESC); + let (sym, tls_module_id, tls_offset) = if reloc.sym != SymbolIndex(0) { + let sym_name = self.dynamic.symbol_name(reloc.sym).unwrap(); + let (sym, _, obj) = resolve_sym(sym_name, &[global_scope, self.scope()]).unwrap(); + (sym.value, obj.tls_module_id, obj.tls_offset) + } else { + (0, self.tls_module_id, self.tls_offset) + }; + + let resolver = unsafe { &mut *ptr }; + let descriptor = unsafe { &mut *ptr.add(1) }; + + if self.dlopened { + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + unimplemented!("`TLSDESC` relocations are not yet implemented for riscv64 and x86"); + } + + let mut tls_index = crate::header::dl_tls::dl_tls_index { + ti_module: tls_module_id, + ti_offset: reloc.addend.unwrap_or_default(), + }; + + // Ensure the DTV entry is initialised. + unsafe { __tls_get_addr(&raw mut tls_index) }; + + *resolver = __tlsdesc_dynamic as *const () as usize; + *descriptor = Box::into_raw(Box::new(TlsDescriptor { + module_id: tls_module_id - 1, + addend: sym + reloc.addend.unwrap_or_default(), + })) as usize; + } else { + *resolver = __tlsdesc_static as *const () as usize; + *descriptor = sym + tls_offset + reloc.addend.unwrap_or_default(); + } + } + + fn static_relocate(&self, global_scope: &Scope, reloc: Relocation) -> object::Result<()> { + let b = self.mmap.as_ptr() as usize; + + let (sym, my_sym) = if reloc.sym != STN_UNDEF { + let name = self.dynamic.symbol_name(reloc.sym).unwrap(); + + let lookup_scopes = [global_scope, self.scope()]; + let sym = if matches!(reloc.kind, RelocationKind::COPY) { + lookup_scopes + .iter() + .find_map(|scope| scope._get_sym(name, 1)) + } else { + resolve_sym(name, &lookup_scopes) + } + .map(|(sym, _, obj)| (sym, obj)); + + (sym, self.dynamic.symbol(reloc.sym)) + } else { + (None, None) + }; + + let (s, tls_obj) = sym + .as_ref() + .map(|(sym, obj)| (sym.as_ptr() as usize, obj.as_ref())) + // (1) According to the System V gABI (Chapter 4, "Relocation"): if + // the symbol index (`reloc.sym`) is undefined, the symbol value + // (`s`) is defined as 0. + // + // (2) According to Drepper's ELF Handling For Thread-Local Storage + // (Section 4.2, "Local Dynamic TLS Model"): a relocation with + // undefined symbol index implies a reference to the current module + // itself. Hence we resolve the object to `self`. + .unwrap_or((0, self)); + + let ptr = if self.pie { + (b + reloc.offset) as *mut u8 + } else { + reloc.offset as *mut u8 + }; + let p = ptr as usize; + let a = match reloc.addend { + Some(some) => some, + None => match reloc.kind { + RelocationKind::COPY | RelocationKind::GOT | RelocationKind::PLT => 0, + _ => unsafe { *ptr.cast::() }, + }, + }; + + // TODO: support different sizes? + let set_usize = |value| unsafe { + *ptr.cast::() = value; + }; + + match reloc.kind { + RelocationKind::DTPMOD => set_usize(tls_obj.tls_module_id), + // TODO: Subtract DTP_OFFSET, which is 0x800 on riscv64, 0 on x86? + RelocationKind::DTPOFF => { + if reloc.sym.0 > 0 { + let (sym, _) = sym + .as_ref() + .expect("RelocationKind::DTPOFF called without valid symbol"); + set_usize(sym.value + a); + } else { + set_usize(a); + } + } + RelocationKind::GOT => set_usize(s), + RelocationKind::OFFSET => set_usize((s + a).wrapping_sub(p)), + RelocationKind::RELATIVE => set_usize(b + a), + RelocationKind::SYMBOLIC => set_usize(s + a), + RelocationKind::TPOFF => { + assert!( + !tls_obj.dlopened, + "The {{local/initial}}-exec access model is used for symbol '{}' in '{}', which requires a static TLS block. However, the definition in '{}' resides in the dynamic TLS block because the object was loaded via dlopen(2).", + reloc.sym, self.name, tls_obj.name + ); + if reloc.sym.0 > 0 { + let (sym, _) = sym + .as_ref() + .expect("RelocationKind::TPOFF called without valid symbol"); + set_usize((sym.value + a).wrapping_sub(tls_obj.tls_offset)); + } else { + set_usize(a.wrapping_sub(tls_obj.tls_offset)); + } + } + RelocationKind::IRELATIVE => unsafe { + let f: unsafe extern "C" fn() -> usize = core::mem::transmute(b + a); + set_usize(f()); + }, + RelocationKind::COPY => unsafe { + let (sym, obj) = sym + .as_ref() + .expect("RelocationKind::COPY called without valid symbol"); + let my_sym = my_sym.expect("RelocationKind::COPY called without valid symbol"); + assert!( + sym.size == my_sym.st_size(NativeEndian) as usize, + "RelocationKind::COPY failed: I was trying to use the symbol {} from {} for {} but they had different sizes. Please consider relinking.", + sym.name, + obj.name, + self.name + ); + // SAFETY: Both the source and destination have the same size. + ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size); + }, + RelocationKind::TLSDESC => { + self.do_tlsdesc_reloc(reloc, ptr.cast::(), global_scope) + } + _ => unimplemented!("relocation type {:?}", reloc.kind), + } + + Ok(()) + } + + fn lazy_relocate(&self, global_scope: &Scope, resolve: Resolve) -> object::Result<()> { + let Some(got) = self.got() else { + assert_eq!(self.dynamic.jmprel, 0); + return Ok(()); + }; + + let object_base_addr = self.mmap.as_ptr() as usize; + let jmprel = self.dynamic.jmprel; + let pltrelsz = self.dynamic.pltrelsz; + + unsafe { + got.add(1).write(core::ptr::addr_of!(*self) as usize); + got.add(2) + .write(__plt_resolve_trampoline as *const () as usize); + } + + let relsz = if self.dynamic.explicit_addend { + size_of::() + } else { + size_of::() + }; + + for addr in (jmprel..(jmprel + pltrelsz)).step_by(relsz) { + let reloc: Relocation = if self.dynamic.explicit_addend { + unsafe { &*(addr as *const Rela) }.into() + } else { + unsafe { &*(addr as *const Rel) }.into() + }; + + let ptr = if self.pie { + (object_base_addr + reloc.offset) as *mut usize + } else { + reloc.offset as *mut usize + }; + + match (reloc.kind, resolve) { + (RelocationKind::PLT, Resolve::Lazy) if self.pie => unsafe { + *ptr += object_base_addr; + }, + + (RelocationKind::PLT, Resolve::Lazy) => { + // NOP. + } + + (RelocationKind::PLT, Resolve::Now) => { + let name = self.dynamic.symbol_name(reloc.sym).unwrap(); + + let resolved = resolve_sym(name, &[global_scope, self.scope()]) + .map(|(sym, _, _)| sym.as_ptr() as usize) + .unwrap_or_else(|| { + panic!( + "unresolved symbol: {name} for soname {:?}", + self.dynamic.soname + ) + }); + + unsafe { + *ptr = resolved + reloc.addend.unwrap_or(0); + } + } + + (RelocationKind::TLSDESC, Resolve::Now) => { + self.do_tlsdesc_reloc(reloc, ptr, global_scope); + } + (RelocationKind::TLSDESC, Resolve::Lazy) => { + unreachable!("TLSDESC cannot be lazily resolved") + } + + _ => { + unimplemented!( + "relocation type {:?} with resolve {:?}", + reloc.kind, + resolve + ) + } + } + } + + Ok(()) + } + + pub fn relocate(&self, ph: &[ProgramHeader], resolve: Resolve) -> object::Result<()> { + let global_scope = GLOBAL_SCOPE.read(); + let base = self.mmap.as_ptr(); + + unsafe { + apply_relr(base, self.dynamic.relr); + } + + self.dynamic + .static_relocations() + .try_for_each(|reloc| self.static_relocate(&global_scope, reloc))?; + + self.lazy_relocate(&global_scope, resolve)?; + + // Protect pages + for ph in ph + .iter() + .filter(|ph| ph.p_type(NativeEndian) == elf::PT_LOAD) + { + let voff = ph.p_vaddr(NativeEndian) % ph.p_align(NativeEndian); + let vaddr = (ph.p_vaddr(NativeEndian) - voff) as usize; + let vsize = ((ph.p_memsz(NativeEndian) + voff) as usize) + .next_multiple_of(ph.p_align(NativeEndian) as usize); + + let mut prot = 0; + if ph.p_flags(NativeEndian) & elf::PF_R == elf::PF_R { + prot |= sys_mman::PROT_READ; + } + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if ph.p_flags(NativeEndian) & elf::PF_X == elf::PF_X { + prot |= sys_mman::PROT_EXEC; + } else if ph.p_flags(NativeEndian) & elf::PF_W == elf::PF_W { + prot |= sys_mman::PROT_WRITE; + } + + unsafe { + let ptr = if self.pie { + self.mmap.as_ptr().add(vaddr) + } else { + vaddr as *const u8 + }; + log::trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); + Sys::mprotect(ptr as *mut c_void, vsize, prot).expect("[ld.so]: mprotect failed"); + } + } + + Ok(()) + } +} + +impl Drop for DSO { + fn drop(&mut self) { + if self.is_ready.load(Ordering::SeqCst) { + // `run_fini` should not be called if we are being prematurely + // dropped (e.g. failed to satisfy dependencies). + self.run_fini(); + } + unsafe { Sys::munmap(self.mmap.as_ptr() as *mut c_void, self.mmap.len()).unwrap() }; + } +} + +fn is_pie_enabled(elf: &ElfFile) -> bool { + elf.elf_header().e_type.get(elf.endian()) == elf::ET_DYN +} + +fn basename(path: &str) -> String { + path.split("/").last().unwrap_or(path).to_string() +} + +fn dirname(path: &str) -> String { + let mut parts: Vec<&str> = path.split("/").collect(); + parts.truncate(parts.len() - 1); + parts.join("/") +} + +pub fn resolve_sym<'a>( + name: &'a str, + scopes: &[&'a Scope], +) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { + scopes.iter().find_map(|scope| scope.get_sym(name)) +} + +#[repr(C)] +struct TlsDescriptor { + module_id: usize, + addend: usize, +} + +#[cfg(target_arch = "x86_64")] +#[unsafe(naked)] +unsafe extern "C" fn __tlsdesc_static() { + core::arch::naked_asm!("mov rax, [rax + 8]", "ret") +} + +#[cfg(target_arch = "x86")] +#[unsafe(naked)] +unsafe extern "C" fn __tlsdesc_static() { + core::arch::naked_asm!("mov eax, [eax + 4]", "ret") +} + +#[cfg(target_arch = "aarch64")] +#[unsafe(naked)] +unsafe extern "C" fn __tlsdesc_static() { + core::arch::naked_asm!("ldr x0, [x0, #8]", "ret") +} + +#[cfg(target_arch = "riscv64")] +#[unsafe(naked)] +unsafe extern "C" fn __tlsdesc_static() { + core::arch::naked_asm!("ld a0, 8(a0)", "ret"); +} + +unsafe extern "C" { + fn __tlsdesc_dynamic(); +} + +#[cfg(target_arch = "x86_64")] +core::arch::global_asm!( + " +.global __tlsdesc_dynamic +.hidden __tlsdesc_dynamic +__tlsdesc_dynamic: + push rbx + push rcx + + mov rax, [rax + 8] // TLS descriptor + mov rbx, [rax + {TLS_DESCRIPTOR_MODULE_ID_OFF}] // tls_descriptor.module_id + mov rcx, [rax + {TLS_DESCRIPTOR_ADDEND_OFF}] // tls_descriptor.addend + mov rax, qword ptr fs:[{DTV_PTR_OFF}] // tcb.dtv_ptr + + // tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend + mov rax, [rax + rbx * 8] + add rax, rcx + sub rax, qword ptr fs:[{TCB_SELF_PTR_OFF}] + + pop rcx + pop rbx + + ret +", + TLS_DESCRIPTOR_MODULE_ID_OFF = const offset_of!(TlsDescriptor, module_id), + TLS_DESCRIPTOR_ADDEND_OFF = const offset_of!(TlsDescriptor, addend), + DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr), + TCB_SELF_PTR_OFF = const offset_of!(Tcb, generic.tcb_ptr), +); + +#[cfg(target_arch = "x86")] +core::arch::global_asm!( + " +.global __tlsdesc_dynamic +.hidden __tlsdesc_dynamic +__tlsdesc_dynamic: + ud2 +" +); + +#[cfg(target_arch = "aarch64")] +core::arch::global_asm!( + " +.global __tlsdesc_dynamic +.hidden __tlsdesc_dynamic +__tlsdesc_dynamic: + stp x1, x2, [sp, #-16]! + + ldr x0, [x0, #8] // TLS descriptor + + // x0 := tls_descriptor.module_id + // x1 := tls_descriptor.addend + ldp x0, x1, [x0] + + mrs x2, tpidr_el0 // ABI ptr + ldr x2, [x2] // TCB ptr + + sub x1, x1, x2 // tls_descriptor.addend -= tcb + + ldr x2, [x2, {DTV_PTR_OFF}] // tcb.dtv_ptr + ldr x2, [x2, x0, lsl #3] // tcb.dtv_ptr[tls_descriptor.module_id] + add x0, x2, x1 // tcb.dtv_ptr[tls_descriptor.module_id] + tls_descriptor.addend + + ldp x1, x2, [sp], #16 + ret +", + DTV_PTR_OFF = const offset_of!(Tcb, dtv_ptr), +); + +#[cfg(target_arch = "riscv64")] +core::arch::global_asm!( + " +.global __tlsdesc_dynamic +.hidden __tlsdesc_dynamic +__tlsdesc_dynamic: + unimp +" +); + +/// Applies [`DT_RELR`] relative relocations. +pub unsafe fn apply_relr(base: *const u8, relr: &[Relr]) { + let mut addr = ptr::null_mut(); + for &entry in relr { + if entry & 1 == 0 { + // An even entry sets up `addr` for subsequent odd entries. + unsafe { + addr = base.add(entry) as *mut usize; + *addr += base as usize; + addr = addr.add(1); + } + } else { + // An odd entry indicates a bitmap describing at maximum 63 + // (for 64-bit) or 31 (for 32-bit) locations following `addr`. + // Odd entries can be chained. + let mut entry = entry >> 1; + let mut i = 0; + while entry != 0 { + if entry & 1 != 0 { + unsafe { + *addr.add(i) += base as usize; + } + } + entry >>= 1; + i += 1; + } + + addr = unsafe { addr.add(CHAR_BITS * size_of::() - 1) }; + } + } +} diff --git a/src/ld_so/library.rs b/src/ld_so/library.rs deleted file mode 100644 index 19862f7e3d..0000000000 --- a/src/ld_so/library.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::linker::Symbol; -use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, - string::String, - vec::Vec, -}; - -#[derive(Default, Debug)] -pub struct DepTree { - pub name: String, - pub deps: Vec, -} - -impl DepTree { - pub fn new(name: String) -> DepTree { - DepTree { - name, - deps: Vec::new(), - } - } -} - -/// Use to represnt a library as well as all th symbols that is loaded withen it. -#[derive(Default)] -pub struct Library { - /// Global symbols - pub globals: BTreeMap, - /// Weak symbols - pub weak_syms: BTreeMap, - /// Loaded library raw data - pub objects: BTreeMap>, - /// Loaded library in-memory data - pub mmaps: BTreeMap, - /// Each object will have its children called once with no repetition. - pub dep_tree: DepTree, - /// A set used to detect circular dependencies in the Linker::load function - pub cir_dep: BTreeSet, - pub runpath: Option, -} -impl Library { - pub fn new() -> Library { - Default::default() - } - pub fn get_sym(&self, name: &str) -> Option { - if let Some(value) = self.globals.get(name) { - Some(*value) - } else if let Some(value) = self.weak_syms.get(name) { - Some(*value) - } else { - None - } - } -} diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index bf3b65ba34..4a01b0d9b1 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -1,1041 +1,1229 @@ use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, + collections::BTreeMap, rc::Rc, string::{String, ToString}, + sync::{Arc, Weak}, vec::Vec, }; +use object::elf; +#[cfg(not(target_arch = "x86"))] +use object::{ + NativeEndian, + read::elf::{Rela as _, Sym}, +}; + use core::{ cell::RefCell, - mem::{size_of, swap, transmute}, - ptr, slice, -}; -use goblin::{ - elf::{ - header::ET_DYN, - program_header, - r#dyn::{Dyn, DT_DEBUG, DT_RUNPATH}, - reloc, sym, Elf, - }, - error::{Error, Result}, + ptr::{self, NonNull}, }; use crate::{ - c_str::CString, - fs::File, - header::{errno::STR_ERROR, fcntl, sys_mman, unistd}, - io::Read, - platform::{errno, types::c_void}, + ALLOCATOR, + c_str::{CStr, CString}, + error::Errno, + header::{ + dl_tls::{__tls_get_addr, dl_tls_index}, + fcntl, sys_mman, + unistd::F_OK, + }, + ld_so::dso::SymbolBinding, + out::Out, + platform::{ + Pal, Sys, + types::{c_int, c_void}, + }, + sync::rwlock::RwLock, }; +#[cfg(feature = "ld_so_cache")] +use crate::header::sys_stat::stat; + +#[cfg(not(target_arch = "x86"))] +use crate::{ld_so::dso::resolve_sym, platform::types::c_uint}; + +#[cfg(not(target_arch = "x86"))] +use super::dso::Rela; use super::{ + PATH_SEP, access::accessible, callbacks::LinkerCallbacks, - debug::{RTLDDebug, RTLDState, _dl_debug_state, _r_debug}, - library::{DepTree, Library}, + debug::{_dl_debug_state, _r_debug, RTLDState}, + dso::{DSO, ProgramHeader}, tcb::{Master, Tcb}, - PAGE_SIZE, }; -#[cfg(target_os = "redox")] -pub const PATH_SEP: char = ';'; -#[cfg(target_os = "linux")] -pub const PATH_SEP: char = ':'; - -pub struct DSO { - pub name: String, - pub base_addr: usize, - pub entry_point: usize, +#[derive(Debug, Copy, Clone)] +pub enum DlError { + /// Failed to locate the requested DSO. + NotFound, + /// The DSO is malformed somehow. + Malformed, + /// Invalid DSO handle. + InvalidHandle, + /// Out of memory. + Oom, } -#[derive(Clone, Copy, Debug)] -pub struct Symbol { +impl DlError { + /// Returns a human-readable, null-terminated C string describing the error. + pub const fn repr(&self) -> &'static core::ffi::CStr { + match self { + DlError::NotFound => { + c"Failed to locate the requested DSO. Set `LD_DEBUG=all` for more information." + } + + DlError::Malformed => { + c"The DSO is malformed somehow. Set `LD_DEBUG=all` for more information." + } + + DlError::InvalidHandle => { + c"Invalid DSO handle. Set `LD_DEBUG=all` for more information." + } + + DlError::Oom => c"Out of memory.", + } + } +} + +pub type Result = core::result::Result; + +pub(super) static GLOBAL_SCOPE: RwLock = RwLock::new(Scope::global()); + +struct MmapFile { + fd: i32, + ptr: *mut c_void, + size: usize, +} + +impl MmapFile { + fn open(path: CStr, oflag: c_int) -> core::result::Result { + let fd = Sys::open(path, oflag, 0 /* mode */)?; + let mut stat = crate::header::sys_stat::stat::default(); + Sys::fstat(fd, Out::from_mut(&mut stat))?; + + Self::from_fd(fd, stat.st_size as usize) + } + + fn from_fd(fd: i32, size: usize) -> core::result::Result { + let ptr = unsafe { + Sys::mmap( + ptr::null_mut(), + size, + sys_mman::PROT_READ, + sys_mman::MAP_PRIVATE, + fd, + 0, + ) + }?; + + Ok(Self { fd, ptr, size }) + } + + fn anonymous(size: usize) -> core::result::Result { + let ptr = unsafe { + Sys::mmap( + ptr::null_mut(), + size, + sys_mman::PROT_READ | sys_mman::PROT_WRITE, + sys_mman::MAP_PRIVATE | sys_mman::MAP_ANONYMOUS, + -1, + 0, + ) + }?; + + Ok(Self { fd: -1, ptr, size }) + } + + fn data(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.ptr.cast::(), self.size) } + } + + fn as_mut_slice(&self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.ptr.cast::(), self.size) } + } +} + +impl Drop for MmapFile { + fn drop(&mut self) { + unsafe { + Sys::munmap(self.ptr, self.size).unwrap(); + if self.fd != -1 { + Sys::close(self.fd).unwrap(); + } + } + } +} + +#[derive(Clone, Debug)] +pub struct Symbol<'a> { + pub name: &'a str, pub value: usize, pub base: usize, pub size: usize, + pub sym_type: u8, } -impl Symbol { - pub fn as_ptr(self) -> *mut c_void { + +impl Symbol<'_> { + pub fn as_ptr(&self) -> *mut c_void { (self.base + self.value) as *mut c_void } } +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub enum Resolve { + /// Resolve all undefined symbols immediately. + #[cfg_attr(not(target_arch = "x86_64"), default)] + Now, + /// Perform lazy binding (i.e. symbols will be resolved when they are first + /// used). + #[cfg_attr(target_arch = "x86_64", default)] + Lazy, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ScopeKind { + Global, + Local, +} + +pub enum Scope { + /// The global scope initially contains the main program and all of its + /// dependencies. Additional objects will be added to this scope via + /// `dlopen(2)` if the `RTLD_GLOBAL` flag is set. + Global { objs: Vec> }, + Local { + owner: Option>, + objs: Vec>, + }, +} + +impl Scope { + #[inline] + const fn global() -> Self { + Self::Global { objs: Vec::new() } + } + + #[inline] + const fn local() -> Self { + Self::Local { + owner: None, + objs: Vec::new(), + } + } + + fn set_owner(&mut self, obj: Weak) { + match self { + Self::Global { .. } => panic!("attempted to set global scope owner"), + Self::Local { owner, .. } => { + assert!(owner.is_none(), "attempted to change local scope owner"); + *owner = Some(obj); + } + } + } + + fn add(&mut self, target: &Arc) { + match self { + Self::Global { objs } => { + let target = Arc::downgrade(target); + for obj in objs.iter() { + if Weak::ptr_eq(obj, &target) { + return; + } + } + + objs.push(target); + } + + Self::Local { objs, .. } => { + for obj in objs.iter() { + if Arc::ptr_eq(obj, target) { + return; + } + } + + objs.push(target.clone()); + } + } + } + + pub(super) fn get_sym<'a>( + &self, + name: &'a str, + ) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { + self._get_sym(name, 0) + } + + pub(super) fn _get_sym<'a>( + &self, + name: &'a str, + skip: usize, + ) -> Option<(Symbol<'a>, SymbolBinding, Arc)> { + let mut res = None; + + let get_sym = |obj: Arc| { + if let Some((sym, binding)) = obj.get_sym(name) { + if binding.is_global() { + return Some((sym, binding, obj.clone())); + } + + res = Some((sym, binding, obj.clone())); + } + + None + }; + + match self { + Self::Global { objs } => objs + .iter() + .skip(skip) + .map(|o| o.upgrade().unwrap()) + .find_map(get_sym), + Self::Local { owner, objs } => { + let owner = owner + .as_ref() + .expect("local scope without owner") + .upgrade() + .expect("local scope owner was dropped"); + + core::iter::once(owner) + .chain(objs.iter().cloned()) + .skip(skip) + .find_map(get_sym) + } + } + .or(res) + } + + fn copy_into(&self, other: &mut Self) { + match (self, other) { + (Self::Local { owner, objs }, Self::Global { objs: other_objs }) => { + // FIXME: may have duplicates + let owner = owner.as_ref().expect("local scope without owner"); + other_objs.push(owner.clone()); + other_objs.extend(objs.iter().map(Arc::downgrade)); + } + + _ => unreachable!(), + } + } + + fn debug(&self) { + match self { + Self::Global { objs } => { + println!( + "[@global] {:?}", + objs.iter() + .map(|x| x.upgrade().unwrap().name.clone()) + .collect::>() + ); + } + + Self::Local { owner, objs } => { + let owner = owner.as_ref().unwrap().upgrade().unwrap(); + println!( + "[{}] {:?}", + owner.name, + objs.iter().map(|x| x.name.clone()).collect::>() + ) + } + } + } +} + +// Used by dlfcn.h +// +// We need this as the handle must be created and destroyed with the dynamic +// linker's allocator. +pub struct ObjectHandle(*const DSO); + +impl ObjectHandle { + #[inline] + fn new(obj: Arc) -> Self { + Self(Arc::into_raw(obj)) + } + + #[inline] + fn into_inner(self) -> Arc { + unsafe { Arc::from_raw(self.0) } + } + + #[inline] + pub fn as_ptr(&self) -> *const c_void { + self.0.cast() + } + + #[inline] + pub fn from_ptr(ptr: *const c_void) -> Option { + NonNull::new(ptr as *mut DSO).map(|ptr| Self(ptr.as_ptr())) + } +} + +impl AsRef for ObjectHandle { + #[inline] + fn as_ref(&self) -> &DSO { + unsafe { &*self.0 } + } +} + +bitflags::bitflags! { + #[derive(Debug, Default)] + pub struct DebugFlags: u32 { + /// Display what objects and where they are being loaded. + const LOAD = 1 << 1; + /// Display library search paths. + const SEARCH = 1 << 2; + /// Display scope information. + const SCOPES = 1 << 3; + } +} + +#[derive(Default)] +pub struct Config { + pub debug_flags: DebugFlags, + library_path: Option, + /// Resolve symbols at program startup. + bind_now: bool, +} + +impl Config { + pub fn from_env(env: &BTreeMap) -> Self { + let debug_flags = env + .get("LD_DEBUG") + .map(|value| { + let mut flags = DebugFlags::empty(); + for opt in value.split(',') { + flags |= match opt { + "load" => DebugFlags::LOAD, + "search" => DebugFlags::SEARCH, + "scopes" => DebugFlags::SCOPES, + "all" => DebugFlags::all(), + _ => { + eprintln!("[ld.so]: unknown debug flag '{}'", opt); + DebugFlags::empty() + } + }; + } + + flags + }) + .unwrap_or(DebugFlags::empty()); + + Self { + debug_flags, + library_path: env.get("LD_LIBRARY_PATH").cloned(), + bind_now: env + .get("LD_BIND_NOW") + .map(|value| !value.is_empty()) + .unwrap_or_default(), + } + } +} + pub struct Linker { - // Used by load - /// Library path to search when loading library by name - default_library_path: String, - ld_library_path: Option, - root: Library, - verbose: bool, - tls_index_offset: usize, - lib_spaces: BTreeMap, - counter: usize, + config: Config, + + next_object_id: usize, + next_tls_module_id: usize, + tls_size: usize, + objects: BTreeMap>, + name_to_object_id_map: BTreeMap, pub cbs: Rc>, } -const root_id: usize = 1; +const ROOT_ID: usize = 1; impl Linker { - pub fn new(ld_library_path: Option, verbose: bool) -> Self { + pub fn new(config: Config) -> Self { Self { - default_library_path: "/lib".to_string(), - ld_library_path: ld_library_path, - root: Library::new(), - verbose, - tls_index_offset: 0, - lib_spaces: BTreeMap::new(), - counter: root_id + 1, + config, + next_object_id: ROOT_ID, + next_tls_module_id: 1, + tls_size: 0, + objects: BTreeMap::new(), + name_to_object_id_map: BTreeMap::new(), cbs: Rc::new(RefCell::new(LinkerCallbacks::new())), } } - pub fn load(&mut self, name: &str, path: &str) -> Result<()> { - let mut lib: Library = Library::new(); - swap(&mut lib, &mut self.root); - lib.dep_tree = self.load_recursive(name, path, &mut lib)?; - swap(&mut lib, &mut self.root); - if self.verbose { - println!("Dep tree: {:#?}", self.root.dep_tree); - } - return Ok(()); - } - pub fn unload(&mut self, libspace: usize) { - if let Some(lib) = self.lib_spaces.remove(&libspace) { - for (_, (_, mmap)) in lib.mmaps { - unsafe { sys_mman::munmap(mmap.as_mut_ptr() as *mut c_void, mmap.len()) }; - } - } - } - fn load_recursive(&mut self, name: &str, path: &str, lib: &mut Library) -> Result { - if self.verbose { - println!("load {}: {}", name, path); - } - if lib.cir_dep.contains(name) { - return Err(Error::Malformed(format!( - "Circular dependency: {} is a dependency of itself", - name - ))); - } - let mut deps = DepTree::new(name.to_string()); - let mut data = Vec::new(); - lib.cir_dep.insert(name.to_string()); - let path_c = CString::new(path) - .map_err(|err| Error::Malformed(format!("invalid path '{}': {}", path, err)))?; - - { - let flags = fcntl::O_RDONLY | fcntl::O_CLOEXEC; - let mut file = File::open(&path_c, flags) - .map_err(|err| Error::Malformed(format!("failed to open '{}': {}", path, err)))?; - - file.read_to_end(&mut data) - .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?; - } - deps.deps = self.load_data(name, data.into_boxed_slice(), lib)?; - lib.cir_dep.remove(name); - Ok(deps) + pub fn load_program(&mut self, path: &str, base_addr: Option) -> Result { + let dso = self.load_object( + path, + &None, + base_addr, + false, + if self.config.bind_now { + Resolve::Now + } else { + Resolve::default() + }, + ScopeKind::Global, + )?; + Ok(dso.entry_point) } - fn load_data( + pub fn load_library( &mut self, - name: &str, - data: Box<[u8]>, - lib: &mut Library, - ) -> Result> { - let elf = Elf::parse(&data)?; - //println!("{:#?}", elf); + name: Option<&str>, + resolve: Resolve, + scope: ScopeKind, + noload: bool, + ) -> Result { + log::trace!( + "[ld.so] load_library(name={:?}, resolve={:#?}, scope={:#?}, noload={})", + name, + resolve, + scope, + noload + ); - // search for RUNPATH - lib.runpath = if let Some(dynamic) = elf.dynamic { - let entry = dynamic.dyns.iter().find(|d| d.d_tag == DT_RUNPATH); - match entry { - Some(entry) => { - let path = elf - .dynstrtab - .get(entry.d_val as usize) - .ok_or(Error::Malformed("Missing RUNPATH in dynstrtab".to_string()))??; - Some(path.to_string()) - } - _ => None, - } - } else { - None - }; - - let mut deps = Vec::new(); - for library in elf.libraries.iter() { - if let Some(dep) = self._load_library(library, lib)? { - deps.push(dep); - } + if noload && resolve == Resolve::Now { + // Do not perform lazy binding anymore. + // * Check if loaded with Resolve::Now and if so, early return. + // * If not, resolve all symbols now. + todo!("resolve symbols now!"); } - let key = match elf.soname { - Some(soname) => soname, - _ => name, - }; - if !lib.objects.contains_key(key) { - lib.objects.insert(key.to_string(), data); - } - return Ok(deps); - } - pub fn load_library(&mut self, name: Option<&str>) -> Result { match name { Some(name) => { - let mut lib = Library::new(); - self._load_library(name, &mut lib)?; - let ret = self.counter; - self.lib_spaces.insert(ret, lib); - self.counter += 1; - return Ok(ret); + if let Some(id) = self.name_to_object_id_map.get(name) { + let obj = self.objects.get(id).unwrap(); + + // We may be upgrading the object from a local scope to the + // global scope. + if scope == ScopeKind::Global { + if self.config.debug_flags.contains(DebugFlags::SCOPES) { + eprintln!("[ld.so]: moving {} into the global scope", obj.name); + } + + { + let mut global_scope = GLOBAL_SCOPE.write(); + obj.scope().copy_into(&mut global_scope); + } + self.scope_debug(); + } + + Ok(ObjectHandle::new(obj.clone())) + } else if !noload { + let parent_runpath = &self + .objects + .get(&ROOT_ID) + .and_then(|parent| parent.runpath().map(|path| path.to_string())); + + Ok(ObjectHandle::new(self.load_object( + name, + parent_runpath, + None, + true, + if self.config.bind_now { + Resolve::Now + } else { + resolve + }, + scope, + )?)) + } else { + // FIXME: LoadError? + // Err(Error::Malformed(format!( + // "object '{}' has not yet been loaded", + // name + // ))) + Ok(ObjectHandle(ptr::null())) + } } - None => return Ok(root_id), + + None => match self.objects.get(&ROOT_ID) { + Some(obj) => Ok(ObjectHandle::new(obj.clone())), + None => Err(DlError::NotFound), + }, } } - fn _load_library(&mut self, name: &str, lib: &mut Library) -> Result> { - if lib.objects.contains_key(name) || self.root.objects.contains_key(name) { - // It should be previously resolved so we don't need to worry about it - Ok(None) - } else if name.contains('/') { - Ok(Some(self.load_recursive(name, name, lib)?)) + + pub fn get_sym(&self, handle: Option, name: &str) -> Option<*mut c_void> { + let guard; + + if let Some(handle) = handle.as_ref() { + handle.as_ref().scope() } else { - let mut paths = Vec::new(); - if let Some(ld_library_path) = &self.ld_library_path { - paths.push(ld_library_path); - } - if let Some(runpath) = &lib.runpath { - paths.push(runpath); - } - paths.push(&self.default_library_path); - for part in paths.iter() { - let path = if part.is_empty() { - format!("./{}", name) - } else { - format!("{}/{}", part, name) - }; - if self.verbose { - println!("check {}", path); - } - - if accessible(&path, unistd::F_OK) == 0 { - return Ok(Some(self.load_recursive(name, &path, lib)?)); - } - } - - Err(Error::Malformed(format!("failed to locate '{}'", name))) + guard = GLOBAL_SCOPE.read(); + &guard } + .get_sym(name) + .map(|(symbol, _, obj)| { + if symbol.sym_type != elf::STT_TLS { + symbol.as_ptr() + } else { + let mut tls_index = dl_tls_index { + ti_module: obj.tls_module_id, + ti_offset: symbol.value, + }; + + unsafe { __tls_get_addr(&raw mut tls_index) } + } + }) } - fn collect_syms( - elf: &Elf, - mmap: &[u8], - verbose: bool, - ) -> Result<(BTreeMap, BTreeMap)> { - let mut globals = BTreeMap::new(); - let mut weak_syms = BTreeMap::new(); - for sym in elf.dynsyms.iter() { - let bind = sym.st_bind(); - if sym.st_value == 0 || ![sym::STB_GLOBAL, sym::STB_WEAK].contains(&bind) { - continue; - } - let name: String; - let value: Symbol; - if let Some(name_res) = elf.dynstrtab.get(sym.st_name) { - name = name_res?.to_string(); - value = if is_pie_enabled(elf) { - Symbol { - base: mmap.as_ptr() as usize, - value: sym.st_value as usize, - size: sym.st_size as usize, - } - } else { - Symbol { - base: 0, - value: sym.st_value as usize, - size: sym.st_size as usize, - } - }; - } else { - continue; - } - match sym.st_bind() { - sym::STB_GLOBAL => { - if verbose { - println!(" global {}: {:x?} = {:p}", &name, sym, value.as_ptr()); - } - globals.insert(name, value); - } - sym::STB_WEAK => { - if verbose { - println!(" weak {}: {:x?} = {:p}", &name, sym, value.as_ptr()); - } - weak_syms.insert(name, value); + pub fn unload(&mut self, handle: ObjectHandle) { + let obj = handle.into_inner(); + if !obj.dlopened { + return; + } + + log::trace!( + "[ld.so] unloading {} (sc={}, wc={})", + obj.name, + Arc::strong_count(&obj), + Arc::weak_count(&obj) + ); + + // One for the reference we have and the other for the one in the + // objects map. + if Arc::strong_count(&obj) == 2 { + // Remove from the global scope. + match *GLOBAL_SCOPE.write() { + Scope::Global { ref mut objs } => { + objs.retain(|o| !Weak::ptr_eq(o, &Arc::downgrade(&obj))); } + _ => unreachable!(), } - } - return Ok((globals, weak_syms)); - } - pub fn get_sym(&self, name: &str, libspace: Option) -> Option { - match libspace { - None | Some(root_id) => self.root.get_sym(name), - Some(id) => { - let lib = self.lib_spaces.get(&id)?; - lib.get_sym(name) - } - } - } - - pub fn run_init(&self, libspace: Option) -> Result<()> { - match libspace { - Some(id) => { - let lib = self.lib_spaces.get(&id).unwrap(); - self.run_tree(&lib, &lib.dep_tree, ".init_array") - } - None => self.run_tree(&self.root, &self.root.dep_tree, ".init_array"), - } - } - - pub fn run_fini(&self, libspace: Option) -> Result<()> { - match libspace { - Some(root_id) => return Ok(()), - Some(id) => { - let lib = self.lib_spaces.get(&id).unwrap(); - self.run_tree(&lib, &lib.dep_tree, ".fini_array") - } - None => { - //TODO we first need to deinitialize all the loaded libraries first! - self.run_tree(&self.root, &self.root.dep_tree, ".fini_array") - } - } - } - - fn run_tree(&self, lib: &Library, root: &DepTree, tree_name: &str) -> Result<()> { - for node in root.deps.iter() { - self.run_tree(lib, node, tree_name)?; - } - if self.verbose { - println!("running {} {}", tree_name, &root.name); - } - let (_, mmap) = match lib.mmaps.get(&root.name) { - Some(some) => some, - None => return Ok(()), - }; - let elf = Elf::parse(lib.objects.get(&root.name).unwrap())?; - for section in &elf.section_headers { - let name = match elf.shdr_strtab.get(section.sh_name) { - Some(x) => match x { - Ok(y) => y, - _ => continue, - }, - _ => continue, - }; - if name == tree_name { - let addr = if is_pie_enabled(&elf) { - mmap.as_ptr() as usize + section.vm_range().start - } else { - section.vm_range().start - }; - for i in (0..section.sh_size).step_by(8) { - unsafe { call_inits_finis(addr + i as usize) }; + let _ = self.objects.remove(&obj.id).unwrap(); + for dep in obj.dependencies() { + if let Some(name) = self.name_to_object_id_map.get(*dep) + && let Some(object_name) = self.objects.get(name) + { + self.unload(ObjectHandle::new(object_name.clone())); } } + self.name_to_object_id_map.remove(&obj.name); + assert!(Arc::strong_count(&obj) == 1); + drop(obj); } - return Ok(()); + + // obj is dropped here. } - pub fn link( + pub fn fini(&self) { + for obj in self.objects.values() { + obj.run_fini(); + } + } + + fn load_object( &mut self, - primary_opt: Option<&str>, - dso: Option, - libspace: Option, - ) -> Result> { - match libspace { - Some(id) => { - let mut lib = self.lib_spaces.remove(&id).unwrap(); - let res = self._link(primary_opt, dso, &mut lib); - self.lib_spaces.insert(id, lib); - res - } - None => { - let mut lib = Library::new(); - swap(&mut lib, &mut self.root); - let res = self._link(primary_opt, dso, &mut lib); - swap(&mut lib, &mut self.root); - res - } - } - } - - pub fn _link( - &mut self, - primary_opt: Option<&str>, - dso: Option, - lib: &mut Library, - ) -> Result> { - unsafe { _r_debug.state = RTLDState::RT_ADD }; - _dl_debug_state(); - let mut skip_list = BTreeSet::new(); - let elfs = { - let mut elfs = BTreeMap::new(); - for (name, data) in lib.objects.iter() { - // Skip already linked libraries - if !lib.mmaps.contains_key(&*name) && !self.root.mmaps.contains_key(&*name) { - elfs.insert(name.as_str(), Elf::parse(&data)?); - } else { - skip_list.insert(name.as_str()); - } - } - elfs - }; - - // Load all ELF files into memory and find all globals - let mut tls_primary = 0; - let mut tls_size = 0; - for (elf_name, elf) in elfs.iter() { - if skip_list.contains(elf_name) { - continue; - } - if self.verbose { - println!("map {}", elf_name); - } - let object = match lib.objects.get(*elf_name) { - Some(some) => some, - None => continue, - }; - // data for struct LinkMap - let mut l_ld = 0; - // Calculate virtual memory bounds - let bounds = { - let mut bounds_opt: Option<(usize, usize)> = None; - for ph in elf.program_headers.iter() { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = - ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - - match ph.p_type { - program_header::PT_DYNAMIC => { - l_ld = ph.p_vaddr; - } - program_header::PT_LOAD => { - if self.verbose { - println!(" load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph); - } - if let Some(ref mut bounds) = bounds_opt { - if vaddr < bounds.0 { - bounds.0 = vaddr; - } - if vaddr + vsize > bounds.1 { - bounds.1 = vaddr + vsize; - } - } else { - bounds_opt = Some((vaddr, vaddr + vsize)); - } - } - program_header::PT_TLS => { - if self.verbose { - println!(" load tls {:#x}: {:x?}", vsize, ph); - } - tls_size += vsize; - if Some(*elf_name) == primary_opt { - tls_primary += vsize; - } - } - _ => (), - } - } - match bounds_opt { - Some(some) => some, - None => continue, - } - }; - if self.verbose { - println!(" bounds {:#x}, {:#x}", bounds.0, bounds.1); - } - // Allocate memory - let mmap = unsafe { - let same_elf = if let Some(prog) = dso.as_ref() { - if prog.name == *elf_name { - true - } else { - false - } - } else { - false - }; - if same_elf { - let addr = dso.as_ref().unwrap().base_addr; - let size = if is_pie_enabled(&elf) { - bounds.1 - } else { - bounds.1 - bounds.0 - }; - - // Fill the gaps i the binary - let mut ranges = Vec::new(); - for ph in elf.program_headers.iter() { - if ph.p_type == program_header::PT_LOAD { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) - * PAGE_SIZE; - if is_pie_enabled(&elf) { - ranges.push((vaddr, vsize)); - } else { - ranges.push((vaddr - addr, vsize)); - } - } - } - ranges.sort(); - let mut start = addr; - for (vaddr, vsize) in ranges.iter() { - if start < addr + vaddr { - if self.verbose { - println!("mmap({:#x}, {})", start, addr + vaddr - start); - } - let mut flags = sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE; - if start != 0 { - flags |= sys_mman::MAP_FIXED_NOREPLACE; - } - let ptr = sys_mman::mmap( - start as *mut c_void, - addr + vaddr - start, - //TODO: Make it possible to not specify PROT_EXEC on Redox - sys_mman::PROT_READ | sys_mman::PROT_WRITE, - flags, - -1, - 0, - ); - if ptr as usize == !0 - /* MAP_FAILED */ - { - return Err(Error::Malformed(format!( - "failed to map {}. errno: {}", - elf_name, STR_ERROR[errno as usize] - ))); - } - if start as *mut c_void != ptr::null_mut() { - assert_eq!( - ptr, start as *mut c_void, - "mmap must always map on the destination we requested" - ); - } - } - start = addr + vaddr + vsize - } - sys_mman::mprotect( - addr as *mut c_void, - size, - sys_mman::PROT_READ | sys_mman::PROT_WRITE, - ); - _r_debug.insert_first(addr as usize, &elf_name, addr + l_ld as usize); - ( - addr as usize, - slice::from_raw_parts_mut(addr as *mut u8, size), - ) - } else { - let (start, end) = bounds; - let size = end - start; - if self.verbose { - println!("mmap({:#x}, {})", start, size); - } - let mut flags = sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE; - if start != 0 { - flags |= sys_mman::MAP_FIXED_NOREPLACE; - } - let ptr = sys_mman::mmap( - start as *mut c_void, - size, - //TODO: Make it possible to not specify PROT_EXEC on Redox - sys_mman::PROT_READ | sys_mman::PROT_WRITE, - flags, - -1, - 0, - ); - if ptr as usize == !0 - /* MAP_FAILED */ - { - return Err(Error::Malformed(format!( - "failed to map {}. errno: {}", - elf_name, STR_ERROR[errno as usize] - ))); - } - if start as *mut c_void != ptr::null_mut() { - assert_eq!( - ptr, start as *mut c_void, - "mmap must always map on the destination we requested" - ); - } - ptr::write_bytes(ptr as *mut u8, 0, size); - _r_debug.insert(ptr as usize, &elf_name, ptr as usize + l_ld as usize); - (start, slice::from_raw_parts_mut(ptr as *mut u8, size)) - } - }; - if self.verbose { - println!(" mmap {:p}, {:#x}", mmap.1.as_mut_ptr(), mmap.1.len()); - } - let (globals, weak_syms) = Linker::collect_syms(&elf, &mmap.1, self.verbose)?; - lib.globals.extend(globals.into_iter()); - lib.weak_syms.extend(weak_syms.into_iter()); - lib.mmaps.insert(elf_name.to_string(), mmap); - } - - // Allocate TLS - let mut tcb_opt = if primary_opt.is_some() { - Some(unsafe { Tcb::new(tls_size)? }) + path: &str, + runpath: &Option, + base_addr: Option, + dlopened: bool, + resolve: Resolve, + scope: ScopeKind, + ) -> Result> { + let resolve = if cfg!(target_arch = "x86_64") { + resolve } else { - None + // Lazy binding is not currently supported on non-x86_64 architectures. + Resolve::Now }; - if self.verbose { - println!("tcb {:x?}", tcb_opt); - } - // Copy data - let mut tls_offset = tls_primary; + + _r_debug.lock().state = RTLDState::RtAdd; + _dl_debug_state(); + + let mut new_objects = Vec::new(); + let mut objects_data = Vec::new(); let mut tcb_masters = Vec::new(); - // Insert main image master - tcb_masters.push(Master { - ptr: ptr::null_mut(), - len: 0, - offset: 0, - }); - let mut tls_ranges = BTreeMap::new(); - for (elf_name, elf) in elfs.iter() { - if skip_list.contains(elf_name) { - continue; - } - let same_elf = if let Some(prog) = dso.as_ref() { - if prog.name == *elf_name { - true - } else { - false + let loaded_dso = self.load_objects_recursive( + path, + runpath, + base_addr, + dlopened, + &mut new_objects, + &mut objects_data, + &mut tcb_masters, + None, + scope, + )?; + + for (i, obj) in new_objects.iter().enumerate() { + obj.relocate(&objects_data[i], resolve).unwrap(); + } + + unsafe { + if !dlopened { + #[cfg(target_os = "redox")] + let (tcb, old_tcb, thr_fd) = { + use redox_rt::signal::tmp_disable_signals; + + let old_tcb = Tcb::current().expect("failed to get bootstrap TCB"); + let thr_fd = (&mut *old_tcb.os_specific.thr_fd.get()) + .take() + .expect("no thread FD present"); + let new_tcb = Tcb::new(self.tls_size)?; // This actually allocates TCB, TLS and ABI page. + + // Stash + let new_tls_end = new_tcb.generic.tls_end; + let new_tls_len = new_tcb.generic.tls_len; + let new_tcb_ptr = new_tcb.generic.tcb_ptr; + let new_tcb_len = new_tcb.generic.tcb_len; + + // Unmap just the TCB page. + Sys::munmap(new_tcb as *mut Tcb as *mut c_void, syscall::PAGE_SIZE).unwrap(); + + let new_addr = ptr::addr_of!(*new_tcb) as usize; + + assert_eq!( + syscall::syscall5( + syscall::SYS_MREMAP, + old_tcb as *mut Tcb as usize, + syscall::PAGE_SIZE, + new_addr, + syscall::PAGE_SIZE, + (syscall::MremapFlags::FIXED | syscall::MremapFlags::KEEP_OLD).bits() + | (syscall::MapFlags::PROT_READ | syscall::MapFlags::PROT_WRITE) + .bits(), + ) + .expect("mremap: failed to alias TCB"), + new_addr, + ); + // XXX: New TCB is now at the same physical address as the old TCB. + + let _guard = tmp_disable_signals(); + // Restore + new_tcb.generic.tls_end = new_tls_end; + new_tcb.generic.tls_len = new_tls_len; + new_tcb.generic.tcb_ptr = new_tcb_ptr; + new_tcb.generic.tcb_len = new_tcb_len; + + drop(_guard); + (new_tcb, old_tcb as *mut Tcb as *mut c_void, thr_fd) + }; + + #[cfg(not(target_os = "redox"))] + let tcb = Tcb::new(self.tls_size)?; + + // We are now loading the main program or its dependencies. The TLS for all initially + // loaded objects reside in the static TLS block. Depending on the architecture, the + // static TLS block is either placed before the TP or after the TP. + // + // Setup the DTVs. + tcb.setup_dtv(tcb_masters.len()); + + for obj in new_objects.iter() { + if obj.tls_module_id == 0 { + // No TLS for this object. + continue; + } + + let dtv_idx = obj.tls_module_id - 1; + + if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // Below the TP + tcb.dtv_mut()[dtv_idx] = tcb.tls_end.sub(obj.tls_offset); + } else { + // FIMXE(andypython): Make it above the TP + // + // tcb.dtv_mut().unwrap()[obj.tls_module_id - 1] = + // tcb_ptr.add(1).cast::().add(obj.tls_offset); + // + // FIXME(andypython): https://gitlab.redox-os.org/redox-os/relibc/-/merge_requests/570#note_35788 + let tls_start = tcb.tls_end.sub(tcb.tls_len); + tcb.dtv_mut()[dtv_idx] = tls_start.add(obj.tls_offset); + } + } + + tcb.append_masters(tcb_masters); + // Copy the master data into the static TLS block. + tcb.copy_masters().map_err(|_| DlError::Malformed)?; + tcb.activate( + #[cfg(target_os = "redox")] + Some(thr_fd), + ); + tcb.mspace = ALLOCATOR.get(); + + #[cfg(target_os = "redox")] + { + // Unmap the old TCB. + Sys::munmap(old_tcb, syscall::PAGE_SIZE).unwrap(); } } else { - false - }; - let object = match lib.objects.get(*elf_name) { - Some(some) => some, - None => continue, - }; + let tcb = Tcb::current().expect("failed to get current tcb"); - let &mut (base_addr, ref mut mmap) = match lib.mmaps.get_mut(*elf_name) { - Some(some) => some, - None => continue, - }; - if self.verbose { - println!("load {}", elf_name); - } - // Copy data - for ph in elf.program_headers.iter() { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - - match ph.p_type { - program_header::PT_LOAD => { - if same_elf { - continue; - } - let obj_data = { - let range = ph.file_range(); - match object.get(range.clone()) { - Some(some) => some, - None => { - return Err(Error::Malformed(format!( - "failed to read {:x?}", - range - ))) - } - } - }; - - let mmap_data = { - let range = ph.p_vaddr as usize - base_addr - ..ph.p_vaddr as usize + obj_data.len() - base_addr; - match mmap.get_mut(range.clone()) { - Some(some) => some, - None => { - println!("mmap: {}", mmap.len()); - return Err(Error::Malformed(format!( - "failed to write {:x?}", - range - ))); - } - } - }; - if self.verbose { - println!( - " copy {:#x}, {:#x}: {:#x}, {:#x}", - vaddr, - vsize, - voff, - obj_data.len() - ); - } - mmap_data.copy_from_slice(obj_data); - } - program_header::PT_TLS => { - let valign = if ph.p_align > 0 { - ((ph.p_memsz + (ph.p_align - 1)) / ph.p_align) * ph.p_align - } else { - ph.p_memsz - } as usize; - let ptr = unsafe { - if is_pie_enabled(elf) { - mmap.as_ptr().add(ph.p_vaddr as usize) - } else { - ph.p_vaddr as *const u8 - } - }; - let mut tcb_master = Master { - ptr: ptr, - len: ph.p_filesz as usize, - offset: tls_size - valign, - }; - if self.verbose { - println!( - " tls master {:p}, {:#x}: {:#x}, {:#x}", - tcb_master.ptr, tcb_master.len, tcb_master.offset, valign, - ); - } - if Some(*elf_name) == primary_opt { - tls_ranges.insert( - elf_name.to_string(), - (self.tls_index_offset, tcb_master.range()), - ); - tcb_masters[0] = tcb_master; - } else { - tcb_master.offset -= tls_offset; - tls_offset += vsize; - tls_ranges.insert( - elf_name.to_string(), - ( - self.tls_index_offset + tcb_masters.len(), - tcb_master.range(), - ), - ); - tcb_masters.push(tcb_master); - } - } - _ => (), - } + // TLS variables for dlopen'ed objects are lazily allocated in `__tls_get_addr`. + tcb.append_masters(tcb_masters); } } - self.tls_index_offset += tcb_masters.len(); - - // Set master images for TLS and copy TLS data - if let Some(ref mut tcb) = tcb_opt { - unsafe { - tcb.set_masters(tcb_masters.into_boxed_slice()); - tcb.copy_masters()?; - } + for obj in new_objects.into_iter() { + obj.mark_ready(); + self.run_init(&obj); + self.register_object(obj); } - // Perform relocations, and protect pages - for (elf_name, elf) in elfs.iter() { - if skip_list.contains(elf_name) { - continue; - } - if self.verbose { - println!("link {}", elf_name); - } - // Relocate - for rel in elf - .dynrelas - .iter() - .chain(elf.dynrels.iter()) - .chain(elf.pltrelocs.iter()) - { - // println!(" rel {}: {:x?}", - // reloc::r_to_str(rel.r_type, elf.header.e_machine), - // rel - // ); - let symbol = if rel.r_sym > 0 { - let sym = elf.dynsyms.get(rel.r_sym).ok_or(Error::Malformed(format!( - "missing symbol for relocation {:?}", - rel - )))?; - - let name = - elf.dynstrtab - .get(sym.st_name) - .ok_or(Error::Malformed(format!( - "missing name for symbol {:?}", - sym - )))??; - lib.get_sym(name).or_else(|| self.root.get_sym(name)) - } else { - None - }; - - let s = symbol - .as_ref() - .map(|sym| sym.as_ptr() as usize) - .unwrap_or(0); - - let a = rel.r_addend.unwrap_or(0) as usize; - - let (_, mmap) = match lib.mmaps.get_mut(*elf_name) { - Some(some) => some, - None => continue, - }; - - let b = mmap.as_mut_ptr() as usize; - - let (tm, t) = if let Some((tls_index, tls_range)) = tls_ranges.get(*elf_name) { - (*tls_index, tls_range.start) - } else { - (0, 0) - }; - - let ptr = if is_pie_enabled(&elf) { - unsafe { mmap.as_mut_ptr().add(rel.r_offset as usize) } - } else { - rel.r_offset as *mut u8 - }; - let set_u64 = |value| { - // println!(" set_u64 {:#x}", value); - unsafe { - *(ptr as *mut u64) = value; - } - }; - - match rel.r_type { - reloc::R_X86_64_64 => { - set_u64((s + a) as u64); - } - reloc::R_X86_64_DTPMOD64 => { - set_u64(tm as u64); - } - reloc::R_X86_64_DTPOFF64 => { - if s != 0 { - set_u64((s - b) as u64); - } else { - set_u64(s as u64); - } - } - reloc::R_X86_64_GLOB_DAT | reloc::R_X86_64_JUMP_SLOT => { - set_u64(s as u64); - } - reloc::R_X86_64_RELATIVE => { - set_u64((b + a) as u64); - } - reloc::R_X86_64_TPOFF64 => { - set_u64((s + a).wrapping_sub(t) as u64); - } - reloc::R_X86_64_IRELATIVE => (), // Handled below - reloc::R_X86_64_COPY => unsafe { - // TODO: Make this work - let sym = symbol - .as_ref() - .expect("R_X86_64_COPY called without valid symbol"); - ptr::copy_nonoverlapping(sym.as_ptr() as *const u8, ptr, sym.size as usize); - }, - _ => { - panic!( - " {} unsupported", - reloc::r_to_str(rel.r_type, elf.header.e_machine) - ); - } - } - } - - // overwrite DT_DEBUG if exist in DYNAMIC segment - // first we identify the location of DYNAMIC segment - let mut dyn_start = None; - let mut debug_start = None; - for ph in elf.program_headers.iter() { - if ph.p_type == program_header::PT_DYNAMIC { - dyn_start = Some(ph.p_vaddr as usize); - } - } - // next we identify the location of DT_DEBUG in .dynamic section - if let Some(dynamic) = elf.dynamic.as_ref() { - let mut i = 0; - for entry in &dynamic.dyns { - if entry.d_tag == DT_DEBUG { - debug_start = Some(i as usize); - break; - } - i += 1; - } - } - if let Some(dyn_start_addr) = dyn_start { - if let Some(i) = debug_start { - let (_, mmap) = match lib.mmaps.get_mut(*elf_name) { - Some(some) => some, - None => continue, - }; - let bytes: [u8; size_of::() / 2] = - unsafe { transmute((&_r_debug) as *const RTLDDebug as usize) }; - let start = if is_pie_enabled(elf) { - dyn_start_addr + i * size_of::() + size_of::() / 2 - } else { - dyn_start_addr + i * size_of::() + size_of::() / 2 - - mmap.as_mut_ptr() as usize - }; - mmap[start..start + size_of::() / 2].clone_from_slice(&bytes); - } - } - - // Protect pages - for ph in elf.program_headers.iter() { - if ph.p_type == program_header::PT_LOAD { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = - ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - - let mut prot = 0; - - if ph.p_flags & program_header::PF_R == program_header::PF_R { - prot |= sys_mman::PROT_READ; - } - - // W ^ X. If it is executable, do not allow it to be writable, even if requested - if ph.p_flags & program_header::PF_X == program_header::PF_X { - prot |= sys_mman::PROT_EXEC; - } else if ph.p_flags & program_header::PF_W == program_header::PF_W { - prot |= sys_mman::PROT_WRITE; - } - - let (_, mmap) = match lib.mmaps.get_mut(*elf_name) { - Some(some) => some, - None => continue, - }; - let res = unsafe { - let ptr = if is_pie_enabled(elf) { - mmap.as_mut_ptr().add(vaddr) - } else { - vaddr as *const u8 - }; - if self.verbose { - println!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); - } - sys_mman::mprotect(ptr as *mut c_void, vsize, prot) - }; - - if res < 0 { - return Err(Error::Malformed(format!("failed to mprotect {}", elf_name))); - } - } - } - } - - // Activate TLS - if let Some(ref mut tcb) = tcb_opt { - unsafe { - tcb.activate(); - } - } - - // Perform indirect relocations (necessary evil), gather entry point - let mut entry_opt = None; - for (elf_name, elf) in elfs.iter() { - if skip_list.contains(elf_name) { - continue; - } - let (_, mmap) = match lib.mmaps.get_mut(*elf_name) { - Some(some) => some, - None => continue, - }; - if self.verbose { - println!("entry {}", elf_name); - } - if Some(*elf_name) == primary_opt { - if is_pie_enabled(&elf) { - entry_opt = Some(mmap.as_mut_ptr() as usize + elf.header.e_entry as usize); - } else { - entry_opt = Some(elf.header.e_entry as usize); - } - } - - // Relocate - for rel in elf - .dynrelas - .iter() - .chain(elf.dynrels.iter()) - .chain(elf.pltrelocs.iter()) - { - // println!(" rel {}: {:x?}", - // reloc::r_to_str(rel.r_type, elf.header.e_machine), - // rel - // ); - - let a = rel.r_addend.unwrap_or(0) as usize; - - let b = mmap.as_mut_ptr() as usize; - - let ptr = unsafe { mmap.as_mut_ptr().add(rel.r_offset as usize) }; - - let set_u64 = |value| { - // println!(" set_u64 {:#x}", value); - unsafe { - *(ptr as *mut u64) = value; - } - }; - - if rel.r_type == reloc::R_X86_64_IRELATIVE { - unsafe { - let f: unsafe extern "C" fn() -> u64 = transmute(b + a); - set_u64(f()); - } - } - } - // Protect pages - for ph in elf.program_headers.iter() { - if let program_header::PT_LOAD = ph.p_type { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = - ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - - let mut prot = 0; - - if ph.p_flags & program_header::PF_R == program_header::PF_R { - prot |= sys_mman::PROT_READ; - } - - // W ^ X. If it is executable, do not allow it to be writable, even if requested - if ph.p_flags & program_header::PF_X == program_header::PF_X { - prot |= sys_mman::PROT_EXEC; - } else if ph.p_flags & program_header::PF_W == program_header::PF_W { - prot |= sys_mman::PROT_WRITE; - } - - let res = unsafe { - let ptr = if is_pie_enabled(&elf) { - mmap.as_mut_ptr().add(vaddr) - } else { - vaddr as *const u8 - }; - if self.verbose { - println!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); - } - sys_mman::mprotect(ptr as *mut c_void, vsize, prot) - }; - - if res < 0 { - return Err(Error::Malformed(format!("failed to mprotect {}", elf_name))); - } - } - } - } - unsafe { _r_debug.state = RTLDState::RT_CONSISTENT }; + _r_debug.lock().state = RTLDState::RtConsistent; _dl_debug_state(); - Ok(entry_opt) + + Ok(loaded_dso) + } + + fn register_object(&mut self, obj: Arc) { + self.name_to_object_id_map.insert(obj.name.clone(), obj.id); + self.objects.insert(obj.id, obj); + } + + /// Loads the specified object and all of its dependencies. + /// + /// `new_objects` contains any new objects that were loaded. Order is + /// reverse of how the scope is populated. + /// + /// The scope is populated such that the loaded objects are in breadth-first + /// order. This means that first the requested object is added to the scope, + /// and then its dependencies are added in the order of their respective + /// `DT_NEEDED` entries in the requested object. This is done recursively + /// until all dependencies have been loaded. + /// + /// If a dependency has already been loaded, it is *not* added to the scope + /// nor to `new_objects`. + #[allow(clippy::too_many_arguments)] + fn load_objects_recursive( + &mut self, + name: &str, + parent_runpath: &Option, + base_addr: Option, + dlopened: bool, + new_objects: &mut Vec>, + objects_data: &mut Vec>, + tcb_masters: &mut Vec, + // Scope of the object that caused this object to be loaded. + dependent_scope: Option<&mut Scope>, + scope_kind: ScopeKind, + ) -> Result> { + // fixme: double lookup slow + if let Some(id) = self.name_to_object_id_map.get(name) { + if let Some(obj) = self.objects.get(id) { + if let Some(scope) = dependent_scope { + match scope_kind { + ScopeKind::Local => scope.add(obj), + ScopeKind::Global => GLOBAL_SCOPE.write().add(obj), + } + } else if scope_kind == ScopeKind::Global { + GLOBAL_SCOPE.write().add(obj); + } + return Ok(obj.clone()); + } + } else if let Some(obj) = new_objects.iter().find(|o| o.name == name) { + if let Some(scope) = dependent_scope { + match scope_kind { + ScopeKind::Local => scope.add(obj), + ScopeKind::Global => GLOBAL_SCOPE.write().add(obj), + } + } else if scope_kind == ScopeKind::Global { + GLOBAL_SCOPE.write().add(obj); + } + return Ok(obj.clone()); + } + + let debug = self.config.debug_flags.contains(DebugFlags::LOAD); + + let path = self.search_object(name, parent_runpath)?; + let file = self.read_file(&path)?; + let data = file.data(); + let (obj, tcb_master, elf) = DSO::new( + &path, + data, + base_addr, + dlopened, + self.next_object_id, + self.next_tls_module_id, + // Ensure TLS is aligned to 16 bytes for SSE + self.tls_size.next_multiple_of(16), + ) + .map_err(|err| { + if debug { + eprintln!("[ld.so]: failed to load '{}': {}", name, err) + } + + DlError::Malformed + })?; + + if debug { + eprintln!( + "[ld.so]: loading object: {} at {:#x}:{:#x} (pie: {})", + name, + obj.mmap.as_ptr() as usize, + obj.mmap.as_ptr() as usize + obj.mmap.len(), + obj.pie, + ); + } + + self.next_object_id += 1; + + if let Some(master) = tcb_master { + if !dlopened { + self.tls_size = master.offset; // => aligned ph.p_memsz + } + + tcb_masters.push(master); + self.next_tls_module_id += 1; + } + + let runpath = obj.runpath().map(|rpath| rpath.to_string()); + let dependencies = obj + .dependencies() + .iter() + .map(|dep| dep.to_string()) + .collect::>(); + + let obj = Arc::new(obj); + let mut scope = Scope::local(); + + if let Some(dependent_scope) = dependent_scope { + match scope_kind { + ScopeKind::Local => dependent_scope.add(&obj), + ScopeKind::Global => GLOBAL_SCOPE.write().add(&obj), + } + } else if let ScopeKind::Global = scope_kind { + GLOBAL_SCOPE.write().add(&obj); + } + + for dep_name in dependencies.iter() { + self.load_objects_recursive( + dep_name, + &runpath, + None, + dlopened, + new_objects, + objects_data, + tcb_masters, + Some(&mut scope), + scope_kind, + )?; + } + + objects_data.push(elf); + new_objects.push(obj.clone()); + + scope.set_owner(Arc::downgrade(&obj)); + obj.scope.call_once(|| scope); + + Ok(obj) + } + + fn search_object(&self, name: &str, parent_runpath: &Option) -> Result { + let debug = self.config.debug_flags.contains(DebugFlags::SEARCH); + if debug { + eprintln!("[ld.so]: looking for '{}'", name); + } + + let mut full_path = name.to_string(); + if accessible(&full_path, F_OK).is_ok() { + if debug { + eprintln!("[ld.so]: found at '{}'!", full_path); + } + return Ok(full_path); + } else { + let mut search_paths = Vec::new(); + if let Some(runpath) = parent_runpath { + search_paths.extend(runpath.split(PATH_SEP)); + } + if let Some(ld_path) = self.config.library_path.as_ref() { + search_paths.extend(ld_path.split(PATH_SEP)); + } + search_paths.push("/lib"); + for part in search_paths.iter() { + full_path = format!("{}/{}", part, name); + if debug { + eprintln!("[ld.so]: trying path '{}'", full_path); + } + if accessible(&full_path, F_OK).is_ok() { + if debug { + eprintln!("[ld.so]: found at '{}'!", full_path); + } + return Ok(full_path); + } + } + } + + if debug { + eprintln!("[ld.so]: failed to locate '{}'", name); + } + + Err(DlError::NotFound) + } + + fn read_file(&self, path: &str) -> Result { + let debug = self.config.debug_flags.contains(DebugFlags::SEARCH); + + let path_c = CString::new(path).map_err(|err| { + if debug { + eprintln!("[ld.so]: invalid path '{}': {}", path, err) + } + + DlError::NotFound + })?; + + // TODO: Caches may silently fail within multiple users (try to leverage capabilities?) + // TODO: No way to specify weak cache or pruning the cache manually + + #[cfg(feature = "ld_so_cache")] + let file = { + let mut mtime_sec = 0; + let mut mtime_nsec = 0; + let mut source_size = 0; + + let src_fd = Sys::open(CStr::borrow(&path_c), fcntl::O_RDONLY | fcntl::O_CLOEXEC, 0) + .map_err(|err| { + if debug { + eprintln!("[ld.so]: failed to open '{}': {}", path, err) + } + DlError::NotFound + })?; + let mut st = stat::default(); + if Sys::fstat(src_fd, Out::from_mut(&mut st)).is_ok() { + mtime_sec = st.st_mtim.tv_sec; + mtime_nsec = st.st_mtim.tv_nsec; + source_size = st.st_size as usize; + } + + let shm_path_str = format!( + "/scheme/shm/ld.so.cache.{}.{}.{}\0", + path.replace('/', "_"), + mtime_sec, + mtime_nsec + ); + let shm_path = unsafe { CStr::from_bytes_with_nul_unchecked(shm_path_str.as_bytes()) }; + + let mut shm_exists = false; + + if let Ok(shm_fd) = Sys::open(shm_path, fcntl::O_RDONLY, 0) { + let mut shm_stat = stat::default(); + if Sys::fstat(shm_fd, Out::from_mut(&mut shm_stat)).is_ok() { + shm_exists = true; + if shm_stat.st_size > 0 { + if let Ok(mmap_file) = MmapFile::anonymous(source_size) { + let mut offset = 0; + let buf = mmap_file.as_mut_slice(); + while offset < source_size { + match Sys::read(shm_fd, &mut buf[offset..]) { + Ok(0) | Err(_) => break, + Ok(n) => offset += n, + } + } + let _ = Sys::close(shm_fd); + return Ok(mmap_file); + } + let _ = Sys::close(shm_fd); + } + } + let _ = Sys::close(shm_fd); + } + + let file = MmapFile::from_fd(src_fd, source_size).map_err(|err| { + if debug { + eprintln!("[ld.so]: failed to map '{}': {}", path, err) + } + DlError::NotFound + })?; + + if !shm_exists { + let _ = Sys::open(shm_path, fcntl::O_CREAT | fcntl::O_RDWR, 0o600).map(Sys::close); + } else if let Ok(shm_fd) = Sys::open(shm_path, fcntl::O_RDWR, 0o600) { + let _ = Sys::ftruncate(shm_fd, source_size as i64); + let _ = Sys::write(shm_fd, file.data()); + let _ = Sys::close(shm_fd); + } + + file + }; + #[cfg(not(feature = "ld_so_cache"))] + let file = { + let flags = fcntl::O_RDONLY | fcntl::O_CLOEXEC; + MmapFile::open(CStr::borrow(&path_c), flags).map_err(|err| { + if debug { + eprintln!("[ld.so]: failed to open '{}': {}", path, err) + } + + DlError::NotFound + })? + }; + + Ok(file) + } + + fn run_init(&self, obj: &DSO) { + use crate::platform::{self, types::*}; + + if let Some((symbol, SymbolBinding::Global)) = obj.get_sym("__relibc_init_environ") { + unsafe { + symbol + .as_ptr() + .cast::<*mut *mut c_char>() + .write(platform::environ); + } + } + + obj.run_init(); + } + + fn scope_debug(&self) { + if self.config.debug_flags.contains(DebugFlags::SCOPES) { + println!("[ld.so]: =========== SCOPES =========="); + GLOBAL_SCOPE.read().debug(); + for obj in self.objects.values() { + obj.scope().debug(); + } + println!("[ld.so]: =============================="); + } } } -unsafe fn call_inits_finis(addr: usize) { - let func = transmute::>(addr); - (*func).map(|x| x()); -} +// GOT[1] = object_id +// GOT[2] = __plt_resolve_trampoline +// +// The stubs in .plt will push the relocation index and the object pointer onto +// the stack and jump to [`__plt_resolve_trampoline`]. The trampoline will then +// call this function to resolve the symbol and update the respective GOT entry. +// The trampoline will then jump to the resolved symbol. +// +// FIXME(andypython): 32-bit +#[cfg(target_pointer_width = "64")] +extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> *mut c_void { + let obj = unsafe { &*obj }; + let obj_base = obj.mmap.as_ptr() as usize; + let jmprel = obj.dynamic.jmprel; -fn is_pie_enabled(elf: &Elf) -> bool { - if elf.header.e_type == ET_DYN { - true + let rela = unsafe { &*(jmprel as *const Rela).add(relocation_index as usize) }; + assert_eq!(rela.r_type(NativeEndian, false), elf::R_X86_64_JUMP_SLOT); + + let sym = obj + .dynamic + .symbol(rela.symbol(NativeEndian, false).unwrap()) + .expect("symbol not found"); + assert_ne!(sym.st_name(NativeEndian), 0); + + let name = core::str::from_utf8( + obj.dynamic + .dynstrtab + .get(sym.st_name(NativeEndian)) + .unwrap(), + ) + .expect("non utf8 symbol name"); + + let resolved = resolve_sym(name, &[&GLOBAL_SCOPE.read(), obj.scope()]) + .map(|(sym, _, _)| sym) + .unwrap_or_else(|| panic!("symbol '{name}' not found")) + .as_ptr(); + + let ptr = if obj.pie { + (obj_base as u64 + rela.r_offset(NativeEndian)) as *mut u64 } else { - false - } + rela.r_offset(NativeEndian) as *mut u64 + }; + #[cfg(feature = "trace_tls")] + log::trace!("@plt: {} -> *mut {:p}", name, ptr); + + unsafe { *ptr = resolved as u64 } + resolved } + +unsafe extern "C" { + pub(super) fn __plt_resolve_trampoline() -> usize; +} + +#[cfg(target_arch = "x86_64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + push rsi + push rdi + + mov rdi, qword ptr [rsp + 0x10] + mov rsi, qword ptr [rsp + 0x18] + + // stash the floating point argument registers + sub rsp, 128 + movdqu [rsp + 0x00], xmm0 + movdqu [rsp + 0x10], xmm1 + movdqu [rsp + 0x20], xmm2 + movdqu [rsp + 0x30], xmm3 + movdqu [rsp + 0x40], xmm4 + movdqu [rsp + 0x50], xmm5 + movdqu [rsp + 0x60], xmm6 + movdqu [rsp + 0x70], xmm7 + + push rax + push rcx + push rdx + push r8 + push r9 + push r10 + + push rbp + mov rbp, rsp + and rsp, 0xfffffffffffffff0 + call {__plt_resolve_inner} + mov r11, rax + mov rsp, rbp + pop rbp + + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + pop rax + + movdqu xmm7, [rsp + 0x70] + movdqu xmm6, [rsp + 0x60] + movdqu xmm5, [rsp + 0x50] + movdqu xmm4, [rsp + 0x40] + movdqu xmm3, [rsp + 0x30] + movdqu xmm2, [rsp + 0x20] + movdqu xmm1, [rsp + 0x10] + movdqu xmm0, [rsp + 0x00] + add rsp, 128 + + pop rdi + pop rsi + + add rsp, 0x10 + jmp r11 + + ud2 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline +", + __plt_resolve_inner = sym __plt_resolve_inner +); + +#[cfg(target_arch = "x86")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + ud2 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); + +#[cfg(target_arch = "aarch64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + udf #0 +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); + +#[cfg(target_arch = "riscv64")] +core::arch::global_asm!( + " +.global __plt_resolve_trampoline +.hidden __plt_resolve_trampoline +__plt_resolve_trampoline: + unimp +.size __plt_resolve_trampoline, . - __plt_resolve_trampoline + " +); diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 19dfe05f52..750f218987 100644 --- a/src/ld_so/mod.rs +++ b/src/ld_so/mod.rs @@ -1,26 +1,50 @@ +//! Dynamic loading and linking. + +// FIXME(andypython): remove this when #![allow(warnings, unused_variables)] is +// dropped from src/lib.rs. +#![warn(warnings, unused_variables)] + use core::{mem, ptr}; -use goblin::elf::program_header::{self, program_header32, program_header64, ProgramHeader}; +use object::{ + Endianness, + elf::{self, ProgramHeader32, ProgramHeader64}, + read::elf::ProgramHeader, +}; use self::tcb::{Master, Tcb}; -use crate::{header::sys_auxv::AT_NULL, start::Stack}; +use crate::{ + header::sys_auxv::{AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM}, + platform::{Pal, Sys}, + start::Stack, +}; -pub const PAGE_SIZE: usize = 4096; +pub const PATH_SEP: char = ':'; mod access; pub mod callbacks; pub mod debug; -mod library; +mod dso; pub mod linker; pub mod start; pub mod tcb; +pub use generic_rt::{ExpectTlsFree, panic_notls}; + static mut STATIC_TCB_MASTER: Master = Master { ptr: ptr::null_mut(), - len: 0, + image_size: 0, + segment_size: 0, offset: 0, }; -pub fn static_init(sp: &'static Stack) { +#[inline(never)] +fn static_init( + sp: &'static Stack, + #[cfg(target_os = "redox")] thr_fd: redox_rt::proc::FdGuardUpper, +) { + const SIZEOF_PHDR64: usize = mem::size_of::>(); + const SIZEOF_PHDR32: usize = mem::size_of::>(); + let mut phdr_opt = None; let mut phent_opt = None; let mut phnum_opt = None; @@ -33,72 +57,156 @@ pub fn static_init(sp: &'static Stack) { } match kind { - 3 => phdr_opt = Some(value), - 4 => phent_opt = Some(value), - 5 => phnum_opt = Some(value), + AT_PHDR => phdr_opt = Some(value), + AT_PHENT => phent_opt = Some(value), + AT_PHNUM => phnum_opt = Some(value), _ => (), } auxv = unsafe { auxv.add(1) }; } - let phdr = phdr_opt.expect("failed to find AT_PHDR"); - let phent = phent_opt.expect("failed to find AT_PHENT"); - let phnum = phnum_opt.expect("failed to find AT_PHNUM"); + let phdr = phdr_opt.expect_notls("failed to find AT_PHDR"); + let phent = phent_opt.expect_notls("failed to find AT_PHENT"); + let phnum = phnum_opt.expect_notls("failed to find AT_PHNUM"); for i in 0..phnum { let ph_addr = phdr + phent * i; - let ph: ProgramHeader = match phent { - program_header32::SIZEOF_PHDR => { - unsafe { *(ph_addr as *const program_header32::ProgramHeader) }.into() - } - program_header64::SIZEOF_PHDR => { - unsafe { *(ph_addr as *const program_header64::ProgramHeader) }.into() - } - _ => panic!("unknown AT_PHENT size {}", phent), + let endian = Endianness::default(); + let (p_align, p_filesz, p_memsz, p_type, p_vaddr) = match phent { + SIZEOF_PHDR64 => unsafe { + let ph = &*(ph_addr as *const ProgramHeader64); + ( + ph.p_align(endian) as usize, + ph.p_filesz(endian) as usize, + ph.p_memsz(endian) as usize, + ph.p_type(endian), + ph.p_vaddr(endian) as usize, + ) + }, + + SIZEOF_PHDR32 => unsafe { + let ph = &*(ph_addr as *const ProgramHeader32); + ( + ph.p_align(endian) as usize, + ph.p_filesz(endian) as usize, + ph.p_memsz(endian) as usize, + ph.p_type(endian), + ph.p_vaddr(endian) as usize, + ) + }, + _ => panic_notls(format_args!("unknown AT_PHENT size {}", phent)), }; - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; + let page_size = Sys::getpagesize(); + let voff = p_vaddr % page_size; + // let vaddr = ph.p_vaddr as usize - voff; + let vsize = (p_memsz + voff).div_ceil(page_size) * page_size; - match ph.p_type { - program_header::PT_TLS => { - let valign = if ph.p_align > 0 { - ((ph.p_memsz + (ph.p_align - 1)) / ph.p_align) * ph.p_align - } else { - ph.p_memsz - } as usize; + if p_type == elf::PT_TLS { + let valign = if p_align > 0 { + p_memsz.div_ceil(p_align) * p_align + } else { + p_memsz + }; - unsafe { - STATIC_TCB_MASTER.ptr = ph.p_vaddr as usize as *const u8; - STATIC_TCB_MASTER.len = ph.p_filesz as usize; - STATIC_TCB_MASTER.offset = vsize - valign; + unsafe { + STATIC_TCB_MASTER.ptr = p_vaddr as *const u8; + STATIC_TCB_MASTER.image_size = p_filesz; + STATIC_TCB_MASTER.offset = valign; - let tcb = Tcb::new(vsize).expect("failed to allocate TCB"); - tcb.masters_ptr = &mut STATIC_TCB_MASTER; - tcb.masters_len = mem::size_of::(); - tcb.copy_masters().expect("failed to copy TLS master data"); - tcb.activate(); - } - - //TODO: Warning on multiple TLS sections? - return; + let tcb = Tcb::new(vsize).expect_notls("failed to allocate TCB"); + tcb.masters_ptr = ptr::addr_of_mut!(STATIC_TCB_MASTER); + tcb.masters_len = mem::size_of::(); + tcb.copy_masters() + .expect_notls("failed to copy TLS master data"); + tcb.activate( + #[cfg(target_os = "redox")] + Some(thr_fd), + ); } - _ => (), + + //TODO: Warning on multiple TLS sections? + return; } } } -#[cfg(target_os = "linux")] -pub unsafe fn init(sp: &'static Stack) { - let mut tp = 0usize; - const ARCH_GET_FS: usize = 0x1003; - syscall!(ARCH_PRCTL, ARCH_GET_FS, &mut tp as *mut usize); +#[cfg(any(target_os = "linux", target_os = "redox"))] +pub unsafe fn init( + sp: &'static Stack, + #[cfg(target_os = "redox")] thr_fd: redox_rt::proc::FdGuardUpper, +) { + let tp: usize; + + #[cfg(all(target_os = "linux", target_arch = "x86_64"))] + { + const ARCH_GET_FS: usize = 0x1003; + let mut val = 0usize; + syscall!(ARCH_PRCTL, ARCH_GET_FS, &raw mut val); + tp = val; + } + #[cfg(target_arch = "aarch64")] + unsafe { + core::arch::asm!( + "mrs {}, tpidr_el0", + out(reg) tp, + ); + } + #[cfg(all(target_os = "redox", target_arch = "x86"))] + { + let mut env = syscall::EnvRegisters::default(); + + { + let file = thr_fd + .dup(b"regs/env") + .expect_notls("failed to open handle for process registers"); + + file.read(&mut env).expect_notls("failed to read gsbase"); + } + + tp = env.gsbase as usize; + } + #[cfg(all(target_os = "redox", target_arch = "x86_64"))] + { + let mut env = syscall::EnvRegisters::default(); + + { + let file = thr_fd + .dup(b"regs/env") + .expect_notls("failed to open handle for process registers"); + + file.read(&mut env).expect_notls("failed to read fsbase"); + } + + tp = env.fsbase as usize; + } + #[cfg(all(target_os = "redox", target_arch = "riscv64"))] + unsafe { + core::arch::asm!( + "mv {}, tp", + out(reg) tp, + ); + } + if tp == 0 { - static_init(sp); + static_init( + sp, + #[cfg(target_os = "redox")] + thr_fd, + ); + } else { + // The thread fd must already be present in the already existing TCB. Don't close it. + #[cfg(target_os = "redox")] + core::mem::forget(thr_fd); } } -#[cfg(target_os = "redox")] -pub unsafe fn init(_sp: &'static Stack) {} +pub unsafe fn fini() { + if let Some(tcb) = unsafe { Tcb::current() } + && !tcb.linker_ptr.is_null() + { + let linker = unsafe { (*tcb.linker_ptr).lock() }; + linker.fini(); + } +} diff --git a/src/ld_so/src/lib.rs b/src/ld_so/src/lib.rs deleted file mode 100644 index 69248ec88a..0000000000 --- a/src/ld_so/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![no_std] -#![feature(asm)] -#![feature(linkage)] -#![feature(llvm_asm)] -#![feature(naked_functions)] - -#[naked] -#[no_mangle] -pub unsafe extern "C" fn _start() { - #[cfg(target_arch = "x86_64")] - llvm_asm!(" - # rsi = _start + 5 - call next -next: pop rsi - - # Save original stack and align stack to 16 bytes - mov rbp, rsp - and rsp, 0xFFFFFFFFFFFFFFF0 - - # Call ld_so_start(stack, entry) - mov rdi, rbp - sub rsi, 5 - call relibc_ld_so_start - - # Restore original stack, clear registers, and jump to new start function - mov rsp, rbp - xor rcx, rcx - xor rdx, rdx - xor rdi, rdi - xor rsi, rsi - xor r8, r8 - xor r9, r9 - xor r10, r10 - xor r11, r11 - fninit - jmp rax - # TODO: Loader::fini() should be called about here - " - : - : - : - : "intel", "volatile" - ); - #[cfg(target_arch = "aarch64")] - llvm_asm!(" - mov x0, sp - bl ld_so_start - TODO - " - : - : - : - : "volatile" - ); -} - -#[naked] -#[no_mangle] -pub unsafe extern "C" fn main(_argc: isize, _argv: *const *const i8) -> usize { - // LD - 0x1D -} - -#[panic_handler] -#[linkage = "weak"] -#[no_mangle] -pub unsafe extern "C" fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { - extern "C" { - fn relibc_panic(pi: &::core::panic::PanicInfo) -> !; - } - relibc_panic(pi) -} diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 6a9439d001..f20778e2ab 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -1,5 +1,7 @@ // Start code adapted from https://gitlab.redox-os.org/redox-os/relibc/blob/master/src/start.rs +use core::slice; + use alloc::{ borrow::ToOwned, boxed::Box, @@ -7,73 +9,84 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; +use object::{ + NativeEndian, + elf::{self, PT_DYNAMIC, PT_PHDR}, + read::elf::{Dyn as _, ProgramHeader as _}, +}; use crate::{ c_str::CStr, - header::{sys_auxv::AT_NULL, unistd}, - platform::{new_mspace, types::c_char}, + header::{ + elf::{AT_BASE, AT_ENTRY, AT_PHDR, AT_PHENT, AT_PHNUM}, + unistd, + }, + ld_so::{ + dso::{ + DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, ProgramHeader, Rel, Rela, Relocation, + RelocationKind, Relr, apply_relr, + }, + linker::DebugFlags, + }, + platform::{auxv_iter, get_auxvs, types::c_char}, start::Stack, sync::mutex::Mutex, - ALLOCATOR, }; use super::{ + PATH_SEP, access::accessible, debug::_r_debug, - linker::{Linker, DSO, PATH_SEP}, + linker::{Config, Linker}, tcb::Tcb, }; -use crate::header::sys_auxv::{AT_ENTRY, AT_PHDR}; + +use generic_rt::ExpectTlsFree; + +#[cfg(target_pointer_width = "32")] +pub const SIZEOF_EHDR: usize = 52; + +#[cfg(target_pointer_width = "64")] +pub const SIZEOF_EHDR: usize = 64; unsafe fn get_argv(mut ptr: *const usize) -> (Vec, *const usize) { //traverse the stack and collect argument vector let mut argv = Vec::new(); - while *ptr != 0 { - let arg = *ptr; - match CStr::from_ptr(arg as *const c_char).to_str() { + while unsafe { *ptr != 0 } { + let arg = unsafe { *ptr }; + match unsafe { CStr::from_ptr(arg as *const c_char).to_str() } { Ok(arg_str) => argv.push(arg_str.to_owned()), _ => { eprintln!("ld.so: failed to parse argv[{}]", argv.len()); unistd::_exit(1); - loop {} } } - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } - return (argv, ptr); + + (argv, ptr) } unsafe fn get_env(mut ptr: *const usize) -> (BTreeMap, *const usize) { //traverse the stack and collect argument environment variables let mut envs = BTreeMap::new(); - while *ptr != 0 { - let env = *ptr; - if let Ok(arg_str) = CStr::from_ptr(env as *const c_char).to_str() { + while unsafe { *ptr != 0 } { + let env = unsafe { *ptr }; + if let Ok(arg_str) = unsafe { CStr::from_ptr(env as *const c_char).to_str() } { let mut parts = arg_str.splitn(2, '='); - if let Some(key) = parts.next() { - if let Some(value) = parts.next() { - envs.insert(key.to_owned(), value.to_owned()); - } + if let Some(key) = parts.next() + && let Some(value) = parts.next() + { + envs.insert(key.to_owned(), value.to_owned()); } } - ptr = ptr.add(1); + ptr = unsafe { ptr.add(1) }; } - return (envs, ptr); -} - -unsafe fn get_auxv(mut ptr: *const usize) -> BTreeMap { - //traverse the stack and collect argument environment variables - let mut auxv = BTreeMap::new(); - while *ptr != AT_NULL { - let kind = *ptr; - ptr = ptr.add(1); - let value = *ptr; - ptr = ptr.add(1); - auxv.insert(kind, value); - } - return auxv; + + (envs, ptr) } +#[allow(unsafe_op_in_unsafe_fn)] unsafe fn adjust_stack(sp: &'static mut Stack) { let mut argv = sp.argv() as *mut usize; @@ -97,16 +110,6 @@ unsafe fn adjust_stack(sp: &'static mut Stack) { if arg == 0 { break; } - if let Ok(arg_str) = CStr::from_ptr(arg as *const c_char).to_str() { - let mut parts = arg_str.splitn(2, '='); - if let Some(key) = parts.next() { - if let Some(value) = parts.next() { - if let "LD_LIBRARY_PATH" = key { - //library_path = value - } - } - } - } } // Move auxiliary vectors @@ -130,7 +133,7 @@ fn resolve_path_name( name_or_path: &str, envs: &BTreeMap, ) -> Option<(String, String)> { - if accessible(name_or_path, unistd::F_OK) == 0 { + if accessible(name_or_path, unistd::F_OK).is_ok() { return Some(( name_or_path.to_string(), name_or_path @@ -152,38 +155,233 @@ fn resolve_path_name( } else { format!("{}/{}", part, name_or_path) }; - if accessible(&path, unistd::F_OK) == 0 { + if accessible(&path, unistd::F_OK).is_ok() { return Some((path.to_string(), name_or_path.to_string())); } } None } -#[no_mangle] -pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> usize { - // First thing we initialize the mspace - ALLOCATOR.set_book_keeper(new_mspace()); - // next we get the arguments, the environment, and the auxilary vector + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn relibc_ld_so_start( + sp: &'static mut Stack, + ld_entry: usize, + dynamic: *const Dyn, +) -> usize { + // Relocate ourselves. + // + // This function is very delicate as it must **not** contain relocations itself. References to + // external symbols **cannot** be made until `stage2()` so, this function might not be very + // elegant. + // + // At this stage the TCB is not setup either so `expect_notls` must be used instead of `expect` + // and `unwrap`. + let mut at_phdr = None; + let mut at_phnum = None; + let mut at_phent = None; + let mut at_base = None; + let mut at_entry = None; + for [kind, value] in unsafe { auxv_iter(sp.auxv().cast::()) } { + match kind { + AT_PHDR => at_phdr = Some(value as *const ProgramHeader), + AT_PHNUM => at_phnum = Some(value), + AT_PHENT => at_phent = Some(value), + AT_BASE => at_base = Some(value), + AT_ENTRY => at_entry = Some(value), + _ => {} + } + } + + let at_phdr = at_phdr.expect_notls("`AT_PHDR` must be present"); + let at_phnum = at_phnum.expect_notls("`AT_PHNUM` must be present if `AT_PHDR` is"); + let at_phent = at_phent.expect_notls("`AT_PHENT` must be present if `AT_PHDR` is"); + assert!(!at_phdr.is_null() && at_phnum != 0 && at_phent == size_of::()); + let phdrs = unsafe { slice::from_raw_parts(at_phdr, at_phnum) }; + + let at_entry = at_entry.expect_notls("`AT_ENTRY` must be present"); + let at_base = at_base.unwrap_or_default(); + + let self_base = if at_base != 0 { + at_base + } else { + let ph = phdrs + .iter() + .find(|ph| ph.p_type(NativeEndian) == PT_DYNAMIC) + .unwrap(); + unsafe { dynamic.byte_sub(ph.p_vaddr(NativeEndian) as usize) as usize } + }; + + let is_manual = at_entry == ld_entry; // Whether the dynamic linker was invoked as a command. + + let mut i = dynamic; + let mut rela_ptr = None; + let mut rela_len = None; + let mut relr_ptr = None; + let mut relr_len = None; + let mut rel_ptr = None; + let mut rel_len = None; + loop { + let entry = unsafe { &*i }; + let val = entry.d_val(NativeEndian); + let ptr = val as *const u8; + match entry.d_tag(NativeEndian) as u32 { + elf::DT_NULL => break, + elf::DT_RELA => rela_ptr = Some(ptr.cast::()), + elf::DT_RELASZ => rela_len = Some(val as usize / size_of::()), + elf::DT_RELAENT => { + assert_eq!(val as usize, size_of::(),); + } + elf::DT_REL => rel_ptr = Some(ptr.cast::()), + elf::DT_RELSZ => rel_len = Some(val as usize / size_of::()), + elf::DT_RELENT => { + assert_eq!(val as usize, size_of::()); + } + DT_RELR => relr_ptr = Some(ptr.cast::()), + DT_RELRSZ => relr_len = Some(val as usize / size_of::()), + DT_RELRENT => { + assert_eq!(val as usize, size_of::()); + } + _ => {} + } + i = unsafe { i.add(1) }; + } + + unsafe fn get_array<'a, T>( + ptr: Option<*const T>, + len: Option, + base_addr: usize, + ) -> &'a [T] { + if let Some(ptr) = ptr { + let len = len.expect_notls("dynamic entry was present without it's corresponding size"); + unsafe { core::slice::from_raw_parts(ptr.byte_add(base_addr), len) } + } else { + &[] + } + } + + fn do_relocs<'a, T>(relocs: &'a [T], self_base: usize) + where + Relocation: From<&'a T>, + { + for reloc in relocs { + let reloc: Relocation = reloc.into(); + let ptr = (reloc.offset + self_base) as *mut usize; + if reloc.kind == RelocationKind::RELATIVE { + unsafe { *ptr = self_base + reloc.addend.unwrap_or_default() }; + } + } + } + + let rela = unsafe { get_array::(rela_ptr, rela_len, self_base) }; + let rel = unsafe { get_array::(rel_ptr, rel_len, self_base) }; + do_relocs(rela, self_base); + do_relocs(rel, self_base); + + unsafe { + let relr = get_array(relr_ptr, relr_len, self_base); + apply_relr(self_base as *const u8, relr); + } + + let mut base_addr = None; + if !is_manual { + // if we are not running in manual mode, then the main + // program is already loaded by the kernel and we want + // to use it. on redox, we treat it the same. + for ph in phdrs.iter() { + if ph.p_type(NativeEndian) == PT_PHDR { + assert!(base_addr.is_none(), "`PT_PHDR` cannot occur more than once"); + base_addr = Some(unsafe { + phdrs + .as_ptr() + .cast::() + .sub(ph.p_vaddr(NativeEndian) as usize) + } as usize); + } + } + } + + stage2(sp, self_base, is_manual, base_addr) +} + +fn stage2( + sp: &'static mut Stack, + self_base: usize, + is_manual: bool, + base_addr: Option, +) -> usize { + // Setup TCB for ourselves. + unsafe { + #[cfg(target_os = "redox")] + let auxv = sp.auxv().cast(); + #[cfg(target_os = "redox")] + let thr_fd = crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_THR_FD) + .expect_notls("no thread fd present"); + + let tcb = Tcb::new(0).expect_notls("[ld.so]: failed to allocate bootstrap TCB"); + tcb.activate( + #[cfg(target_os = "redox")] + Some( + redox_rt::proc::FdGuard::new(thr_fd) + .to_upper() + .expect_notls("failed to move thread fd to upper table"), + ), + ); + #[cfg(target_os = "redox")] + { + let proc_fd = + crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_PROC_FD) + .expect_notls("no proc fd present"); + + let ns_fd = crate::platform::get_auxv_raw(auxv, redox_rt::auxv_defs::AT_REDOX_NS_FD) + .filter(|&fd| fd != usize::MAX) + .map(|fd| { + redox_rt::proc::FdGuard::new(fd) + .to_upper() + .expect_notls("failed to move ns fd to upper table") + }); + + redox_rt::initialize( + redox_rt::proc::FdGuard::new(proc_fd) + .to_upper() + .expect_notls("failed to move proc fd to upper table"), + ns_fd, + ); + redox_rt::signal::setup_sighandler(&tcb.os_specific, true); + } + } + + // We get the arguments, the environment, and the auxilary vector let (argv, envs, auxv) = unsafe { let argv_start = sp.argv() as *mut usize; let (argv, argv_end) = get_argv(argv_start); let (envs, envs_end) = get_env(argv_end.add(1)); - let auxv = get_auxv(envs_end.add(1)); + let auxv = get_auxvs(envs_end.add(1)); (argv, envs, auxv) }; - let is_manual = if let Some(img_entry) = auxv.get(&AT_ENTRY) { - *img_entry == ld_entry - } else { - true - }; - - // we might need global lock for this kind of stuff unsafe { - _r_debug.r_ldbase = ld_entry; + crate::platform::OUR_ENVIRON.unsafe_set( + envs.iter() + .map(|(k, v)| { + let mut var = Vec::with_capacity(k.len() + v.len() + 2); + var.extend(k.as_bytes()); + var.push(b'='); + var.extend(v.as_bytes()); + var.push(b'\0'); + let mut var = var.into_boxed_slice(); + let ptr = var.as_mut_ptr(); + core::mem::forget(var); + ptr.cast() + }) + .chain(core::iter::once(core::ptr::null_mut())) + .collect::>(), + ); + + crate::platform::environ = crate::platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr(); } - // Some variables that will be overridden by environment and auxiliary vectors - let ld_library_path = envs.get("LD_LIBRARY_PATH").map(|s| s.to_owned()); + // we might need global lock for this kind of stuff + _r_debug.lock().r_ldbase = self_base; let name_or_path = if is_manual { // ld.so is run directly by user and not via execve() or similar systemcall @@ -194,7 +392,6 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> if sp.argc < 2 { eprintln!("ld.so [executable] [arguments...]"); unistd::_exit(1); - loop {} } unsafe { adjust_stack(sp) }; argv[1].to_string() @@ -202,71 +399,42 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> argv[0].to_string() }; - let (path, name) = match resolve_path_name(&name_or_path, &envs) { + // TODO: Fix memory leak, although minimal. + #[cfg(target_os = "redox")] + unsafe { + crate::platform::init_inner(auxv); + } + + let (path, _name) = match resolve_path_name(&name_or_path, &envs) { Some((p, n)) => (p, n), None => { - eprintln!("ld.so: failed to locate '{}'", name_or_path); + eprintln!("[ld.so]: failed to locate '{name_or_path}'"); unistd::_exit(1); - loop {} } }; - // if we are not running in manual mode, then the main - // program is already loaded by the kernel and we want - // to use it. on redox, we treat it the same. - let program = { - let mut pr = None; - if !is_manual && cfg!(not(target_os = "redox")) { - let phdr = *auxv.get(&AT_PHDR).unwrap(); - if phdr != 0 { - let p = DSO { - name: path.to_owned(), - entry_point: *auxv.get(&AT_ENTRY).unwrap(), - // The 0x40 is the size of Elf header not a good idea for different bit size - // compatiablility but it will always work on 64 bit systems, - base_addr: phdr - 0x40, - }; - pr = Some(p); - } - } - pr - }; - let mut linker = Linker::new(ld_library_path, false); - match linker.load(&path, &path) { - Ok(()) => (), - Err(err) => { - eprintln!("ld.so: failed to load '{}': {}", path, err); - unistd::_exit(1); - loop {} + let config = Config::from_env(&envs); + if config.debug_flags.contains(DebugFlags::LOAD) { + println!("[ld.so]: relocated self at {self_base:#x}!"); + if let Some(base_addr) = base_addr { + println!("[ld.so]: executable has been already loaded at {base_addr:#x?}"); } } - let entry = match linker.link(Some(&path), program, None) { - Ok(ok) => match ok { - Some(some) => some, - None => { - eprintln!("ld.so: failed to link '{}': missing entry", path); - unistd::_exit(1); - loop {} - } - }, + let mut linker = Linker::new(config); + let entry = match linker.load_program(&path, base_addr) { + Ok(entry) => entry, Err(err) => { - eprintln!("ld.so: failed to link '{}': {}", path, err); + eprintln!("[ld.so]: failed to link '{path}': {err:?}"); + eprintln!("[ld.so]: enable debug output with `LD_DEBUG=all` for more information"); unistd::_exit(1); - loop {} } }; - if let Err(e) = linker.run_init(None) { - eprintln!("ld.so: failed to run .init_array"); - unistd::_exit(1); - loop {} - } if let Some(tcb) = unsafe { Tcb::current() } { tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); - tcb.mspace = ALLOCATOR.get_book_keeper(); } if is_manual { - eprintln!("ld.so: entry '{}': {:#x}", path, entry); + eprintln!("[ld.so]: entry '{path}': {entry:#x}"); } entry } diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 848901a6ed..2d872d43da 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -1,17 +1,31 @@ -use alloc::boxed::Box; -use core::{mem, ops::Range, ptr, slice}; -use goblin::error::{Error, Result}; +use alloc::vec::Vec; +use core::{ + cell::UnsafeCell, + mem, + ops::{Deref, DerefMut}, + ptr, slice, + sync::atomic::AtomicBool, +}; +use generic_rt::GenericTcb; -use crate::{header::sys_mman, ld_so::linker::Linker, sync::mutex::Mutex}; +use crate::{ + header::sys_mman, + ld_so::linker::Linker, + platform::{Dlmalloc, Pal, Sys}, + pthread::{OsTid, Pthread}, + sync::{mutex::Mutex, waitval::Waitval}, +}; -use super::PAGE_SIZE; +use super::linker::DlError; #[repr(C)] +#[derive(Debug, Clone)] pub struct Master { /// Pointer to initial data pub ptr: *const u8, /// Length of initial data in bytes - pub len: usize, + pub image_size: usize, + pub segment_size: usize, /// Offset in TLS to copy initial data to pub offset: usize, } @@ -19,54 +33,85 @@ pub struct Master { impl Master { /// The initial data for this TLS region pub unsafe fn data(&self) -> &'static [u8] { - slice::from_raw_parts(self.ptr, self.len) - } - - /// The region of TLS that the master will initialize - pub fn range(&self) -> Range { - self.offset..self.offset + self.len + unsafe { slice::from_raw_parts(self.ptr, self.image_size) } } } +#[cfg(target_os = "linux")] +pub type OsSpecific = (); + +#[cfg(target_os = "redox")] +pub type OsSpecific = redox_rt::signal::RtSigarea; + #[derive(Debug)] #[repr(C)] +// FIXME: Only return &Tcb, and use interior mutability, since it contains the Pthread struct pub struct Tcb { - /// Pointer to the end of static TLS. Must be the first member - pub tls_end: *mut u8, - /// Size of the memory allocated for the static TLS in bytes (multiple of PAGE_SIZE) - pub tls_len: usize, - /// Pointer to this structure - pub tcb_ptr: *mut Tcb, - /// Size of the memory allocated for this structure in bytes (should be PAGE_SIZE) - pub tcb_len: usize, + pub generic: GenericTcb, /// Pointer to a list of initial TLS data pub masters_ptr: *mut Master, - /// Size of the masters list in bytes (multiple of mem::size_of::()) + /// Size of the masters list in bytes (multiple of `mem::size_of::()`) pub masters_len: usize, + /// Index of last copied Master + pub num_copied_masters: usize, /// Pointer to dynamic linker pub linker_ptr: *const Mutex, /// pointer to rust memory allocator structure - pub mspace: usize, + pub mspace: *const Mutex, + /// Underlying pthread_t struct, pthread_self() returns &self.pthread + pub pthread: Pthread, + + // Dynamic TLS Vector + pub dtv_ptr: *mut *mut u8, + // Number of DTV entries. + pub dtv_len: usize, } +#[cfg(target_os = "redox")] +const _: () = { + if mem::size_of::() > syscall::PAGE_SIZE { + panic!("too large TCB!"); + } +}; + impl Tcb { /// Create a new TCB - pub unsafe fn new(size: usize) -> Result<&'static mut Self> { - let (tls, tcb_page) = Self::os_new(size)?; + /// + /// `size` is the size of the TLS in bytes. + #[allow(unsafe_op_in_unsafe_fn)] + pub unsafe fn new(size: usize) -> Result<&'static mut Self, DlError> { + let page_size = Sys::getpagesize(); + let (_abi_page, tls, tcb_page) = Self::os_new(size.next_multiple_of(page_size))?; - let tcb_ptr = tcb_page.as_mut_ptr() as *mut Self; - // println!("New TCB: {:p}", tcb_ptr); + let tcb_ptr = tcb_page.as_mut_ptr().cast::(); ptr::write( tcb_ptr, Self { - tls_end: tls.as_mut_ptr().add(tls.len()), - tls_len: tls.len(), - tcb_ptr, - tcb_len: tcb_page.len(), + generic: GenericTcb { + tls_end: tls.as_mut_ptr().add(tls.len()), + tls_len: tls.len(), + tcb_ptr: tcb_ptr.cast(), + tcb_len: tcb_page.len(), + os_specific: OsSpecific::default(), + }, masters_ptr: ptr::null_mut(), masters_len: 0, + num_copied_masters: 0, linker_ptr: ptr::null(), - mspace: 0, + mspace: ptr::null(), + pthread: Pthread { + waitval: Waitval::new(), + flags: Default::default(), + has_enabled_cancelation: AtomicBool::new(false), + has_queued_cancelation: AtomicBool::new(false), + stack_base: core::ptr::null_mut(), + stack_size: 0, + os_tid: UnsafeCell::new(OsTid::default()), + robust_list_head: UnsafeCell::new(core::ptr::null_mut()), + }, + + dtv_ptr: ptr::null_mut(), + dtv_len: 0, }, ); @@ -75,13 +120,7 @@ impl Tcb { /// Get the current TCB pub unsafe fn current() -> Option<&'static mut Self> { - let tcb_ptr = Self::arch_read(offset_of!(Self, tcb_ptr)) as *mut Self; - let tcb_len = Self::arch_read(offset_of!(Self, tcb_len)); - if tcb_ptr.is_null() || tcb_len < mem::size_of::() { - None - } else { - Some(&mut *tcb_ptr) - } + unsafe { Some(&mut *GenericTcb::::current_ptr()?.cast()) } } /// A slice for all of the TLS data @@ -89,131 +128,260 @@ impl Tcb { if self.tls_end.is_null() || self.tls_len == 0 { None } else { - Some(slice::from_raw_parts_mut( - self.tls_end.offset(-(self.tls_len as isize)), - self.tls_len, - )) + unsafe { + let tls_start = self.tls_end.sub(self.tls_len); + Some(slice::from_raw_parts_mut(tls_start, self.tls_len)) + } } } /// The initial images for TLS - pub unsafe fn masters(&self) -> Option<&'static mut [Master]> { + pub fn masters(&self) -> Option<&'static mut [Master]> { if self.masters_ptr.is_null() || self.masters_len == 0 { None } else { - Some(slice::from_raw_parts_mut( - self.masters_ptr, - self.masters_len / mem::size_of::(), - )) + Some(unsafe { + slice::from_raw_parts_mut( + self.masters_ptr, + self.masters_len / mem::size_of::(), + ) + }) } } /// Copy data from masters - pub unsafe fn copy_masters(&self) -> Result<()> { + pub unsafe fn copy_masters(&mut self) -> Result<(), DlError> { //TODO: Complain if masters or tls exist without the other - if let Some(tls) = self.tls() { - if let Some(masters) = self.masters() { - for (i, master) in masters.iter().enumerate() { - let range = master.range(); - let data = master.data(); - if let Some(tls_data) = tls.get_mut(range) { - // println!( - // "tls master {}: {:p}, {:#x}: {:p}, {:#x}", - // i, - // data.as_ptr(), data.len(), - // tls_data.as_mut_ptr(), tls_data.len() - // ); - - tls_data.copy_from_slice(data); - } else { - return Err(Error::Malformed(format!("failed to copy tls master {}", i))); - } + if let Some(tls) = unsafe { self.tls() } + && let Some(masters) = self.masters() + { + for master in masters + .iter() + .skip(self.num_copied_masters) + .filter(|master| master.image_size != 0) + { + let range = if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // x86{_64} TLS layout is backwards + self.tls_len - master.offset..self.tls_len - master.offset + master.image_size + } else { + master.offset..master.offset + master.image_size + }; + if let Some(tls_data) = tls.get_mut(range) { + let data = unsafe { master.data() }; + #[cfg(feature = "trace_tls")] + log::trace!( + "tls master: {:p}, {:#x}: {:p}, {:#x}", + data.as_ptr(), + data.len(), + tls_data.as_mut_ptr(), + tls_data.len() + ); + tls_data.copy_from_slice(data); + } else { + return Err(DlError::Malformed); } } + self.num_copied_masters = masters.len(); } Ok(()) } /// The initial images for TLS - pub unsafe fn set_masters(&mut self, mut masters: Box<[Master]>) { - self.masters_ptr = masters.as_mut_ptr(); - self.masters_len = masters.len() * mem::size_of::(); - mem::forget(masters); + pub unsafe fn append_masters(&mut self, mut new_masters: Vec) { + if self.masters_ptr.is_null() { + self.masters_ptr = new_masters.as_mut_ptr(); + self.masters_len = new_masters.len() * mem::size_of::(); + mem::forget(new_masters); + } else { + // XXX: [`Vec::from_raw_parts`] cannot be used here as the masters were originally + // allocated by the ld.so allocator and that would violate that function's invariants. + let mut masters = self.masters().unwrap().to_vec(); + masters.extend(new_masters); + + self.masters_ptr = masters.as_mut_ptr(); + self.masters_len = masters.len() * mem::size_of::(); + mem::forget(masters); + } } /// Activate TLS - pub unsafe fn activate(&mut self) { - Self::os_arch_activate(self.tcb_ptr as usize); + pub unsafe fn activate( + &mut self, + #[cfg(target_os = "redox")] thr_fd: Option, + ) { + unsafe { + Self::os_arch_activate( + &self.os_specific, + self.tls_end as usize, + self.tls_len, + #[cfg(target_os = "redox")] + thr_fd, + ) + }; + } + + pub fn setup_dtv(&mut self, n: usize) { + if self.dtv_ptr.is_null() { + let mut dtv = vec![ptr::null_mut(); n]; + + if let Some(masters) = self.masters() { + for (i, master) in masters.iter().enumerate() { + let tls = unsafe { self.tls().unwrap() }; + let offset = if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // x86{_64} TLS layout is backwards + self.tls_len - master.offset + } else { + master.offset + }; + + dtv[i] = unsafe { tls.as_mut_ptr().add(offset) }; + } + } + + let (ptr, len, _) = dtv.into_raw_parts(); + + self.dtv_ptr = ptr; + self.dtv_len = len; + } else { + // Resize DTV. + // + // XXX: [`Vec::from_raw_parts`] cannot be used here as the DTV was originally allocated + // by the ld.so allocator and that would violate that function's invariants. + let mut dtv = self.dtv_mut().to_vec(); + dtv.resize(n, ptr::null_mut()); + + let (ptr, len, _) = dtv.into_raw_parts(); + self.dtv_ptr = ptr; + self.dtv_len = len; + } + } + + pub fn dtv_mut(&mut self) -> &'static mut [*mut u8] { + if self.dtv_len != 0 { + unsafe { slice::from_raw_parts_mut(self.dtv_ptr, self.dtv_len) } + } else { + &mut [] + } } /// Mapping with correct flags for TCB and TLS - unsafe fn map(size: usize) -> Result<&'static mut [u8]> { - let ptr = sys_mman::mmap( + #[allow(unsafe_op_in_unsafe_fn)] + unsafe fn map(size: usize) -> Result<&'static mut [u8], DlError> { + let ptr = Sys::mmap( ptr::null_mut(), size, sys_mman::PROT_READ | sys_mman::PROT_WRITE, sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE, -1, 0, - ); - if ptr as usize == !0 - /* MAP_FAILED */ - { - return Err(Error::Malformed(format!("failed to map tls"))); - } - ptr::write_bytes(ptr as *mut u8, 0, size); - Ok(slice::from_raw_parts_mut(ptr as *mut u8, size)) + ) + .map_err(|_| DlError::Oom)?; + + ptr::write_bytes(ptr.cast::(), 0, size); + Ok(slice::from_raw_parts_mut(ptr.cast::(), size)) } - /// OS specific code to create a new TLS and TCB - Linux - #[cfg(target_os = "linux")] - unsafe fn os_new(size: usize) -> Result<(&'static mut [u8], &'static mut [u8])> { - let tls_tcb = Self::map(size + PAGE_SIZE)?; - Ok(tls_tcb.split_at_mut(size)) - } - - /// OS specific code to create a new TLS and TCB - Redox - #[cfg(target_os = "redox")] - unsafe fn os_new(size: usize) -> Result<(&'static mut [u8], &'static mut [u8])> { - use crate::header::unistd; - //TODO: better method of finding fs offset - let pid = unistd::getpid(); - let tcb_addr = 0xB000_0000 + pid as usize * PAGE_SIZE; - let tls = Self::map(size)?; - Ok(( - tls, - //TODO: Consider allocating TCB as part of TLS - slice::from_raw_parts_mut(tcb_addr as *mut u8, PAGE_SIZE), - )) - } - - /// Architecture specific code to read a usize from the TCB - x86_64 - #[inline(always)] - #[cfg(target_arch = "x86_64")] - unsafe fn arch_read(offset: usize) -> usize { - let value; - llvm_asm!(" - mov rax, fs:[rdi] - " - : "={rax}"(value) - : "{rdi}"(offset) - : - : "intel" - ); - value + /// OS specific code to create a new TLS and TCB - Linux and Redox + /// + /// Memory layout: + /// + /// ```text + /// 0 page_size size (size + page_size * 2) + /// |----------|---------------------------|----------| + /// +++++++++++++++++++++++++++++++++++++++++++++++++++ + /// | ABI Page | TLS | TCB Page | + /// +++++++++++++++++++++++++++++++++++++++++++++++++++ + /// ^ $tp (aarch64) ^ $tp (x86_64) + /// ``` + /// + /// `$tp` refers to the architecture specific thread pointer. + /// + /// **Note**: On x86{_64}, the TLS layout is backwards (i.e. the first byte of the TLS is at + /// the end of the TLS region). + /// + /// ABI page layout for aarch64: + /// ```text + /// 0 4096 + /// +---------------------+ + /// | ABI Page | + /// +---------------------+ + /// ^ + /// | + /// +-------> (page_size - 16): pointer to the start of the TCB page + /// ``` + /// + /// ABI page layout for riscv64: + /// + /// ```text + /// 0 4096 + /// +---------------------+ + /// | ABI Page | + /// +---------------------+ + /// ^ + /// | + /// +-------> (page_size - 8): pointer to the start of the TCB page + /// ``` + /// + /// For x86_64, the ABI page is not used. + #[cfg(any(target_os = "linux", target_os = "redox"))] + unsafe fn os_new( + size: usize, + ) -> Result<(&'static mut [u8], &'static mut [u8], &'static mut [u8]), DlError> { + let page_size = Sys::getpagesize(); + let abi_tls_tcb = unsafe { Self::map(page_size + size + page_size)? }; + let (abi, tls_tcb) = abi_tls_tcb.split_at_mut(page_size); + let (tls, tcb) = tls_tcb.split_at_mut(size); + Ok((abi, tls, tcb)) } /// OS and architecture specific code to activate TLS - Linux x86_64 #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - unsafe fn os_arch_activate(tp: usize) { + unsafe fn os_arch_activate(_os: &(), tls_end: usize, _tls_len: usize) { const ARCH_SET_FS: usize = 0x1002; - syscall!(ARCH_PRCTL, ARCH_SET_FS, tp); + unsafe { + syscall!(ARCH_PRCTL, ARCH_SET_FS, tls_end); + } } - /// OS and architecture specific code to activate TLS - Linux x86_64 - #[cfg(all(target_os = "redox", target_arch = "x86_64"))] - unsafe fn os_arch_activate(tp: usize) { - //TODO: Consider setting FS offset to TCB pointer + #[cfg(all(target_os = "linux", target_arch = "aarch64"))] + unsafe fn os_arch_activate(_os: &(), tls_end: usize, tls_len: usize) { + // Uses ABI page + let abi_ptr = tls_end - tls_len - 16; + unsafe { + core::ptr::write(abi_ptr as *mut usize, tls_end); + core::arch::asm!( + "msr tpidr_el0, {}", + in(reg) abi_ptr, + ); + } + } + + #[cfg(target_os = "redox")] + unsafe fn os_arch_activate( + os: &OsSpecific, + tls_end: usize, + tls_len: usize, + thr_fd: Option, + ) { + unsafe { + if let Some(thr_fd) = thr_fd { + os.thr_fd.get().write(Some(thr_fd)); + } + redox_rt::tcb_activate(os, tls_end, tls_len) + } + } +} + +impl Deref for Tcb { + type Target = GenericTcb; + + fn deref(&self) -> &Self::Target { + &self.generic + } +} +impl DerefMut for Tcb { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.generic } } diff --git a/src/lib.rs b/src/lib.rs index 163d2e118a..f41627985e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,38 +1,33 @@ +//! POSIX C library, implemented in Rust. +//! +//! This crate exists to provide a standard libc as its public API. This is +//! largely provided by automatically generated bindings to the functions and +//! data structures in the [`header`] module. +//! +//! Currently, Linux and Redox syscall backends are supported. + #![no_std] -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] -#![allow(unused_variables)] +#![feature(alloc_error_handler)] #![feature(allocator_api)] -#![feature(asm)] -#![feature(box_into_pin)] #![feature(c_variadic)] -#![feature(const_fn)] -#![feature(const_raw_ptr_deref)] #![feature(core_intrinsics)] -#![feature(global_asm)] +#![feature(macro_derive)] +#![feature(maybe_uninit_slice)] #![feature(lang_items)] #![feature(linkage)] -#![feature(llvm_asm)] -#![feature(maybe_uninit_extra)] +#![feature(pointer_is_aligned_to)] +#![feature(ptr_as_uninit)] +#![feature(slice_ptr_get)] #![feature(stmt_expr_attributes)] -#![feature(str_internals)] +#![feature(sync_unsafe_cell)] #![feature(thread_local)] -#![allow(clippy::cast_lossless)] -#![allow(clippy::cast_ptr_alignment)] -#![allow(clippy::derive_hash_xor_eq)] -#![allow(clippy::eval_order_dependence)] -#![allow(clippy::mut_from_ref)] +#![feature(vec_into_raw_parts)] +#![feature(negative_impls)] #[macro_use] extern crate alloc; extern crate cbitset; -extern crate core_io; -extern crate goblin; -#[macro_use] -extern crate lazy_static; extern crate memchr; -#[macro_use] -extern crate memoffset; extern crate posix_regex; extern crate rand; @@ -43,78 +38,80 @@ extern crate sc; #[cfg(target_os = "redox")] extern crate syscall; -#[cfg(target_os = "redox")] -extern crate spin; - #[macro_use] mod macros; pub mod c_str; pub mod c_vec; pub mod cxa; pub mod db; +pub mod error; pub mod fs; pub mod header; pub mod io; +pub mod iter; pub mod ld_so; +pub mod out; pub mod platform; +pub mod pthread; +pub mod raw_cell; pub mod start; pub mod sync; -use crate::platform::{Allocator, Pal, Sys, NEWALLOCATOR}; +use crate::platform::{Allocator, NEWALLOCATOR}; #[global_allocator] static ALLOCATOR: Allocator = NEWALLOCATOR; -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! { use core::fmt::Write; - let mut w = platform::FileWriter(2); + let mut w = platform::FileWriter::new(2); let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi)); - Sys::exit(1); + unsafe { crate::header::stdlib::abort() }; } #[cfg(not(test))] #[panic_handler] #[linkage = "weak"] -#[no_mangle] -pub extern "C" fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { +pub fn rust_begin_unwind(pi: &::core::panic::PanicInfo) -> ! { relibc_panic(pi) } #[cfg(not(test))] #[lang = "eh_personality"] -#[no_mangle] #[linkage = "weak"] pub extern "C" fn rust_eh_personality() {} #[cfg(not(test))] -#[lang = "oom"] +#[alloc_error_handler] #[linkage = "weak"] -#[no_mangle] +#[allow(improper_ctypes_definitions)] +#[unsafe(no_mangle)] pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! { + // Layout not FFI-safe? use core::fmt::Write; - let mut w = platform::FileWriter(2); + let mut w = platform::FileWriter::new(2); let _ = w.write_fmt(format_args!( "RELIBC OOM: {} bytes aligned to {} bytes\n", layout.size(), layout.align() )); - Sys::exit(1); + unsafe { crate::header::stdlib::abort() }; } #[cfg(not(test))] #[allow(non_snake_case)] #[linkage = "weak"] -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn _Unwind_Resume() -> ! { use core::fmt::Write; - let mut w = platform::FileWriter(2); + let mut w = platform::FileWriter::new(2); let _ = w.write_str("_Unwind_Resume\n"); - Sys::exit(1); + unsafe { crate::header::stdlib::abort() }; } diff --git a/src/macros.rs b/src/macros.rs index 132d784186..72b4e414db 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,89 +1,85 @@ -#[macro_export] -macro_rules! c_str { - ($lit:expr) => { - #[allow(unused_unsafe)] - unsafe { - $crate::c_str::CStr::from_bytes_with_nul_unchecked(concat!($lit, "\0").as_bytes()) - } - }; -} - /// Print to stdout #[macro_export] macro_rules! print { - ($($arg:tt)*) => ({ + ($($arg:tt)*) => {{ use core::fmt::Write; - let _ = write!($crate::platform::FileWriter(1), $($arg)*); - }); + let _ = $crate::platform::FileWriter::new(1).write_fmt(format_args!($($arg)*)); + }}; } -/// Print with new line to stdout +/// Print with new line to stdout. +/// Deprecated, consider using log::info instead #[macro_export] macro_rules! println { - () => (print!("\n")); - ($fmt:expr) => (print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); + () => { + $crate::print!("\n") + }; + ($($arg:tt)*) => { + $crate::print!("{}\n", format_args!($($arg)*)) + }; } /// Print to stderr #[macro_export] macro_rules! eprint { - ($($arg:tt)*) => ({ + ($($arg:tt)*) => {{ use core::fmt::Write; - let _ = write!($crate::platform::FileWriter(2), $($arg)*); - }); + let _ = $crate::platform::FileWriter::new(2).write_fmt(format_args!($($arg)*)); + }}; } -/// Print with new line to stderr +/// Print with new line to stderr. +/// Deprecated, consider using log::info instead #[macro_export] macro_rules! eprintln { - () => (eprint!("\n")); - ($fmt:expr) => (eprint!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (eprint!(concat!($fmt, "\n"), $($arg)*)); + () => { + $crate::eprint!("\n") + }; + ($($arg:tt)*) => { + $crate::eprint!("{}\n", format_args!($($arg)*)) + }; } -/// Lifted from libstd +pub const ISSUE_URL: &str = "https://gitlab.redox-os.org/redox-os/relibc/-/issues/"; + +// Skippable todo!(issue, fmt) #[macro_export] -macro_rules! dbg { - () => { - eprintln!("[{}:{}]", file!(), line!()); +macro_rules! todo_skip { + ($issue:expr, $($arg:tt)*) => { + if $issue != 0 { + log::info!("TODO ({}{}): {}", $crate::macros::ISSUE_URL, $issue, format_args!($($arg)*)) + } else { + log::info!("TODO: {}", format_args!($($arg)*)) + } }; - ($val:expr) => { - // Use of `match` here is intentional because it affects the lifetimes - // of temporaries - https://stackoverflow.com/a/48732525/1063961 - match $val { - tmp => { - eprintln!( - "[{}:{}] {} = {:#?}", - file!(), - line!(), - stringify!($val), - &tmp - ); - tmp - } +} + +// Recoverable error todo!(issue, fmt, err) +#[macro_export] +macro_rules! todo_error { + ($issue:expr, $err:expr, $($arg:tt)*) => { + if $issue != 0 { + log::error!("TODO ({}{}): {}: {}", $crate::macros::ISSUE_URL, $issue, format_args!($($arg)*), $err) + } else { + log::error!("TODO: {}: {:?}", format_args!($($arg)*), $err) + } + }; +} + +// Unrecoverable error todo!(issue, fmt) +#[macro_export] +macro_rules! todo_panic { + ($issue:expr, $($arg:tt)*) => { + if $issue != 0 { + todo!("{} ({}{})", format_args!($($arg)*), $crate::macros::ISSUE_URL, $issue) + } else { + todo!("{}", format_args!($($arg)*)) } }; } #[macro_export] -#[cfg(not(feature = "trace"))] -macro_rules! trace { - ($($arg:tt)*) => {}; -} - -#[macro_export] -#[cfg(feature = "trace")] -macro_rules! trace { - ($($arg:tt)*) => ({ - use $crate::{Pal, Sys}; - eprintln!($($arg)*); - Sys::fsync(2); - }); -} - -#[macro_export] -#[cfg(not(feature = "trace"))] +#[cfg(feature = "no_trace")] macro_rules! trace_expr { ($expr:expr, $($arg:tt)*) => { $expr @@ -91,26 +87,22 @@ macro_rules! trace_expr { } #[macro_export] -#[cfg(feature = "trace")] +#[cfg(not(feature = "no_trace"))] macro_rules! trace_expr { ($expr:expr, $($arg:tt)*) => ({ use $crate::header::errno::STR_ERROR; use $crate::platform; - trace!("{}", format_args!($($arg)*)); + log::trace!("{}", format_args!($($arg)*)); - #[allow(unused_unsafe)] - let trace_old_errno = unsafe { platform::errno }; - #[allow(unused_unsafe)] - unsafe { platform::errno = 0; } + let trace_old_errno = platform::ERRNO.get(); + platform::ERRNO.set(0); let ret = $expr; - #[allow(unused_unsafe)] - let trace_errno = unsafe { platform::errno } as isize; + let trace_errno = platform::ERRNO.get() as isize; if trace_errno == 0 { - #[allow(unused_unsafe)] - unsafe { platform::errno = trace_old_errno; } + platform::ERRNO.set(trace_old_errno); } let trace_strerror = if trace_errno >= 0 && trace_errno < STR_ERROR.len() as isize { @@ -119,14 +111,92 @@ macro_rules! trace_expr { "Unknown error" }; - trace!("{} = {} ({}, {})", format_args!($($arg)*), ret, trace_errno, trace_strerror); + log::trace!("{} = {} ({}, {})", format_args!($($arg)*), ret, trace_errno, trace_strerror); ret }); } +#[macro_export] +macro_rules! skipws { + ($ptr:expr) => { + while isspace(unsafe { *$ptr }) != 0 { + $ptr = unsafe { $ptr.add(1) }; + } + }; +} + +#[macro_export] +macro_rules! strtou_impl { + ($type:ident, $ptr:expr, $base:expr) => { + strtou_impl!($type, $ptr, $base, false) + }; + ($type:ident, $ptr:expr, $base:expr, $negative:expr) => {{ + let mut base = $base; + + if (base == 16 || base == 0) + && unsafe { *$ptr } == '0' as wchar_t + && (unsafe { *$ptr.add(1) } == 'x' as wchar_t + || unsafe { *$ptr.add(1) } == 'X' as wchar_t) + { + $ptr = unsafe { $ptr.add(2) }; + base = 16; + } + + if base == 0 { + base = if unsafe { *$ptr } == '0' as wchar_t { + 8 + } else { + 10 + }; + }; + + let mut result: $type = 0; + while let Some(digit) = + char::from_u32(unsafe { *$ptr } as u32).and_then(|c| c.to_digit(base as u32)) + { + let new = result.checked_mul(base as $type).and_then(|result| { + if $negative { + #[cfg(target_arch = "x86")] + { + result.checked_sub( + $type::try_from(digit).expect("single digit never overflows"), + ) + } + #[cfg(not(target_arch = "x86"))] + { + result.checked_sub($type::from(digit)) + } + } else { + #[cfg(target_arch = "x86")] + { + result.checked_add( + $type::try_from(digit).expect("single digit never overflows"), + ) + } + #[cfg(not(target_arch = "x86"))] + { + result.checked_add($type::from(digit)) + } + } + }); + result = match new { + Some(new) => new, + None => { + platform::ERRNO.set(ERANGE); + return !0; + } + }; + + $ptr = unsafe { $ptr.add(1) }; + } + result + }}; +} + #[macro_export] macro_rules! strto_impl { + // this variant is used by inttypes and stdlib ( $rettype:ty, $signed:expr, $maxval:expr, $minval:expr, $s:ident, $endptr:ident, $base:ident ) => {{ @@ -141,17 +211,19 @@ macro_rules! strto_impl { // const input but mut output, yet the man page says // "stores the address of the first invalid character in *endptr" // so obviously it doesn't want us to clone it. - *$endptr = $s.offset(idx) as *mut _; + unsafe { + *$endptr = $s.offset(idx).cast_mut(); + } } }; let invalid_input = || { - platform::errno = EINVAL; + platform::ERRNO.set(EINVAL); set_endptr(0); }; // only valid bases are 2 through 36 - if $base != 0 && ($base < 2 || $base > 36) { + if $base != 0 && !(2..=36).contains(&$base) { invalid_input(); return 0; } @@ -159,12 +231,12 @@ macro_rules! strto_impl { let mut idx = 0; // skip any whitespace at the beginning of the string - while ctype::isspace(*$s.offset(idx) as c_int) != 0 { + while ctype::isspace(c_int::from(unsafe { *$s.offset(idx) })) != 0 { idx += 1; } // check for +/- - let positive = match is_positive(*$s.offset(idx)) { + let positive = match is_positive(unsafe { *$s.offset(idx) }) { Some((pos, i)) => { idx += i; pos @@ -176,13 +248,15 @@ macro_rules! strto_impl { }; // convert the string to a number - let num_str = $s.offset(idx); + let num_str = unsafe { $s.offset(idx) }; let res = match $base { - 0 => detect_base(num_str) - .and_then(|($base, i)| convert_integer(num_str.offset(i), $base)), - 8 => convert_octal(num_str), - 16 => convert_hex(num_str), - _ => convert_integer(num_str, $base), + 0 => unsafe { detect_base(num_str) }.and_then(|($base, i)| { + idx += i; + unsafe { convert_integer(num_str.offset(i), $base) } + }), + 8 => unsafe { convert_octal(num_str) }, + 16 => unsafe { convert_hex(num_str) }, + _ => unsafe { convert_integer(num_str, $base) }, }; // check for error parsing octal/hex prefix @@ -204,13 +278,9 @@ macro_rules! strto_impl { // account for the sign let num = num as $rettype; let num = if overflow { - platform::errno = ERANGE; + platform::ERRNO.set(ERANGE); if CHECK_SIGN { - if positive { - MAX_VAL - } else { - MIN_VAL - } + if positive { MAX_VAL } else { MIN_VAL } } else { MAX_VAL } @@ -227,52 +297,123 @@ macro_rules! strto_impl { num }}; + // this variant is used by wchar (also wcstoimax and wcstoumax from inttypes) + ($type:ident, $ptr:expr, $base:expr) => {{ + let negative = unsafe { *$ptr } == '-' as wchar_t; + if negative { + $ptr = unsafe { $ptr.add(1) }; + } + strtou_impl!($type, $ptr, $base, negative) + }}; } + #[macro_export] macro_rules! strto_float_impl { ($type:ident, $s:expr, $endptr:expr) => {{ let mut s = $s; let endptr = $endptr; - while ctype::isspace(*s as c_int) != 0 { - s = s.offset(1); + while ctype::isspace(c_int::from(unsafe{*s})) != 0 { + s = unsafe{ s.offset(1)}; } let mut result: $type = 0.0; + let mut exponent: Option<$type> = None; let mut radix = 10; - let negative = match *s as u8 { + let result_sign = match unsafe{*s} as u8 { b'-' => { - s = s.offset(1); - true + s = unsafe{s.offset(1)}; + -1.0 } b'+' => { - s = s.offset(1); - false + s = unsafe{s.offset(1)}; + 1.0 } - _ => false, + _ => 1.0, }; - if *s as u8 == b'0' && *s.offset(1) as u8 == b'x' { - s = s.offset(2); - radix = 16; - } + let rust_s = unsafe{CStr::from_ptr(s)}.to_string_lossy(); - while let Some(digit) = (*s as u8 as char).to_digit(radix) { - result *= radix as $type; - result += digit as $type; - s = s.offset(1); - } - - if *s as u8 == b'.' { - s = s.offset(1); - - let mut i = 1.0; - while let Some(digit) = (*s as u8 as char).to_digit(radix) { - i *= radix as $type; - result += digit as $type / i; - s = s.offset(1); + // detect NaN, Inf + if rust_s.to_lowercase().starts_with("inf") { + result = $type::INFINITY; + s = unsafe{s.offset(3)}; + } else if rust_s.to_lowercase().starts_with("nan") { + // we cannot signal negative NaN in LLVM backed languages + // https://github.com/rust-lang/rust/issues/73328 , https://github.com/rust-lang/rust/issues/81261 + result = $type::NAN; + s = unsafe{s.offset(3)}; + } else { + if unsafe{*s} as u8 == b'0' && unsafe{*s.offset(1)} as u8 == b'x' { + s = unsafe{s.offset(2)}; + radix = 16; } + + while let Some(digit) = (unsafe{*s} as u8 as char).to_digit(radix) { + result *= radix as $type; + result += digit as $type; + s = unsafe{s.offset(1)}; + } + + if unsafe{*s} as u8 == b'.' { + s = unsafe{s.offset(1)}; + + let mut i = 1.0; + while let Some(digit) = (unsafe{*s} as u8 as char).to_digit(radix) { + i *= radix as $type; + result += digit as $type / i; + s = unsafe{s.offset(1)}; + } + } + + let s_before_exponent = s; + + exponent = match (unsafe{*s} as u8, radix) { + (b'e' | b'E', 10) | (b'p' | b'P', 16) => { + s = unsafe{s.offset(1)}; + + let is_exponent_positive = match unsafe{*s} as u8 { + b'-' => { + s = unsafe{s.offset(1)}; + false + } + b'+' => { + s = unsafe{s.offset(1)}; + true + } + _ => true, + }; + + // Exponent digits are always in base 10. + if (unsafe{*s} as u8 as char).is_digit(10) { + let mut exponent_value = 0; + + while let Some(digit) = (unsafe{*s} as u8 as char).to_digit(10) { + exponent_value *= 10; + exponent_value += digit; + s = unsafe{s.offset(1)}; + } + + let exponent_base = match radix { + 10 => 10u128, + 16 => 2u128, + _ => unreachable!(), + }; + + if is_exponent_positive { + Some(exponent_base.pow(exponent_value) as $type) + } else { + Some(1.0 / (exponent_base.pow(exponent_value) as $type)) + } + } else { + // Exponent had no valid digits after 'e'/'p' and '+'/'-', rollback + s = s_before_exponent; + None + } + } + _ => None, + }; } if !endptr.is_null() { @@ -280,13 +421,165 @@ macro_rules! strto_float_impl { // const input but mut output, yet the man page says // "stores the address of the first invalid character in *endptr" // so obviously it doesn't want us to clone it. - *endptr = s as *mut _; + unsafe{*endptr = s.cast_mut()}; } - if negative { - -result + if let Some(exponent) = exponent { + result_sign * result * exponent } else { - result + result_sign * result } }}; } + +/// Project an `Out` to `struct X { field: Out }`. +/// +/// It is allowed to include only a subset of the struct's fields. The struct must implement +/// `OutProject`. +#[macro_export] +macro_rules! out_project { + { + let $struct:ty { $($field:ident : $fieldty:ty),*$(,)? } = $src:ident; + } => { + // Verify $src actually has type Out<$struct>. Also verify it implements `OutProject`. This + // excludes + // + // - the case where $src is Out<&Struct>, where it would be very UB to just construct a + // writable reference to $src.$field, or a smart pointer + // - the case where there are unaligned fields where it would be UB to call ptr::write to + // them (requiring packed structs) + { + fn ensure_type(_t: &$crate::out::Out) {} + ensure_type::<$struct>(&$src); + } + // Verify there are no duplicate struct fields. This is not strictly necessary as Out lacks + // the noalias requirement, but forbidding the same field to occur multiple times would + // allow both cases. The compiler will reject any struct that reuses the same identifier. + const _: () = { + $( + if ::core::mem::offset_of!($struct, $field) % ::core::mem::align_of::<$fieldty>() != 0 { + panic!(concat!("unaligned field ", stringify!($field), " of struct ", stringify!($struct), ".")); + } + )* + struct S { + $( + $field: $fieldty + ),* + } + }; + + // Finally, create an Out<$fieldty> for each field. + $( + // getting the pointer to $field is safe + let $field = unsafe { &raw mut (*$crate::out::Out::<_>::as_mut_ptr(&mut $src)).$field }; + )* + $( + let mut $field: $crate::out::Out<$fieldty> = unsafe { + // SAFETY: the only guarantee is that the pointer is valid and writable for the + // duration of 'b where $src: Out<'b, T>. But if so, and T is a struct, that + // must also be true for all the struct fields. + $crate::out::Out::with_lifetime_of( + $crate::out::Out::nonnull($field), + &$src, + ) + }; + )* + } +} +#[macro_export] +macro_rules! OutProject { + derive() { $(#[$($attrs:meta),*])* $v:vis struct $name:ident { + $( + $(#[$($fa:meta),*])* $fv:vis $field:ident : $type:ty + ),*$(,)? + } } => { + // SAFETY: As simple as it is, OutProject is valid for any struct, and the pattern we have + // matched above ensures $name is one. + unsafe impl $crate::out::OutProject for $name {} + } +} +#[macro_export] +#[cfg(not(feature = "check_against_libc_crate"))] +macro_rules! CheckVsLibcCrate { + derive() { $(#[$($attrs:meta),*])* $v:vis struct $name:ident { + $( + $(#[$($fa:meta),*])* $fv:vis $field:ident : $type:ty + ),*$(,)? + } } => { + } +} + +// TODO: probably exists nice nightly features that allow conflicting impls. Then we wouldn't need +// much of this redundant code just to say A == B -> B == A and say A == B -> *mut A == *mut B. +pub trait LibcTypeEquals {} +//impl LibcTypeEquals for () {} +impl LibcTypeEquals<*mut A, *mut B> for () where (): LibcTypeEquals {} +impl LibcTypeEquals<*const A, *const B> for () where (): LibcTypeEquals {} +impl LibcTypeEquals<[A; N], [B; N]> for () where (): LibcTypeEquals {} +macro_rules! for_primitive_int( + ($i:ident) => { + impl LibcTypeEquals<$i, $i> for () {} + } +); +for_primitive_int!(u8); +for_primitive_int!(u16); +for_primitive_int!(u32); +for_primitive_int!(u64); +for_primitive_int!(u128); +for_primitive_int!(usize); +for_primitive_int!(i8); +for_primitive_int!(i16); +for_primitive_int!(i32); +for_primitive_int!(i64); +for_primitive_int!(i128); +for_primitive_int!(isize); +impl LibcTypeEquals for () {} +#[cfg(feature = "check_against_libc_crate")] +impl LibcTypeEquals<__libc_only_for_layout_checks::c_void, crate::platform::types::c_void> for () {} +#[cfg(feature = "check_against_libc_crate")] +impl LibcTypeEquals for () {} + +//impl LibcTypeEquals<__libc_only_for_layout_checks::c_void> + +/// Derive macro which checks that structs here are defined the same as in the libc crate. Perhaps +/// not sufficiently rigorous to soundly cast between the types, but should catch most mistakes. +#[macro_export] +#[cfg(feature = "check_against_libc_crate")] +macro_rules! CheckVsLibcCrate { + // XXX: not sure we can have the name be different from libc::$name without parameters to the + // derive macro + derive() { $(#[$($attrs:meta),*])* $v:vis struct $name:ident { + $( + $(#[$($fa:meta),*])* $fv:vis $field:ident : $type:ty + ),*$(,)? + } } => { + // TODO: check repr(C)? probably possible to match on $attrs + #[allow(dead_code)] + const _: () = { + if ::core::mem::size_of::<$name>() != ::core::mem::size_of::<::__libc_only_for_layout_checks::$name>() { + panic!("struct size mismatch"); + } + if ::core::mem::align_of::<$name>() != ::core::mem::align_of::<::__libc_only_for_layout_checks::$name>() { + panic!("struct alignment mismatch"); + } + $( + if ::core::mem::offset_of!($name, $field) != ::core::mem::offset_of!(__libc_only_for_layout_checks::$name, $field) { + panic!("struct field offset mismatch"); + } + )* + }; + $( + // check all field types are equivalent + #[allow(dead_code)] + const _: () = { + fn ensure_ty(a: A, b: B) where (): $crate::macros::LibcTypeEquals:: {} + fn for_libc(a: $name, b: __libc_only_for_layout_checks::$name) { + let a: $type = panic!("never called"); + ensure_ty(a, b.$field); + } + }; + )* + impl $crate::macros::LibcTypeEquals<$name, __libc_only_for_layout_checks::$name> for () {} + impl $crate::macros::LibcTypeEquals<__libc_only_for_layout_checks::$name, $name> for () {} + } +} diff --git a/src/out.rs b/src/out.rs new file mode 100644 index 0000000000..5729a82b64 --- /dev/null +++ b/src/out.rs @@ -0,0 +1,231 @@ +//! Wrapper for the "out pointer" pattern. +//! +//! This is functionally equivalent to `&Cell>` except the only allowed operation is +//! to write a `T`. Using `MaybeUninit` directly would not have been equally general; a +//! `&mut MaybeUninit` could never then be created from a `&mut T` and passed to safe code, +//! which can safely replace it with `MaybeUninit::uninit` and make the existence of `&mut T` UB. +//! +//! As for the "`&Cell<...>`", this is to be slightly weaker than Rust's normally strict +//! requirement that `&mut` references are never aliased, which can typically not be assumed when +//! getting pointers from C. + +use core::{cell::UnsafeCell, fmt, marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; + +/// Wrapper for write-only "out pointers" that are safe to write to +// TODO: We may want to change this to &mut MaybeUninit, or have a generic parameter deciding +// whether it should be noalias or not +#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Out<'a, T: ?Sized> { + ptr: NonNull, + _marker: PhantomData<&'a UnsafeCell>, +} +impl<'a, T: ?Sized> Out<'a, T> { + /// # Safety + /// + /// - pointer must either be NULL, or be valid for the duration of lifetime `'a` + #[inline] + pub unsafe fn nullable(ptr: *mut T) -> Option { + Some(Self { + ptr: NonNull::new(ptr)?, + _marker: PhantomData, + }) + } + /// # Safety + /// + /// - pointer must be valid for the duration of lifetime `'a` + #[inline] + pub unsafe fn nonnull(ptr: *mut T) -> Self { + if cfg!(debug_assertions) { + assert!(!ptr.is_null()); + } + Self { + ptr: unsafe { NonNull::new_unchecked(ptr) }, + _marker: PhantomData, + } + } + #[inline] + pub fn from_mut(r: &'a mut T) -> Self { + // SAFETY: + // + // - `r` will obviously have the same lifetime as Self + // - a Rust reference is obviously valid as a pointer, and the lifetime is tied to that of + // this struct + unsafe { Self::nonnull(r) } + } + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut T { + self.ptr.as_ptr() + } +} +impl<'a, T> Out<'a, T> { + #[inline] + pub fn from_uninit_mut(r: &'a mut MaybeUninit) -> Self { + // SAFETY: + // + // Same as for from_mut. It's fine if *r is uninitialized, as this wrapper only allows + // writes. + unsafe { Self::nonnull(r.as_mut_ptr()) } + } + #[inline] + pub fn write(&mut self, t: T) { + unsafe { + self.ptr.as_ptr().write(t); + } + } +} +impl<'a, T, const N: usize> Out<'a, [T; N]> { + #[inline] + pub fn as_slice_mut<'b>(&'b mut self) -> Out<'b, [T]> { + unsafe { + let ptr: *mut [T; N] = self.as_mut_ptr(); + Out::from_raw_parts(ptr.cast::(), N) + } + } +} +impl<'a, T> Out<'a, [T]> { + /// # Safety + /// + /// If `len > 0`, `ptr` be valid for `len` elements of `T`, during lifetime `'a`. + pub unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self { + // Empty slices must be non-NULL in Rust, but C typically does not force this for + // pointer-length pairs. + let ptr = if len == 0 { + core::ptr::dangling_mut::() + } else { + ptr + }; + unsafe { Self::nonnull(core::ptr::slice_from_raw_parts_mut(ptr, len)) } + } + pub fn len(&self) -> usize { + self.ptr.as_ptr().len() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + // TODO: Maybe strengthen lifetimes? + #[inline] + pub fn split_at_checked<'b>(&'b mut self, n: usize) -> Option<[Out<'b, [T]>; 2]> { + let l = self.ptr.len(); + if n > l { + return None; + } + Some([ + Out { + ptr: unsafe { + NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut( + self.ptr.as_mut_ptr(), + n, + )) + }, + _marker: PhantomData, + }, + Out { + ptr: unsafe { + NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut( + self.ptr.as_mut_ptr().add(n), + l - n, + )) + }, + _marker: PhantomData, + }, + ]) + } + #[inline] + pub fn copy_from_slice(&mut self, src: &[T]) + where + T: Copy, + { + assert_eq!( + self.ptr.len(), + src.len(), + "Out::copy_from_slice size mismatch" + ); + unsafe { + // SAFETY: + // + // - we have already know from the existence of self that the slice is a valid writable + // pointer + // - src is similarly also a valid readable pointer of the same type + // - because of `T: Copy`, it is valid to copy bytes directly + // - although self.ptr may alias, src must not alias with any writable pointer, and the + // Copy bound ensures T cannot have interior mutability since `UnsafeCell: !Copy` + self.ptr + .as_mut_ptr() + .copy_from_nonoverlapping(src.as_ptr(), src.len()); + } + } + pub fn copy_common_length_from_slice(&mut self, src: &[T]) -> usize + where + T: Copy, + { + let l = src.len().min(self.len()); + self.split_at_checked(l).unwrap()[0].copy_from_slice(&src[..l]); + l + } + // TODO: better API, impl RangeBounds, also fn get(usize) -> Out + pub fn subslice<'b>(&'b mut self, start: usize, end: usize) -> Out<'b, [T]> { + assert!(start <= end); + assert!(end <= self.len()); + unsafe { Self::from_raw_parts(self.as_mut_ptr().as_mut_ptr().add(start), end - start) } + } + pub fn index<'b>(&'b mut self, i: usize) -> Out<'b, T> { + assert!(i <= self.len()); + unsafe { Out::nonnull(self.as_mut_ptr().as_mut_ptr().add(i)) } + } +} +// TODO: use bytemuck +impl Out<'_, [T]> { + pub fn zero(&mut self) { + let l = self.ptr.len(); + unsafe { + // SAFETY: + // - already know the pointer is valid up to its length + // - the Plain trait ensures zero is a valid bit pattern + self.ptr.as_mut_ptr().write_bytes(0, l) + } + } + #[inline] + pub fn cast_slice_to<'b, U>(mut self) -> Out<'b, [U]> + where + T: CastSlice, + { + assert_eq!(self.as_mut_ptr().as_mut_ptr() as usize % align_of::(), 0); + + let byte_length = self.as_mut_ptr().len() * size_of::(); + + unsafe { + Out::from_raw_parts( + self.as_mut_ptr().as_mut_ptr().cast(), + byte_length / size_of::(), + ) + } + } +} +// TODO: use bytemuck +pub unsafe trait CastSlice {} +unsafe impl CastSlice for u8 {} +unsafe impl CastSlice for i8 {} +unsafe impl CastSlice for u8 {} +unsafe impl CastSlice for i8 {} + +impl fmt::Pointer for Out<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:p}", self.ptr) + } +} +impl fmt::Debug for Out<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[Out: {:p}]", self.ptr) + } +} +/// Marker trait for types where it is sound to turn `Out` into `struct { ...: +/// Out<...> }` by simply referencing fields. This is safe for any struct but must not be +/// implemented for `Deref` types so that `Out<&struct { ... }>` is never projected in a way that +/// adds mutability. +pub unsafe trait OutProject {} + +impl<'a, T: ?Sized> Out<'a, T> { + pub unsafe fn with_lifetime_of<'b, U: ?Sized>(mut self, u: &'b U) -> Out<'b, T> { + unsafe { Out::nonnull(self.as_mut_ptr()) } + } +} diff --git a/src/platform/allocator/dlmalloc.rs b/src/platform/allocator/dlmalloc.rs deleted file mode 100644 index 329df7389a..0000000000 --- a/src/platform/allocator/dlmalloc.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::ALLOCATOR; -use core::{ - alloc::{GlobalAlloc, Layout}, - sync::atomic::{AtomicUsize, Ordering}, -}; - -use super::types::*; - -extern "C" { - fn create_mspace(capacity: size_t, locked: c_int) -> usize; - fn mspace_malloc(msp: usize, bytes: size_t) -> *mut c_void; - fn mspace_memalign(msp: usize, alignment: size_t, bytes: size_t) -> *mut c_void; - fn mspace_realloc(msp: usize, oldmem: *mut c_void, bytes: size_t) -> *mut c_void; - fn mspace_free(msp: usize, mem: *mut c_void); -//fn dlmalloc(bytes: size_t) -> *mut c_void; -//fn dlmemalign(alignment: size_t, bytes: size_t) -> *mut c_void; -//fn dlrealloc(oldmem: *mut c_void, bytes: size_t) -> *mut c_void; -//fn dlfree(mem: *mut c_void); -} - -pub struct Allocator { - mstate: AtomicUsize, -} - -pub const NEWALLOCATOR: Allocator = Allocator { - mstate: AtomicUsize::new(0), -}; - -impl Allocator { - pub fn set_book_keeper(&self, mstate: usize) { - self.mstate.store(mstate, Ordering::Relaxed); - } - pub fn get_book_keeper(&self) -> usize { - self.mstate.load(Ordering::Relaxed) - } -} -unsafe impl<'a> GlobalAlloc for Allocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - alloc_align(layout.size(), layout.align()) as *mut u8 - } - - unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - free(ptr as *mut c_void) - } -} - -pub unsafe fn alloc(size: usize) -> *mut c_void { - mspace_malloc(ALLOCATOR.get_book_keeper(), size) -} - -pub unsafe fn alloc_align(size: usize, alignment: usize) -> *mut c_void { - mspace_memalign(ALLOCATOR.get_book_keeper(), alignment, size) -} - -pub unsafe fn realloc(ptr: *mut c_void, size: size_t) -> *mut c_void { - mspace_realloc(ALLOCATOR.get_book_keeper(), ptr, size) -} - -pub unsafe fn free(ptr: *mut c_void) { - mspace_free(ALLOCATOR.get_book_keeper(), ptr) -} - -pub fn new_mspace() -> usize { - unsafe { create_mspace(0, 0) } -} diff --git a/src/platform/allocator/mod.rs b/src/platform/allocator/mod.rs new file mode 100644 index 0000000000..acdb643787 --- /dev/null +++ b/src/platform/allocator/mod.rs @@ -0,0 +1,121 @@ +use core::{ + alloc::{GlobalAlloc, Layout}, + cell::SyncUnsafeCell, + cmp, + mem::align_of, + ptr::{self, copy_nonoverlapping, write_bytes}, + sync::atomic::{AtomicPtr, Ordering}, +}; + +mod sys; +use super::types::*; +use crate::{ALLOCATOR, sync::Mutex}; +use dlmalloc::DlmallocCApi; + +pub type Dlmalloc = DlmallocCApi; + +#[allow(clippy::declare_interior_mutable_const)] +pub const NEWALLOCATOR: Allocator = Allocator::new(); + +pub struct Allocator { + inner: SyncUnsafeCell>, + pub ptr: AtomicPtr>, +} + +impl Allocator { + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Allocator { + inner: SyncUnsafeCell::new(Mutex::new(Dlmalloc::new(sys::System::new()))), + ptr: AtomicPtr::new(ptr::null_mut()), + } + } + + pub fn get(&self) -> *const Mutex { + let ptr = self.ptr.load(Ordering::Acquire); + if !ptr.is_null() { + return ptr; + } + + self.inner.get() + } + + pub fn set(&self, mspace: *const Mutex) { + self.ptr.store(mspace.cast_mut(), Ordering::Release); + } +} + +unsafe impl GlobalAlloc for Allocator { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if layout.align() <= align_of::() { + unsafe { (*self.get()).lock().malloc(layout.size()) } + } else { + unsafe { (*self.get()).lock().memalign(layout.align(), layout.size()) } + } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { (*self.get()).lock().free(ptr) } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let ptr = unsafe { self.alloc(layout) }; + if !ptr.is_null() && unsafe { (*self.get()).lock().calloc_must_clear(ptr) } { + unsafe { write_bytes(ptr, 0, layout.size()) }; + } + ptr + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + if layout.align() <= align_of::() { + unsafe { (*self.get()).lock().realloc(ptr, new_size) } + } else { + let new = + unsafe { self.alloc(Layout::from_size_align_unchecked(new_size, layout.align())) }; + let old_size = layout.size(); + + if !new.is_null() { + let size = cmp::min(old_size, new_size); + unsafe { copy_nonoverlapping(ptr, new, size) }; + } + + unsafe { (*self.get()).lock().free(ptr) }; + + new + } + } +} + +pub unsafe fn alloc(size: size_t) -> *mut c_void { + unsafe { (*ALLOCATOR.get()).lock().malloc(size) }.cast() +} + +pub unsafe fn alloc_align(size: size_t, alignment: size_t) -> *mut c_void { + unsafe { (*ALLOCATOR.get()).lock().memalign(alignment, size) }.cast() +} + +pub unsafe fn realloc(ptr: *mut c_void, size: size_t) -> *mut c_void { + if ptr.is_null() { + unsafe { (*ALLOCATOR.get()).lock().malloc(size) }.cast() + } else { + unsafe { (*ALLOCATOR.get()).lock().realloc(ptr.cast(), size) }.cast() + } +} + +pub unsafe fn free(ptr: *mut c_void) { + if ptr.is_null() { + return; + } + unsafe { (*ALLOCATOR.get()).lock().free(ptr.cast()) } +} + +pub unsafe fn alloc_usable_size(ptr: *mut c_void) -> size_t { + if ptr.is_null() { + return 0; + } + unsafe { (*ALLOCATOR.get()).lock().usable_size(ptr.cast()) } +} diff --git a/src/platform/allocator/ralloc.rs b/src/platform/allocator/ralloc.rs deleted file mode 100644 index c4fd1a8109..0000000000 --- a/src/platform/allocator/ralloc.rs +++ /dev/null @@ -1,48 +0,0 @@ -extern crate ralloc; - -pub use ralloc::Allocator; - -unsafe fn alloc_inner(size: usize, offset: usize, align: usize) -> *mut c_void { - let ptr = ralloc::alloc(size + offset, align); - if !ptr.is_null() { - *(ptr as *mut u64) = (size + offset) as u64; - *(ptr as *mut u64).offset(1) = align as u64; - ptr.offset(offset as isize) as *mut c_void - } else { - ptr as *mut c_void - } -} - -pub unsafe fn alloc(size: usize) -> *mut c_void { - alloc_inner(size, 16, 8) -} - -pub unsafe fn alloc_align(size: usize, alignment: usize) -> *mut c_void { - let mut align = 32; - while align <= alignment { - align *= 2; - } - - alloc_inner(size, align / 2, align) -} - -pub unsafe fn realloc(ptr: *mut c_void, size: size_t) -> *mut c_void { - let old_ptr = (ptr as *mut u8).offset(-16); - let old_size = *(old_ptr as *mut u64); - let align = *(old_ptr as *mut u64).offset(1); - let ptr = ralloc::realloc(old_ptr, old_size as usize, size + 16, align as usize); - if !ptr.is_null() { - *(ptr as *mut u64) = (size + 16) as u64; - *(ptr as *mut u64).offset(1) = align; - ptr.offset(16) as *mut c_void - } else { - ptr as *mut c_void - } -} - -pub unsafe fn free(ptr: *mut c_void) { - let ptr = (ptr as *mut u8).offset(-16); - let size = *(ptr as *mut u64); - let _align = *(ptr as *mut u64).offset(1); - ralloc::free(ptr, size as usize); -} diff --git a/src/platform/allocator/sys.rs b/src/platform/allocator/sys.rs new file mode 100644 index 0000000000..401910d59b --- /dev/null +++ b/src/platform/allocator/sys.rs @@ -0,0 +1,129 @@ +use crate::{ + header::{ + pthread::pthread_atfork, + sys_mman::{self, MREMAP_MAYMOVE}, + }, + platform::{Pal, Sys}, + sync::Mutex, +}; +use core::ptr; + +use dlmalloc::Allocator; + +/// System setting for Redox/Linux +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +static LOCK: Mutex<()> = Mutex::new(()); + +unsafe impl Allocator for System { + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let Ok(addr) = (unsafe { + Sys::mmap( + ptr::null_mut(), + size, + sys_mman::PROT_WRITE | sys_mman::PROT_READ, + sys_mman::MAP_ANON | sys_mman::MAP_PRIVATE, + -1, + 0, + ) + }) else { + return (ptr::null_mut(), 0, 0); + }; + (addr.cast::(), size, 0) + } + + fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8 { + let flags = if can_move { MREMAP_MAYMOVE } else { 0 }; + let Ok(ptr) = + (unsafe { Sys::mremap(ptr.cast(), oldsize, newsize, flags, ptr::null_mut()) }) + else { + return ptr::null_mut(); + }; + ptr.cast::() + } + + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { + unsafe { + if Sys::mremap(ptr.cast(), oldsize, newsize, 0, ptr::null_mut()).is_ok() { + return true; + } + Sys::munmap(ptr.add(newsize).cast(), oldsize - newsize).is_ok() + } + } + + fn free(&self, ptr: *mut u8, size: usize) -> bool { + unsafe { Sys::munmap(ptr.cast(), size).is_ok() } + } + + fn can_release_part(&self, _flags: u32) -> bool { + true + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + 4096 + } +} + +pub fn acquire_global_lock() { + unsafe { + // SAFETY: No data inside + LOCK.manual_lock(); + } +} + +pub(super) fn release_global_lock() { + unsafe { + // SAFETY: No data inside + LOCK.manual_unlock(); + } +} + +/// Allows the allocator to remain unsable in the child process, +/// after a call to `fork(2)` +/// +/// # Safety +/// +/// if used, this function must be called, +/// before any allocations are made with the global allocator. +pub unsafe fn enable_alloc_after_fork() { + // atfork must only be called once, to avoid a deadlock, + // where the handler attempts to acquire the global lock twice + static mut FORK_PROTECTED: bool = false; + + extern "C" fn _acquire_global_lock() { + acquire_global_lock() + } + + extern "C" fn _release_global_lock() { + release_global_lock() + } + + acquire_global_lock(); + // if a process forks, + // it will acquire the lock before any other thread, + // protecting it from deadlock, + // due to the child being created with only the calling thread. + unsafe { + if !FORK_PROTECTED { + pthread_atfork( + Some(_acquire_global_lock), + Some(_release_global_lock), + Some(_release_global_lock), + ); + FORK_PROTECTED = true; + } + } + release_global_lock(); +} diff --git a/src/platform/auxv_defs.rs b/src/platform/auxv_defs.rs new file mode 100644 index 0000000000..748800aa44 --- /dev/null +++ b/src/platform/auxv_defs.rs @@ -0,0 +1,60 @@ +pub const AT_NULL: usize = 0; /* End of vector */ +pub const AT_IGNORE: usize = 1; /* Entry should be ignored */ +pub const AT_EXECFD: usize = 2; /* File descriptor of program */ +pub const AT_PHDR: usize = 3; /* Program headers for program */ +pub const AT_PHENT: usize = 4; /* Size of program header entry */ +pub const AT_PHNUM: usize = 5; /* Number of program headers */ +pub const AT_PAGESZ: usize = 6; /* System page size */ +pub const AT_BASE: usize = 7; /* Base address of interpreter */ +pub const AT_FLAGS: usize = 8; /* Flags */ +pub const AT_ENTRY: usize = 9; /* Entry point of program */ +pub const AT_NOTELF: usize = 10; /* Program is not ELF */ +pub const AT_UID: usize = 11; /* Real uid */ +pub const AT_EUID: usize = 12; /* Effective uid */ +pub const AT_GID: usize = 13; /* Real gid */ +pub const AT_EGID: usize = 14; /* Effective gid */ +pub const AT_CLKTCK: usize = 17; /* Frequency of times() */ +pub const AT_PLATFORM: usize = 15; /* String identifying platform. */ +pub const AT_HWCAP: usize = 16; /* Machine-dependent hints about */ +pub const AT_FPUCW: usize = 18; /* Used FPU control word. */ +pub const AT_DCACHEBSIZE: usize = 19; /* Data cache block size. */ +pub const AT_ICACHEBSIZE: usize = 20; /* Instruction cache block size. */ +pub const AT_UCACHEBSIZE: usize = 21; /* Unified cache block size. */ +pub const AT_IGNOREPPC: usize = 22; /* Entry should be ignored. */ +pub const AT_BASE_PLATFORM: usize = 24; /* String identifying real platforms.*/ +pub const AT_RANDOM: usize = 25; /* Address of 16 random bytes. */ +pub const AT_HWCAP2: usize = 26; /* More machine-dependent hints about*/ +pub const AT_EXECFN: usize = 31; /* Filename of executable. */ + +// TODO: Downgrade aux vectors to getauxval constants on Redox, and use a regular struct for +// passing important runtime info between exec (or posix_spawn in the future) calls. + +#[cfg(target_os = "redox")] +// XXX: The name AT_CWD is already used in openat... for a completely different purpose. +pub const AT_REDOX_INITIAL_CWD_PTR: usize = 32; +#[cfg(target_os = "redox")] +pub const AT_REDOX_INITIAL_CWD_LEN: usize = 33; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_INHERITED_SIGIGNMASK: usize = 34; +#[cfg(all(target_os = "redox", target_pointer_width = "32"))] +pub const AT_REDOX_INHERITED_SIGIGNMASK_HI: usize = 35; +#[cfg(target_os = "redox")] +pub const AT_REDOX_INHERITED_SIGPROCMASK: usize = 36; +#[cfg(all(target_os = "redox", target_pointer_width = "32"))] +pub const AT_REDOX_INHERITED_SIGPROCMASK_HI: usize = 37; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_UMASK: usize = 40; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_PROC_FD: usize = 41; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_THR_FD: usize = 42; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_NS_FD: usize = 43; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_CWD_FD: usize = 44; diff --git a/src/platform/linux/epoll.rs b/src/platform/linux/epoll.rs index b92dc08d6b..ce5b9be3da 100644 --- a/src/platform/linux/epoll.rs +++ b/src/platform/linux/epoll.rs @@ -1,34 +1,45 @@ -use super::{ - super::{types::*, PalEpoll}, - e, Sys, +use core::mem; + +use super::{Sys, e_raw}; +use crate::{ + error::Result, + header::{bits_sigset_t::sigset_t, sys_epoll::epoll_event}, + platform::{ + PalEpoll, + types::{c_int, size_t}, + }, }; -use crate::header::{signal::sigset_t, sys_epoll::epoll_event}; impl PalEpoll for Sys { - fn epoll_create1(flags: c_int) -> c_int { - unsafe { e(syscall!(EPOLL_CREATE1, flags)) as c_int } + fn epoll_create1(flags: c_int) -> Result { + Ok(unsafe { e_raw(syscall!(EPOLL_CREATE1, flags))? as c_int }) } - fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int { - unsafe { e(syscall!(EPOLL_CTL, epfd, op, fd, event)) as c_int } + unsafe fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> Result<()> { + unsafe { + e_raw(syscall!(EPOLL_CTL, epfd, op, fd, event))?; + } + Ok(()) } - fn epoll_pwait( + unsafe fn epoll_pwait( epfd: c_int, events: *mut epoll_event, maxevents: c_int, timeout: c_int, sigmask: *const sigset_t, - ) -> c_int { + ) -> Result { + let sigsetsize: size_t = mem::size_of::(); unsafe { - e(syscall!( + e_raw(syscall!( EPOLL_PWAIT, epfd, events, maxevents, timeout, - sigmask - )) as c_int + sigmask, + sigsetsize + )) } } } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 7e996a0fc6..995df1b2d3 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -1,35 +1,42 @@ -use core::ptr; -use core_io::Write; +#[cfg(target_arch = "x86_64")] +use core::arch::asm; -use super::{errno, types::*, Pal}; +use super::{Pal, types::*}; use crate::{ c_str::CStr, - header::{dirent::dirent, signal::SIGCHLD, sys_stat::S_IFIFO}, -}; -// use header::sys_resource::rusage; -use crate::header::{ - sys_resource::rlimit, - sys_stat::stat, - sys_statvfs::statvfs, - sys_time::{timeval, timezone}, + error::{Errno, Result}, + header::{ + bits_timespec::timespec, + dirent::dirent, + errno::{EINVAL, EIO}, + fcntl::{AT_EMPTY_PATH, AT_FDCWD, AT_REMOVEDIR}, + signal::{SIGCHLD, sigevent}, + sys_resource::{rlimit, rusage}, + sys_select::timeval, + sys_stat::{S_IFIFO, stat}, + sys_statvfs::statvfs, + sys_time::timezone, + sys_utsname::utsname, + time::itimerspec, + unistd::{SEEK_CUR, SEEK_SET}, + }, + ld_so::tcb::OsSpecific, + out::Out, }; +use core::{num::NonZeroU64, ptr}; // use header::sys_times::tms; -use crate::header::{sys_utsname::utsname, time::timespec}; mod epoll; mod ptrace; mod signal; mod socket; -const AT_FDCWD: c_int = -100; -const AT_EMPTY_PATH: c_int = 0x1000; -const AT_REMOVEDIR: c_int = 0x200; - const SYS_CLONE: usize = 56; const CLONE_VM: usize = 0x0100; const CLONE_FS: usize = 0x0200; const CLONE_FILES: usize = 0x0400; const CLONE_SIGHAND: usize = 0x0800; +const CLONE_THREAD: usize = 0x00010000; #[repr(C)] #[derive(Default)] @@ -48,27 +55,24 @@ struct linux_statfs { f_spare: [c_long; 4], } -pub fn e(sys: usize) -> usize { - if (sys as isize) < 0 && (sys as isize) >= -256 { - unsafe { - errno = -(sys as isize) as c_int; - } - !0 +// TODO +const ERRNO_MAX: usize = 4095; + +pub fn e_raw(sys: usize) -> Result { + if sys > ERRNO_MAX.wrapping_neg() { + Err(Errno(sys.wrapping_neg() as _)) } else { - sys + Ok(sys) } } +/// Linux syscall implementation of [`Pal`]. pub struct Sys; impl Sys { - // fn getrusage(who: c_int, r_usage: *mut rusage) -> c_int { - // e(unsafe { syscall!(GETRUSAGE, who, r_usage) }) as c_int - // } - - pub unsafe fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> c_int { + pub unsafe fn ioctl(fd: c_int, request: c_ulong, out: *mut c_void) -> Result { // TODO: Somehow support varargs to syscall?? - e(syscall!(IOCTL, fd, request, out)) as c_int + Ok(e_raw(syscall!(IOCTL, fd, request, out))? as c_int) } // fn times(out: *mut tms) -> clock_t { @@ -77,52 +81,83 @@ impl Sys { } impl Pal for Sys { - fn access(path: &CStr, mode: c_int) -> c_int { - e(unsafe { syscall!(ACCESS, path.as_ptr(), mode) }) as c_int + fn faccessat(fd: c_int, path: CStr, amode: c_int, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(FACCESSAT, fd, path.as_ptr(), amode, flags) }).map(|_| ()) } - fn brk(addr: *mut c_void) -> *mut c_void { - unsafe { syscall!(BRK, addr) as *mut c_void } + unsafe fn brk(addr: *mut c_void) -> Result<*mut c_void> { + Ok(e_raw(unsafe { syscall!(BRK, addr) })? as *mut c_void) } - fn chdir(path: &CStr) -> c_int { - e(unsafe { syscall!(CHDIR, path.as_ptr()) }) as c_int + fn chdir(path: CStr) -> Result<()> { + e_raw(unsafe { syscall!(CHDIR, path.as_ptr()) }).map(|_| ()) } - fn chmod(path: &CStr, mode: mode_t) -> c_int { - e(unsafe { syscall!(FCHMODAT, AT_FDCWD, path.as_ptr(), mode, 0) }) as c_int - } - - fn chown(path: &CStr, owner: uid_t, group: gid_t) -> c_int { - e(unsafe { + fn fchownat(fildes: c_int, path: CStr, owner: uid_t, group: gid_t, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!( FCHOWNAT, - AT_FDCWD, + fildes, path.as_ptr(), owner as u32, - group as u32 + group as u32, + flags ) - }) as c_int + }) + .map(|_| ()) } - fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_int { - e(unsafe { syscall!(CLOCK_GETTIME, clk_id, tp) }) as c_int + fn clock_getres(clk_id: clockid_t, res: Option>) -> Result<()> { + e_raw(unsafe { + syscall!( + CLOCK_GETRES, + clk_id, + res.map_or(core::ptr::null_mut(), |mut p| p.as_mut_ptr()) + ) + }) + .map(|_| ()) } - fn close(fildes: c_int) -> c_int { - e(unsafe { syscall!(CLOSE, fildes) }) as c_int + fn clock_gettime(clk_id: clockid_t, mut tp: Out) -> Result<()> { + e_raw(unsafe { syscall!(CLOCK_GETTIME, clk_id, tp.as_mut_ptr()) }).map(|_| ()) } - fn dup(fildes: c_int) -> c_int { - e(unsafe { syscall!(DUP, fildes) }) as c_int + unsafe fn clock_settime(clk_id: clockid_t, tp: *const timespec) -> Result<()> { + e_raw(syscall!(CLOCK_SETTIME, clk_id, tp)).map(|_| ()) } - fn dup2(fildes: c_int, fildes2: c_int) -> c_int { - e(unsafe { syscall!(DUP3, fildes, fildes2, 0) }) as c_int + fn close(fildes: c_int) -> Result<()> { + e_raw(unsafe { syscall!(CLOSE, fildes) }).map(|_| ()) } - unsafe fn execve(path: &CStr, argv: *const *mut c_char, envp: *const *mut c_char) -> c_int { - e(syscall!(EXECVE, path.as_ptr(), argv, envp)) as c_int + fn dup(fildes: c_int) -> Result { + e_raw(unsafe { syscall!(DUP, fildes) }).map(|f| f as c_int) + } + + fn dup2(fildes: c_int, fildes2: c_int) -> Result { + e_raw(unsafe { syscall!(DUP3, fildes, fildes2, 0) }).map(|f| f as c_int) + } + + unsafe fn execve(path: CStr, argv: *const *mut c_char, envp: *const *mut c_char) -> Result<()> { + e_raw(syscall!(EXECVE, path.as_ptr(), argv, envp))?; + unreachable!() + } + unsafe fn fexecve( + fildes: c_int, + argv: *const *mut c_char, + envp: *const *mut c_char, + ) -> Result<()> { + let empty = b"\0"; + let empty_ptr = empty.as_ptr().cast::(); + e_raw(syscall!( + EXECVEAT, + fildes, + empty_ptr, + argv, + envp, + AT_EMPTY_PATH + ))?; + unreachable!() } fn exit(status: c_int) -> ! { @@ -131,172 +166,353 @@ impl Pal for Sys { } loop {} } - - fn fchdir(fildes: c_int) -> c_int { - e(unsafe { syscall!(FCHDIR, fildes) }) as c_int + unsafe fn exit_thread(_stack_base: *mut (), _stack_size: usize) -> ! { + // TODO + Self::exit(0) } - fn fchmod(fildes: c_int, mode: mode_t) -> c_int { - e(unsafe { syscall!(FCHMOD, fildes, mode) }) as c_int + fn fchdir(fildes: c_int) -> Result<()> { + e_raw(unsafe { syscall!(FCHDIR, fildes) }).map(|_| ()) } - fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> c_int { - e(unsafe { syscall!(FCHOWN, fildes, owner, group) }) as c_int + fn fchmod(fildes: c_int, mode: mode_t) -> Result<()> { + e_raw(unsafe { syscall!(FCHMOD, fildes, mode) }).map(|_| ()) } - fn flock(fd: c_int, operation: c_int) -> c_int { - e(unsafe { syscall!(FLOCK, fd, operation) }) as c_int + fn fchmodat(dirfd: c_int, path: Option, mode: mode_t, flags: c_int) -> Result<()> { + e_raw(unsafe { + syscall!( + FCHMODAT, + dirfd, + path.map_or(core::ptr::null(), |p| p.as_ptr()), + mode, + flags + ) + }) + .map(|_| ()) } - fn fstat(fildes: c_int, buf: *mut stat) -> c_int { + fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> Result<()> { + e_raw(unsafe { syscall!(FCHOWN, fildes, owner, group) }).map(|_| ()) + } + + fn fdatasync(fildes: c_int) -> Result<()> { + e_raw(unsafe { syscall!(FDATASYNC, fildes) }).map(|_| ()) + } + + fn flock(fd: c_int, operation: c_int) -> Result<()> { + e_raw(unsafe { syscall!(FLOCK, fd, operation) }).map(|_| ()) + } + + fn fstat(fildes: c_int, mut buf: Out) -> Result<()> { let empty = b"\0"; - let empty_ptr = empty.as_ptr() as *const c_char; - e(unsafe { syscall!(NEWFSTATAT, fildes, empty_ptr, buf, AT_EMPTY_PATH) }) as c_int + let empty_ptr = empty.as_ptr().cast::(); + e_raw(unsafe { + syscall!( + NEWFSTATAT, + fildes, + empty_ptr, + buf.as_mut_ptr(), + AT_EMPTY_PATH + ) + }) + .map(|_| ()) } - fn fstatvfs(fildes: c_int, buf: *mut statvfs) -> c_int { + fn fstatat(fildes: c_int, path: Option, mut buf: Out, flags: c_int) -> Result<()> { + e_raw(unsafe { + syscall!( + NEWFSTATAT, + fildes, + path.map_or(core::ptr::null(), |s| s.as_ptr()), + buf.as_mut_ptr(), + flags + ) + }) + .map(|_| ()) + } + + fn fstatvfs(fildes: c_int, mut buf: Out) -> Result<()> { + let buf = buf.as_mut_ptr(); + let mut kbuf = linux_statfs::default(); - let kbuf_ptr = &mut kbuf as *mut linux_statfs; - let res = e(unsafe { syscall!(FSTATFS, fildes, kbuf_ptr) }) as c_int; - if res == 0 { + let kbuf_ptr = &raw mut kbuf; + e_raw(unsafe { syscall!(FSTATFS, fildes, kbuf_ptr) })?; + + if !buf.is_null() { unsafe { - if !buf.is_null() { - (*buf).f_bsize = kbuf.f_bsize as c_ulong; - (*buf).f_frsize = if kbuf.f_frsize != 0 { - kbuf.f_frsize - } else { - kbuf.f_bsize - } as c_ulong; - (*buf).f_blocks = kbuf.f_blocks; - (*buf).f_bfree = kbuf.f_bfree; - (*buf).f_bavail = kbuf.f_bavail; - (*buf).f_files = kbuf.f_files; - (*buf).f_ffree = kbuf.f_ffree; - (*buf).f_favail = kbuf.f_ffree; - (*buf).f_fsid = kbuf.f_fsid as c_ulong; - (*buf).f_flag = kbuf.f_flags as c_ulong; - (*buf).f_namemax = kbuf.f_namelen as c_ulong; - } + (*buf).f_bsize = kbuf.f_bsize as c_ulong; + (*buf).f_frsize = if kbuf.f_frsize != 0 { + kbuf.f_frsize + } else { + kbuf.f_bsize + } as c_ulong; + (*buf).f_blocks = kbuf.f_blocks; + (*buf).f_bfree = kbuf.f_bfree; + (*buf).f_bavail = kbuf.f_bavail; + (*buf).f_files = kbuf.f_files; + (*buf).f_ffree = kbuf.f_ffree; + (*buf).f_favail = kbuf.f_ffree; + (*buf).f_fsid = kbuf.f_fsid as c_ulong; + (*buf).f_flag = kbuf.f_flags as c_ulong; + (*buf).f_namemax = kbuf.f_namelen as c_ulong; } } - res + Ok(()) } - fn fcntl(fildes: c_int, cmd: c_int, arg: c_int) -> c_int { - e(unsafe { syscall!(FCNTL, fildes, cmd, arg) }) as c_int + fn fcntl(fildes: c_int, cmd: c_int, arg: c_ulonglong) -> Result { + Ok(e_raw(unsafe { syscall!(FCNTL, fildes, cmd, arg) })? as c_int) } - fn fork() -> pid_t { - e(unsafe { syscall!(CLONE, SIGCHLD, 0, 0, 0, 0) }) as pid_t + unsafe fn fork() -> Result { + Ok(e_raw(unsafe { syscall!(CLONE, SIGCHLD, 0, 0, 0, 0) })? as pid_t) } - fn fpath(fildes: c_int, out: &mut [u8]) -> ssize_t { - let mut proc_path = b"/proc/self/fd/".to_vec(); - write!(proc_path, "{}", fildes).unwrap(); - proc_path.push(0); - + fn fpath(fildes: c_int, out: &mut [u8]) -> Result { + let proc_path = format!("/proc/self/fd/{}\0", fildes).into_bytes(); Self::readlink(CStr::from_bytes_with_nul(&proc_path).unwrap(), out) } - fn fsync(fildes: c_int) -> c_int { - e(unsafe { syscall!(FSYNC, fildes) }) as c_int + fn fsync(fildes: c_int) -> Result<()> { + e_raw(unsafe { syscall!(FSYNC, fildes) }).map(|_| ()) } - fn ftruncate(fildes: c_int, length: off_t) -> c_int { - e(unsafe { syscall!(FTRUNCATE, fildes, length) }) as c_int + fn ftruncate(fildes: c_int, length: off_t) -> Result<()> { + e_raw(unsafe { syscall!(FTRUNCATE, fildes, length) }).map(|_| ()) } - fn futex(addr: *mut c_int, op: c_int, val: c_int) -> c_int { - unsafe { syscall!(FUTEX, addr, op, val, 0, 0, 0) as c_int } + #[inline] + unsafe fn futex_wait(addr: *mut u32, val: u32, deadline: Option<×pec>) -> Result<()> { + let deadline = deadline.map_or(0, |d| ptr::from_ref(d) as usize); + e_raw(unsafe { + syscall!( + FUTEX, addr, // uaddr + 9, // futex_op: FUTEX_WAIT_BITSET + val, // val + deadline, // timeout: deadline + 0, // uaddr2/val2: 0/NULL + 0xffffffff // val3: FUTEX_BITSET_MATCH_ANY + ) + }) + .map(|_| ()) + } + #[inline] + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result { + e_raw(unsafe { + syscall!(FUTEX, addr, 1 /* FUTEX_WAKE */, num) + }) + .map(|n| n as u32) } - fn futimens(fd: c_int, times: *const timespec) -> c_int { - e(unsafe { syscall!(UTIMENSAT, fd, ptr::null::(), times, 0) }) as c_int + unsafe fn futimens(fd: c_int, times: *const timespec) -> Result<()> { + e_raw(unsafe { syscall!(UTIMENSAT, fd, ptr::null::(), times, 0) }).map(|_| ()) } - fn utimens(path: &CStr, times: *const timespec) -> c_int { - e(unsafe { syscall!(UTIMENSAT, AT_FDCWD, path.as_ptr(), times, 0) }) as c_int + unsafe fn utimens(path: CStr, times: *const timespec) -> Result<()> { + e_raw(unsafe { syscall!(UTIMENSAT, AT_FDCWD, path.as_ptr(), times, 0) }).map(|_| ()) } - fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char { - if e(unsafe { syscall!(GETCWD, buf, size) }) == !0 { - ptr::null_mut() - } else { - buf - } + fn getcwd(mut buf: Out<[u8]>) -> Result<()> { + e_raw(unsafe { + syscall!( + GETCWD, + buf.as_mut_ptr().as_mut_ptr(), + buf.as_mut_ptr().len() + ) + })?; + Ok(()) } - fn getdents(fd: c_int, dirents: *mut dirent, bytes: usize) -> c_int { - unsafe { syscall!(GETDENTS64, fd, dirents, bytes) as c_int } + fn getdents(fd: c_int, buf: &mut [u8], _off: u64) -> Result { + e_raw(unsafe { syscall!(GETDENTS64, fd, buf.as_mut_ptr(), buf.len()) }) + } + fn dir_seek(fd: c_int, off: u64) -> Result<()> { + e_raw(unsafe { syscall!(LSEEK, fd, off, SEEK_SET) })?; + Ok(()) + } + unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)> { + let dent = this_dent.as_ptr().cast::(); + Some((unsafe { (*dent).d_reclen }, unsafe { (*dent).d_off } as u64)) } fn getegid() -> gid_t { - e(unsafe { syscall!(GETEGID) }) as gid_t + // Always successful + unsafe { syscall!(GETEGID) as gid_t } } fn geteuid() -> uid_t { - e(unsafe { syscall!(GETEUID) }) as uid_t + // Always successful + unsafe { syscall!(GETEUID) as uid_t } } fn getgid() -> gid_t { - e(unsafe { syscall!(GETGID) }) as gid_t + // Always successful + unsafe { syscall!(GETGID) as gid_t } } - fn getpgid(pid: pid_t) -> pid_t { - e(unsafe { syscall!(GETPGID, pid) }) as pid_t + fn getgroups(mut list: Out<[gid_t]>) -> Result { + Ok(e_raw(unsafe { + syscall!( + GETGROUPS, + list.len() as c_int, + list.as_mut_ptr().as_mut_ptr() + ) + })? as c_int) + } + + fn getpagesize() -> usize { + 4096 + } + + fn getpgid(pid: pid_t) -> Result { + Ok(e_raw(unsafe { syscall!(GETPGID, pid) })? as pid_t) } fn getpid() -> pid_t { - e(unsafe { syscall!(GETPID) }) as pid_t + // Always successful + unsafe { syscall!(GETPID) as pid_t } } fn getppid() -> pid_t { - e(unsafe { syscall!(GETPPID) }) as pid_t + // Always successful + unsafe { syscall!(GETPPID) as pid_t } } - fn getrandom(buf: &mut [u8], flags: c_uint) -> ssize_t { - e(unsafe { syscall!(GETRANDOM, buf.as_mut_ptr(), buf.len(), flags) }) as ssize_t + fn getpriority(which: c_int, who: id_t) -> Result { + Ok(e_raw(unsafe { syscall!(GETPRIORITY, which, who) })? as c_int) } - unsafe fn getrlimit(resource: c_int, rlim: *mut rlimit) -> c_int { - e(syscall!(GETRLIMIT, resource, rlim)) as c_int + fn getrandom(buf: &mut [u8], flags: c_uint) -> Result { + e_raw(unsafe { syscall!(GETRANDOM, buf.as_mut_ptr(), buf.len(), flags) }) + } + + fn getrlimit(resource: c_int, mut rlim: Out) -> Result<()> { + e_raw(unsafe { syscall!(GETRLIMIT, resource, rlim.as_mut_ptr()) }).map(|_| ()) + } + + fn getresgid( + rgid: Option>, + egid: Option>, + sgid: Option>, + ) -> Result<()> { + unsafe { + e_raw(syscall!( + GETRESGID, + rgid.map_or(0, |mut r| r.as_mut_ptr() as usize), + egid.map_or(0, |mut r| r.as_mut_ptr() as usize), + sgid.map_or(0, |mut r| r.as_mut_ptr() as usize) + )) + .map(|_| ()) + } + } + fn getresuid( + ruid: Option>, + euid: Option>, + suid: Option>, + ) -> Result<()> { + unsafe { + e_raw(syscall!( + GETRESUID, + ruid.map_or(0, |mut r| r.as_mut_ptr() as usize), + euid.map_or(0, |mut r| r.as_mut_ptr() as usize), + suid.map_or(0, |mut r| r.as_mut_ptr() as usize) + )) + .map(|_| ()) + } + } + + unsafe fn setrlimit(resource: c_int, rlimit: *const rlimit) -> Result<()> { + e_raw(syscall!(SETRLIMIT, resource, rlimit)).map(|_| ()) + } + + fn getrusage(who: c_int, mut r_usage: Out) -> Result<()> { + e_raw(unsafe { syscall!(GETRUSAGE, who, r_usage.as_mut_ptr()) })?; + Ok(()) + } + + fn getsid(pid: pid_t) -> Result { + Ok(e_raw(unsafe { syscall!(GETSID, pid) })? as pid_t) } fn gettid() -> pid_t { - e(unsafe { syscall!(GETTID) }) as pid_t + // Always successful + unsafe { syscall!(GETTID) as pid_t } } - fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int { - e(unsafe { syscall!(GETTIMEOFDAY, tp, tzp) }) as c_int + fn gettimeofday(mut tp: Out, tzp: Option>) -> Result<()> { + e_raw(unsafe { + syscall!( + GETTIMEOFDAY, + tp.as_mut_ptr(), + tzp.map_or(0, |mut p| p.as_mut_ptr() as usize) + ) + }) + .map(|_| ()) } fn getuid() -> uid_t { - e(unsafe { syscall!(GETUID) }) as uid_t + unsafe { syscall!(GETUID) as uid_t } } - fn link(path1: &CStr, path2: &CStr) -> c_int { - e(unsafe { + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + fn lchown(path: CStr, owner: uid_t, group: gid_t) -> Result<()> { + e_raw(unsafe { syscall!(LCHOWN, path.as_ptr(), owner, group) }).map(|_| ()) + } + + #[cfg(target_arch = "aarch64")] + fn lchown(path: CStr, owner: uid_t, group: gid_t) -> Result<()> { + e_raw(unsafe { syscall!( - LINKAT, + FCHOWNAT, AT_FDCWD, - path1.as_ptr(), - AT_FDCWD, - path2.as_ptr(), - 0 + path.as_ptr(), + owner as u32, + group as u32, + crate::header::fcntl::AT_SYMLINK_NOFOLLOW ) - }) as c_int + }) + .map(|_| ()) } - fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> off_t { - e(unsafe { syscall!(LSEEK, fildes, offset, whence) }) as off_t + fn linkat(fd1: c_int, path1: CStr, fd2: c_int, path2: CStr, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(LINKAT, fd1, path1.as_ptr(), fd2, path2.as_ptr(), flags) }) + .map(|_| ()) } - fn mkdir(path: &CStr, mode: mode_t) -> c_int { - e(unsafe { syscall!(MKDIRAT, AT_FDCWD, path.as_ptr(), mode) }) as c_int + fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result { + e_raw(unsafe { syscall!(LSEEK, fildes, offset, whence) }).map(|o| o as off_t) } - fn mkfifo(path: &CStr, mode: mode_t) -> c_int { - e(unsafe { syscall!(MKNODAT, AT_FDCWD, path.as_ptr(), mode | S_IFIFO, 0) }) as c_int + fn mkdirat(dir_fildes: c_int, path: CStr, mode: mode_t) -> Result<()> { + e_raw(unsafe { syscall!(MKDIRAT, dir_fildes, path.as_ptr(), mode) }).map(|_| ()) + } + + fn mknodat(dir_fildes: c_int, path: CStr, mode: mode_t, dev: dev_t) -> Result<()> { + // Note: dev_t is c_long (i64) and __kernel_dev_t is u32; So we need to cast it + // and check for overflow + let k_dev: c_uint = dev as c_uint; + if dev_t::from(k_dev) != dev { + return Err(Errno(EINVAL)); + } + + e_raw(unsafe { syscall!(MKNODAT, dir_fildes, path.as_ptr(), mode, k_dev) }).map(|_| ()) + } + + fn mkfifoat(dir_fd: c_int, path: CStr, mode: mode_t) -> Result<()> { + Sys::mknodat(dir_fd, path, mode | S_IFIFO, 0) + } + + fn mkfifo(path: CStr, mode: mode_t) -> Result<()> { + Sys::mknod(path, mode | S_IFIFO, 0) + } + + unsafe fn mlock(addr: *const c_void, len: usize) -> Result<()> { + e_raw(unsafe { syscall!(MLOCK, addr, len) }).map(|_| ()) + } + + unsafe fn mlockall(flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(MLOCKALL, flags) }).map(|_| ()) } unsafe fn mmap( @@ -306,144 +522,338 @@ impl Pal for Sys { flags: c_int, fildes: c_int, off: off_t, - ) -> *mut c_void { - e(syscall!(MMAP, addr, len, prot, flags, fildes, off)) as *mut c_void + ) -> Result<*mut c_void> { + Ok(e_raw(syscall!(MMAP, addr, len, prot, flags, fildes, off))? as *mut c_void) } - unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int { - e(syscall!(MPROTECT, addr, len, prot)) as c_int + unsafe fn mremap( + addr: *mut c_void, + len: usize, + new_len: usize, + flags: c_int, + args: *mut c_void, + ) -> Result<*mut c_void> { + Ok(e_raw(syscall!(MREMAP, addr, len, new_len, flags, args))? as *mut c_void) } - unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> c_int { - e(syscall!(MSYNC, addr, len, flags)) as c_int + unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> Result<()> { + e_raw(syscall!(MPROTECT, addr, len, prot)).map(|_| ()) } - unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int { - e(syscall!(MUNMAP, addr, len)) as c_int + unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { + e_raw(syscall!(MSYNC, addr, len, flags)).map(|_| ()) } - fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int { - e(unsafe { syscall!(NANOSLEEP, rqtp, rmtp) }) as c_int + unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> { + e_raw(syscall!(MUNLOCK, addr, len)).map(|_| ()) } - fn open(path: &CStr, oflag: c_int, mode: mode_t) -> c_int { - e(unsafe { syscall!(OPENAT, AT_FDCWD, path.as_ptr(), oflag, mode) }) as c_int + unsafe fn munlockall() -> Result<()> { + e_raw(unsafe { syscall!(MUNLOCKALL) }).map(|_| ()) } - fn pipe2(fildes: &mut [c_int], flags: c_int) -> c_int { - e(unsafe { syscall!(PIPE2, fildes.as_mut_ptr(), flags) }) as c_int + unsafe fn munmap(addr: *mut c_void, len: usize) -> Result<()> { + e_raw(syscall!(MUNMAP, addr, len)).map(|_| ()) + } + + unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { + e_raw(syscall!(MADVISE, addr, len, flags)).map(|_| ()) + } + + unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> { + e_raw(unsafe { syscall!(NANOSLEEP, rqtp, rmtp) }).map(|_| ()) + } + + fn open(path: CStr, oflag: c_int, mode: mode_t) -> Result { + e_raw(unsafe { syscall!(OPENAT, AT_FDCWD, path.as_ptr(), oflag, mode) }) + .map(|fd| fd as c_int) + } + + fn openat(dirfd: c_int, path: CStr, oflag: c_int, mode: mode_t) -> Result { + e_raw(unsafe { syscall!(OPENAT, dirfd, path.as_ptr(), oflag, mode) }).map(|fd| fd as c_int) + } + + fn pipe2(mut fildes: Out<[c_int; 2]>, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(PIPE2, fildes.as_mut_ptr(), flags) }).map(|_| ()) + } + + fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()> { + let length = length.get(); + e_raw(unsafe { syscall!(FALLOCATE, fd, 0, offset, length) }).map(|_| ()) + } + + fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result { + let current_offset = Self::lseek(fildes, 0, SEEK_CUR)? as u64; + let bytes_read = Self::getdents(fildes, buf, current_offset)?; + if bytes_read == 0 { + return Ok(0); + } + let mut bytes_processed = 0; + let mut next_offset = current_offset; + + while bytes_processed < bytes_read { + let remaining_slice = &buf[bytes_processed..]; + let (reclen, opaque_next) = + unsafe { Self::dent_reclen_offset(remaining_slice, bytes_processed) } + .ok_or(Errno(EIO))?; + if reclen == 0 { + return Err(Errno(EIO)); + } + + bytes_processed += reclen as usize; + next_offset = opaque_next; + } + + Self::lseek(fildes, next_offset as off_t, SEEK_SET)?; + Ok(bytes_read) } #[cfg(target_arch = "x86_64")] - unsafe fn pte_clone(stack: *mut usize) -> pid_t { - let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND; + unsafe fn rlct_clone( + stack: *mut usize, + _os_specific: &mut OsSpecific, + ) -> Result { + let flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; let pid; - llvm_asm!(" - # Call clone syscall - syscall + unsafe { + asm!(" + # Call clone syscall + syscall - # Check if child or parent - test rax, rax - jnz .parent + # Check if child or parent + test rax, rax + jnz 2f - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 + # Load registers + pop rax + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 - # Call entry point - call rax + # Call entry point + call rax - # Exit - mov rax, 60 - xor rdi, rdi - syscall + # Exit + mov rax, 60 + xor rdi, rdi + syscall - # Invalid instruction on failure to exit - ud2 + # Invalid instruction on failure to exit + ud2 - # Return PID if parent - .parent: - " - : "={rax}"(pid) - : "{rax}"(SYS_CLONE), "{rdi}"(flags), "{rsi}"(stack), "{rdx}"(0), "{rcx}"(0), "{r8}"(0) - : "memory", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", - "r9", "r10", "r11", "r12", "r13", "r14", "r15" - : "intel", "volatile" - ); - e(pid) as pid_t + # Return PID if parent + 2: + ", + inout("rax") SYS_CLONE => pid, + inout("rdi") flags => _, + inout("rsi") stack => _, + inout("rdx") 0 => _, + inout("r10") 0 => _, + inout("r8") 0 => _, + //TODO: out("rbx") _, + out("rcx") _, + out("r9") _, + out("r11") _, + out("r12") _, + out("r13") _, + out("r14") _, + out("r15") _, + ); + } + let tid = e_raw(pid)?; + + Ok(crate::pthread::OsTid { thread_id: tid }) } - fn read(fildes: c_int, buf: &mut [u8]) -> ssize_t { - e(unsafe { syscall!(READ, fildes, buf.as_mut_ptr(), buf.len()) }) as ssize_t + #[cfg(target_arch = "aarch64")] + unsafe fn rlct_clone( + stack: *mut usize, + _os_specific: &mut OsSpecific, + ) -> Result { + todo!("rlct_clone not implemented for aarch64 yet") } - fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t { - e(unsafe { + unsafe fn rlct_kill(os_tid: crate::pthread::OsTid, signal: usize) -> Result<()> { + let tgid = Self::getpid(); + e_raw(unsafe { syscall!(TGKILL, tgid, os_tid.thread_id, signal) }).map(|_| ()) + } + + fn current_os_tid() -> crate::pthread::OsTid { + crate::pthread::OsTid { + thread_id: unsafe { syscall!(GETTID) }, + } + } + + fn read(fildes: c_int, buf: &mut [u8]) -> Result { + e_raw(unsafe { syscall!(READ, fildes, buf.as_mut_ptr(), buf.len()) }) + } + fn pread(fildes: c_int, buf: &mut [u8], off: off_t) -> Result { + e_raw(unsafe { syscall!(PREAD64, fildes, buf.as_mut_ptr(), buf.len(), off) }) + } + + fn readlinkat(dirfd: c_int, pathname: CStr, out: &mut [u8]) -> Result { + e_raw(unsafe { syscall!( READLINKAT, - AT_FDCWD, + dirfd, pathname.as_ptr(), out.as_mut_ptr(), out.len() ) - }) as ssize_t + }) } - fn rename(old: &CStr, new: &CStr) -> c_int { - e(unsafe { syscall!(RENAMEAT, AT_FDCWD, old.as_ptr(), AT_FDCWD, new.as_ptr()) }) as c_int + fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()> { + e_raw(unsafe { + syscall!( + RENAMEAT, + old_dir, + old_path.as_ptr(), + new_dir, + new_path.as_ptr() + ) + }) + .map(|_| ()) } - fn rmdir(path: &CStr) -> c_int { - e(unsafe { syscall!(UNLINKAT, AT_FDCWD, path.as_ptr(), AT_REMOVEDIR) }) as c_int + fn renameat2( + old_dir: c_int, + old_path: CStr, + new_dir: c_int, + new_path: CStr, + flags: c_uint, + ) -> Result<()> { + e_raw(unsafe { + syscall!( + RENAMEAT2, + old_dir, + old_path.as_ptr(), + new_dir, + new_path.as_ptr(), + flags + ) + }) + .map(|_| ()) } - fn sched_yield() -> c_int { - e(unsafe { syscall!(SCHED_YIELD) }) as c_int + fn rmdir(path: CStr) -> Result<()> { + e_raw(unsafe { syscall!(UNLINKAT, AT_FDCWD, path.as_ptr(), AT_REMOVEDIR) }).map(|_| ()) } - fn setpgid(pid: pid_t, pgid: pid_t) -> c_int { - e(unsafe { syscall!(SETPGID, pid, pgid) }) as c_int + fn sched_yield() -> Result<()> { + e_raw(unsafe { syscall!(SCHED_YIELD) }).map(|_| ()) } - fn setregid(rgid: gid_t, egid: gid_t) -> c_int { - e(unsafe { syscall!(SETREGID, rgid, egid) }) as c_int + unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()> { + e_raw(unsafe { syscall!(SETGROUPS, size, list) }).map(|_| ()) } - fn setreuid(ruid: uid_t, euid: uid_t) -> c_int { - e(unsafe { syscall!(SETREUID, ruid, euid) }) as c_int + fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> { + e_raw(unsafe { syscall!(SETPGID, pid, pgid) }).map(|_| ()) } - fn symlink(path1: &CStr, path2: &CStr) -> c_int { - e(unsafe { syscall!(SYMLINKAT, path1.as_ptr(), AT_FDCWD, path2.as_ptr()) }) as c_int + fn setpriority(which: c_int, who: id_t, prio: c_int) -> Result<()> { + e_raw(unsafe { syscall!(SETPRIORITY, which, who, prio) }).map(|_| ()) + } + + fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) -> Result<()> { + e_raw(unsafe { syscall!(SETRESGID, rgid, egid, sgid) }).map(|_| ()) + } + + fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) -> Result<()> { + e_raw(unsafe { syscall!(SETRESUID, ruid, euid, suid) }).map(|_| ()) + } + + fn setsid() -> Result { + e_raw(unsafe { syscall!(SETSID) }).map(|s| s as c_int) + } + + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()> { + e_raw(unsafe { syscall!(SYMLINKAT, path1.as_ptr(), fd, path2.as_ptr()) }).map(|_| ()) + } + + fn sync() -> Result<()> { + e_raw(unsafe { syscall!(SYNC) }).map(|_| ()) + } + + fn timer_create(clock_id: clockid_t, evp: &sigevent, mut timerid: Out) -> Result<()> { + e_raw(unsafe { + syscall!( + TIMER_CREATE, + clock_id, + ptr::addr_of!(evp), + timerid.as_mut_ptr() + ) + }) + .map(|_| ()) + } + + fn timer_delete(timerid: timer_t) -> Result<()> { + e_raw(unsafe { syscall!(TIMER_DELETE, timerid) }).map(|_| ()) + } + + fn timer_gettime(timerid: timer_t, mut value: Out) -> Result<()> { + e_raw(unsafe { syscall!(TIMER_GETTIME, timerid, value.as_mut_ptr()) }).map(|_| ()) + } + + fn timer_settime( + timerid: timer_t, + flags: c_int, + value: &itimerspec, + ovalue: Option>, + ) -> Result<()> { + e_raw(unsafe { + syscall!( + TIMER_SETTIME, + timerid, + flags, + ptr::addr_of!(value), + match ovalue { + None => ptr::null_mut(), + Some(mut o) => o.as_mut_ptr(), + } + ) + }) + .map(|_| ()) } fn umask(mask: mode_t) -> mode_t { unsafe { syscall!(UMASK, mask) as mode_t } } - fn uname(utsname: *mut utsname) -> c_int { - e(unsafe { syscall!(UNAME, utsname, 0) }) as c_int + fn uname(mut utsname: Out) -> Result<()> { + e_raw(unsafe { syscall!(UNAME, utsname.as_mut_ptr(), 0) }).map(|_| ()) } - fn unlink(path: &CStr) -> c_int { - e(unsafe { syscall!(UNLINKAT, AT_FDCWD, path.as_ptr(), 0) }) as c_int + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(UNLINKAT, fd, path.as_ptr(), flags) }).map(|_| ()) } - fn waitpid(pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t { - e(unsafe { syscall!(WAIT4, pid, stat_loc, options, 0) }) as pid_t + fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result { + e_raw(unsafe { + syscall!( + WAIT4, + pid, + stat_loc.map_or(core::ptr::null_mut(), |mut o| o.as_mut_ptr()), + options, + 0 + ) + }) + .map(|p| p as pid_t) } - fn write(fildes: c_int, buf: &[u8]) -> ssize_t { - e(unsafe { syscall!(WRITE, fildes, buf.as_ptr(), buf.len()) }) as ssize_t + fn write(fildes: c_int, buf: &[u8]) -> Result { + e_raw(unsafe { syscall!(WRITE, fildes, buf.as_ptr(), buf.len()) }) + } + fn pwrite(fildes: c_int, buf: &[u8], off: off_t) -> Result { + e_raw(unsafe { syscall!(PWRITE64, fildes, buf.as_ptr(), buf.len(), off) }) } fn verify() -> bool { // GETPID on Linux is 39, which does not exist on Redox - e(unsafe { sc::syscall5(sc::nr::GETPID, !0, !0, !0, !0, !0) }) != !0 + e_raw(unsafe { sc::syscall5(sc::nr::GETPID, !0, !0, !0, !0, !0) }).is_ok() } } diff --git a/src/platform/linux/ptrace.rs b/src/platform/linux/ptrace.rs index c6dbaeeba1..8301ea303b 100644 --- a/src/platform/linux/ptrace.rs +++ b/src/platform/linux/ptrace.rs @@ -1,10 +1,16 @@ use super::{ - super::{types::*, PalPtrace}, - e, Sys, + super::{PalPtrace, types::*}, + Sys, e_raw, }; +use crate::error::Result; impl PalPtrace for Sys { - fn ptrace(request: c_int, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> c_int { - unsafe { e(syscall!(PTRACE, request, pid, addr, data)) as c_int } + unsafe fn ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, + ) -> Result { + Ok(unsafe { e_raw(syscall!(PTRACE, request, pid, addr, data))? as c_int }) } } diff --git a/src/platform/linux/signal.rs b/src/platform/linux/signal.rs index d8bf311c62..ef748a36e7 100644 --- a/src/platform/linux/signal.rs +++ b/src/platform/linux/signal.rs @@ -1,57 +1,205 @@ -use core::mem; +use core::{ + mem, + ptr::{self, addr_of}, +}; use super::{ - super::{types::*, PalSignal}, - e, Sys, + super::{ + PalSignal, + types::{c_int, c_uint, pid_t}, + }, + Sys, e_raw, }; -use crate::header::{ - signal::{sigaction, sigset_t, stack_t}, - sys_time::itimerval, +#[allow(deprecated)] +use crate::header::sys_time::itimerval; +use crate::{ + error::{Errno, Result}, + header::{ + bits_sigset_t::sigset_t, + bits_timespec::timespec, + signal::{ + SA_RESTORER, SI_QUEUE, SIGALRM, SIGEV_SIGNAL, sigaction, sigevent, siginfo_t, sigval, + stack_t, + }, + time, + }, + out::Out, + platform::{self, Pal, types::timer_t}, }; impl PalSignal for Sys { - fn getitimer(which: c_int, out: *mut itimerval) -> c_int { - e(unsafe { syscall!(GETITIMER, which, out) }) as c_int + #[allow(deprecated)] + fn getitimer(which: c_int, out: &mut itimerval) -> Result<()> { + unsafe { + e_raw(syscall!(GETITIMER, which, ptr::from_mut(out)))?; + } + Ok(()) } - fn kill(pid: pid_t, sig: c_int) -> c_int { - e(unsafe { syscall!(KILL, pid, sig) }) as c_int + fn kill(pid: pid_t, sig: c_int) -> Result<()> { + e_raw(unsafe { syscall!(KILL, pid, sig) })?; + Ok(()) + } + fn sigqueue(pid: pid_t, sig: c_int, val: sigval) -> Result<()> { + let info = siginfo_t { + si_addr: core::ptr::null_mut(), + si_code: SI_QUEUE, + si_errno: 0, + si_pid: 0, // TODO: GETPID? + si_signo: sig, + si_status: 0, + si_uid: 0, // TODO: GETUID? + si_value: val, + }; + e_raw(unsafe { syscall!(RT_SIGQUEUEINFO, pid, sig, addr_of!(info)) }).map(|_| ()) } - fn killpg(pgrp: pid_t, sig: c_int) -> c_int { - e(unsafe { syscall!(KILL, -(pgrp as isize) as pid_t, sig) }) as c_int + fn killpg(pgrp: pid_t, sig: c_int) -> Result<()> { + e_raw(unsafe { syscall!(KILL, -(pgrp as isize) as pid_t, sig) })?; + Ok(()) } - fn raise(sig: c_int) -> c_int { - let tid = e(unsafe { syscall!(GETTID) }) as pid_t; - if tid == !0 { - -1 + fn raise(sig: c_int) -> Result<()> { + let tid = e_raw(unsafe { syscall!(GETTID) })? as pid_t; + e_raw(unsafe { syscall!(TKILL, tid, sig) })?; + Ok(()) + } + + #[allow(deprecated)] + fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()> { + e_raw(unsafe { + syscall!( + SETITIMER, + which, + ptr::from_ref(new), + old.map_or_else(ptr::null_mut, ptr::from_mut) + ) + })?; + Ok(()) + } + + /// See . + fn alarm(seconds: c_uint) -> c_uint { + let mut signal_event = sigevent { + sigev_value: sigval { sival_int: 0 }, + sigev_signo: SIGALRM as c_int, + sigev_notify: SIGEV_SIGNAL, + sigev_notify_attributes: ptr::null_mut(), + sigev_notify_function: None, + }; + let mut timerid: timer_t = Default::default(); + let timer = unsafe { + time::timer_create( + time::CLOCK_REALTIME, + ptr::from_mut(&mut signal_event), + ptr::from_mut(&mut timerid), + ) + }; + let value = time::itimerspec { + it_value: timespec { + tv_sec: i64::from(seconds), + tv_nsec: 0, + }, + it_interval: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + }; + let mut ovalue = Default::default(); + + let errno_backup = platform::ERRNO.get(); + if Sys::timer_settime(timerid, 0, &value, Some(Out::from_mut(&mut ovalue))).is_err() { + platform::ERRNO.set(errno_backup); + 0 } else { - e(unsafe { syscall!(TKILL, tid, sig) }) as c_int + ovalue.it_value.tv_sec as c_uint + if ovalue.it_value.tv_nsec > 0 { 1 } else { 0 } } } - fn setitimer(which: c_int, new: *const itimerval, old: *mut itimerval) -> c_int { - e(unsafe { syscall!(SETITIMER, which, new, old) }) as c_int - } - - fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> c_int { - e(unsafe { + fn sigaction( + sig: c_int, + act: Option<&sigaction>, + oact: Option<&mut sigaction>, + ) -> Result<(), Errno> { + unsafe extern "C" { + fn __restore_rt(); + } + let act = act.map(|act| { + let mut act_clone = act.clone(); + act_clone.sa_flags |= SA_RESTORER as c_int; + act_clone.sa_restorer = Some(__restore_rt); + act_clone + }); + e_raw(unsafe { syscall!( RT_SIGACTION, sig, - act.map_or_else(core::ptr::null, |x| x as *const _), - oact.map_or_else(core::ptr::null_mut, |x| x as *mut _), + act.as_ref().map_or_else(ptr::null, ptr::from_ref), + oact.map_or_else(ptr::null_mut, ptr::from_mut), mem::size_of::() ) - }) as c_int + }) + .map(|_| ()) } - fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int { - e(unsafe { syscall!(SIGALTSTACK, ss, old_ss) }) as c_int + unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<()> { + e_raw(syscall!( + SIGALTSTACK, + ss.map_or_else(ptr::null, ptr::from_ref), + old_ss.map_or_else(ptr::null_mut, ptr::from_mut) + )) + .map(|_| ()) } - fn sigprocmask(how: c_int, set: *const sigset_t, oset: *mut sigset_t) -> c_int { - e(unsafe { syscall!(RT_SIGPROCMASK, how, set, oset, mem::size_of::()) }) as c_int + fn sigpending(set: &mut sigset_t) -> Result<()> { + e_raw(unsafe { + syscall!( + RT_SIGPENDING, + ptr::from_mut::(set) as usize, + mem::size_of::() + ) + }) + .map(|_| ()) + } + + fn sigprocmask(how: c_int, set: Option<&sigset_t>, oset: Option<&mut sigset_t>) -> Result<()> { + e_raw(unsafe { + syscall!( + RT_SIGPROCMASK, + how, + set.map_or_else(ptr::null, ptr::from_ref), + oset.map_or_else(ptr::null_mut, ptr::from_mut), + mem::size_of::() + ) + }) + .map(|_| ()) + } + + fn sigsuspend(mask: &sigset_t) -> Errno { + unsafe { + e_raw(syscall!( + RT_SIGSUSPEND, + ptr::from_ref::(mask), + size_of::() + )) + .expect_err("must fail") + } + } + + fn sigtimedwait( + set: &sigset_t, + sig: Option<&mut siginfo_t>, + tp: Option<×pec>, + ) -> Result { + unsafe { + e_raw(syscall!( + RT_SIGTIMEDWAIT, + ptr::from_ref(set), + sig.map_or_else(ptr::null_mut, ptr::from_mut), + tp.map_or_else(ptr::null, ptr::from_ref), + size_of::() + )) + .map(|s| s as c_int) + } } } diff --git a/src/platform/linux/socket.rs b/src/platform/linux/socket.rs index e865b32063..47aef77141 100644 --- a/src/platform/linux/socket.rs +++ b/src/platform/linux/socket.rs @@ -1,46 +1,64 @@ -use super::{ - super::{types::*, PalSocket}, - e, Sys, +use super::{Sys, e_raw}; +use crate::{ + error::Result, + header::{ + bits_socklen_t::socklen_t, + sys_socket::{msghdr, sockaddr}, + }, + platform::{ + PalSocket, + types::{c_int, c_void, size_t}, + }, }; -use crate::header::sys_socket::{sockaddr, socklen_t}; impl PalSocket for Sys { - unsafe fn accept(socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int { - e(syscall!(ACCEPT, socket, address, address_len)) as c_int + unsafe fn accept( + socket: c_int, + address: *mut sockaddr, + address_len: *mut socklen_t, + ) -> Result { + Ok(e_raw(syscall!(ACCEPT, socket, address, address_len))? as c_int) } - unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int { - e(syscall!(BIND, socket, address, address_len)) as c_int + unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()> { + e_raw(syscall!(BIND, socket, address, address_len))?; + Ok(()) } - unsafe fn connect(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int { - e(syscall!(CONNECT, socket, address, address_len)) as c_int + unsafe fn connect( + socket: c_int, + address: *const sockaddr, + address_len: socklen_t, + ) -> Result { + Ok(e_raw(syscall!(CONNECT, socket, address, address_len))? as c_int) } unsafe fn getpeername( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int { - e(syscall!(GETPEERNAME, socket, address, address_len)) as c_int + ) -> Result<()> { + e_raw(syscall!(GETPEERNAME, socket, address, address_len))?; + Ok(()) } unsafe fn getsockname( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int { - e(syscall!(GETSOCKNAME, socket, address, address_len)) as c_int + ) -> Result<()> { + e_raw(syscall!(GETSOCKNAME, socket, address, address_len))?; + Ok(()) } - fn getsockopt( + unsafe fn getsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *mut c_void, option_len: *mut socklen_t, - ) -> c_int { - e(unsafe { + ) -> Result<()> { + e_raw(unsafe { syscall!( GETSOCKOPT, socket, @@ -49,11 +67,13 @@ impl PalSocket for Sys { option_value, option_len ) - }) as c_int + })?; + Ok(()) } - fn listen(socket: c_int, backlog: c_int) -> c_int { - e(unsafe { syscall!(LISTEN, socket, backlog) }) as c_int + fn listen(socket: c_int, backlog: c_int) -> Result<()> { + e_raw(unsafe { syscall!(LISTEN, socket, backlog) })?; + Ok(()) } unsafe fn recvfrom( @@ -63,8 +83,8 @@ impl PalSocket for Sys { flags: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> ssize_t { - e(syscall!( + ) -> Result { + e_raw(syscall!( RECVFROM, socket, buf, @@ -72,7 +92,15 @@ impl PalSocket for Sys { flags, address, address_len - )) as ssize_t + )) + } + + unsafe fn recvmsg(socket: c_int, msg: *mut msghdr, flags: c_int) -> Result { + e_raw(syscall!(RECVMSG, socket, msg, flags)) + } + + unsafe fn sendmsg(socket: c_int, msg: *const msghdr, flags: c_int) -> Result { + e_raw(syscall!(SENDMSG, socket, msg, flags)) } unsafe fn sendto( @@ -82,20 +110,20 @@ impl PalSocket for Sys { flags: c_int, dest_addr: *const sockaddr, dest_len: socklen_t, - ) -> ssize_t { - e(syscall!( + ) -> Result { + e_raw(syscall!( SENDTO, socket, buf, len, flags, dest_addr, dest_len - )) as ssize_t + )) } - fn setsockopt( + unsafe fn setsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *const c_void, option_len: socklen_t, - ) -> c_int { - e(unsafe { + ) -> Result<()> { + e_raw(unsafe { syscall!( SETSOCKOPT, socket, @@ -104,18 +132,21 @@ impl PalSocket for Sys { option_value, option_len ) - }) as c_int + })?; + Ok(()) } - fn shutdown(socket: c_int, how: c_int) -> c_int { - e(unsafe { syscall!(SHUTDOWN, socket, how) }) as c_int + fn shutdown(socket: c_int, how: c_int) -> Result<()> { + e_raw(unsafe { syscall!(SHUTDOWN, socket, how) })?; + Ok(()) } - unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> c_int { - e(syscall!(SOCKET, domain, kind, protocol)) as c_int + unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> Result { + Ok(e_raw(syscall!(SOCKET, domain, kind, protocol))? as c_int) } - fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> c_int { - e(unsafe { syscall!(SOCKETPAIR, domain, kind, protocol, sv.as_mut_ptr()) }) as c_int + fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> Result<()> { + e_raw(unsafe { syscall!(SOCKETPAIR, domain, kind, protocol, sv.as_mut_ptr()) })?; + Ok(()) } } diff --git a/src/platform/logger.rs b/src/platform/logger.rs new file mode 100644 index 0000000000..bdf4dce188 --- /dev/null +++ b/src/platform/logger.rs @@ -0,0 +1,293 @@ +use core::{fmt, str::FromStr}; + +use crate::{c_str::CStr, io::prelude::*, sync::Mutex}; + +use alloc::string::{String, ToString}; +use log::{Metadata, Record}; + +const DEFAULT_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info; + +pub unsafe fn init() { + let mut logger = RedoxLogger::new(); + let log_env = c"RELIBC_LOG_LEVEL".as_ptr(); + #[cfg(feature = "no_trace")] + let mut trace_warn = false; + unsafe { + if let Some(env) = CStr::from_nullable_ptr(crate::header::stdlib::getenv(log_env)) + && let Ok(level) = log::LevelFilter::from_str(env.to_str().unwrap_or("")) + { + #[cfg(feature = "no_trace")] + if level == log::LevelFilter::Trace { + trace_warn = true; + } + + logger = logger.with_output(OutputBuilder::stderr().with_filter(level).build()); + } + if let Some(name) = CStr::from_nullable_ptr(crate::platform::program_invocation_short_name) + { + logger = logger.with_process_name(name.to_str().unwrap_or("").to_string()); + } + } + if logger.enable().is_err() { + log::error!("Logger already initialized"); + } + + #[cfg(feature = "no_trace")] + if trace_warn { + log::warn!( + "The 'no_trace' feature is enabled but RELIBC_LOG_LEVEL=TRACE, there will be no trace logs" + ); + } +} + +/// Copied from redox_log crate with some modifications, in future we might use it instead? +/// An output that will be logged to. The two major outputs for most Redox system programs are +/// usually the log file, and the global stdout. +pub struct Output { + // the actual endpoint to write to. + endpoint: Mutex>, + + // useful for devices like BufWrite or BufRead. You don't want the log file to never but + // written until the program exists. + flush_on_newline: bool, + + // specifies the maximum log level possible + filter: log::LevelFilter, +} + +impl fmt::Debug for Output { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Output") + .field("endpoint", &"opaque") + .field("flush_on_newline", &self.flush_on_newline) + .field("filter", &self.filter) + .finish() + } +} + +impl Default for Output { + fn default() -> Self { + // Uses default level of max_level_in_use == None a.k.a LogLevel::Info + OutputBuilder::stderr().build() + } +} + +pub struct OutputBuilder { + endpoint: Box, + flush_on_newline: Option, + filter: Option, + ansi: Option, +} +impl OutputBuilder { + /* + pub fn in_redox_logging_scheme( + category: A, + subcategory: B, + logfile: C, + ) -> Result + where + A: AsRef, + B: AsRef, + C: AsRef, + { + if !cfg!(target_os = "redox") { + return Ok(Self::with_endpoint(Vec::new())); + } + + let mut path = PathBuf::from("/scheme/logging/"); + path.push(category.as_ref()); + path.push(subcategory.as_ref()); + path.push(logfile.as_ref()); + path.set_extension("log"); + + if let Some(parent) = path.parent() { + if !parent.exists() { + fs::create_dir_all(parent)?; + } + } + + Ok(Self::with_endpoint(BufWriter::new(File::create( + path, + fcntl::O_CREAT | fcntl::O_CLOEXEC, + 0, + )?))) + } + */ + pub fn stdout() -> Self { + Self::with_endpoint(crate::platform::FileWriter::new(1)) + } + pub fn stderr() -> Self { + Self::with_endpoint(crate::platform::FileWriter::new(2)) + } + + pub fn with_endpoint(endpoint: T) -> Self + where + T: fmt::Write + Send + 'static, + { + Self::with_dyn_endpoint(Box::new(endpoint)) + } + pub fn with_dyn_endpoint(endpoint: Box) -> Self { + Self { + endpoint, + flush_on_newline: None, + filter: None, + ansi: None, + } + } + pub fn flush_on_newline(mut self, flush: bool) -> Self { + self.flush_on_newline = Some(flush); + self + } + pub fn with_filter(mut self, filter: log::LevelFilter) -> Self { + self.filter = Some(filter); + self + } + pub fn build(self) -> Output { + Output { + endpoint: Mutex::new(self.endpoint), + filter: self.filter.unwrap_or(DEFAULT_LOG_LEVEL), + flush_on_newline: self.flush_on_newline.unwrap_or(true), + } + } +} + +#[derive(Debug, Default)] +pub struct RedoxLogger { + output: Output, + min_filter: Option, + max_filter: Option, + max_level_in_use: Option, + min_level_in_use: Option, + process_name: Option, +} + +impl RedoxLogger { + pub fn new() -> Self { + Self::default() + } + fn adjust_output_level( + max_filter: Option, + min_filter: Option, + max_in_use: &mut Option, + min_in_use: &mut Option, + output: &mut Output, + ) { + if let Some(max) = max_filter { + output.filter = core::cmp::max(output.filter, max); + } + if let Some(min) = min_filter { + output.filter = core::cmp::min(output.filter, min); + } + match max_in_use { + &mut Some(ref mut max) => *max = core::cmp::max(output.filter, *max), + max @ &mut None => *max = Some(output.filter), + } + match min_in_use { + &mut Some(ref mut min) => *min = core::cmp::min(output.filter, *min), + min @ &mut None => *min = Some(output.filter), + } + } + pub fn with_output(mut self, mut output: Output) -> Self { + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + &mut output, + ); + self.output = output; + self + } + pub fn with_min_level_override(mut self, min: log::LevelFilter) -> Self { + self.min_filter = Some(min); + let output = &mut self.output; + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + output, + ); + self + } + pub fn with_max_level_override(mut self, max: log::LevelFilter) -> Self { + self.max_filter = Some(max); + let output = &mut self.output; + Self::adjust_output_level( + self.max_filter, + self.min_filter, + &mut self.max_level_in_use, + &mut self.min_level_in_use, + output, + ); + self + } + pub fn with_process_name(mut self, name: String) -> Self { + self.process_name = Some(name); + self + } + pub fn enable(self) -> Result<&'static Self, log::SetLoggerError> { + let leak = Box::leak(Box::new(self)); + log::set_logger(leak)?; + if let Some(max) = leak.max_level_in_use { + log::set_max_level(max); + } else { + log::set_max_level(DEFAULT_LOG_LEVEL); + } + Ok(leak) + } + fn write_record( + record: &Record, + process_name: Option<&str>, + writer: &mut W, + ) -> fmt::Result { + let target = record.module_path().unwrap_or(record.target()); + let level = record.level(); + let message = record.args(); + + let show_lines = true; + let line_number = if show_lines { record.line() } else { None }; + + let process_name = process_name.unwrap_or(""); + let line = &LineFmt(line_number); + writeln!(writer, "[{process_name}@{target}{line} {level}] {message}",) + } +} + +impl log::Log for RedoxLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.max_level_in_use + .map(|min| metadata.level() >= min) + .unwrap_or(false) + && self + .min_level_in_use + .map(|max| metadata.level() <= max) + .unwrap_or(false) + } + fn log(&self, record: &Record) { + let output = &self.output; + if record.metadata().level() <= output.filter { + let mut endpoint_guard = output.endpoint.lock(); + + let _ = Self::write_record( + record, + self.process_name.as_deref(), + endpoint_guard.as_mut(), + ); + } + } + fn flush(&self) { + // no-op + } +} + +struct LineFmt(Option); +impl fmt::Display for LineFmt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(line) = self.0 { + write!(f, ":{line}") + } else { + write!(f, "") + } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 3978625e4f..d3a10b92a5 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,101 +1,143 @@ -use crate::io::{self, Read, Write}; -use alloc::vec::Vec; -use core::{fmt, ptr}; +//! Platform abstractions and environment. + +use crate::{ + error::{Errno, ResultExt}, + io::{self, Read, Write}, + raw_cell::RawCell, +}; +use alloc::{boxed::Box, vec::Vec}; +use core::{cell::Cell, fmt, ptr}; pub use self::allocator::*; -#[cfg(not(feature = "ralloc"))] -#[path = "allocator/dlmalloc.rs"] mod allocator; -#[cfg(feature = "ralloc")] -#[path = "allocator/ralloc.rs"] -mod allocator; +pub mod logger; pub use self::pal::{Pal, PalEpoll, PalPtrace, PalSignal, PalSocket}; mod pal; -pub use self::sys::{e, Sys}; +pub use self::sys::Sys; -#[cfg(all(not(feature = "no_std"), target_os = "linux"))] +#[cfg(target_os = "linux")] #[path = "linux/mod.rs"] -mod sys; +pub(crate) mod sys; -#[cfg(all(not(feature = "no_std"), target_os = "redox"))] +#[cfg(target_os = "redox")] #[path = "redox/mod.rs"] -mod sys; - -#[cfg(test)] -mod test; - -mod pte; +pub(crate) mod sys; pub use self::rlb::{Line, RawLineBuffer}; pub mod rlb; +#[cfg(target_os = "linux")] +pub mod auxv_defs; + +#[cfg(target_os = "redox")] +pub use redox_rt::auxv_defs; + use self::types::*; pub mod types; +/// The global `errno` variable used internally in relibc. #[thread_local] -#[allow(non_upper_case_globals)] -#[no_mangle] -pub static mut errno: c_int = 0; +pub static ERRNO: Cell = Cell::new(0); +/// The `argv` argument available to a program's `main` function. #[allow(non_upper_case_globals)] pub static mut argv: *mut *mut c_char = ptr::null_mut(); #[allow(non_upper_case_globals)] -pub static mut inner_argv: Vec<*mut c_char> = Vec::new(); +pub static inner_argv: RawCell> = RawCell::new(Vec::new()); +#[allow(non_upper_case_globals)] +pub static mut program_invocation_name: *mut c_char = ptr::null_mut(); +#[allow(non_upper_case_globals)] +pub static mut program_invocation_short_name: *mut c_char = ptr::null_mut(); #[allow(non_upper_case_globals)] -#[no_mangle] +#[unsafe(no_mangle)] pub static mut environ: *mut *mut c_char = ptr::null_mut(); -#[allow(non_upper_case_globals)] -pub static mut inner_environ: Vec<*mut c_char> = Vec::new(); + +pub static OUR_ENVIRON: RawCell> = RawCell::new(Vec::new()); + +pub fn environ_iter() -> impl Iterator + 'static { + unsafe { + let mut ptrs = environ; + + core::iter::from_fn(move || { + if ptrs.is_null() { + None + } else { + let ptr = ptrs.read(); + if ptr.is_null() { + None + } else { + ptrs = ptrs.add(1); + Some(ptr) + } + } + }) + } +} pub trait WriteByte: fmt::Write { fn write_u8(&mut self, byte: u8) -> fmt::Result; } -impl<'a, W: WriteByte> WriteByte for &'a mut W { +impl WriteByte for &mut W { fn write_u8(&mut self, byte: u8) -> fmt::Result { (**self).write_u8(byte) } } -pub struct FileWriter(pub c_int); +/// An implementation of [`core::fmt::Write`] for a file descriptor. +pub struct FileWriter(pub c_int, Option); impl FileWriter { - pub fn write(&mut self, buf: &[u8]) -> isize { - Sys::write(self.0, buf) + pub fn new(fd: c_int) -> Self { + Self(fd, None) + } + + pub fn write(&mut self, buf: &[u8]) -> fmt::Result { + let _ = Sys::write(self.0, buf).map_err(|err| { + self.1 = Some(err); + fmt::Error + })?; + Ok(()) } } impl fmt::Write for FileWriter { fn write_str(&mut self, s: &str) -> fmt::Result { - self.write(s.as_bytes()); + if let Ok(()) = self.write(s.as_bytes()) {}; // TODO handle error Ok(()) } } impl WriteByte for FileWriter { fn write_u8(&mut self, byte: u8) -> fmt::Result { - self.write(&[byte]); + if let Ok(()) = self.write(&[byte]) {}; // TODO handle error Ok(()) } } +/// An implementation of [`Read`] for a file descriptor. pub struct FileReader(pub c_int); impl FileReader { + // TODO: This is a bad interface. Rustify pub fn read(&mut self, buf: &mut [u8]) -> isize { Sys::read(self.0, buf) + .map(|u| u as isize) + .or_minus_one_errno() } } impl Read for FileReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let i = Sys::read(self.0, buf); + let i = Sys::read(self.0, buf) + .map(|u| u as isize) + .or_minus_one_errno(); // TODO if i >= 0 { Ok(i as usize) } else { @@ -104,6 +146,7 @@ impl Read for FileReader { } } +/// An implementation of [`Write`]/[`core::fmt::Write`] for a byte array. pub struct StringWriter(pub *mut u8, pub usize); impl Write for StringWriter { fn write(&mut self, buf: &[u8]) -> io::Result { @@ -144,6 +187,8 @@ impl WriteByte for StringWriter { } } +/// An implementation of [`Write`]/[`core::fmt::Write`] for a byte array, +/// without buffer overflow protection. pub struct UnsafeStringWriter(pub *mut u8); impl Write for UnsafeStringWriter { fn write(&mut self, buf: &[u8]) -> io::Result { @@ -173,16 +218,18 @@ impl WriteByte for UnsafeStringWriter { } } +/// An implementation of [`Read`] for a byte array, without buffer over-read +/// protection. pub struct UnsafeStringReader(pub *const u8); impl Read for UnsafeStringReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { unsafe { - for i in 0..buf.len() { + for (i, inner) in buf.iter_mut().enumerate() { if *self.0 == 0 { return Ok(i); } - buf[i] = *self.0; + *inner = *self.0; self.0 = self.0.offset(1); } Ok(buf.len()) @@ -190,6 +237,8 @@ impl Read for UnsafeStringReader { } } +/// A wrapper that keeps track of the number of bytes written with the +/// underlying writer `T`. pub struct CountingWriter { pub inner: T, pub written: usize, @@ -223,7 +272,7 @@ impl Write for CountingWriter { res } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - match self.inner.write_all(&buf) { + match self.inner.write_all(buf) { Ok(()) => (), Err(ref err) if err.kind() == io::ErrorKind::WriteZero => (), Err(err) => return Err(err), @@ -235,3 +284,138 @@ impl Write for CountingWriter { self.inner.flush() } } + +// TODO: Set a global variable once get_auxvs is called, and then implement getauxval based on +// get_auxv. + +#[cold] +pub unsafe fn auxv_iter<'a>(ptr: *const usize) -> impl Iterator + 'a { + struct St(*const usize); + impl Iterator for St { + type Item = [usize; 2]; + + fn next(&mut self) -> Option { + unsafe { + if *self.0 == self::auxv_defs::AT_NULL { + return None; + } + let kind = *self.0; + let value = *self.0.add(1); + self.0 = self.0.add(2); + + Some([kind, value]) + } + } + } + St(ptr) +} + +#[cold] +pub unsafe fn get_auxvs(ptr: *const usize) -> Box<[[usize; 2]]> { + //traverse the stack and collect argument environment variables + let mut auxvs = unsafe { auxv_iter(ptr) }.collect::>(); + + auxvs.sort_unstable_by_key(|[kind, _]| *kind); + auxvs.into_boxed_slice() +} +// TODO: Find an auxv replacement for Redox's execv protocol +#[cold] +pub unsafe fn get_auxv_raw(ptr: *const usize, requested_kind: usize) -> Option { + unsafe { auxv_iter(ptr) } + .find_map(|[kind, value]| Some(value).filter(|_| kind == requested_kind)) +} +pub fn get_auxv(auxvs: &[[usize; 2]], key: usize) -> Option { + auxvs + .binary_search_by_key(&key, |[entry_key, _]| *entry_key) + .ok() + .map(|idx| auxvs[idx][1]) +} + +#[cold] +#[cfg(target_os = "redox")] +// SAFETY: Must only be called when only one thread exists. +pub unsafe fn init(auxvs: Box<[[usize; 2]]>) { + use self::auxv_defs::*; + use redox_rt::proc::FdGuard; + + let Some(proc_fd) = get_auxv(&auxvs, AT_REDOX_PROC_FD) else { + panic!("Missing proc and thread fd!"); + }; + let Some(ns_fd) = get_auxv(&auxvs, AT_REDOX_NS_FD) else { + panic!("Missing namespace fd!"); + }; + unsafe { + redox_rt::initialize( + FdGuard::new(proc_fd).to_upper().unwrap(), + if ns_fd == usize::MAX { + None + } else { + Some(FdGuard::new(ns_fd).to_upper().unwrap()) + }, + ); + init_inner(auxvs) + } +} +#[cold] +#[cfg(target_os = "redox")] +pub unsafe fn init_inner(auxvs: Box<[[usize; 2]]>) { + use self::auxv_defs::*; + use crate::header::sys_stat::S_ISVTX; + use redox_rt::proc::FdGuard; + use syscall::MODE_PERM; + + // TODO: Is it safe to assume setup_sighandler has been called at this point? + redox_rt::sys::this_proc_call( + &mut [], + syscall::CallFlags::empty(), + &[redox_protocols::protocol::ProcCall::SyncSigPctl as u64], + ) + .expect("failed to sync signal pctl"); + + if let (Some(cwd_ptr), Some(cwd_len), Some(cwd_fd)) = ( + get_auxv(&auxvs, AT_REDOX_INITIAL_CWD_PTR), + get_auxv(&auxvs, AT_REDOX_INITIAL_CWD_LEN), + get_auxv(&auxvs, AT_REDOX_CWD_FD), + ) { + let cwd_bytes: &'static [u8] = + unsafe { core::slice::from_raw_parts(cwd_ptr as *const u8, cwd_len) }; + if let (Ok(cwd_path), Some(cwd_fd)) = ( + core::str::from_utf8(cwd_bytes), + (cwd_fd != usize::MAX).then(|| { + FdGuard::new(cwd_fd) + .to_upper() + .expect("failed to move cwd fd to upper table") + }), + ) { + self::sys::path::set_cwd_manual(cwd_path.into(), cwd_fd); + } + } + + let mut inherited_sigignmask = 0_u64; + if let Some(mask) = get_auxv(&auxvs, AT_REDOX_INHERITED_SIGIGNMASK) { + inherited_sigignmask |= mask as u64; + } + #[cfg(target_pointer_width = "32")] + if let Some(mask) = get_auxv(&auxvs, AT_REDOX_INHERITED_SIGIGNMASK_HI) { + inherited_sigignmask |= (mask as u64) << 32; + } + redox_rt::signal::apply_inherited_sigignmask(inherited_sigignmask); + + let mut inherited_sigprocmask = 0_u64; + + if let Some(mask) = get_auxv(&auxvs, AT_REDOX_INHERITED_SIGPROCMASK) { + inherited_sigprocmask |= mask as u64; + } + #[cfg(target_pointer_width = "32")] + if let Some(mask) = get_auxv(&auxvs, AT_REDOX_INHERITED_SIGPROCMASK_HI) { + inherited_sigprocmask |= (mask as u64) << 32; + } + redox_rt::signal::set_sigmask(Some(inherited_sigprocmask), None).unwrap(); + + if let Some(umask) = get_auxv(&auxvs, AT_REDOX_UMASK) { + let _ = + redox_rt::sys::swap_umask((umask as u32) & u32::from(MODE_PERM) & !(S_ISVTX as u32)); + } +} +#[cfg(not(target_os = "redox"))] +pub unsafe fn init(auxvs: Box<[[usize; 2]]>) {} diff --git a/src/platform/pal/epoll.rs b/src/platform/pal/epoll.rs index 6162368b38..0d417d7df7 100644 --- a/src/platform/pal/epoll.rs +++ b/src/platform/pal/epoll.rs @@ -1,14 +1,23 @@ -use super::super::{types::*, Pal}; -use crate::header::{signal::sigset_t, sys_epoll::epoll_event}; +use crate::{ + error::Result, + header::{bits_sigset_t::sigset_t, sys_epoll::epoll_event}, + platform::{Pal, types::c_int}, +}; +/// Platform abstraction for `epoll` functionality. pub trait PalEpoll: Pal { - fn epoll_create1(flags: c_int) -> c_int; - fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int; - fn epoll_pwait( + /// Platform implementation of [`epoll_create1()`](crate::header::sys_epoll::epoll_create1) from [`sys/epoll.h`](crate::header::sys_epoll). + fn epoll_create1(flags: c_int) -> Result; + + /// Platform implementation of [`epoll_ctl()`](crate::header::sys_epoll::epoll_ctl) from [`sys/epoll.h`](crate::header::sys_epoll). + unsafe fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> Result<()>; + + /// Platform implementation of [`epoll_pwait()`](crate::header::sys_epoll::epoll_pwait) from [`sys/epoll.h`](crate::header::sys_epoll). + unsafe fn epoll_pwait( epfd: c_int, events: *mut epoll_event, maxevents: c_int, timeout: c_int, sigmask: *const sigset_t, - ) -> c_int; + ) -> Result; } diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs index f7984dac69..ad31680e0b 100644 --- a/src/platform/pal/mod.rs +++ b/src/platform/pal/mod.rs @@ -1,15 +1,24 @@ +use core::num::NonZeroU64; + use super::types::*; use crate::{ c_str::CStr, + error::{Errno, Result}, header::{ - dirent::dirent, - sys_resource::rlimit, + bits_timespec::timespec, + fcntl::{AT_EMPTY_PATH, AT_FDCWD}, + signal::sigevent, + sys_resource::{rlimit, rusage}, + sys_select::timeval, sys_stat::stat, sys_statvfs::statvfs, - sys_time::{timeval, timezone}, + sys_time::timezone, sys_utsname::utsname, - time::timespec, + time::itimerspec, }, + ld_so::tcb::OsSpecific, + out::Out, + pthread, }; pub use self::epoll::PalEpoll; @@ -24,91 +33,256 @@ mod signal; pub use self::socket::PalSocket; mod socket; +/// Platform abstraction layer, a platform-agnostic abstraction over syscalls. pub trait Pal { - fn access(path: &CStr, mode: c_int) -> c_int; + /// Platform implementation of [`access()`](crate::header::unistd::access) from [`unistd.h`](crate::header::unistd). + fn access(path: CStr, mode: c_int) -> Result<()> { + Self::faccessat(AT_FDCWD, path, mode, 0) + } - fn brk(addr: *mut c_void) -> *mut c_void; + /// Platform implementation of [`faccessat()`](crate::header::unistd::faccessat) from [`unistd.h`](crate::header::unistd). + fn faccessat(fd: c_int, path: CStr, amode: c_int, flags: c_int) -> Result<()>; - fn chdir(path: &CStr) -> c_int; + /// Platform implementation of [`brk()`](crate::header::unistd::brk) from [`unistd.h`](crate::header::unistd). + unsafe fn brk(addr: *mut c_void) -> Result<*mut c_void>; - fn chmod(path: &CStr, mode: mode_t) -> c_int; + /// Platform implementation of [`chdir()`](crate::header::unistd::chdir) from [`unistd.h`](crate::header::unistd). + fn chdir(path: CStr) -> Result<()>; - fn chown(path: &CStr, owner: uid_t, group: gid_t) -> c_int; + /// Platform implementation of [`chmod()`](crate::header::sys_stat::chmod) from [`sys/stat.h`](crate::header::sys_stat). + fn chmod(path: CStr, mode: mode_t) -> Result<()> { + Self::fchmodat(AT_FDCWD, Some(path), mode, 0) + } - fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_int; + /// Platform implementation of [`chown()`](crate::header::unistd::chown) from [`unistd.h`](crate::header::unistd). + fn chown(path: CStr, owner: uid_t, group: gid_t) -> Result<()> { + Self::fchownat(AT_FDCWD, path, owner, group, 0) + } - fn close(fildes: c_int) -> c_int; + /// Platform implementation of [`clock_getres()`](crate::header::time::clock_getres) from [`time.h`](crate::header::time). + fn clock_getres(clk_id: clockid_t, tp: Option>) -> Result<()>; - fn dup(fildes: c_int) -> c_int; + // TODO: maybe remove tp and change signature to -> Result? + /// Platform implementation of [`clock_gettime()`](crate::header::time::clock_gettime) from [`time.h`](crate::header::time). + fn clock_gettime(clk_id: clockid_t, tp: Out) -> Result<()>; - fn dup2(fildes: c_int, fildes2: c_int) -> c_int; + /// Platform implementation of [`clock_settime()`](crate::header::time::clock_settime) from [`time.h`](crate::header::time). + unsafe fn clock_settime(clk_id: clockid_t, tp: *const timespec) -> Result<()>; - unsafe fn execve(path: &CStr, argv: *const *mut c_char, envp: *const *mut c_char) -> c_int; + /// Platform implementation of [`close()`](crate::header::unistd::close) from [`unistd.h`](crate::header::unistd). + fn close(fildes: c_int) -> Result<()>; + /// Platform implementation of [`dup()`](crate::header::unistd::dup) from [`unistd.h`](crate::header::unistd). + fn dup(fildes: c_int) -> Result; + + /// Platform implementation of [`dup2()`](crate::header::unistd::dup2) from [`unistd.h`](crate::header::unistd). + fn dup2(fildes: c_int, fildes2: c_int) -> Result; + + /// Platform implementation of [`execve()`](crate::header::unistd::execve) from [`unistd.h`](crate::header::unistd). + unsafe fn execve(path: CStr, argv: *const *mut c_char, envp: *const *mut c_char) -> Result<()>; + + /// Platform implementation of [`fexecve()`](crate::header::unistd::fexecve) from [`unistd.h`](crate::header::unistd). + unsafe fn fexecve( + fildes: c_int, + argv: *const *mut c_char, + envp: *const *mut c_char, + ) -> Result<()>; + + /// Platform implementation of [`_Exit()`](crate::header::stdlib::_Exit) from [`stdlib.h`](crate::header::stdlib) (or the equivalent [`_exit()`](crate::header::unistd::_exit) from [`unistd.h`](crate::header::unistd)). fn exit(status: c_int) -> !; - fn fchdir(fildes: c_int) -> c_int; + unsafe fn exit_thread(stack_base: *mut (), stack_size: usize) -> !; - fn fchmod(fildes: c_int, mode: mode_t) -> c_int; + /// Platform implementation of [`fchdir()`](crate::header::unistd::fchdir) from [`unistd.h`](crate::header::unistd). + fn fchdir(fildes: c_int) -> Result<()>; - fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> c_int; + /// Platform implementation of [`fchmod()`](crate::header::sys_stat::fchmod) from [`sys/stat.h`](crate::header::sys_stat). + fn fchmod(fildes: c_int, mode: mode_t) -> Result<()> { + Self::fchmodat(fildes, Some(c"".into()), mode, AT_EMPTY_PATH) + } - fn flock(fd: c_int, operation: c_int) -> c_int; + /// Platform implementation of [`fchmodat()`](crate::header::sys_stat::fchmodat) from [`sys/stat.h`](crate::header::sys_stat). + fn fchmodat(dirfd: c_int, path: Option, mode: mode_t, flags: c_int) -> Result<()>; - fn fstat(fildes: c_int, buf: *mut stat) -> c_int; + /// Platform implementation of [`fchown()`](crate::header::unistd::fchown) from [`unistd.h`](crate::header::unistd). + fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> Result<()> { + Self::fchownat(fildes, c"".into(), owner, group, AT_EMPTY_PATH) + } - fn fstatvfs(fildes: c_int, buf: *mut statvfs) -> c_int; + /// Platform implementation of [`fchownat()`](crate::header::unistd::fchownat) from [`unistd.h`](crate::header::unistd). + fn fchownat(fildes: c_int, path: CStr, owner: uid_t, group: gid_t, flags: c_int) -> Result<()>; - fn fcntl(fildes: c_int, cmd: c_int, arg: c_int) -> c_int; + /// Platform implementation of [`fdatasync()`](crate::header::unistd::fdatasync) from [`unistd.h`](crate::header::unistd). + fn fdatasync(fildes: c_int) -> Result<()>; - fn fork() -> pid_t; + /// Platform implementation of [`flock()`](crate::header::sys_file::flock) from [`sys/file.h`](crate::header::sys_file). + fn flock(fd: c_int, operation: c_int) -> Result<()>; - fn fpath(fildes: c_int, out: &mut [u8]) -> ssize_t; + /// Platform implementation of [`fstat()`](crate::header::sys_stat::fstat) from [`sys/stat.h`](crate::header::sys_stat). + fn fstat(fildes: c_int, buf: Out) -> Result<()> { + Self::fstatat(fildes, Some(c"".into()), buf, 0) + } - fn fsync(fildes: c_int) -> c_int; + /// Platform implementation of [`fstatat()`](crate::header::sys_stat::fstatat) from [`sys/stat.h`](crate::header::sys_stat). + fn fstatat(fildes: c_int, path: Option, buf: Out, flags: c_int) -> Result<()>; - fn ftruncate(fildes: c_int, length: off_t) -> c_int; + /// Platform implementation of [`fstatvfs()`](crate::header::sys_statvfs::fstatvfs) from [`sys/statvfs.h`](crate::header::sys_statvfs). + fn fstatvfs(fildes: c_int, buf: Out) -> Result<()>; - fn futex(addr: *mut c_int, op: c_int, val: c_int) -> c_int; + /// Platform implementation of [`fcntl()`](crate::header::fcntl::fcntl) from [`fcntl.h`](crate::header::fcntl). + fn fcntl(fildes: c_int, cmd: c_int, arg: c_ulonglong) -> Result; - fn futimens(fd: c_int, times: *const timespec) -> c_int; + /// Platform implementation of [`_Fork()`](crate::header::unistd::_Fork) from [`unistd.h`](crate::header::unistd). + unsafe fn fork() -> Result; - fn utimens(path: &CStr, times: *const timespec) -> c_int; + fn fpath(fildes: c_int, out: &mut [u8]) -> Result; - fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char; + /// Platform implementation of [`fsync()`](crate::header::unistd::fsync) from [`unistd.h`](crate::header::unistd). + fn fsync(fildes: c_int) -> Result<()>; - fn getdents(fd: c_int, dirents: *mut dirent, bytes: usize) -> c_int; + /// Platform implementation of [`ftruncate()`](crate::header::unistd::ftruncate) from [`unistd.h`](crate::header::unistd). + fn ftruncate(fildes: c_int, length: off_t) -> Result<()>; + unsafe fn futex_wait(addr: *mut u32, val: u32, deadline: Option<×pec>) -> Result<()>; + + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result; + + /// Platform implementation of [`futimens()`](crate::header::sys_stat::futimens) from [`sys/stat.h`](crate::header::sys_stat). + unsafe fn futimens(fd: c_int, times: *const timespec) -> Result<()>; + + /// Platform implementation of `utimens()` (TODO) from [`sys/stat.h`](crate::header::sys_stat). + unsafe fn utimens(path: CStr, times: *const timespec) -> Result<()>; + + /// Platform implementation of [`getcwd()`](crate::header::unistd::getcwd) from [`unistd.h`](crate::header::unistd). + fn getcwd(buf: Out<[u8]>) -> Result<()>; + + fn getdents(fd: c_int, buf: &mut [u8], opaque_offset: u64) -> Result; + + fn dir_seek(fd: c_int, opaque_offset: u64) -> Result<()>; + + // SAFETY: This_dent must satisfy platform-specific size and alignment constraints. On Linux, + // this means the buffer came from a valid getdents64 invocation, whereas on Redox, every + // possible this_dent slice is safe (and will be validated). + unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)>; + + // Always successful + /// Platform implementation of [`getegid()`](crate::header::unistd::getegid) from [`unistd.h`](crate::header::unistd). fn getegid() -> gid_t; + // Always successful + /// Platform implementation of [`geteuid()`](crate::header::unistd::geteuid) from [`unistd.h`](crate::header::unistd). fn geteuid() -> uid_t; + // Always successful + /// Platform implementation of [`getgid()`](crate::header::unistd::getgid) from [`unistd.h`](crate::header::unistd). fn getgid() -> gid_t; - fn getpgid(pid: pid_t) -> pid_t; + /// Platform implementation of [`getgroups()`](crate::header::unistd::getgroups) from [`unistd.h`](crate::header::unistd). + fn getgroups(list: Out<[gid_t]>) -> Result; + /* Note that this is distinct from the legacy POSIX function + * getpagesize(), which returns a c_int. On some Linux platforms, + * page size may be determined through a syscall ("getpagesize"). */ + fn getpagesize() -> usize; + + /// Platform implementation of [`getpgid()`](crate::header::unistd::getpgid) from [`unistd.h`](crate::header::unistd). + fn getpgid(pid: pid_t) -> Result; + + // Always successful + /// Platform implementation of [`getpid()`](crate::header::unistd::getpid) from [`unistd.h`](crate::header::unistd). fn getpid() -> pid_t; + // Always successful + /// Platform implementation of [`getppid()`](crate::header::unistd::getppid) from [`unistd.h`](crate::header::unistd). fn getppid() -> pid_t; - fn getrandom(buf: &mut [u8], flags: c_uint) -> ssize_t; + /// Platform implementation of [`getpriority()`](crate::header::sys_resource::getpriority) from [`sys/resource.h`](crate::header::sys_resource). + fn getpriority(which: c_int, who: id_t) -> Result; - unsafe fn getrlimit(resource: c_int, rlim: *mut rlimit) -> c_int; + /// Platform implementation of [`getrandom()`](crate::header::sys_random::getrandom) from [`sys/random.h`](crate::header::sys_random). + fn getrandom(buf: &mut [u8], flags: c_uint) -> Result; + /// Platform implementation of [`getresgid()`](crate::header::unistd::getresgid) from [`unistd.h`](crate::header::unistd). + fn getresgid( + rgid: Option>, + egid: Option>, + sgid: Option>, + ) -> Result<()>; + + /// Platform implementation of [`getresuid()`](crate::header::unistd::getresuid) from [`unistd.h`](crate::header::unistd). + fn getresuid( + ruid: Option>, + euid: Option>, + suid: Option>, + ) -> Result<()>; + + /// Platform implementation of [`getrlimit()`](crate::header::sys_resource::getrlimit) from [`sys/resource.h`](crate::header::sys_resource). + fn getrlimit(resource: c_int, rlim: Out) -> Result<()>; + + /// Platform implementation of [`setrlimit()`](crate::header::sys_resource::setrlimit) from [`sys/resource.h`](crate::header::sys_resource). + unsafe fn setrlimit(resource: c_int, rlim: *const rlimit) -> Result<()>; + + /// Platform implementation of [`getrusage()`](crate::header::sys_resource::getrusage) from [`sys/resource.h`](crate::header::sys_resource). + fn getrusage(who: c_int, r_usage: Out) -> Result<()>; + + /// Platform implementation of [`getsid()`](crate::header::unistd::getsid) from [`unistd.h`](crate::header::unistd). + fn getsid(pid: pid_t) -> Result; + + // Always successful + /// Platform implementation of `gettid()` (TODO) from [`unistd.h`](crate::header::unistd). fn gettid() -> pid_t; - fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int; + /// Platform implementation of [`gettimeofday()`](crate::header::sys_time::gettimeofday) from [`sys/time.h`](crate::header::sys_time). + fn gettimeofday(tp: Out, tzp: Option>) -> Result<()>; + /// Platform implementation of [`getuid()`](crate::header::unistd::getuid) from [`unistd.h`](crate::header::unistd). fn getuid() -> uid_t; - fn link(path1: &CStr, path2: &CStr) -> c_int; + /// Platform implementation of [`lchown()`](crate::header::unistd::lchown) from [`unistd.h`](crate::header::unistd). + fn lchown(path: CStr, owner: uid_t, group: gid_t) -> Result<()>; - fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> off_t; + /// Platform implementation of [`link()`](crate::header::unistd::link) from [`unistd.h`](crate::header::unistd). + fn link(path1: CStr, path2: CStr) -> Result<()> { + Self::linkat(AT_FDCWD, path1, AT_FDCWD, path2, 0) + } - fn mkdir(path: &CStr, mode: mode_t) -> c_int; + /// Platform implementation of [`linkat()`](crate::header::unistd::linkat) from [`unistd.h`](crate::header::unistd). + fn linkat(fd1: c_int, oldpath: CStr, fd2: c_int, newpath: CStr, flags: c_int) -> Result<()>; - fn mkfifo(path: &CStr, mode: mode_t) -> c_int; + /// Platform implementation of [`lseek()`](crate::header::unistd::lseek) from [`unistd.h`](crate::header::unistd). + fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result; + /// Platform implementation of [`mkdirat()`](crate::header::sys_stat::mkdirat) from [`sys/stat.h`](crate::header::sys_stat). + fn mkdirat(fildes: c_int, path: CStr, mode: mode_t) -> Result<()>; + + /// Platform implementation of [`mkdir()`](crate::header::sys_stat::mkdir) from [`sys/stat.h`](crate::header::sys_stat). + fn mkdir(path: CStr, mode: mode_t) -> Result<()> { + Self::mkdirat(AT_FDCWD, path, mode) + } + + /// Platform implementation of [`mkfifoat()`](crate::header::sys_stat::mkfifoat) from [`sys/stat.h`](crate::header::sys_stat). + fn mkfifoat(dir_fd: c_int, path: CStr, mode: mode_t) -> Result<()>; + + /// Platform implementation of [`mkfifo()`](crate::header::sys_stat::mkfifo) from [`sys/stat.h`](crate::header::sys_stat). + fn mkfifo(path: CStr, mode: mode_t) -> Result<()> { + Self::mkfifoat(AT_FDCWD, path, mode) + } + + /// Platform implementation of [`mknodat()`](crate::header::sys_stat::mknodat) from [`sys/stat.h`](crate::header::sys_stat). + fn mknodat(fildes: c_int, path: CStr, mode: mode_t, dev: dev_t) -> Result<()>; + + /// Platform implementation of [`mknod()`](crate::header::sys_stat::mknod) from [`sys/stat.h`](crate::header::sys_stat). + fn mknod(path: CStr, mode: mode_t, dev: dev_t) -> Result<()> { + Self::mknodat(AT_FDCWD, path, mode, dev) + } + + /// Platform implementation of [`mlock()`](crate::header::sys_mman::mlock) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn mlock(addr: *const c_void, len: usize) -> Result<()>; + + /// Platform implementation of [`mlockall()`](crate::header::sys_mman::mlockall) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn mlockall(flags: c_int) -> Result<()>; + + /// Platform implementation of [`mmap()`](crate::header::sys_mman::mmap) from [`sys/mman.h`](crate::header::sys_mman). unsafe fn mmap( addr: *mut c_void, len: usize, @@ -116,49 +290,170 @@ pub trait Pal { flags: c_int, fildes: c_int, off: off_t, - ) -> *mut c_void; + ) -> Result<*mut c_void>; - unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int; + /// Platform implementation of [`mremap()`](crate::header::sys_mman::mremap) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn mremap( + addr: *mut c_void, + len: usize, + new_len: usize, + flags: c_int, + args: *mut c_void, + ) -> Result<*mut c_void>; - unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> c_int; + /// Platform implementation of [`mprotect()`](crate::header::sys_mman::mprotect) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> Result<()>; - unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int; + /// Platform implementation of [`msync()`](crate::header::sys_mman::msync) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()>; - fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int; + /// Platform implementation of [`munlock()`](crate::header::sys_mman::munlock) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()>; - fn open(path: &CStr, oflag: c_int, mode: mode_t) -> c_int; + /// Platform implementation of [`madvise()`](crate::header::sys_mman::madvise) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()>; - fn pipe2(fildes: &mut [c_int], flags: c_int) -> c_int; + /// Platform implementation of [`munlockall()`](crate::header::sys_mman::munlockall) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn munlockall() -> Result<()>; - unsafe fn pte_clone(stack: *mut usize) -> pid_t; + /// Platform implementation of [`munmap()`](crate::header::sys_mman::munmap) from [`sys/mman.h`](crate::header::sys_mman). + unsafe fn munmap(addr: *mut c_void, len: usize) -> Result<()>; - fn read(fildes: c_int, buf: &mut [u8]) -> ssize_t; + /// Platform implementation of [`nanosleep()`](crate::header::time::nanosleep) from [`time.h`](crate::header::time). + unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()>; - fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t; + /// Platform implementation of [`open()`](crate::header::fcntl::open) from [`fcntl.h`](crate::header::fcntl). + fn open(path: CStr, oflag: c_int, mode: mode_t) -> Result; - fn rename(old: &CStr, new: &CStr) -> c_int; + /// Platform implementation of `openat()` (TODO) from [`fcntl.h`](crate::header::fcntl). + fn openat(dirfd: c_int, path: CStr, oflag: c_int, mode: mode_t) -> Result; - fn rmdir(path: &CStr) -> c_int; + /// Platform implementation of [`pipe2()`](crate::header::unistd::pipe2) from [`unistd.h`](crate::header::unistd). + fn pipe2(fildes: Out<[c_int; 2]>, flags: c_int) -> Result<()>; - fn sched_yield() -> c_int; + /// Platform implementation of [`posix_fallocate()`](crate::header::fcntl::posix_fallocate) from [`fcntl.h`](crate::header::fcntl). + fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()>; - fn setpgid(pid: pid_t, pgid: pid_t) -> c_int; + /// Platform implementation of [`posix_getdents()`](crate::header::dirent::posix_getdents) from [`dirent.h`](crate::header::dirent). + fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result; - fn setregid(rgid: gid_t, egid: gid_t) -> c_int; + unsafe fn rlct_clone( + stack: *mut usize, + os_specific: &mut OsSpecific, + ) -> Result; - fn setreuid(ruid: uid_t, euid: uid_t) -> c_int; + unsafe fn rlct_kill(os_tid: pthread::OsTid, signal: usize) -> Result<()>; - fn symlink(path1: &CStr, path2: &CStr) -> c_int; + fn current_os_tid() -> pthread::OsTid; + /// Platform implementation of [`read()`](crate::header::unistd::read) from [`unistd.h`](crate::header::unistd). + fn read(fildes: c_int, buf: &mut [u8]) -> Result; + + /// Platform implementation of [`pread()`](crate::header::unistd::pread) from [`unistd.h`](crate::header::unistd). + fn pread(fildes: c_int, buf: &mut [u8], offset: off_t) -> Result; + + /// Platform implementation of [`readlink()`](crate::header::unistd::readlink) from [`unistd.h`](crate::header::unistd). + fn readlink(pathname: CStr, out: &mut [u8]) -> Result { + Self::readlinkat(AT_FDCWD, pathname, out) + } + + /// Platform implementation of [`readlinkat()`](crate::header::unistd::readlinkat) from [`unistd.h`](crate::header::unistd). + fn readlinkat(dirfd: c_int, pathname: CStr, out: &mut [u8]) -> Result; + + /// Platform implementation of [`rename()`](crate::header::stdio::rename) from [`stdio.h`](crate::header::stdio). + fn rename(old: CStr, new: CStr) -> Result<()> { + Self::renameat(AT_FDCWD, old, AT_FDCWD, new) + } + + /// Platform implementation of [`renameat()`](crate::header::stdio::renameat) from [`stdio.h`](crate::header::stdio). + fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()> { + Self::renameat2(old_dir, old_path, new_dir, new_path, 0) + } + + /// Platform implementation of [`renameat2()`](crate::header::stdio::renameat2) from [`stdio.h`](crate::header::stdio). + fn renameat2( + old_dir: c_int, + old_path: CStr, + new_dir: c_int, + new_path: CStr, + flags: c_uint, + ) -> Result<()>; + + /// Platform implementation of [`rmdir()`](crate::header::unistd::rmdir) from [`unistd.h`](crate::header::unistd). + fn rmdir(path: CStr) -> Result<()>; + + /// Platform implementation of [`sched_yield()`](crate::header::sched::sched_yield) from [`sched.h`](crate::header::sched). + fn sched_yield() -> Result<()>; + + /// Platform implementation of [`setgroups()`](crate::header::grp::setgroups) from [`grp.h`](crate::header::grp). + unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()>; + + /// Platform implementation of [`setpgid()`](crate::header::unistd::setpgid) from [`unistd.h`](crate::header::unistd). + fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()>; + + /// Platform implementation of [`setpriority()`](crate::header::sys_resource::setpriority) from [`sys/resource.h`](crate::header::sys_resource). + fn setpriority(which: c_int, who: id_t, prio: c_int) -> Result<()>; + + /// Platform implementation of [`setresgid()`](crate::header::unistd::setresgid) from [`unistd.h`](crate::header::unistd). + fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) -> Result<()>; + + /// Platform implementation of [`setresuid()`](crate::header::unistd::setresuid) from [`unistd.h`](crate::header::unistd). + fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) -> Result<()>; + + /// Platform implementation of [`setsid()`](crate::header::unistd::setsid) from [`unistd.h`](crate::header::unistd). + fn setsid() -> Result; + + /// Platform implementation of [`symlink()`](crate::header::unistd::symlink) from [`unistd.h`](crate::header::unistd). + fn symlink(path1: CStr, path2: CStr) -> Result<()> { + Self::symlinkat(path1, AT_FDCWD, path2) + } + + /// Platform implementation of [`symlinkat()`](crate::header::unistd::symlinkat) from [`unistd.h`](crate::header::unistd). + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()>; + + /// Platform implementation of [`sync()`](crate::header::unistd::sync) from [`unistd.h`](crate::header::unistd). + fn sync() -> Result<()>; + + /// Platform implementation of [`timer_create()`](crate::header::time::timer_create) from [`time.h`](crate::header::time). + fn timer_create(clock_id: clockid_t, evp: &sigevent, timerid: Out) -> Result<()>; + + /// Platform implementation of [`timer_delete()`](crate::header::time::timer_delete) from [`time.h`](crate::header::time). + fn timer_delete(timerid: timer_t) -> Result<()>; + + /// Platform implementation of [`timer_gettime()`](crate::header::time::timer_gettime) from [`time.h`](crate::header::time). + fn timer_gettime(timerid: timer_t, value: Out) -> Result<()>; + + /// Platform implementation of [`timer_settime()`](crate::header::time::timer_settime) from [`time.h`](crate::header::time). + fn timer_settime( + timerid: timer_t, + flags: c_int, + value: &itimerspec, + ovalue: Option>, + ) -> Result<()>; + + // Always successful + /// Platform implementation of [`umask()`](crate::header::sys_stat::umask) from [`sys/stat.h`](crate::header::sys_stat). fn umask(mask: mode_t) -> mode_t; - fn uname(utsname: *mut utsname) -> c_int; + /// Platform implementation of [`uname()`](crate::header::sys_utsname::uname) from [`sys/utsname.h`](crate::header::sys_utsname). + fn uname(utsname: Out) -> Result<()>; - fn unlink(path: &CStr) -> c_int; + /// Platform implementation of [`unlink()`](crate::header::unistd::unlink) from [`unistd.h`](crate::header::unistd). + fn unlink(path: CStr) -> Result<()> { + Self::unlinkat(AT_FDCWD, path, 0) + } - fn waitpid(pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t; + /// Platform implementation of [`unlinkat()`](crate::header::unistd::unlinkat) from [`unistd.h`](crate::header::unistd). + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()>; - fn write(fildes: c_int, buf: &[u8]) -> ssize_t; + /// Platform implementation of [`waitpid()`](crate::header::sys_wait::waitpid) from [`sys/wait.h`](crate::header::sys_wait). + fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result; + + /// Platform implementation of [`write()`](crate::header::unistd::write) from [`unistd.h`](crate::header::unistd). + fn write(fildes: c_int, buf: &[u8]) -> Result; + + /// Platform implementation of [`pwrite()`](crate::header::unistd::pwrite) from [`unistd.h`](crate::header::unistd). + fn pwrite(fildes: c_int, buf: &[u8], offset: off_t) -> Result; fn verify() -> bool; } diff --git a/src/platform/pal/ptrace.rs b/src/platform/pal/ptrace.rs index cc13ebbb68..d174435c7d 100644 --- a/src/platform/pal/ptrace.rs +++ b/src/platform/pal/ptrace.rs @@ -1,5 +1,15 @@ -use super::super::{types::*, Pal}; +use crate::{ + error::Result, + platform::{Pal, types::*}, +}; +/// Platform abstraction for `ptrace` functionality. pub trait PalPtrace: Pal { - fn ptrace(request: c_int, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> c_int; + /// Platform implementation of [`ptrace()`](crate::header::sys_ptrace::ptrace) from [`sys/ptrace.h`](crate::header::sys_ptrace). + unsafe fn ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, + ) -> Result; } diff --git a/src/platform/pal/signal.rs b/src/platform/pal/signal.rs index b432f57922..1eeba0fa1e 100644 --- a/src/platform/pal/signal.rs +++ b/src/platform/pal/signal.rs @@ -1,23 +1,62 @@ -use super::super::{types::*, Pal}; -use crate::header::{ - signal::{sigaction, sigset_t, stack_t}, - sys_time::itimerval, +use super::super::{ + Pal, + types::{c_int, c_uint, pid_t}, +}; +#[allow(deprecated)] +use crate::header::sys_time::itimerval; +use crate::{ + error::{Errno, Result}, + header::{ + bits_sigset_t::sigset_t, + bits_timespec::timespec, + signal::{sigaction, siginfo_t, sigval, stack_t}, + }, }; +/// Platform abstraction of signal-related functionality. pub trait PalSignal: Pal { - fn getitimer(which: c_int, out: *mut itimerval) -> c_int; + /// Platform implementation of [`getitimer()`](crate::header::sys_time::getitimer) from [`sys/time.h`](crate::header::sys_time). + #[allow(deprecated)] + fn getitimer(which: c_int, out: &mut itimerval) -> Result<()>; - fn kill(pid: pid_t, sig: c_int) -> c_int; + /// Platform implementation of [`kill()`](crate::header::signal::kill) from [`signal.h`](crate::header::signal). + fn kill(pid: pid_t, sig: c_int) -> Result<()>; - fn killpg(pgrp: pid_t, sig: c_int) -> c_int; + /// Platform implementation of [`sigqueue()`](crate::header::signal::sigqueue) from [`signal.h`](crate::header::signal). + fn sigqueue(pid: pid_t, sig: c_int, val: sigval) -> Result<()>; - fn raise(sig: c_int) -> c_int; + /// Platform implementation of [`killpg()`](crate::header::signal::killpg) from [`signal.h`](crate::header::signal). + fn killpg(pgrp: pid_t, sig: c_int) -> Result<()>; - fn setitimer(which: c_int, new: *const itimerval, old: *mut itimerval) -> c_int; + /// Platform implementation of [`raise()`](crate::header::signal::raise) from [`signal.h`](crate::header::signal). + fn raise(sig: c_int) -> Result<()>; - fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> c_int; + /// Platform implementation of [`setitimer()`](crate::header::sys_time::setitimer) from [`sys/time.h`](crate::header::sys_time). + #[allow(deprecated)] + fn setitimer(which: c_int, new: &itimerval, old: Option<&mut itimerval>) -> Result<()>; - fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int; + /// Platform implementation of [`alarm()`](crate::header::unistd::alarm) from [`unistd.h`](crate::header::unistd). + fn alarm(seconds: c_uint) -> c_uint; - fn sigprocmask(how: c_int, set: *const sigset_t, oset: *mut sigset_t) -> c_int; + /// Platform implementation of [`sigaction()`](crate::header::signal::sigaction()) from [`signal.h`](crate::header::signal). + fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> Result<()>; + + /// Platform implementation of [`sigaltstack()`](crate::header::signal::sigaltstack()) from [`signal.h`](crate::header::signal). + unsafe fn sigaltstack(ss: Option<&stack_t>, old_ss: Option<&mut stack_t>) -> Result<()>; + + /// Platform implementation of [`sigpending()`](crate::header::signal::sigpending) from [`signal.h`](crate::header::signal). + fn sigpending(set: &mut sigset_t) -> Result<()>; + + /// Platform implementation of [`sigprocmask()`](crate::header::signal::sigprocmask) from [`signal.h`](crate::header::signal). + fn sigprocmask(how: c_int, set: Option<&sigset_t>, oset: Option<&mut sigset_t>) -> Result<()>; + + /// Platform implementation of [`sigsuspend()`](crate::header::signal::sigsuspend) from [`signal.h`](crate::header::signal). + fn sigsuspend(mask: &sigset_t) -> Errno; // always fails + + /// Platform implementation of [`sigtimedwait()`](crate::header::signal::sigtimedwait) from [`signal.h`](crate::header::signal). + fn sigtimedwait( + set: &sigset_t, + sig: Option<&mut siginfo_t>, + tp: Option<×pec>, + ) -> Result; } diff --git a/src/platform/pal/socket.rs b/src/platform/pal/socket.rs index 47ea952bf3..9d2c29ffbb 100644 --- a/src/platform/pal/socket.rs +++ b/src/platform/pal/socket.rs @@ -1,35 +1,61 @@ -use super::super::{types::*, Pal}; -use crate::header::sys_socket::{sockaddr, socklen_t}; +use crate::{ + error::Result, + header::{ + bits_socklen_t::socklen_t, + sys_socket::{msghdr, sockaddr}, + }, + platform::{ + Pal, + types::{c_int, c_void, size_t}, + }, +}; +/// Platform abstraction of socket functionality. pub trait PalSocket: Pal { - unsafe fn accept(socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int; + /// Platform implementation of [`accept()`](crate::header::sys_socket::accept) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn accept( + socket: c_int, + address: *mut sockaddr, + address_len: *mut socklen_t, + ) -> Result; - unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int; + /// Platform implementation of [`bind()`](crate::header::sys_socket::bind) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()>; - unsafe fn connect(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int; + /// Platform implementation of [`connect()`](crate::header::sys_socket::connect) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn connect( + socket: c_int, + address: *const sockaddr, + address_len: socklen_t, + ) -> Result; + /// Platform implementation of [`getpeername()`](crate::header::sys_socket::getpeername) from [`sys/socket.h`](crate::header::sys_socket). unsafe fn getpeername( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int; + ) -> Result<()>; + /// Platform implementation of [`getsockname()`](crate::header::sys_socket::getsockname) from [`sys/socket.h`](crate::header::sys_socket). unsafe fn getsockname( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int; + ) -> Result<()>; - fn getsockopt( + /// Platform implementation of [`getsockopt()`](crate::header::sys_socket::getsockopt) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn getsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *mut c_void, option_len: *mut socklen_t, - ) -> c_int; + ) -> Result<()>; - fn listen(socket: c_int, backlog: c_int) -> c_int; + /// Platform implementation of [`listen()`](crate::header::sys_socket::listen) from [`sys/socket.h`](crate::header::sys_socket). + fn listen(socket: c_int, backlog: c_int) -> Result<()>; + /// Platform implementation of [`recvfrom()`](crate::header::sys_socket::recvfrom) from [`sys/socket.h`](crate::header::sys_socket). unsafe fn recvfrom( socket: c_int, buf: *mut c_void, @@ -37,8 +63,15 @@ pub trait PalSocket: Pal { flags: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> ssize_t; + ) -> Result; + /// Platform implementation of [`recvmsg()`](crate::header::sys_socket::recvmsg) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn recvmsg(socket: c_int, msg: *mut msghdr, flags: c_int) -> Result; + + /// Platform implementation of [`sendmsg()`](crate::header::sys_socket::sendmsg) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn sendmsg(socket: c_int, msg: *const msghdr, flags: c_int) -> Result; + + /// Platform implementation of [`sendto()`](crate::header::sys_socket::sendto) from [`sys/socket.h`](crate::header::sys_socket). unsafe fn sendto( socket: c_int, buf: *const c_void, @@ -46,19 +79,23 @@ pub trait PalSocket: Pal { flags: c_int, dest_addr: *const sockaddr, dest_len: socklen_t, - ) -> ssize_t; + ) -> Result; - fn setsockopt( + /// Platform implementation of [`setsockopt()`](crate::header::sys_socket::setsockopt) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn setsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *const c_void, option_len: socklen_t, - ) -> c_int; + ) -> Result<()>; - fn shutdown(socket: c_int, how: c_int) -> c_int; + /// Platform implementation of [`shutdown()`](crate::header::sys_socket::shutdown) from [`sys/socket.h`](crate::header::sys_socket). + fn shutdown(socket: c_int, how: c_int) -> Result<()>; - unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> c_int; + /// Platform implementation of [`socket()`](crate::header::sys_socket::socket) from [`sys/socket.h`](crate::header::sys_socket). + unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> Result; - fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> c_int; + /// Platform implementation of [`socketpair()`](crate::header::sys_socket::socketpair) from [`sys/socket.h`](crate::header::sys_socket). + fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> Result<()>; } diff --git a/src/platform/pte.rs b/src/platform/pte.rs deleted file mode 100644 index 39e004582e..0000000000 --- a/src/platform/pte.rs +++ /dev/null @@ -1,410 +0,0 @@ -#![allow(non_snake_case)] - -use alloc::{boxed::Box, collections::BTreeMap}; -use core::{ - intrinsics, ptr, - sync::atomic::{AtomicU32, Ordering}, -}; - -use crate::{ - header::{sys_mman, time::timespec}, - ld_so::{ - linker::Linker, - tcb::{Master, Tcb}, - }, - platform::{ - types::{c_int, c_uint, c_void, pid_t, size_t}, - Pal, Sys, - }, - sync::{Mutex, Semaphore}, - ALLOCATOR, -}; - -type pte_osThreadHandle = pid_t; -type pte_osMutexHandle = *mut Mutex<()>; -type pte_osSemaphoreHandle = *mut Semaphore; -type pte_osThreadEntryPoint = unsafe extern "C" fn(params: *mut c_void) -> *mut c_void; - -#[repr(C)] -#[derive(Eq, PartialEq)] -#[allow(dead_code)] -pub enum pte_osResult { - PTE_OS_OK = 0, - PTE_OS_NO_RESOURCES, - PTE_OS_GENERAL_FAILURE, - PTE_OS_TIMEOUT, - PTE_OS_INTERRUPTED, - PTE_OS_INVALID_PARAM, -} - -use self::pte_osResult::*; - -static mut pid_mutexes: Option> = None; -static mut pid_mutexes_lock: Mutex<()> = Mutex::new(()); - -static mut pid_stacks: Option> = None; -static mut pid_stacks_lock: Mutex<()> = Mutex::new(()); - -#[thread_local] -static mut LOCALS: *mut BTreeMap = ptr::null_mut(); - -static NEXT_KEY: AtomicU32 = AtomicU32::new(0); - -unsafe fn locals() -> &'static mut BTreeMap { - if LOCALS.is_null() { - LOCALS = Box::into_raw(Box::new(BTreeMap::new())); - } - &mut *LOCALS -} - -// pte_osResult pte_osInit(void) -#[no_mangle] -pub unsafe extern "C" fn pte_osInit() -> pte_osResult { - PTE_OS_OK -} - -/// A shim to wrap thread entry points in logic to set up TLS, for example -unsafe extern "C" fn pte_osThreadShim( - entryPoint: pte_osThreadEntryPoint, - argv: *mut c_void, - mutex: pte_osMutexHandle, - tls_size: usize, - tls_masters_ptr: *mut Master, - tls_masters_len: usize, - tls_linker_ptr: *const Mutex, - tls_mspace: usize, -) { - // The kernel allocated TLS does not have masters set, so do not attempt to copy it. - // It will be copied by the kernel. - if !tls_masters_ptr.is_null() { - let tcb = Tcb::new(tls_size).unwrap(); - tcb.masters_ptr = tls_masters_ptr; - tcb.masters_len = tls_masters_len; - tcb.linker_ptr = tls_linker_ptr; - tcb.mspace = tls_mspace; - tcb.copy_masters().unwrap(); - tcb.activate(); - } - - // Wait until pte_osThreadStart - pte_osMutexLock(mutex); - entryPoint(argv); - pte_osThreadExit(); -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadCreate( - entryPoint: pte_osThreadEntryPoint, - stackSize: c_int, - _initialPriority: c_int, - argv: *mut c_void, - ppte_osThreadHandle: *mut pte_osThreadHandle, -) -> pte_osResult { - // Create a locked mutex, unlocked by pte_osThreadStart - let mutex: pte_osMutexHandle = Box::into_raw(Box::new(Mutex::locked(()))); - - let stack_size = if stackSize == 0 { - 1024 * 1024 - } else { - stackSize as usize - }; - let stack_base = sys_mman::mmap( - ptr::null_mut(), - stack_size, - sys_mman::PROT_READ | sys_mman::PROT_WRITE, - sys_mman::MAP_SHARED | sys_mman::MAP_ANONYMOUS, - -1, - 0, - ); - if stack_base as isize == -1 { - return PTE_OS_GENERAL_FAILURE; - } - ptr::write_bytes(stack_base as *mut u8, 0, stack_size); - let stack_end = stack_base.add(stack_size); - let mut stack = stack_end as *mut usize; - { - let mut push = |value: usize| { - stack = stack.offset(-1); - *stack = value; - }; - - //WARNING: Stack must be 128-bit aligned for SSE - if let Some(tcb) = Tcb::current() { - push(tcb.mspace as usize); - push(tcb.linker_ptr as usize); - push(tcb.masters_len); - push(tcb.masters_ptr as usize); - push(tcb.tls_len); - } else { - push(ALLOCATOR.get_book_keeper()); - push(0); - push(0); - push(0); - push(0); - } - - push(mutex as usize); - - push(argv as usize); - push(entryPoint as usize); - - push(pte_osThreadShim as usize); - } - - let id = Sys::pte_clone(stack); - if id < 0 { - return PTE_OS_GENERAL_FAILURE; - } - - pte_osMutexLock(&mut pid_mutexes_lock); - if pid_mutexes.is_none() { - pid_mutexes = Some(BTreeMap::new()); - } - pid_mutexes.as_mut().unwrap().insert(id, mutex); - pte_osMutexUnlock(&mut pid_mutexes_lock); - - pte_osMutexLock(&mut pid_stacks_lock); - if pid_stacks.is_none() { - pid_stacks = Some(BTreeMap::new()); - } - pid_stacks - .as_mut() - .unwrap() - .insert(id, (stack_base, stack_size)); - pte_osMutexUnlock(&mut pid_stacks_lock); - - *ppte_osThreadHandle = id; - - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadStart(handle: pte_osThreadHandle) -> pte_osResult { - let mut ret = PTE_OS_GENERAL_FAILURE; - pte_osMutexLock(&mut pid_mutexes_lock); - if let Some(ref mutexes) = pid_mutexes { - if let Some(mutex) = mutexes.get(&handle) { - pte_osMutexUnlock(*mutex); - ret = PTE_OS_OK; - } - } - pte_osMutexUnlock(&mut pid_mutexes_lock); - ret -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadExit() { - Sys::exit(0); -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadExitAndDelete(handle: pte_osThreadHandle) -> pte_osResult { - let res = pte_osThreadDelete(handle); - if res != PTE_OS_OK { - return res; - } - pte_osThreadExit(); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadDelete(handle: pte_osThreadHandle) -> pte_osResult { - pte_osMutexLock(&mut pid_mutexes_lock); - if let Some(ref mut mutexes) = pid_mutexes { - if let Some(mutex) = mutexes.remove(&handle) { - Box::from_raw(mutex); - } - } - pte_osMutexUnlock(&mut pid_mutexes_lock); - - pte_osMutexLock(&mut pid_stacks_lock); - if let Some(ref mut stacks) = pid_stacks { - if let Some((stack_base, stack_size)) = stacks.remove(&handle) { - //TODO: this currently unmaps the thread's stack, while it is being used! - //sys_mman::munmap(stack_base, stack_size); - } - } - pte_osMutexUnlock(&mut pid_stacks_lock); - - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadWaitForEnd(handle: pte_osThreadHandle) -> pte_osResult { - let mut status = 0; - Sys::waitpid(handle, &mut status, 0); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadCancel(handle: pte_osThreadHandle) -> pte_osResult { - //TODO: allow cancel of thread - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadCheckCancel(handle: pte_osThreadHandle) -> pte_osResult { - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadSleep(msecs: c_uint) { - let tm = timespec { - tv_sec: msecs as i64 / 1000, - tv_nsec: (msecs % 1000) as i64 * 1000000, - }; - Sys::nanosleep(&tm, ptr::null_mut()); -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadGetHandle() -> pte_osThreadHandle { - Sys::gettid() -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadGetPriority(threadHandle: pte_osThreadHandle) -> c_int { - // XXX Shouldn't Redox support priorities? - 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadSetPriority( - threadHandle: pte_osThreadHandle, - newPriority: c_int, -) -> pte_osResult { - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadGetMinPriority() -> c_int { - 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadGetMaxPriority() -> c_int { - 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osThreadGetDefaultPriority() -> c_int { - 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osMutexCreate(pHandle: *mut pte_osMutexHandle) -> pte_osResult { - *pHandle = Box::into_raw(Box::new(Mutex::new(()))); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osMutexDelete(handle: pte_osMutexHandle) -> pte_osResult { - Box::from_raw(handle); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osMutexLock(handle: pte_osMutexHandle) -> pte_osResult { - (*handle).manual_lock(); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osMutexUnlock(handle: pte_osMutexHandle) -> pte_osResult { - (*handle).manual_unlock(); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osSemaphoreCreate( - initialValue: c_int, - pHandle: *mut pte_osSemaphoreHandle, -) -> pte_osResult { - *pHandle = Box::into_raw(Box::new(Semaphore::new(initialValue))); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osSemaphoreDelete(handle: pte_osSemaphoreHandle) -> pte_osResult { - Box::from_raw(handle); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osSemaphorePost( - handle: pte_osSemaphoreHandle, - count: c_int, -) -> pte_osResult { - (*handle).post(); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osSemaphorePend( - handle: pte_osSemaphoreHandle, - pTimeout: *mut c_uint, -) -> pte_osResult { - //TODO: pTimeout - (*handle).wait(); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osSemaphoreCancellablePend( - handle: pte_osSemaphoreHandle, - pTimeout: *mut c_uint, -) -> pte_osResult { - //TODO - pte_osSemaphorePend(handle, pTimeout) -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osAtomicExchange(ptarg: *mut c_int, val: c_int) -> c_int { - intrinsics::atomic_xchg(ptarg, val) -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osAtomicCompareExchange( - pdest: *mut c_int, - exchange: c_int, - comp: c_int, -) -> c_int { - intrinsics::atomic_cxchg(pdest, comp, exchange).0 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osAtomicExchangeAdd(pAppend: *mut c_int, value: c_int) -> c_int { - intrinsics::atomic_xadd(pAppend, value) -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osAtomicDecrement(pdest: *mut c_int) -> c_int { - intrinsics::atomic_xadd(pdest, -1) - 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osAtomicIncrement(pdest: *mut c_int) -> c_int { - intrinsics::atomic_xadd(pdest, 1) + 1 -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osTlsSetValue(index: c_uint, value: *mut c_void) -> pte_osResult { - locals().insert(index, value); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osTlsGetValue(index: c_uint) -> *mut c_void { - locals().get_mut(&index).copied().unwrap_or(ptr::null_mut()) -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osTlsAlloc(pKey: *mut c_uint) -> pte_osResult { - *pKey = NEXT_KEY.fetch_add(1, Ordering::SeqCst); - PTE_OS_OK -} - -#[no_mangle] -pub unsafe extern "C" fn pte_osTlsFree(index: c_uint) -> pte_osResult { - // XXX free keys - PTE_OS_OK -} diff --git a/src/platform/redox/epoll.rs b/src/platform/redox/epoll.rs index 587aaca73a..060b645767 100644 --- a/src/platform/redox/epoll.rs +++ b/src/platform/redox/epoll.rs @@ -1,13 +1,13 @@ use super::{ - super::{types::*, Pal, PalEpoll}, + super::{Pal, PalEpoll, types::*}, Sys, }; use crate::{ + error::Errno, fs::File, - header::{errno::*, fcntl::*, signal::sigset_t, sys_epoll::*}, + header::{bits_sigset_t::sigset_t, errno::*, fcntl::*, sys_epoll::*}, io::prelude::*, - platform, }; use core::{mem, slice}; use syscall::{ @@ -15,25 +15,63 @@ use syscall::{ flag::EVENT_READ, }; -impl PalEpoll for Sys { - fn epoll_create1(flags: c_int) -> c_int { - Sys::open(c_str!("event:"), O_RDWR | flags, 0) +fn epoll_to_event_flags(epoll: c_uint) -> syscall::EventFlags { + let mut event_flags = syscall::EventFlags::empty(); + + if epoll & EPOLLIN != 0 { + event_flags |= syscall::EventFlags::EVENT_READ; } - fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int { + if epoll & EPOLLOUT != 0 { + event_flags |= syscall::EventFlags::EVENT_WRITE; + } + + /*TODO: support more EPOLL flags */ + let unsupported = !(EPOLLIN | EPOLLOUT); + if epoll & unsupported != 0 { + log::trace!("epoll unsupported flags 0x{:X}", epoll & unsupported); + } + + event_flags +} + +fn event_flags_to_epoll(flags: syscall::EventFlags) -> c_uint { + let mut epoll = 0; + + if flags.contains(syscall::EventFlags::EVENT_READ) { + epoll |= EPOLLIN; + } + + if flags.contains(syscall::EventFlags::EVENT_WRITE) { + epoll |= EPOLLOUT; + } + + epoll +} + +impl PalEpoll for Sys { + fn epoll_create1(flags: c_int) -> Result { + Sys::open(c"/scheme/event".into(), O_RDWR | flags, 0) + } + + unsafe fn epoll_ctl( + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut epoll_event, + ) -> Result<(), Errno> { match op { EPOLL_CTL_ADD | EPOLL_CTL_MOD => { Sys::write( epfd, &Event { id: fd as usize, - flags: syscall::EventFlags::from_bits(unsafe { (*event).events as usize }) - .expect("epoll: invalid bit pattern"), + flags: unsafe { epoll_to_event_flags((*event).events) }, // NOTE: Danger when using something smaller than 64-bit // systems. If this is needed, use a box or something data: unsafe { (*event).data.u64 as usize }, }, - ) as c_int + )?; } EPOLL_CTL_DEL => { Sys::write( @@ -44,70 +82,77 @@ impl PalEpoll for Sys { //TODO: Is data required? data: 0, }, - ) as c_int - } - _ => { - unsafe { platform::errno = EINVAL }; - return -1; + )?; } + _ => return Err(Errno(EINVAL)), } + Ok(()) } - fn epoll_pwait( + unsafe fn epoll_pwait( epfd: c_int, events: *mut epoll_event, maxevents: c_int, timeout: c_int, - _sigset: *const sigset_t, - ) -> c_int { - // TODO: sigset + sigset: *const sigset_t, + ) -> Result { assert_eq!(mem::size_of::(), mem::size_of::()); + if maxevents <= 0 { + return Err(Errno(EINVAL)); + } + let timer_opt = if timeout != -1 { - match File::open(c_str!("time:4"), O_RDWR) { - Err(_) => return -1, - Ok(mut timer) => { - if Sys::write( - epfd, - &Event { - id: timer.fd as usize, - flags: EVENT_READ, - data: 0, - }, - ) == -1 - { - return -1; - } + let mut timer = File::open(c"/scheme/time/4".into(), O_RDWR)?; + Sys::write( + epfd, + &Event { + id: timer.fd as usize, + flags: EVENT_READ, + data: 0, + }, + )?; - let mut time = TimeSpec::default(); - if let Err(err) = timer.read(&mut time) { - return -1; - } - time.tv_sec += (timeout as i64) / 1000; - time.tv_nsec += (timeout % 1000) * 1000000; - if let Err(err) = timer.write(&time) { - return -1; - } + let mut time = TimeSpec::default(); + let _ = timer + .read(&mut time) + .map_err(|err| Errno(err.raw_os_error().unwrap_or(EIO)))?; + time.tv_sec += (timeout as i64) / 1000; + time.tv_nsec += (timeout % 1000) * 1000000; + let _ = timer + .write(&time) + .map_err(|err| Errno(err.raw_os_error().unwrap_or(EIO)))?; - Some(timer) - } - } + Some(timer) } else { None }; - let bytes_read = Sys::read(epfd, unsafe { - slice::from_raw_parts_mut(events as *mut u8, maxevents as usize) - }); - if bytes_read == -1 { - return -1; - } + let callback = || { + let res = syscall::read(epfd as usize, unsafe { + slice::from_raw_parts_mut( + events as *mut u8, + maxevents as usize * mem::size_of::(), + ) + }); + res + }; + + let bytes_read = if sigset.is_null() { + callback() + } else { + // Allowset is inverse of sigset mask + let allowset = !unsafe { *sigset }; + redox_rt::signal::callback_or_signal_async(allowset, callback) + }?; + let read = bytes_read as usize / mem::size_of::(); let mut count = 0; for i in 0..read { unsafe { let event_ptr = events.add(i); + let target_ptr = events.add(count); let event = *(event_ptr as *mut Event); if let Some(ref timer) = timer_opt { if event.id as c_int == timer.fd { @@ -115,8 +160,8 @@ impl PalEpoll for Sys { continue; } } - *event_ptr = epoll_event { - events: event.flags.bits() as _, + *target_ptr = epoll_event { + events: event_flags_to_epoll(event.flags), data: epoll_data { u64: event.data as u64, }, @@ -126,6 +171,6 @@ impl PalEpoll for Sys { } } - count as c_int + Ok(count) } } diff --git a/src/platform/redox/event.rs b/src/platform/redox/event.rs new file mode 100644 index 0000000000..b02f641ce9 --- /dev/null +++ b/src/platform/redox/event.rs @@ -0,0 +1,82 @@ +use core::mem::size_of; + +use crate::header::{ + bits_sigset_t::sigset_t, + bits_timespec::timespec, + fcntl::{O_CLOEXEC, O_CREAT, O_RDWR}, +}; + +use super::libredox::RawResult; + +use syscall::{EINVAL, Error, Result}; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_event_queue_create_v1(flags: u32) -> RawResult { + Error::mux((|| { + if flags != 0 { + return Err(Error::new(EINVAL)); + } + Ok(super::libredox::open("/scheme/event", O_CLOEXEC | O_CREAT | O_RDWR, 0o700)? as usize) + })()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_event_queue_get_events_v1( + queue: usize, + buf: *mut event::raw::RawEventV1, + buf_count: usize, + flags: u32, + _timeout: *const timespec, + _sigset: *const sigset_t, +) -> RawResult { + Error::mux((|| -> Result { + if flags != 0 || buf_count == 0 { + return Err(Error::new(EINVAL)); + } + let mut event = syscall::Event::default(); + let res = syscall::read(queue, &mut event)?; + assert_eq!( + res, + size_of::(), + "EOF not yet defined for event queue reads" + ); + unsafe { + buf.write(event::raw::RawEventV1 { + fd: event.id, + flags: event::raw::EventFlags::from(event.flags).bits(), + user_data: event.data, + }) + }; + + Ok(1) + })()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_event_queue_ctl_v1( + queue: usize, + fd: usize, + flags: u32, + user_data: usize, +) -> RawResult { + Error::mux((|| -> Result { + let res = syscall::write( + queue, + &syscall::Event { + id: fd, + flags: event::raw::EventFlags::from_bits(flags) + .ok_or(Error::new(EINVAL))? + .into(), + data: user_data, + }, + )?; + assert_eq!( + res, + size_of::(), + "EOF not yet defined for event queue writes" + ); + Ok(0) + })()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_event_queue_destroy_v1(queue: usize) -> RawResult { + Error::mux(syscall::close(queue)) +} diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs new file mode 100644 index 0000000000..1b4b96bb66 --- /dev/null +++ b/src/platform/redox/exec.rs @@ -0,0 +1,618 @@ +use core::{ + convert::Infallible, + num::{NonZeroU64, NonZeroUsize}, +}; + +use crate::{ + c_str::{CStr, CString}, + fs::File, + header::{limits::PATH_MAX, string::strlen}, + io::{BufReader, SeekFrom, prelude::*}, + platform::types::*, +}; + +use redox_rt::{ + RtTcb, + proc::{ExtraInfo, FdGuard, FdGuardUpper, FexecResult, InterpOverride}, + sys::Resugid, +}; +use syscall::{data::Stat, error::*, flag::*}; + +fn fexec_impl( + exec_file: FdGuardUpper, + path: &[u8], + args: &[&[u8]], + envs: &[&[u8]], + extrainfo: &ExtraInfo, + interp_override: Option, +) -> Result { + let FexecResult::Interp { + path, + interp_override: new_interp_override, + } = redox_rt::proc::fexec_impl( + exec_file, + &RtTcb::current().thread_fd(), + redox_rt::current_proc_fd(), + path, + args, + envs, + extrainfo, + interp_override, + )?; + + // According to elf(5), PT_INTERP requires that the interpreter path be + // null-terminated. Violating this should therefore give the "format error" ENOEXEC. + let path_cstr = CStr::from_bytes_with_nul(&path).map_err(|_| Error::new(ENOEXEC))?; + + return execve( + Executable::AtPath(path_cstr), + ArgEnv::Parsed { args, envs }, + Some(new_interp_override), + ); +} + +pub enum ArgEnv<'a> { + C { + argv: *const *mut c_char, + envp: *const *mut c_char, + }, + Parsed { + args: &'a [&'a [u8]], + envs: &'a [&'a [u8]], + }, +} + +pub enum Executable<'a> { + AtPath(CStr<'a>), + InFd { file: File, arg0: &'a [u8] }, +} + +pub fn execve( + exec: Executable<'_>, + arg_env: ArgEnv, + interp_override: Option, +) -> Result { + // NOTE: We must omit O_CLOEXEC and close manually, otherwise it will be closed before we + // have even read it! + let (mut image_file, stat, arg0) = match exec { + Executable::AtPath(path) => { + let Ok(src_fd) = File::open(path, O_RDONLY as c_int) else { + return Err(Error::new(ENOENT)); + }; + + let mut src_stat = Stat::default(); + redox_rt::sys::fstat(*src_fd as usize, &mut src_stat)?; + + #[cfg(feature = "ld_so_cache")] + let src_fd = { + let mtime_sec = src_stat.st_mtime; + let mtime_nsec = src_stat.st_mtime_nsec; + + let safe_path = path.to_str().unwrap_or("").replace('/', "_"); + let shm_path_owned = format!( + "/scheme/shm/ld.so.cache.{}.{}.{}\0", + safe_path, mtime_sec, mtime_nsec + ); + let shm_path = + unsafe { CStr::from_bytes_with_nul_unchecked(shm_path_owned.as_bytes()) }; + + let mut file_opt = None; + if let Ok(shm_fd) = File::open(shm_path, O_RDONLY as c_int) { + if let Ok(shm_stat) = shm_fd.fstat() + && shm_stat.st_size > 0 + { + file_opt = Some(shm_fd); + } + } + file_opt.unwrap_or(src_fd) + }; + + (src_fd, src_stat, path.to_bytes()) + } + Executable::InFd { file, arg0 } => { + let mut stat = Stat::default(); + redox_rt::sys::fstat(*file as usize, &mut stat)?; + (file, stat, arg0) + } + }; + + // With execve now being implemented in userspace, we need to check ourselves that this + // file is actually executable. While checking for read permission is unnecessary as the + // scheme will not allow us to read otherwise, the execute bit is completely unenforced. + // + // But we do (currently) have the permission to mmap executable memory and fill it with any + // program, even marked non-executable, so really the best we can do is check that nothing is + // executed by accident. + // + // TODO: At some point we might have capabilities limiting the ability to allocate + // executable memory. + + let Resugid { ruid, euid, rgid, .. } = redox_rt::sys::posix_getresugid(); + + // Root (uid 0) bypasses execute permission checks, matching Linux behavior. + // Check both ruid and euid since Linux checks the effective UID. + if ruid != 0 && euid != 0 { + let mode = if ruid == stat.st_uid { + (stat.st_mode >> 3 * 2) & 0o7 + } else if rgid == stat.st_gid { + (stat.st_mode >> 3 * 1) & 0o7 + } else { + stat.st_mode & 0o7 + }; + + if mode & 0o1 == 0o0 { + return Err(Error::new(EACCES)); + } + } + + let cwd: Box<[u8]> = super::path::clone_cwd().unwrap_or_default().into(); + + // Path to interpreter binary and args if found + let (interpreter_path, interpreter_args) = { parse_interpreter(&mut image_file)? }; + + // Total number of arguments which includes the interpreter if interpreted and its args + let mut len = 0; + if interpreter_path.is_some() { + len = 1; + if interpreter_args.is_some() { + len = 2; + } + } + + // Count arguments for `exec` which is different from the interpreter's args + // + // When there's an interpreter, we skip the original `argv[0]` and replace it with the script + // path (`arg0`). + match arg_env { + ArgEnv::C { argv, .. } => unsafe { + let mut count = 0; + let ptr = if interpreter_path.is_some() && !(*argv).is_null() { + argv.add(1) + } else { + argv + }; + + while !(*ptr.add(count)).is_null() { + count += 1; + } + len += count; + }, + ArgEnv::Parsed { args, .. } => { + let skip = if interpreter_path.is_some() { 1 } else { 0 }; + len += args.len().saturating_sub(skip); + } + } + let mut args: Vec<&[u8]> = Vec::with_capacity(len); + + if let Some(interpreter) = &interpreter_path { + image_file = File::open(CStr::borrow(&interpreter), O_RDONLY as c_int) + .map_err(|_| Error::new(ENOENT))?; + + // Push interpreter to arguments + args.push(interpreter.as_bytes()); + + // Push interpreter args, if any, to our main arguments + if let Some(args_ref) = interpreter_args.as_ref() { + args.push(args_ref.as_bytes()); + } + } else { + image_file + .seek(SeekFrom::Start(0)) + .map_err(|_| Error::new(EIO))?; + } + + let (args, envs): (Vec<_>, Vec<_>) = match arg_env { + ArgEnv::C { mut argv, mut envp } => unsafe { + // Arguments + if interpreter_path.is_some() { + args.push(arg0); + if !(*argv).is_null() { + argv = argv.add(1); + } + } + + while !argv.read().is_null() { + let arg = argv.read(); + + let len = strlen(arg); + args.push(core::slice::from_raw_parts(arg as *const u8, len)); + argv = argv.add(1); + } + + // Environment variables + let mut len = 0; + while !envp.add(len).read().is_null() { + len += 1; + } + + let mut envs: Vec<&[u8]> = Vec::with_capacity(len); + while !envp.read().is_null() { + let env = envp.read(); + + let len = strlen(env); + envs.push(core::slice::from_raw_parts(env as *const u8, len)); + envp = envp.add(1); + } + (args, envs) + }, + ArgEnv::Parsed { + args: new_args, + envs, + } => { + if interpreter_path.is_some() { + args.push(arg0); + args.extend(new_args.iter().skip(1)); + } else { + args.extend(new_args); + } + (args, Vec::from(envs)) + } + }; + + // TODO: Convert image_file to FdGuard earlier? + let exec_fd_guard = FdGuard::new(image_file.fd as usize).to_upper().unwrap(); + core::mem::forget(image_file); + + let sigprocmask = redox_rt::signal::get_sigmask().unwrap(); + + let extrainfo = ExtraInfo { + cwd: Some(&cwd), + sigignmask: redox_rt::signal::get_sigignmask_to_inherit(), + sigprocmask, + umask: redox_rt::sys::get_umask(), + thr_fd: RtTcb::current().thread_fd().as_raw_fd(), + proc_fd: redox_rt::current_proc_fd().as_raw_fd(), + ns_fd: redox_rt::current_namespace_fd().ok(), + cwd_fd: super::path::current_dir() + .ok() + .map(|fd| fd.as_ref().unwrap().fd.as_raw_fd()), + }; + + fexec_impl( + exec_fd_guard, + arg0, + &args, + &envs, + &extrainfo, + interp_override, + ) +} + +// Parse the interpreter and its args if `reader` starts with a shebang (#!). +// +// # Return +// * Path to the interpreter and its args, if any +// * `None` if no shebang +// * An error if parsing failed +// +// # Errors +// * E2BIG: The full path of the shebang is greater than [`PATH_MAX`] +// * ENOEXEC: Invalid shebang line, such as a line of all whitespace +// * EIO: Failure reading from `reader` +fn parse_interpreter(image_file: &mut R) -> Result<(Option, Option)> +where + R: Read + Seek, +{ + // Read shebang (for example #!/bin/sh) + let mut read = 0; + let mut shebang = [0; 2]; + + while read < 2 { + match image_file + .read(&mut shebang) + .map_err(|_| Error::new(ENOEXEC))? + { + 0 => break, + i => read += i, + } + } + if shebang != *b"#!" { + return Ok((None, None)); + } + + // BufReader is created after parsing the shebang because it doesn't make sense to buffer + // bytes to read two bytes especially if `image_file` is NOT a script. + let mut reader_ = BufReader::new(image_file); + let reader = &mut reader_; + + // Skip prepended whitespace for interpreter + // Ex: #! /usr/bin/python + let pos = reader + .bytes() + .position(|byte| byte.ok().is_some_and(|byte| !byte.is_ascii_whitespace())) + .and_then(|pos| (pos + 2).try_into().ok()) + // Fail if all whitespace or empty + .ok_or_else(|| Error::new(ENOEXEC))?; + // We read the non-whitespace character which sets reader position one past it. + // Seeking back to that position is essentially free since reads are buffered and it's + // unlikely that there was enough whitespace that we performed multiple reads. + reader + .seek(SeekFrom::Start(pos)) + .map_err(|_| Error::new(EIO))?; + + // Scan the first line once for the mandatory interpreter and optional args. + // This is nicer than using `read_until` or `read_line` because it avoids having to scan the + // data twice to check if there are args. + let mut interp_offset = None; + let mut args_offset = None; + for (i, byte) in reader.bytes().enumerate() { + let byte = byte.map_err(|_| Error::new(EIO))?; + + match (byte, interp_offset, args_offset) { + // No args; only interpreter + (b'\n', None, None) => { + interp_offset = NonZeroUsize::new(i); + break; + } + // Interpreter found, so we're scanning for where the args ends + (b'\n', Some(_), None) => { + args_offset = NonZeroUsize::new(i); + break; + } + // Found args so interpreter ends at `i` + (b' ', None, None) => { + interp_offset = NonZeroUsize::new(i); + } + _ => {} + } + } + + // Interpreter is mandatory since we found #! earlier + let Some(interp_offset) = interp_offset.map(NonZeroUsize::get) else { + return Err(Error::new(ENOEXEC)); + }; + // We need u64s and usizes; converting them now is easier + let Ok(interp_offset_u64) = interp_offset.try_into() else { + return Err(Error::new(E2BIG)); + }; + let args_offset_u64: Option = args_offset + .map(|offset| offset.try_into()) + .transpose() + .map_err(|_| Error::new(E2BIG))?; + + // Spec: full length of the shebang can't exceed max path length + let shebang_len = pos + .checked_add(interp_offset_u64) + .and_then(|len| len.checked_add(args_offset_u64.map(NonZeroU64::get).unwrap_or_default())) + .ok_or_else(|| Error::new(E2BIG))?; + // PATH_MAX is a small number that fits into u64 so `as` is okay + if shebang_len > PATH_MAX as u64 { + return Err(Error::new(E2BIG)); + } + + // Rewind to the beginning of the interpreter. + // As above, this is essentially free because the internal buf size is several times larger + // than PATH_MAX by default, and our shebang_len < PATH_MAX as checked above. + reader + .seek(SeekFrom::Start(pos)) + .map_err(|_| Error::new(E2BIG))?; + + let mut interpreter = Vec::with_capacity(interp_offset); + reader + .take(interp_offset_u64) + .read_to_end(&mut interpreter) + .map_err(|_| Error::new(EIO))?; + + // Read args, but treat as an opaque block to pass to the interpreter. + // Linux and FreeBSD both pass the args as is to the interpreter whereas macOS splits + // the args similar to `/usr/bin/env -S`. + // POSIX leaves the behavior up to the implementation. + // It's simpler to rely on env because well behaved, portable scripts will use + // it to ensure correct operation on Linux/FreeBSD. + // Splitting args ourselves gains little while reinventing env -S + let interpreter_args = if let (Some(offset), Some(offset_u64)) = ( + args_offset.map(NonZeroUsize::get), + args_offset_u64.map(NonZeroU64::get), + ) { + let len = offset - interp_offset; + let len_u64 = offset_u64 - interp_offset_u64; + let mut args = Vec::with_capacity(len); + + reader + .take(len_u64) + .read_to_end(&mut args) + .map_err(|_| Error::new(E2BIG))?; + + // Eat '\n' + reader.consume(1); + + let mut arg_start = 0; + while arg_start < args.len() && (args[arg_start] == b' ' || args[arg_start] == b'\t') { + arg_start += 1; + } + + if arg_start < args.len() { + let args = CString::new(&args[arg_start..]).map_err(|_| Error::new(ENOEXEC))?; + Some(args) + } else { + None + } + } else { + None + }; + + let interpreter = CString::new(interpreter).map_err(|_| Error::new(ENOEXEC))?; + Ok((Some(interpreter), interpreter_args)) +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::parse_interpreter; + + // Shebangs without a script attached + const NO_FRILLS: &str = "#!/bin/sh\n"; + const NO_FRILLS_EXPECTED: &str = "/bin/sh"; + + const SPACE_B4_INTERP: &str = "#! /bin/sh\n"; + const SPACE_B4_INTERP_EXPECTED: &str = "/bin/sh"; + + const NO_FRILLS_ENV: &str = "#!/usr/bin/env sh\n"; + const NO_FRILLS_ENV_EXPECTED: &str = "/usr/bin/env"; + const NO_FRILLS_ENV_EXPECTED_ARGS: &str = "sh"; + + const SPACE_B4_ENV: &str = "#! /usr/bin/env sh\n"; + const SPACE_B4_EXPECTED: &str = NO_FRILLS_ENV_EXPECTED; + const SPACE_B4_EXPECTED_ARGS: &str = NO_FRILLS_ENV_EXPECTED_ARGS; + + const MULT_SPACES_B4: &str = "#! /usr/bin/env sh\n"; + const MULT_SPACES_B4_EXPECTED: &str = NO_FRILLS_ENV_EXPECTED; + const MULT_SPACES_B4_EXPECTED_ARGS: &str = NO_FRILLS_ENV_EXPECTED_ARGS; + + // Shebangs with a script attached + // These test that the parser doesn't run off the first line + const NO_FRILLS_W_SCRIPT: &str = r#"#!/bin/sh + echo "Hello from Redox""#; + const NO_FRILLS_W_SCRIPT_EXPECTED: &str = NO_FRILLS_EXPECTED; + + const SPACE_B4_INTERP_W_SCRIPT: &str = r#"#! /bin/sh + echo "Doctor Eigenvalue""#; + const SPACE_B4_INTERP_W_SCRIPT_EXPECTED: &str = NO_FRILLS_EXPECTED; + + const MULT_ARGUMENTS: &str = r#"#! /usr/bin/env -S python -OO + assert False + print("This totally works") + "#; + const MULT_ARGUMENTS_EXPECTED: &str = NO_FRILLS_ENV_EXPECTED; + const MULT_ARGUMENTS_EXPECTED_ARGS: &str = "-S python -OO"; + + // No hashbang conditions + const NO_SHEBANG: &str = "/bin/sh"; + const EMPTY: &str = ""; + + // Error conditions + const SHEBANG_NO_INTERP: &str = "#!"; + const SHEBANG_NO_INTERP_SPACE: &str = "#! "; + const SHEBANG_NO_INTERP_SCRIPT: &str = "#!\necho ${PATH}"; + + fn success(input: &str, expected_interp: &str, expected_args: Option<&str>) { + let mut reader = Cursor::new(input); + let (actual_interp, actual_args) = parse_interpreter(&mut reader) + .unwrap_or_else(|e| panic!("Shebang ({input}) should parse\n\t{e}")); + + let actual_interp = actual_interp + .expect("Expected an interpreter") + .into_string() + .expect("Interpreter is ASCII (valid UTF-8)"); + assert_eq!(expected_interp, actual_interp); + + if let Some(expected_args) = expected_args { + let actual_args = actual_args + .expect("Expected arguments to interpreter") + .into_string() + .expect("Args string is ASCII (valid UTF-8)"); + assert_eq!(expected_args, actual_args); + } + } + + #[test] + fn parse_interpreter_without_space() { + success(NO_FRILLS, NO_FRILLS_EXPECTED, None); + } + + #[test] + fn parse_interpreter_with_space() { + success(SPACE_B4_INTERP, SPACE_B4_INTERP_EXPECTED, None); + } + + #[test] + fn parse_interpreter_with_arg() { + success( + NO_FRILLS_ENV, + NO_FRILLS_ENV_EXPECTED, + Some(NO_FRILLS_ENV_EXPECTED_ARGS), + ); + } + + #[test] + fn parse_interpreter_with_arg_and_space() { + success( + SPACE_B4_ENV, + SPACE_B4_EXPECTED, + Some(SPACE_B4_EXPECTED_ARGS), + ); + } + + #[test] + fn parse_interpreter_with_multiple_spaces() { + success( + MULT_SPACES_B4, + MULT_SPACES_B4_EXPECTED, + Some(MULT_SPACES_B4_EXPECTED_ARGS), + ); + } + + #[test] + fn parse_interpreter_with_script() { + success(NO_FRILLS_W_SCRIPT, NO_FRILLS_W_SCRIPT_EXPECTED, None); + } + + #[test] + fn parse_interpreter_with_script_and_space() { + success( + SPACE_B4_INTERP_W_SCRIPT, + SPACE_B4_INTERP_W_SCRIPT_EXPECTED, + None, + ); + } + + #[test] + fn parse_interpreter_with_script_args_space() { + success( + MULT_ARGUMENTS, + MULT_ARGUMENTS_EXPECTED, + Some(MULT_ARGUMENTS_EXPECTED_ARGS), + ); + } + + #[test] + fn parse_interpreter_no_shebang() { + let mut reader = Cursor::new(NO_SHEBANG); + let (interpreter, args) = + parse_interpreter(&mut reader).expect("Shouldn't fail if file doesn't have a shebang"); + + assert!( + interpreter.is_none(), + "Interpreter should be `None` if shebang isn't present" + ); + assert!( + args.is_none(), + "Args should be empty without an interpreter." + ); + } + + #[test] + fn parse_interpreter_empty() { + let mut reader = Cursor::new(EMPTY); + let (interpreter, args) = + parse_interpreter(&mut reader).expect("Shouldn't fail if file doesn't have a shebang"); + + assert!( + interpreter.is_none(), + "Interpreter should be `None` for empty image" + ); + assert!(args.is_none(), "Args should be empty for empty image"); + } + + #[test] + fn parse_interpreter_no_interpreter_fail() { + let mut reader = Cursor::new(SHEBANG_NO_INTERP); + parse_interpreter(&mut reader) + .expect_err("A hashbang without an interpreter should return an error"); + } + + #[test] + fn parse_interpreter_no_interpreter_space_fail() { + let mut reader = Cursor::new(SHEBANG_NO_INTERP_SPACE); + parse_interpreter(&mut reader) + .expect_err("A hashbang without an interpreter should return an error"); + } + + #[test] + fn parse_interpreter_no_interpreter_script_fail() { + let mut reader = Cursor::new(SHEBANG_NO_INTERP_SCRIPT); + parse_interpreter(&mut reader) + .expect_err("A hashbang without an interpreter should return an error"); + } +} diff --git a/src/platform/redox/extra.rs b/src/platform/redox/extra.rs index 19138aa394..1e3138ed44 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -1,49 +1,38 @@ -use core::{ptr, slice}; +use core::slice; -use crate::platform::{sys::e, types::*}; +use crate::{ + error::{Errno, ResultExt}, + platform::types::*, +}; +use syscall::{F_SETFD, F_SETFL, O_RDONLY, O_WRONLY, error::*}; -#[no_mangle] +pub use redox_rt::proc::FdGuard; + +#[unsafe(no_mangle)] pub unsafe extern "C" fn redox_fpath(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t { - e(syscall::fpath( - fd as usize, - slice::from_raw_parts_mut(buf as *mut u8, count), - )) as ssize_t + syscall::fpath(fd as usize, unsafe { + slice::from_raw_parts_mut(buf as *mut u8, count) + }) + .map_err(Errno::from) + .map(|l| l as ssize_t) + .or_minus_one_errno() } -#[no_mangle] -pub unsafe extern "C" fn redox_physalloc(size: size_t) -> *mut c_void { - let res = e(syscall::physalloc(size)); - if res == !0 { - return ptr::null_mut(); - } else { - return res as *mut c_void; - } -} +pub fn pipe2(flags: usize) -> syscall::error::Result<[c_int; 2]> { + let read_flags = flags | O_RDONLY; + let write_flags = flags | O_WRONLY; + let read_fd = FdGuard::open("/scheme/pipe", read_flags)?; + let write_fd = read_fd.dup(b"write")?; + write_fd.fcntl(F_SETFL, write_flags)?; + write_fd.fcntl(F_SETFD, write_flags)?; -#[no_mangle] -pub unsafe extern "C" fn redox_physfree(physical_address: *mut c_void, size: size_t) -> c_int { - e(syscall::physfree(physical_address as usize, size)) as c_int -} + let fds = [ + c_int::try_from(read_fd.as_raw_fd()).map_err(|_| Error::new(EMFILE))?, + c_int::try_from(write_fd.as_raw_fd()).map_err(|_| Error::new(EMFILE))?, + ]; -#[no_mangle] -pub unsafe extern "C" fn redox_physmap( - physical_address: *mut c_void, - size: size_t, - flags: c_int, -) -> *mut c_void { - let res = e(syscall::physmap( - physical_address as usize, - size, - syscall::PhysmapFlags::from_bits(flags as usize).expect("physmap: invalid bit pattern"), - )); - if res == !0 { - return ptr::null_mut(); - } else { - return res as *mut c_void; - } -} + read_fd.take(); + write_fd.take(); -#[no_mangle] -pub unsafe extern "C" fn redox_physunmap(virtual_address: *mut c_void) -> c_int { - e(syscall::physunmap(virtual_address as usize)) as c_int + Ok(fds) } diff --git a/src/platform/redox/libcscheme.rs b/src/platform/redox/libcscheme.rs new file mode 100644 index 0000000000..a089606055 --- /dev/null +++ b/src/platform/redox/libcscheme.rs @@ -0,0 +1,52 @@ +use crate::{c_str::CStr, header::stdlib::getenv, platform::types::*}; +use core::ptr; +use syscall::{EIO, ENOENT, Error, Result, flag::*}; + +pub const LIBC_SCHEME: &'static str = "libc:"; + +const ENV_MAX_LEN: i32 = i32::MAX; + +macro_rules! env_str { + ($lit:expr) => { + #[allow(unused_unsafe)] + { + let val_bytes = unsafe { getenv(concat!($lit, "\0").as_ptr() as *const c_char) }; + if val_bytes != ptr::null_mut() { + if let Ok(val_str) = unsafe { CStr::from_ptr(val_bytes) }.to_str() { + Some(val_str) + } else { + None + } + } else { + None + } + } + }; +} + +pub fn open(path: &str, flags: usize) -> Result { + assert!(path.starts_with(LIBC_SCHEME)); + + if flags & O_SYMLINK != 0 { + return Err(Error::new(ENOENT)); + } + + let basename = match path.strip_prefix(LIBC_SCHEME) { + Some(path) => path.trim_matches('/'), + _ => return Err(Error::new(EIO)), + }; + + // Linux seems to allow you to read from or write to any of /dev/{stdin,stderr,stdout} + match basename { + "stderr" => syscall::dup(2, &[]), + "stdin" => syscall::dup(0, &[]), + "stdout" => syscall::dup(1, &[]), + "tty" => { + if let Some(tty) = env_str!("TTY") { + return redox_rt::sys::open(tty, flags); + } + Err(Error::new(ENOENT)) + } + _ => Err(Error::new(ENOENT)), + } +} diff --git a/src/platform/redox/libredox.rs b/src/platform/redox/libredox.rs new file mode 100644 index 0000000000..04c76dede8 --- /dev/null +++ b/src/platform/redox/libredox.rs @@ -0,0 +1,639 @@ +use core::{mem, slice, str}; + +use alloc::vec::Vec; +use ioslice::IoSlice; +use redox_protocols::protocol::{ProcKillTarget, SocketCall, WaitFlags}; +use redox_rt::sys::{WaitpidTarget, posix_read, posix_write, std_fs_call_ro, std_fs_call_wo}; +use syscall::{ + EMFILE, ENAMETOOLONG, ENOSYS, EOPNOTSUPP, Error, Result, StdFsCallKind, + data::StdFsCallMeta, + dirent::{DirentHeader, DirentKind}, +}; + +use crate::{ + header::{ + bits_iovec::iovec, bits_timespec::timespec, errno::EINVAL, signal::sigaction, + sys_stat::UTIME_NOW, + }, + out::Out, + platform::{PalSignal, pal::Pal, types::*}, +}; + +use super::Sys; + +pub type RawResult = usize; + +pub fn open(path: &str, oflag: c_int, mode: mode_t) -> Result { + let usize_fd = super::path::open( + path, + ((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF), + )?; + + c_int::try_from(usize_fd) + .map_err(|_| { + let _ = syscall::close(usize_fd); + Error::new(EMFILE) + }) + .map(|f| f as usize) +} + +pub fn openat(dirfd: c_int, path: &str, oflag: c_int, mode: mode_t) -> Result { + let usize_fd = super::path::openat( + dirfd, + path, + ((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF), + )?; + + c_int::try_from(usize_fd) + .map_err(|_| { + let _ = syscall::close(usize_fd); + Error::new(EMFILE) + }) + .map(|f| f as usize) +} +pub fn fchmod(fd: usize, new_mode: u16) -> Result<()> { + std_fs_call_wo( + fd, + &[], + &StdFsCallMeta::new(StdFsCallKind::Fchmod, new_mode as u64, 0), + )?; + Ok(()) +} +pub fn fchown(fd: usize, new_uid: u32, new_gid: u32) -> Result<()> { + /* std_fs_call + Error::mux(std_fs_call_wo( + fd, + &[], + &StdFsCallMeta::new( + StdFsCallKind::Fchmod, + (new_uid as u64) | ((new_gid as u64) << 32), + 0, + ), + )) + */ + syscall::fchown(fd, new_uid, new_gid)?; + Ok(()) +} +pub fn getdents(fd: usize, buf: &mut [u8], opaque: u64) -> Result { + //println!("GETDENTS {} into ({:p}+{})", fd, buf.as_ptr(), buf.len()); + + const HEADER_SIZE: usize = mem::size_of::(); + + // Use syscall if it exists. + match std_fs_call_ro( + fd, + buf, + &StdFsCallMeta::new(StdFsCallKind::Getdents, opaque, HEADER_SIZE as u64), + ) { + Err(Error { + errno: EOPNOTSUPP | ENOSYS, + }) => (), + other => { + //println!("REAL GETDENTS {:?}", other); + return Ok(other?); + } + } + + // Otherwise, for legacy schemes, assume the buffer is pre-arranged (all schemes do this in + // practice), and just read the name. If multiple names appear, pretend it didn't happen + // and just use the first entry. + + let (header, name) = buf.split_at_mut(mem::size_of::()); + + let bytes_read = Sys::pread(fd as c_int, name, opaque as i64)? as usize; + if bytes_read == 0 { + return Ok(0); + } + + let (name_len, advance) = match name[..bytes_read].iter().position(|c| *c == b'\n') { + Some(idx) => (idx, idx + 1), + + // Insufficient space for NUL byte, or entire entry was not read. Indicate we need a + // larger buffer. + None if bytes_read == name.len() => return Err(Error::new(EINVAL)), + + None => (bytes_read, name.len()), + }; + name[name_len] = b'\0'; + + let record_len = u16::try_from(mem::size_of::() + name_len + 1) + .map_err(|_| Error::new(ENAMETOOLONG))?; + header.copy_from_slice(&DirentHeader { + inode: 0, + next_opaque_id: opaque + advance as u64, + record_len, + kind: DirentKind::Unspecified as u8, + }); + //println!("EMULATED GETDENTS"); + + Ok(record_len.into()) +} +pub unsafe fn fstat(fd: usize, buf: *mut crate::header::sys_stat::stat) -> Result<()> { + let mut redox_buf: syscall::Stat = Default::default(); + redox_rt::sys::fstat(fd, &mut redox_buf)?; + + if let Some(buf) = unsafe { buf.as_mut() } { + buf.st_dev = redox_buf.st_dev as dev_t; + buf.st_ino = redox_buf.st_ino as ino_t; + buf.st_nlink = redox_buf.st_nlink as nlink_t; + buf.st_mode = redox_buf.st_mode as mode_t; + buf.st_uid = redox_buf.st_uid as uid_t; + buf.st_gid = redox_buf.st_gid as gid_t; + // TODO st_rdev + buf.st_rdev = 0; + buf.st_size = redox_buf.st_size as off_t; + buf.st_blksize = redox_buf.st_blksize as blksize_t; + buf.st_blocks = redox_buf.st_blocks as blkcnt_t; + buf.st_atim = timespec { + tv_sec: redox_buf.st_atime as time_t, + tv_nsec: redox_buf.st_atime_nsec as c_long, + }; + buf.st_mtim = timespec { + tv_sec: redox_buf.st_mtime as time_t, + tv_nsec: redox_buf.st_mtime_nsec as c_long, + }; + buf.st_ctim = timespec { + tv_sec: redox_buf.st_ctime as time_t, + tv_nsec: redox_buf.st_ctime_nsec as c_long, + }; + } + Ok(()) +} +pub unsafe fn fstatvfs(fd: usize, buf: *mut crate::header::sys_statvfs::statvfs) -> Result<()> { + let mut kbuf: syscall::StatVfs = Default::default(); + std_fs_call_ro( + fd, + &mut kbuf, + &StdFsCallMeta::new(StdFsCallKind::Fstatvfs, 0, 0), + )?; + + if !buf.is_null() { + unsafe { + (*buf).f_bsize = kbuf.f_bsize as c_ulong; + (*buf).f_frsize = kbuf.f_bsize as c_ulong; + (*buf).f_blocks = kbuf.f_blocks as c_ulong; + (*buf).f_bfree = kbuf.f_bfree as c_ulong; + (*buf).f_bavail = kbuf.f_bavail as c_ulong; + //TODO + (*buf).f_files = 0; + (*buf).f_ffree = 0; + (*buf).f_favail = 0; + (*buf).f_fsid = 0; + (*buf).f_flag = 0; + (*buf).f_namemax = 0; + } + } + Ok(()) +} +pub fn fsync(fd: usize) -> Result<()> { + std_fs_call_wo(fd, &[], &StdFsCallMeta::new(StdFsCallKind::Fsync, 0, 0))?; + Ok(()) +} +pub fn ftruncate(fd: usize, len: usize) -> Result<()> { + std_fs_call_wo( + fd, + &[], + &StdFsCallMeta::new(StdFsCallKind::Ftruncate, len as u64, 0), + )?; + Ok(()) +} +pub unsafe fn futimens(fd: usize, times: *const timespec) -> Result<()> { + let times = if times.is_null() { + // null means set to current time using special UTIME_NOW value (tv_sec is ignored in that case) + [ + syscall::TimeSpec { + tv_sec: 0, + tv_nsec: UTIME_NOW as c_int, + }, + syscall::TimeSpec { + tv_sec: 0, + tv_nsec: UTIME_NOW as c_int, + }, + ] + } else { + unsafe { times.cast::<[timespec; 2]>().read() }.map(|ts| syscall::TimeSpec::from(&ts)) + }; + let redox_buf = unsafe { + slice::from_raw_parts( + times.as_ptr() as *const u8, + times.len() * mem::size_of::(), + ) + }; + std_fs_call_wo( + fd, + redox_buf, + &StdFsCallMeta::new(StdFsCallKind::Futimens, 0, 0), + )?; + Ok(()) +} +pub fn clock_gettime(clock: usize, mut tp: Out) -> Result<()> { + let mut redox_tp = syscall::TimeSpec::default(); + syscall::clock_gettime(clock as usize, &mut redox_tp)?; + tp.write(timespec { + tv_sec: redox_tp.tv_sec as time_t, + tv_nsec: redox_tp.tv_nsec as c_long, + }); + Ok(()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_open_v1( + path_base: *const u8, + path_len: usize, + flags: u32, + mode: u16, +) -> RawResult { + Error::mux(open( + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }, + flags as c_int, + mode as mode_t, + )) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_openat_v1( + fd: usize, + path_base: *const u8, + path_len: usize, + flags: u32, + fcntl_flags: u32, +) -> RawResult { + Error::mux(syscall::openat( + fd, + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(path_base, path_len)) }, + flags as usize, + fcntl_flags as usize, + )) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> RawResult { + Error::mux(syscall::dup(fd, unsafe { + core::slice::from_raw_parts(buf, len) + })) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_dup2_v1( + old_fd: usize, + new_fd: usize, + buf: *const u8, + len: usize, +) -> RawResult { + Error::mux(syscall::dup2(old_fd, new_fd, unsafe { + core::slice::from_raw_parts(buf, len) + })) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_read_v1(fd: usize, dst_base: *mut u8, dst_len: usize) -> RawResult { + Error::mux(posix_read(fd, unsafe { + slice::from_raw_parts_mut(dst_base, dst_len) + })) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_write_v1( + fd: usize, + src_base: *const u8, + src_len: usize, +) -> RawResult { + Error::mux(posix_write(fd, unsafe { + slice::from_raw_parts(src_base, src_len) + })) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fchmod_v1(fd: usize, new_mode: u16) -> RawResult { + Error::mux(fchmod(fd, new_mode).map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fchown_v1(fd: usize, new_uid: u32, new_gid: u32) -> RawResult { + Error::mux(fchown(fd, new_uid, new_gid).map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_getdents_v0( + fd: usize, + buf: *mut u8, + buf_len: usize, + opaque: u64, +) -> RawResult { + Error::mux( + Sys::getdents( + fd as c_int, + unsafe { slice::from_raw_parts_mut(buf, buf_len) }, + opaque, + ) + .map_err(Into::into), + ) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fstat_v1( + fd: usize, + stat: *mut crate::header::sys_stat::stat, +) -> RawResult { + Error::mux(unsafe { fstat(fd, stat) }.map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fstatvfs_v1( + fd: usize, + stat: *mut crate::header::sys_statvfs::statvfs, +) -> RawResult { + Error::mux(unsafe { fstatvfs(fd, stat) }.map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fsync_v1(fd: usize) -> RawResult { + Error::mux(fsync(fd).map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fdatasync_v1(fd: usize) -> RawResult { + // TODO + Error::mux(fsync(fd).map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_ftruncate_v0(fd: usize, len: usize) -> RawResult { + Error::mux(ftruncate(fd, len).map(|()| 0)) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_futimens_v1(fd: usize, times: *const timespec) -> RawResult { + Error::mux(unsafe { futimens(fd, times) }.map(|()| 0)) +} +/* TODO: Support unlinkat +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_unlinkat_v0( + fd: usize, + path_base: *const u8, + path_len: usize, + flags: u32, +) -> RawResult { + Error::mux(std_fs_call_wo( + fd, + unsafe { slice::from_raw_parts(path_base, path_len) }, + &StdFsCallMeta::new(StdFsCallKind::Unlinkat, flags as u64, 0), + )) +} +*/ +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fpath_v1(fd: usize, dst_base: *mut u8, dst_len: usize) -> RawResult { + Error::mux(syscall::fpath(fd, unsafe { + core::slice::from_raw_parts_mut(dst_base, dst_len) + })) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_close_v1(fd: usize) -> RawResult { + Error::mux(syscall::close(fd)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_fcntl_v0(fd: usize, cmd: usize, arg: usize) -> RawResult { + Error::mux(redox_rt::sys::fcntl(fd, cmd, arg)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_pid_v1() -> RawResult { + redox_rt::sys::posix_getpid() as _ +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_euid_v1() -> RawResult { + redox_rt::sys::posix_getresugid().euid as _ +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_ruid_v1() -> RawResult { + redox_rt::sys::posix_getresugid().ruid as _ +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_egid_v1() -> RawResult { + redox_rt::sys::posix_getresugid().egid as _ +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_rgid_v1() -> RawResult { + redox_rt::sys::posix_getresugid().rgid as _ +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_ens_v0() -> RawResult { + Error::mux(redox_rt::sys::getens()) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_ns_v0() -> RawResult { + Error::mux(redox_rt::sys::getns()) +} +#[allow(improper_ctypes_definitions)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_proc_credentials_v1( + cap_fd: usize, + target_pid: usize, + buf: &mut [u8], // not FFI safe +) -> RawResult { + Error::mux(redox_rt::sys::get_proc_credentials(cap_fd, target_pid, buf)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_setrens_v1(rns: usize, ens: usize) -> RawResult { + let _ = if ens == 0 { + let null_namespace: [IoSlice; 2] = [IoSlice::new(b"memory"), IoSlice::new(b"pipe")]; + match redox_rt::sys::mkns(&null_namespace) { + Ok(new_ns_fd) => redox_rt::sys::setns(new_ns_fd.take()), + Err(e) => return Error::mux(Err(e)), + } + } else { + redox_rt::sys::setns(ens) + }; + 0 +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_waitpid_v1(pid: usize, status: *mut i32, options: u32) -> RawResult { + let mut sts = 0_usize; + let res = Error::mux(redox_rt::sys::sys_waitpid( + WaitpidTarget::from_posix_arg(pid as isize), + &mut sts, + WaitFlags::from_bits_truncate(options as usize), + )); + unsafe { status.write(sts as i32) }; + res +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_kill_v1(pid: usize, signal: u32) -> RawResult { + Error::mux( + redox_rt::sys::posix_kill(ProcKillTarget::from_raw(pid), signal as usize).map(|()| 0), + ) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_sigaction_v1( + signal: u32, + new: *const sigaction, + old: *mut sigaction, +) -> RawResult { + Error::mux( + Sys::sigaction(signal as c_int, unsafe { new.as_ref() }, unsafe { + old.as_mut() + }) + .map(|()| 0) + .map_err(Into::into), + ) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_sigprocmask_v1( + how: u32, + new: *const u64, + old: *mut u64, +) -> RawResult { + Error::mux( + Sys::sigprocmask(how as c_int, unsafe { new.as_ref() }, unsafe { + old.as_mut() + }) + .map(|()| 0) + .map_err(Into::into), + ) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_mmap_v1( + addr: *mut (), + unaligned_len: usize, + prot: u32, + flags: u32, + fd: usize, + offset: u64, +) -> RawResult { + Error::mux(unsafe { + syscall::fmap( + fd, + &syscall::Map { + address: addr as usize, + offset: offset as usize, + size: unaligned_len, + flags: syscall::MapFlags::from_bits_truncate( + ((prot << 16) | (flags & 0xffff)) as usize, + ), + }, + ) + }) +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_munmap_v1(addr: *mut (), unaligned_len: usize) -> RawResult { + Error::mux(unsafe { syscall::funmap(addr as usize, unaligned_len) }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_clock_gettime_v1(clock: usize, ts: *mut timespec) -> RawResult { + Error::mux(clock_gettime(clock, unsafe { Out::nonnull(ts) }).map(|()| 0)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_strerror_v1( + buf: *mut u8, + buflen: *mut usize, + error: u32, +) -> RawResult { + let dst = unsafe { core::slice::from_raw_parts_mut(buf, buflen.read()) }; + + Error::mux((|| { + // TODO: Merge syscall::error::STR_ERROR into crate::header::error::? + + let src = syscall::error::STR_ERROR + .get(error as usize) + .ok_or(Error::new(EINVAL))?; + + // This API ensures that the returned buffer is proper UTF-8. Thus, it returns both the + // copied length and the actual length. + + unsafe { buflen.write(src.len()) }; + + let raw_len = core::cmp::min(dst.len(), src.len()); + let len = match core::str::from_utf8(&src.as_bytes()[..raw_len]) { + Ok(_valid) => raw_len, + Err(error) => error.valid_up_to(), + }; + + dst[..len].copy_from_slice(&src.as_bytes()[..len]); + Ok(len) + })()) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_mkns_v1( + names: *const iovec, + num_names: usize, + flags: u32, +) -> RawResult { + Error::mux((|| { + if flags != 0 { + return Err(Error::new(EINVAL)); + } + let raw_iovecs = unsafe { slice::from_raw_parts(names, num_names) }; + let names_ioslice: Vec = raw_iovecs + .iter() + .map(|iov| { + IoSlice::new(unsafe { + slice::from_raw_parts(iov.iov_base as *const u8, iov.iov_len) + }) + }) + .collect(); + redox_rt::sys::mkns(&names_ioslice).map(|fd| fd.take()) + })()) +} + +// ABI-UNSTABLE +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_cur_procfd_v0() -> usize { + redox_rt::current_proc_fd().as_raw_fd() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_cur_thrfd_v0() -> usize { + redox_rt::RtTcb::current().thread_fd().as_raw_fd() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_sys_call_v0( + fd: usize, + payload: *mut u8, + payload_len: usize, + flags: usize, + metadata: *const u64, + metadata_len: usize, +) -> RawResult { + Error::mux(redox_rt::sys::sys_call( + fd, + unsafe { slice::from_raw_parts_mut(payload, payload_len) }, + syscall::CallFlags::from_bits_retain(flags), + unsafe { slice::from_raw_parts(metadata, metadata_len) }, + )) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_get_socket_token_v0( + fd: usize, + payload: *mut u8, + payload_len: usize, +) -> RawResult { + let metadata = [SocketCall::GetToken as u64]; + Error::mux(redox_rt::sys::sys_call_ro( + fd, + unsafe { slice::from_raw_parts_mut(payload, payload_len) }, + syscall::CallFlags::empty(), + &metadata, + )) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_setns_v0(fd: usize) -> RawResult { + match redox_rt::sys::setns(fd) { + Some(guard) => guard.take(), + None => usize::MAX, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn redox_register_scheme_to_ns_v0( + ns_fd: usize, + name_base: *const u8, + name_len: usize, + cap_fd: usize, +) -> RawResult { + Error::mux( + redox_rt::sys::register_scheme_to_ns( + ns_fd, + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(name_base, name_len)) }, + cap_fd, + ) + .map(|()| 0), + ) +} diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index ec0c7448e7..3f2029ceab 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -1,608 +1,715 @@ -use core::{mem, ptr, result::Result as CoreResult, slice}; +use core::{ + convert::TryFrom, + mem::{self, size_of}, + num::NonZeroU64, + ptr, slice, str, +}; +use object::bytes_of_slice_mut; +use redox_protocols::protocol::{WaitFlags, wifstopped}; +use redox_rt::{ + RtTcb, + sys::{Resugid, WaitpidTarget}, +}; use syscall::{ - self, - data::{Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec}, - PtraceEvent, Result, + self, EILSEQ, ESRCH, Error, MODE_PERM, StdFsCallKind, StdFsCallMeta, + data::{Map, TimeSpec as redox_timespec}, + dirent::DirentHeader, }; +use self::{ + exec::Executable, + path::{FileLock, canonicalize, openat2, openat2_path}, +}; +use super::{Pal, Read, types::*}; use crate::{ c_str::{CStr, CString}, + error::{Errno, Result}, fs::File, header::{ - dirent::dirent, - errno::{EINVAL, EIO, ENOMEM, EPERM, ERANGE}, - fcntl, + bits_timespec::timespec, + errno::{ + EBADF, EBADFD, EEXIST, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, ENOMEM, + ENOSYS, EOPNOTSUPP, EPERM, + }, + fcntl::{ + self, AT_EACCESS, AT_EMPTY_PATH, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_FOLLOW, + AT_SYMLINK_NOFOLLOW, F_GETLK, F_OFD_GETLK, F_OFD_SETLK, F_RDLCK, F_SETLK, F_SETLKW, + F_UNLCK, F_WRLCK, flock, + }, + limits, + pthread::{pthread_cancel, pthread_create}, + signal::{NSIG, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD, SIGRTMIN, sigevent}, + stdio::RENAME_NOREPLACE, + sys_file, sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE}, sys_random, - sys_resource::{rlimit, RLIM_INFINITY}, - sys_stat::stat, + sys_resource::{RLIM_INFINITY, RLIMIT_NLIMITS, rlimit, rusage}, + sys_select::timeval, + sys_stat::{S_ISVTX, stat}, sys_statvfs::statvfs, - sys_time::{timeval, timezone}, - sys_utsname::{utsname, UTSLENGTH}, - sys_wait, - time::timespec, - unistd::{F_OK, R_OK, W_OK, X_OK}, + sys_time::timezone, + sys_utsname::{UTSLENGTH, utsname}, + time::{TIMER_ABSTIME, itimerspec, timer_internal_t}, + unistd::{F_OK, R_OK, SEEK_CUR, SEEK_SET, W_OK, X_OK}, }, - io::{self, prelude::*, BufReader, SeekFrom}, + io::{self, BufReader, prelude::*}, + ld_so::tcb::OsSpecific, + out::Out, + platform::sys::timer::{timer_routine, timer_update_wake_time}, + sync::rwlock::RwLock, }; -use super::{errno, types::*, Pal, Read}; +pub use redox_rt::proc::FdGuard; + +mod epoll; +mod event; +mod exec; +mod extra; +mod libcscheme; +mod libredox; +pub(crate) mod path; +mod ptrace; +pub(crate) mod signal; +mod socket; +mod timer; static mut BRK_CUR: *mut c_void = ptr::null_mut(); static mut BRK_END: *mut c_void = ptr::null_mut(); -mod epoll; -mod extra; -mod ptrace; -mod signal; -mod socket; - -pub fn e(sys: Result) -> usize { - match sys { - Ok(ok) => ok, - Err(err) => { - unsafe { - errno = err.errno as c_int; - } - !0 - } - } +const PAGE_SIZE: usize = 4096; +fn round_up_to_page_size(val: usize) -> Option { + val.checked_add(PAGE_SIZE) + .map(|val| (val - 1) / PAGE_SIZE * PAGE_SIZE) } +fn cvt_uid(id: c_int) -> Result> { + if id == -1 { + return Ok(None); + } + Ok(Some(id.try_into().map_err(|_| Errno(EINVAL))?)) +} + +macro_rules! path_from_c_str { + ($c_str:expr) => {{ + match $c_str.to_str() { + Ok(ok) => ok, + Err(err) => { + ERRNO.set(EINVAL); + return -1; + } + } + }}; +} + +static CLONE_LOCK: RwLock<()> = RwLock::new(()); + +/// Per-process resource limits. Initialized with Linux-compatible defaults. +/// Inherited automatically across fork() (kernel copies address space). +const RLIMIT_DEFAULTS: [rlimit; RLIMIT_NLIMITS as usize] = [ + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_CPU + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_FSIZE + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_DATA + rlimit { rlim_cur: 8 * 1024 * 1024, rlim_max: RLIM_INFINITY }, // RLIMIT_STACK (8 MB soft) + rlimit { rlim_cur: 0, rlim_max: RLIM_INFINITY }, // RLIMIT_CORE + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_RSS + rlimit { rlim_cur: 4096, rlim_max: RLIM_INFINITY }, // RLIMIT_NPROC + rlimit { rlim_cur: 1024, rlim_max: 1024 * 64 }, // RLIMIT_NOFILE + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_MEMLOCK + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_AS + rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, // RLIMIT_LOCKS + rlimit { rlim_cur: 4096, rlim_max: RLIM_INFINITY }, // RLIMIT_SIGPENDING + rlimit { rlim_cur: 819200, rlim_max: RLIM_INFINITY }, // RLIMIT_MSGQUEUE + rlimit { rlim_cur: 0, rlim_max: 0 }, // RLIMIT_NICE + rlimit { rlim_cur: 0, rlim_max: 0 }, // RLIMIT_RTPRIO +]; + +/// Runtime resource limits, mutable via setrlimit(). +/// Inherited across fork() (kernel copies address space). +static RLIMIT_TABLE: RwLock<[rlimit; RLIMIT_NLIMITS as usize]> = RwLock::new( + RLIMIT_DEFAULTS +); + +/// Redox syscall implementation of [`Pal`]. pub struct Sys; impl Pal for Sys { - fn access(path: &CStr, mode: c_int) -> c_int { - let fd = match File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC) { - Ok(fd) => fd, - Err(_) => return -1, - }; + fn access(path: CStr, mode: c_int) -> Result<()> { + Sys::faccessat(AT_FDCWD, path, mode, 0) + } + + fn faccessat(fd: c_int, path: CStr, mode: c_int, flags: c_int) -> Result<()> { + let fd = FdGuard::new(Sys::openat(fd, path, fcntl::O_PATH | fcntl::O_CLOEXEC, 0)? as usize); + + if (flags & !(AT_EACCESS)) != 0 { + return Err(Errno(EINVAL)); + } if mode == F_OK { - return 0; + return Ok(()); } let mut stat = syscall::Stat::default(); - if e(syscall::fstat(*fd as usize, &mut stat)) == !0 { - return -1; - } + fd.fstat(&mut stat)?; - let uid = e(syscall::getuid()); - if uid == !0 { - return -1; - } - let gid = e(syscall::getgid()); - if gid == !0 { - return -1; - } - - let perms = if stat.st_uid as usize == uid { - stat.st_mode >> (3 * 2 & 0o7) - } else if stat.st_gid as usize == gid { - stat.st_mode >> (3 * 1 & 0o7) + let Resugid { + ruid, + rgid, + euid, + egid, + .. + } = redox_rt::sys::posix_getresugid(); + let (uid, gid) = if (flags & AT_EACCESS) == AT_EACCESS { + (euid, egid) } else { - stat.st_mode & 0o7 + (ruid, rgid) }; + + let perms = (if stat.st_uid == uid { + stat.st_mode >> (3 * 2) + } else if stat.st_gid == gid { + stat.st_mode >> (3 * 1) + } else { + stat.st_mode + }) & 0o7; if (mode & R_OK == R_OK && perms & 0o4 != 0o4) || (mode & W_OK == W_OK && perms & 0o2 != 0o2) || (mode & X_OK == X_OK && perms & 0o1 != 0o1) { - unsafe { - errno = EINVAL; - } - return -1; + return Err(Errno(EINVAL)); } - 0 + Ok(()) } - fn brk(addr: *mut c_void) -> *mut c_void { - unsafe { - // On first invocation, allocate a buffer for brk - if BRK_CUR.is_null() { - // 4 megabytes of RAM ought to be enough for anybody - const BRK_MAX_SIZE: usize = 4 * 1024 * 1024; + unsafe fn brk(addr: *mut c_void) -> Result<*mut c_void> { + // On first invocation, allocate a buffer for brk + if unsafe { BRK_CUR }.is_null() { + // 4 megabytes of RAM ought to be enough for anybody + const BRK_MAX_SIZE: usize = 4 * 1024 * 1024; - let allocated = Self::mmap( + let allocated = unsafe { + Self::mmap( ptr::null_mut(), BRK_MAX_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, 0, 0, - ); - if allocated == !0 as *mut c_void - /* MAP_FAILED */ - { - return !0 as *mut c_void; - } + ) + }?; + unsafe { BRK_CUR = allocated; - BRK_END = (allocated as *mut u8).add(BRK_MAX_SIZE) as *mut c_void; - } + BRK_END = (allocated as *mut u8).add(BRK_MAX_SIZE) as *mut c_void + }; + } - if addr.is_null() { - // Lookup what previous brk() invocations have set the address to - BRK_CUR - } else if BRK_CUR <= addr && addr < BRK_END { - // It's inside buffer, return - BRK_CUR = addr; - addr - } else { - // It was outside of valid range - errno = ENOMEM; - ptr::null_mut() - } + if addr.is_null() { + // Lookup what previous brk() invocations have set the address to + Ok(unsafe { BRK_CUR }) + } else if unsafe { BRK_CUR } <= addr && addr < unsafe { BRK_END } { + // It's inside buffer, return + unsafe { BRK_CUR = addr }; + Ok(addr) + } else { + // It was outside of valid range + Err(Errno(ENOMEM)) } } - fn chdir(path: &CStr) -> c_int { - e(syscall::chdir(path.to_bytes())) as c_int + fn chdir(path: CStr) -> Result<()> { + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + path::chdir(path)?; + Ok(()) } - fn chmod(path: &CStr, mode: mode_t) -> c_int { - match File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC) { - Ok(file) => Self::fchmod(*file, mode), - Err(_) => -1, + fn chmod(path: CStr, mode: mode_t) -> Result<()> { + let file = File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC)?; + Self::fchmod(*file, mode) + } + + fn chown(path: CStr, owner: uid_t, group: gid_t) -> Result<()> { + let file = File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC)?; + Self::fchown(*file, owner, group) + } + + fn clock_getres(clk_id: clockid_t, res: Option>) -> Result<()> { + let path = format!("/scheme/time/{clk_id}/getres"); + let timerfd = FdGuard::open(&path, syscall::O_RDONLY)?; + let mut redox_res = timespec::default(); + let buffer = unsafe { + slice::from_raw_parts_mut( + &mut redox_res as *mut _ as *mut u8, + mem::size_of::(), + ) + }; + + let bytes_read = redox_rt::sys::posix_read(timerfd.as_raw_fd(), buffer)?; + + if bytes_read < mem::size_of::() { + return Err(Errno(EIO)); } - } - fn chown(path: &CStr, owner: uid_t, group: gid_t) -> c_int { - match File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC) { - Ok(file) => Self::fchown(*file, owner, group), - Err(_) => -1, + if let Some(mut res) = res { + res.write(redox_res); } + + Ok(()) } - fn clock_gettime(clk_id: clockid_t, tp: *mut timespec) -> c_int { - let mut redox_tp = unsafe { redox_timespec::from(&*tp) }; - match e(syscall::clock_gettime(clk_id as usize, &mut redox_tp)) as c_int { - -1 => -1, - _ => { - unsafe { - (*tp).tv_sec = redox_tp.tv_sec; - (*tp).tv_nsec = redox_tp.tv_nsec as i64; - }; - 0 - } - } + fn clock_gettime(clk_id: clockid_t, tp: Out) -> Result<()> { + libredox::clock_gettime(clk_id as usize, tp)?; + Ok(()) } - fn close(fd: c_int) -> c_int { - e(syscall::close(fd as usize)) as c_int + unsafe fn clock_settime(clk_id: clockid_t, tp: *const timespec) -> Result<()> { + todo_skip!(0, "clock_settime({}, {:p}): not implemented", clk_id, tp); + Err(Errno(ENOSYS)) } - fn dup(fd: c_int) -> c_int { - e(syscall::dup(fd as usize, &[])) as c_int + fn close(fd: c_int) -> Result<()> { + syscall::close(fd as usize)?; + Ok(()) } - fn dup2(fd1: c_int, fd2: c_int) -> c_int { - e(syscall::dup2(fd1 as usize, fd2 as usize, &[])) as c_int + fn dup(fd: c_int) -> Result { + Ok(syscall::dup(fd as usize, &[])? as c_int) + } + + fn dup2(fd1: c_int, fd2: c_int) -> Result { + Ok(syscall::dup2(fd1 as usize, fd2 as usize, &[])? as c_int) } fn exit(status: c_int) -> ! { - let _ = syscall::exit(status as usize); + let _ = redox_rt::sys::posix_exit(status); loop {} } - unsafe fn execve( - path: &CStr, - mut argv: *const *mut c_char, - mut envp: *const *mut c_char, - ) -> c_int { - let mut file = match File::open(path, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { - Ok(file) => file, - Err(_) => return -1, - }; - let fd = *file as usize; + unsafe fn execve(path: CStr, argv: *const *mut c_char, envp: *const *mut c_char) -> Result<()> { + self::exec::execve( + Executable::AtPath(path), + self::exec::ArgEnv::C { argv, envp }, + None, + )?; + unreachable!() + } + unsafe fn fexecve( + fildes: c_int, + argv: *const *mut c_char, + envp: *const *mut c_char, + ) -> Result<()> { + self::exec::execve( + Executable::InFd { + file: File::new(fildes), + arg0: unsafe { CStr::from_ptr(argv.read()) }.to_bytes(), + }, + self::exec::ArgEnv::C { argv, envp }, + None, + )?; + unreachable!() + } - // Count arguments - let mut len = 0; - while !(*argv.offset(len)).is_null() { - len += 1; + fn fchdir(fd: c_int) -> Result<()> { + path::fchdir(fd)?; + Ok(()) + } + + fn fchmod(fd: c_int, mode: mode_t) -> Result<()> { + libredox::fchmod(fd as usize, mode as u16)?; + Ok(()) + } + + fn fchmodat(dirfd: c_int, path: Option, mode: mode_t, flags: c_int) -> Result<()> { + const MASK: c_int = !(fcntl::AT_SYMLINK_NOFOLLOW | fcntl::AT_EMPTY_PATH); + if MASK & flags != 0 { + return Err(Errno(EINVAL)); } + let mut path = path + .and_then(|cs| str::from_utf8(cs.to_bytes()).ok()) + .ok_or(Errno(ENOENT))?; - let mut args: Vec<[usize; 2]> = Vec::with_capacity(len as usize); - - // Read shebang (for example #!/bin/sh) - let interpreter = { - let mut reader = BufReader::new(&mut file); - - let mut shebang = [0; 2]; - let mut read = 0; - - while read < 2 { - match reader.read(&mut shebang) { - Ok(0) => break, - Ok(i) => read += i, - Err(_) => return -1, - } - } - - if &shebang == b"#!" { - // So, this file is interpreted. - // That means the actual file descriptor passed to `fexec` won't be this file. - // So we need to check ourselves that this file is actually be executable. - - let mut stat = redox_stat::default(); - if e(syscall::fstat(fd, &mut stat)) == !0 { - return -1; - } - let uid = e(syscall::getuid()); - if uid == !0 { - return -1; - } - let gid = e(syscall::getuid()); - if gid == !0 { - return -1; - } - - let mode = if uid == stat.st_uid as usize { - (stat.st_mode >> 3 * 2) & 0o7 - } else if gid == stat.st_gid as usize { - (stat.st_mode >> 3 * 1) & 0o7 + if path.is_empty() { + if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { + if dirfd == AT_FDCWD { + path = "."; } else { - stat.st_mode & 0o7 - }; - - if mode & 0o1 == 0o0 { - errno = EPERM; - return -1; - } - - // Then, read the actual interpreter: - let mut interpreter = Vec::new(); - match reader.read_until(b'\n', &mut interpreter) { - Err(_) => return -1, - Ok(_) => { - if interpreter.ends_with(&[b'\n']) { - interpreter.pop().unwrap(); - } - // TODO: Returning the interpreter here is actually a - // hack. Preferrably we should reassign `file =` - // directly from here. Just wait until NLL comes - // around... - Some(interpreter) - } + return Sys::fchmod(dirfd, mode); } } else { - None - } - }; - let mut _interpreter_path = None; - if let Some(interpreter) = interpreter { - let cstring = match CString::new(interpreter) { - Ok(cstring) => cstring, - Err(_) => return -1, - }; - file = match File::open(&cstring, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { - Ok(file) => file, - Err(_) => return -1, - }; - - // Make sure path is kept alive long enough, and push it to the arguments - _interpreter_path = Some(cstring); - let path_ref = _interpreter_path.as_ref().unwrap(); - args.push([path_ref.as_ptr() as usize, path_ref.to_bytes().len()]); - } else { - if file.seek(SeekFrom::Start(0)).is_err() { - return -1; + // If the path is empty but `AT_EMPTY_PATH` is **not** set, bail out. + return Err(Errno(ENOENT)); } } - // Arguments - while !(*argv).is_null() { - let arg = *argv; + let file = openat2(dirfd, path, flags, 0)?; + libredox::fchmod(*file as usize, mode as u16)?; + Ok(()) + } - let mut len = 0; - while *arg.offset(len) != 0 { - len += 1; + fn fchown(fd: c_int, owner: uid_t, group: gid_t) -> Result<()> { + libredox::fchown(fd as usize, owner as u32, group as u32)?; + Ok(()) + } + + fn fchownat(fildes: c_int, path: CStr, owner: uid_t, group: gid_t, flags: c_int) -> Result<()> { + const MASK: c_int = !(fcntl::AT_SYMLINK_NOFOLLOW | fcntl::AT_EMPTY_PATH); + if MASK & flags != 0 { + return Err(Errno(EINVAL)); + } + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let file = openat2(fildes, path, flags, 0)?; + libredox::fchown( + *file as usize, + owner.try_into().map_err(|_| Errno(EINVAL))?, + group.try_into().map_err(|_| Errno(EINVAL))?, + )?; + Ok(()) + } + + fn fcntl(fd: c_int, cmd: c_int, args: c_ulonglong) -> Result { + match cmd { + F_SETLK | F_OFD_SETLK => { + let is_ofd = cmd == F_OFD_SETLK; + let flock = unsafe { &mut *(args as *mut flock) }; + + let (start, len) = Self::relative_to_absolute_foffset( + fd as usize, + flock.l_whence, + flock.l_start, + flock.l_len, + )?; + + let start = start as u64 | if is_ofd { 1 << 63 } else { 0 }; + let len = len as u64; + + match flock.l_type as i32 { + F_UNLCK => { + let meta = StdFsCallMeta::new(StdFsCallKind::Unlock, start, len); + syscall::std_fs_call(fd as usize, &mut [], &meta)?; + return Ok(0); + } + + F_RDLCK | F_WRLCK => { + let meta = StdFsCallMeta::new( + StdFsCallKind::Lock, + start, + len | if flock.l_type as i32 == F_WRLCK { + 1 << 63 + } else { + 0 + }, + ); + syscall::std_fs_call(fd as usize, &mut [], &meta)?; + return Ok(0); + } + + _ => return Err(Errno(EINVAL)), + }; } - args.push([arg as usize, len as usize]); - argv = argv.offset(1); - } - // Environment variables - let mut len = 0; - while !(*envp.offset(len)).is_null() { - len += 1; - } + F_GETLK | F_OFD_GETLK => { + let is_ofd = cmd == F_OFD_GETLK; + let flock = unsafe { &mut *(args as *mut flock) }; - let mut envs: Vec<[usize; 2]> = Vec::with_capacity(len as usize); - while !(*envp).is_null() { - let env = *envp; + if is_ofd && flock.l_pid != 0 { + log::warn!("POSIX requires `l_pid` to be 0 on input for `F_OFD_GETLK`"); + return Err(Errno(EINVAL)); + } - let mut len = 0; - while *env.offset(len) != 0 { - len += 1; + let (start, len) = Self::relative_to_absolute_foffset( + fd as usize, + flock.l_whence, + flock.l_start, + flock.l_len, + )?; + + let mut start = start as u64; + if is_ofd { + start |= 1 << 63; + } + + let mut len = len as u64; + if flock.l_type as i32 == F_WRLCK { + len |= 1 << 63; + } + + let meta = StdFsCallMeta::new(StdFsCallKind::GetLock, 0, 0); + let payload = &mut [start, len]; + // `pid` if traditional POSIX otherwise 0 + let val = + match syscall::std_fs_call(fd as usize, bytes_of_slice_mut(payload), &meta) { + // According to POSIX Issue 8: + // > If no lock is found that would prevent this lock from being created, then + // > the structure shall be left unchanged except for the lock type in `l_type` + // > which shall be set to F_UNLCK. + Err(err) if err.errno == ESRCH => { + flock.l_type = F_UNLCK as i16; + return Ok(0); + } + + Ok(val) => val, + Err(err) => return Err(Errno(err.errno)), + }; + + debug_assert_ne!(payload[0] & (1 << 63), 1 << 63); + + if is_ofd { + flock.l_pid = -1; + } else { + flock.l_pid = val as i32; + } + + let len = payload[1] & !(1 << 63); + if payload[1] & (1 << 63) == (1 << 63) { + flock.l_type = F_WRLCK as i16; + } else { + flock.l_type = F_RDLCK as i16; + } + + flock.l_whence = SEEK_SET as _; + flock.l_start = start as i64; + flock.l_len = len as i64; + return Ok(0); } - envs.push([env as usize, len as usize]); - envp = envp.offset(1); + + F_SETLKW => log::warn!("F_SETLKW: not yet implemented"), + + _ => {} } - e(syscall::fexec(*file as usize, &args, &envs)) as c_int + Ok(syscall::fcntl(fd as usize, cmd as usize, args as usize)? as c_int) } - fn fchdir(fd: c_int) -> c_int { - let mut buf = [0; 4096]; - let res = e(syscall::fpath(fd as usize, &mut buf)); - if res == !0 { - !0 - } else { - e(syscall::chdir(&buf[..res])) as c_int - } + fn fdatasync(fd: c_int) -> Result<()> { + // TODO: "Needs" syscall update + syscall::fsync(fd as usize)?; + Ok(()) } - fn fchmod(fd: c_int, mode: mode_t) -> c_int { - e(syscall::fchmod(fd as usize, mode as u16)) as c_int - } - - fn fchown(fd: c_int, owner: uid_t, group: gid_t) -> c_int { - e(syscall::fchown(fd as usize, owner as u32, group as u32)) as c_int - } - - fn fcntl(fd: c_int, cmd: c_int, args: c_int) -> c_int { - e(syscall::fcntl(fd as usize, cmd as usize, args as usize)) as c_int - } - - fn flock(_fd: c_int, _operation: c_int) -> c_int { + fn flock(_fd: c_int, _operation: c_int) -> Result<()> { // TODO: Redox does not have file locking yet - 0 + Ok(()) } - fn fork() -> pid_t { - e(unsafe { syscall::clone(syscall::CloneFlags::empty()) }) as pid_t + unsafe fn fork() -> Result { + // TODO: Find way to avoid lock. + let _guard = CLONE_LOCK.write(); + + Ok(redox_rt::proc::fork_impl(&redox_rt::proc::ForkArgs::Managed)? as pid_t) } - fn fstat(fildes: c_int, buf: *mut stat) -> c_int { - let mut redox_buf: redox_stat = redox_stat::default(); - match e(syscall::fstat(fildes as usize, &mut redox_buf)) { - 0 => { - if let Some(buf) = unsafe { buf.as_mut() } { - buf.st_dev = redox_buf.st_dev as dev_t; - buf.st_ino = redox_buf.st_ino as ino_t; - buf.st_nlink = redox_buf.st_nlink as nlink_t; - buf.st_mode = redox_buf.st_mode as mode_t; - buf.st_uid = redox_buf.st_uid as uid_t; - buf.st_gid = redox_buf.st_gid as gid_t; - // TODO st_rdev - buf.st_rdev = 0; - buf.st_size = redox_buf.st_size as off_t; - buf.st_blksize = redox_buf.st_blksize as blksize_t; - buf.st_atim = timespec { - tv_sec: redox_buf.st_atime as time_t, - tv_nsec: redox_buf.st_atime_nsec as c_long, - }; - buf.st_mtim = timespec { - tv_sec: redox_buf.st_mtime as time_t, - tv_nsec: redox_buf.st_mtime_nsec as c_long, - }; - buf.st_ctim = timespec { - tv_sec: redox_buf.st_ctime as time_t, - tv_nsec: redox_buf.st_ctime_nsec as c_long, - }; + fn fstat(fildes: c_int, mut buf: Out) -> Result<()> { + unsafe { + libredox::fstat(fildes as usize, buf.as_mut_ptr())?; + } + Ok(()) + } + + fn fstatat(dirfd: c_int, path: Option, buf: Out, flags: c_int) -> Result<()> { + // `path` should be non-null. + let path = path.ok_or(Errno(EFAULT))?; + let mut path = str::from_utf8(path.to_bytes()).ok().ok_or(Errno(EILSEQ))?; + + if path.is_empty() { + if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { + if dirfd == AT_FDCWD { + path = "."; + } else { + return Sys::fstat(dirfd, buf); } - 0 - } - _ => -1, - } - } - - fn fstatvfs(fildes: c_int, buf: *mut statvfs) -> c_int { - let mut kbuf: redox_statvfs = redox_statvfs::default(); - match e(syscall::fstatvfs(fildes as usize, &mut kbuf)) { - 0 => { - unsafe { - if !buf.is_null() { - (*buf).f_bsize = kbuf.f_bsize as c_ulong; - (*buf).f_frsize = kbuf.f_bsize as c_ulong; - (*buf).f_blocks = kbuf.f_blocks; - (*buf).f_bfree = kbuf.f_bfree; - (*buf).f_bavail = kbuf.f_bavail; - //TODO - (*buf).f_files = 0; - (*buf).f_ffree = 0; - (*buf).f_favail = 0; - (*buf).f_fsid = 0; - (*buf).f_flag = 0; - (*buf).f_namemax = 0; - } - } - 0 - } - _ => -1, - } - } - - fn fsync(fd: c_int) -> c_int { - e(syscall::fsync(fd as usize)) as c_int - } - - fn ftruncate(fd: c_int, len: off_t) -> c_int { - e(syscall::ftruncate(fd as usize, len as usize)) as c_int - } - - fn futex(addr: *mut c_int, op: c_int, val: c_int) -> c_int { - match unsafe { - syscall::futex( - addr as *mut i32, - op as usize, - val as i32, - 0, - ptr::null_mut(), - ) - } { - Ok(success) => success as c_int, - Err(err) => -(err.errno as c_int), - } - } - - fn futimens(fd: c_int, times: *const timespec) -> c_int { - let times = [unsafe { redox_timespec::from(&*times) }, unsafe { - redox_timespec::from(&*times.offset(1)) - }]; - e(syscall::futimens(fd as usize, ×)) as c_int - } - - fn utimens(path: &CStr, times: *const timespec) -> c_int { - match File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC) { - Ok(file) => Self::futimens(*file, times), - Err(_) => -1, - } - } - - fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char { - let buf_slice = unsafe { slice::from_raw_parts_mut(buf as *mut u8, size as usize) }; - if !buf_slice.is_empty() { - let nonnull_size = buf_slice.len() - 1; - let read = e(syscall::getcwd(&mut buf_slice[..nonnull_size])); - if read == !0 { - ptr::null_mut() - } else if read == nonnull_size { - unsafe { - errno = ERANGE; - } - ptr::null_mut() } else { - for b in &mut buf_slice[read..] { - *b = 0; - } - buf + // If the path is empty but `AT_EMPTY_PATH` is **not** set, bail out. + return Err(Errno(ENOENT)); } - } else { - unsafe { - errno = EINVAL; - } - ptr::null_mut() } + + // Use `O_PATH` to obtain a file descriptor without actually *opening* the file. This + // bypasses permission checks and avoids cases where opening a file is blocking operation + // (e.g., FIFOs). This gives a file descriptor where fstat(2) can be performed (and some + // other meta operations) but nothing else (e.g. read/write). + // + // `O_CLOEXEC` is used to avoid leaking file descriptors to child processes on exec(2). + // + // FIXME: Ideally we would want the file descriptor to not leak on fork(2) too because + // fstatat(2) should not have side effects. However, Redox does not currently support that, + // so we use `CLOEXEC` as a compromise. + // FIXME: Should we handle AT_* flags here or in openat2? + let mut open_flags = fcntl::O_PATH | fcntl::O_CLOEXEC; + if flags & AT_SYMLINK_NOFOLLOW == AT_SYMLINK_NOFOLLOW { + open_flags |= fcntl::O_SYMLINK | fcntl::O_NOFOLLOW; + } + + let file = openat2(dirfd, path, 0, open_flags)?; + // Close the file descriptor after fstat(2) regardless of success or failure. + let fstat_res = Sys::fstat(*file, buf); + let close_res = syscall::close(file.fd as usize); + if let Err(err) = fstat_res { + return Err(err); + } + close_res?; + fstat_res } - fn getdents(fd: c_int, mut dirents: *mut dirent, max_bytes: usize) -> c_int { - //TODO: rewrite this code. Originally the *dirents = dirent { ... } stuff below caused - // massive issues. This has been hacked around, but it still isn't perfect - - // Get initial reading position - let mut read = match syscall::lseek(fd as usize, 0, syscall::SEEK_CUR) { - Ok(pos) => pos as isize, - Err(err) => return -err.errno, - }; - - let mut written = 0; - let mut buf = [0; 1024]; - - let mut name = [0; 256]; - let mut i = 0; - - let mut flush = |written: &mut usize, i: &mut usize, name: &mut [c_char; 256]| { - if *i < name.len() { - // Set NUL byte - name[*i] = 0; - } - // Get size: full size - unused bytes - if *written + mem::size_of::() > max_bytes { - // Seek back to after last read entry and return - match syscall::lseek(fd as usize, read, syscall::SEEK_SET) { - Ok(_) => return Some(*written as c_int), - Err(err) => return Some(-err.errno), - } - } - let size = mem::size_of::() - name.len().saturating_sub(*i + 1); - unsafe { - //This is the offending code mentioned above - *dirents = dirent { - d_ino: 0, - d_off: read as off_t, - d_reclen: size as c_ushort, - d_type: 0, - d_name: *name, - }; - dirents = (dirents as *mut u8).offset(size as isize) as *mut dirent; - } - read += *i as isize + /* newline */ 1; - *written += size; - *i = 0; - None - }; - - loop { - // Read a chunk from the directory - let len = match syscall::read(fd as usize, &mut buf) { - Ok(0) => { - if i > 0 { - if let Some(value) = flush(&mut written, &mut i, &mut name) { - return value; - } - } - return written as c_int; - } - Ok(n) => n, - Err(err) => return -err.errno, - }; - - // Handle everything - let mut start = 0; - while start < len { - let buf = &buf[start..len]; - - // Copy everything up until a newline - let newline = buf.iter().position(|&c| c == b'\n'); - let pre_len = newline.unwrap_or(buf.len()); - let post_len = newline.map(|i| i + 1).unwrap_or(buf.len()); - if i < pre_len { - // Reserve space for NUL byte - let name_len = name.len() - 1; - let name = &mut name[i..name_len]; - let copy = pre_len.min(name.len()); - let buf = unsafe { slice::from_raw_parts(buf.as_ptr() as *const c_char, copy) }; - name[..copy].copy_from_slice(buf); - } - - i += pre_len; - start += post_len; - - // Write the directory entry - if newline.is_some() { - if let Some(value) = flush(&mut written, &mut i, &mut name) { - return value; - } - } - } + fn fstatvfs(fildes: c_int, mut buf: Out) -> Result<()> { + unsafe { + libredox::fstatvfs(fildes as usize, buf.as_mut_ptr())?; } + Ok(()) + } + + fn fsync(fd: c_int) -> Result<()> { + libredox::fsync(fd as usize)?; + Ok(()) + } + + fn ftruncate(fd: c_int, len: off_t) -> Result<()> { + libredox::ftruncate(fd as usize, len as usize)?; + Ok(()) + } + + #[inline] + unsafe fn futex_wait(addr: *mut u32, val: u32, deadline: Option<×pec>) -> Result<()> { + let deadline = deadline.map(|d| syscall::TimeSpec { + tv_sec: d.tv_sec, + tv_nsec: d.tv_nsec as i32, + }); + (unsafe { redox_rt::sys::sys_futex_wait(addr, val, deadline.as_ref()) })?; + Ok(()) + } + #[inline] + unsafe fn futex_wake(addr: *mut u32, num: u32) -> Result { + Ok(unsafe { redox_rt::sys::sys_futex_wake(addr, num) }?) + } + + unsafe fn futimens(fd: c_int, times: *const timespec) -> Result<()> { + (unsafe { libredox::futimens(fd as usize, times) })?; + Ok(()) + } + + unsafe fn utimens(path: CStr, times: *const timespec) -> Result<()> { + let file = File::open(path, fcntl::O_PATH | fcntl::O_CLOEXEC)?; + unsafe { Self::futimens(*file, times) } + } + + fn getcwd(buf: Out<[u8]>) -> Result<()> { + path::getcwd(buf)?; + Ok(()) + } + + fn getdents(fd: c_int, buf: &mut [u8], opaque: u64) -> Result { + Ok(libredox::getdents(fd as usize, buf, opaque)?) + } + + fn dir_seek(_fd: c_int, _off: u64) -> Result<()> { + // Redox getdents takes an explicit (opaque) offset, so this is a no-op. + Ok(()) + } + // NOTE: fn is unsafe, but this just means we can assume more things. impl is safe + unsafe fn dent_reclen_offset(this_dent: &[u8], offset: usize) -> Option<(u16, u64)> { + let mut header = DirentHeader::default(); + header.copy_from_slice(&this_dent.get(..size_of::())?); + + // If scheme does not send a NUL byte, this shouldn't be able to cause UB for the caller. + if this_dent.get(usize::from(header.record_len) - 1) != Some(&b'\0') { + return None; + } + + Some((header.record_len, header.next_opaque_id)) } fn getegid() -> gid_t { - e(syscall::getegid()) as gid_t + redox_rt::sys::posix_getresugid().egid as gid_t } fn geteuid() -> uid_t { - e(syscall::geteuid()) as uid_t + redox_rt::sys::posix_getresugid().euid as uid_t } fn getgid() -> gid_t { - e(syscall::getgid()) as gid_t + redox_rt::sys::posix_getresugid().rgid as gid_t } - fn getpgid(pid: pid_t) -> pid_t { - e(syscall::getpgid(pid as usize)) as pid_t + fn getgroups(mut list: Out<[gid_t]>) -> Result { + // FIXME: this operation doesn't scale when group/passwd file grows + + let uid = Self::geteuid(); + let pwd = crate::header::pwd::getpwuid(uid); + + if pwd.is_null() { + return Err(Errno(ENOENT)); + } + + let username = unsafe { CStr::from_ptr((*pwd).pw_name) }; + let username = username.to_bytes_with_nul(); + let mut count = 0; + + unsafe { + use crate::header::grp; + grp::setgrent(); + + while let Some(grp) = grp::getgrent().as_ref() { + let mut i = 0; + let mut found = false; + + while !(*grp.gr_mem.offset(i)).is_null() { + let member = CStr::from_ptr(*grp.gr_mem.offset(i)); + if member.to_bytes_with_nul() == username { + found = true; + break; + } + i += 1; + } + + if found { + if !list.is_empty() && (count as usize) < list.len() { + list.index(count).write(grp.gr_gid); + } + count += 1; + } + } + grp::endgrent(); + } + + if !list.is_empty() && (count as usize) > list.len() { + return Err(Errno(EINVAL)); + } + + Ok(count as i32) + } + + fn getpagesize() -> usize { + PAGE_SIZE + } + + fn getpgid(pid: pid_t) -> Result { + Ok(redox_rt::sys::posix_getpgid(pid as usize)? as pid_t) } fn getpid() -> pid_t { - e(syscall::getpid()) as pid_t + redox_rt::sys::posix_getpid() as pid_t } fn getppid() -> pid_t { - e(syscall::getppid()) as pid_t + redox_rt::sys::posix_getppid() as pid_t } - fn getrandom(buf: &mut [u8], flags: c_uint) -> ssize_t { - //TODO: make this a system call? + fn getpriority(which: c_int, who: id_t) -> Result { + match redox_rt::sys::posix_getpriority(which, who as u32) { + Ok(kernel_prio) => { + let posix_prio = (kernel_prio as i32 * -1) + 40 as i32; + Ok(posix_prio) + } + Err(e) => Err(Errno(e.errno)), + } + } + fn getrandom(buf: &mut [u8], flags: c_uint) -> Result { let path = if flags & sys_random::GRND_RANDOM != 0 { //TODO: /dev/random equivalent - "rand:" + "/scheme/rand" } else { - "rand:" + "/scheme/rand" }; let mut open_flags = syscall::O_RDONLY | syscall::O_CLOEXEC; @@ -610,90 +717,229 @@ impl Pal for Sys { open_flags |= syscall::O_NONBLOCK; } - let fd = e(syscall::open(path, open_flags)); - if fd == !0 { - return -1; - } - - let res = e(syscall::read(fd, buf)) as ssize_t; - - let _ = syscall::close(fd); - - res + //TODO: store fd internally + let fd = FdGuard::open(path, open_flags)?; + Ok(fd.read(buf)?) } - unsafe fn getrlimit(resource: c_int, rlim: *mut rlimit) -> c_int { - //TODO - if !rlim.is_null() { - (*rlim).rlim_cur = RLIM_INFINITY; - (*rlim).rlim_max = RLIM_INFINITY; + fn getresgid( + rgid_out: Option>, + egid_out: Option>, + sgid_out: Option>, + ) -> Result<()> { + let Resugid { + rgid, egid, sgid, .. + } = redox_rt::sys::posix_getresugid(); + if let Some(mut rgid_out) = rgid_out { + rgid_out.write(rgid as _); } - 0 + if let Some(mut egid_out) = egid_out { + egid_out.write(egid as _); + } + if let Some(mut sgid_out) = sgid_out { + sgid_out.write(sgid as _); + } + Ok(()) + } + fn getresuid( + ruid_out: Option>, + euid_out: Option>, + suid_out: Option>, + ) -> Result<()> { + let Resugid { + ruid, euid, suid, .. + } = redox_rt::sys::posix_getresugid(); + if let Some(mut ruid_out) = ruid_out { + ruid_out.write(ruid as _); + } + if let Some(mut euid_out) = euid_out { + euid_out.write(euid as _); + } + if let Some(mut suid_out) = suid_out { + suid_out.write(suid as _); + } + Ok(()) + } + + fn getrlimit(resource: c_int, mut rlim: Out) -> Result<()> { + if resource < 0 || resource >= RLIMIT_NLIMITS as c_int { + return Err(Errno(EINVAL)); + } + let table = RLIMIT_TABLE.read(); + let current = &table[resource as usize]; + rlim.write(rlimit { + rlim_cur: current.rlim_cur, + rlim_max: current.rlim_max, + }); + Ok(()) + } + + unsafe fn setrlimit(resource: c_int, rlim: *const rlimit) -> Result<()> { + if resource < 0 || resource >= RLIMIT_NLIMITS as c_int { + return Err(Errno(EINVAL)); + } + if rlim.is_null() { + return Err(Errno(EFAULT)); + } + let new = unsafe { &*rlim }; + if new.rlim_cur > new.rlim_max { + return Err(Errno(EINVAL)); + } + let mut table = RLIMIT_TABLE.write(); + let old = &table[resource as usize]; + if new.rlim_max > old.rlim_max { + return Err(Errno(EPERM)); + } + table[resource as usize] = rlimit { + rlim_cur: new.rlim_cur, + rlim_max: new.rlim_max, + }; + Ok(()) + } + + fn getrusage(who: c_int, mut r_usage: Out) -> Result<()> { + let clock_id = match who { + 0 /* RUSAGE_SELF */ => 2 /* CLOCK_PROCESS_CPUTIME_ID */, + 1 /* RUSAGE_THREAD */ => 3 /* CLOCK_THREAD_CPUTIME_ID */, + -1 /* RUSAGE_CHILDREN */ => { + r_usage.write(rusage { + ru_utime: timeval { tv_sec: 0, tv_usec: 0 }, + ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, + ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0, + ru_minflt: 0, ru_majflt: 0, ru_nswap: 0, + ru_inblock: 0, ru_oublock: 0, ru_msgsnd: 0, ru_msgrcv: 0, + ru_nsignals: 0, ru_nvcsw: 0, ru_nivcsw: 0, + }); + return Ok(()); + } + _ => return Err(Errno(EINVAL)), + }; + + let mut redox_tp = syscall::TimeSpec::default(); + let clock_result = syscall::clock_gettime(clock_id, &mut redox_tp); + + let (tv_sec, tv_usec) = if clock_result.is_ok() { + let ts: timespec = (&redox_tp).into(); + (ts.tv_sec, (ts.tv_nsec / 1000) as _) + } else { + (0, 0) + }; + + r_usage.write(rusage { + ru_utime: timeval { tv_sec, tv_usec }, + ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, + ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0, + ru_minflt: 0, ru_majflt: 0, ru_nswap: 0, + ru_inblock: 0, ru_oublock: 0, ru_msgsnd: 0, ru_msgrcv: 0, + ru_nsignals: 0, ru_nvcsw: 0, ru_nivcsw: 0, + }); + Ok(()) + } + + fn getsid(pid: pid_t) -> Result { + Ok(redox_rt::sys::posix_getsid(pid as usize)? as _) } fn gettid() -> pid_t { - //TODO - Self::getpid() + // This is used by pthread mutexes for reentrant checks and must be nonzero + // and unique for each thread in the same process (but not cross-process) + let thread_fd = Self::current_os_tid().thread_fd; + (thread_fd & !syscall::UPPER_FDTBL_TAG) + .checked_add(1) + .unwrap() + .try_into() + .unwrap() } - fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int { + fn gettimeofday(mut tp: Out, tzp: Option>) -> Result<()> { let mut redox_tp = redox_timespec::default(); - let err = e(syscall::clock_gettime( - syscall::CLOCK_REALTIME, - &mut redox_tp, - )) as c_int; - if err < 0 { - return err; - } - unsafe { - (*tp).tv_sec = redox_tp.tv_sec as time_t; - (*tp).tv_usec = (redox_tp.tv_nsec / 1000) as suseconds_t; + syscall::clock_gettime(syscall::CLOCK_REALTIME, &mut redox_tp)?; + tp.write(timeval { + tv_sec: redox_tp.tv_sec as time_t, + tv_usec: (redox_tp.tv_nsec / 1000) as suseconds_t, + }); - if !tzp.is_null() { - (*tzp).tz_minuteswest = 0; - (*tzp).tz_dsttime = 0; - } + if let Some(mut tzp) = tzp { + tzp.write(timezone { + tz_minuteswest: 0, + tz_dsttime: 0, + }); } - 0 + Ok(()) } fn getuid() -> uid_t { - e(syscall::getuid()) as pid_t + redox_rt::sys::posix_getresugid().ruid as uid_t } - fn link(path1: &CStr, path2: &CStr) -> c_int { - e(unsafe { syscall::link(path1.as_ptr() as *const u8, path2.as_ptr() as *const u8) }) - as c_int + fn lchown(path: CStr, owner: uid_t, group: gid_t) -> Result<()> { + // TODO: Is it correct for regular chown to use O_PATH? On Linux the meaning of that flag + // is to forbid file operations, including fchown. + + // unlike chown, never follow symbolic links + let file = File::open(path, fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW)?; + Self::fchown(*file, owner, group) } - fn lseek(fd: c_int, offset: off_t, whence: c_int) -> off_t { - e(syscall::lseek( - fd as usize, - offset as isize, - whence as usize, - )) as off_t + fn linkat(fd1: c_int, oldpath: CStr, fd2: c_int, newpath: CStr, flags: c_int) -> Result<()> { + // make sure the flags passed are valid. + // valid states: AT_SYMLINK_FOLLOW, or 0. + if (flags & !(AT_SYMLINK_FOLLOW)) != 0 { + return Err(Errno(EINVAL)); + } + let newpath = newpath.to_str().map_err(|_| Errno(EINVAL))?; + + // By default, we don't follow the symlink if there is one. + // We only follow it if AT_SYMLINK_FOLLOW is passed in flags. + // We represent this by setting O_NOFOLLOW by default, and clearing it + // if AT_SYMLINK_FOLLOW is present. + let mut oflags = fcntl::O_PATH | fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW; + if (flags & AT_SYMLINK_FOLLOW) == AT_SYMLINK_FOLLOW { + oflags &= !fcntl::O_NOFOLLOW; + } + + let file = File::openat(fd1, oldpath, oflags)?; + let newpath = openat2_path(fd2, newpath, 0)?; + syscall::flink(*file as usize, newpath)?; + Ok(()) } - fn mkdir(path: &CStr, mode: mode_t) -> c_int { - match File::create( - path, + fn lseek(fd: c_int, offset: off_t, whence: c_int) -> Result { + Ok(syscall::lseek(fd as usize, offset as isize, whence as usize)? as off_t) + } + + fn mkdirat(dir_fd: c_int, path_name: CStr, mode: mode_t) -> Result<()> { + File::createat( + dir_fd, + path_name, fcntl::O_DIRECTORY | fcntl::O_EXCL | fcntl::O_CLOEXEC, 0o777, - ) { - Ok(_fd) => 0, - Err(_) => -1, - } + )?; + Ok(()) } - fn mkfifo(path: &CStr, mode: mode_t) -> c_int { - match File::create( - path, - fcntl::O_CREAT | fcntl::O_CLOEXEC, + fn mkfifoat(dir_fd: c_int, path_name: CStr, mode: mode_t) -> Result<()> { + Sys::mknodat( + dir_fd, + path_name, syscall::MODE_FIFO as mode_t | (mode & 0o777), - ) { - Ok(fd) => 0, - Err(_) => -1, - } + 0, + ) + } + + fn mknodat(dir_fd: c_int, path_name: CStr, mode: mode_t, dev: dev_t) -> Result<()> { + File::createat(dir_fd, path_name, fcntl::O_CREAT | fcntl::O_CLOEXEC, mode)?; + Ok(()) + } + + unsafe fn mlock(addr: *const c_void, len: usize) -> Result<()> { + // Redox never swaps + Ok(()) + } + + unsafe fn mlockall(flags: c_int) -> Result<()> { + // Redox never swaps + Ok(()) } unsafe fn mmap( @@ -703,64 +949,104 @@ impl Pal for Sys { flags: c_int, fildes: c_int, off: off_t, - ) -> *mut c_void { + ) -> Result<*mut c_void> { + // 0 is invalid per spec + if len == 0 { + return Err(Errno(EINVAL)); + } + let Some(size) = round_up_to_page_size(len) else { + return Err(Errno(ENOMEM)); + }; + let map = Map { offset: off as usize, - size: len, + size, flags: syscall::MapFlags::from_bits_truncate( ((prot as usize) << 16) | ((flags as usize) & 0xFFFF), ), address: addr as usize, }; - if flags & MAP_ANONYMOUS == MAP_ANONYMOUS { - let fd = e(syscall::open( - "memory:", - syscall::O_STAT | syscall::O_CLOEXEC, - )); // flags don't matter currently - if fd == !0 { - return !0 as *mut c_void; - } - - let addr = e(syscall::fmap(fd, &map)) as *mut c_void; - - let _ = syscall::close(fd); - - addr + Ok(if flags & MAP_ANONYMOUS == MAP_ANONYMOUS { + (unsafe { syscall::fmap(!0, &map) })? } else { - e(syscall::fmap(fildes as usize, &map)) as *mut c_void - } + (unsafe { syscall::fmap(fildes as usize, &map) })? + } as *mut c_void) } - unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int { - e(syscall::mprotect( - addr as usize, - len, - syscall::MapFlags::from_bits((prot as usize) << 16) - .expect("mprotect: invalid bit pattern"), - )) as c_int + unsafe fn mremap( + addr: *mut c_void, + len: usize, + new_len: usize, + flags: c_int, + args: *mut c_void, + ) -> Result<*mut c_void> { + Err(Errno(ENOSYS)) } - unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> c_int { - eprintln!("msync {:p} {:x} {:x}", addr, len, flags); - e(Err(syscall::Error::new(syscall::ENOSYS))) as c_int - /* TODO - e(syscall::msync( - addr as usize, + unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> Result<()> { + let Some(len) = round_up_to_page_size(len) else { + return Err(Errno(ENOMEM)); + }; + let Some(prot) = syscall::MapFlags::from_bits((prot as usize) << 16) else { + return Err(Errno(EINVAL)); + }; + (unsafe { syscall::mprotect(addr as usize, len, prot) })?; + Ok(()) + } + + unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { + todo_skip!( + 0, + "msync({:p}, 0x{:x}, 0x{:x}): not implemented", + addr, len, flags - )) as c_int + ); + Err(Errno(ENOSYS)) + /* TODO + syscall::msync( + addr as usize, + round_up_to_page_size(len), + flags + )?; */ } - unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int { - if e(syscall::funmap(addr as usize, len)) == !0 { - return !0; - } - 0 + unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> { + // Redox never swaps + Ok(()) } - fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int { + unsafe fn munlockall() -> Result<()> { + // Redox never swaps + Ok(()) + } + + unsafe fn munmap(addr: *mut c_void, len: usize) -> Result<()> { + // 0 is invalid per spec + if len == 0 { + return Err(Errno(EINVAL)); + } + let Some(len) = round_up_to_page_size(len) else { + return Err(Errno(ENOMEM)); + }; + (unsafe { syscall::funmap(addr as usize, len) })?; + Ok(()) + } + + unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { + todo_skip!( + 0, + "madvise({:p}, 0x{:x}, 0x{:x}): not implemented", + addr, + len, + flags + ); + Err(Errno(ENOSYS)) + } + + unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> { let redox_rqtp = unsafe { redox_timespec::from(&*rqtp) }; let mut redox_rmtp: redox_timespec; if rmtp.is_null() { @@ -768,318 +1054,710 @@ impl Pal for Sys { } else { redox_rmtp = unsafe { redox_timespec::from(&*rmtp) }; } - match e(syscall::nanosleep(&redox_rqtp, &mut redox_rmtp)) as c_int { - -1 => -1, - _ => { + match redox_rt::sys::posix_nanosleep(&redox_rqtp, &mut redox_rmtp) { + Ok(_) => Ok(()), + Err(Error { errno: EINTR }) => { unsafe { if !rmtp.is_null() { - (*rmtp).tv_sec = redox_rmtp.tv_sec; - (*rmtp).tv_nsec = redox_rmtp.tv_nsec as i64; + (*rmtp).tv_sec = redox_rmtp.tv_sec as time_t; + (*rmtp).tv_nsec = redox_rmtp.tv_nsec as c_long; } - } - 0 + }; + Err(Errno(EINTR)) } + Err(Error { errno: e }) => Err(Errno(e)), } } - fn open(path: &CStr, oflag: c_int, mode: mode_t) -> c_int { - e(syscall::open( - path.to_bytes(), - ((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF), - )) as c_int + fn open(path: CStr, oflag: c_int, mode: mode_t) -> Result { + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + + // POSIX states that umask should affect the following: + // + // open, openat, creat, mkdir, mkdirat, + // mkfifo, mkfifoat, mknod, mknodat, + // mq_open, and sem_open, + // + // all of which (the ones that exist thus far) currently call this function. + let effective_mode = mode & !(redox_rt::sys::get_umask() as mode_t); + + Ok(libredox::open(path, oflag, effective_mode)? as c_int) } - fn pipe2(fds: &mut [c_int], flags: c_int) -> c_int { - let mut usize_fds: [usize; 2] = [0; 2]; - let res = e(syscall::pipe2(&mut usize_fds, flags as usize)); - fds[0] = usize_fds[0] as c_int; - fds[1] = usize_fds[1] as c_int; - res as c_int + fn openat(dirfd: c_int, path: CStr, oflag: c_int, mode: mode_t) -> Result { + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + + // POSIX states that umask should affect the following: + // + // open, openat, creat, mkdir, mkdirat, + // mkfifo, mkfifoat, mknod, mknodat, + // mq_open, and sem_open, + // + // all of which (the ones that exist thus far) currently call this function. + let effective_mode = mode & !(redox_rt::sys::get_umask() as mode_t); + + Ok(libredox::openat(dirfd, path, oflag, effective_mode)? as c_int) } - #[cfg(target_arch = "x86_64")] - unsafe fn pte_clone(stack: *mut usize) -> pid_t { - let flags = syscall::CLONE_VM - | syscall::CLONE_FS - | syscall::CLONE_FILES - | syscall::CLONE_SIGHAND - | syscall::CLONE_STACK; - let pid; - llvm_asm!(" - # Call clone syscall - syscall - - # Check if child or parent - test rax, rax - jnz .parent - - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 - - # Call entry point - call rax - - # Exit - mov rax, 1 - xor rdi, rdi - syscall - - # Invalid instruction on failure to exit - ud2 - - # Return PID if parent - .parent: - " - : "={rax}"(pid) - : "{rax}"(syscall::SYS_CLONE), "{rdi}"(flags), "{rsi}"(stack) - : "memory", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", - "r9", "r10", "r11", "r12", "r13", "r14", "r15" - : "intel", "volatile" - ); - e(syscall::Error::demux(pid)) as pid_t + fn pipe2(mut fds: Out<[c_int; 2]>, flags: c_int) -> Result<()> { + fds.write(extra::pipe2(flags as usize)?); + Ok(()) } - fn read(fd: c_int, buf: &mut [u8]) -> ssize_t { - e(syscall::read(fd as usize, buf)) as ssize_t + fn posix_fallocate(fd: c_int, offset: u64, length: NonZeroU64) -> Result<()> { + // Redox doesn't actually have flock yet but presumably the file will need to be locked to + // avoid accidentally truncating it if the length changes. + let _guard = FileLock::lock(fd, sys_file::LOCK_EX)?; + + // posix_fallocate is less nuanced than the Linux syscall fallocate. + // If the byte range is already allocated, posix_fallocate doesn't do any extra work. + // If the byte range is unallocated; free, uninitialized bytes are reserved. + // posix_fallocate does not shrink files. + // + // The main purpose of it is to ensure subsequent writes to a byte range don't fail. + let length = length.get(); + let total_offset = offset.checked_add(length).ok_or(Errno(EFBIG))?; + + let mut stat: stat = unsafe { mem::zeroed() }; + unsafe { libredox::fstat(fd as usize, &mut stat)? }; + let st_size = stat.st_size as u64; + // The difference between total_offset and the file size is the number of bytes to + // allocate. So, if it's negative then the file is already large enough and we don't + // need to do any extra work. + if let Some(total_len) = total_offset + .checked_sub(st_size) + .and_then(|diff| st_size.checked_add(diff)) + { + let total_len: usize = total_len.try_into().map_err(|_| Errno(EFBIG))?; + libredox::ftruncate(fd as usize, total_len)?; + } + + Ok(()) } - fn fpath(fildes: c_int, out: &mut [u8]) -> ssize_t { - e(syscall::fpath(fildes as usize, out)) as ssize_t + fn posix_getdents(fildes: c_int, buf: &mut [u8]) -> Result { + let current_offset = Self::lseek(fildes, 0, SEEK_CUR)? as u64; + let bytes_read = Self::getdents(fildes, buf, current_offset)?; + if bytes_read == 0 { + return Ok(0); + } + let mut bytes_processed = 0; + let mut next_offset = current_offset; + + while bytes_processed < bytes_read { + let remaining_slice = &buf[bytes_processed..]; + let (reclen, opaque_next) = + unsafe { Self::dent_reclen_offset(remaining_slice, bytes_processed) } + .ok_or(Errno(EIO))?; + if reclen == 0 { + return Err(Errno(EIO)); + } + + bytes_processed += reclen as usize; + next_offset = opaque_next; + } + + Self::lseek(fildes, next_offset as off_t, SEEK_SET)?; + Ok(bytes_read) } - fn readlink(pathname: &CStr, out: &mut [u8]) -> ssize_t { - let file = match File::open( - pathname, - fcntl::O_PATH | fcntl::O_SYMLINK | fcntl::O_CLOEXEC, - ) { - Ok(ok) => ok, - Err(_) => return -1, + unsafe fn rlct_clone( + stack: *mut usize, + os_specific: &mut OsSpecific, + ) -> Result { + let _guard = CLONE_LOCK.read(); + let res = unsafe { redox_rt::thread::rlct_clone_impl(stack, os_specific) }; + + res.map(|thread_fd| crate::pthread::OsTid { thread_fd }) + .map_err(|error| Errno(error.errno)) + } + + unsafe fn rlct_kill(os_tid: crate::pthread::OsTid, signal: usize) -> Result<()> { + redox_rt::sys::posix_kill_thread(os_tid.thread_fd, signal as u32)?; + Ok(()) + } + fn current_os_tid() -> crate::pthread::OsTid { + crate::pthread::OsTid { + thread_fd: RtTcb::current().thread_fd().as_raw_fd(), + } + } + + fn read(fd: c_int, buf: &mut [u8]) -> Result { + let fd = usize::try_from(fd).map_err(|_| Errno(EBADF))?; + Ok(redox_rt::sys::posix_read(fd, buf)?) + } + + fn pread(fd: c_int, buf: &mut [u8], offset: off_t) -> Result { + unsafe { + Ok(syscall::syscall5( + syscall::SYS_READ2, + fd as usize, + buf.as_mut_ptr() as usize, + buf.len(), + offset as usize, + !0, + )?) + } + } + + fn fpath(fildes: c_int, out: &mut [u8]) -> Result { + // Since this is used by realpath, it converts from the old format to the new one for + // compatibility reasons + let mut buf = [0; limits::PATH_MAX]; + let count = syscall::fpath(fildes as usize, &mut buf)?; + + let redox_path = str::from_utf8(&buf[..count]) + .ok() + .and_then(|x| redox_path::RedoxPath::from_absolute(x)) + .ok_or(Errno(EINVAL))?; + + let (scheme, reference) = redox_path.as_parts().ok_or(Errno(EINVAL))?; + + let mut cursor = io::Cursor::new(out); + let res = match scheme.as_ref() { + "file" => write!(cursor, "/{}", reference.as_ref().trim_start_matches('/')), + _ => write!( + cursor, + "/scheme/{}/{}", + scheme.as_ref(), + reference.as_ref().trim_start_matches('/') + ), }; - - if out.is_empty() { - return 0; - } - - let len = out.len(); - let read = e(syscall::fpath(*file as usize, &mut out[..len - 1])); - if (read as c_int) < 0 { - return -1; - } - out[read as usize] = 0; - - 0 - } - - fn rename(oldpath: &CStr, newpath: &CStr) -> c_int { - match File::open(oldpath, fcntl::O_PATH | fcntl::O_CLOEXEC) { - Ok(file) => e(syscall::frename(*file as usize, newpath.to_bytes())) as c_int, - Err(_) => -1, + match res { + Ok(()) => Ok(cursor.position() as usize), + Err(_err) => Err(Errno(ENAMETOOLONG)), } } - fn rmdir(path: &CStr) -> c_int { - e(syscall::rmdir(path.to_bytes())) as c_int + fn readlinkat(dirfd: c_int, path: CStr, out: &mut [u8]) -> Result { + let path = str::from_utf8(path.to_bytes()).map_err(|_| Errno(ENOENT))?; + let file = openat2( + dirfd, + path, + 0, + fcntl::O_RDONLY | fcntl::O_SYMLINK | fcntl::O_CLOEXEC, + )?; + Sys::read(*file, out) } - fn sched_yield() -> c_int { - e(syscall::sched_yield()) as c_int + fn rename(oldpath: CStr, newpath: CStr) -> Result<()> { + let newpath = newpath.to_str().map_err(|_| Errno(EINVAL))?; + let newpath = canonicalize(newpath).map_err(|_| Errno(EINVAL))?; + + let file = File::open( + oldpath, + fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_CLOEXEC, + )?; + syscall::frename(*file as usize, newpath)?; + Ok(()) } - fn setpgid(pid: pid_t, pgid: pid_t) -> c_int { - e(syscall::setpgid(pid as usize, pgid as usize)) as c_int + fn renameat(old_dir: c_int, old_path: CStr, new_dir: c_int, new_path: CStr) -> Result<()> { + Sys::renameat2(old_dir, old_path, new_dir, new_path, 0) } - fn setregid(rgid: gid_t, egid: gid_t) -> c_int { - e(syscall::setregid(rgid as usize, egid as usize)) as c_int + fn renameat2( + old_dir: c_int, + old_path: CStr, + new_dir: c_int, + new_path: CStr, + flags: c_uint, + ) -> Result<()> { + const MASK: c_uint = !RENAME_NOREPLACE; + if MASK & flags != 0 { + return Err(Errno(EOPNOTSUPP)); + } + + let new_path = new_path.to_str().map_err(|_| Errno(EINVAL))?; + // Fail if the target exists with RENAME_NOREPLACE. + if flags & RENAME_NOREPLACE != 0 + && let Ok(fd) = + libredox::openat(new_dir, &new_path, fcntl::O_PATH | fcntl::O_CLOEXEC, 0) + .map(FdGuard::new) + { + return Err(Errno(EEXIST)); + } + + let old_path = old_path.to_str().map_err(|_| Errno(EINVAL))?; + // oflags are the same as Sys::rename above. + let source = openat2(old_dir, old_path, 0, fcntl::O_NOFOLLOW | fcntl::O_PATH)?; + + let target = openat2_path(new_dir, new_path, 0)?; + // I'm avoiding Sys::rename to avoid reallocating a CString from a String. + syscall::frename(*source as usize, target) + .map(|_| ()) + .map_err(Into::into) } - fn setreuid(ruid: uid_t, euid: uid_t) -> c_int { - e(syscall::setreuid(ruid as usize, euid as usize)) as c_int + fn rmdir(path: CStr) -> Result<()> { + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let canon = canonicalize(path)?; + redox_rt::sys::unlink(&canon, fcntl::AT_REMOVEDIR as usize)?; + Ok(()) } - fn symlink(path1: &CStr, path2: &CStr) -> c_int { - let mut file = match File::create( + fn sched_yield() -> Result<()> { + syscall::sched_yield()?; + Ok(()) + } + + unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()> { + // TODO + todo_skip!(0, "setgroups({}, {:p}): not implemented", size, list); + Err(Errno(ENOSYS)) + } + + fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> { + redox_rt::sys::posix_setpgid(pid as usize, pgid as usize)?; + Ok(()) + } + + fn setpriority(which: c_int, who: id_t, prio: c_int) -> Result<()> { + let clamped_prio = prio.clamp(-20, 19); + let kernel_prio = (20 + clamped_prio) as u32; + + match redox_rt::sys::posix_setpriority(which, who as u32, kernel_prio) { + Ok(_) => Ok(()), + Err(e) => Err(Errno(e.errno)), + } + } + + fn setsid() -> Result { + Ok(redox_rt::sys::posix_setsid()? as c_int) + } + + fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) -> Result<()> { + redox_rt::sys::posix_setresugid(&Resugid { + ruid: None, + euid: None, + suid: None, + rgid: cvt_uid(rgid)?, + egid: cvt_uid(egid)?, + sgid: cvt_uid(sgid)?, + })?; + Ok(()) + } + + fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) -> Result<()> { + redox_rt::sys::posix_setresugid(&Resugid { + ruid: cvt_uid(ruid)?, + euid: cvt_uid(euid)?, + suid: cvt_uid(suid)?, + rgid: None, + egid: None, + sgid: None, + })?; + Ok(()) + } + + fn symlink(path1: CStr, path2: CStr) -> Result<()> { + Sys::symlinkat(path1, AT_FDCWD, path2) + } + + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()> { + let mut file = File::createat( + fd, path2, fcntl::O_WRONLY | fcntl::O_SYMLINK | fcntl::O_CLOEXEC, 0o777, - ) { - Ok(ok) => ok, - Err(_) => return -1, - }; + )?; - if file.write(path1.to_bytes()).is_err() { - return -1; + file.write(path1.to_bytes()) + .map_err(|err| Errno(err.raw_os_error().unwrap_or(EIO)))?; + + Ok(()) + } + + fn sync() -> Result<()> { + Ok(()) + } + + fn timer_create(clock_id: clockid_t, evp: &sigevent, mut timerid: Out) -> Result<()> { + if evp.sigev_notify == SIGEV_THREAD { + if evp.sigev_notify_function.is_none() { + return Err(Errno(EINVAL)); + } + } else if evp.sigev_notify == SIGEV_SIGNAL { + const n_sig: i32 = NSIG as i32; + const rt_min: i32 = SIGRTMIN as i32; + const rt_max: i32 = SIGRTMIN as i32; + match evp.sigev_signo { + 0..n_sig => {} + rt_min..=rt_max => {} + _ => { + return Err(Errno(EINVAL)); + } + } } - 0 + let path = format!("/scheme/time/{clock_id}"); + let timerfd = FdGuard::open(&path, syscall::O_RDWR)?; + let eventfd = FdGuard::new(Error::demux(unsafe { + event::redox_event_queue_create_v1(0) + })?); + let caller_thread = Self::current_os_tid(); + + let timer_buf = unsafe { + let timer_buf = Self::mmap( + ptr::null_mut(), + size_of::(), + PROT_READ | PROT_WRITE, + MAP_ANONYMOUS, + 0, + 0, + )?; + + let timer_ptr = timer_buf as *mut timer_internal_t; + let timer_st = &mut *timer_ptr; + + timer_st.clockid = clock_id; + timer_st.timerfd = timerfd.take(); + timer_st.eventfd = eventfd.take(); + timer_st.evp = (*evp).clone(); + timer_st.next_wake_time = itimerspec::default(); + timer_st.thread = ptr::null_mut(); + timer_st.caller_thread = caller_thread; + timer_st.process_pid = 0; + timer_buf + }; + + timerid.write(timer_buf); + + Ok(()) + } + + fn timer_delete(timerid: timer_t) -> Result<()> { + unsafe { + let timer_st = &mut *(timerid as *mut timer_internal_t); + let _ = syscall::close(timer_st.timerfd); + let _ = syscall::close(timer_st.eventfd); + if !timer_st.thread.is_null() { + let _ = pthread_cancel(timer_st.thread); + } + Self::munmap(timerid, size_of::())?; + } + + Ok(()) + } + + fn timer_gettime(timerid: timer_t, mut value: Out) -> Result<()> { + let timer_st = unsafe { &mut *(timerid as *mut timer_internal_t) }; + let mut now = timespec::default(); + Self::clock_gettime(timer_st.clockid, Out::from_mut(&mut now))?; + + if timer_st.evp.sigev_notify == SIGEV_NONE { + if timespec::subtract(timer_st.next_wake_time.it_value.clone(), now.clone()).is_none() { + // error here means the timer is disarmed + let _ = timer_update_wake_time(timer_st); + } + } + + value.write(if timer_st.next_wake_time.it_value.is_default() { + // disarmed + itimerspec::default() + } else { + itimerspec { + it_interval: timer_st.next_wake_time.it_interval.clone(), + it_value: timespec::subtract(timer_st.next_wake_time.it_value.clone(), now) + .unwrap_or_default(), + } + }); + + Ok(()) + } + + fn timer_settime( + timerid: timer_t, + flags: c_int, + value: &itimerspec, + ovalue: Option>, + ) -> Result<()> { + let timer_st = unsafe { &mut *(timerid as *mut timer_internal_t) }; + + if let Some(ovalue) = ovalue { + Self::timer_gettime(timerid, ovalue)?; + } + + let mut now = timespec::default(); + Self::clock_gettime(timer_st.clockid, Out::from_mut(&mut now))?; + + //FIXME: make these atomic? + timer_st.next_wake_time = { + let mut val = value.clone(); + if flags & TIMER_ABSTIME == 0 { + val.it_value = timespec::add(now, val.it_value).ok_or(Errno(EINVAL))?; + } + val + }; + + Error::demux(unsafe { + event::redox_event_queue_ctl_v1(timer_st.eventfd, timer_st.timerfd, 1, 0) + })?; + + let buf_to_write = unsafe { + slice::from_raw_parts( + &timer_st.next_wake_time.it_value as *const _ as *const u8, + mem::size_of::(), + ) + }; + + let bytes_written = redox_rt::sys::posix_write(timer_st.timerfd, buf_to_write)?; + + if bytes_written < mem::size_of::() { + return Err(Errno(EIO)); + } + + if timer_st.thread.is_null() { + timer_st.thread = match timer_st.evp.sigev_notify { + SIGEV_THREAD | SIGEV_SIGNAL => { + let mut tid = pthread_t::default(); + let result = unsafe { + pthread_create( + &mut tid as *mut _, + ptr::null(), + timer_routine, + timerid as *mut c_void, + ) + }; + if result != 0 { + return Err(Errno(result)); + } + tid + } + SIGEV_NONE => ptr::null_mut(), + _ => { + return Err(Errno(EINVAL)); + } + }; + } + + Ok(()) } fn umask(mask: mode_t) -> mode_t { - e(syscall::umask(mask as usize)) as mode_t + let new_effective_mask = mask & mode_t::from(MODE_PERM) & !S_ISVTX; + (redox_rt::sys::swap_umask(new_effective_mask as u32) as mode_t) & !S_ISVTX } - fn uname(utsname: *mut utsname) -> c_int { - fn gethostname(name: &mut [u8]) -> io::Result<()> { + fn uname(mut utsname: Out) -> Result<(), Errno> { + fn gethostname(mut name: Out<[u8]>) -> io::Result<()> { if name.is_empty() { return Ok(()); } - let mut file = File::open( - &CString::new("/etc/hostname").unwrap(), - fcntl::O_RDONLY | fcntl::O_CLOEXEC, - )?; + let mut file = File::open(c"/etc/hostname".into(), fcntl::O_RDONLY | fcntl::O_CLOEXEC)?; let mut read = 0; let name_len = name.len(); loop { - match file.read(&mut name[read..name_len - 1])? { + match file.read_out(name.subslice(read, name_len - 1))? { 0 => break, n => read += n, } } - name[read] = 0; + name.index(read).write(0); Ok(()) } + out_project! { + let utsname { + nodename: [c_char; UTSLENGTH], + sysname: [c_char; UTSLENGTH], + release: [c_char; UTSLENGTH], + machine: [c_char; UTSLENGTH], + version: [c_char; UTSLENGTH], + domainname: [c_char; UTSLENGTH], + } = utsname; + } - fn inner(utsname: *mut utsname) -> CoreResult<(), i32> { - match gethostname(unsafe { - slice::from_raw_parts_mut( - (*utsname).nodename.as_mut_ptr() as *mut u8, - (*utsname).nodename.len(), - ) - }) { - Ok(_) => (), - Err(_) => return Err(EIO), - } + match gethostname(nodename.as_slice_mut().cast_slice_to::()) { + Ok(_) => (), + Err(_) => return Err(Errno(EIO)), + } - let file_path = c_str!("sys:uname"); - let mut file = match File::open(file_path, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { - Ok(ok) => ok, - Err(_) => return Err(EIO), - }; - let mut lines = BufReader::new(&mut file).lines(); + let file_path = c"/scheme/sys/uname".into(); + let mut file = match File::open(file_path, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { + Ok(ok) => ok, + Err(_) => return Err(Errno(EIO)), + }; + let mut lines = BufReader::new(&mut file).lines(); - let mut read_line = |dst: &mut [c_char]| { - let line = match lines.next() { - Some(Ok(l)) => match CString::new(l) { - Ok(l) => l, - Err(_) => return Err(EIO), - }, - None | Some(Err(_)) => return Err(EIO), - }; - - let line_slice: &[c_char] = unsafe { mem::transmute(line.as_bytes_with_nul()) }; - - if line_slice.len() <= UTSLENGTH { - dst[..line_slice.len()].copy_from_slice(line_slice); - Ok(()) - } else { - Err(EIO) - } + let mut read_line = |mut dst: Out<[u8]>| { + // TODO: set nul byte without allocating CString + let line = match lines.next() { + Some(Ok(l)) => CString::new(l).map_err(|_| Errno(EIO))?, + None | Some(Err(_)) => return Err(Errno(EIO)), }; - unsafe { - read_line(&mut (*utsname).sysname)?; - read_line(&mut (*utsname).release)?; - read_line(&mut (*utsname).machine)?; - - // Version is not provided - ptr::write_bytes((*utsname).version.as_mut_ptr(), 0, UTSLENGTH); - - // Redox doesn't provide domainname in sys:uname - //read_line(&mut (*utsname).domainname)?; - ptr::write_bytes((*utsname).domainname.as_mut_ptr(), 0, UTSLENGTH); + let line_slice: &[u8] = line.as_bytes_with_nul(); + if line_slice.len() > UTSLENGTH { + return Err(Errno(EIO)); } + dst.copy_common_length_from_slice(line_slice); Ok(()) - } + }; - match inner(utsname) { - Ok(()) => 0, - Err(err) => unsafe { - errno = err; - -1 - }, - } + // The file format is currently as follows: + // \n e.g. "Redox" + // \n e.g. "0.9.0" + // \n e.g. "x86_64" + // \n e.g. "yyyy-mm-ddThh:mm:ssZ" + + // A future file format might add the domainname. + + // nodename is handled above with /etc/hostname, and the domainname is + // currently zeroed out. + + read_line(sysname.as_slice_mut().cast_slice_to::())?; + read_line(release.as_slice_mut().cast_slice_to::())?; + read_line(machine.as_slice_mut().cast_slice_to::())?; + read_line(version.as_slice_mut().cast_slice_to::())?; + + // Redox doesn't provide domainname in uname scheme + //read_line(domainname.as_slice_mut().cast_slice_to::())?; + domainname.as_slice_mut().zero(); + + Ok(()) } - fn unlink(path: &CStr) -> c_int { - e(syscall::unlink(path.to_bytes())) as c_int + fn unlink(path: CStr) -> Result<()> { + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let canon = canonicalize(path)?; + redox_rt::sys::unlink(&canon, 0)?; + Ok(()) } - fn waitpid(mut pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t { - if pid == !0 { - pid = 0; + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()> { + if (flags & !AT_REMOVEDIR) != 0 { + return Err(Errno(EINVAL)); } - let mut res = None; + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let path = openat2_path(fd, path, 0)?; + let canon = canonicalize(&path)?; + redox_rt::sys::unlink(&canon, flags.try_into().map_err(|_| Errno(EINVAL))?)?; + Ok(()) + } + + fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result { + let res = None; let mut status = 0; + let options = usize::try_from(options) + .ok() + .and_then(WaitFlags::from_bits) + .ok_or(Errno(EINVAL))?; + let inner = |status: &mut usize, flags| { - syscall::waitpid( - pid as usize, - status, - syscall::WaitFlags::from_bits(flags as usize) - .expect("waitpid: invalid bit pattern"), - ) + redox_rt::sys::sys_waitpid(WaitpidTarget::from_posix_arg(pid as isize), status, flags) }; // First, allow ptrace to handle waitpid // TODO: Handle special PIDs here (such as -1) let state = ptrace::init_state(); - let mut sessions = state.sessions.lock(); - if let Ok(session) = ptrace::get_session(&mut sessions, pid) { - if options & sys_wait::WNOHANG != sys_wait::WNOHANG { - let mut _event = PtraceEvent::default(); - let _ = (&mut &session.tracer).read(&mut _event); + // TODO: Fix ptrace deadlock seen during openposixtestsuite signals tests + // let mut sessions = state.sessions.lock(); + // if let Ok(session) = ptrace::get_session(&mut sessions, pid) { + // if !options.contains(WaitFlags::WNOHANG) { + // let mut _event = PtraceEvent::default(); + // let _ = (&mut &session.tracer).read(&mut _event); - res = Some(e(inner( - &mut status, - options | sys_wait::WNOHANG | sys_wait::WUNTRACED, - ))); - if res == Some(0) { - // WNOHANG, just pretend ptrace SIGSTOP:ped this - status = (syscall::SIGSTOP << 8) | 0x7f; - assert!(syscall::wifstopped(status)); - assert_eq!(syscall::wstopsig(status), syscall::SIGSTOP); - res = Some(pid as usize); - } - } - } + // res = Some(inner( + // &mut status, + // options | WaitFlags::WNOHANG | WaitFlags::WUNTRACED, + // )); + // if res == Some(Ok(0)) { + // // WNOHANG, just pretend ptrace SIGSTOP:ped this + // status = (redox_rt::protocol::SIGSTOP << 8) | 0x7f; + // assert!(wifstopped(status)); + // assert_eq!(wstopsig(status), redox_rt::protocol::SIGSTOP); + // res = Some(Ok(pid as usize)); + // } + // } + // } // If ptrace didn't impact this waitpid, proceed *almost* as // normal: We still need to add WUNTRACED, but we only return // it if (and only if) a ptrace traceme was activated during // the wait. - let res = res.unwrap_or_else(|| loop { - let res = e(inner(&mut status, options | sys_wait::WUNTRACED)); + let res = res.unwrap_or_else(|| { + loop { + let res = inner(&mut status, options | WaitFlags::WUNTRACED); - // TODO: Also handle special PIDs here - if !syscall::wifstopped(res) || ptrace::is_traceme(pid) { - break res; + // TODO: Also handle special PIDs here + if !wifstopped(status) + || options.contains(WaitFlags::WUNTRACED) + || ptrace::is_traceme(pid) + { + break res; + } } }); // If stat_loc is non-null, set that and the return - unsafe { - if !stat_loc.is_null() { - *stat_loc = status as c_int; - } + if let Some(mut stat_loc) = stat_loc { + stat_loc.write(status as c_int); } - res as pid_t + + Ok(res? as pid_t) } - fn write(fd: c_int, buf: &[u8]) -> ssize_t { - e(syscall::write(fd as usize, buf)) as ssize_t + fn write(fd: c_int, buf: &[u8]) -> Result { + let fd = usize::try_from(fd).map_err(|_| Errno(EBADFD))?; + Ok(redox_rt::sys::posix_write(fd, buf)?) + } + fn pwrite(fd: c_int, buf: &[u8], offset: off_t) -> Result { + unsafe { + Ok(syscall::syscall5( + syscall::SYS_WRITE2, + fd as usize, + buf.as_ptr() as usize, + buf.len(), + offset as usize, + !0, + )?) + } } fn verify() -> bool { - // GETPID on Redox is 20, which is WRITEV on Linux - e(unsafe { syscall::syscall5(syscall::number::SYS_GETPID, !0, !0, !0, !0, !0) }) != !0 + // YIELD on Redox is 20, which is SYS_ARCH_PRCTL on Linux + (unsafe { syscall::syscall5(syscall::number::SYS_YIELD, !0, !0, !0, !0, !0) }).is_ok() + } + + unsafe fn exit_thread(stack_base: *mut (), stack_size: usize) -> ! { + unsafe { redox_rt::thread::exit_this_thread(stack_base, stack_size) } + } +} + +impl Sys { + fn relative_to_absolute_foffset( + fd: usize, + whence: c_short, + start: off_t, + len: off_t, + ) -> Result<(off_t, off_t)> { + // let file_off = Self::lseek(fd, 0, SEEK_SET)?; + match whence as i32 { + SEEK_SET => { + let (start, len) = if len < 0 { + (start + len, -len) + } else { + (start, len) + }; + + if start < 0 { + return Err(Errno(EINVAL)); + } + + assert!(len >= 0); + Ok((start, len)) + } + // FIXME: andypython: SEEK_CUR, SEEK_END + c => { + log::warn!( + "Sys::relative_to_absolute_foffset: whence={whence} not yet implemented" + ); + Ok((0, 0)) + } + } } } diff --git a/src/platform/redox/path.rs b/src/platform/redox/path.rs new file mode 100644 index 0000000000..4d4ae48358 --- /dev/null +++ b/src/platform/redox/path.rs @@ -0,0 +1,522 @@ +use alloc::{ + boxed::Box, + collections::btree_set::BTreeSet, + ffi::CString, + string::{String, ToString}, + vec::Vec, +}; +use core::{ffi::c_int, str}; +use redox_rt::{proc::FdGuardUpper, signal::tmp_disable_signals}; +use syscall::{data::Stat, error::*, flag::*}; + +use super::{FdGuard, Pal, Sys, libcscheme}; +use crate::{ + error::Errno, + fs::File, + header::{fcntl, limits, sys_file}, + out::Out, + sync::rwlock::{ReadGuard, RwLock}, +}; + +pub use redox_path::{RedoxPath, canonicalize_using_cwd}; + +pub fn normalize_path<'a>(path: &'a str) -> Option<(bool, String)> { + let absolute = match RedoxPath::from_absolute(path) { + Some(absolute) => absolute, + None => return Some((true, partially_canonical(path)?)), + }; + let canonical = absolute.canonical()?; + Some((false, canonical.to_string())) +} + +pub fn normalize_scheme_rooted_path<'a>(path: &'a str) -> Option<(bool, String)> { + let absolute = match RedoxPath::from_absolute(path) { + Some(absolute) => absolute, + None => return Some((true, partially_canonical(path)?)), + }; + let canonical = absolute.canonical()?; + Some((false, scheme_rooted_path(&canonical.to_string()).ok()?)) +} + +fn partially_canonical(path: &str) -> Option { + let mut stack = Vec::new(); + let mut paths_to_check = BTreeSet::new(); + let mut up_counts = 0; + + for part in path.split('/') { + if part.is_empty() || part == "." { + continue; + } else if part == ".." { + if let Some(part) = stack.pop() { + paths_to_check.insert( + core::iter::repeat("..") + .take(up_counts) + .chain(stack.clone()) + .chain(core::iter::once(part)) + .collect::>() + .join("/"), + ); + } else { + up_counts += 1; + } + } else { + stack.push(part); + } + } + + for path in paths_to_check { + let _ = current_dir() + .ok()? + .as_ref() + .unwrap() + .fd + .openat(&path, O_STAT, 0) + .ok()?; + } + + Some( + core::iter::repeat("..") + .take(up_counts) + .chain(stack) + .collect::>() + .join("/"), + ) +} + +// POSIX states chdir is both thread-safe and signal-safe. Thus we need to synchronize access to CWD, but at the +// same time forbid signal handlers from running in the meantime, to avoid reentrant deadlock. +pub fn chdir(path: &str) -> Result<()> { + let _siglock = tmp_disable_signals(); + let mut cwd_guard = CWD.write(); + let (is_relative, path) = normalize_path(path).ok_or(Error::new(ENOENT))?; + if is_relative { + let fd = cwd_guard + .as_ref() + .unwrap() + .fd + .openat(&path, O_STAT, 0)? + .to_upper() + .unwrap(); + let mut stat = Stat::default(); + if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { + return Err(Error::new(ENOTDIR)); + } + + let canon = canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &path) + .ok_or(Error::new(ENOENT))?; + *cwd_guard = Some(Cwd { + path: canon.into_boxed_str(), + fd, + }); + } else { + let canon_with_scheme = scheme_rooted_path(&path)?; + + let fd = FdGuard::open(&canon_with_scheme, O_STAT)? + .to_upper() + .unwrap(); + let mut stat = Stat::default(); + if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { + return Err(Error::new(ENOTDIR)); + } + + *cwd_guard = Some(Cwd { + path: path.into_boxed_str(), + fd, + }); + } + + Ok(()) +} + +pub fn fchdir(fd: c_int) -> Result<()> { + let mut buf = [0_u8; limits::PATH_MAX]; + let res = Sys::fpath(fd, &mut buf)?; + + let path = core::str::from_utf8(&buf[..res]) + .map_err(|_| Errno(EINVAL))? + .to_string(); + let fd = FdGuard::new(syscall::fcntl( + fd as usize, + syscall::F_DUPFD, + syscall::UPPER_FDTBL_TAG, + )?) + .to_upper() + .unwrap(); + set_cwd_manual(path.into_boxed_str(), fd); + Ok(()) +} + +// getcwd is similarly both thread-safe and signal-safe. +pub fn getcwd(mut buf: Out<[u8]>) -> Result { + let _siglock = tmp_disable_signals(); + let guard = CWD.read(); + let cwd = guard.as_ref().ok_or(Error::new(ENOENT))?; + let path_bytes = cwd.path.as_bytes(); + + let [mut before, mut after] = buf + .split_at_checked(path_bytes.len()) + .ok_or(Error::new(ERANGE))?; + + before.copy_from_slice(path_bytes); + after.zero(); + + Ok(path_bytes.len()) +} + +// Get Cwd object +pub fn current_dir() -> Result>> { + let _siglock = tmp_disable_signals(); + let guard = CWD.read(); + + if guard.as_ref().is_none() { + return Err(Error::new(ENOENT)); + } + + Ok(guard) +} + +fn scheme_rooted_path(path: &str) -> Result { + let standard_scheme = path == "/scheme" || path.starts_with("/scheme/"); + let legacy_scheme = path + .split("/") + .next() + .map(|c| c.contains(":")) + .unwrap_or(false); + + Ok(if standard_scheme || legacy_scheme { + path.to_string() + } else { + let mut result = format!("/scheme/file{}", path); + + // Trim trailing / to keep path canonical. + if result.as_bytes().last() == Some(&b'/') { + result.pop(); + } + + result + }) +} + +// TODO: How much of this logic should be in redox-path? +fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result { + let path = canonicalize_using_cwd(cwd, path).ok_or(Error::new(ENOENT))?; + scheme_rooted_path(&path) +} + +pub fn canonicalize(path: &str) -> Result { + let _siglock = tmp_disable_signals(); + let cwd_guard = CWD.read(); + canonicalize_with_cwd_internal(cwd_guard.as_ref().map(|c| c.path.as_ref()), path) +} + +pub struct Cwd { + pub path: Box, + pub fd: FdGuardUpper, +} + +// TODO: arraystring? +static CWD: RwLock> = RwLock::new(None); + +pub fn set_cwd_manual(path: Box, fd: FdGuardUpper) { + let _siglock = tmp_disable_signals(); + *CWD.write() = Some(Cwd { path, fd }); +} + +pub fn clone_cwd() -> Option> { + let _siglock = tmp_disable_signals(); + CWD.read().as_ref().map(|cwd| cwd.path.clone()) +} + +fn open_absolute(path: &str, flags: usize) -> Result { + if path.starts_with(libcscheme::LIBC_SCHEME) { + libcscheme::open(path, flags) + } else { + redox_rt::sys::open(path, flags) + } +} + +fn link_target(fd: FdGuard) -> Result { + let mut resolve_buf = [0_u8; limits::PATH_MAX]; + let count = fd.read(&mut resolve_buf)?; + if count == resolve_buf.len() { + // TODO: make resolve_buf PATH_MAX + 1 bytes? + return Err(Error::new(ENAMETOOLONG)); + } + // If the symbolic link path is non-UTF8, it cannot be opened, and is thus + // considered a "dangling symbolic link". + core::str::from_utf8(&resolve_buf[..count]) + .map_err(|_| Error::new(ENOENT)) + .map(|s| s.to_string()) +} + +fn read_link_content(path: &str, is_relative: bool) -> Result { + let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; + + let fd = if is_relative { + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(path, resolve_flags, 0)? + } else { + FdGuard::open(path, resolve_flags)? + }; + + link_target(fd) +} + +fn calc_next_abs_path(current_abs: &str, link_target: &str) -> Result { + let parent = get_parent_path(current_abs).ok_or(Error::new(ENOENT))?; + + canonicalize_using_cwd(Some(&parent), link_target).ok_or(Error::new(ENOENT)) +} + +fn resolve_sym_links(mut current_path_string: String, flags: usize) -> Result { + // TODO: SYMLOOP_MAX + const MAX_LEVEL: usize = 64; + // Sym reolve loop + for _ in 0..(MAX_LEVEL - 1) { + match open_absolute(¤t_path_string, flags) { + Ok(fd) => return Ok(fd), + Err(e) if e == Error::new(EXDEV) => { + let link_target = read_link_content(¤t_path_string, false)?; + + current_path_string = calc_next_abs_path(¤t_path_string, &link_target)?; + } + Err(e) => return Err(e), + } + } + + Err(Error::new(ELOOP)) +} + +// TODO: Move to redox-rt, or maybe part of it? +pub fn openat(dirfd: c_int, path: &str, flags: usize) -> Result { + if path.is_empty() && flags as i32 & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH { + return Err(Error::new(ENOENT)); + } + + let (is_relative, path_to_open) = normalize_path(path).ok_or(Error::new(ENOENT))?; + + if !is_relative { + return open(path, flags); + } + + let _siglock = tmp_disable_signals(); + let fcntl_flags = flags & syscall::O_FCNTL_MASK; + // First try + let initial_res = if dirfd == fcntl::AT_FDCWD { + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(&path_to_open, flags, fcntl_flags) + .map(|fd: FdGuard| fd.take()) + } else { + redox_rt::sys::openat(dirfd as usize, &path_to_open, flags, fcntl_flags) + }; + + let current_path_string = match initial_res { + Ok(fd) => return Ok(fd), + Err(e) if e == Error::new(EXDEV) => { + let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; + + let fd = if dirfd == fcntl::AT_FDCWD { + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(path, resolve_flags, 0)? + } else { + redox_rt::sys::openat(dirfd as usize, &path_to_open, flags, fcntl_flags) + .map(FdGuard::new)? + }; + + let link_target = link_target(fd)?; + + let current_abs = openat2_path(dirfd, path, 0)?; + calc_next_abs_path(¤t_abs, &link_target)? + } + Err(e) => return Err(e), + }; + + resolve_sym_links(current_path_string, flags) +} + +// TODO: Move to redox-rt, or maybe part of it? +pub fn open(path: &str, flags: usize) -> Result { + let _siglock = tmp_disable_signals(); + if path == "" { + return Err(Error::new(ENOENT)); + } + + let (is_relative, canon) = normalize_scheme_rooted_path(path).ok_or(Error::new(ENOENT))?; + + // First try + let initial_res = if is_relative { + let fcntl_flags = flags & syscall::O_FCNTL_MASK; + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(&canon, flags, fcntl_flags) + .map(|fd: FdGuard| fd.take()) + } else { + open_absolute(&canon, flags) + }; + + let current_path_string = match initial_res { + Ok(fd) => return Ok(fd), + Err(e) if e == Error::new(EXDEV) => { + let link_target = read_link_content(&canon, is_relative)?; + + let cwd_guard = CWD.read(); + let current_abs = + canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &canon) + .ok_or(Error::new(ENOENT))?; + + calc_next_abs_path(¤t_abs, &link_target)? + } + Err(e) => return Err(e), + }; + + // Sym reolve loop + resolve_sym_links(current_path_string, flags) +} + +fn get_parent_path(path: &str) -> Option { + let path = path.strip_suffix('/').unwrap_or(path); + fn parent_opt(path: &str) -> Option<&str> { + path.rfind('/').map(|index| { + if index == 0 { + // Path is something like "/file.txt" or the root "/". + // The parent is the root directory "/". + "/" + } else { + // Path is something like "/a/b/c.txt". + // Take the slice from the beginning up to the last '/'. + &path[..index] + } + }) + } + match RedoxPath::from_absolute(path) { + Some(path) => { + let (scheme, reference) = path.as_parts()?; + let parent_ref = parent_opt(reference.as_ref()).unwrap_or(""); + Some(format!("/scheme/{}/{}", scheme.as_ref(), parent_ref)) + } + None => parent_opt(path).map(String::from), + } +} + +pub fn dir_path_and_fd_path(socket_path: &str) -> Result<(String, String)> { + let _siglock = tmp_disable_signals(); + let cwd_guard = CWD.read(); + let cwd_path = cwd_guard.as_ref().map(|c| c.path.as_ref()); + + let full_path = canonicalize_with_cwd_internal(cwd_path, socket_path)?; + + let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; + let (_, ref_path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; + if ref_path.as_ref().is_empty() { + return Err(Error::new(EINVAL)); + } + if redox_path.is_default_scheme() { + let dir_to_open = get_parent_path(&full_path).ok_or(Error::new(EINVAL))?; + Ok((dir_to_open, ref_path.as_ref().to_string())) + } else { + let full_path = canonicalize_with_cwd_internal(cwd_path, ref_path.as_ref())?; + let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; + let (_, path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; + let dir_to_open = get_parent_path(&full_path).ok_or(Error::new(EINVAL))?; + Ok((dir_to_open, path.as_ref().to_string())) + } +} + +pub struct FileLock(c_int); + +impl FileLock { + pub fn lock(fd: c_int, op: c_int) -> Result { + if op & sys_file::LOCK_SH | sys_file::LOCK_EX == 0 { + return Err(Error::new(EINVAL)); + } + + Sys::flock(fd, op)?; + Ok(Self(fd)) + } + + pub fn unlock(self) -> Result<()> { + Sys::flock(self.0, sys_file::LOCK_UN).map_err(Into::into) + } +} + +impl Drop for FileLock { + fn drop(&mut self) { + let fd = self.0; + self.0 = -1; + let _ = Sys::flock(self.0, sys_file::LOCK_UN); + } +} +/// Resolve `path` under `dirfd`. +/// +/// See [`openat2`] for more information. +pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result { + // Ideally, the function calling this fn would check AT_EMPTY_PATH and just call fstat or + // whatever with the fd. + if path.is_empty() && at_flags & fcntl::AT_EMPTY_PATH != fcntl::AT_EMPTY_PATH { + return Err(Errno(ENOENT)); + } + + // Absolute paths are passed without processing unless RESOLVE_BENEATH is used. + // canonicalize_using_cwd checks that path is absolute so a third branch that does so here + // isn't needed. + if dirfd == fcntl::AT_FDCWD { + // The special constant AT_FDCWD indicates that we should use the cwd. + let cwd_guard = CWD.read(); + canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), path) + .ok_or(Errno(EBADF)) + } else { + let mut buf = [0; limits::PATH_MAX]; + let len = Sys::fpath(dirfd, &mut buf)?; + // SAFETY: fpath checks then copies valid UTF8. + let dir = unsafe { str::from_utf8_unchecked(&buf[..len]) }; + + canonicalize_using_cwd(Some(dir), path).ok_or(Errno(EBADF)) + } +} + +/// Canonicalize and open `path` with respect to `dirfd`. +/// +/// This unexported openat2 is similar to the Linux syscall but with a different interface. The +/// naming is mostly for convenience - it's not a drop in replacement for openat2. +/// +/// # Arguments +/// * `dirfd` is a directory descriptor to which `path` is resolved. +/// * `path` is a relative or absolute path. Relative paths are resolved in relation to `dirfd` +/// while absolute paths skip `dirfd`. +/// * `at_flags` constrains how `path` is resolved. +/// * `oflags` are flags that are passed to open. +/// +/// # Constants +/// `at_flags`: +/// * AT_EMPTY_PATH returns the path at `dirfd` itself if `path` is empty. If `path` is not +/// empty, it's resolved w.r.t `dirfd` like normal. +/// +/// `dirfd`: +/// `AT_FDCWD` is a special constant for `dirfd` that resolves `path` under the current working +/// directory. +pub(super) fn openat2( + dirfd: c_int, + path: &str, + at_flags: c_int, + oflags: c_int, +) -> Result { + // Translate at flags into open flags; openat will do this on its own most likely. + let oflags = if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW { + fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_SYMLINK | oflags + } else { + fcntl::O_CLOEXEC | oflags + }; + let c_path = CString::new(path).map_err(|_| Errno(EINVAL))?; + File::openat(dirfd, c_path.as_c_str().into(), oflags) +} diff --git a/src/platform/redox/ptrace.rs b/src/platform/redox/ptrace.rs index 508d98fe44..eef280d46b 100644 --- a/src/platform/redox/ptrace.rs +++ b/src/platform/redox/ptrace.rs @@ -4,21 +4,33 @@ //! we are NOT going to bend our API for the sake of //! compatibility. So, this module will be a hellhole. -use super::super::{errno, types::*, Pal, PalPtrace, PalSignal, Sys}; -#[cfg(target_arch = "aarch64")] -use crate::header::arch_aarch64_user::user_regs_struct; +use super::super::{ERRNO, PalPtrace, Sys, types::*}; +#[cfg(target_arch = "x86_64")] +use super::super::{Pal, PalSignal}; #[cfg(target_arch = "x86_64")] use crate::header::arch_x64_user::user_regs_struct; use crate::{ - c_str::CString, + c_str::{CStr, CString}, + error::Errno, fs::File, - header::{errno as errnoh, fcntl, signal, sys_ptrace}, - io::{self, prelude::*}, - sync::{Mutex, Once}, + header::{ + errno::{self as errnoh, EIO}, + fcntl, + }, + io, + raw_cell::RawCell, + sync::Mutex, +}; +#[cfg(target_arch = "x86_64")] +use crate::{ + header::{signal, sys_ptrace}, + io::prelude::*, }; -use alloc::collections::{btree_map::Entry, BTreeMap}; +use alloc::collections::{BTreeMap, btree_map::Entry}; +#[cfg(target_arch = "x86_64")] use core::mem; +#[cfg(target_arch = "x86_64")] use syscall; pub struct Session { @@ -39,14 +51,26 @@ impl State { } } -static STATE: Once = Once::new(); +#[thread_local] +static STATE: RawCell> = RawCell::new(None); pub fn init_state() -> &'static State { - STATE.call_once(|| State::new()) + // Safe due to STATE being thread_local (TODO: is it though?) + unsafe { + if STATE.unsafe_ref().is_none() { + STATE.unsafe_set(Some(State::new())); + } + let state_ptr = STATE.unsafe_ref().as_ref().unwrap() as *const State; + &*state_ptr + } } pub fn is_traceme(pid: pid_t) -> bool { + // Skip special PIDs (<=0) + if pid <= 0 { + return false; + } File::open( - &CString::new(format!("chan:ptrace-relibc/{}/traceme", pid)).unwrap(), + CStr::borrow(&CString::new(format!("/scheme/chan/ptrace-relibc/{}/traceme", pid)).unwrap()), fcntl::O_PATH, ) .is_ok() @@ -63,26 +87,28 @@ pub fn get_session( Ok(entry.insert(Session { first: true, tracer: File::open( - &CString::new(format!("proc:{}/trace", pid)).unwrap(), + CStr::borrow(&CString::new(format!("/scheme/proc/{}/trace", pid)).unwrap()), NEW_FLAGS, )?, mem: File::open( - &CString::new(format!("proc:{}/mem", pid)).unwrap(), + CStr::borrow(&CString::new(format!("/scheme/proc/{}/mem", pid)).unwrap()), NEW_FLAGS, )?, regs: File::open( - &CString::new(format!("proc:{}/regs/int", pid)).unwrap(), + CStr::borrow( + &CString::new(format!("/scheme/proc/{}/regs/int", pid)).unwrap(), + ), NEW_FLAGS, )?, fpregs: File::open( - &CString::new(format!("proc:{}/regs/float", pid)).unwrap(), + CStr::borrow( + &CString::new(format!("/scheme/proc/{}/regs/float", pid)).unwrap(), + ), NEW_FLAGS, )?, })) } else { - unsafe { - errno = errnoh::ESRCH; - } + ERRNO.set(errnoh::ESRCH); Err(io::last_os_error()) } } @@ -90,7 +116,30 @@ pub fn get_session( } } -fn inner_ptrace( +#[cfg(target_arch = "aarch64")] +unsafe fn inner_ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, +) -> io::Result { + //TODO: aarch64 + unimplemented!("inner_ptrace not implemented on aarch64"); +} + +#[cfg(target_arch = "x86")] +unsafe fn inner_ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, +) -> io::Result { + //TODO: x86 + unimplemented!("inner_ptrace not implemented on x86"); +} + +#[cfg(target_arch = "x86_64")] +unsafe fn inner_ptrace( request: c_int, pid: pid_t, addr: *mut c_void, @@ -102,7 +151,9 @@ fn inner_ptrace( // Mark this child as traced, parent will check for this marker file let pid = Sys::getpid(); mem::forget(File::open( - &CString::new(format!("chan:ptrace-relibc/{}/traceme", pid)).unwrap(), + CStr::borrow( + &CString::new(format!("/scheme/chan/ptrace-relibc/{}/traceme", pid)).unwrap(), + ), fcntl::O_CREAT | fcntl::O_PATH | fcntl::O_EXCL, )?); return Ok(0); @@ -117,7 +168,7 @@ fn inner_ptrace( | sys_ptrace::PTRACE_SYSCALL | sys_ptrace::PTRACE_SYSEMU | sys_ptrace::PTRACE_SYSEMU_SINGLESTEP => { - Sys::kill(pid, signal::SIGCONT as _); + if let Ok(()) = Sys::kill(pid, signal::SIGCONT as _) {}; // TODO handle error // TODO: Translate errors let syscall = syscall::PTRACE_STOP_PRE_SYSCALL | syscall::PTRACE_STOP_POST_SYSCALL; @@ -171,8 +222,8 @@ fn inner_ptrace( gs_base: 0, // gs_base: redox_regs.gs_base as _, ds: 0, // ds: redox_regs.ds as _, es: 0, // es: redox_regs.es as _, - fs: redox_regs.fs as _, - gs: 0, // gs: redox_regs.gs as _, + fs: 0, // fs: redox_regs.fs as _, + gs: 0, // gs: redox_regs.gs as _, }; Ok(0) } @@ -204,7 +255,7 @@ fn inner_ptrace( // gs_base: c_regs.gs_base as _, // ds: c_regs.ds as _, // es: c_regs.es as _, - fs: c_regs.fs as _, + // fs: c_regs.fs as _, // gs: c_regs.gs as _, }; (&mut &session.regs).write(&redox_regs)?; @@ -214,8 +265,26 @@ fn inner_ptrace( } } +#[cfg(target_arch = "riscv64")] +fn inner_ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, +) -> io::Result { + //TODO: riscv64 + unimplemented!("inner_ptrace not implemented on riscv64"); +} + impl PalPtrace for Sys { - fn ptrace(request: c_int, pid: pid_t, addr: *mut c_void, data: *mut c_void) -> c_int { - inner_ptrace(request, pid, addr, data).unwrap_or(-1) + #[allow(unused_unsafe)] // keeping inner unsafe fails x86_64, removing fails riscv cross-build + unsafe fn ptrace( + request: c_int, + pid: pid_t, + addr: *mut c_void, + data: *mut c_void, + ) -> Result { + unsafe { inner_ptrace(request, pid, addr, data) } + .map_err(|err| Errno(err.raw_os_error().unwrap_or(EIO))) } } diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs index 6032a37fcf..71ac274f01 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -1,155 +1,362 @@ -use core::mem; -use syscall; - use super::{ - super::{types::*, Pal, PalSignal}, - e, Sys, + super::{Pal, PalSignal, types::*}, + Sys, }; +#[allow(deprecated)] +use crate::header::sys_time::{ITIMER_REAL, itimerval}; use crate::{ + error::{Errno, Result}, header::{ - errno::EINVAL, - signal::{sigaction, sigset_t, stack_t}, - sys_time::{itimerval, ITIMER_REAL}, + bits_sigset_t::sigset_t, + bits_timespec::timespec, + errno::{EINVAL, ENOSYS}, + signal::{ + SIG_BLOCK, SIG_DFL, SIG_IGN, SIG_SETMASK, SIG_UNBLOCK, SIGALRM, SIGEV_SIGNAL, + SS_DISABLE, SS_ONSTACK, sigaction, sigevent, siginfo_t, sigval, stack_t, ucontext_t, + }, + time::{itimerspec, timer_internal_t}, }, - platform::errno, + out::Out, + sync::Mutex, +}; +use core::mem::offset_of; +use redox_protocols::protocol::ProcKillTarget; +use redox_rt::signal::{ + PosixStackt, SigStack, Sigaction, SigactionFlags, SigactionKind, Sigaltstack, SignalHandler, +}; + +/// Wrapper for timer_t that implements Send (the timer_t pointer is a process- +/// wide mmap'd allocation that outlives any single thread). +struct AlarmTimer(timer_t); +// SAFETY: The timer_t pointer refers to an mmap'd timer_internal_t that is +// only accessed under the ALARM_TIMER mutex lock. +unsafe impl Send for AlarmTimer {} + +/// Process-global singleton timer used by alarm(). Protected by a mutex to +/// ensure only one alarm is active at a time (POSIX requirement). +static ALARM_TIMER: Mutex> = Mutex::new(None); + +const _: () = { + #[track_caller] + const fn assert_eq(a: usize, b: usize) { + if a != b { + panic!("compile-time struct verification failed"); + } + } + assert_eq(offset_of!(ucontext_t, uc_link), offset_of!(SigStack, link)); + assert_eq( + offset_of!(ucontext_t, uc_stack), + offset_of!(SigStack, old_stack), + ); + assert_eq( + offset_of!(ucontext_t, uc_sigmask), + offset_of!(SigStack, old_mask), + ); + assert_eq( + offset_of!(ucontext_t, uc_mcontext), + offset_of!(SigStack, regs), + ); }; impl PalSignal for Sys { - fn getitimer(which: c_int, out: *mut itimerval) -> c_int { + #[allow(deprecated)] + fn getitimer(which: c_int, out: &mut itimerval) -> Result<()> { let path = match which { - ITIMER_REAL => "itimer:1", - _ => unsafe { - errno = EINVAL; - return -1; - }, + ITIMER_REAL => "/scheme/itimer/1", + _ => return Err(Errno(EINVAL)), }; + // TODO: implement setitimer + // let fd = FdGuard::new(redox_rt::sys::open(path, syscall::O_RDONLY | syscall::O_CLOEXEC)?); + // let count = syscall::read(*fd, &mut spec)?; - let fd = e(syscall::open(path, syscall::O_RDONLY | syscall::O_CLOEXEC)); - if fd == !0 { - return -1; - } + let spec = syscall::ITimerSpec::default(); + out.it_interval.tv_sec = spec.it_interval.tv_sec as time_t; + out.it_interval.tv_usec = spec.it_interval.tv_nsec / 1000; + out.it_value.tv_sec = spec.it_value.tv_sec as time_t; + out.it_value.tv_usec = spec.it_value.tv_nsec / 1000; - let mut spec = syscall::ITimerSpec::default(); - let count = e(syscall::read(fd, &mut spec)); - - let _ = syscall::close(fd); - - if count == !0 { - return -1; - } - - unsafe { - (*out).it_interval.tv_sec = spec.it_interval.tv_sec; - (*out).it_interval.tv_usec = spec.it_interval.tv_nsec / 1000; - (*out).it_value.tv_sec = spec.it_value.tv_sec; - (*out).it_value.tv_usec = spec.it_value.tv_nsec / 1000; - } - - 0 + Ok(()) } - fn kill(pid: pid_t, sig: c_int) -> c_int { - e(syscall::kill(pid as usize, sig as usize)) as c_int + fn kill(pid: pid_t, sig: c_int) -> Result<()> { + redox_rt::sys::posix_kill(ProcKillTarget::from_raw(pid as usize), sig as usize)?; + Ok(()) + } + fn sigqueue(pid: pid_t, sig: c_int, val: sigval) -> Result<()> { + Ok(redox_rt::sys::posix_sigqueue( + pid as usize, + sig as usize, + unsafe { val.sival_ptr } as usize, + )?) } - fn killpg(pgrp: pid_t, sig: c_int) -> c_int { - e(syscall::kill(-(pgrp as isize) as usize, sig as usize)) as c_int - } - - fn raise(sig: c_int) -> c_int { - Self::kill(Self::getpid(), sig) - } - - fn setitimer(which: c_int, new: *const itimerval, old: *mut itimerval) -> c_int { - let path = match which { - ITIMER_REAL => "itimer:1", - _ => unsafe { - errno = EINVAL; - return -1; - }, - }; - - let fd = e(syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)); - if fd == !0 { - return -1; + fn killpg(pgrp: pid_t, sig: c_int) -> Result<()> { + if pgrp == 1 { + return Err(Errno(EINVAL)); } + Self::kill(-pgrp, sig) + } - let mut spec = syscall::ITimerSpec::default(); + fn raise(sig: c_int) -> Result<()> { + // TODO: Bypass kernel? + unsafe { Self::rlct_kill(Self::current_os_tid(), sig as _) } + } - let mut count = e(syscall::read(fd, &mut spec)); + #[allow(deprecated)] + fn setitimer(which: c_int, _new: &itimerval, old: Option<&mut itimerval>) -> Result<()> { + // TODO: setitimer is no longer part of POSIX and should not be implemented in Redox + // Change the platform-independent implementation to use POSIX timers. + // For Redox, the timer should probably use "/scheme/time" + todo_skip!(0, "setitimer not implemented"); + Err(Errno(ENOSYS)) + } - if count != !0 { - unsafe { - if !old.is_null() { - (*old).it_interval.tv_sec = spec.it_interval.tv_sec; - (*old).it_interval.tv_usec = spec.it_interval.tv_nsec / 1000; - (*old).it_value.tv_sec = spec.it_value.tv_sec; - (*old).it_value.tv_usec = spec.it_value.tv_nsec / 1000; + fn sigaction( + sig: c_int, + c_act: Option<&sigaction>, + c_oact: Option<&mut sigaction>, + ) -> Result<(), Errno> { + let sig = u8::try_from(sig).map_err(|_| syscall::Error::new(syscall::EINVAL))?; + + let new_action = c_act.map(|c_act| { + let handler = c_act.sa_handler.map_or(0, |f| f as usize); + + let kind = if handler == SIG_DFL { + SigactionKind::Default + } else if handler == SIG_IGN { + SigactionKind::Ignore + } else { + SigactionKind::Handled { + handler: if c_act.sa_flags & crate::header::signal::SA_SIGINFO as c_ulong != 0 { + SignalHandler { + sigaction: unsafe { core::mem::transmute(c_act.sa_handler) }, + } + } else { + SignalHandler { + handler: c_act.sa_handler, + } + }, } + }; - spec.it_interval.tv_sec = (*new).it_interval.tv_sec; - spec.it_interval.tv_nsec = (*new).it_interval.tv_usec * 1000; - spec.it_value.tv_sec = (*new).it_value.tv_sec; - spec.it_value.tv_nsec = (*new).it_value.tv_usec * 1000; - } - - count = e(syscall::write(fd, &spec)); - } - - let _ = syscall::close(fd); - - if count == !0 { - return -1; - } - - 0 - } - - fn sigaction(sig: c_int, act: Option<&sigaction>, oact: Option<&mut sigaction>) -> c_int { - let new_opt = act.map(|act| { - let m = act.sa_mask; - let sa_handler = unsafe { mem::transmute(act.sa_handler) }; - syscall::SigAction { - sa_handler, - sa_mask: [m as u64, 0], - sa_flags: syscall::SigActionFlags::from_bits(act.sa_flags as usize) - .expect("sigaction: invalid bit pattern"), + Sigaction { + kind, + mask: c_act.sa_mask, + flags: SigactionFlags::from_bits_retain(c_act.sa_flags as u32), } }); - let mut old_opt = oact.as_ref().map(|_| syscall::SigAction::default()); - let ret = e(syscall::sigaction( - sig as usize, - new_opt.as_ref(), - old_opt.as_mut(), - )) as c_int; - if let (Some(old), Some(oact)) = (old_opt, oact) { - oact.sa_handler = unsafe { mem::transmute(old.sa_handler) }; - let m = old.sa_mask; - oact.sa_mask = m[0] as c_ulong; - oact.sa_flags = old.sa_flags.bits() as c_ulong; + let mut old_action = c_oact.as_ref().map(|_| Sigaction::default()); + + redox_rt::signal::sigaction(sig, new_action.as_ref(), old_action.as_mut())?; + + if let (Some(c_oact), Some(old_action)) = (c_oact, old_action) { + *c_oact = match old_action.kind { + SigactionKind::Ignore => sigaction { + sa_handler: unsafe { core::mem::transmute(SIG_IGN) }, + sa_flags: 0, + sa_restorer: None, + sa_mask: 0, + }, + SigactionKind::Default => sigaction { + sa_handler: unsafe { core::mem::transmute(SIG_DFL) }, + sa_flags: 0, + sa_restorer: None, + sa_mask: 0, + }, + SigactionKind::Handled { handler } => sigaction { + sa_handler: if old_action.flags.contains(SigactionFlags::SIGINFO) { + unsafe { core::mem::transmute(handler.sigaction) } + } else { + unsafe { handler.handler } + }, + sa_restorer: None, + sa_flags: old_action.flags.bits() as c_ulong, + sa_mask: old_action.mask, + }, + }; } - ret + Ok(()) } - fn sigaltstack(ss: *const stack_t, old_ss: *mut stack_t) -> c_int { - unimplemented!() + unsafe fn sigaltstack( + new_c: Option<&stack_t>, + old_c: Option<&mut stack_t>, + ) -> Result<(), Errno> { + let new = new_c + .map(|c_stack| { + let flags = usize::try_from(c_stack.ss_flags).map_err(|_| Errno(EINVAL))?; + if flags != flags & (SS_DISABLE | SS_ONSTACK) { + return Err(Errno(EINVAL)); + } + + Ok(if flags & SS_DISABLE == SS_DISABLE { + Sigaltstack::Disabled + } else { + Sigaltstack::Enabled { + onstack: false, + base: c_stack.ss_sp.cast(), + size: c_stack.ss_size, + } + }) + }) + .transpose()?; + + let mut old = old_c.as_ref().map(|_| Sigaltstack::default()); + (unsafe { redox_rt::signal::sigaltstack(new.as_ref(), old.as_mut()) })?; + + if let (Some(old_c_stack), Some(old)) = (old_c, old) { + let c_stack = PosixStackt::from(old); + *old_c_stack = stack_t { + ss_sp: c_stack.sp.cast(), + ss_size: c_stack.size, + ss_flags: c_stack.flags, + }; + } + Ok(()) } - fn sigprocmask(how: c_int, set: *const sigset_t, oset: *mut sigset_t) -> c_int { - let new_opt = if set.is_null() { - None - } else { - Some([unsafe { *set as u64 }, 0]) - }; - let mut old_opt = if oset.is_null() { None } else { Some([0, 0]) }; - let ret = e(syscall::sigprocmask( - how as usize, - new_opt.as_ref(), - old_opt.as_mut(), - )) as c_int; - if let Some(old) = old_opt { - unsafe { *oset = old[0] as sigset_t }; + fn sigpending(set: &mut sigset_t) -> Result<(), Errno> { + *set = redox_rt::signal::currently_pending_blocked(); + Ok(()) + } + + fn sigprocmask( + how: c_int, + set: Option<&sigset_t>, + oset: Option<&mut sigset_t>, + ) -> Result<(), Errno> { + match how { + _ if set.is_none() => { + if let Some(oset) = oset { + *oset = redox_rt::signal::get_sigmask()?; + } + } + SIG_SETMASK => redox_rt::signal::set_sigmask(set.copied(), oset)?, + SIG_BLOCK => redox_rt::signal::or_sigmask(set.copied(), oset)?, + SIG_UNBLOCK => redox_rt::signal::andn_sigmask(set.copied(), oset)?, + + _ => return Err(Errno(EINVAL)), } - ret + Ok(()) + } + + fn sigsuspend(mask: &sigset_t) -> Errno { + match redox_rt::signal::await_signal_async(!*mask) { + Ok(_) => unreachable!(), + Err(err) => err.into(), + } + } + + fn sigtimedwait( + set: &sigset_t, + info_out: Option<&mut siginfo_t>, + timeout: Option<×pec>, + ) -> Result { + // TODO: deadline-based API + let timeout = timeout.map(|timeout| syscall::TimeSpec { + tv_sec: timeout.tv_sec, + tv_nsec: timeout.tv_nsec as _, + }); + let info = redox_rt::signal::await_signal_sync(*set, timeout.as_ref())?.into(); + if let Some(out) = info_out { + *out = info; + } + Ok(info.si_signo) + } + + /// Thread-based alarm() that Recycles the existing POSIX timer machinery + /// (timerfd + eventfd + pthread) with process-level signal delivery. + /// + /// Internally works with a timespec to allow sub-second timers in the + /// future (e.g. ualarm) as i've been asked, though the public API only exposes whole seconds. + fn alarm(seconds: c_uint) -> c_uint { + alarm_timespec(timespec { + tv_sec: seconds as time_t, + tv_nsec: 0, + }) } } + +/// Internal helper that arms/disarms the process-global alarm timer. +/// Accepts a full timespec so sub-second timers (ualarm) can reuse this later. +/// Returns the number of seconds remaining on the previous alarm (rounded up), +/// or 0 if there was no previous alarm. +/// +/// TODO: This implementation does not survive `exec()`. POSIX requires that a +/// pending alarm be preserved across exec (the timer continues counting down +/// in the new process image as i understand). +fn alarm_timespec(duration: timespec) -> c_uint { + let mut guard = ALARM_TIMER.lock(); + + // Determine remaining time on any existing alarm + let remaining = if let Some(ref alarm) = *guard { + let mut cur = itimerspec::default(); + if Sys::timer_gettime(alarm.0, Out::from_mut(&mut cur)).is_ok() { + let secs = cur.it_value.tv_sec as c_uint; + if cur.it_value.tv_nsec > 0 { + secs + 1 // POSIX: round up + } else { + secs + } + } else { + 0 + } + } else { + 0 + }; + + let disarm = duration.tv_sec == 0 && duration.tv_nsec == 0; + + if disarm { + // alarm(0): cancel any pending alarm + if let Some(ref alarm) = *guard { + let zero = itimerspec::default(); + let _ = Sys::timer_settime(alarm.0, 0, &zero, None); + } + return remaining; + } + + // Lazily create the singleton timer if it doesn't exist yet + if guard.is_none() { + let evp = sigevent { + sigev_value: sigval { + sival_ptr: core::ptr::null_mut(), + }, + sigev_signo: SIGALRM as c_int, + sigev_notify: SIGEV_SIGNAL, + sigev_notify_function: None, + sigev_notify_attributes: core::ptr::null_mut(), + }; + + let mut timer_id: timer_t = core::ptr::null_mut(); + if Sys::timer_create( + crate::header::time::CLOCK_REALTIME, + &evp, + Out::from_mut(&mut timer_id), + ) + .is_err() + { + return remaining; + } + + // Enable process-wide signal delivery instead of thread-specific + let timer_st = unsafe { &mut *(timer_id as *mut timer_internal_t) }; + timer_st.process_pid = Sys::getpid(); + + *guard = Some(AlarmTimer(timer_id)); + } + + let timer_id = guard + .as_ref() + .expect("alarm timer must exist after lazy init") + .0; + + // Arm the timer as a one-shot (no interval) + let spec = itimerspec { + it_value: duration, + it_interval: timespec::default(), + }; + let _ = Sys::timer_settime(timer_id, 0, &spec, None); + + remaining +} diff --git a/src/platform/redox/socket.rs b/src/platform/redox/socket.rs index 6e9fee0bed..d0fbe9d010 100644 --- a/src/platform/redox/socket.rs +++ b/src/platform/redox/socket.rs @@ -1,124 +1,120 @@ use alloc::vec::Vec; use core::{cmp, mem, ptr, slice, str}; -use syscall::{self, flag::*, Result}; +use redox_protocols::protocol::{FsCall, SocketCall}; +use redox_rt::proc::FdGuard; +use syscall::{self, flag::*}; use super::{ - super::{errno, types::*, Pal, PalSocket}, - e, Sys, + super::{Pal, PalSocket, types::*}, + Sys, + path::dir_path_and_fd_path, }; -use crate::header::{ - arpa_inet::inet_aton, - netinet_in::{in_addr, in_port_t, sockaddr_in}, - string::strnlen, - sys_socket::{constants::*, sa_family_t, sockaddr, socklen_t}, - sys_time::timeval, - sys_un::sockaddr_un, +use crate::{ + error::{Errno, Result}, + header::{ + arpa_inet::inet_aton, + bits_iovec::iovec, + bits_safamily_t::sa_family_t, + bits_socklen_t::socklen_t, + errno::{ + EAFNOSUPPORT, EDOM, EFAULT, EINVAL, EMSGSIZE, ENOMEM, ENOSYS, ENOTSOCK, EOPNOTSUPP, + EPROTONOSUPPORT, + }, + netinet_in::{in_addr, in_port_t, sockaddr_in}, + string::strnlen, + sys_select::timeval, + sys_socket::{ + CMSG_ALIGN, CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_NXTHDR, CMSG_SPACE, cmsghdr, + constants::*, msghdr, sockaddr, ucred, + }, + sys_un::sockaddr_un, + }, }; -macro_rules! bind_or_connect { - (bind $path:expr) => { - concat!("/", $path) - }; - (connect $path:expr) => { - $path - }; - ($mode:ident into, $socket:expr, $address:expr, $address_len:expr) => {{ - let fd = bind_or_connect!($mode copy, $socket, $address, $address_len); +unsafe fn bind_or_connect( + op: SocketCall, + socket: c_int, + address: *const sockaddr, + address_len: socklen_t, +) -> Result { + if (address_len as usize) < mem::size_of::() { + return Err(Errno(EINVAL)); + } - let result = syscall::dup2(fd, $socket as usize, &[]); - let _ = syscall::close(fd); - if (e(result) as c_int) < 0 { - return -1; - } - 0 - }}; - ($mode:ident copy, $socket:expr, $address:expr, $address_len:expr) => {{ - if ($address_len as usize) < mem::size_of::() { - errno = syscall::EINVAL; - return -1; - } + let path = match unsafe { (*address).sa_family } as c_int { + AF_INET => { + if (address_len as usize) != mem::size_of::() { + return Err(Errno(EINVAL)); + } - let path = match (*$address).sa_family as c_int { - AF_INET => { - if ($address_len as usize) != mem::size_of::() { - errno = syscall::EINVAL; - return -1; - } - let data = &*($address as *const sockaddr_in); - let addr = slice::from_raw_parts( + let data = unsafe { &*(address as *const sockaddr_in) }; + let addr = unsafe { + slice::from_raw_parts( &data.sin_addr.s_addr as *const _ as *const u8, mem::size_of_val(&data.sin_addr.s_addr), - ); - let port = in_port_t::from_be(data.sin_port); - let path = format!( - bind_or_connect!($mode "{}.{}.{}.{}:{}"), - addr[0], - addr[1], - addr[2], - addr[3], - port - ); + ) + }; + let port = in_port_t::from_be(data.sin_port); - path - }, - AF_UNIX => { - let data = &*($address as *const sockaddr_un); - - // NOTE: It's UB to access data in given address that exceeds - // the given address length. - - let maxlen = cmp::min( - // Max path length of the full-sized struct - data.sun_path.len(), - // Length inferred from given addrlen - $address_len as usize - data.path_offset() - ); - let len = cmp::min( - // The maximum length of the address - maxlen, - // The first NUL byte, if any - strnlen(&data.sun_path as *const _, maxlen as size_t), - ); - - let addr = slice::from_raw_parts( - &data.sun_path as *const _ as *const u8, - len, - ); - let path = format!( - "{}", - str::from_utf8(addr).unwrap() - ); - trace!("path: {:?}", path); - - path - }, - _ => { - errno = syscall::EAFNOSUPPORT; - return -1; - }, - }; - - // Duplicate the socket, and then duplicate the copy back to the original fd - let fd = e(syscall::dup($socket as usize, path.as_bytes())); - if (fd as c_int) < 0 { - return -1; + match op { + SocketCall::Bind => { + format!("/{}.{}.{}.{}:{}", addr[0], addr[1], addr[2], addr[3], port) + } + SocketCall::Connect => { + format!("{}.{}.{}.{}:{}", addr[0], addr[1], addr[2], addr[3], port) + } + _ => unreachable!(), + } } - fd - }}; + AF_UNIX => { + log::warn!("bind/connect with AF_UNIX were replaced with SYS_CALL."); + return Err(Errno(EAFNOSUPPORT)); + } + AF_UNSPEC => match op { + SocketCall::Bind => { + // Bind is not a valid socket call for AF_UNSPEC + return Err(Errno(EAFNOSUPPORT)); + } + SocketCall::Connect => { + // When a connect is made using AF_UNSPEC TCP and UDP need to disconnect from the default peer + format!("disconnect") + } + _ => unreachable!(), + }, + _ => return Err(Errno(EAFNOSUPPORT)), + }; + let fd = syscall::dup(socket as usize, path.as_bytes())?; + Ok(fd) +} + +pub unsafe fn bind_or_connect_into( + op: SocketCall, + socket: c_int, + address: *const sockaddr, + address_len: socklen_t, +) -> Result { + // Duplicate the socket, and then duplicate the copy back to the original fd + let fd = FdGuard::new(unsafe { bind_or_connect(op, socket, address, address_len) }?); + syscall::dup2(fd.as_raw_fd(), socket as usize, &[])?; + Ok(0) } unsafe fn inner_af_unix(buf: &[u8], address: *mut sockaddr, address_len: *mut socklen_t) { - let data = &mut *(address as *mut sockaddr_un); + let data = unsafe { &mut *(address as *mut sockaddr_un) }; data.sun_family = AF_UNIX as c_ushort; - let path = - slice::from_raw_parts_mut(&mut data.sun_path as *mut _ as *mut u8, data.sun_path.len()); + let path = unsafe { + slice::from_raw_parts_mut(&mut data.sun_path as *mut _ as *mut u8, data.sun_path.len()) + }; let len = cmp::min(path.len(), buf.len()); path[..len].copy_from_slice(&buf[..len]); + if len < path.len() { + path[len] = 0; + } - *address_len = len as socklen_t; + unsafe { *address_len = len as socklen_t }; } unsafe fn inner_af_inet( @@ -145,52 +141,75 @@ unsafe fn inner_af_inet( // Make address be followed by a NUL-byte colon[0] = b'\0'; - trace!("address: {:?}, port: {:?}", str::from_utf8(&raw_addr), port); + log::trace!("address: {:?}, port: {:?}", str::from_utf8(&raw_addr), port); let mut addr = in_addr::default(); assert_eq!( - inet_aton(raw_addr.as_ptr() as *mut i8, &mut addr), + unsafe { inet_aton(raw_addr.as_ptr() as *mut c_char, &mut addr) }, 1, "inet_aton might be broken, failed to parse netstack address" ); let ret = sockaddr_in { sin_family: AF_INET as sa_family_t, - sin_port: port, + sin_port: in_port_t::to_be(port), sin_addr: addr, ..sockaddr_in::default() }; - let len = cmp::min(*address_len as usize, mem::size_of_val(&ret)); + let len = cmp::min(unsafe { *address_len } as usize, mem::size_of_val(&ret)); - ptr::copy_nonoverlapping(&ret as *const _ as *const u8, address as *mut u8, len); - *address_len = len as socklen_t; + unsafe { + ptr::copy_nonoverlapping(&ret as *const _ as *const u8, address as *mut u8, len); + *address_len = len as socklen_t; + } } -unsafe fn inner_get_name( +unsafe fn inner_get_name_inner( local: bool, - socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, -) -> Result { - // Format: [udp|tcp:]remote/local, chan:path - let mut buf = [0; 256]; - let len = syscall::fpath(socket as usize, &mut buf)?; - let buf = &buf[..len]; - + buf: &[u8], +) -> Result<()> { if buf.starts_with(b"tcp:") || buf.starts_with(b"udp:") { - inner_af_inet(local, &buf[4..], address, address_len); + unsafe { inner_af_inet(local, &buf[4..], address, address_len) }; + } else if buf.starts_with(b"/scheme/tcp/") || buf.starts_with(b"/scheme/udp/") { + unsafe { inner_af_inet(local, &buf[12..], address, address_len) }; } else if buf.starts_with(b"chan:") { - inner_af_unix(&buf[5..], address, address_len); + unsafe { inner_af_unix(&buf[5..], address, address_len) }; + } else if buf.starts_with(b"/scheme/chan/") { + unsafe { inner_af_unix(&buf[13..], address, address_len) }; + } else if buf.starts_with(b"/scheme/uds_stream/") { + unsafe { inner_af_unix(&buf[19..], address, address_len) }; + } else if buf.starts_with(b"/scheme/uds_dgram/") { + unsafe { inner_af_unix(&buf[18..], address, address_len) }; } else { // Socket doesn't belong to any scheme - panic!( + log::trace!( "socket {:?} doesn't match either tcp, udp or chan schemes", str::from_utf8(buf) ); + return Err(Errno(ENOTSOCK)); } + Ok(()) +} - Ok(0) +fn socket_domain_type(socket: c_int) -> Result<(c_int, c_int)> { + let mut buf = [0; 256]; + let len = syscall::fpath(socket as usize, &mut buf)?; + Ok( + if buf.starts_with(b"tcp:") || buf.starts_with(b"/scheme/tcp/") { + (AF_INET, SOCK_STREAM) + } else if buf.starts_with(b"udp:") || buf.starts_with(b"/scheme/udp/") { + (AF_INET, SOCK_DGRAM) + } else if buf.starts_with(b"/scheme/uds_stream/") { + (AF_UNIX, SOCK_STREAM) + } else if buf.starts_with(b"/scheme/uds_dgram/") { + (AF_UNIX, SOCK_DGRAM) + } else { + return Err(Errno(ENOTSOCK)); + }, + ) } fn socket_kind(mut kind: c_int) -> (c_int, usize) { @@ -206,84 +225,588 @@ fn socket_kind(mut kind: c_int) -> (c_int, usize) { (kind, flags) } -impl PalSocket for Sys { - unsafe fn accept(socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int { - let stream = e(syscall::dup(socket as usize, b"listen")) as c_int; - if stream < 0 { - return -1; +unsafe fn serialize_payload_to_stream( + msg_stream: &mut Vec, + iovs: &[iovec], + whole_iov_size: usize, +) -> Result { + msg_stream.extend_from_slice(&whole_iov_size.to_le_bytes()); + + for iov in iovs { + if iov.iov_len > 0 { + if iov.iov_base.is_null() { + return Err(Errno(EFAULT)); + } + let source_slice: &[u8] = + unsafe { slice::from_raw_parts(iov.iov_base as *const u8, iov.iov_len) }; + msg_stream.extend_from_slice(source_slice); } - if address != ptr::null_mut() - && address_len != ptr::null_mut() - && Self::getpeername(stream, address, address_len) < 0 + } + Ok(whole_iov_size) +} + +unsafe fn serialize_ancillary_data_to_stream( + msg: *const msghdr, + mhdr: &msghdr, + socket: c_int, + msg_stream: &mut Vec, +) -> Result<()> { + if mhdr.msg_control.is_null() { + return Err(Errno(EINVAL)); + } + + let mut cmsg: *mut cmsghdr = unsafe { CMSG_FIRSTHDR(msg) }; + let mut cmsg_count = 0; + while !cmsg.is_null() { + cmsg_count += 1; + let current_cmsg = unsafe { &*cmsg }; + let min_cmsg_len = unsafe { CMSG_ALIGN(mem::size_of::()) }; + if current_cmsg.cmsg_len < min_cmsg_len { + return Err(Errno(EINVAL)); + } + + // cmsg entry format: [level(i32)][type(i32)][data_len(usize)][data] + msg_stream.extend_from_slice(¤t_cmsg.cmsg_level.to_le_bytes()); + msg_stream.extend_from_slice(¤t_cmsg.cmsg_type.to_le_bytes()); + + match (current_cmsg.cmsg_level, current_cmsg.cmsg_type) { + (SOL_SOCKET, SCM_RIGHTS) => { + let data_len = current_cmsg.cmsg_len - min_cmsg_len; + if data_len % mem::size_of::() != 0 { + return Err(Errno(EINVAL)); + } + let fd_count = data_len / mem::size_of::(); + + if fd_count > 0 { + let fds_ptr = unsafe { CMSG_DATA(cmsg) } as *const c_int; + let c_fds = unsafe { slice::from_raw_parts(fds_ptr, fd_count) }; + let fds_usize: Vec = c_fds.iter().map(|&fd| fd as usize).collect(); + let fds_slice = unsafe { + slice::from_raw_parts( + fds_usize.as_ptr() as *const u8, + fds_usize.len() * mem::size_of::(), + ) + }; + redox_rt::sys::sys_call_wo(socket as usize, &fds_slice, CallFlags::FD, &[])?; + } + + // Serialize to ancillary_data_stream. + // Our intermediate format: data_len is size of fd_count (usize), data is fd_count (usize) + let data_for_stream_len = mem::size_of::(); + let data_for_stream_payload = (fd_count as usize).to_le_bytes(); + + msg_stream.extend_from_slice(&(data_for_stream_len as usize).to_le_bytes()); + msg_stream.extend_from_slice(&data_for_stream_payload); + } + (SOL_SOCKET, SCM_CREDENTIALS) => { + // Our intermediate format: data_len is 0, no data payload + let data_for_stream_len = 0usize; + msg_stream.extend_from_slice(&(data_for_stream_len as usize).to_le_bytes()); + } + _ => { + return Err(Errno(EOPNOTSUPP)); + } + } + cmsg = unsafe { CMSG_NXTHDR(msg, cmsg) }; + } + Ok(()) +} + +unsafe fn deserialize_name_from_stream( + mhdr: &mut msghdr, + msg_stream: &[u8], + cursor: &mut usize, +) -> Result<()> { + // Read name_len from stream + let name_len_in_stream = read_num::(&msg_stream[*cursor..])?; + let name_len = cmp::min(name_len_in_stream, mhdr.msg_namelen as usize); + *cursor += mem::size_of::(); + + if name_len > 0 { + if *cursor + name_len > msg_stream.len() { + return Err(Errno(EMSGSIZE)); + } + if !mhdr.msg_name.is_null() && mhdr.msg_namelen > 0 { + let name_buffer = &msg_stream[*cursor..*cursor + name_len_in_stream]; + (unsafe { + inner_get_name_inner( + false, + mhdr.msg_name as *mut sockaddr, + &mut mhdr.msg_namelen, + name_buffer, + ) + })?; + } + *cursor += name_len_in_stream; + } else { + // If name_len is 0, set msg_namelen to 0 + mhdr.msg_namelen = 0; + } + Ok(()) +} + +unsafe fn deserialize_payload_from_stream( + mhdr: &mut msghdr, + msg_stream: &[u8], + iovs: &[iovec], + whole_iov_size: usize, + cursor: &mut usize, + test: u8, +) -> Result { + let full_payload_len_from_scheme = read_num::(&msg_stream[*cursor..])?; + *cursor += mem::size_of::(); + // Determine actual payload data available in the stream + let payload_len_to_read = cmp::min(full_payload_len_from_scheme, whole_iov_size); + let payload_data_from_stream = &msg_stream[*cursor..*cursor + payload_len_to_read]; + *cursor += payload_len_to_read; + + let mut total_bytes_written: usize = 0; + if !iovs.is_empty() && payload_len_to_read > 0 { + let mut source_bytes_consumed: usize = 0; + for iov in iovs { + if iov.iov_len == 0 { + continue; + } + if iov.iov_base.is_null() { + return Err(Errno(EFAULT)); + } + + let source_bytes_remaining = payload_data_from_stream + .len() + .saturating_sub(source_bytes_consumed); + if source_bytes_remaining == 0 { + break; + } + + let bytes_to_write = cmp::min(iov.iov_len, source_bytes_remaining); + if bytes_to_write > 0 { + let dest_slice: &mut [u8] = + unsafe { slice::from_raw_parts_mut(iov.iov_base as *mut u8, iov.iov_len) }; + + let source_sub_slice = &payload_data_from_stream + [source_bytes_consumed..source_bytes_consumed + bytes_to_write]; + dest_slice[..bytes_to_write].copy_from_slice(source_sub_slice); + total_bytes_written += bytes_to_write; + source_bytes_consumed += bytes_to_write; + } + } + } + + if full_payload_len_from_scheme > whole_iov_size { + mhdr.msg_flags |= MSG_TRUNC; + } + + Ok(total_bytes_written) +} + +unsafe fn deserialize_ancillary_data_from_stream( + mhdr: &mut msghdr, + socket: c_int, + msg_stream: &[u8], + cursor: &mut usize, + cmsg_space_provided: usize, + flags: c_int, +) -> Result<()> { + let mut current_cmsg_ptr_in_user_buf = if !mhdr.msg_control.is_null() && cmsg_space_provided > 0 + { + unsafe { CMSG_FIRSTHDR(mhdr) } + } else { + ptr::null_mut() + }; + let mut remaining_user_cmsg_buf_len = cmsg_space_provided; + let mut total_csmg_bytes_written_to_user_buf: usize = 0; + + while *cursor < msg_stream.len() { + const CMSG_HEADER_LEN_IN_STREAM: usize = + mem::size_of::() * 2 + mem::size_of::(); + if *cursor + CMSG_HEADER_LEN_IN_STREAM > msg_stream.len() { + if msg_stream[*cursor..].iter().any(|&b| b != 0) { + mhdr.msg_flags |= MSG_CTRUNC; + } + break; + } + + // cmsg entry format: [level(i32)][type(i32)][data_len(usize)][data] + let cmsg_level = read_num::(&msg_stream[*cursor..])?; + *cursor += mem::size_of::(); + let cmsg_type = read_num::(&msg_stream[*cursor..])?; + *cursor += mem::size_of::(); + let cmsg_data_len_in_stream = read_num::(&msg_stream[*cursor..])?; + *cursor += mem::size_of::(); + + if *cursor + cmsg_data_len_in_stream > msg_stream.len() { + mhdr.msg_flags |= MSG_CTRUNC; + break; + } + + let cmsg_data_from_stream = &msg_stream[*cursor..*cursor + cmsg_data_len_in_stream]; + *cursor += cmsg_data_len_in_stream; + + let mut temp_posix_cmsg_data_buf: Vec = Vec::new(); + + let actual_posix_cmsg_data_len = match (cmsg_level, cmsg_type) { + (SOL_SOCKET, SCM_RIGHTS) => { + if cmsg_data_len_in_stream != mem::size_of::() { + return Err(Errno(EINVAL)); + } + let fd_count = read_num::(&cmsg_data_from_stream)?; + + let mut fds_usize = vec![0usize; fd_count]; + + let fds_bytes = unsafe { + slice::from_raw_parts_mut( + fds_usize.as_mut_ptr() as *mut u8, + fds_usize.len() * mem::size_of::(), + ) + }; + + let mut call_flags = CallFlags::FD; + if flags & MSG_CMSG_CLOEXEC == MSG_CMSG_CLOEXEC { + call_flags |= CallFlags::FD_CLOEXEC; + } + + redox_rt::sys::sys_call_ro(socket as usize, fds_bytes, call_flags, &[])?; + + for fd in fds_usize { + temp_posix_cmsg_data_buf.extend_from_slice(&(fd as c_int).to_le_bytes()); + } + temp_posix_cmsg_data_buf.len() + } + (SOL_SOCKET, SCM_CREDENTIALS) => { + if cmsg_data_len_in_stream + != mem::size_of::() + mem::size_of::() + mem::size_of::() + { + return Err(Errno(EINVAL)); + } + + let pid = read_num::(&cmsg_data_from_stream)?; + let uid_offset = mem::size_of::(); + let uid = read_num::(&cmsg_data_from_stream[uid_offset..])?; + let gid_offset = uid_offset + mem::size_of::(); + let gid = read_num::(&cmsg_data_from_stream[gid_offset..])?; + let cred = ucred { pid, uid, gid }; + + temp_posix_cmsg_data_buf.extend_from_slice(unsafe { + slice::from_raw_parts( + &cred as *const ucred as *const u8, + mem::size_of::(), + ) + }); + temp_posix_cmsg_data_buf.len() + } + _ => { + return Err(Errno(EINVAL)); + } + }; + + let space_needed_for_posix_cmsg = + unsafe { CMSG_SPACE(actual_posix_cmsg_data_len as u32) } as usize; + + if !current_cmsg_ptr_in_user_buf.is_null() + && remaining_user_cmsg_buf_len >= space_needed_for_posix_cmsg { - return -1; + let cmsg_ref = unsafe { &mut *current_cmsg_ptr_in_user_buf }; + cmsg_ref.cmsg_len = unsafe { CMSG_LEN(actual_posix_cmsg_data_len as u32) } as usize; + cmsg_ref.cmsg_level = cmsg_level; + cmsg_ref.cmsg_type = cmsg_type; + + let data_ptr_in_user_cmsg = unsafe { CMSG_DATA(cmsg_ref) }; + unsafe { + ptr::copy_nonoverlapping( + temp_posix_cmsg_data_buf.as_ptr(), + data_ptr_in_user_cmsg as *mut u8, + actual_posix_cmsg_data_len, + ) + }; + + let aligned_len_written = unsafe { CMSG_ALIGN(cmsg_ref.cmsg_len) }; + total_csmg_bytes_written_to_user_buf += aligned_len_written; + remaining_user_cmsg_buf_len -= aligned_len_written; + current_cmsg_ptr_in_user_buf = + unsafe { CMSG_NXTHDR(mhdr, current_cmsg_ptr_in_user_buf) }; + } else { + mhdr.msg_flags |= MSG_CTRUNC; + break; } - stream + } + mhdr.msg_controllen = total_csmg_bytes_written_to_user_buf; + Ok(()) +} + +impl PalSocket for Sys { + unsafe fn accept( + socket: c_int, + address: *mut sockaddr, + address_len: *mut socklen_t, + ) -> Result { + let stream = syscall::dup(socket as usize, b"listen")?; + if address != ptr::null_mut() && address_len != ptr::null_mut() { + if let Err(err) = unsafe { Self::getpeername(stream as c_int, address, address_len) } { + let _ = syscall::close(stream); + return Err(err); + } + } + Ok(stream as c_int) } - unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int { - bind_or_connect!(bind into, socket, address, address_len) + unsafe fn bind(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> Result<()> { + match unsafe { (*address).sa_family } as c_int { + AF_INET => { + (unsafe { bind_or_connect_into(SocketCall::Bind, socket, address, address_len) })?; + } + AF_UNIX => { + let data = unsafe { &*(address as *const sockaddr_un) }; + + // NOTE: It's UB to access data in given address that exceeds + // the given address length. + + let maxlen = cmp::min( + // Max path length of the full-sized struct + data.sun_path.len(), + // Length inferred from given addrlen + address_len as usize - data.path_offset(), + ); + let len = cmp::min( + // The maximum length of the address + maxlen, + // The first NUL byte, if any + unsafe { strnlen(&data.sun_path as *const _, maxlen as size_t) }, + ); + + let addr = + unsafe { slice::from_raw_parts(&data.sun_path as *const _ as *const u8, len) }; + let path = format!("{}", str::from_utf8(addr).unwrap()); + log::trace!("bind(): path: {:?}", path); + + let (dir_path, fd_path) = dir_path_and_fd_path(&path)?; + + redox_rt::sys::sys_call_wo( + socket as usize, + fd_path.as_bytes(), + CallFlags::empty(), + &[SocketCall::Bind as u64], + )?; + + let fs_bind_result = (|| -> Result<()> { + let dirfd = FdGuard::open( + &dir_path, + syscall::O_RDONLY | syscall::O_DIRECTORY | syscall::O_CLOEXEC, + )?; + let fd_to_send = FdGuard::new(syscall::dup(socket as usize, &[])?); + syscall::sendfd(dirfd.as_raw_fd(), fd_to_send.as_raw_fd(), 0, 0)?; + Ok(()) + })(); + + if let Err(original_error) = fs_bind_result { + if let Err(unbind_error) = redox_rt::sys::sys_call_wo( + socket as usize, + &[], + CallFlags::empty(), + &[SocketCall::Unbind as u64], + ) { + todo_error!( + 0, + unbind_error, + "bind: CRITICAL: failed to unbind socket after a failed transaction" + ); + } + + return Err(original_error); + } + } + _ => { + return Err(Errno(EAFNOSUPPORT)); + } + }; + + Ok(()) } - unsafe fn connect(socket: c_int, address: *const sockaddr, address_len: socklen_t) -> c_int { - bind_or_connect!(connect into, socket, address, address_len) + unsafe fn connect( + socket: c_int, + address: *const sockaddr, + address_len: socklen_t, + ) -> Result { + match unsafe { (*address).sa_family } as c_int { + AF_INET => unsafe { + bind_or_connect_into(SocketCall::Connect, socket, address, address_len) + }, + AF_UNIX => { + let data = unsafe { &*(address as *const sockaddr_un) }; + + // NOTE: It's UB to access data in given address that exceeds + // the given address length. + + let maxlen = cmp::min( + // Max path length of the full-sized struct + data.sun_path.len(), + // Length inferred from given addrlen + address_len as usize - data.path_offset(), + ); + let len = cmp::min( + // The maximum length of the address + maxlen, + // The first NUL byte, if any + unsafe { strnlen(&data.sun_path as *const _, maxlen as size_t) }, + ); + + let addr = + unsafe { slice::from_raw_parts(&data.sun_path as *const _ as *const u8, len) }; + let path = format!("{}", str::from_utf8(addr).unwrap()); + log::trace!("connect(): path: {:?}", path); + + let (_, fd_path) = dir_path_and_fd_path(&path)?; + + let target_path = format!("/{fd_path}"); + let socket_file_fd = FdGuard::open(&target_path, syscall::O_RDWR)?; + + const TOKEN_BUF_SIZE: usize = 16; + + let mut token_buf = [0u8; TOKEN_BUF_SIZE]; + + redox_rt::sys::sys_call_ro( + socket_file_fd.as_raw_fd(), + &mut token_buf, + CallFlags::empty(), + &[FsCall::Connect as u64], + )?; + + redox_rt::sys::sys_call_wo( + socket as usize, + &token_buf, + CallFlags::empty(), + &[SocketCall::Connect as u64], + )?; + Result::::Ok(0) + } + AF_UNSPEC => unsafe { + bind_or_connect_into(SocketCall::Connect, socket, address, address_len) + }, + _ => Err(Errno(EAFNOSUPPORT)), + } } unsafe fn getpeername( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int { - e(inner_get_name(false, socket, address, address_len)) as c_int + ) -> Result<()> { + let mut buf = [0; 256]; + let len = redox_rt::sys::sys_call_ro( + socket as usize, + &mut buf, + CallFlags::empty(), + &[SocketCall::GetPeerName as u64], + )?; + + unsafe { inner_get_name_inner(false, address, address_len, &buf[..len]) } } unsafe fn getsockname( socket: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> c_int { - e(inner_get_name(true, socket, address, address_len)) as c_int + ) -> Result<()> { + let mut buf = [0; 256]; + let len = syscall::fpath(socket as usize, &mut buf)?; + + unsafe { inner_get_name_inner(true, address, address_len, &buf[..len]) } } - fn getsockopt( + unsafe fn getsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *mut c_void, - option_len: *mut socklen_t, - ) -> c_int { + option_len_ptr: *mut socklen_t, + ) -> Result<()> { + if option_len_ptr.is_null() { + return Err(Errno(EFAULT)); + } + let option_len = (unsafe { *option_len_ptr }) as usize; + + let option_c_int = || -> Result<&mut c_int> { + if option_value.is_null() { + return Err(Errno(EFAULT)); + } + + if option_len < mem::size_of::() { + return Err(Errno(EINVAL)); + } + + Ok(unsafe { &mut *(option_value as *mut c_int) }) + }; + match level { SOL_SOCKET => match option_name { - SO_ERROR => { - if option_value.is_null() { - return e(Err(syscall::Error::new(syscall::EFAULT))) as c_int; - } - - if (option_len as usize) < mem::size_of::() { - return e(Err(syscall::Error::new(syscall::EINVAL))) as c_int; - } - - let error = unsafe { &mut *(option_value as *mut c_int) }; - //TODO: Socket nonblock connection error - *error = 0; - - return 0; + SO_DOMAIN => { + let option = option_c_int()?; + *option = socket_domain_type(socket)?.0; + unsafe { *option_len_ptr = mem::size_of::() as socklen_t }; + return Ok(()); + } + SO_ERROR => { + let option = option_c_int()?; + //TODO: Socket nonblock connection error + *option = 0; + unsafe { *option_len_ptr = mem::size_of::() as socklen_t }; + return Ok(()); + } + SO_TYPE => { + let option = option_c_int()?; + *option = socket_domain_type(socket)?.1; + unsafe { *option_len_ptr = mem::size_of::() as socklen_t }; + return Ok(()); + } + _ => { + let metadata = [SocketCall::GetSockOpt as u64, option_name as u64]; + let payload = + unsafe { slice::from_raw_parts_mut(option_value as *mut u8, option_len) }; + let call_flags = CallFlags::empty(); + unsafe { + *option_len_ptr = redox_rt::sys::sys_call_ro( + socket as usize, + payload, + CallFlags::empty(), + &metadata, + )? as socklen_t; + } + return Ok(()); } - _ => (), }, + crate::header::sys_socket::constants::IPPROTO_TCP => { + let metadata = [SocketCall::GetSockOpt as u64, option_name as u64]; + let payload = + unsafe { slice::from_raw_parts_mut(option_value as *mut u8, option_len) }; + let call_flags = CallFlags::empty(); + unsafe { + *option_len_ptr = redox_rt::sys::sys_call_ro( + socket as usize, + payload, + CallFlags::empty(), + &metadata, + )? as socklen_t; + } + return Ok(()); + } _ => (), } - eprintln!( + todo_skip!( + 0, "getsockopt({}, {}, {}, {:p}, {:p})", - socket, level, option_name, option_value, option_len + socket, + level, + option_name, + option_value, + option_len_ptr ); - e(Err(syscall::Error::new(syscall::ENOSYS))) as c_int + Err(Errno(ENOSYS)) } - fn listen(socket: c_int, backlog: c_int) -> c_int { + fn listen(socket: c_int, backlog: c_int) -> Result<()> { // Redox has no need to listen - 0 + Ok(()) } unsafe fn recvfrom( @@ -293,29 +816,177 @@ impl PalSocket for Sys { flags: c_int, address: *mut sockaddr, address_len: *mut socklen_t, - ) -> ssize_t { - if flags != 0 { - errno = syscall::EOPNOTSUPP; - return -1; - } - if address == ptr::null_mut() || address_len == ptr::null_mut() { - Self::read(socket, slice::from_raw_parts_mut(buf as *mut u8, len)) + ) -> Result { + if address.is_null() && flags == 0 { + Self::read(socket, unsafe { + slice::from_raw_parts_mut(buf as *mut u8, len) + }) } else { - let fd = e(syscall::dup(socket as usize, b"listen")); - if fd == !0 { - return -1; + // Convert to recvmsg + let mut iov = iovec { + iov_base: buf, + iov_len: len, + }; + let mut msg = msghdr { + msg_name: address as *mut c_void, + msg_namelen: if !address_len.is_null() { + unsafe { *address_len } + } else { + 0 + }, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: ptr::null_mut(), + msg_controllen: 0, + msg_flags: 0, + }; + let count = unsafe { Self::recvmsg(socket, &mut msg, flags) }?; + if !address_len.is_null() { + unsafe { *address_len = msg.msg_namelen }; } - if Self::getpeername(fd as c_int, address, address_len) < 0 { - let _ = syscall::close(fd); - return -1; - } - - let ret = Self::read(fd as c_int, slice::from_raw_parts_mut(buf as *mut u8, len)); - let _ = syscall::close(fd); - ret + return Ok(count); } } + unsafe fn recvmsg(socket: c_int, msg: *mut msghdr, flags: c_int) -> Result { + if msg.is_null() { + return Err(Errno(EINVAL)); + } + let mut mhdr = unsafe { &mut *msg }; + let iovs_slice: &[iovec] = if mhdr.msg_iov.is_null() || mhdr.msg_iovlen == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(mhdr.msg_iov, mhdr.msg_iovlen as usize) } + }; + let whole_iov_size: usize = iovs_slice.iter().map(|iov| iov.iov_len).sum(); + + let mut msg_stream: Vec = Vec::new(); + + // Prepare space for the message stream. + // [name_len(usize)][name_buffer] + // [payload_len(usize)][payload_data_buffer] + // [ancillary_stream_buffer] + let expected_stream_size = { + 64 //reserve extra space for the scheme path + + mem::size_of::() // name_len + + mhdr.msg_namelen as usize // name_buffer + + mem::size_of::() // payload_len + + whole_iov_size // payload_data_buffer + + mem::size_of::() // control_len + + mhdr.msg_controllen as usize // ancillary_stream_buffer + }; + msg_stream + .try_reserve_exact(expected_stream_size) + .map_err(|_| Errno(ENOMEM))?; + msg_stream.resize(expected_stream_size, 0); + + // Write the information about the msghdr + let mut cursor: usize = 0; + msg_stream[cursor..cursor + mem::size_of::()] + .copy_from_slice(&(mhdr.msg_namelen as usize).to_le_bytes()); + cursor += mem::size_of::(); + msg_stream[cursor..cursor + mem::size_of::()] + .copy_from_slice(&(whole_iov_size).to_le_bytes()); + cursor += mem::size_of::(); + msg_stream[cursor..cursor + mem::size_of::()] + .copy_from_slice(&(mhdr.msg_controllen as usize).to_le_bytes()); + + // Read the message stream. + let metadata = [SocketCall::RecvMsg as u64, flags as u64]; + let call_flags = CallFlags::empty(); + let actual_read_len = + redox_rt::sys::sys_call_rw(socket as usize, &mut msg_stream, call_flags, &metadata)?; + msg_stream.truncate(actual_read_len); + + cursor = 0; + let cmsg_space_provided_by_user = mhdr.msg_controllen; + mhdr.msg_flags = 0; + + // Read sender name. + (unsafe { deserialize_name_from_stream(&mut mhdr, &msg_stream, &mut cursor) })?; + + // Read payload data. + let actual_payload_bytes_written_to_iov = unsafe { + deserialize_payload_from_stream( + &mut mhdr, + &msg_stream, + iovs_slice, + whole_iov_size, + &mut cursor, + 0u8, + ) + }?; + + // Reconstruct the ancillary data in the user-provided buffer. + let has_cmsg_buffer = !mhdr.msg_control.is_null() && cmsg_space_provided_by_user > 0; + let has_ancillary_data = cursor < msg_stream.len(); + if has_cmsg_buffer && has_ancillary_data { + (unsafe { + deserialize_ancillary_data_from_stream( + mhdr, + socket, + &msg_stream, + &mut cursor, + cmsg_space_provided_by_user as usize, + flags, + ) + })?; + } else { + mhdr.msg_controllen = 0; // No ancillary data + } + Ok(actual_payload_bytes_written_to_iov) + } + + unsafe fn sendmsg(socket: c_int, msg: *const msghdr, flags: c_int) -> Result { + if msg.is_null() { + return Err(Errno(EINVAL)); + } + let mhdr = unsafe { &*msg }; + + // Reserve space for the message stream. + // [payload_len(usize)][payload_data_buffer] + // [ancillary_stream_buffer] + let iovs_slice: &[iovec] = if mhdr.msg_iov.is_null() || mhdr.msg_iovlen == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(mhdr.msg_iov, mhdr.msg_iovlen as usize) } + }; + + let mut msg_stream: Vec = Vec::new(); + let whole_iov_size: usize = iovs_slice.iter().map(|iov| iov.iov_len).sum(); + msg_stream + .try_reserve_exact( + mem::size_of::() // payload_len + + whole_iov_size // payload_data_buffer + + mhdr.msg_controllen as usize, // ancillary_stream_buffer + ) + .map_err(|_| Errno(ENOMEM))?; + + // Write the message to the msg_stream. + let mut actual_payload_bytes_serialized = 0; + if !mhdr.msg_iov.is_null() && mhdr.msg_iovlen > 0 { + actual_payload_bytes_serialized = unsafe { + serialize_payload_to_stream(&mut msg_stream, &iovs_slice, whole_iov_size) + }?; + } + // Process Control Messages from msghdr and serialize them. + if mhdr.msg_controllen > 0 { + (unsafe { serialize_ancillary_data_to_stream(msg, mhdr, socket, &mut msg_stream) })?; + } + + // Send the message stream. + let metadata = [SocketCall::SendMsg as u64, flags as u64]; + let call_flags = CallFlags::empty(); + let written = redox_rt::sys::sys_call_rw( + socket as usize, + msg_stream.as_mut_slice(), + call_flags, + &metadata, + )?; + + Ok(actual_payload_bytes_serialized) + } + unsafe fn sendto( socket: c_int, buf: *const c_void, @@ -323,88 +994,130 @@ impl PalSocket for Sys { flags: c_int, dest_addr: *const sockaddr, dest_len: socklen_t, - ) -> ssize_t { + ) -> Result { if flags != 0 { - errno = syscall::EOPNOTSUPP; - return -1; + // Convert to sendmsg + let mut iov = iovec { + iov_base: buf as *mut c_void, + iov_len: len, + }; + let msg = msghdr { + msg_name: dest_addr as *mut c_void, + msg_namelen: dest_len, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: ptr::null_mut(), + msg_controllen: 0, + msg_flags: 0, + }; + return unsafe { Self::sendmsg(socket, &msg, flags) }; } if dest_addr == ptr::null() || dest_len == 0 { - Self::write(socket, slice::from_raw_parts(buf as *const u8, len)) + Self::write(socket, unsafe { + slice::from_raw_parts(buf as *const u8, len) + }) } else { - let fd = bind_or_connect!(connect copy, socket, dest_addr, dest_len); - let ret = Self::write(fd as c_int, slice::from_raw_parts(buf as *const u8, len)); - let _ = syscall::close(fd); - ret + let fd = FdGuard::new(unsafe { + bind_or_connect(SocketCall::Connect, socket, dest_addr, dest_len) + }?); + Self::write(fd.as_c_fd().unwrap(), unsafe { + slice::from_raw_parts(buf as *const u8, len) + }) } } - fn setsockopt( + unsafe fn setsockopt( socket: c_int, level: c_int, option_name: c_int, option_value: *const c_void, option_len: socklen_t, - ) -> c_int { - let set_timeout = |timeout_name: &[u8]| -> c_int { + ) -> Result<()> { + let set_timeout = |timeout_name: &[u8]| -> Result<()> { if option_value.is_null() { - return e(Err(syscall::Error::new(syscall::EFAULT))) as c_int; + return Err(Errno(EFAULT)); } if (option_len as usize) < mem::size_of::() { - return e(Err(syscall::Error::new(syscall::EINVAL))) as c_int; + return Err(Errno(EINVAL)); } let timeval = unsafe { &*(option_value as *const timeval) }; - let fd = e(syscall::dup(socket as usize, timeout_name)); - if fd == !0 { - return -1; - } + let fd = FdGuard::new(syscall::dup(socket as usize, timeout_name)?); - let timespec = syscall::TimeSpec { - tv_sec: timeval.tv_sec, - tv_nsec: timeval.tv_usec * 1000, + let Some(tv_nsec) = timeval.tv_usec.checked_mul(1000) else { + return Err(Errno(EDOM)); }; - let ret = Self::write(fd as c_int, ×pec); + let timespec = syscall::TimeSpec { + tv_sec: timeval.tv_sec as i64, + tv_nsec, + }; - let _ = syscall::close(fd); - - if ret >= 0 { - 0 - } else { - -1 - } + Self::write(fd.as_c_fd().unwrap(), ×pec)?; + Ok(()) }; match level { SOL_SOCKET => match option_name { SO_RCVTIMEO => return set_timeout(b"read_timeout"), SO_SNDTIMEO => return set_timeout(b"write_timeout"), - _ => (), + _ => { + let metadata = [SocketCall::SetSockOpt as u64, option_name as u64]; + let payload = unsafe { + slice::from_raw_parts(option_value as *const u8, option_len as usize) + }; + let call_flags = CallFlags::empty(); + redox_rt::sys::sys_call_wo( + socket as usize, + payload, + CallFlags::empty(), + &metadata, + )?; + return Ok(()); + } }, + crate::header::sys_socket::constants::IPPROTO_TCP => { + let metadata = [SocketCall::SetSockOpt as u64, option_name as u64]; + let payload = unsafe { + slice::from_raw_parts(option_value as *const u8, option_len as usize) + }; + redox_rt::sys::sys_call_wo( + socket as usize, + payload, + CallFlags::empty(), + &metadata, + )?; + return Ok(()); + } _ => (), } - eprintln!( + todo_skip!( + 0, "setsockopt({}, {}, {}, {:p}, {}) - unknown option", - socket, level, option_name, option_value, option_len + socket, + level, + option_name, + option_value, + option_len ); - 0 + Ok(()) } - fn shutdown(socket: c_int, how: c_int) -> c_int { - eprintln!("shutdown({}, {})", socket, how); - e(Err(syscall::Error::new(syscall::ENOSYS))) as c_int + fn shutdown(socket: c_int, how: c_int) -> Result<()> { + let metadata = [SocketCall::Shutdown as u64, how as u64]; + redox_rt::sys::sys_call_wo(socket as usize, &[], CallFlags::empty(), &metadata)?; + Ok(()) } - unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> c_int { + unsafe fn socket(domain: c_int, kind: c_int, protocol: c_int) -> Result { if domain != AF_INET && domain != AF_UNIX { - errno = syscall::EAFNOSUPPORT; - return -1; + return Err(Errno(EAFNOSUPPORT)); } // if protocol != 0 { - // errno = syscall::EPROTONOSUPPORT; + // ERRNO.set(syscall::EPROTONOSUPPORT); // return -1; // } @@ -412,59 +1125,91 @@ impl PalSocket for Sys { // The tcp: and udp: schemes allow using no path, // and later specifying one using `dup`. - match (domain, kind) { - (AF_INET, SOCK_STREAM) => e(syscall::open("tcp:", flags)) as c_int, - (AF_INET, SOCK_DGRAM) => e(syscall::open("udp:", flags)) as c_int, - (AF_UNIX, SOCK_STREAM) => e(syscall::open("chan:", flags | O_CREAT)) as c_int, - _ => { - errno = syscall::EPROTONOSUPPORT; - -1 + Ok(match (domain, kind) { + (AF_INET, SOCK_STREAM) => redox_rt::sys::open("/scheme/tcp", flags)? as c_int, + (AF_INET, SOCK_DGRAM) => redox_rt::sys::open("/scheme/udp", flags)? as c_int, + (AF_UNIX, SOCK_STREAM) => { + redox_rt::sys::open("/scheme/uds_stream", flags | O_CREAT)? as c_int } - } + (AF_UNIX, SOCK_DGRAM) => { + redox_rt::sys::open("/scheme/uds_dgram", flags | O_CREAT)? as c_int + } + _ => return Err(Errno(EPROTONOSUPPORT)), + }) } - fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> c_int { + fn socketpair(domain: c_int, kind: c_int, protocol: c_int, sv: &mut [c_int; 2]) -> Result<()> { let (kind, flags) = socket_kind(kind); match (domain, kind) { (AF_UNIX, SOCK_STREAM) => { - let listener = e(syscall::open("chan:", flags | O_CREAT)); - if listener == !0 { - return -1; - } + let listener = FdGuard::open("/scheme/uds_stream", flags | O_CREAT)?; - // For now, chan: lets connects be instant, and instead blocks + // For now, uds_stream: lets connects be instant, and instead blocks // on any I/O performed. So we don't need to mark this as // nonblocking. - let fd0 = e(syscall::dup(listener, b"connect")); - if fd0 == !0 { - let _ = syscall::close(listener); - return -1; - } + let fd0 = listener.dup(b"connect")?; + let fd1 = listener.dup(b"listen")?; - let fd1 = e(syscall::dup(listener, b"listen")); - if fd1 == !0 { - let _ = syscall::close(fd0); - let _ = syscall::close(listener); - return -1; - } - - sv[0] = fd0 as c_int; - sv[1] = fd1 as c_int; - 0 + sv[0] = fd0.take() as c_int; + sv[1] = fd1.take() as c_int; + Ok(()) } - _ => unsafe { - eprintln!( + (AF_UNIX, SOCK_DGRAM) => { + let listener = FdGuard::open("/scheme/uds_dgram", flags | O_CREAT)?; + + // For now, uds_dgram: lets connects be instant, and instead blocks + // on any I/O performed. So we don't need to mark this as + // nonblocking. + + let fd0 = listener.dup(b"connect")?; + + sv[0] = fd0.take() as c_int; + sv[1] = listener.take() as c_int; + Ok(()) + } + _ => { + todo_skip!( + 0, "socketpair({}, {}, {}, {:p})", domain, kind, protocol, sv.as_mut_ptr() ); - errno = syscall::EPROTONOSUPPORT; - -1 - }, + Err(Errno(EPROTONOSUPPORT)) + } } } } + +fn read_num(buffer: &[u8]) -> Result +where + T: NumFromBytes, +{ + T::from_le_bytes_slice(buffer) +} +trait NumFromBytes: Sized { + fn from_le_bytes_slice(buffer: &[u8]) -> Result; +} +impl NumFromBytes for i32 { + fn from_le_bytes_slice(buffer: &[u8]) -> Result { + Ok(i32::from_le_bytes( + buffer + .get(..mem::size_of::()) + .and_then(|slice| slice.try_into().ok()) + .ok_or_else(|| Errno(EFAULT))?, + )) + } +} +impl NumFromBytes for usize { + fn from_le_bytes_slice(buffer: &[u8]) -> Result { + Ok(usize::from_le_bytes( + buffer + .get(..mem::size_of::()) + .and_then(|slice| slice.try_into().ok()) + .ok_or_else(|| Errno(EFAULT))?, + )) + } +} diff --git a/src/platform/redox/timer.rs b/src/platform/redox/timer.rs new file mode 100644 index 0000000000..4ca03d67fd --- /dev/null +++ b/src/platform/redox/timer.rs @@ -0,0 +1,103 @@ +use syscall::Error; + +use crate::{ + error::{Errno, Result}, + header::{ + bits_timespec::timespec, + errno::EIO, + signal::{SIGEV_SIGNAL, SIGEV_THREAD}, + time::timer_internal_t, + }, + out::Out, + platform::{Pal, Sys, sys::event, types::c_void}, +}; +use core::{ + mem::{MaybeUninit, size_of}, + ptr, slice, +}; + +pub extern "C" fn timer_routine(arg: *mut c_void) -> *mut c_void { + let timer_st = unsafe { &mut *(arg as *mut timer_internal_t) }; + + loop { + let mut buf = MaybeUninit::uninit(); + let res = Error::demux(unsafe { + event::redox_event_queue_get_events_v1( + timer_st.eventfd, + buf.as_mut_ptr(), + 1, + 0, + core::ptr::null(), + core::ptr::null(), + ) + }); + if let Ok(res) = res { + assert_eq!(res, 1, "EOF is not yet well defined for event queues"); + } else { + break; + } + + if timer_st.evp.sigev_notify == SIGEV_THREAD { + if let Some(fun) = timer_st.evp.sigev_notify_function { + fun(timer_st.evp.sigev_value); + } + } else if timer_st.evp.sigev_notify == SIGEV_SIGNAL { + // TODO: This will deliver signal to process, which is required for alarm() + // Until it can bypass the exec() boundary, do not uncomment this code + // if timer_st.process_pid != 0 && Sys::kill(timer_st.process_pid, timer_st.evp.sigev_signo).is_err() { break; } else + if unsafe { Sys::rlct_kill(timer_st.caller_thread, timer_st.evp.sigev_signo as _) } + .is_err() + { + break; + } + } + + if timer_next_event(timer_st).is_err() { + break; + } + } + + timer_st.thread = ptr::null_mut(); + ptr::null_mut() +} + +fn timer_next_event(timer_st: &mut timer_internal_t) -> Result<()> { + timer_update_wake_time(timer_st)?; + let buf_to_write = unsafe { + Error::demux(event::redox_event_queue_ctl_v1( + timer_st.eventfd, + timer_st.timerfd, + 1, + 0, + ))?; + + slice::from_raw_parts( + &timer_st.next_wake_time.it_value as *const _ as *const u8, + size_of::(), + ) + }; + let bytes_written = redox_rt::sys::posix_write(timer_st.timerfd, buf_to_write)?; + if bytes_written < size_of::() { + return Err(Errno(EIO)); + } + Ok(()) +} + +pub(crate) fn timer_update_wake_time(timer_st: &mut timer_internal_t) -> Result<()> { + timer_st.next_wake_time.it_value = if timer_st.next_wake_time.it_interval.is_default() { + timespec::default() + } else { + let mut now = timespec::default(); + Sys::clock_gettime(timer_st.clockid, Out::from_mut(&mut now))?; + let next_time = match timespec::add(now, timer_st.next_wake_time.it_interval.clone()) { + Some(a) => a, + None => timespec::default(), + }; + + next_time + }; + if timer_st.next_wake_time.it_value.is_default() { + return Err(Errno(0)); + } + Ok(()) +} diff --git a/src/platform/rlb.rs b/src/platform/rlb.rs index 95ccb2ab0b..6ceb9d6bf4 100644 --- a/src/platform/rlb.rs +++ b/src/platform/rlb.rs @@ -1,8 +1,11 @@ use alloc::vec::Vec; -use crate::platform::{types::*, Pal, Sys}; +use crate::platform::{Pal, Sys, types::*}; -use crate::header::unistd::{lseek, SEEK_SET}; +use crate::{ + error::ResultExt, + header::unistd::{SEEK_SET, lseek}, +}; /// Implements an `Iterator` which returns on either newline or EOF. #[derive(Clone)] pub struct RawLineBuffer { @@ -32,7 +35,7 @@ impl RawLineBuffer { // Can't use iterators because we want to return a reference. // See https://stackoverflow.com/a/30422716/5069285 #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> Line { + pub fn next(&mut self) -> Line<'_> { // Remove last line if let Some(newline) = self.newline { self.buf.drain(..=newline); @@ -58,7 +61,9 @@ impl RawLineBuffer { self.buf.set_len(capacity); } - let read = Sys::read(self.fd, &mut self.buf[len..]); + let read = Sys::read(self.fd, &mut self.buf[len..]) + .map(|u| u as isize) + .or_minus_one_errno(); let read_usize = read.max(0) as usize; @@ -92,7 +97,7 @@ impl RawLineBuffer { /// Seek to a byte position in the file pub fn seek(&mut self, pos: usize) -> off_t { - let ret = lseek(self.fd, pos as i64, SEEK_SET); + let ret = lseek(self.fd, pos as off_t, SEEK_SET); if ret != !0 { self.read = pos; } diff --git a/src/platform/test/epoll.rs b/src/platform/test/epoll.rs deleted file mode 100644 index 58c750d197..0000000000 --- a/src/platform/test/epoll.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::platform::{PalEpoll, Sys}; diff --git a/src/platform/test/mod.rs b/src/platform/test/mod.rs deleted file mode 100644 index 0bcab03711..0000000000 --- a/src/platform/test/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::platform::{Pal, Sys}; - -// Stub for call used in exit -#[no_mangle] -pub extern "C" fn pthread_terminate() {} - -mod epoll; - -#[test] -fn access() { - use crate::header::{errno, unistd}; - - //TODO: create test files - assert_eq!(Sys::access(c_str!("not a file!"), unistd::F_OK), !0); - assert_eq!(Sys::access(c_str!("README.md"), unistd::F_OK), 0); - assert_eq!(Sys::access(c_str!("README.md"), unistd::R_OK), 0); - assert_eq!(Sys::access(c_str!("README.md"), unistd::W_OK), 0); - assert_eq!(Sys::access(c_str!("README.md"), unistd::X_OK), !0); -} - -#[test] -fn brk() { - use core::ptr; - - let current = Sys::brk(ptr::null_mut()); - assert_ne!(current, ptr::null_mut()); - - let request = unsafe { current.add(4096) }; - let next = Sys::brk(request); - assert_eq!(next, request); -} - -#[test] -fn chdir() { - //TODO: create test files - assert_eq!(Sys::chdir(c_str!("src")), 0); -} - -//TODO: chmod - -//TODO: chown - -#[test] -fn clock_gettime() { - use crate::header::time; - - { - let mut timespec = time::timespec { - tv_sec: -1, - tv_nsec: -1, - }; - assert_eq!(Sys::clock_gettime(time::CLOCK_REALTIME, &mut timespec), 0); - assert_ne!(timespec.tv_sec, -1); - assert_ne!(timespec.tv_nsec, -1); - } - - { - let mut timespec = time::timespec { - tv_sec: -1, - tv_nsec: -1, - }; - assert_eq!(Sys::clock_gettime(time::CLOCK_MONOTONIC, &mut timespec), 0); - assert_ne!(timespec.tv_sec, -1); - assert_ne!(timespec.tv_nsec, -1); - } -} - -//TDOO: everything else - -#[test] -fn getrandom() { - use crate::{header::sys_random, platform::types::ssize_t}; - - let mut arrays = [[0; 32]; 32]; - for i in 1..arrays.len() { - assert_eq!( - Sys::getrandom(&mut arrays[i], 0), - arrays[i].len() as ssize_t - ); - - for j in 0..arrays.len() { - if i != j { - assert_ne!(&arrays[i], &arrays[j]); - } - } - } -} diff --git a/src/platform/types.rs b/src/platform/types.rs index 5d604d383a..3d14bb29a1 100644 --- a/src/platform/types.rs +++ b/src/platform/types.rs @@ -1,8 +1,9 @@ -use core::i32; +//! C data types for this platform. // Use repr(u8) as LLVM expects `void*` to be the same as `i8*` to help enable // more optimization opportunities around it recognizing things like // malloc/free. +/// The `void` type in C. #[repr(u8)] pub enum c_void { // Two dummy variants so the #[repr] attribute can be used. @@ -12,62 +13,149 @@ pub enum c_void { __variant2, } +/// The `int8_t` type provided in `stdint.h`, see . pub type int8_t = i8; +/// The `int16_t` type provided in `stdint.h`, see . pub type int16_t = i16; +/// The `int32_t` type provided in `stdint.h`, see . pub type int32_t = i32; +/// The `int64_t` type provided in `stdint.h`, see . pub type int64_t = i64; +/// The `uint8_t` type provided in `stdint.h`, see . pub type uint8_t = u8; +/// The `uint16_t` type provided in `stdint.h`, see . pub type uint16_t = u16; +/// The `uint32_t` type provided in `stdint.h`, see . pub type uint32_t = u32; +/// The `uint64_t` type provided in `stdint.h`, see . pub type uint64_t = u64; +/// The `signed char` type in C. pub type c_schar = i8; +/// The `unsigned char` type in C. pub type c_uchar = u8; +/// The `short` type in C. pub type c_short = i16; +/// The `unsigned short` type in C. pub type c_ushort = u16; +/// The `int` type in C. pub type c_int = i32; +/// The `unsigned int` type in C. pub type c_uint = u32; +/// The `float` type in C. pub type c_float = f32; +/// The `double` type in C. pub type c_double = f64; +/// The `long long` type in C. pub type c_longlong = i64; +/// The `unsigned long long` type in C. pub type c_ulonglong = u64; +/// The `intmax_t` type provided in `stdint.h`, see . pub type intmax_t = i64; +/// The `uintmax_t` type provided in `stdint.h`, see . pub type uintmax_t = u64; +/// The `size_t` type provided in `stddef.h`, see . pub type size_t = usize; +/// The `ptrdiff_t` type provided in `stddef.h`, see . pub type ptrdiff_t = isize; +/// The `intptr_t` type provided in `stdint.h`, see . pub type intptr_t = isize; +/// The `uintptr_t` type provided in `stdint.h`, see . pub type uintptr_t = usize; +/// The `ssize_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type ssize_t = isize; -pub type c_char = i8; +/// The `char` type in C. +pub type c_char = core::ffi::c_char; +/// The `long` type in C. +#[cfg(target_pointer_width = "32")] +pub type c_long = i32; +/// The `unsigned long` type in C. +#[cfg(target_pointer_width = "32")] +pub type c_ulong = u32; +/// The `long` type in C. +#[cfg(target_pointer_width = "64")] pub type c_long = i64; +/// The `unsigned long` type in C. +#[cfg(target_pointer_width = "64")] pub type c_ulong = u64; +/// The `wchar_t` type provided in `stddef.h`, see . pub type wchar_t = i32; +/// The `wint_t` type provided in [`wchar.h`](crate::header::wchar). pub type wint_t = u32; -pub type wctype_t = i64; +/// The `regoff_t` type provided in [`regex.h`](crate::header::regex). pub type regoff_t = size_t; -pub type off_t = c_long; +/// The `off_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type off_t = c_longlong; +/// The `mode_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type mode_t = c_int; -pub type time_t = c_long; +/// The `time_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type time_t = c_longlong; +/// The `pid_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type pid_t = c_int; +/// The `id_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type id_t = c_uint; +/// The `gid_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type gid_t = c_int; +/// The `uid_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type uid_t = c_int; -pub type dev_t = c_long; -pub type ino_t = c_ulong; +/// The `dev_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type dev_t = c_ulonglong; +/// The `ino_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type ino_t = c_ulonglong; +/// The `reclen_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type reclen_t = c_ushort; +/// The `nlink_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type nlink_t = c_ulong; +/// The `blksize_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type blksize_t = c_long; -pub type blkcnt_t = c_ulong; +/// The `blkcnt_t` type provided in [`sys/types.h`](crate::header::sys_types). +pub type blkcnt_t = c_longlong; +/// The `fsblkcnt_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type fsblkcnt_t = c_ulong; +/// The `fsfilcnt_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type fsfilcnt_t = c_ulong; +/// The `useconds_t` type provided in [`sys/types.h`](crate::header::sys_types) prior to Issue 7. +#[deprecated] pub type useconds_t = c_uint; +/// The `suseconds_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[cfg(target_os = "linux")] +pub type suseconds_t = c_long; +// TODO: Should we break this to c_long as well? This also breaks timeval as well +// but it will be consistent with timespec.tv_nsec (note that syscall already uses c_int) +/// The `suseconds_t` type provided in [`sys/types.h`](crate::header::sys_types). +#[cfg(not(target_os = "linux"))] pub type suseconds_t = c_int; +/// The `clock_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type clock_t = c_long; +/// The `clockid_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type clockid_t = c_int; +/// The `timer_t` type provided in [`sys/types.h`](crate::header::sys_types). pub type timer_t = *mut c_void; + +// A C long double is 96 bit in x86, 128 bit in other 64-bit targets +// However, both in x86 and x86_64 is actually f80 padded which rust has no underlying support, +// while aarch64 (and possibly riscv64) support full f128 type but behind a feature gate. +// Until rust supporting them, relibc will lose precision to get them working, plus: +// All read operation to this type must be converted from "relibc_ldtod". +// All write operation to this type must be converted with "relibc_dtold". +/// The `long double` type in C. +#[cfg(target_pointer_width = "64")] +pub type c_longdouble = u128; +/// The `long double` type in C. +#[cfg(target_pointer_width = "32")] +pub type c_longdouble = [u32; 3]; + +pub use crate::header::bits_pthread::*; + +/// The `max_align_t` type provided in `stddef.h`, see . +#[repr(C, align(16))] +pub struct max_align_t { + _priv: [f64; 4], +} diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs new file mode 100644 index 0000000000..0a5f5082f6 --- /dev/null +++ b/src/pthread/mod.rs @@ -0,0 +1,522 @@ +//! Relibc Threads, or RLCT. + +use core::{ + cell::UnsafeCell, + ptr, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; + +use alloc::collections::BTreeMap; + +use crate::{ + error::Errno, + header::{errno::*, pthread as header, sched::sched_param, sys_mman}, + ld_so::{ExpectTlsFree, tcb::Tcb}, + platform::{Pal, Sys, types::*}, +}; + +use crate::sync::{Mutex, waitval::Waitval}; + +/// Called only by the main thread, as part of relibc_start. +#[allow(unused_mut)] +pub unsafe fn init() { + let mut thread = Pthread { + waitval: Waitval::new(), + has_enabled_cancelation: AtomicBool::new(false), + has_queued_cancelation: AtomicBool::new(false), + flags: PthreadFlags::empty().bits().into(), + + //index: FIRST_THREAD_IDX, + + // TODO: set these values on Linux as well + stack_base: ptr::null_mut(), + stack_size: 0, + + os_tid: UnsafeCell::new(Sys::current_os_tid()), + + robust_list_head: UnsafeCell::new(core::ptr::null_mut()), + }; + + #[cfg(target_os = "redox")] + { + //TODO: what is the best way to get these values? + use redox_rt::arch::{STACK_SIZE, STACK_TOP}; + thread.stack_base = (STACK_TOP - STACK_SIZE) as *mut c_void; + thread.stack_size = STACK_SIZE; + } + + unsafe { Tcb::current() } + .expect_notls("no TCB present for main thread") + .pthread = thread; +} + +//static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1); +//const FIRST_THREAD_IDX: usize = 1; + +pub unsafe fn terminate_from_main_thread() { + for (_, tcb) in OS_TID_TO_PTHREAD.lock().iter() { + let _ = unsafe { cancel(&(*tcb.0).pthread) }; + } +} + +bitflags::bitflags! { + pub struct PthreadFlags: usize { + const DETACHED = 1; + const FINISHED = 2; + } +} + +#[derive(Debug)] +pub struct Pthread { + pub(crate) waitval: Waitval, + pub(crate) has_queued_cancelation: AtomicBool, + pub(crate) has_enabled_cancelation: AtomicBool, + pub(crate) flags: AtomicUsize, + + pub(crate) stack_base: *mut c_void, + pub(crate) stack_size: usize, + + pub os_tid: UnsafeCell, + + /// Head of the per-thread robust mutex list, used by the kernel + /// during thread exit to mark any held robust mutexes with + /// `FUTEX_OWNER_DIED` so a future `pthread_mutex_lock` on a + /// dead-owner mutex can recover via `EOWNERDEAD`. Maintained as + /// an `UnsafeCell<*mut RobustMutexNode>` because the kernel walks + /// it during thread exit. `null` = empty list. + pub(crate) robust_list_head: UnsafeCell<*mut crate::sync::pthread_mutex::RobustMutexNode>, +} + +#[derive(Clone, Copy, Debug, Default, Ord, Eq, PartialOrd, PartialEq)] +pub struct OsTid { + #[cfg(target_os = "redox")] + pub thread_fd: usize, + #[cfg(target_os = "linux")] + pub thread_id: usize, +} + +unsafe impl Send for Pthread {} +unsafe impl Sync for Pthread {} + +#[derive(Clone, Copy, Debug)] +pub struct Retval(pub *mut c_void); + +struct MmapGuard { + page_start: *mut c_void, + mmap_size: usize, +} +impl Drop for MmapGuard { + fn drop(&mut self) { + unsafe { + let _ = Sys::munmap(self.page_start, self.mmap_size); + } + } +} + +#[allow(unused_mut)] +pub(crate) unsafe fn create( + attrs: Option<&header::RlctAttr>, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, +) -> Result { + let attrs = attrs.cloned().unwrap_or_default(); + + #[cfg(target_os = "redox")] + let current_sigmask = + redox_rt::signal::get_sigmask().expect("failed to obtain sigprocmask for caller"); + #[cfg(not(target_os = "redox"))] + let current_sigmask = 0_u64; + + // Create a locked mutex, unlocked by the thread after it has started. + let synchronization_mutex = unsafe { Mutex::locked(current_sigmask) }; + let synchronization_mutex = &synchronization_mutex; + + let stack_size = attrs.stacksize.next_multiple_of(Sys::getpagesize()); + + let stack_base = if attrs.stack != 0 { + attrs.stack as *mut c_void + } else { + let ret = unsafe { + sys_mman::mmap( + core::ptr::null_mut(), + stack_size, + sys_mman::PROT_READ | sys_mman::PROT_WRITE, + sys_mman::MAP_PRIVATE | sys_mman::MAP_ANONYMOUS, + -1, + 0, + ) + }; + if ret as isize == -1 { + // "Insufficient resources" + return Err(Errno(EAGAIN)); + } + ret + }; + + let mut flags = PthreadFlags::empty(); + match i32::from(attrs.detachstate) { + header::PTHREAD_CREATE_DETACHED => flags |= PthreadFlags::DETACHED, + header::PTHREAD_CREATE_JOINABLE => (), + + other => unreachable!("unknown detachstate {}", other), + } + + let stack_raii = MmapGuard { + page_start: stack_base, + mmap_size: stack_size, + }; + + let current_tcb = unsafe { Tcb::current() }.expect("no TCB!"); + let new_tcb = unsafe { Tcb::new(current_tcb.tls_len) }.map_err(|_| Errno(ENOMEM))?; + new_tcb.pthread.flags = flags.bits().into(); + new_tcb.pthread.stack_base = stack_base; + new_tcb.pthread.stack_size = stack_size; + // Initialize the robust mutex list head to null. The + // Pthread struct is zero-initialized in Tcb::new but we + // set this explicitly for documentation/clarity, and to + // guard against future changes that might not zero the + // struct (e.g. a switch to Box>). + // Without this, the first pthread_mutex_lock of a + // ROBUST mutex would dereference an uninitialized pointer. + new_tcb.pthread.robust_list_head = UnsafeCell::new(core::ptr::null_mut()); + + new_tcb.masters_ptr = current_tcb.masters_ptr; + new_tcb.masters_len = current_tcb.masters_len; + new_tcb.linker_ptr = current_tcb.linker_ptr; + new_tcb.mspace = current_tcb.mspace; + + let stack_end = unsafe { stack_base.add(stack_size) }; + let mut stack = stack_end.cast::(); + { + let mut push = |value: usize| { + stack = unsafe { stack.sub(1) }; + unsafe { stack.write(value) }; + }; + + if cfg!(target_arch = "aarch64") { + // Aarch64 requires the stack to be 16 byte aligned after + // the call instruction, unlike x86 which requires it to be + // aligned before the call instruction. As such push an + // extra word on the stack to align the stack to 16 bytes. + push(0); + } + push(0); + push(0); + push(ptr::from_ref(synchronization_mutex) as usize); + push(ptr::from_mut(new_tcb) as usize); + + push(arg as usize); + push(start_routine as usize); + + push(new_thread_shim as *const () as usize); + } + + let Ok(os_tid) = (unsafe { Sys::rlct_clone(stack, &mut new_tcb.os_specific) }) else { + return Err(Errno(EAGAIN)); + }; + core::mem::forget(stack_raii); + + let _ = synchronization_mutex.lock(); + + OS_TID_TO_PTHREAD + .lock() + .insert(os_tid, ForceSendSync(new_tcb)); + + Ok(&raw const new_tcb.pthread as *mut _) +} + +/// A shim to wrap thread entry points in logic to set up TLS, for example +unsafe extern "C" fn new_thread_shim( + entry_point: unsafe extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void, + tcb: *mut Tcb, + synchronization_mutex: *const Mutex, +) -> ! { + let tcb = unsafe { tcb.as_mut() }.expect_notls("non-null TLS is required"); + + #[cfg(not(target_os = "redox"))] + { + unsafe { tcb.activate() }; + } + #[cfg(target_os = "redox")] + { + // `thr_fd` in `tcb` is set by [`Sys::rlct_clone`] *before* jumping to + // the entry point of the new thread. + unsafe { + tcb.activate(None); + } + redox_rt::signal::setup_sighandler(&tcb.os_specific, false); + } + + let procmask = unsafe { (&*synchronization_mutex).as_ptr().read() }; + + unsafe { tcb.copy_masters() }.unwrap(); + + unsafe { (*tcb).pthread.os_tid.get().write(Sys::current_os_tid()) }; + + unsafe { (&*synchronization_mutex).manual_unlock() }; + + #[cfg(target_os = "redox")] + { + redox_rt::signal::set_sigmask(Some(procmask), None) + .expect("failed to set procmask in child thread"); + } + + let retval = unsafe { entry_point(arg) }; + + unsafe { exit_current_thread(Retval(retval)) } +} +pub unsafe fn join(thread: &Pthread) -> Result { + // We don't have to return EDEADLK, but unlike e.g. pthread_t lifetime checking, it's a + // relatively easy check. + if core::ptr::eq( + thread, + current_thread().expect("current thread not present"), + ) { + return Err(Errno(EDEADLK)); + } + + // Waitval starts locked, and is unlocked when the thread finishes. + let retval = *thread.waitval.wait(); + + // We have now awaited the thread and received its return value. POSIX states that the + // pthread_t of this thread, will no longer be valid. In practice, we can thus deallocate the + // thread state. + + unsafe { dealloc_thread(thread) }; + + Ok(retval) +} + +pub unsafe fn detach(thread: &Pthread) -> Result<(), Errno> { + thread + .flags + .fetch_or(PthreadFlags::DETACHED.bits(), Ordering::AcqRel); + Ok(()) +} + +pub fn current_thread() -> Option<&'static Pthread> { + unsafe { Tcb::current().map(|p| &p.pthread) } +} + +pub unsafe fn testcancel() { + let this_thread = current_thread().expect("current thread not present"); + + if this_thread.has_queued_cancelation.load(Ordering::Acquire) + && this_thread.has_enabled_cancelation.load(Ordering::Acquire) + { + unsafe { cancel_current_thread() }; + } +} + +pub unsafe fn exit_current_thread(retval: Retval) -> ! { + // Run pthread_cleanup_push/pthread_cleanup_pop destructors. + unsafe { header::run_destructor_stack() }; + + unsafe { header::tls::run_all_destructors() }; + + let this = current_thread().expect("failed to obtain current thread when exiting"); + + unsafe { crate::sync::pthread_mutex::mark_robust_mutexes_dead(this); } + + let stack_base = this.stack_base; + let stack_size = this.stack_size; + + if this.flags.load(Ordering::Acquire) & PthreadFlags::DETACHED.bits() != 0 { + // When detached, the thread state no longer makes any sense, and can immediately be + // deallocated. + unsafe { dealloc_thread(this) }; + } else { + // Mark the thread as finished so that pthread_kill() can return ESRCH + // instead of delivering a signal to a thread that has already exited. + this.flags.fetch_or(PthreadFlags::FINISHED.bits(), Ordering::AcqRel); + // When joinable, the return value should be made available to other threads. + unsafe { this.waitval.post(retval) }; + } + + unsafe { Sys::exit_thread(stack_base.cast(), stack_size) } +} + +unsafe fn dealloc_thread(thread: &Pthread) { + // TODO: How should this be handled on Linux? + unsafe { + OS_TID_TO_PTHREAD.lock().remove(&thread.os_tid.get().read()); + } +} +pub const SIGRT_RLCT_CANCEL: usize = 33; +pub const SIGRT_RLCT_TIMER: usize = 34; + +unsafe extern "C" fn cancel_sighandler(_: c_int) { + unsafe { cancel_current_thread() }; +} +unsafe fn cancel_current_thread() { + // Terminate the thread + unsafe { exit_current_thread(Retval(header::PTHREAD_CANCELED)) }; +} + +pub unsafe fn cancel(thread: &Pthread) -> Result<(), Errno> { + // TODO: What order should these atomic bools be accessed in? + thread.has_queued_cancelation.store(true, Ordering::Release); + + if thread.has_enabled_cancelation.load(Ordering::Acquire) { + (unsafe { Sys::rlct_kill(thread.os_tid.get().read(), SIGRT_RLCT_CANCEL) })?; + } + + Ok(()) +} + +pub fn set_sched_param( + _thread: &Pthread, + _policy: c_int, + _param: &sched_param, +) -> Result<(), Errno> { + // TODO + Ok(()) +} +pub fn set_sched_priority(_thread: &Pthread, _prio: c_int) -> Result<(), Errno> { + // TODO + Ok(()) +} +pub fn set_cancel_state(state: c_int) -> Result { + let this_thread = current_thread().expect("current thread not present"); + + let was_cancelable = match state { + header::PTHREAD_CANCEL_ENABLE => { + let old = this_thread + .has_enabled_cancelation + .swap(true, Ordering::Release); + + if this_thread.has_queued_cancelation.load(Ordering::Acquire) { + unsafe { + cancel_current_thread(); + } + } + old + } + header::PTHREAD_CANCEL_DISABLE => this_thread + .has_enabled_cancelation + .swap(false, Ordering::Release), + + _ => return Err(Errno(EINVAL)), + }; + + Ok(match was_cancelable { + true => header::PTHREAD_CANCEL_ENABLE, + false => header::PTHREAD_CANCEL_DISABLE, + }) +} +pub fn set_cancel_type(ty: c_int) -> Result { + let this_thread = current_thread().expect("current thread not present"); + + // TODO + match ty { + header::PTHREAD_CANCEL_DEFERRED => (), + header::PTHREAD_CANCEL_ASYNCHRONOUS => (), + + _ => return Err(Errno(EINVAL)), + } + Ok(header::PTHREAD_CANCEL_DEFERRED) +} +pub fn get_cpu_clkid(thread: &Pthread) -> Result { + // TODO + Err(Errno(ENOENT)) +} +pub fn get_sched_param(thread: &Pthread) -> Result<(clockid_t, sched_param), Errno> { + todo!() +} + +// TODO: Hash map? +// TODO: RwLock to improve perf? +static OS_TID_TO_PTHREAD: Mutex>> = + Mutex::new(BTreeMap::new()); + +#[derive(Clone, Copy)] +struct ForceSendSync(T); +unsafe impl Send for ForceSendSync {} +unsafe impl Sync for ForceSendSync {} + +/*pub(crate) fn current_thread_index() -> u32 { + current_thread().expect("current thread not present").index +}*/ + +#[derive(Clone, Copy, Default, Debug)] +pub enum Pshared { + #[default] + Private, + + Shared, +} +impl Pshared { + pub const fn from_raw(raw: c_int) -> Option { + Some(match raw { + header::PTHREAD_PROCESS_PRIVATE => Self::Private, + header::PTHREAD_PROCESS_SHARED => Self::Shared, + + _ => return None, + }) + } + pub const fn raw(self) -> c_int { + match self { + Self::Private => header::PTHREAD_PROCESS_PRIVATE, + Self::Shared => header::PTHREAD_PROCESS_SHARED, + } + } +} + +/// Return `true` if the given mutex owner ID refers to a thread that +/// is still alive in this process. Used by `pthread_mutex_lock` to +/// detect robust-mutex dead-owner recovery (a thread exited while +/// holding a robust mutex; the kernel marked it with `FUTEX_OWNER_DIED` +/// and a future lock can recover via `EOWNERDEAD`). +/// +/// On Redox, the owner ID is the kernel's `thread_fd` for the +/// thread. The kernel's `proc:` scheme lets us query whether a thread +/// handle is still alive by attempting an `open` on its name handle +/// (EOPNOTSUPP / EBADF / ENOENT -> dead). +/// +/// On Linux, the owner ID is the `tid` (kernel task ID). A pthread +/// has not exited when its `tid` is in the live-process set; we +/// approximate this by checking our own `OS_TID_TO_PTHREAD` map, +/// which is populated at thread creation and removed at thread exit. +/// +/// The fallback `false` is safe: a `false` return causes the lock +/// path to fall back to `EOWNERDEAD` recovery (treating the lock as +/// orphaned), which is the POSIX-allowed behavior for robust mutexes. +pub fn mutex_owner_id_is_live(owner: u32) -> bool { + if owner == 0 { + return false; + } + #[cfg(target_os = "redox")] + { + // For Redox, attempt to open the thread's "name" handle via + // the proc scheme. If the thread is alive, the open succeeds; + // if it has exited and been reaped, the open returns ENOENT. + let path = alloc::format!("proc:{}/name\0", owner); + let cstr = match crate::c_str::CStr::from_bytes_with_nul(path.as_bytes()) { + Ok(c) => c, + Err(_) => return false, + }; + let fd = crate::platform::Sys::open(cstr, crate::header::fcntl::O_RDONLY, 0); + match fd { + Ok(fd) => { + let _ = crate::platform::Sys::close(fd); + true + } + Err(_) => false, + } + } + #[cfg(target_os = "linux")] + { + // For Linux, the OS_TID_TO_PTHREAD map is maintained at + // thread creation (insert) and exit (remove). If owner is + // in the map, the thread is alive. We acquire the map lock + // briefly to avoid a TOCTOU race. + crate::pthread::OS_TID_TO_PTHREAD + .lock() + .iter() + .any(|(_os_tid, pthread)| { + let tid = unsafe { (*pthread).os_tid.get().read() }; + tid.thread_id as u32 == owner + }) + } +} diff --git a/src/raw_cell.rs b/src/raw_cell.rs new file mode 100644 index 0000000000..998fccdca0 --- /dev/null +++ b/src/raw_cell.rs @@ -0,0 +1,50 @@ +use core::cell::UnsafeCell; + +/// Wrapper over `UnsafeCell` that can directly be used in statics, where all modifications require +/// unsafe. +#[repr(transparent)] +pub struct RawCell { + inner: UnsafeCell, +} +impl RawCell { + #[inline] + pub const fn new(t: T) -> Self { + Self { + inner: UnsafeCell::new(t), + } + } + #[inline] + pub fn as_mut_ptr(&self) -> *mut T { + self.inner.get() + } + #[inline] + pub fn get_mut(&mut self) -> &mut T { + self.inner.get_mut() + } + #[inline] + pub fn into_inner(self) -> T { + self.inner.into_inner() + } + #[inline] + pub unsafe fn unsafe_ref(&self) -> &T { + unsafe { &*self.inner.get() } + } + #[inline] + pub unsafe fn unsafe_set(&self, t: T) { + unsafe { *self.inner.get() = t }; + } + #[inline] + pub unsafe fn unsafe_mut(&self) -> &mut T { + unsafe { &mut *self.inner.get() } + } +} + +// SAFETY: Sync requires that no safe interface be allowed to act on &self in a way that is +// undefined behavior when accessed concurrently. The interface above only allows get, set, and +// as_mut_ptr, where the former two that access memory are unsafe anyway. +unsafe impl Sync for RawCell {} + +const _: () = { + // Check that RawCell works for non-Sync types. + static X: RawCell<*mut ()> = RawCell::new(core::ptr::null_mut()); +}; diff --git a/src/start.rs b/src/start.rs index 2082dc84ef..915056e5b5 100644 --- a/src/start.rs +++ b/src/start.rs @@ -1,11 +1,17 @@ -use alloc::vec::Vec; -use core::{intrinsics, ptr}; +//! Startup code. + +use alloc::{boxed::Box, vec::Vec}; +use core::ptr; + +#[cfg(target_os = "redox")] +use generic_rt::ExpectTlsFree; use crate::{ - header::{stdio, stdlib}, - ld_so, - platform::{self, new_mspace, types::*, Pal, Sys}, ALLOCATOR, + header::{libgen, stdio, stdlib}, + ld_so::{self, linker::Linker}, + platform::{self, Pal, Sys, get_auxvs, types::*}, + sync::mutex::Mutex, }; #[repr(C)] @@ -16,7 +22,7 @@ pub struct Stack { impl Stack { pub fn argv(&self) -> *const *const c_char { - &self.argv0 as *const _ + ptr::from_ref(&self.argv0) } pub fn envp(&self) -> *const *const c_char { @@ -29,25 +35,39 @@ impl Stack { while !(*envp).is_null() { envp = envp.add(1); } - envp.add(1) as *const (usize, usize) + envp.add(1).cast::<(usize, usize)>() } } } unsafe fn copy_string_array(array: *const *const c_char, len: usize) -> Vec<*mut c_char> { + use crate::header::string::strlen; + let mut vec = Vec::with_capacity(len + 1); + let mut lengths = Vec::with_capacity(len); + let mut size = 0; for i in 0..len { - let item = *array.add(i); - let mut len = 0; - while *item.add(len) != 0 { - len += 1; + let item = unsafe { *array.add(i) }; + lengths.push(unsafe { strlen(item) } + 1); + size += lengths[i]; + } + + // Programs unfortunately rely on the strings being contiguous in memory. For example: + // https://github.com/libuv/libuv/blob/12d0dd48e3c6baf1e2f0d9f85f11f0ef58285d6f/src/unix/proctitle.c#L87 + let mut offset = 0; + let buf = unsafe { platform::alloc(size).cast::() }; + + for i in 0..len { + let dest_buf = unsafe { buf.add(offset) }; + let item = unsafe { *array.add(i) }; + let len = lengths[i]; + + unsafe { + ptr::copy_nonoverlapping(item, dest_buf, len); } - let buf = platform::alloc(len + 1) as *mut c_char; - for i in 0..=len { - *buf.add(i) = *item.add(i); - } - vec.push(buf); + vec.push(dest_buf); + offset += len; } vec.push(ptr::null_mut()); vec @@ -55,28 +75,33 @@ unsafe fn copy_string_array(array: *const *const c_char, len: usize) -> Vec<*mut // Since Redox and Linux are so similar, it is easy to accidentally run a binary from one on the // other. This will test that the current system is compatible with the current binary -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe fn relibc_verify_host() { if !Sys::verify() { - intrinsics::abort(); + unsafe { stdlib::abort() }; } } -#[link_section = ".init_array"] +#[unsafe(link_section = ".init_array")] #[used] static INIT_ARRAY: [extern "C" fn(); 1] = [init_array]; -static mut init_complete: bool = false; +static mut INIT_COMPLETE: bool = false; + +#[used] +#[unsafe(no_mangle)] +static mut __relibc_init_environ: *mut *mut c_char = ptr::null_mut(); fn alloc_init() { unsafe { - if let Some(tcb) = ld_so::tcb::Tcb::current() { - if tcb.mspace != 0 { - ALLOCATOR.set_book_keeper(tcb.mspace); - } else if ALLOCATOR.get_book_keeper() == 0 { - ALLOCATOR.set_book_keeper(new_mspace()); - } - } else if ALLOCATOR.get_book_keeper() == 0 { - ALLOCATOR.set_book_keeper(new_mspace()); + if INIT_COMPLETE { + return; + } + } + unsafe { + if let Some(tcb) = ld_so::tcb::Tcb::current() + && !tcb.mspace.is_null() + { + ALLOCATOR.set(tcb.mspace); } } } @@ -88,7 +113,7 @@ extern "C" fn init_array() { // memory allocator before doing anything else. unsafe { - if init_complete { + if INIT_COMPLETE { return; } } @@ -96,88 +121,139 @@ extern "C" fn init_array() { alloc_init(); io_init(); - extern "C" { - fn pthread_init(); - } unsafe { - pthread_init(); - init_complete = true + if platform::environ.is_null() { + platform::environ = __relibc_init_environ; + } + } + + unsafe { + crate::pthread::init(); + INIT_COMPLETE = true } } + fn io_init() { unsafe { - // Initialize stdin/stdout/stderr, see https://github.com/rust-lang/rust/issues/51718 - stdio::stdin = stdio::default_stdin.get(); - stdio::stdout = stdio::default_stdout.get(); - stdio::stderr = stdio::default_stderr.get(); + // Initialize stdin/stdout/stderr. + // TODO: const fn initialization of FILE + stdio::stdin = stdio::default_stdin().get(); + stdio::stdout = stdio::default_stdout().get(); + stdio::stderr = stdio::default_stderr().get(); } } #[inline(never)] -#[no_mangle] -pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! { - extern "C" { +#[unsafe(no_mangle)] +pub unsafe extern "C" fn relibc_start_v1( + sp: &'static Stack, + main: unsafe extern "C" fn( + argc: isize, + argv: *mut *mut c_char, + envp: *mut *mut c_char, + ) -> c_int, +) -> ! { + unsafe extern "C" { static __preinit_array_start: extern "C" fn(); static __preinit_array_end: extern "C" fn(); static __init_array_start: extern "C" fn(); static __init_array_end: extern "C" fn(); - - fn _init(); - fn main(argc: isize, argv: *mut *mut c_char, envp: *mut *mut c_char) -> c_int; } // Ensure correct host system before executing more system calls - relibc_verify_host(); + unsafe { relibc_verify_host() }; + + #[cfg(target_os = "redox")] + let thr_fd = redox_rt::proc::FdGuard::new( + unsafe { + crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD) + } + .expect_notls("no thread fd present"), + ) + .to_upper() + .expect_notls("failed to move thread fd to upper table"); // Initialize TLS, if necessary - ld_so::init(sp); + unsafe { + ld_so::init( + sp, + #[cfg(target_os = "redox")] + thr_fd, + ) + }; // Set up the right allocator... // if any memory rust based memory allocation happen before this step .. we are doomed. alloc_init(); + if let Some(tcb) = unsafe { ld_so::tcb::Tcb::current() } { + // Update TCB mspace + if tcb.mspace.is_null() { + tcb.mspace = ALLOCATOR.get(); + } + + // Set linker pointer if necessary + if tcb.linker_ptr.is_null() { + //TODO: get ld path + let linker = Linker::new(ld_so::linker::Config::default()); + //TODO: load root object + tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); + } + #[cfg(target_os = "redox")] + redox_rt::signal::setup_sighandler(&tcb.os_specific, true); + } + // Set up argc and argv let argc = sp.argc; let argv = sp.argv(); - platform::inner_argv = copy_string_array(argv, argc as usize); - platform::argv = platform::inner_argv.as_mut_ptr(); - - // Set up envp - let envp = sp.envp(); - let mut len = 0; - while !(*envp.add(len)).is_null() { - len += 1; + unsafe { platform::inner_argv.unsafe_set(copy_string_array(argv, argc as usize)) }; + unsafe { platform::argv = platform::inner_argv.unsafe_mut().as_mut_ptr() }; + // Special code for program_invocation_name and program_invocation_short_name + if let Some(arg) = unsafe { platform::inner_argv.unsafe_ref() }.first() { + unsafe { platform::program_invocation_name = *arg }; + unsafe { platform::program_invocation_short_name = libgen::basename(*arg) }; + } + // We check for NULL here since ld.so might already have initialized it for us, and we don't + // want to overwrite it if constructors in .init_array of dependency libraries have called + // setenv. + if unsafe { platform::environ }.is_null() { + // Set up envp + let envp = sp.envp(); + let mut len = 0; + while !(unsafe { *envp.add(len) }).is_null() { + len += 1; + } + unsafe { platform::OUR_ENVIRON.unsafe_set(copy_string_array(envp, len)) }; + unsafe { platform::environ = platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr() }; } - platform::inner_environ = copy_string_array(envp, len); - platform::environ = platform::inner_environ.as_mut_ptr(); + let auxvs = unsafe { get_auxvs(sp.auxv().cast()) }; + unsafe { crate::platform::init(auxvs) }; init_array(); + unsafe { crate::platform::logger::init() }; // Run preinit array { - let mut f = &__preinit_array_start as *const _; + let mut f = unsafe { &__preinit_array_start } as *const _; #[allow(clippy::op_ref)] - while f < &__preinit_array_end { - (*f)(); - f = f.offset(1); + while f < &raw const __preinit_array_end { + (unsafe { *f })(); + f = unsafe { f.offset(1) }; } } - // Call init section - _init(); - // Run init array { - let mut f = &__init_array_start as *const _; + let mut f = unsafe { &__init_array_start } as *const _; #[allow(clippy::op_ref)] - while f < &__init_array_end { - (*f)(); - f = f.offset(1); + while f < &raw const __init_array_end { + (unsafe { *f })(); + f = unsafe { f.offset(1) }; } } // not argv or envp, because programs like bash try to modify this *const* pointer :| - stdlib::exit(main(argc, platform::argv, platform::environ)); + unsafe { stdlib::exit(main(argc, platform::argv, platform::environ)) }; unreachable!(); } diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs new file mode 100644 index 0000000000..b5847b5363 --- /dev/null +++ b/src/sync/barrier.rs @@ -0,0 +1,76 @@ +use core::{ + num::NonZeroU32, + sync::atomic::{AtomicU32, Ordering}, +}; + +pub struct Barrier { + original_count: NonZeroU32, + // 4 + lock: crate::sync::Mutex, + // 16 + cvar: FutexState, + // 24 +} +#[derive(Debug)] +struct Inner { + _unused0: u32, + _unused1: u32, +} + +struct FutexState { + count: AtomicU32, + sense: AtomicU32, +} + +impl FutexState { + const fn new(count: u32) -> Self { + Self { + count: AtomicU32::new(count), + sense: AtomicU32::new(0), + } + } +} + +pub enum WaitResult { + Waited, + NotifiedAll, +} + +impl Barrier { + pub fn new(count: NonZeroU32) -> Self { + Self { + original_count: count, + lock: crate::sync::Mutex::new(Inner { + _unused0: 0, + _unused1: 0, + }), + cvar: FutexState::new(count.get()), + } + } + pub fn wait(&self) -> WaitResult { + let _ = &self.lock; + let sense = self.cvar.sense.load(Ordering::Acquire); + + if self.cvar.count.fetch_sub(1, Ordering::AcqRel) == 1 { + self.cvar + .count + .store(self.original_count.get(), Ordering::Relaxed); + self.cvar + .sense + .store(sense.wrapping_add(1), Ordering::Release); + crate::sync::futex_wake(&self.cvar.sense, i32::MAX); + + WaitResult::NotifiedAll + } else { + // SMP fix: wait directly on the barrier generation word instead of routing through the + // condvar unlock->futex_wait path. If the last thread flips `sense` after we load it + // but before our futex wait starts, the futex observes a stale value and returns + // immediately instead of sleeping forever after a missed broadcast wakeup. + while self.cvar.sense.load(Ordering::Acquire) == sense { + let _ = crate::sync::futex_wait(&self.cvar.sense, sense, None); + } + + WaitResult::Waited + } + } +} diff --git a/src/sync/barrier.rs.unused b/src/sync/barrier.rs.unused new file mode 100644 index 0000000000..c07c9d3ca3 --- /dev/null +++ b/src/sync/barrier.rs.unused @@ -0,0 +1,91 @@ +use core::cmp; +use core::num::NonZeroU32; +use core::sync::atomic::{AtomicU32 as AtomicUint, Ordering}; + +pub struct Barrier { + waited_count: AtomicUint, + notified_count: AtomicUint, + cycles_count: AtomicUint, + original_count: NonZeroU32, +} + +pub enum WaitResult { + Waited, + NotifiedAll, +} + +impl Barrier { + pub fn new(count: NonZeroU32) -> Self { + Self { + waited_count: AtomicUint::new(0), + notified_count: AtomicUint::new(0), + cycles_count: AtomicUint::new(0), + original_count: count, + } + } + pub fn wait(&self) -> WaitResult { + // The barrier wait operation can be divided into two parts: (1) incrementing the wait count where + // N-1 waiters wait and one notifies the rest, and (2) notifying all threads that have been + // waiting. + let original_count = self.original_count.get(); + let mut new = self.waited_count.fetch_add(1, Ordering::Acquire) + 1; + let original_cycle_count = self.cycles_count.load(Ordering::Acquire); + + loop { + let result = match Ord::cmp(&new, &original_count) { + cmp::Ordering::Less => { + // new < original_count, i.e. we were one of the threads that incremented the + // counter, and will return without SERIAL_THREAD later, but need to continue + // waiting for the last waiter to notify the others. + + loop { + let count = self.waited_count.load(Ordering::Acquire); + + if count >= original_count { break } + + let _ = crate::sync::futex_wait(&self.waited_count, count, None); + } + + WaitResult::Waited + } + cmp::Ordering::Equal => { + // new == original_count, i.e. we were the one thread doing the last increment, and we + // will be responsible for waking up all other waiters. + + crate::sync::futex_wake(&self.waited_count, original_count as i32 - 1); + + WaitResult::NotifiedAll + } + cmp::Ordering::Greater => { + let mut next_cycle_count; + + loop { + next_cycle_count = self.cycles_count.load(Ordering::Acquire); + + if next_cycle_count != original_cycle_count { + break; + } + + crate::sync::futex_wait(&self.cycles_count, next_cycle_count, None); + } + let difference = next_cycle_count.wrapping_sub(original_cycle_count); + + new = new.saturating_sub(difference * original_cycle_count); + continue; + } + }; + + if self.notified_count.fetch_add(1, Ordering::AcqRel) + 1 == original_count { + self.notified_count.store(0, Ordering::Relaxed); + // Cycle count can be incremented nonatomically here, as this branch can only be + // reached once until waited_count is decremented again. + self.cycles_count.store(self.cycles_count.load(Ordering::Acquire).wrapping_add(1), Ordering::Release); + + let _ = self.waited_count.fetch_sub(original_count, Ordering::Relaxed); + + let _ = crate::sync::futex_wake(&self.cycles_count, i32::MAX); + } + return result; + } + } +} diff --git a/src/sync/cond.rs b/src/sync/cond.rs new file mode 100644 index 0000000000..171a35dc3a --- /dev/null +++ b/src/sync/cond.rs @@ -0,0 +1,140 @@ +// Used design from https://www.remlab.net/op/futex-condvar.shtml + +use crate::{ + error::Errno, + header::{ + bits_timespec::timespec, + errno::{EINVAL, ETIMEDOUT}, + pthread::*, + time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic}, + }, + platform::types::clockid_t, +}; + +use core::sync::atomic::{AtomicU32 as AtomicUint, Ordering}; + +#[derive(Clone, Copy)] +pub struct CondAttr { + pub clock: clockid_t, + pub pshared: i32, +} + +impl Default for CondAttr { + fn default() -> Self { + Self { + // defaults according to POSIX + clock: CLOCK_REALTIME, // for timedwait + pshared: PTHREAD_PROCESS_PRIVATE, // TODO + } + } +} + +pub struct Cond { + cur: AtomicUint, + prev: AtomicUint, +} + +type Result = core::result::Result; + +impl Default for Cond { + fn default() -> Self { + Self::new() + } +} + +impl Cond { + pub fn new() -> Self { + Self { + cur: AtomicUint::new(0), + prev: AtomicUint::new(0), + } + } + fn wake(&self, count: i32) -> Result<(), Errno> { + // This is formally correct as long as we don't have more than u32::MAX threads. + let prev = self.prev.load(Ordering::Relaxed); + self.cur.store(prev.wrapping_add(1), Ordering::Relaxed); + + crate::sync::futex_wake(&self.cur, count); + Ok(()) + } + pub fn broadcast(&self) -> Result<(), Errno> { + self.wake(i32::MAX) + } + pub fn signal(&self) -> Result<(), Errno> { + // POSIX requires pthread_cond_signal to wake AT LEAST ONE waiter that + // is currently waiting on the condition variable, but it must not + // wake all waiters (that is pthread_cond_broadcast semantics). + // Wake exactly one via FUTEX_WAKE with count=1. Using broadcast() here + // was a thundering-herd bug: every cond_signal woke every waiter on + // every CPU. Fixed 2026-07-02 (Red Bear OS multi-threading plan, + // Phase 0a). + self.wake(1) + } + pub fn clockwait( + &self, + mutex: &RlctMutex, + timeout: ×pec, + clock_id: clockid_t, + ) -> Result<(), Errno> { + let relative = match clock_id { + // FUTEX expect monotonic clock + CLOCK_MONOTONIC => timeout.clone(), + CLOCK_REALTIME => timespec_realtime_to_monotonic(timeout.clone())?, + _ => return Err(Errno(EINVAL)), + }; + + self.wait_inner(mutex, Some(&relative)) + } + pub fn timedwait(&self, mutex: &RlctMutex, timeout: ×pec) -> Result<(), Errno> { + // TODO: The clock can be other than CLOCK_REALTIME depends on CondAttr + self.clockwait(mutex, timeout, CLOCK_REALTIME) + } + fn wait_inner(&self, mutex: &RlctMutex, timeout: Option<×pec>) -> Result<(), Errno> { + self.wait_inner_generic(|| mutex.unlock(), || mutex.lock(), timeout) + } + pub fn wait_inner_typedmutex<'lock, T>( + &self, + guard: crate::sync::MutexGuard<'lock, T>, + ) -> crate::sync::MutexGuard<'lock, T> { + let mut newguard = None; + let lock = guard.mutex; + self.wait_inner_generic( + move || { + drop(guard); + Ok(()) + }, + || { + newguard = Some(lock.lock()); + Ok(()) + }, + None, + ) + .unwrap(); + newguard.unwrap() + } + // TODO: FUTEX_REQUEUE + fn wait_inner_generic( + &self, + unlock: impl FnOnce() -> Result<()>, + lock: impl FnOnce() -> Result<()>, + deadline: Option<×pec>, + ) -> Result<(), Errno> { + // TODO: Error checking for certain types (i.e. robust and errorcheck) of mutexes, e.g. if the + // mutex is not locked. + let current = self.cur.load(Ordering::Relaxed); + self.prev.store(current, Ordering::Relaxed); + + unlock()?; + let futex_r = crate::sync::futex_wait(&self.cur, current, deadline); + lock()?; + + match futex_r { + super::FutexWaitResult::Waited => Ok(()), + super::FutexWaitResult::Stale => Ok(()), + super::FutexWaitResult::TimedOut => Err(Errno(ETIMEDOUT)), + } + } + pub fn wait(&self, mutex: &RlctMutex) -> Result<(), Errno> { + self.wait_inner(mutex, None) + } +} diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 430abd5b78..9edda90ece 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -1,6 +1,15 @@ +//! Synchronization primitives. + +pub mod barrier; +pub mod cond; +// TODO: Merge with pthread_mutex pub mod mutex; + pub mod once; +pub mod pthread_mutex; +pub mod rwlock; pub mod semaphore; +pub mod waitval; pub use self::{ mutex::{Mutex, MutexGuard}, @@ -8,57 +17,206 @@ pub use self::{ semaphore::Semaphore, }; -use crate::platform::{types::*, Pal, Sys}; +use crate::{ + error::Errno, + header::{ + bits_timespec::timespec, + errno::{EAGAIN, EINTR, ETIMEDOUT}, + }, + out::Out, + platform::{Pal, Sys, types::c_int}, +}; use core::{ - cell::UnsafeCell, + hint, + mem::MaybeUninit, ops::Deref, - sync::atomic::{self, AtomicI32 as AtomicInt}, + ptr, + sync::atomic::{AtomicI32, AtomicI32 as AtomicInt, AtomicU32}, }; const FUTEX_WAIT: c_int = 0; const FUTEX_WAKE: c_int = 1; #[derive(Clone, Copy, PartialEq, Eq)] -enum AttemptStatus { +pub enum AttemptStatus { Desired, Waiting, Other, } +pub trait FutexTy { + fn conv(self) -> u32; +} +pub trait FutexAtomicTy { + type Ty: FutexTy; + + fn ptr(&self) -> *mut Self::Ty; +} +impl FutexTy for u32 { + fn conv(self) -> u32 { + self + } +} +impl FutexTy for i32 { + fn conv(self) -> u32 { + self as u32 + } +} +impl FutexAtomicTy for AtomicU32 { + type Ty = u32; + + fn ptr(&self) -> *mut u32 { + // TODO: Change when Redox's toolchain is updated. This is not about targets, but compiler + // versions! + /* + + #[cfg(target_os = "redox")] + return AtomicU32::as_ptr(self); + + #[cfg(target_os = "linux")] + return AtomicU32::as_mut_ptr(self); + + */ + + // AtomicU32::as_mut_ptr internally calls UnsafeCell::get, which itself simply does (&self + // as *const Self as *mut Self). + ptr::from_ref::(self) as *mut u32 + } +} +impl FutexAtomicTy for AtomicI32 { + type Ty = i32; + + fn ptr(&self) -> *mut i32 { + // TODO + /*#[cfg(target_os = "redox")] + return AtomicI32::as_ptr(self); + + #[cfg(target_os = "linux")] + return AtomicI32::as_mut_ptr(self);*/ + + ptr::from_ref::(self) as *mut i32 + } +} + +pub unsafe fn futex_wake_ptr(ptr: *mut impl FutexTy, n: i32) -> usize { + // TODO: unwrap_unchecked? + unsafe { Sys::futex_wake(ptr.cast(), n as u32) }.unwrap() as usize +} +pub unsafe fn futex_wait_ptr( + ptr: *mut T, + value: T, + deadline_opt: Option<×pec>, +) -> FutexWaitResult { + match unsafe { Sys::futex_wait(ptr.cast(), value.conv(), deadline_opt) } { + Ok(()) | Err(Errno(EINTR)) => FutexWaitResult::Waited, + Err(Errno(EAGAIN)) => FutexWaitResult::Stale, + Err(Errno(ETIMEDOUT)) if deadline_opt.is_some() => FutexWaitResult::TimedOut, + Err(err) => { + todo_error!(0, err, "futex failed"); + FutexWaitResult::Waited + } + } +} +pub fn futex_wake(atomic: &impl FutexAtomicTy, n: i32) -> usize { + unsafe { futex_wake_ptr(atomic.ptr(), n) } +} +pub fn futex_wait( + atomic: &T, + value: T::Ty, + deadline_opt: Option<×pec>, +) -> FutexWaitResult { + unsafe { futex_wait_ptr(atomic.ptr(), value, deadline_opt) } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FutexWaitResult { + Waited, // possibly spurious + Stale, // outdated value + TimedOut, +} + +pub fn rttime() -> timespec { + unsafe { + let mut time = MaybeUninit::uninit(); + + if let Ok(()) = Sys::clock_gettime( + crate::header::time::CLOCK_REALTIME, + Out::from_uninit_mut(&mut time), + ) {}; // TODO handle error + + time.assume_init() + } +} + +pub fn wait_until_generic(word: &AtomicInt, attempt: F1, mark_long: F2, long: c_int) +where + F1: Fn(&AtomicInt) -> AttemptStatus, + F2: Fn(&AtomicInt) -> AttemptStatus, +{ + // First, try spinning for really short durations + for _ in 0..999 { + hint::spin_loop(); + if attempt(word) == AttemptStatus::Desired { + return; + } + } + + // One last attempt, to initiate "previous" + let mut previous = attempt(word); + + // Ok, that seems to take quite some time. Let's go into a + // longer, more patient, wait. + loop { + if previous == AttemptStatus::Desired { + return; + } + + if + // If we or somebody else already initiated a long + // wait, OR + previous == AttemptStatus::Waiting || + // Otherwise, unless our attempt to initiate a long + // wait informed us that we might be done waiting + mark_long(word) != AttemptStatus::Desired + { + futex_wait(word, long, None); + } + + previous = attempt(word); + } +} + /// Convenient wrapper around the "futex" system call for /// synchronization implementations -struct AtomicLock { - atomic: UnsafeCell, +#[repr(C)] +pub(crate) struct AtomicLock { + pub(crate) atomic: AtomicInt, } impl AtomicLock { pub const fn new(value: c_int) -> Self { Self { - atomic: UnsafeCell::new(AtomicInt::new(value)), + atomic: AtomicInt::new(value), } } pub fn notify_one(&self) { - Sys::futex(unsafe { &mut *self.atomic.get() }.get_mut(), FUTEX_WAKE, 1); + futex_wake(&self.atomic, 1); } pub fn notify_all(&self) { - Sys::futex( - unsafe { &mut *self.atomic.get() }.get_mut(), - FUTEX_WAKE, - c_int::max_value(), - ); + futex_wake(&self.atomic, i32::MAX); } - pub fn wait_if(&self, value: c_int) { - Sys::futex( - unsafe { &mut *self.atomic.get() }.get_mut(), - FUTEX_WAIT, - value, - ); + pub fn wait_if(&self, value: c_int, timeout_opt: Option<×pec>) { + self.wait_if_raw(value, timeout_opt); } + pub fn wait_if_raw(&self, value: c_int, timeout_opt: Option<×pec>) -> FutexWaitResult { + futex_wait(&self.atomic, value, timeout_opt) + } + /// A general way to efficiently wait for what might be a long time, using two closures: /// /// - `attempt` = Attempt to modify the atomic value to any - /// desired state. + /// desired state. /// - `mark_long` = Attempt to modify the atomic value to sign - /// that it want's to get notified when waiting is done. + /// that it want's to get notified when waiting is done. /// /// Both of these closures are allowed to spuriously give a /// non-success return value, they are used only as optimization @@ -77,43 +235,13 @@ impl AtomicLock { F1: Fn(&AtomicInt) -> AttemptStatus, F2: Fn(&AtomicInt) -> AttemptStatus, { - // First, try spinning for really short durations - for _ in 0..999 { - atomic::spin_loop_hint(); - if attempt(self) == AttemptStatus::Desired { - return; - } - } - - // One last attempt, to initiate "previous" - let mut previous = attempt(self); - - // Ok, that seems to take quite some time. Let's go into a - // longer, more patient, wait. - loop { - if previous == AttemptStatus::Desired { - return; - } - - if - // If we or somebody else already initiated a long - // wait, OR - previous == AttemptStatus::Waiting || - // Otherwise, unless our attempt to initiate a long - // wait informed us that we might be done waiting - mark_long(self) != AttemptStatus::Desired - { - self.wait_if(long); - } - - previous = attempt(self); - } + wait_until_generic(&self.atomic, attempt, mark_long, long) } } impl Deref for AtomicLock { type Target = AtomicInt; fn deref(&self) -> &Self::Target { - unsafe { &*self.atomic.get() } + &self.atomic } } diff --git a/src/sync/mutex.rs b/src/sync/mutex.rs index 127c78ca84..fc8cda2892 100644 --- a/src/sync/mutex.rs +++ b/src/sync/mutex.rs @@ -1,21 +1,55 @@ use super::{AtomicLock, AttemptStatus}; -use crate::platform::types::*; +use crate::platform::types::c_int; use core::{ cell::UnsafeCell, ops::{Deref, DerefMut}, - sync::atomic::Ordering::SeqCst, + sync::atomic::{AtomicI32 as AtomicInt, Ordering}, }; -const UNLOCKED: c_int = 0; -const LOCKED: c_int = 1; -const WAITING: c_int = 2; +pub(crate) const UNLOCKED: c_int = 0; +pub(crate) const LOCKED: c_int = 1; +pub(crate) const WAITING: c_int = 2; pub struct Mutex { - lock: AtomicLock, + pub(crate) lock: AtomicLock, content: UnsafeCell, } unsafe impl Send for Mutex {} unsafe impl Sync for Mutex {} + +pub(crate) unsafe fn manual_try_lock_generic(word: &AtomicInt) -> bool { + word.compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) + .is_ok() +} +pub(crate) unsafe fn manual_lock_generic(word: &AtomicInt) { + crate::sync::wait_until_generic( + word, + |lock| { + lock.compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed) + .map(|_| AttemptStatus::Desired) + .unwrap_or_else(|e| match e { + WAITING => AttemptStatus::Waiting, + _ => AttemptStatus::Other, + }) + }, + |lock| match lock + // TODO: Ordering + .compare_exchange_weak(LOCKED, WAITING, Ordering::SeqCst, Ordering::SeqCst) + .unwrap_or_else(|e| e) + { + UNLOCKED => AttemptStatus::Desired, + WAITING => AttemptStatus::Waiting, + _ => AttemptStatus::Other, + }, + WAITING, + ); +} +pub(crate) unsafe fn manual_unlock_generic(word: &AtomicInt) { + if word.swap(UNLOCKED, Ordering::Release) == WAITING { + crate::sync::futex_wake(word, i32::MAX); + } +} + impl Mutex { /// Create a new mutex pub const fn new(content: T) -> Self { @@ -42,45 +76,30 @@ impl Mutex { /// on failure. You should probably not worry about this, it's used for /// internal optimizations. pub unsafe fn manual_try_lock(&self) -> Result<&mut T, c_int> { - self.lock - .compare_exchange(UNLOCKED, LOCKED, SeqCst, SeqCst) - .map(|_| &mut *self.content.get()) + if unsafe { manual_try_lock_generic(&self.lock) } { + Ok(unsafe { &mut *self.content.get() }) + } else { + Err(0) + } } /// Lock the mutex, returning the inner content. After doing this, it's /// your responsibility to unlock it after usage. Mostly useful for FFI: /// Prefer normal .lock() where possible. pub unsafe fn manual_lock(&self) -> &mut T { - self.lock.wait_until( - |lock| { - lock.compare_exchange_weak(UNLOCKED, LOCKED, SeqCst, SeqCst) - .map(|_| AttemptStatus::Desired) - .unwrap_or_else(|e| match e { - WAITING => AttemptStatus::Waiting, - _ => AttemptStatus::Other, - }) - }, - |lock| match lock - .compare_exchange_weak(LOCKED, WAITING, SeqCst, SeqCst) - .unwrap_or_else(|e| e) - { - UNLOCKED => AttemptStatus::Desired, - WAITING => AttemptStatus::Waiting, - _ => AttemptStatus::Other, - }, - WAITING, - ); - &mut *self.content.get() + unsafe { manual_lock_generic(&self.lock) }; + unsafe { &mut *self.content.get() } } /// Unlock the mutex, if it's locked. pub unsafe fn manual_unlock(&self) { - if self.lock.swap(UNLOCKED, SeqCst) == WAITING { - self.lock.notify_one(); - } + unsafe { manual_unlock_generic(&self.lock) } + } + pub fn as_ptr(&self) -> *mut T { + self.content.get() } /// Tries to lock the mutex and returns a guard that automatically unlocks /// the mutex when it falls out of scope. - pub fn try_lock(&self) -> Option> { + pub fn try_lock(&self) -> Option> { unsafe { self.manual_try_lock().ok().map(|content| MutexGuard { mutex: self, @@ -90,7 +109,7 @@ impl Mutex { } /// Locks the mutex and returns a guard that automatically unlocks the /// mutex when it falls out of scope. - pub fn lock(&self) -> MutexGuard { + pub fn lock(&self) -> MutexGuard<'_, T> { MutexGuard { mutex: self, content: unsafe { self.manual_lock() }, @@ -99,14 +118,14 @@ impl Mutex { } pub struct MutexGuard<'a, T: 'a> { - mutex: &'a Mutex, + pub(crate) mutex: &'a Mutex, content: &'a mut T, } impl<'a, T> Deref for MutexGuard<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { - &self.content + self.content } } impl<'a, T> DerefMut for MutexGuard<'a, T> { diff --git a/src/sync/once.rs b/src/sync/once.rs index 5118d866d9..293ab84cb9 100644 --- a/src/sync/once.rs +++ b/src/sync/once.rs @@ -1,6 +1,10 @@ -use super::{AtomicLock, AttemptStatus}; +use super::AttemptStatus; use crate::platform::types::*; -use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering::SeqCst}; +use core::{ + cell::UnsafeCell, + mem::MaybeUninit, + sync::atomic::{AtomicI32 as AtomicInt, Ordering}, +}; const UNINITIALIZED: c_int = 0; const INITIALIZING: c_int = 1; @@ -8,44 +12,79 @@ const WAITING: c_int = 2; const INITIALIZED: c_int = 3; pub struct Once { - status: AtomicLock, + status: AtomicInt, data: UnsafeCell>, } + +// SAFETY: +// +// Sending a Once is the same as sending a (wrapped) T. unsafe impl Send for Once {} -unsafe impl Sync for Once {} + +// SAFETY: +// +// For Once to be shared between threads without being unsound, only call_once needs to be safe, at +// the moment. +// +// Send requirement: the thread that gets to run the initializer function, will put a T in the cell +// which can then be accessed by other threads, thus T needs to be send. +// +// Sync requirement: after call_once has been called, it returns the value via &T, which naturally +// forces T to be Sync. +unsafe impl Sync for Once {} + impl Once { pub const fn new() -> Self { Self { - status: AtomicLock::new(UNINITIALIZED), + status: AtomicInt::new(UNINITIALIZED), data: UnsafeCell::new(MaybeUninit::uninit()), } } - pub fn call_once(&self, f: F) -> &mut T - where - F: FnOnce() -> T, - { - match self - .status - .compare_and_swap(UNINITIALIZED, INITIALIZING, SeqCst) - { - UNINITIALIZED => { - // We now have a lock, let's initiate things! - let ret = unsafe { &mut *self.data.get() }.write(f()); + pub fn call_once(&self, constructor: impl FnOnce() -> T) -> &T { + match self.status.compare_exchange( + UNINITIALIZED, + INITIALIZING, + // SAFETY: Success ordering: if the CAS succeeds, we technically need no + // synchronization besides the Release store to INITIALIZED, and Acquire here forbids + // possible loads in f() to be re-ordered before this CAS. One could argue whether or + // not that is reasonable, but the main point is that the success ordering must be at + // least as strong as the failure ordering. + Ordering::Acquire, + // SAFETY: Failure ordering: if the CAS fails, and status was INITIALIZING | WAITING, + // then Relaxed is sufficient, as it will have to be Acquire-loaded again later. If + // INITIALIZED is encountered however, it will nonatomically read the value in the + // Cell, which necessitates Acquire. + Ordering::Acquire, // TODO: On archs where this matters, use Relaxed and core::sync::atomic::fence? + ) { + Ok(_must_be_uninit) => { + // We now have exclusive access to the cell, let's initiate things! + unsafe { self.data.get().cast::().write(constructor()) }; // Mark the data as initialized - if self.status.swap(INITIALIZED, SeqCst) == WAITING { + if self.status.swap(INITIALIZED, Ordering::Release) == WAITING { // At least one thread is waiting on this to finish - self.status.notify_all(); + crate::sync::futex_wake(&self.status, i32::MAX); } } - INITIALIZING | WAITING => self.status.wait_until( - |lock| match lock.load(SeqCst) { + Err(INITIALIZING) | Err(WAITING) => crate::sync::wait_until_generic( + &self.status, + // SAFETY: An Acquire load is necessary for the nonatomic store by the thread + // running the constructor, to become visible. + |status| match status.load(Ordering::Acquire) { WAITING => AttemptStatus::Waiting, INITIALIZED => AttemptStatus::Desired, _ => AttemptStatus::Other, }, - |lock| match lock - .compare_exchange_weak(INITIALIZING, WAITING, SeqCst, SeqCst) + // SAFETY: Double-Acquire is necessary here as well, because if the CAS fails and + // it was INITIALIZED, the nonatomic write by the constructor thread, must be + // visible. + |status| match status + .compare_exchange_weak( + INITIALIZING, + WAITING, + Ordering::Acquire, + Ordering::Acquire, + ) .unwrap_or_else(|e| e) { WAITING => AttemptStatus::Waiting, @@ -54,12 +93,14 @@ impl Once { }, WAITING, ), - INITIALIZED => (), - _ => unreachable!("invalid state for Once"), + Err(INITIALIZED) => (), + + // TODO: Only for debug builds? + Err(_) => unreachable!("invalid state for Once"), } // At this point the data must be initialized! - unsafe { &mut *(&mut *self.data.get()).as_mut_ptr() } + unsafe { (&*self.data.get()).assume_init_ref() } } } impl Default for Once { @@ -67,3 +108,17 @@ impl Default for Once { Self::new() } } +// TODO: Drop doesn't work well in const fn, instead use a wrapper for relibc Rust code that adds +// Drop, and don't use that wrapper when writing the header file impls. +/* +impl Drop for Once { + fn drop(&mut self) { + unsafe { + if *self.status.get_mut() == INITIALIZED { + // SAFETY: It must be initialized, because of the above condition. + self.data.get_mut().assume_init_drop(); + } + } + } +} +*/ diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs new file mode 100644 index 0000000000..aa91bc20f7 --- /dev/null +++ b/src/sync/pthread_mutex.rs @@ -0,0 +1,404 @@ +use alloc::boxed::Box; +use core::{ + cell::Cell, + sync::atomic::{AtomicU32 as AtomicUint, Ordering}, +}; + +use crate::{ + error::Errno, + header::{bits_timespec::timespec, errno::*, pthread::*}, + platform::{Pal, Sys, types::c_int}, +}; + +use super::FutexWaitResult; + +pub struct RlctMutex { + // Actual locking word. + inner: AtomicUint, + recursive_count: AtomicUint, + + ty: Ty, + robust: bool, +} + +pub struct RobustMutexNode { + pub next: *mut RobustMutexNode, + pub prev: *mut RobustMutexNode, + pub mutex: *const RlctMutex, +} + +const STATE_UNLOCKED: u32 = 0; +const WAITING_BIT: u32 = 1 << 31; +const FUTEX_OWNER_DIED: u32 = 1 << 30; +const INDEX_MASK: u32 = !(WAITING_BIT | FUTEX_OWNER_DIED); + +// TODO: Lower limit is probably better. +const RECURSIVE_COUNT_MAX_INCLUSIVE: u32 = u32::MAX; +// TODO: How many spins should we do before it becomes more time-economical to enter kernel mode +// via futexes? +const SPIN_COUNT: usize = 100; + +impl RlctMutex { + pub(crate) fn new(attr: &RlctMutexAttr) -> Result { + let RlctMutexAttr { + prioceiling, + protocol, + pshared: _, + robust, + ty, + } = *attr; + + Ok(Self { + inner: AtomicUint::new(STATE_UNLOCKED), + recursive_count: AtomicUint::new(0), + robust: match robust { + PTHREAD_MUTEX_STALLED => false, + PTHREAD_MUTEX_ROBUST => true, + + _ => return Err(Errno(EINVAL)), + }, + ty: match ty { + PTHREAD_MUTEX_DEFAULT => Ty::Def, + PTHREAD_MUTEX_ERRORCHECK => Ty::Errck, + PTHREAD_MUTEX_RECURSIVE => Ty::Recursive, + PTHREAD_MUTEX_NORMAL => Ty::Normal, + + _ => return Err(Errno(EINVAL)), + }, + }) + } + pub fn prioceiling(&self) -> Result { + todo_skip!(0, "pthread_getprioceiling: not implemented"); + Ok(0) + } + pub fn replace_prioceiling(&self, _: c_int) -> Result { + todo_skip!(0, "pthread_setprioceiling: not implemented"); + Ok(0) + } + pub fn make_consistent(&self) -> Result<(), Errno> { + debug_assert!(self.robust, "make_consistent called on non-robust mutex"); + + if !self.robust { + return Err(Errno(EINVAL)); + } + + let current = self.inner.load(Ordering::Relaxed); + let owner = current & INDEX_MASK; + + if owner == os_tid_invalid_after_fork() && current & FUTEX_OWNER_DIED != 0 { + self.inner.store(0, Ordering::Release); + Ok(()) + } else { + Err(Errno(EINVAL)) + } + } + fn lock_inner(&self, deadline: Option<×pec>) -> Result<(), Errno> { + let this_thread = os_tid_invalid_after_fork(); + let mut spins_left = SPIN_COUNT; + + loop { + let result = self.inner.compare_exchange_weak( + STATE_UNLOCKED, + this_thread, + Ordering::Acquire, + Ordering::Relaxed, + ); + + match result { + Ok(_) => return self.finish_lock_acquire(false), + Err(thread) if thread & INDEX_MASK == this_thread && self.ty == Ty::Recursive => { + self.increment_recursive_count()?; + return Ok(()); + } + Err(thread) if thread & INDEX_MASK == this_thread && self.ty == Ty::Errck => { + return Err(Errno(EDEADLK)); + } + Err(thread) if thread & FUTEX_OWNER_DIED != 0 && thread & INDEX_MASK == 0 => { + return Err(Errno(ENOTRECOVERABLE)); + } + Err(thread) if thread & FUTEX_OWNER_DIED != 0 => { + if !self.robust { + return Err(Errno(ENOTRECOVERABLE)); + } + + let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread; + match self.inner.compare_exchange( + thread, + new_value, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return self.finish_lock_acquire(true), + Err(_) => continue, + } + } + Err(thread) if thread & INDEX_MASK == 0 => continue, + Err(thread) => { + let owner = thread & INDEX_MASK; + + if self.robust && !crate::pthread::mutex_owner_id_is_live(owner) { + let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread; + match self.inner.compare_exchange( + thread, + new_value, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return self.finish_lock_acquire(true), + Err(_) => continue, + } + } + + if spins_left > 0 { + spins_left -= 1; + core::hint::spin_loop(); + continue; + } + if crate::sync::futex_wait(&self.inner, thread, deadline) + == FutexWaitResult::TimedOut + { + return Err(Errno(ETIMEDOUT)); + } + } + } + } + } + pub fn lock(&self) -> Result<(), Errno> { + self.lock_inner(None) + } + pub fn lock_with_timeout(&self, deadline: ×pec) -> Result<(), Errno> { + self.lock_inner(Some(deadline)) + } + fn finish_lock_acquire(&self, owner_dead: bool) -> Result<(), Errno> { + if self.ty == Ty::Recursive { + self.increment_recursive_count()?; + } + if self.robust { + add_to_robust_list(self); + } + + if owner_dead { + Err(Errno(EOWNERDEAD)) + } else { + Ok(()) + } + } + fn increment_recursive_count(&self) -> Result<(), Errno> { + // We don't have to worry about asynchronous signals here, since pthread_mutex_trylock + // is not async-signal-safe. + // + // TODO: Maybe just use Cell? Send/Sync doesn't matter much anyway, and will be + // protected by the lock itself anyway. + + let prev_recursive_count = self.recursive_count.load(Ordering::Relaxed); + + if prev_recursive_count == RECURSIVE_COUNT_MAX_INCLUSIVE { + return Err(Errno(EAGAIN)); + } + + self.recursive_count + .store(prev_recursive_count + 1, Ordering::Relaxed); + + Ok(()) + } + pub fn try_lock(&self) -> Result<(), Errno> { + let this_thread = os_tid_invalid_after_fork(); + + loop { + let current = self.inner.load(Ordering::Relaxed); + + if current == STATE_UNLOCKED { + match self.inner.compare_exchange( + STATE_UNLOCKED, + this_thread, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return self.finish_lock_acquire(false), + Err(_) => continue, + } + } + + let owner = current & INDEX_MASK; + + if owner == this_thread && self.ty == Ty::Recursive { + self.increment_recursive_count()?; + return Ok(()); + } + + if owner == this_thread && self.ty == Ty::Errck { + return Err(Errno(EDEADLK)); + } + + if self.robust && (current & FUTEX_OWNER_DIED != 0 || (owner != 0 && !crate::pthread::mutex_owner_id_is_live(owner))) { + let new_value = (current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread; + match self.inner.compare_exchange( + current, + new_value, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return self.finish_lock_acquire(true), + Err(_) => continue, + } + } + + return Err(Errno(EBUSY)); + } + } + // Safe because we are not protecting any data. + pub fn unlock(&self) -> Result<(), Errno> { + let current = self.inner.load(Ordering::Relaxed); + + if self.robust || matches!(self.ty, Ty::Recursive | Ty::Errck) { + if current & INDEX_MASK != os_tid_invalid_after_fork() { + return Err(Errno(EPERM)); + } + + core::sync::atomic::fence(Ordering::Acquire); + } + + if self.ty == Ty::Recursive { + let next = self.recursive_count.load(Ordering::Relaxed) - 1; + self.recursive_count.store(next, Ordering::Relaxed); + + if next > 0 { + return Ok(()); + } + } + + if self.robust { + remove_from_robust_list(self); + } + + let new_state = if self.robust && current & FUTEX_OWNER_DIED != 0 { + FUTEX_OWNER_DIED + } else { + STATE_UNLOCKED + }; + + self.inner.store(new_state, Ordering::Release); + crate::sync::futex_wake(&self.inner, i32::MAX); + + Ok(()) + } +} + +pub(crate) unsafe fn mark_robust_mutexes_dead(thread: &crate::pthread::Pthread) { + let head = thread.robust_list_head.get(); + // Null guard: if the thread's robust_list_head is null + // (e.g. the thread was created but never locked a robust + // mutex, or the kernel's robust list walk is invoked for a + // thread that doesn't support it), the list is empty and + // there's nothing to do. Without this check, dereferencing + // *head on a null pointer would be UB. + if unsafe { (*head).is_null() } { + return; + } + let this_thread = os_tid_invalid_after_fork(); + let mut node = unsafe { *head }; + + unsafe { *head = core::ptr::null_mut() }; + + while !node.is_null() { + let next = unsafe { (*node).next }; + let mutex = unsafe { &*(*node).mutex }; + let current = mutex.inner.load(Ordering::Relaxed); + + if current & INDEX_MASK == this_thread { + mutex + .inner + .store((current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread, Ordering::Release); + crate::sync::futex_wake(&mutex.inner, i32::MAX); + } + + unsafe { drop(Box::from_raw(node)) }; + node = next; + } +} + +#[repr(u8)] +#[derive(PartialEq)] +enum Ty { + // The only difference between PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_DEFAULT appears to be + // that "normal" mutexes deadlock if locked multiple times on the same thread, whereas + // "default" mutexes are UB in that case. So we can treat them as being the same type. + Normal, + Def, + + Errck, + Recursive, +} + +// Children after fork can only call async-signal-safe functions until they exec. +#[thread_local] +static CACHED_OS_TID_INVALID_AFTER_FORK: Cell = Cell::new(0); + +fn add_to_robust_list(mutex: &RlctMutex) { + let thread = crate::pthread::current_thread().expect("current thread not present"); + let node_ptr = Box::into_raw(Box::new(RobustMutexNode { + next: core::ptr::null_mut(), + prev: core::ptr::null_mut(), + mutex: core::ptr::from_ref(mutex), + })); + + unsafe { + let head = thread.robust_list_head.get(); + if !(*head).is_null() { + (**head).prev = node_ptr; + } + (*node_ptr).next = *head; + *head = node_ptr; + } +} + +fn remove_from_robust_list(mutex: &RlctMutex) { + let thread = match crate::pthread::current_thread() { + Some(thread) => thread, + None => return, + }; + + unsafe { + let mut node = *thread.robust_list_head.get(); + + while !node.is_null() { + if core::ptr::eq((*node).mutex, core::ptr::from_ref(mutex)) { + if !(*node).prev.is_null() { + (*(*node).prev).next = (*node).next; + } else { + *thread.robust_list_head.get() = (*node).next; + } + + if !(*node).next.is_null() { + (*(*node).next).prev = (*node).prev; + } + + drop(Box::from_raw(node)); + return; + } + + node = (*node).next; + } + } +} + +// Assumes TIDs are unique between processes, which I only know is true for Redox. +fn os_tid_invalid_after_fork() -> u32 { + // TODO: Coordinate better if using shared == PTHREAD_PROCESS_SHARED, with up to 2^32 separate + // threads within possibly distinct processes, using the mutex. OS thread IDs on Redox are + // pointer-sized, but relibc and POSIX uses int everywhere. + + let value = CACHED_OS_TID_INVALID_AFTER_FORK.get(); + + if value == 0 { + let tid = Sys::gettid(); + + assert_ne!(tid, -1, "failed to obtain current thread ID"); + + CACHED_OS_TID_INVALID_AFTER_FORK.set(tid as u32); + + tid as u32 + } else { + value + } +} diff --git a/src/sync/reentrant_mutex.rs b/src/sync/reentrant_mutex.rs new file mode 100644 index 0000000000..43e60772d0 --- /dev/null +++ b/src/sync/reentrant_mutex.rs @@ -0,0 +1,27 @@ +use super::{AtomicLock, AttemptStatus}; + +const WAITING_BIT: u32 = 1 << 31; +const UNLOCKED: u32 = 0; +// We now have 2^32 - 1 possible thread ID values + +pub struct ReentrantMutex { + lock: AtomicLock, + content: UnsafeCell, +} +unsafe impl Send for ReentrantMutex {} +unsafe impl Sync for ReentrantMutex {} + +impl ReentrantMutex { + pub const fn new(context: T) -> Self { + Self { + lock: AtomicLock::new(UNLOCKED), + content: UnsafeCell::new(content), + } + } +} +pub struct ReentrantMutexGuard<'a, T: 'a> { + mutex: &'a ReentrantMutex, + content: &'a T, +} +impl<'a, T> Deref for MutexGuard { +} diff --git a/src/sync/rwlock.rs b/src/sync/rwlock.rs new file mode 100644 index 0000000000..c93fe05dc9 --- /dev/null +++ b/src/sync/rwlock.rs @@ -0,0 +1,318 @@ +use core::{ + cell::UnsafeCell, + fmt, ops, + sync::atomic::{AtomicU32, Ordering}, +}; + +use crate::{ + error::{Errno, Result}, + header::{ + bits_timespec::timespec, + errno::{EINVAL, ETIMEDOUT}, + time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic}, + }, + platform::types::clockid_t, + pthread::Pshared, +}; + +pub struct InnerRwLock { + state: AtomicU32, +} +// PTHREAD_RWLOCK_INITIALIZER is defined as "all zeroes". + +const WAITING_WR: u32 = 1 << (u32::BITS - 1); +const COUNT_MASK: u32 = WAITING_WR - 1; +const EXCLUSIVE: u32 = COUNT_MASK; + +// TODO: Optimize for short waits and long waits, using AtomicLock::wait_until, but still +// supporting timeouts. +// TODO: Add futex ops that use bitmasks. + +impl InnerRwLock { + pub const fn new(_pshared: Pshared) -> Self { + Self { + state: AtomicU32::new(0), + } + } + fn translate_timeout(deadline: Option<(×pec, i32)>) -> Result, Errno> { + let relative = match deadline { + // FUTEX expect monotonic clock + Some((abstime, CLOCK_MONOTONIC)) => Some(abstime.clone()), + Some((abstime, CLOCK_REALTIME)) => { + Some(timespec_realtime_to_monotonic(abstime.clone())?) + } + None => None, + _ => { + return Err(Errno(EINVAL)); + } + }; + Ok(relative) + } + pub fn acquire_write_lock( + &self, + deadline: Option<(×pec, clockid_t)>, + ) -> Result<(), Errno> { + let relative = Self::translate_timeout(deadline)?; + let mut waiting_wr = self.state.load(Ordering::Relaxed) & WAITING_WR; + + loop { + match self.state.compare_exchange_weak( + waiting_wr, + EXCLUSIVE, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(actual) => { + let expected = actual; + let expected = if actual & COUNT_MASK != EXCLUSIVE { + // Set the exclusive bit, but only if we're waiting for readers, to avoid + // reader starvation by overprioritizing write locks. + self.state.fetch_or(WAITING_WR, Ordering::Relaxed); + + actual | WAITING_WR + } else { + actual + }; + waiting_wr = expected & WAITING_WR; + + if actual & COUNT_MASK > 0 { + match crate::sync::futex_wait(&self.state, expected, relative.as_ref()) { + super::FutexWaitResult::TimedOut => return Err(Errno(ETIMEDOUT)), + _ => {} + } + } else { + // We must avoid blocking indefinitely in our `futex_wait()`, in this case + // where it's possible that `self.state == expected` but our futex might + // never be woken again, because it's possible that all other threads + // already did their `futex_wake()` before we would've done our + // `futex_wait()`. + } + } + } + } + + Ok(()) + } + pub fn acquire_read_lock(&self, deadline: Option<(×pec, clockid_t)>) -> Result<(), Errno> { + let relative = Self::translate_timeout(deadline)?; + while let Err(old) = self.try_acquire_read_lock() { + match crate::sync::futex_wait(&self.state, old, relative.as_ref()) { + super::FutexWaitResult::TimedOut => return Err(Errno(ETIMEDOUT)), + _ => {} + } + } + + Ok(()) + } + pub fn try_acquire_read_lock(&self) -> Result<(), u32> { + let mut cached = self.state.load(Ordering::Acquire); + + loop { + let waiting_wr = cached & WAITING_WR; + let old = if cached & COUNT_MASK == EXCLUSIVE { + 0 + } else { + cached & COUNT_MASK + }; + let new = old + 1; + + // TODO: Return with error code instead? + assert_ne!( + new & COUNT_MASK, + EXCLUSIVE, + "maximum number of rwlock readers reached" + ); + + match self.state.compare_exchange_weak( + (old & COUNT_MASK) | waiting_wr, + new | waiting_wr, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return Ok(()), + + Err(value) if value & COUNT_MASK == EXCLUSIVE => return Err(value), + Err(value) => { + cached = value; + // TODO: SCHED_YIELD? + core::hint::spin_loop(); + } + } + } + } + pub fn try_acquire_write_lock(&self) -> Result<(), u32> { + let mut waiting_wr = self.state.load(Ordering::Relaxed) & WAITING_WR; + + loop { + match self.state.compare_exchange_weak( + waiting_wr, + EXCLUSIVE, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return Ok(()), + Err(actual) if actual & COUNT_MASK > 0 => return Err(actual), + Err(can_retry) => { + waiting_wr = can_retry & WAITING_WR; + + core::hint::spin_loop(); + continue; + } + } + } + } + + pub fn unlock(&self) { + let state = self.state.load(Ordering::Relaxed); + + if state & COUNT_MASK == EXCLUSIVE { + // Unlocking a write lock. + + // This discards the writer-waiting bit, in order to ensure some level of fairness + // between read and write locks. + self.state.store(0, Ordering::Release); + + let _ = crate::sync::futex_wake(&self.state, i32::MAX); + } else { + // Unlocking a read lock. Subtract one from the reader count, but preserve the + // WAITING_WR bit. + + if self.state.fetch_sub(1, Ordering::Release) & COUNT_MASK == 1 { + let _ = crate::sync::futex_wake(&self.state, i32::MAX); + } + } + } +} + +pub struct RwLock { + inner: InnerRwLock, + data: UnsafeCell, +} + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +impl RwLock { + pub const fn new(val: T) -> Self { + Self { + inner: InnerRwLock::new(Pshared::Private), + data: UnsafeCell::new(val), + } + } +} + +impl RwLock { + pub fn read(&self) -> ReadGuard<'_, T> { + let _ = self.inner.acquire_read_lock(None); + unsafe { ReadGuard::new(self) } + } + + pub fn write(&self) -> WriteGuard<'_, T> { + let _ = self.inner.acquire_write_lock(None); + unsafe { WriteGuard::new(self) } + } + + pub fn try_read(&self) -> Option> { + if self.inner.try_acquire_read_lock().is_ok() { + Some(unsafe { ReadGuard::new(self) }) + } else { + None + } + } + + pub fn try_write(&self) -> Option> { + if self.inner.try_acquire_write_lock().is_ok() { + Some(unsafe { WriteGuard::new(self) }) + } else { + None + } + } +} + +pub struct ReadGuard<'a, T: ?Sized + 'a> { + lock: &'a RwLock, +} + +impl !Send for ReadGuard<'_, T> {} +unsafe impl Sync for ReadGuard<'_, T> {} + +impl<'a, T: ?Sized> ReadGuard<'a, T> { + unsafe fn new(lock: &'a RwLock) -> Self { + Self { lock } + } +} + +impl<'a, T: ?Sized> ops::Deref for ReadGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: We have shared reference to the data. + unsafe { &*self.lock.data.get() } + } +} + +impl<'a, T: ?Sized> Drop for ReadGuard<'a, T> { + fn drop(&mut self) { + self.lock.inner.unlock(); + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for ReadGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized + fmt::Display> fmt::Display for ReadGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +pub struct WriteGuard<'a, T: ?Sized + 'a> { + lock: &'a RwLock, +} + +impl !Send for WriteGuard<'_, T> {} +unsafe impl Sync for WriteGuard<'_, T> {} + +impl<'a, T: ?Sized> WriteGuard<'a, T> { + unsafe fn new(lock: &'a RwLock) -> Self { + Self { lock } + } +} + +impl<'a, T: ?Sized> ops::Deref for WriteGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: We have exclusive reference to the data. + unsafe { &*self.lock.data.get() } + } +} + +impl<'a, T: ?Sized> ops::DerefMut for WriteGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: We have exclusive reference to the data. + unsafe { &mut *self.lock.data.get() } + } +} + +impl<'a, T: ?Sized> Drop for WriteGuard<'a, T> { + fn drop(&mut self) { + self.lock.inner.unlock(); + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for WriteGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized + fmt::Display> fmt::Display for WriteGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} diff --git a/src/sync/semaphore.rs b/src/sync/semaphore.rs index 7734c58a17..33f7b3273d 100644 --- a/src/sync/semaphore.rs +++ b/src/sync/semaphore.rs @@ -1,46 +1,90 @@ // From https://www.remlab.net/op/futex-misc.shtml //TODO: improve implementation -use crate::platform::{types::*, Pal, Sys}; -use super::AtomicLock; -use core::sync::atomic::Ordering; +use crate::{ + header::{ + bits_timespec::timespec, + errno::{EINTR, ETIMEDOUT}, + time::{CLOCK_MONOTONIC, CLOCK_REALTIME, timespec_realtime_to_monotonic}, + }, + platform::{ + ERRNO, + types::{c_int, c_uint, clockid_t}, + }, +}; + +use core::sync::atomic::{AtomicU32, Ordering}; pub struct Semaphore { - lock: AtomicLock, + count: AtomicU32, } impl Semaphore { - pub const fn new(value: c_int) -> Self { + pub const fn new(value: c_uint) -> Self { Self { - lock: AtomicLock::new(value), + count: AtomicU32::new(value), } } - pub fn post(&self) { - self.lock.fetch_add(1, Ordering::Relaxed); - self.lock.notify_one(); + // TODO: Acquire-Release ordering? + + pub fn post(&self, count: c_uint) { + self.count.fetch_add(count, Ordering::SeqCst); + // TODO: notify one? + crate::sync::futex_wake(&self.count, i32::MAX); } - pub fn wait(&self) { - let mut value = 1; - + pub fn try_wait(&self) -> u32 { loop { - match self.lock.compare_exchange_weak( - value, - value - 1, - Ordering::Acquire, - Ordering::Relaxed - ) { - Ok(ok) => return, - Err(err) => { - value = err; - } - } + let value = self.count.load(Ordering::SeqCst); if value == 0 { - self.lock.wait_if(0); - value = 1; + return 0; + } + + match self.count.compare_exchange_weak( + value, + value - 1, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + Ok(_) => { + // Acquired + return value; + } + Err(_) => (), + } + // Try again (as long as value > 0) + } + } + + pub fn wait(&self, timeout_opt: Option<×pec>, clock_id: clockid_t) -> Result<(), c_int> { + loop { + let value = self.try_wait(); + + if value == 0 { + return Ok(()); + } + + if let Some(timeout) = timeout_opt { + let relative = match clock_id { + CLOCK_MONOTONIC => timeout.clone(), + CLOCK_REALTIME => match timespec_realtime_to_monotonic(timeout.clone()) { + Ok(relative) => relative, + Err(_) => return Err(ETIMEDOUT), + }, + _ => return Err(ETIMEDOUT), + }; + crate::sync::futex_wait(&self.count, value, Some(&relative)); + } else { + crate::sync::futex_wait(&self.count, value, None); + } + if ERRNO.get() == EINTR { + return Err(EINTR); } } } + pub fn value(&self) -> c_uint { + self.count.load(Ordering::SeqCst) + } } diff --git a/src/sync/waitval.rs b/src/sync/waitval.rs new file mode 100644 index 0000000000..bb6cc1b6ba --- /dev/null +++ b/src/sync/waitval.rs @@ -0,0 +1,42 @@ +use core::{ + cell::UnsafeCell, + mem::MaybeUninit, + sync::atomic::{AtomicU32 as AtomicUint, Ordering}, +}; + +/// An unsafe "one thread to one thread" synchronization primitive. Used for and modeled after +/// pthread_join only, at the moment. +#[derive(Debug)] +pub struct Waitval { + state: AtomicUint, + value: UnsafeCell>, +} + +unsafe impl Send for Waitval {} +unsafe impl Sync for Waitval {} + +impl Waitval { + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self { + state: AtomicUint::new(0), + value: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + // SAFETY: Caller must ensure both (1) that the value has not yet been initialized, and (2) + // that this is never run by more than one thread simultaneously. + pub unsafe fn post(&self, value: T) { + unsafe { self.value.get().write(MaybeUninit::new(value)) }; + self.state.store(1, Ordering::Release); + crate::sync::futex_wake(&self.state, i32::MAX); + } + + pub fn wait(&self) -> &T { + while self.state.load(Ordering::Acquire) == 0 { + crate::sync::futex_wait(&self.state, 0, None); + } + + unsafe { (*self.value.get()).assume_init_ref() } + } +} diff --git a/stripcore.sh b/stripcore.sh new file mode 100755 index 0000000000..e711ea6409 --- /dev/null +++ b/stripcore.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# This script exists as a workaround for https://github.com/rust-lang/rust/issues/142119 + +set -e + +target=$1 + +if [ -z "$target" ]; then + echo "Usage:\n\t./stripcore.sh TARGET" + exit 1 +fi + +for sym in cbrtf ceilf copysignf fabsf fdimf floorf fmaf fmaxf fminf fmodf rintf roundf sqrtf truncf \ + cbrt ceil copysign fabs fdim floor fmax fmin fmod rint round sqrt trunc; do \ + "$OBJCOPY" --globalize-symbol=$sym --strip-symbol=$sym "$target"; \ +done diff --git a/tests/.gitignore b/tests/.gitignore index 748ff9d390..f7f5661096 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,4 +1,4 @@ -/bins_static/ -/bins_dynamic/ +/build_*/ +/bins_*/ /gen/ /*.out diff --git a/tests/Cargo.lock b/tests/Cargo.lock new file mode 100644 index 0000000000..40fd7924fc --- /dev/null +++ b/tests/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "relibc-tests" +version = "0.1.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000000..c50afb5f99 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "relibc-tests" +version = "0.1.0" +authors = ["Jeremy Soller "] +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/Makefile b/tests/Makefile index 815911acb0..98fbc6a0bd 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,249 +1,183 @@ -# Binaries that should generate the same output every time -EXPECT_NAMES=\ - alloca \ - args \ - arpainet \ - assert \ - constructor \ - ctype \ - dirent/scandir \ - errno \ - error \ - fcntl/create \ - fcntl/fcntl \ - fnmatch \ - libgen \ - locale \ - math \ - netdb/getaddrinfo \ - ptrace \ - regex \ - select \ - setjmp \ - sigaction \ - signal \ - stdio/fputs \ - stdio/fread \ - stdio/fseek \ - stdio/fwrite \ - stdio/mutex \ - stdio/popen \ - stdio/printf \ - stdio/rename \ - stdio/scanf \ - stdio/sprintf \ - stdio/printf_space_pad \ - stdio/ungetc_ftell \ - stdio/fscanf_offby1 \ - stdio/fscanf \ - stdio/printf_neg_pad \ - stdlib/a64l \ - stdlib/alloc \ - stdlib/atof \ - stdlib/atoi \ - stdlib/div \ - stdlib/env \ - stdlib/mkostemps \ - stdlib/rand \ - stdlib/rand48 \ - stdlib/random \ - stdlib/strtod \ - stdlib/strtol \ - stdlib/strtoul \ - stdlib/system \ - string/mem \ - string/strcat \ - string/strchr \ - string/strcpy \ - string/strcspn \ - string/strlen \ - string/strncmp \ - string/strpbrk \ - string/strrchr \ - string/strspn \ - string/strstr \ - string/strtok \ - string/strtok_r \ - string/strsignal \ - strings \ - sys_mman \ - time/asctime \ - time/gmtime \ - time/macros \ - time/mktime \ - time/strftime \ - time/time \ - tls \ - unistd/access \ - unistd/brk \ - unistd/dup \ - unistd/exec \ - unistd/fchdir \ - unistd/fork \ - unistd/fsync \ - unistd/ftruncate \ - unistd/pipe \ - unistd/rmdir \ - unistd/sleep \ - unistd/swab \ - unistd/write \ - waitpid \ - wchar/fwide \ - wchar/mbrtowc \ - wchar/mbsrtowcs \ - wchar/printf-on-wchars \ - wchar/putwchar \ - wchar/wcrtomb \ - wchar/wcscspn \ - wchar/wcsrchr \ - wchar/wcsstr \ - wchar/wcstod \ - wchar/wcstol \ - wchar/wcscasecmp \ - wchar/wcsncasecmp \ - # TODO: Fix these - # mkfifo - # netdb/netdb \ +include ../config.mk -# issues with linking stdin, stdout, stderr -STATIC_ONLY_NAMES=\ - futimens \ - stdio/all \ - stdio/buffer \ - stdio/fgets \ - stdio/freopen \ - stdio/getc_unget \ - stdio/setvbuf \ - stdio/ungetc_multiple \ - time/localtime \ - wchar/wcstok \ - wctype/towlower \ - wctype/towupper \ -# need to call fini in ld_so's _start -STATIC_ONLY_NAMES+=\ - destructor \ -# issues with linking optarg, optind etc. -STATIC_ONLY_NAMES+=\ - unistd/getopt \ - unistd/getopt_long \ +IS_REDOX?=0 +IS_STATIC?=0 +BUILD?=./build_$(TARGET) -DYNAMIC_ONLY_NAMES=\ - dlfcn +include ./Makefile.tests.mk -# Binaries that may generate varied output -NAMES=\ - $(EXPECT_NAMES) \ - dirent/main \ - pwd \ - stdio/tempnam \ - stdio/tmpnam \ - stdlib/bsearch \ - stdlib/mktemp \ - stdlib/realpath \ - sys_epoll/epoll \ - sys_utsname/uname \ - time/gettimeofday \ - unistd/chdir \ - unistd/getcwd \ - unistd/gethostname \ - unistd/getid \ - unistd/getpagesize \ - unistd/isatty \ - unistd/link \ - unistd/pathconf \ - unistd/setid \ - unistd/stat \ - unistd/sysconf -# resource/getrusage -# time/times - -BINS=$(patsubst %,bins_static/%,$(NAMES)) -BINS+=$(patsubst %,bins_static/%,$(STATIC_ONLY_NAMES)) -BINS+=$(patsubst %,bins_dynamic/%,$(NAMES)) -BINS+=$(patsubst %,bins_dynamic/%,$(DYNAMIC_ONLY_NAMES)) -EXPECT_BINS=$(patsubst %,bins_static/%,$(EXPECT_NAMES)) -EXPECT_BINS+=$(patsubst %,bins_static/%,$(STATIC_ONLY_NAMES)) -EXPECT_BINS+=$(patsubst %,bins_dynamic/%,$(EXPECT_NAMES)) -EXPECT_BINS+=$(patsubst %,bins_dynamic/%,$(DYNAMIC_ONLY_NAMES)) - -TEST_RUNNER?=sh -- +CARGO_TEST?=cargo +TEST_RUNNER?= .PHONY: all clean run expected verify -all: $(BINS) +all: build clean: - rm -rf bins_* gen *.out + rm -rf $(BUILD)/bins_* $(BUILD)/gen $(BUILD)/target -run: | $(BINS) - for bin in $(BINS); \ - do \ - echo "# $${bin} #"; \ - "$${bin}" test args || exit $$?; \ - done +build: $(BUILD)/bins_verify/relibc-tests $(BINS) $(EXPECT_BINS) $(EXPECT_INPUT_BINS) support_build -expected: | $(EXPECT_BINS) - rm -rf expected - mkdir -p expected - for bin in $(EXPECT_BINS); \ - do \ - echo "# $${bin} #"; \ - mkdir -p expected/`dirname $${bin}`; \ - "$${bin}" test args > "expected/$${bin}.stdout" 2> "expected/$${bin}.stderr" || exit $$?; \ - done +support_build: $(BUILD)/expected \ + $(BUILD)/Makefile \ + $(BUILD)/Makefile.tests.mk \ + $(BUILD)/example_dir \ + $(BUILD)/select.c \ + $(BUILD)/stdlib/realpath.c \ + $(BUILD)/stdio/fread.in \ + $(BUILD)/stdio/fscanf_offby1.c \ + $(BUILD)/stdio/fscanf.c \ + $(BUILD)/stdio/stdio.in \ + $(BUILD)/stdio/getline.in \ + $(BUILD)/stdio/ungetc_ftell.c \ + $(BUILD)/sys_stat/stat.c \ + $(BUILD)/unistd/link.c \ + $(BUILD)/wchar/fgetwc.in \ + $(BUILD)/wchar/ungetwc.in -verify: | $(EXPECT_BINS) - $(TEST_RUNNER) ./verify.sh $(EXPECT_BINS) +run: build + $(MAKE) run -C $(BUILD) + +run-once: $(BUILD)/bins_verify/relibc-tests $(BUILD)/$(TESTBIN) support_build + $(MAKE) run-once -C $(BUILD) TESTBIN=$(TESTBIN) + +$(BUILD)/bins_verify/relibc-tests: src/main.rs + mkdir -p $(BUILD)/bins_verify + $(CARGO_TEST) build --release --bin relibc-tests --artifact-dir $(BUILD)/bins_verify --target-dir $(BUILD)/target -Z unstable-options + rm -rf $(BUILD)/target + +$(BUILD)/Makefile: Makefile.run.mk + cp -a $< "$@" + +$(BUILD)/Makefile.tests.mk: Makefile.tests.mk + cp -a $< "$@" + +$(BUILD)/expected: expected + rm -rf "$@" + cp -a "$<" $(BUILD)/ + +$(BUILD)/example_dir: example_dir + rm -rf "$@" + cp -a "$<" $(BUILD)/ + +$(BUILD)/%.in: %.in + @mkdir -p "$$(dirname "$@")" + cp "$*.in" "$@" + +$(BUILD)/%.c: %.c + @mkdir -p "$$(dirname "$@")" + cp "$*.c" "$@" FLAGS=\ -std=c11 \ -fno-builtin \ -fno-stack-protector \ -Wall \ + -Wextra \ + -Werror \ + -Wno-deprecated-declarations \ -pedantic \ -g \ -I . STATIC_FLAGS=\ - ../sysroot/lib/libc.a \ -static -NATIVE_RELIBC?=0 -ifeq ($(NATIVE_RELIBC),0) +DYNAMIC_FLAGS=\ + -Wl,--enable-new-dtags \ + -Wl,-export-dynamic + +SYSROOT?=$(abspath ../sysroot/$(TARGET)/) +SYSROOT_TARGET?=$(SYSROOT) + +$(SYSROOT): + $(MAKE) -C .. sysroot + +NATIVE_LIBC?=0 +ifeq ($(NATIVE_LIBC),0) FLAGS+=\ -nostdinc \ -nostdlib \ - -isystem ../sysroot/include \ - ../sysroot/lib/crt0.o \ - ../sysroot/lib/crti.o \ - ../sysroot/lib/crtn.o + -isystem $(SYSROOT)/include \ + $(SYSROOT)/lib/crt0.o \ + $(SYSROOT)/lib/crti.o \ + $(SYSROOT)/lib/crtn.o -../sysroot: - $(MAKE) -C .. sysroot - -bins_static/%: %.c ../sysroot - mkdir -p "$$(dirname "$@")" - $(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS) - -SYSROOT_LIB=$(shell realpath ../sysroot/lib/) - -DYNAMIC_FLAGS=\ - -Wl,-dynamic-linker=$(SYSROOT_LIB)/ld64.so.1 \ - -Wl,--enable-new-dtags \ - -Wl,-rpath=$(SYSROOT_LIB) \ - -Wl,-export-dynamic \ - -L $(SYSROOT_LIB) \ - -lc - -bins_dynamic/%: %.c ../sysroot - mkdir -p "$$(dirname "$@")" - $(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) -else -bins_static/%: %.c - mkdir -p "$$(dirname "$@")" - $(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS) - -bins_dynamic/%: %.c - mkdir -p "$$(dirname "$@")" - $(CC) "$<" -o "$@" $(FLAGS) +ifeq ($(TARGET),aarch64-unknown-redox) +FLAGS+=-mno-outline-atomics endif + +STATIC_FLAGS+=\ + $(SYSROOT)/lib/libc.a + +ifeq ($(IS_REDOX),0) +DYNAMIC_FLAGS+=\ + -Wl,-dynamic-linker=$(SYSROOT_TARGET)/$(LD_SO_PATH) \ + -Wl,-rpath=$(SYSROOT_TARGET)/lib:\$$ORIGIN \ + -L $(SYSROOT)/lib \ + -lc \ + -fpic +else +# TODO: Make -dynamic-linker works +DYNAMIC_FLAGS+=\ + -Wl,-rpath=$(SYSROOT_TARGET)/lib:\$$ORIGIN \ + -L $(SYSROOT)/lib \ + -lc \ + -fpic +endif + +DEPS=../sysroot/$(TARGET) + +else # ifeq ($(NATIVE_LIBC),1) +DYNAMIC_FLAGS+=\ + -Wl,-rpath=\$$ORIGIN +ifeq ($(IS_REDOX),0) +# glibc +FLAGS+=\ + -D_POSIX_C_SOURCE=200809L \ + -D_DEFAULT_SOURCE \ + -D_XOPEN_SOURCE=700 \ + -D_GNU_SOURCE \ + -lcrypt \ + -lm +else +# hosted redox, no extra options needed +endif +endif + +$(BUILD)/bins_static/%: %.c $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS) + +$(BUILD)/bins_expect_input/%: %.c %.exp $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS) + cp "$*.exp" $(addsuffix .exp,"$@") + +$(BUILD)/bins_dynamic/%.so: %.c $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" -shared -fpic $(FLAGS) $(DYNAMIC_FLAGS) + +# foobar depends on foo +$(BUILD)/bins_dynamic/libfoobar.so: libfoobar.c $(BUILD)/bins_dynamic/libfoo.so $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" -shared -fpic $(FLAGS) $(DYNAMIC_FLAGS) -L $(BUILD)/bins_dynamic -lfoo + +$(BUILD)/bins_dynamic/dlfcn: dlfcn.c $(BUILD)/bins_dynamic/sharedlib.so $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) + +$(BUILD)/bins_dynamic/dlopen_scopes: dlopen_scopes.c $(BUILD)/bins_dynamic/libfoobar.so $(BUILD)/bins_dynamic/libfoo.so $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) + +$(BUILD)/bins_dynamic/%: %.c $(DEPS) + mkdir -p "$$(dirname "$@")" + @$(CC) "$<" -o "$@" $(FLAGS) $(DYNAMIC_FLAGS) + +$(BUILD)/gen/includes: includes.c $(DEPS) + rm -rf $@.tmp $@ + mkdir -p $@.tmp + @$(CC) "$<" -H $(FLAGS) $(DYNAMIC_FLAGS) 2> $@.tmp/graph.txt + sed -i 's|$(SYSROOT)/include/||g' $@.tmp/graph.txt + mv $@.tmp $@ diff --git a/tests/Makefile.run.mk b/tests/Makefile.run.mk new file mode 100644 index 0000000000..31e93b92e4 --- /dev/null +++ b/tests/Makefile.run.mk @@ -0,0 +1,33 @@ +BUILD?=. +ifeq ($(shell uname),Redox) +IS_REDOX=1 +else +IS_REDOX=0 +endif + +include ./Makefile.tests.mk + +CARGO_TEST?=cargo +TEST_RUNNER?= + +.PHONY: all clean run run-once + +all: run + +clean: + rm -rf $(BUILD)/gen + +run: $(BUILD)/bins_verify/relibc-tests $(BINS) $(EXPECT_BINS) $(EXPECT_INPUT_BINS) + @echo "\033[1;36;49mRunning tests\033[0m" + $(TEST_RUNNER) $< $(patsubst %,-s%,$(BINS)) $(EXPECT_BINS) + for exp in $(EXPECT_INPUT_BINS); \ + do \ + echo "# expect $$(readlink -e $${exp}.exp) $$(readlink -e $${exp}) #"; \ + expect "$$(readlink -e $${exp}.exp)" "$$(readlink -e $${exp})" test args || exit $$?; \ + done + +run-once: $(BUILD)/bins_verify/relibc-tests $(BUILD)/$(TESTBIN) + @echo "\033[1;36;49mRunning single test $(TESTBIN) $*\033[0m" + @if [ -f "expected/$(TESTBIN).stdout" ]; then \ + $(TEST_RUNNER) $< $(BUILD)/$(TESTBIN); \ + else $(TEST_RUNNER) $< -s$(BUILD)/$(TESTBIN); fi diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk new file mode 100644 index 0000000000..2c9eb45152 --- /dev/null +++ b/tests/Makefile.tests.mk @@ -0,0 +1,358 @@ + +# If compiling for Redox, IS_REDOX must be 1 +ifeq ($(IS_REDOX),1) +FAILING_TESTS= +else +# Wrong modified time +FAILING_TESTS := futimens +# Crash, mmap issue +FAILING_TESTS += malloc/usable_size +# Not a FIFO +FAILING_TESTS += mkfifo +# Waitpid had EINTR +FAILING_TESTS += sigchld +# not triggering ERANGE +FAILING_TESTS += stdlib/ptsname +# Hang +FAILING_TESTS += sys_epoll/epollet +# Kernel hit todo! +FAILING_TESTS += sys_mman/fmap +# Hang +FAILING_TESTS += sys_socket/unixpeername +# Task failed successfully? +FAILING_TESTS += signals/pthread_kill-child +# Got EBADF +FAILING_TESTS += unistd/isatty +# Got EINVAL +FAILING_TESTS += unistd/link +# Signal kept unmasked +FAILING_TESTS += sigqueue +# Unwrap hit, written as TODO +FAILING_TESTS += pthread/customstack +# Returning garbage values +FAILING_TESTS += sys_resource/getrusage +# No times.h header +#FAILING_TESTS += time/times +# Outdated test +#FAILING_TESTS += netdb/netdb + +endif + +# Binaries that should generate the same output every time +EXPECT_NAMES=\ + alloca \ + arpainet \ + assert \ + ctype \ + crypt/blowfish \ + crypt/md5 \ + crypt/pbkdf2 \ + crypt/scrypt \ + crypt/sha256 \ + crypt/sha512 \ + dirent/fdopendir \ + dirent/scandir \ + endian \ + err \ + errno \ + error \ + fcntl/create \ + fcntl/fcntl \ + fcntl/open \ + fcntl/posix_fallocate \ + features \ + fnmatch \ + glob \ + iso646 \ + libgen \ + locale/duplocale \ + locale/newlocale \ + locale/setlocale \ + math \ + regex \ + select \ + setjmp \ + sigaction \ + sigaltstack \ + signal \ + stdio/all \ + stdio/buffer \ + stdio/dprintf \ + stdio/fgets \ + stdio/fputs \ + stdio/fread \ + stdio/freopen \ + stdio/fseek \ + stdio/fwrite \ + stdio/getc_unget \ + stdio/getline \ + stdio/mutex \ + stdio/popen \ + stdio/printf \ + stdio/putc_unlocked \ + stdio/rename \ + stdio/renameat \ + stdio/scanf \ + stdio/setvbuf \ + stdio/sprintf \ + stdio/printf_space_pad \ + stdio/ungetc_ftell \ + stdio/ungetc_multiple \ + stdio/fscanf_offby1 \ + stdio/fscanf \ + stdio/printf_neg_pad \ + stdlib/a64l \ + stdlib/alloc \ + stdlib/atof \ + stdlib/atoi \ + stdlib/div \ + stdlib/getsubopt \ + stdlib/mkostemps \ + stdlib/qsort \ + stdlib/rand \ + stdlib/rand48 \ + stdlib/random \ + stdlib/strtod \ + stdlib/strtol \ + stdlib/strtoul \ + stdlib/system \ + string/mem \ + string/memcpy \ + string/memmem \ + string/strcat \ + string/strchr \ + string/strchrnul \ + string/strcpy \ + string/strcspn \ + string/strlen \ + string/strncmp \ + string/strpbrk \ + string/strrchr \ + string/strspn \ + string/strstr \ + string/strtok \ + string/strtok_r \ + string/strsep \ + string/strsignal \ + string/stpcpy \ + string/stpncpy \ + strings \ + sys_socket/recv \ + sys_socket/recvfrom \ + sys_socket/unixrecv \ + sys_socket/unixrecvfrom \ + sys_socket/unixsocketpair \ + sys_stat/chmod \ + sys_stat/lstat \ + sys_stat/fstatat \ + sys_syslog/syslog \ + time/asctime \ + time/constants \ + time/gmtime \ + time/localtime \ + time/localtime_r \ + time/macros \ + time/mktime \ + time/strftime \ + time/strptime \ + time/time \ + time/timegm \ + time/tzset \ + unistd/access \ + unistd/alarm \ + unistd/constants \ + unistd/confstr \ + unistd/dup \ + unistd/exec \ + unistd/fchdir \ + unistd/fork \ + unistd/fsync \ + unistd/ftruncate \ + unistd/getopt \ + unistd/getopt_long \ + unistd/pipe \ + unistd/readlinkat \ + unistd/rmdir \ + unistd/sleep \ + unistd/swab \ + unistd/write \ + wchar/fgetwc \ + wchar/fwide \ + wchar/mbrtowc \ + wchar/mbsrtowcs \ + wchar/printf-on-wchars \ + wchar/putwchar \ + wchar/wscanf \ + wchar/ungetwc \ + wchar/wprintf \ + wchar/wcrtomb \ + wchar/wcpcpy \ + wchar/wcpncpy \ + wchar/wcschr \ + wchar/wcscspn \ + wchar/wcsdup \ + wchar/wcsrchr \ + wchar/wcsrtombs \ + wchar/wcsstr \ + wchar/wcstod \ + wchar/wcstok \ + wchar/wcstol \ + wchar/wcstoimax \ + wchar/wcstoumax \ + wchar/wcscasecmp \ + wchar/wcsncasecmp \ + wchar/wcsnlen \ + wchar/wcsnrtombs \ + wchar/wcswidth \ + wctype/towlower \ + wctype/towupper \ + mknod \ + mknodat + +# Binaries that may generate varied output +VARIED_NAMES=\ + dirent/main \ + dirent/posix_getdents \ + includes \ + kill-waitpid \ + limits \ + net/if \ + netdb/getaddrinfo \ + netdb/getaddrinfo_null \ + pthread/timedwait \ + pty/forkpty \ + psignal \ + pwd \ + sa_restart \ + signals/kill-self \ + signals/kill0-self \ + signals/kill-invalid \ + signals/kill-permission \ + signals/killpg-esrch \ + signals/killpg-invalid \ + signals/killpg0-self \ + signals/kill-group \ + signals/kill-child \ + signals/killpg-child \ + signals/killpg-self \ + signals/pthread_kill-invalid \ + signals/pthread_kill-self \ + signals/pthread_kill0-self \ + signals/raise-compliance \ + signals/sigismember-invalid \ + signals/sigismember-valid \ + signals/sigaddset-add \ + signals/sigdelset-delete \ + signals/signal-h \ + signals/signal-h-2 \ + signals/signal-handle_return \ + signals/signal-handler \ + signals/signal-handler2 \ + signals/signal-ignore \ + signals/signal-invalid \ + signals/signal-uncatchable \ + signals/sigprocmask-3 \ + signals/sigprocmask-4 \ + signals/sigprocmask-5 \ + signals/sigprocmask-6 \ + signals/sigprocmask-7 \ + signals/sigprocmask-8 \ + signals/sigprocmask-9 \ + signals/sigprocmask-10 \ + signals/sigprocmask-11 \ + signals/sigpause-invalid \ + signals/sigpause-revert \ + signals/sigpause-suspend \ + signals/sigprocmask-blocksingle \ + signals/sigrelse-1 \ + signals/sigrelse-2 \ + signals/sigrelse-3 \ + signals/sigset-1 \ + signals/sigset-10 \ + signals/sigset-2 \ + signals/sigset-3 \ + signals/sigset-4 \ + signals/sigset-5 \ + signals/sigset-9 \ + stdio/ctermid \ + stdio/tempnam \ + stdio/tmpnam \ + stdlib/bsearch \ + stdlib/mktemp \ + stdlib/realpath \ + sys_epoll/epoll \ + sys_mman/mmap \ + sys_resource/constants \ + sys_socket/getpeername \ + sys_stat/stat \ + sys_statvfs/statvfs \ + sys_utsname/uname \ + time/gettimeofday \ + unistd/chdir \ + unistd/getcwd \ + unistd/gethostname \ + unistd/getid \ + unistd/getpagesize \ + unistd/pathconf \ + unistd/setid \ + unistd/sysconf \ + pthread/main \ + pthread/cleanup \ + pthread/exit \ + pthread/extjoin \ + pthread/once \ + pthread/barrier \ + pthread/rwlock_trylock \ + pthread/rwlock_randtest \ + pthread/mutex_recursive \ + pthread/timeout \ + pthread/tls \ + grp/getgrouplist \ + grp/getgroups \ + grp/getgrgid_r \ + grp/getgrnam_r \ + grp/gr_iter \ + waitid \ + waitpid \ + waitpid_multiple \ + $(FAILING_TESTS) + + +# Tests that only working with when ld.so exist +DYNAMIC_ONLY_EXPECT_NAMES=\ + dlfcn \ + dlopen_scopes + +# Tests that may produce different result when ld.so absent +STATIC_CHECK_EXPECT_NAMES=\ + args \ + constructor \ + destructor \ + stdlib/env \ + unistd/brk \ + tls + +# Tests run with `expect` (require a .c file and an .exp file +# that takes the produced binary as the first argument) +EXPECT_INPUT_NAMES=\ + unistd/getpass + +# TODO: Dynamic linking doesn't work with NATIVE_LIBC=0 +ifeq ($(IS_STATIC),1) +BINS+=$(patsubst %,$(BUILD)/bins_static/%,$(VARIED_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_static/%,$(EXPECT_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_static/%,$(STATIC_CHECK_EXPECT_NAMES)) +ifeq ($(IS_REDOX),0) +EXPECT_INPUT_BINS=$(patsubst %,$(BUILD)/bins_expect_input/%,$(EXPECT_INPUT_NAMES)) +endif +else +BINS+=$(patsubst %,$(BUILD)/bins_dynamic/%,$(VARIED_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_static/%,$(STATIC_CHECK_EXPECT_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_dynamic/%,$(EXPECT_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_dynamic/%,$(STATIC_CHECK_EXPECT_NAMES)) +EXPECT_BINS+=$(patsubst %,$(BUILD)/bins_dynamic/%,$(DYNAMIC_ONLY_EXPECT_NAMES)) +ifeq ($(IS_REDOX),0) +# TODO: redoxer does not have "expect" binary +EXPECT_INPUT_BINS=$(patsubst %,$(BUILD)/bins_expect_input/%,$(EXPECT_INPUT_NAMES)) +endif +endif diff --git a/tests/args.c b/tests/args.c index 6dc49d1325..9095e3a451 100644 --- a/tests/args.c +++ b/tests/args.c @@ -4,7 +4,7 @@ #include "test_helpers.h" int main(int argc, char *argv[]) { - for(int i = 0; i < argc; i++) { + for(int i = 1; i < argc; i++) { write(STDOUT_FILENO, argv[i], strlen(argv[i])); write(STDOUT_FILENO, " ", 1); } diff --git a/tests/crypt/blowfish.c b/tests/crypt/blowfish.c new file mode 100644 index 0000000000..0a5b62e34b --- /dev/null +++ b/tests/crypt/blowfish.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +int main() +{ + char *expected_output = "$2y$12$CJr4uRfziaGp4CWIBk0fB.I2tCOHYe3pomaWbC53/92p"; + char *result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(strcmp(result, expected_output) == 0); + + // Invalid salt for Blowfish + result = crypt("correctbatteryhorsestapler", "$2t$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(result == NULL); + + expected_output = "$2a$4$IAwt7hxuME3DekssMMTWU.xnJub2Xn45xK/oP.wWt3UC"; + result = crypt("password", "$2a$04$UuTkLRZZ6QofpDOlMz32Mu"); + assert(strcmp(result, expected_output) == 0); + + // Invalid salt for Blowfish + result = "$2b$10$testtesttesttes"; + result = crypt("correctbatteryhorsestapler", "$2y$12$L6Bc/AlTQHyd9liGgGEZyO"); + assert(result != NULL); + + return 0; +} diff --git a/tests/crypt/md5.c b/tests/crypt/md5.c new file mode 100644 index 0000000000..4251d70f9d --- /dev/null +++ b/tests/crypt/md5.c @@ -0,0 +1,38 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include + +int main () { + const char salt[] = "$1$saltstring"; + char *cp; + int result = 0; + + cp = crypt ("Hello world!", salt); + + /* MD5 is disabled in FIPS mode. */ + if (cp) { + result |= strcmp ("$1$saltstri$YMyguxXMBpd2TEZ.vS/3q1", cp); + if (!result) + printf("Success!\n"); + } + + return result; +} \ No newline at end of file diff --git a/tests/crypt/pbkdf2.c b/tests/crypt/pbkdf2.c new file mode 100644 index 0000000000..f3e9a3cfbc --- /dev/null +++ b/tests/crypt/pbkdf2.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +int main() +{ + char *expected_output = "$8$3e8$salt$ZyTZgT5Pyp4CKdom6q6zHg0QrrAQO4ptWYCpWz4gu16"; + char *result = crypt("pleaseletmein", "$8$3e8$salt"); + assert(strcmp(result, expected_output) == 0); + + // No salt + result = crypt("pleaseletmein", "$8$3e8$"); + assert(result != NULL); + + // Invalid encoded number for rounds + result = crypt("pleaseletmein", "$8$$salt"); + assert(result == NULL); + + // Invalid encoded number for rounds + result = crypt("pleaseletmein", "$8$.$salt"); + assert(result == NULL); + + return 0; +} diff --git a/tests/crypt/scrypt.c b/tests/crypt/scrypt.c new file mode 100644 index 0000000000..d6f70778ce --- /dev/null +++ b/tests/crypt/scrypt.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +int main() +{ + char *expected_output = "$7$C6..../....$SodiumChloride$aAM7wxp7ayfEF.ZLedy2490m85oOR228oZTB7jPbmdG"; + char *result = crypt("pleaseletmein", "$7$C6..../....SodiumChloride"); + assert(strcmp(result, expected_output) == 0); + + // No salt + result = crypt("pleaseletmein", "$7$C6..../...."); + assert(result != NULL); + + // Invalid encoded number for r + result = crypt("pleaseletmein", "$7$C6.../....SodiumChloride"); + assert(result == NULL); + + // Invalid encoded number for p + result = crypt("pleaseletmein", "$7$C6..../...SodiumChloride"); + assert(result == NULL); + + return 0; +} diff --git a/tests/crypt/sha256.c b/tests/crypt/sha256.c new file mode 100644 index 0000000000..6e9c3ffa47 --- /dev/null +++ b/tests/crypt/sha256.c @@ -0,0 +1,67 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include + +static const struct +{ + const char *salt; + const char *input; + const char *expected; +} tests[] = +{ + { "$5$saltstring", "Hello world!", + "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" }, + { "$5$rounds=10000$saltstringsaltstring", "Hello world!", + "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2." + "opqey6IcA" }, + { "$5$rounds=5000$toolongsaltstring", "This is just a test", + "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8" + "mGRcvxa5" }, + { "$5$rounds=1400$anotherlongsaltstring", + "a very much longer text to encrypt. This one even stretches over more" + "than one line.", + "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12" + "oP84Bnq1" }, + { "$5$rounds=77777$short", + "we have a short salt string but not a short password", + "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" }, + { "$5$rounds=123456$asaltof16chars..", "a short string", + "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/" + "cZKmF/wJvD" }, + { "$5$rounds=10$roundstoolow", "the minimum number is still observed", + "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL97" + "2bIC" }, +}; + +const int ntests = sizeof(tests) / sizeof(tests[0]); + +int main (void) { + int result = 0; + + for (int i = 0; i < ntests; ++i){ + char *cp = crypt (tests[i].input, tests[i].salt); + if (strcmp (cp, tests[i].expected) != 0) { + printf ("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp); + result = 1; + } + } + return result; +} \ No newline at end of file diff --git a/tests/crypt/sha512.c b/tests/crypt/sha512.c new file mode 100644 index 0000000000..efab1e3162 --- /dev/null +++ b/tests/crypt/sha512.c @@ -0,0 +1,72 @@ +/* Copyright (C) 1991-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include + +static const struct +{ + const char *salt; + const char *input; + const char *expected; +} tests[] = +{ + { "$6$saltstring", "Hello world!", + "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu" + "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" }, + { "$6$rounds=10000$saltstringsaltstring", "Hello world!", + "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb" + "HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." }, + { "$6$rounds=5000$toolongsaltstring", "This is just a test", + "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQ" + "zQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0" }, + { "$6$rounds=1400$anotherlongsaltstring", + "a very much longer text to encrypt. This one even stretches over more" + "than one line.", + "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wP" + "vMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1" }, + { "$6$rounds=77777$short", + "we have a short salt string but not a short password", + "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0g" + "ge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0" }, + { "$6$rounds=123456$asaltof16chars..", "a short string", + "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc" + "elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1" }, + { "$6$rounds=10$roundstoolow", "the minimum number is still observed", + "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x" + "hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." }, +}; + +const int ntests = sizeof(tests) / sizeof(tests[0]); + +int main(void) { + int result = 0; + int i; + + for (i = 0; i < ntests; ++i) { + char * cp = crypt(tests[i].input, tests[i].salt); + + if (strcmp(cp, tests[i].expected) != 0) { + printf("test %d: expected \"%s\", got \"%s\"\n", i, tests[i].expected, cp); + result = 1; + } + } + + return result; +} \ No newline at end of file diff --git a/tests/ctype.c b/tests/ctype.c index 049ac99a70..0f6aa1d1bc 100644 --- a/tests/ctype.c +++ b/tests/ctype.c @@ -301,7 +301,9 @@ size_t num_test_cases = sizeof(test_cases) / sizeof(struct test_case); int main(void) { int retval = EXIT_SUCCESS; - for(int i = 0; i < num_test_cases; ++i) { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + for(size_t i = 0; i < num_test_cases; ++i) { struct test_case tc = test_cases[i]; CHECK_TEST(tc, isalnum, retval); CHECK_TEST(tc, isalpha, retval); @@ -320,6 +322,7 @@ int main(void) { CHECK_TEST(tc, tolower, retval); CHECK_TEST(tc, toupper, retval); } + #pragma GCC diagnostic pop if (retval == EXIT_SUCCESS) { printf("Success: %d\n", retval); diff --git a/tests/dirent/fdopendir.c b/tests/dirent/fdopendir.c new file mode 100644 index 0000000000..268dda2283 --- /dev/null +++ b/tests/dirent/fdopendir.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +__attribute__((nonnull)) +static bool check_dot(struct dirent* dirent) { + const char* dots[2] = { + ".", + ".." + }; + + for (size_t i = 0; i < 2; i++) { + if (strcmp(dots[i], dirent->d_name) == 0) { + return true; + } + } + + return false; +} + +int main(void) { + int status = EXIT_FAILURE; + + char template[] = "/tmp/fdotest.XXXXXX"; + if (!mkdtemp(template)) { + perror("mkdtemp"); + goto bye; + } + + const char* movies[] = { + "big_lebowski", + "blade_runner", + "grand_budapest_hotel", + "taxi_driver" + }; + const size_t movies_len = sizeof(movies)/sizeof(char*); + char paths[sizeof(movies)/sizeof(char*)][PATH_MAX] = {0}; + for (size_t i = 0; i < movies_len; ++i) { + // Concat the path + const size_t len = sizeof(template) - 1; + char buf[PATH_MAX] = {0}; + memcpy(buf, template, len); + buf[len] = '/'; + + memcpy(&buf[len + 1], movies[i], strlen(movies[i])); + memcpy(paths[i], buf, PATH_MAX); + + // And now create the file + int fd = open(buf, O_CREAT); + if (fd == -1) { + perror("open"); + goto rmfiles; + } + close(fd); + } + + // FIXME: Redox requires read perms for the dir while Linux/BSD don't. + int dir = open(template, O_DIRECTORY | O_RDONLY); + if (dir == -1) { + perror("open"); + goto rmfiles; + } + + DIR* iter = fdopendir(dir); + if (!iter) { + perror("fdopendir"); + goto closedirfd; + } + + for (size_t i = 0; i < movies_len; ++i) { + errno = 0; + + struct dirent* dirent = readdir(iter); + if (!dirent) { + if (errno) { + perror("readdir"); + } + fprintf( + stderr, + "Expected entry #%lu but directory stream is complete\n", + i + ); + + goto closediriter; + } + + // Skip . and .. + if (check_dot(dirent)) { + continue; + } + + // Check that the entry matches one of the names. + // readdir's order is indeterministic and looping over the names + // is simpler than qsort for a test. + for (size_t j = 0; j < movies_len; ++j) { + if (strcmp(movies[j], dirent->d_name) == 0) { + goto continue_outer; + } + } + + fprintf( + stderr, + "Unexpected entry: %s\n", + dirent->d_name + ); + goto closediriter; + + continue_outer: + continue; + } + +// fdclosedir is a BSD extension +#ifndef __GLIBC__ + // fdclosedir returns ownership of the original fd. + int returned_fd = fdclosedir(iter); + // Internally, both closedir and fdclosedir consume the boxed DIR. + iter = NULL; + if (returned_fd != dir) { + fputs("fdclosedir returned the wrong descriptor\n", stderr); + goto closedirfd; + } + + // Check that the file descriptor is still valid. + struct stat stat = {0}; + if (fstatat(returned_fd, "", &stat, AT_EMPTY_PATH) == -1) { + perror("fstatat"); + fputs("fdclosedir shouldn't have closed the fd\n", stderr); + goto closedirfd; + } +#endif + + status = EXIT_SUCCESS; +closediriter: + if (iter) { + closedir(iter); + } +closedirfd: + close(dir); +rmfiles: + for (size_t i = 0; i < movies_len; ++i) { + if (strnlen(paths[i], PATH_MAX) > 4) { + unlink(paths[i]); + } + } + rmdir(template); +bye: + return status; +} diff --git a/tests/dirent/main.c b/tests/dirent/main.c index f1c92ed506..b524ed1e98 100644 --- a/tests/dirent/main.c +++ b/tests/dirent/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -26,6 +27,7 @@ int main(void) { puts("--- Testing rewind ---"); rewinddir(dir); entry = readdir(dir); + assert(entry != NULL); puts(entry->d_name); // puts("--- Testing seek ---"); diff --git a/tests/dirent/posix_getdents.c b/tests/dirent/posix_getdents.c new file mode 100644 index 0000000000..ee62be96ff --- /dev/null +++ b/tests/dirent/posix_getdents.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +#define BUFFER_SIZE 4096 + +#ifndef __GLIBC__ +void read_and_print_directory(int fd) { + char buffer[BUFFER_SIZE]; + long nread; + long bpos; + struct posix_dent *d; + + for (;;) { + nread = posix_getdents(fd, buffer, BUFFER_SIZE, 0); + ERROR_IF(posix_getdents, nread, == -1); + UNEXP_IF(posix_getdents, nread, < 0); + + if (nread == 0) { + break; + } + + for (bpos = 0; bpos < nread;) { + d = (struct posix_dent *) (buffer + bpos); + printf(" ino = %-10lu name = %s\n", (unsigned long)d->d_ino, d->d_name); + bpos += d->d_reclen; + } + } +} + +int main(void) { + int fd = open("example_dir/", O_RDONLY | O_DIRECTORY); + ERROR_IF(open, fd, == -1); + read_and_print_directory(fd); + + off_t seek_result = lseek(fd, 0, SEEK_SET); + ERROR_IF(lseek, seek_result, == -1); + read_and_print_directory(fd); + + close(fd); +} +#else + +int main(void) { + // glibc doesn't support posix_getdents & O_DIRECTORY +} +#endif diff --git a/tests/dlfcn.c b/tests/dlfcn.c index 16729e1cec..ed81a7e0e5 100644 --- a/tests/dlfcn.c +++ b/tests/dlfcn.c @@ -1,28 +1,122 @@ +#include +#include #include #include -#include +#define SHARED_LIB "sharedlib.so" -int add(int a, int b) -{ - return a + b; +int add(int a, int b) { return a + b; } + +void test_dlopen_null() { + void *handle = dlopen(NULL, RTLD_LAZY); + if (!handle) { + printf("dlopen(NULL) failed: %s\n", dlerror()); + exit(1); + } + + int (*f)(int, int); + *(void **)(&f) = dlsym(handle, "add"); + + if (!f) { + printf("dlsym(handle, add) failed: %s\n", dlerror()); + exit(2); + } + int a = 22; + int b = 33; + printf("add(%d, %d) = %d\n", a, b, f(a, b)); + dlclose(handle); } +void test_dlopen_libc() { + void *handle = dlopen("libc.so.6", RTLD_LAZY); + if (!handle) { + printf("dlopen(libc.so.6) failed\n"); + exit(1); + } -int main() -{ - void* handle = dlopen(NULL, RTLD_LAZY); - if (!handle) { - printf("dlopen(NULL) failed\n"); - exit(1); - } - int (*f)(int, int) = dlsym(handle, "add"); - if (!f) { - printf("dlsym(handle, add) failed\n"); - exit(2); - } - int a = 22; - int b = 33; - printf("add(%d, %d) = %d\n", a, b, f(a, b)); + int (*f)(const char *); + *(void **)(&f) = dlsym(handle, "puts"); + + if (!f) { + printf("dlsym(handle, puts) failed\n"); + exit(2); + } + f("puts from dlopened libc"); + dlclose(handle); } +void test_dlsym_function() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); + if (!handle) { + printf("dlopen(sharedlib.so) failed\n"); + exit(1); + } + + void (*f)(); + *(void **)(&f) = dlsym(handle, "print"); + + if (!f) { + printf("dlsym(handle, print) failed\n"); + exit(2); + } + f(); + dlclose(handle); +} + +void test_dlsym_global_var() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); + if (!handle) { + printf("dlopen(sharedlib.so) failed\n"); + exit(1); + } + int *global_var = dlsym(handle, "global_var"); + if (!global_var) { + printf("dlsym(handle, global_var) failed\n"); + exit(2); + } + printf("main: global_var == %d\n", *global_var); + dlclose(handle); +} + +void test_dlsym_tls_var() { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY); + if (!handle) { + printf("dlopen(sharedlib.so) failed\n"); + exit(1); + } + int *tls_var = dlsym(handle, "tls_var"); + if (!tls_var) { + printf("dlsym(handle, tls_var) failed\n"); + exit(2); + } + printf("main: tls_var == %d\n", *tls_var); + dlclose(handle); +} + +void test_dlunload(void) { + void *handle = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_LOCAL); + void *handle2 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_LOCAL); + assert(handle == handle2 && handle); + assert(!dlclose(handle)); + void *handle3 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle3 == handle2); + assert(!dlclose(handle3)); + assert(!dlclose(handle2)); + void *handle4 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle4 == NULL); + + void *handle5 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_GLOBAL); + assert(handle5); + assert(!dlclose(handle5)); + void *handle6 = dlopen(SHARED_LIB, RTLD_LAZY | RTLD_NOLOAD); + assert(handle6 == NULL); +} + +int main() { + test_dlopen_null(); + test_dlopen_libc(); + test_dlsym_function(); + test_dlsym_global_var(); + test_dlsym_tls_var(); + test_dlunload(); +} diff --git a/tests/dlopen_scopes.c b/tests/dlopen_scopes.c new file mode 100644 index 0000000000..3d8de7d74c --- /dev/null +++ b/tests/dlopen_scopes.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +int main(void) { + void *handle = dlopen("libfoobar.so", RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + printf("dlopen(libfoobar.so): %s\n", dlerror()); + return EXIT_FAILURE; + } + + assert(dlsym(handle, "BAR") != NULL); + assert(dlsym(handle, "FOO") != NULL); + // not in the global scope + assert(dlsym(RTLD_DEFAULT, "BAR") == NULL); + assert(dlsym(RTLD_DEFAULT, "FOO") == NULL); + + void *self = dlopen(NULL, RTLD_LAZY | RTLD_LOCAL); + if (!self) { + printf("dlopen(NULL): %s\n", dlerror()); + return EXIT_FAILURE; + } + + assert(dlsym(self, "FOO") == NULL); + assert(dlsym(self, "BAR") == NULL); + + assert(dlclose(self) == 0); + + // Promote the library to the global scope. + assert(dlopen("libfoobar.so", /* RTLD_NOLOAD |*/ RTLD_NOW | RTLD_GLOBAL)); + + assert(dlsym(RTLD_DEFAULT, "FOO") != NULL); + assert(dlsym(RTLD_DEFAULT, "BAR") != NULL); + + return EXIT_SUCCESS; +} diff --git a/tests/endian.c b/tests/endian.c new file mode 100644 index 0000000000..2eeb7cdab0 --- /dev/null +++ b/tests/endian.c @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "test_helpers.h" + +void to_be(uintmax_t in, uint8_t *out, size_t size) { + for (size_t i = 0; i < size; i++) { + out[i] = (in >> 8*(size - 1 - i)) & 0xff; + } +} + +void to_le(uintmax_t in, uint8_t *out, size_t size) { + for (size_t i = 0; i < size; i++) { + out[i] = (in >> 8*i) & 0xff; + } +} + +int main() { + uint16_t zero_u16 = 0; + assert(be16toh(zero_u16) == zero_u16); + assert(htobe16(zero_u16) == zero_u16); + assert(htole16(zero_u16) == zero_u16); + assert(le16toh(zero_u16) == zero_u16); + + uint16_t u16_ne = 0x0123; + uint16_t u16_be, u16_le; + to_be(u16_ne, (uint8_t *)&u16_be, sizeof(uint16_t)); + to_le(u16_ne, (uint8_t *)&u16_le, sizeof(uint16_t)); + assert(be16toh(u16_be) == u16_ne); + assert(htobe16(u16_ne) == u16_be); + assert(htole16(u16_ne) == u16_le); + assert(le16toh(u16_le) == u16_ne); + + uint32_t zero_u32 = 0; + assert(be32toh(zero_u32) == zero_u32); + assert(htobe32(zero_u32) == zero_u32); + assert(htole32(zero_u32) == zero_u32); + assert(le32toh(zero_u32) == zero_u32); + + uint32_t u32_ne = 0x01234567; + uint32_t u32_be, u32_le; + to_be(u32_ne, (uint8_t *)&u32_be, sizeof(uint32_t)); + to_le(u32_ne, (uint8_t *)&u32_le, sizeof(uint32_t)); + assert(be32toh(u32_be) == u32_ne); + assert(htobe32(u32_ne) == u32_be); + assert(htole32(u32_ne) == u32_le); + assert(le32toh(u32_le) == u32_ne); + + uint64_t zero_u64 = 0; + assert(be64toh(zero_u64) == zero_u64); + assert(htobe64(zero_u64) == zero_u64); + assert(htole64(zero_u64) == zero_u64); + assert(le64toh(zero_u64) == zero_u64); + + uint64_t u64_ne = 0x0123456789ABCDEF; + uint64_t u64_be, u64_le; + to_be(u64_ne, (uint8_t *)&u64_be, sizeof(uint64_t)); + to_le(u64_ne, (uint8_t *)&u64_le, sizeof(uint64_t)); + assert(be64toh(u64_be) == u64_ne); + assert(htobe64(u64_ne) == u64_be); + assert(htole64(u64_ne) == u64_le); + assert(le64toh(u64_le) == u64_ne); + + /* Test that the BYTE_ORDER, LITTLE_ENDIAN and BIG_ENDIAN macros are available */ + /* It is in principle possible to have further endiannesses, like PDP_ENDIAN */ + if (u64_ne == u64_le) { + assert(BYTE_ORDER == LITTLE_ENDIAN); + assert(BYTE_ORDER != BIG_ENDIAN); + } + if (u64_ne == u64_be) { + assert(BYTE_ORDER == BIG_ENDIAN); + assert(BYTE_ORDER != LITTLE_ENDIAN); + } +} diff --git a/tests/err.c b/tests/err.c new file mode 100644 index 0000000000..eda95d1743 --- /dev/null +++ b/tests/err.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +// does not compile with glibc +#ifndef __GLIBC__ + +__attribute__((nonnull(2))) +static void vwarn_test(int code, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vwarnc(code, fmt, ap); + va_end(ap); +} + +static void log_to_stdout(void) { + err_set_file(stdout); + warnc(ENOENT, "Dang it, Bobby."); + err_set_file(NULL); +} + +void user_callback(int code) { + printf("Exiting due to error code: %d\n", code); +} + +int main(void) { + err_set_exit(user_callback); + + // Set errno to a known value for verifiable messages + // (Also, "Owner died" is just too funny not to use) + errno = EOWNERDEAD; + + warn("Ran out of coffee"); + warnx("%s pulled out your ethernet cable", "Cat"); + warnc(EACCES, "Eat %d cookies", 42); + + vwarn_test(EBADE, "Potato, %s", "krumpli"); + + // Set the sink to stdout then back to stderr + log_to_stdout(); + warnc(EPERM, + "I'm sorry, Dave. I'm afraid I can't do that." + ); + + // As long as one err function works they should all work since + // two functions handle everything internally. + errc(EXIT_SUCCESS, EUSERS, "Bye. It's crowded."); + + + // Unreachable + puts("err did not exit"); + return EXIT_FAILURE; +} + +#else +int main(void) { + exit(0); +} +#endif diff --git a/tests/errno.c b/tests/errno.c index 92bc5515a8..29c7df5265 100644 --- a/tests/errno.c +++ b/tests/errno.c @@ -1,13 +1,678 @@ +#include #include #include #include "test_helpers.h" int main(int argc, char **argv) { - puts(argv[0]); - puts(program_invocation_name); + // Recent POSIX requires E2BIG, EACCESS etc. to be available as macros +#ifdef E2BIG + puts("E2BIG macro available"); +#else + puts("E2BIG macro missing"); +#endif /* E2BIG */ +#ifdef EACCES + puts("EACCES macro available"); +#else + puts("EACCES macro missing"); +#endif /* EACCES */ +#ifdef EADDRINUSE + puts("EADDRINUSE macro available"); +#else + puts("EADDRINUSE macro missing"); +#endif /* EADDRINUSE */ +#ifdef EADDRNOTAVAIL + puts("EADDRNOTAVAIL macro available"); +#else + puts("EADDRNOTAVAIL macro missing"); +#endif /* EADDRNOTAVAIL */ +#ifdef EADV + puts("EADV macro available"); +#else + puts("EADV macro missing"); +#endif /* EADV */ +#ifdef EAFNOSUPPORT + puts("EAFNOSUPPORT macro available"); +#else + puts("EAFNOSUPPORT macro missing"); +#endif /* EAFNOSUPPORT */ +#ifdef EAGAIN + puts("EAGAIN macro available"); +#else + puts("EAGAIN macro missing"); +#endif /* EAGAIN */ +#ifdef EALREADY + puts("EALREADY macro available"); +#else + puts("EALREADY macro missing"); +#endif /* EALREADY */ +#ifdef EBADE + puts("EBADE macro available"); +#else + puts("EBADE macro missing"); +#endif /* EBADE */ +#ifdef EBADF + puts("EBADF macro available"); +#else + puts("EBADF macro missing"); +#endif /* EBADF */ +#ifdef EBADFD + puts("EBADFD macro available"); +#else + puts("EBADFD macro missing"); +#endif /* EBADFD */ +#ifdef EBADMSG + puts("EBADMSG macro available"); +#else + puts("EBADMSG macro missing"); +#endif /* EBADMSG */ +#ifdef EBADR + puts("EBADR macro available"); +#else + puts("EBADR macro missing"); +#endif /* EBADR */ +#ifdef EBADRQC + puts("EBADRQC macro available"); +#else + puts("EBADRQC macro missing"); +#endif /* EBADRQC */ +#ifdef EBADSLT + puts("EBADSLT macro available"); +#else + puts("EBADSLT macro missing"); +#endif /* EBADSLT */ +#ifdef EBFONT + puts("EBFONT macro available"); +#else + puts("EBFONT macro missing"); +#endif /* EBFONT */ +#ifdef EBUSY + puts("EBUSY macro available"); +#else + puts("EBUSY macro missing"); +#endif /* EBUSY */ +#ifdef ECANCELED + puts("ECANCELED macro available"); +#else + puts("ECANCELED macro missing"); +#endif /* ECANCELED */ +#ifdef ECHILD + puts("ECHILD macro available"); +#else + puts("ECHILD macro missing"); +#endif /* ECHILD */ +#ifdef ECHRNG + puts("ECHRNG macro available"); +#else + puts("ECHRNG macro missing"); +#endif /* ECHRNG */ +#ifdef ECOMM + puts("ECOMM macro available"); +#else + puts("ECOMM macro missing"); +#endif /* ECOMM */ +#ifdef ECONNABORTED + puts("ECONNABORTED macro available"); +#else + puts("ECONNABORTED macro missing"); +#endif /* ECONNABORTED */ +#ifdef ECONNREFUSED + puts("ECONNREFUSED macro available"); +#else + puts("ECONNREFUSED macro missing"); +#endif /* ECONNREFUSED */ +#ifdef ECONNRESET + puts("ECONNRESET macro available"); +#else + puts("ECONNRESET macro missing"); +#endif /* ECONNRESET */ +#ifdef EDEADLK + puts("EDEADLK macro available"); +#else + puts("EDEADLK macro missing"); +#endif /* EDEADLK */ +#ifdef EDEADLOCK + puts("EDEADLOCK macro available"); +#else + puts("EDEADLOCK macro missing"); +#endif /* EDEADLOCK */ +#ifdef EDESTADDRREQ + puts("EDESTADDRREQ macro available"); +#else + puts("EDESTADDRREQ macro missing"); +#endif /* EDESTADDRREQ */ +#ifdef EDOM + puts("EDOM macro available"); +#else + puts("EDOM macro missing"); +#endif /* EDOM */ +#ifdef EDOTDOT + puts("EDOTDOT macro available"); +#else + puts("EDOTDOT macro missing"); +#endif /* EDOTDOT */ +#ifdef EDQUOT + puts("EDQUOT macro available"); +#else + puts("EDQUOT macro missing"); +#endif /* EDQUOT */ +#ifdef EEXIST + puts("EEXIST macro available"); +#else + puts("EEXIST macro missing"); +#endif /* EEXIST */ +#ifdef EFAULT + puts("EFAULT macro available"); +#else + puts("EFAULT macro missing"); +#endif /* EFAULT */ +#ifdef EFBIG + puts("EFBIG macro available"); +#else + puts("EFBIG macro missing"); +#endif /* EFBIG */ +#ifdef EHOSTDOWN + puts("EHOSTDOWN macro available"); +#else + puts("EHOSTDOWN macro missing"); +#endif /* EHOSTDOWN */ +#ifdef EHOSTUNREACH + puts("EHOSTUNREACH macro available"); +#else + puts("EHOSTUNREACH macro missing"); +#endif /* EHOSTUNREACH */ +#ifdef EIDRM + puts("EIDRM macro available"); +#else + puts("EIDRM macro missing"); +#endif /* EIDRM */ +#ifdef EILSEQ + puts("EILSEQ macro available"); +#else + puts("EILSEQ macro missing"); +#endif /* EILSEQ */ +#ifdef EINPROGRESS + puts("EINPROGRESS macro available"); +#else + puts("EINPROGRESS macro missing"); +#endif /* EINPROGRESS */ +#ifdef EINTR + puts("EINTR macro available"); +#else + puts("EINTR macro missing"); +#endif /* EINTR */ +#ifdef EINVAL + puts("EINVAL macro available"); +#else + puts("EINVAL macro missing"); +#endif /* EINVAL */ +#ifdef EIO + puts("EIO macro available"); +#else + puts("EIO macro missing"); +#endif /* EIO */ +#ifdef EISCONN + puts("EISCONN macro available"); +#else + puts("EISCONN macro missing"); +#endif /* EISCONN */ +#ifdef EISDIR + puts("EISDIR macro available"); +#else + puts("EISDIR macro missing"); +#endif /* EISDIR */ +#ifdef EISNAM + puts("EISNAM macro available"); +#else + puts("EISNAM macro missing"); +#endif /* EISNAM */ +#ifdef EKEYEXPIRED + puts("EKEYEXPIRED macro available"); +#else + puts("EKEYEXPIRED macro missing"); +#endif /* EKEYEXPIRED */ +#ifdef EKEYREJECTED + puts("EKEYREJECTED macro available"); +#else + puts("EKEYREJECTED macro missing"); +#endif /* EKEYREJECTED */ +#ifdef EKEYREVOKED + puts("EKEYREVOKED macro available"); +#else + puts("EKEYREVOKED macro missing"); +#endif /* EKEYREVOKED */ +#ifdef EL2HLT + puts("EL2HLT macro available"); +#else + puts("EL2HLT macro missing"); +#endif /* EL2HLT */ +#ifdef EL2NSYNC + puts("EL2NSYNC macro available"); +#else + puts("EL2NSYNC macro missing"); +#endif /* EL2NSYNC */ +#ifdef EL3HLT + puts("EL3HLT macro available"); +#else + puts("EL3HLT macro missing"); +#endif /* EL3HLT */ +#ifdef EL3RST + puts("EL3RST macro available"); +#else + puts("EL3RST macro missing"); +#endif /* EL3RST */ +#ifdef ELIBACC + puts("ELIBACC macro available"); +#else + puts("ELIBACC macro missing"); +#endif /* ELIBACC */ +#ifdef ELIBBAD + puts("ELIBBAD macro available"); +#else + puts("ELIBBAD macro missing"); +#endif /* ELIBBAD */ +#ifdef ELIBEXEC + puts("ELIBEXEC macro available"); +#else + puts("ELIBEXEC macro missing"); +#endif /* ELIBEXEC */ +#ifdef ELIBMAX + puts("ELIBMAX macro available"); +#else + puts("ELIBMAX macro missing"); +#endif /* ELIBMAX */ +#ifdef ELIBSCN + puts("ELIBSCN macro available"); +#else + puts("ELIBSCN macro missing"); +#endif /* ELIBSCN */ +#ifdef ELNRNG + puts("ELNRNG macro available"); +#else + puts("ELNRNG macro missing"); +#endif /* ELNRNG */ +#ifdef ELOOP + puts("ELOOP macro available"); +#else + puts("ELOOP macro missing"); +#endif /* ELOOP */ +#ifdef EMEDIUMTYPE + puts("EMEDIUMTYPE macro available"); +#else + puts("EMEDIUMTYPE macro missing"); +#endif /* EMEDIUMTYPE */ +#ifdef EMFILE + puts("EMFILE macro available"); +#else + puts("EMFILE macro missing"); +#endif /* EMFILE */ +#ifdef EMLINK + puts("EMLINK macro available"); +#else + puts("EMLINK macro missing"); +#endif /* EMLINK */ +#ifdef EMSGSIZE + puts("EMSGSIZE macro available"); +#else + puts("EMSGSIZE macro missing"); +#endif /* EMSGSIZE */ +#ifdef EMULTIHOP + puts("EMULTIHOP macro available"); +#else + puts("EMULTIHOP macro missing"); +#endif /* EMULTIHOP */ +#ifdef ENAMETOOLONG + puts("ENAMETOOLONG macro available"); +#else + puts("ENAMETOOLONG macro missing"); +#endif /* ENAMETOOLONG */ +#ifdef ENAVAIL + puts("ENAVAIL macro available"); +#else + puts("ENAVAIL macro missing"); +#endif /* ENAVAIL */ +#ifdef ENETDOWN + puts("ENETDOWN macro available"); +#else + puts("ENETDOWN macro missing"); +#endif /* ENETDOWN */ +#ifdef ENETRESET + puts("ENETRESET macro available"); +#else + puts("ENETRESET macro missing"); +#endif /* ENETRESET */ +#ifdef ENETUNREACH + puts("ENETUNREACH macro available"); +#else + puts("ENETUNREACH macro missing"); +#endif /* ENETUNREACH */ +#ifdef ENFILE + puts("ENFILE macro available"); +#else + puts("ENFILE macro missing"); +#endif /* ENFILE */ +#ifdef ENOANO + puts("ENOANO macro available"); +#else + puts("ENOANO macro missing"); +#endif /* ENOANO */ +#ifdef ENOBUFS + puts("ENOBUFS macro available"); +#else + puts("ENOBUFS macro missing"); +#endif /* ENOBUFS */ +#ifdef ENOCSI + puts("ENOCSI macro available"); +#else + puts("ENOCSI macro missing"); +#endif /* ENOCSI */ +#ifdef ENODATA + puts("ENODATA macro available"); +#else + puts("ENODATA macro missing"); +#endif /* ENODATA */ +#ifdef ENODEV + puts("ENODEV macro available"); +#else + puts("ENODEV macro missing"); +#endif /* ENODEV */ +#ifdef ENOENT + puts("ENOENT macro available"); +#else + puts("ENOENT macro missing"); +#endif /* ENOENT */ +#ifdef ENOEXEC + puts("ENOEXEC macro available"); +#else + puts("ENOEXEC macro missing"); +#endif /* ENOEXEC */ +#ifdef ENOKEY + puts("ENOKEY macro available"); +#else + puts("ENOKEY macro missing"); +#endif /* ENOKEY */ +#ifdef ENOLCK + puts("ENOLCK macro available"); +#else + puts("ENOLCK macro missing"); +#endif /* ENOLCK */ +#ifdef ENOLINK + puts("ENOLINK macro available"); +#else + puts("ENOLINK macro missing"); +#endif /* ENOLINK */ +#ifdef ENOMEDIUM + puts("ENOMEDIUM macro available"); +#else + puts("ENOMEDIUM macro missing"); +#endif /* ENOMEDIUM */ +#ifdef ENOMEM + puts("ENOMEM macro available"); +#else + puts("ENOMEM macro missing"); +#endif /* ENOMEM */ +#ifdef ENOMSG + puts("ENOMSG macro available"); +#else + puts("ENOMSG macro missing"); +#endif /* ENOMSG */ +#ifdef ENONET + puts("ENONET macro available"); +#else + puts("ENONET macro missing"); +#endif /* ENONET */ +#ifdef ENOPKG + puts("ENOPKG macro available"); +#else + puts("ENOPKG macro missing"); +#endif /* ENOPKG */ +#ifdef ENOPROTOOPT + puts("ENOPROTOOPT macro available"); +#else + puts("ENOPROTOOPT macro missing"); +#endif /* ENOPROTOOPT */ +#ifdef ENOSPC + puts("ENOSPC macro available"); +#else + puts("ENOSPC macro missing"); +#endif /* ENOSPC */ +#ifdef ENOSR + puts("ENOSR macro available"); +#else + puts("ENOSR macro missing"); +#endif /* ENOSR */ +#ifdef ENOSTR + puts("ENOSTR macro available"); +#else + puts("ENOSTR macro missing"); +#endif /* ENOSTR */ +#ifdef ENOSYS + puts("ENOSYS macro available"); +#else + puts("ENOSYS macro missing"); +#endif /* ENOSYS */ +#ifdef ENOTBLK + puts("ENOTBLK macro available"); +#else + puts("ENOTBLK macro missing"); +#endif /* ENOTBLK */ +#ifdef ENOTCONN + puts("ENOTCONN macro available"); +#else + puts("ENOTCONN macro missing"); +#endif /* ENOTCONN */ +#ifdef ENOTDIR + puts("ENOTDIR macro available"); +#else + puts("ENOTDIR macro missing"); +#endif /* ENOTDIR */ +#ifdef ENOTEMPTY + puts("ENOTEMPTY macro available"); +#else + puts("ENOTEMPTY macro missing"); +#endif /* ENOTEMPTY */ +#ifdef ENOTNAM + puts("ENOTNAM macro available"); +#else + puts("ENOTNAM macro missing"); +#endif /* ENOTNAM */ +#ifdef ENOTRECOVERABLE + puts("ENOTRECOVERABLE macro available"); +#else + puts("ENOTRECOVERABLE macro missing"); +#endif /* ENOTRECOVERABLE */ +#ifdef ENOTSOCK + puts("ENOTSOCK macro available"); +#else + puts("ENOTSOCK macro missing"); +#endif /* ENOTSOCK */ +#ifdef ENOTSUP + puts("ENOTSUP macro available"); +#else + puts("ENOTSUP macro missing"); +#endif /* ENOTSUP */ +#ifdef ENOTTY + puts("ENOTTY macro available"); +#else + puts("ENOTTY macro missing"); +#endif /* ENOTTY */ +#ifdef ENOTUNIQ + puts("ENOTUNIQ macro available"); +#else + puts("ENOTUNIQ macro missing"); +#endif /* ENOTUNIQ */ +#ifdef ENXIO + puts("ENXIO macro available"); +#else + puts("ENXIO macro missing"); +#endif /* ENXIO */ +#ifdef EOPNOTSUPP + puts("EOPNOTSUPP macro available"); +#else + puts("EOPNOTSUPP macro missing"); +#endif /* EOPNOTSUPP */ +#ifdef EOVERFLOW + puts("EOVERFLOW macro available"); +#else + puts("EOVERFLOW macro missing"); +#endif /* EOVERFLOW */ +#ifdef EOWNERDEAD + puts("EOWNERDEAD macro available"); +#else + puts("EOWNERDEAD macro missing"); +#endif /* EOWNERDEAD */ +#ifdef EPERM + puts("EPERM macro available"); +#else + puts("EPERM macro missing"); +#endif /* EPERM */ +#ifdef EPFNOSUPPORT + puts("EPFNOSUPPORT macro available"); +#else + puts("EPFNOSUPPORT macro missing"); +#endif /* EPFNOSUPPORT */ +#ifdef EPIPE + puts("EPIPE macro available"); +#else + puts("EPIPE macro missing"); +#endif /* EPIPE */ +#ifdef EPROTO + puts("EPROTO macro available"); +#else + puts("EPROTO macro missing"); +#endif /* EPROTO */ +#ifdef EPROTONOSUPPORT + puts("EPROTONOSUPPORT macro available"); +#else + puts("EPROTONOSUPPORT macro missing"); +#endif /* EPROTONOSUPPORT */ +#ifdef EPROTOTYPE + puts("EPROTOTYPE macro available"); +#else + puts("EPROTOTYPE macro missing"); +#endif /* EPROTOTYPE */ +#ifdef ERANGE + puts("ERANGE macro available"); +#else + puts("ERANGE macro missing"); +#endif /* ERANGE */ +#ifdef EREMCHG + puts("EREMCHG macro available"); +#else + puts("EREMCHG macro missing"); +#endif /* EREMCHG */ +#ifdef EREMOTE + puts("EREMOTE macro available"); +#else + puts("EREMOTE macro missing"); +#endif /* EREMOTE */ +#ifdef EREMOTEIO + puts("EREMOTEIO macro available"); +#else + puts("EREMOTEIO macro missing"); +#endif /* EREMOTEIO */ +#ifdef ERESTART + puts("ERESTART macro available"); +#else + puts("ERESTART macro missing"); +#endif /* ERESTART */ +#ifdef EROFS + puts("EROFS macro available"); +#else + puts("EROFS macro missing"); +#endif /* EROFS */ +#ifdef ESHUTDOWN + puts("ESHUTDOWN macro available"); +#else + puts("ESHUTDOWN macro missing"); +#endif /* ESHUTDOWN */ +#ifdef ESOCKTNOSUPPORT + puts("ESOCKTNOSUPPORT macro available"); +#else + puts("ESOCKTNOSUPPORT macro missing"); +#endif /* ESOCKTNOSUPPORT */ +#ifdef ESPIPE + puts("ESPIPE macro available"); +#else + puts("ESPIPE macro missing"); +#endif /* ESPIPE */ +#ifdef ESRCH + puts("ESRCH macro available"); +#else + puts("ESRCH macro missing"); +#endif /* ESRCH */ +#ifdef ESRMNT + puts("ESRMNT macro available"); +#else + puts("ESRMNT macro missing"); +#endif /* ESRMNT */ +#ifdef ESTALE + puts("ESTALE macro available"); +#else + puts("ESTALE macro missing"); +#endif /* ESTALE */ +#ifdef ESTRPIPE + puts("ESTRPIPE macro available"); +#else + puts("ESTRPIPE macro missing"); +#endif /* ESTRPIPE */ +#ifdef ETIME + puts("ETIME macro available"); +#else + puts("ETIME macro missing"); +#endif /* ETIME */ +#ifdef ETIMEDOUT + puts("ETIMEDOUT macro available"); +#else + puts("ETIMEDOUT macro missing"); +#endif /* ETIMEDOUT */ +#ifdef ETOOMANYREFS + puts("ETOOMANYREFS macro available"); +#else + puts("ETOOMANYREFS macro missing"); +#endif /* ETOOMANYREFS */ +#ifdef ETXTBSY + puts("ETXTBSY macro available"); +#else + puts("ETXTBSY macro missing"); +#endif /* ETXTBSY */ +#ifdef EUCLEAN + puts("EUCLEAN macro available"); +#else + puts("EUCLEAN macro missing"); +#endif /* EUCLEAN */ +#ifdef EUNATCH + puts("EUNATCH macro available"); +#else + puts("EUNATCH macro missing"); +#endif /* EUNATCH */ +#ifdef EUSERS + puts("EUSERS macro available"); +#else + puts("EUSERS macro missing"); +#endif /* EUSERS */ +#ifdef EWOULDBLOCK + puts("EWOULDBLOCK macro available"); +#else + puts("EWOULDBLOCK macro missing"); +#endif /* EWOULDBLOCK */ +#ifdef EXDEV + puts("EXDEV macro available"); +#else + puts("EXDEV macro missing"); +#endif /* EXDEV */ +#ifdef EXFULL + puts("EXFULL macro available"); +#else + puts("EXFULL macro missing"); +#endif /* EXFULL */ + + assert(argc > 0); + puts(program_invocation_short_name); - program_invocation_name = "yes, you can change this"; + argv[0] = "changed to argv[0]"; + program_invocation_name = "changed to program_invocation_name"; + program_invocation_short_name = "changed to program_invocation_short_name"; puts(argv[0]); puts(program_invocation_name); diff --git a/tests/error.c b/tests/error.c index 4170e1f4be..61ba98f7f3 100644 --- a/tests/error.c +++ b/tests/error.c @@ -1,10 +1,15 @@ -#include -#include -#include +#include #include +#include +#include +#include +#include +#include #include "test_helpers.h" +// does not compile with glibc +#ifndef __GLIBC__ int main(void) { chdir("nonexistent"); int err = errno; @@ -12,15 +17,44 @@ int main(void) { printf("errno: %d = %s\n", err, strerror(errno)); perror("perror"); - char buf1[256]; + char buf1[256] = {0}; int ret1 = strerror_r(err, buf1, 256); printf("errno: %d = %s, return: %d\n", err, buf1, ret1); - char buf2[3]; + char buf2[3] = {0}; int ret2 = strerror_r(err, buf2, 3); printf("errno: %d = %s, return: %d\n", err, buf2, ret2); char buf3[256] = {0}; int ret3 = strerror_r(err, buf3, 0); printf("errno: %d = %s, return: %d\n", err, buf3, ret3); + + // Test that the statically allocated buffer doesn't overflow + for (int code = EPERM; code < ENOTRECOVERABLE; ++code) { + const char* message = strerror(code); + if (!message) { + errx(EXIT_FAILURE, + "Expected message for error code: %d", + code + ); + } + } + + const char* actual_error = strerror(INT_MAX); + char expected_error[256] = {0}; + sprintf(expected_error, "Unknown error %d", INT_MAX); + if (strncmp(expected_error, actual_error, 25) != 0) { + errx(EXIT_FAILURE, + "Expected: %s\nGot: %s", + expected_error, actual_error + ); + } + + return EXIT_SUCCESS; } + +#else +int main(void) { + exit(0); +} +#endif diff --git a/tests/expected/alloca.stderr b/tests/expected/bins_dynamic/alloca.stderr similarity index 100% rename from tests/expected/alloca.stderr rename to tests/expected/bins_dynamic/alloca.stderr diff --git a/tests/expected/alloca.stdout b/tests/expected/bins_dynamic/alloca.stdout similarity index 100% rename from tests/expected/alloca.stdout rename to tests/expected/bins_dynamic/alloca.stdout diff --git a/tests/expected/bins_dynamic/args.stdout b/tests/expected/bins_dynamic/args.stdout index 05c95c249b..deefb2f3da 100644 --- a/tests/expected/bins_dynamic/args.stdout +++ b/tests/expected/bins_dynamic/args.stdout @@ -1 +1 @@ -bins_dynamic/args test args +test args diff --git a/tests/expected/arpainet.stderr b/tests/expected/bins_dynamic/arpainet.stderr similarity index 100% rename from tests/expected/arpainet.stderr rename to tests/expected/bins_dynamic/arpainet.stderr diff --git a/tests/expected/arpainet.stdout b/tests/expected/bins_dynamic/arpainet.stdout similarity index 100% rename from tests/expected/arpainet.stdout rename to tests/expected/bins_dynamic/arpainet.stdout diff --git a/tests/expected/assert.stderr b/tests/expected/bins_dynamic/assert.stderr similarity index 100% rename from tests/expected/assert.stderr rename to tests/expected/bins_dynamic/assert.stderr diff --git a/tests/expected/assert.stdout b/tests/expected/bins_dynamic/assert.stdout similarity index 100% rename from tests/expected/assert.stdout rename to tests/expected/bins_dynamic/assert.stdout diff --git a/tests/expected/constructor.stderr b/tests/expected/bins_dynamic/constructor.stderr similarity index 100% rename from tests/expected/constructor.stderr rename to tests/expected/bins_dynamic/constructor.stderr diff --git a/tests/expected/constructor.stdout b/tests/expected/bins_dynamic/constructor.stdout similarity index 100% rename from tests/expected/constructor.stdout rename to tests/expected/bins_dynamic/constructor.stdout diff --git a/tests/expected/ctype.stderr b/tests/expected/bins_dynamic/crypt/blowfish.stderr similarity index 100% rename from tests/expected/ctype.stderr rename to tests/expected/bins_dynamic/crypt/blowfish.stderr diff --git a/tests/expected/destructor.stderr b/tests/expected/bins_dynamic/crypt/blowfish.stdout similarity index 100% rename from tests/expected/destructor.stderr rename to tests/expected/bins_dynamic/crypt/blowfish.stdout diff --git a/tests/expected/dirent/scandir.stderr b/tests/expected/bins_dynamic/crypt/md5.stderr similarity index 100% rename from tests/expected/dirent/scandir.stderr rename to tests/expected/bins_dynamic/crypt/md5.stderr diff --git a/tests/expected/bins_dynamic/crypt/md5.stdout b/tests/expected/bins_dynamic/crypt/md5.stdout new file mode 100644 index 0000000000..f985b46aff --- /dev/null +++ b/tests/expected/bins_dynamic/crypt/md5.stdout @@ -0,0 +1 @@ +Success! diff --git a/tests/expected/dlfcn.stderr b/tests/expected/bins_dynamic/crypt/pbkdf2.stderr similarity index 100% rename from tests/expected/dlfcn.stderr rename to tests/expected/bins_dynamic/crypt/pbkdf2.stderr diff --git a/tests/expected/fcntl/create.stderr b/tests/expected/bins_dynamic/crypt/pbkdf2.stdout similarity index 100% rename from tests/expected/fcntl/create.stderr rename to tests/expected/bins_dynamic/crypt/pbkdf2.stdout diff --git a/tests/expected/fcntl/create.stdout b/tests/expected/bins_dynamic/crypt/scrypt.stderr similarity index 100% rename from tests/expected/fcntl/create.stdout rename to tests/expected/bins_dynamic/crypt/scrypt.stderr diff --git a/tests/expected/fcntl/fcntl.stderr b/tests/expected/bins_dynamic/crypt/scrypt.stdout similarity index 100% rename from tests/expected/fcntl/fcntl.stderr rename to tests/expected/bins_dynamic/crypt/scrypt.stdout diff --git a/tests/expected/fnmatch.stderr b/tests/expected/bins_dynamic/crypt/sha256.stderr similarity index 100% rename from tests/expected/fnmatch.stderr rename to tests/expected/bins_dynamic/crypt/sha256.stderr diff --git a/tests/expected/futimens.stderr b/tests/expected/bins_dynamic/crypt/sha256.stdout similarity index 100% rename from tests/expected/futimens.stderr rename to tests/expected/bins_dynamic/crypt/sha256.stdout diff --git a/tests/expected/futimens.stdout b/tests/expected/bins_dynamic/crypt/sha512.stderr similarity index 100% rename from tests/expected/futimens.stdout rename to tests/expected/bins_dynamic/crypt/sha512.stderr diff --git a/tests/expected/libgen.stderr b/tests/expected/bins_dynamic/crypt/sha512.stdout similarity index 100% rename from tests/expected/libgen.stderr rename to tests/expected/bins_dynamic/crypt/sha512.stdout diff --git a/tests/expected/locale.stderr b/tests/expected/bins_dynamic/ctype.stderr similarity index 100% rename from tests/expected/locale.stderr rename to tests/expected/bins_dynamic/ctype.stderr diff --git a/tests/expected/ctype.stdout b/tests/expected/bins_dynamic/ctype.stdout similarity index 100% rename from tests/expected/ctype.stdout rename to tests/expected/bins_dynamic/ctype.stdout diff --git a/tests/expected/math.stderr b/tests/expected/bins_dynamic/destructor.stderr similarity index 100% rename from tests/expected/math.stderr rename to tests/expected/bins_dynamic/destructor.stderr diff --git a/tests/expected/destructor.stdout b/tests/expected/bins_dynamic/destructor.stdout similarity index 100% rename from tests/expected/destructor.stdout rename to tests/expected/bins_dynamic/destructor.stdout diff --git a/tests/expected/mkfifo.stderr b/tests/expected/bins_dynamic/dirent/fdopendir.stderr similarity index 100% rename from tests/expected/mkfifo.stderr rename to tests/expected/bins_dynamic/dirent/fdopendir.stderr diff --git a/tests/expected/mkfifo.stdout b/tests/expected/bins_dynamic/dirent/fdopendir.stdout similarity index 100% rename from tests/expected/mkfifo.stdout rename to tests/expected/bins_dynamic/dirent/fdopendir.stdout diff --git a/tests/expected/netdb/getaddrinfo.stderr b/tests/expected/bins_dynamic/dirent/scandir.stderr similarity index 100% rename from tests/expected/netdb/getaddrinfo.stderr rename to tests/expected/bins_dynamic/dirent/scandir.stderr diff --git a/tests/expected/dirent/scandir.stdout b/tests/expected/bins_dynamic/dirent/scandir.stdout similarity index 100% rename from tests/expected/dirent/scandir.stdout rename to tests/expected/bins_dynamic/dirent/scandir.stdout diff --git a/tests/expected/netdb/netdb.stderr b/tests/expected/bins_dynamic/dlfcn.stderr similarity index 100% rename from tests/expected/netdb/netdb.stderr rename to tests/expected/bins_dynamic/dlfcn.stderr diff --git a/tests/expected/bins_dynamic/dlfcn.stdout b/tests/expected/bins_dynamic/dlfcn.stdout new file mode 100644 index 0000000000..5b51826bea --- /dev/null +++ b/tests/expected/bins_dynamic/dlfcn.stdout @@ -0,0 +1,6 @@ +add(22, 33) = 55 +puts from dlopened libc +sharedlib: global_var == 42 +sharedlib: tls_var == 21 +main: global_var == 42 +main: tls_var == 21 diff --git a/tests/expected/regex.stderr b/tests/expected/bins_dynamic/dlopen_scopes.stderr similarity index 100% rename from tests/expected/regex.stderr rename to tests/expected/bins_dynamic/dlopen_scopes.stderr diff --git a/tests/expected/select.stderr b/tests/expected/bins_dynamic/dlopen_scopes.stdout similarity index 100% rename from tests/expected/select.stderr rename to tests/expected/bins_dynamic/dlopen_scopes.stdout diff --git a/tests/expected/bins_dynamic/err.stderr b/tests/expected/bins_dynamic/err.stderr new file mode 100644 index 0000000000..b79d9c196f --- /dev/null +++ b/tests/expected/bins_dynamic/err.stderr @@ -0,0 +1,6 @@ +err: Ran out of coffee: Owner died +err: Cat pulled out your ethernet cable +err: Eat 42 cookies: Permission denied +err: Potato, krumpli: Invalid exchange +err: I'm sorry, Dave. I'm afraid I can't do that.: Operation not permitted +err: Bye. It's crowded.: Too many users diff --git a/tests/expected/bins_dynamic/err.stdout b/tests/expected/bins_dynamic/err.stdout new file mode 100644 index 0000000000..8ef72cb5bc --- /dev/null +++ b/tests/expected/bins_dynamic/err.stdout @@ -0,0 +1,2 @@ +err: Dang it, Bobby.: No such file or directory +Exiting due to error code: 87 diff --git a/tests/expected/bins_dynamic/errno.stdout b/tests/expected/bins_dynamic/errno.stdout index b3aff4878b..58419805ea 100644 --- a/tests/expected/bins_dynamic/errno.stdout +++ b/tests/expected/bins_dynamic/errno.stdout @@ -1,6 +1,136 @@ -bins_dynamic/errno -bins_dynamic/errno +E2BIG macro available +EACCES macro available +EADDRINUSE macro available +EADDRNOTAVAIL macro available +EADV macro available +EAFNOSUPPORT macro available +EAGAIN macro available +EALREADY macro available +EBADE macro available +EBADF macro available +EBADFD macro available +EBADMSG macro available +EBADR macro available +EBADRQC macro available +EBADSLT macro available +EBFONT macro available +EBUSY macro available +ECANCELED macro available +ECHILD macro available +ECHRNG macro available +ECOMM macro available +ECONNABORTED macro available +ECONNREFUSED macro available +ECONNRESET macro available +EDEADLK macro available +EDEADLOCK macro available +EDESTADDRREQ macro available +EDOM macro available +EDOTDOT macro available +EDQUOT macro available +EEXIST macro available +EFAULT macro available +EFBIG macro available +EHOSTDOWN macro available +EHOSTUNREACH macro available +EIDRM macro available +EILSEQ macro available +EINPROGRESS macro available +EINTR macro available +EINVAL macro available +EIO macro available +EISCONN macro available +EISDIR macro available +EISNAM macro available +EKEYEXPIRED macro available +EKEYREJECTED macro available +EKEYREVOKED macro available +EL2HLT macro available +EL2NSYNC macro available +EL3HLT macro available +EL3RST macro available +ELIBACC macro available +ELIBBAD macro available +ELIBEXEC macro available +ELIBMAX macro available +ELIBSCN macro available +ELNRNG macro available +ELOOP macro available +EMEDIUMTYPE macro available +EMFILE macro available +EMLINK macro available +EMSGSIZE macro available +EMULTIHOP macro available +ENAMETOOLONG macro available +ENAVAIL macro available +ENETDOWN macro available +ENETRESET macro available +ENETUNREACH macro available +ENFILE macro available +ENOANO macro available +ENOBUFS macro available +ENOCSI macro available +ENODATA macro available +ENODEV macro available +ENOENT macro available +ENOEXEC macro available +ENOKEY macro available +ENOLCK macro available +ENOLINK macro available +ENOMEDIUM macro available +ENOMEM macro available +ENOMSG macro available +ENONET macro available +ENOPKG macro available +ENOPROTOOPT macro available +ENOSPC macro available +ENOSR macro available +ENOSTR macro available +ENOSYS macro available +ENOTBLK macro available +ENOTCONN macro available +ENOTDIR macro available +ENOTEMPTY macro available +ENOTNAM macro available +ENOTRECOVERABLE macro available +ENOTSOCK macro available +ENOTSUP macro available +ENOTTY macro available +ENOTUNIQ macro available +ENXIO macro available +EOPNOTSUPP macro available +EOVERFLOW macro available +EOWNERDEAD macro available +EPERM macro available +EPFNOSUPPORT macro available +EPIPE macro available +EPROTO macro available +EPROTONOSUPPORT macro available +EPROTOTYPE macro available +ERANGE macro available +EREMCHG macro available +EREMOTE macro available +EREMOTEIO macro available +ERESTART macro available +EROFS macro available +ESHUTDOWN macro available +ESOCKTNOSUPPORT macro available +ESPIPE macro available +ESRCH macro available +ESRMNT macro available +ESTALE macro available +ESTRPIPE macro available +ETIME macro available +ETIMEDOUT macro available +ETOOMANYREFS macro available +ETXTBSY macro available +EUCLEAN macro available +EUNATCH macro available +EUSERS macro available +EWOULDBLOCK macro available +EXDEV macro available +EXFULL macro available errno -yes, you can change this -yes, you can change this -yes, you can change this +changed to argv[0] +changed to program_invocation_name +changed to program_invocation_short_name diff --git a/tests/expected/error.stderr b/tests/expected/bins_dynamic/error.stderr similarity index 100% rename from tests/expected/error.stderr rename to tests/expected/bins_dynamic/error.stderr diff --git a/tests/expected/error.stdout b/tests/expected/bins_dynamic/error.stdout similarity index 100% rename from tests/expected/error.stdout rename to tests/expected/bins_dynamic/error.stdout diff --git a/tests/expected/setjmp.stderr b/tests/expected/bins_dynamic/fcntl/create.stderr similarity index 100% rename from tests/expected/setjmp.stderr rename to tests/expected/bins_dynamic/fcntl/create.stderr diff --git a/tests/expected/sigaction.stderr b/tests/expected/bins_dynamic/fcntl/create.stdout similarity index 100% rename from tests/expected/sigaction.stderr rename to tests/expected/bins_dynamic/fcntl/create.stdout diff --git a/tests/expected/signal.stderr b/tests/expected/bins_dynamic/fcntl/fcntl.stderr similarity index 100% rename from tests/expected/signal.stderr rename to tests/expected/bins_dynamic/fcntl/fcntl.stderr diff --git a/tests/expected/fcntl/fcntl.stdout b/tests/expected/bins_dynamic/fcntl/fcntl.stdout similarity index 100% rename from tests/expected/fcntl/fcntl.stdout rename to tests/expected/bins_dynamic/fcntl/fcntl.stdout diff --git a/tests/expected/stdio/all.stderr b/tests/expected/bins_dynamic/fcntl/open.stderr similarity index 100% rename from tests/expected/stdio/all.stderr rename to tests/expected/bins_dynamic/fcntl/open.stderr diff --git a/tests/expected/stdio/buffer.stderr b/tests/expected/bins_dynamic/fcntl/open.stdout similarity index 100% rename from tests/expected/stdio/buffer.stderr rename to tests/expected/bins_dynamic/fcntl/open.stdout diff --git a/tests/expected/stdio/fgets.stderr b/tests/expected/bins_dynamic/fcntl/posix_fallocate.stderr similarity index 100% rename from tests/expected/stdio/fgets.stderr rename to tests/expected/bins_dynamic/fcntl/posix_fallocate.stderr diff --git a/tests/expected/stdio/fputs.stderr b/tests/expected/bins_dynamic/fcntl/posix_fallocate.stdout similarity index 100% rename from tests/expected/stdio/fputs.stderr rename to tests/expected/bins_dynamic/fcntl/posix_fallocate.stdout diff --git a/tests/expected/stdio/fputs.stdout b/tests/expected/bins_dynamic/fnmatch.stderr similarity index 100% rename from tests/expected/stdio/fputs.stdout rename to tests/expected/bins_dynamic/fnmatch.stderr diff --git a/tests/expected/fnmatch.stdout b/tests/expected/bins_dynamic/fnmatch.stdout similarity index 93% rename from tests/expected/fnmatch.stdout rename to tests/expected/bins_dynamic/fnmatch.stdout index 14e0ae0140..48ba8aef2e 100644 --- a/tests/expected/fnmatch.stdout +++ b/tests/expected/bins_dynamic/fnmatch.stdout @@ -29,3 +29,4 @@ Should fail: "[a!][a!]" doesn't match "ab" "hello[/+]world" doesn't match "hello/world" "hello world" doesn't match "HELLO WORLD" +"********************a" doesn't match "xxxxxxxxxxxxxxxxxxxxb" diff --git a/tests/expected/stdio/fread.stderr b/tests/expected/bins_dynamic/futimens.stderr similarity index 100% rename from tests/expected/stdio/fread.stderr rename to tests/expected/bins_dynamic/futimens.stderr diff --git a/tests/expected/stdio/freopen.stderr b/tests/expected/bins_dynamic/futimens.stdout similarity index 100% rename from tests/expected/stdio/freopen.stderr rename to tests/expected/bins_dynamic/futimens.stdout diff --git a/tests/expected/stdio/fscanf.stderr b/tests/expected/bins_dynamic/iso646.stderr similarity index 100% rename from tests/expected/stdio/fscanf.stderr rename to tests/expected/bins_dynamic/iso646.stderr diff --git a/tests/expected/bins_dynamic/iso646.stdout b/tests/expected/bins_dynamic/iso646.stdout new file mode 100644 index 0000000000..5f8a399fe8 --- /dev/null +++ b/tests/expected/bins_dynamic/iso646.stdout @@ -0,0 +1,33 @@ +0 and 0: 0 +1 and 0: 0 +0 and 1: 0 +1 and 1: 1 +0 and_eq 0: 0 +1 and_eq 0: 0 +0 and_eq 1: 0 +1 and_eq 1: 1 +a bitand b: 8 +a bitor b: 14 +compl a: 245 +not 0: 1 +not 1: 0 +0 not_eq 0: 0 +1 not_eq 0: 1 +0 not_eq 1: 0 +1 not_eq 1: 1 +0 or 0: 0 +1 or 0: 1 +0 or 1: 1 +1 or 1: 1 +0 or_eq 0: 0 +1 or_eq 0: 1 +0 or_eq 1: 1 +1 or_eq 1: 1 +0 xor 0: 0 +1 xor 0: 1 +0 xor 1: 1 +1 xor 1: 0 +0 xor_eq 0: 0 +1 xor_eq 0: 1 +0 xor_eq 1: 1 +1 xor_eq 1: 0 diff --git a/tests/expected/stdio/fscanf_offby1.stderr b/tests/expected/bins_dynamic/libgen.stderr similarity index 100% rename from tests/expected/stdio/fscanf_offby1.stderr rename to tests/expected/bins_dynamic/libgen.stderr diff --git a/tests/expected/libgen.stdout b/tests/expected/bins_dynamic/libgen.stdout similarity index 100% rename from tests/expected/libgen.stdout rename to tests/expected/bins_dynamic/libgen.stdout diff --git a/tests/expected/stdio/fseek.stderr b/tests/expected/bins_dynamic/locale/duplocale.stderr similarity index 100% rename from tests/expected/stdio/fseek.stderr rename to tests/expected/bins_dynamic/locale/duplocale.stderr diff --git a/tests/expected/stdio/fwrite.stderr b/tests/expected/bins_dynamic/locale/duplocale.stdout similarity index 100% rename from tests/expected/stdio/fwrite.stderr rename to tests/expected/bins_dynamic/locale/duplocale.stdout diff --git a/tests/expected/stdio/fwrite.stdout b/tests/expected/bins_dynamic/locale/newlocale.stderr similarity index 100% rename from tests/expected/stdio/fwrite.stdout rename to tests/expected/bins_dynamic/locale/newlocale.stderr diff --git a/tests/expected/stdio/getc_unget.stderr b/tests/expected/bins_dynamic/locale/newlocale.stdout similarity index 100% rename from tests/expected/stdio/getc_unget.stderr rename to tests/expected/bins_dynamic/locale/newlocale.stdout diff --git a/tests/expected/stdio/mutex.stderr b/tests/expected/bins_dynamic/locale/setlocale.stderr similarity index 100% rename from tests/expected/stdio/mutex.stderr rename to tests/expected/bins_dynamic/locale/setlocale.stderr diff --git a/tests/expected/locale.stdout b/tests/expected/bins_dynamic/locale/setlocale.stdout similarity index 100% rename from tests/expected/locale.stdout rename to tests/expected/bins_dynamic/locale/setlocale.stdout diff --git a/tests/expected/stdio/mutex.stdout b/tests/expected/bins_dynamic/malloc/usable_size.stderr similarity index 100% rename from tests/expected/stdio/mutex.stdout rename to tests/expected/bins_dynamic/malloc/usable_size.stderr diff --git a/tests/expected/bins_dynamic/malloc/usable_size.stdout b/tests/expected/bins_dynamic/malloc/usable_size.stdout new file mode 100644 index 0000000000..4188b67b66 --- /dev/null +++ b/tests/expected/bins_dynamic/malloc/usable_size.stdout @@ -0,0 +1,2 @@ +malloc: 27 bytes +malloc_usable_size: 48 bytes diff --git a/tests/expected/stdio/popen.stderr b/tests/expected/bins_dynamic/math.stderr similarity index 100% rename from tests/expected/stdio/popen.stderr rename to tests/expected/bins_dynamic/math.stderr diff --git a/tests/expected/math.stdout b/tests/expected/bins_dynamic/math.stdout similarity index 100% rename from tests/expected/math.stdout rename to tests/expected/bins_dynamic/math.stdout diff --git a/tests/expected/stdio/printf.stderr b/tests/expected/bins_dynamic/mkfifo.stderr similarity index 100% rename from tests/expected/stdio/printf.stderr rename to tests/expected/bins_dynamic/mkfifo.stderr diff --git a/tests/expected/stdio/printf_neg_pad.stderr b/tests/expected/bins_dynamic/mkfifo.stdout similarity index 100% rename from tests/expected/stdio/printf_neg_pad.stderr rename to tests/expected/bins_dynamic/mkfifo.stdout diff --git a/tests/expected/stdio/printf_space_pad.stderr b/tests/expected/bins_dynamic/mknod.stderr similarity index 100% rename from tests/expected/stdio/printf_space_pad.stderr rename to tests/expected/bins_dynamic/mknod.stderr diff --git a/tests/expected/stdio/rename.stderr b/tests/expected/bins_dynamic/mknod.stdout similarity index 100% rename from tests/expected/stdio/rename.stderr rename to tests/expected/bins_dynamic/mknod.stdout diff --git a/tests/expected/stdio/rename.stdout b/tests/expected/bins_dynamic/mknodat.stderr similarity index 100% rename from tests/expected/stdio/rename.stdout rename to tests/expected/bins_dynamic/mknodat.stderr diff --git a/tests/expected/stdio/scanf.stderr b/tests/expected/bins_dynamic/mknodat.stdout similarity index 100% rename from tests/expected/stdio/scanf.stderr rename to tests/expected/bins_dynamic/mknodat.stdout diff --git a/tests/expected/stdio/setvbuf.stderr b/tests/expected/bins_dynamic/netdb/getaddrinfo.stderr similarity index 100% rename from tests/expected/stdio/setvbuf.stderr rename to tests/expected/bins_dynamic/netdb/getaddrinfo.stderr diff --git a/tests/expected/netdb/getaddrinfo.stdout b/tests/expected/bins_dynamic/netdb/getaddrinfo.stdout similarity index 100% rename from tests/expected/netdb/getaddrinfo.stdout rename to tests/expected/bins_dynamic/netdb/getaddrinfo.stdout diff --git a/tests/expected/ptrace.stderr b/tests/expected/bins_dynamic/ptrace.stderr similarity index 100% rename from tests/expected/ptrace.stderr rename to tests/expected/bins_dynamic/ptrace.stderr diff --git a/tests/expected/bins_dynamic/ptrace.stdout b/tests/expected/bins_dynamic/ptrace.stdout new file mode 100644 index 0000000000..824e89accd --- /dev/null +++ b/tests/expected/bins_dynamic/ptrace.stdout @@ -0,0 +1,5 @@ +Set regs +Set regs +Set regs +Set regs +Child exited with status 0 diff --git a/tests/expected/stdio/sprintf.stderr b/tests/expected/bins_dynamic/pty/forkpty.stderr similarity index 100% rename from tests/expected/stdio/sprintf.stderr rename to tests/expected/bins_dynamic/pty/forkpty.stderr diff --git a/tests/expected/stdio/ungetc_ftell.stderr b/tests/expected/bins_dynamic/pty/forkpty.stdout similarity index 100% rename from tests/expected/stdio/ungetc_ftell.stderr rename to tests/expected/bins_dynamic/pty/forkpty.stdout diff --git a/tests/expected/stdio/ungetc_multiple.stderr b/tests/expected/bins_dynamic/regex.stderr similarity index 100% rename from tests/expected/stdio/ungetc_multiple.stderr rename to tests/expected/bins_dynamic/regex.stderr diff --git a/tests/expected/regex.stdout b/tests/expected/bins_dynamic/regex.stdout similarity index 100% rename from tests/expected/regex.stdout rename to tests/expected/bins_dynamic/regex.stdout diff --git a/tests/expected/stdlib/a64l.stderr b/tests/expected/bins_dynamic/select.stderr similarity index 100% rename from tests/expected/stdlib/a64l.stderr rename to tests/expected/bins_dynamic/select.stderr diff --git a/tests/expected/select.stdout b/tests/expected/bins_dynamic/select.stdout similarity index 100% rename from tests/expected/select.stdout rename to tests/expected/bins_dynamic/select.stdout diff --git a/tests/expected/stdlib/alloc.stderr b/tests/expected/bins_dynamic/setjmp.stderr similarity index 100% rename from tests/expected/stdlib/alloc.stderr rename to tests/expected/bins_dynamic/setjmp.stderr diff --git a/tests/expected/setjmp.stdout b/tests/expected/bins_dynamic/setjmp.stdout similarity index 100% rename from tests/expected/setjmp.stdout rename to tests/expected/bins_dynamic/setjmp.stdout diff --git a/tests/expected/stdlib/atof.stderr b/tests/expected/bins_dynamic/sigaction.stderr similarity index 100% rename from tests/expected/stdlib/atof.stderr rename to tests/expected/bins_dynamic/sigaction.stderr diff --git a/tests/expected/sigaction.stdout b/tests/expected/bins_dynamic/sigaction.stdout similarity index 100% rename from tests/expected/sigaction.stdout rename to tests/expected/bins_dynamic/sigaction.stdout diff --git a/tests/expected/stdlib/atoi.stderr b/tests/expected/bins_dynamic/sigaltstack.stderr similarity index 100% rename from tests/expected/stdlib/atoi.stderr rename to tests/expected/bins_dynamic/sigaltstack.stderr diff --git a/tests/expected/bins_dynamic/sigaltstack.stdout b/tests/expected/bins_dynamic/sigaltstack.stdout new file mode 100644 index 0000000000..be1dfac342 --- /dev/null +++ b/tests/expected/bins_dynamic/sigaltstack.stdout @@ -0,0 +1,101 @@ +SIGUSR2 handler00 +SIGUSR2 handler01 +SIGUSR2 handler02 +SIGUSR2 handler03 +SIGUSR2 handler04 +SIGUSR2 handler05 +SIGUSR2 handler06 +SIGUSR2 handler07 +SIGUSR2 handler08 +SIGUSR2 handler09 +SIGUSR2 handler10 +SIGUSR2 handler11 +SIGUSR2 handler12 +SIGUSR2 handler13 +SIGUSR2 handler14 +SIGUSR2 handler15 +SIGUSR2 handler16 +SIGUSR2 handler17 +SIGUSR2 handler18 +SIGUSR2 handler19 +SIGUSR2 handler20 +SIGUSR2 handler21 +SIGUSR2 handler22 +SIGUSR2 handler23 +SIGUSR2 handler24 +SIGUSR2 handler25 +SIGUSR2 handler26 +SIGUSR2 handler27 +SIGUSR2 handler28 +SIGUSR2 handler29 +SIGUSR2 handler30 +SIGUSR2 handler31 +SIGUSR2 handler32 +SIGUSR2 handler33 +SIGUSR2 handler34 +SIGUSR2 handler35 +SIGUSR2 handler36 +SIGUSR2 handler37 +SIGUSR2 handler38 +SIGUSR2 handler39 +SIGUSR2 handler40 +SIGUSR2 handler41 +SIGUSR2 handler42 +SIGUSR2 handler43 +SIGUSR2 handler44 +SIGUSR2 handler45 +SIGUSR2 handler46 +SIGUSR2 handler47 +SIGUSR2 handler48 +SIGUSR2 handler49 +SIGUSR2 handler50 +SIGUSR2 handler51 +SIGUSR2 handler52 +SIGUSR2 handler53 +SIGUSR2 handler54 +SIGUSR2 handler55 +SIGUSR2 handler56 +SIGUSR2 handler57 +SIGUSR2 handler58 +SIGUSR2 handler59 +SIGUSR2 handler60 +SIGUSR2 handler61 +SIGUSR2 handler62 +SIGUSR2 handler63 +SIGUSR2 handler64 +SIGUSR2 handler65 +SIGUSR2 handler66 +SIGUSR2 handler67 +SIGUSR2 handler68 +SIGUSR2 handler69 +SIGUSR2 handler70 +SIGUSR2 handler71 +SIGUSR2 handler72 +SIGUSR2 handler73 +SIGUSR2 handler74 +SIGUSR2 handler75 +SIGUSR2 handler76 +SIGUSR2 handler77 +SIGUSR2 handler78 +SIGUSR2 handler79 +SIGUSR2 handler80 +SIGUSR2 handler81 +SIGUSR2 handler82 +SIGUSR2 handler83 +SIGUSR2 handler84 +SIGUSR2 handler85 +SIGUSR2 handler86 +SIGUSR2 handler87 +SIGUSR2 handler88 +SIGUSR2 handler89 +SIGUSR2 handler90 +SIGUSR2 handler91 +SIGUSR2 handler92 +SIGUSR2 handler93 +SIGUSR2 handler94 +SIGUSR2 handler95 +SIGUSR2 handler96 +SIGUSR2 handler97 +SIGUSR2 handler98 +SIGUSR2 handler99 +SIGUSR2 handler00 diff --git a/tests/expected/stdlib/div.stderr b/tests/expected/bins_dynamic/signal.stderr similarity index 100% rename from tests/expected/stdlib/div.stderr rename to tests/expected/bins_dynamic/signal.stderr diff --git a/tests/expected/signal.stdout b/tests/expected/bins_dynamic/signal.stdout similarity index 100% rename from tests/expected/signal.stdout rename to tests/expected/bins_dynamic/signal.stdout diff --git a/tests/expected/stdlib/div.stdout b/tests/expected/bins_dynamic/sigsetjmp.stderr similarity index 100% rename from tests/expected/stdlib/div.stdout rename to tests/expected/bins_dynamic/sigsetjmp.stderr diff --git a/tests/expected/bins_dynamic/sigsetjmp.stdout b/tests/expected/bins_dynamic/sigsetjmp.stdout new file mode 100644 index 0000000000..eb99a60c8e --- /dev/null +++ b/tests/expected/bins_dynamic/sigsetjmp.stdout @@ -0,0 +1,2 @@ +Starting jump... +Jump done. diff --git a/tests/expected/stdlib/env.stderr b/tests/expected/bins_dynamic/stdio/all.stderr similarity index 100% rename from tests/expected/stdlib/env.stderr rename to tests/expected/bins_dynamic/stdio/all.stderr diff --git a/tests/expected/stdio/all.stdout b/tests/expected/bins_dynamic/stdio/all.stdout similarity index 100% rename from tests/expected/stdio/all.stdout rename to tests/expected/bins_dynamic/stdio/all.stdout diff --git a/tests/expected/stdlib/mkostemps.stderr b/tests/expected/bins_dynamic/stdio/buffer.stderr similarity index 100% rename from tests/expected/stdlib/mkostemps.stderr rename to tests/expected/bins_dynamic/stdio/buffer.stderr diff --git a/tests/expected/stdio/buffer.stdout b/tests/expected/bins_dynamic/stdio/buffer.stdout similarity index 100% rename from tests/expected/stdio/buffer.stdout rename to tests/expected/bins_dynamic/stdio/buffer.stdout diff --git a/tests/expected/stdlib/rand.stderr b/tests/expected/bins_dynamic/stdio/dprintf.stderr similarity index 100% rename from tests/expected/stdlib/rand.stderr rename to tests/expected/bins_dynamic/stdio/dprintf.stderr diff --git a/tests/expected/bins_dynamic/stdio/dprintf.stdout b/tests/expected/bins_dynamic/stdio/dprintf.stdout new file mode 100644 index 0000000000..e12bc872a0 --- /dev/null +++ b/tests/expected/bins_dynamic/stdio/dprintf.stdout @@ -0,0 +1,2 @@ +Hello, world +a diff --git a/tests/expected/stdlib/rand48.stderr b/tests/expected/bins_dynamic/stdio/fgets.stderr similarity index 100% rename from tests/expected/stdlib/rand48.stderr rename to tests/expected/bins_dynamic/stdio/fgets.stderr diff --git a/tests/expected/stdio/fgets.stdout b/tests/expected/bins_dynamic/stdio/fgets.stdout similarity index 100% rename from tests/expected/stdio/fgets.stdout rename to tests/expected/bins_dynamic/stdio/fgets.stdout diff --git a/tests/expected/stdlib/random.stderr b/tests/expected/bins_dynamic/stdio/fputs.stderr similarity index 100% rename from tests/expected/stdlib/random.stderr rename to tests/expected/bins_dynamic/stdio/fputs.stderr diff --git a/tests/expected/stdlib/strtod.stderr b/tests/expected/bins_dynamic/stdio/fputs.stdout similarity index 100% rename from tests/expected/stdlib/strtod.stderr rename to tests/expected/bins_dynamic/stdio/fputs.stdout diff --git a/tests/expected/stdlib/strtol.stderr b/tests/expected/bins_dynamic/stdio/fread.stderr similarity index 100% rename from tests/expected/stdlib/strtol.stderr rename to tests/expected/bins_dynamic/stdio/fread.stderr diff --git a/tests/expected/stdio/fread.stdout b/tests/expected/bins_dynamic/stdio/fread.stdout similarity index 100% rename from tests/expected/stdio/fread.stdout rename to tests/expected/bins_dynamic/stdio/fread.stdout diff --git a/tests/expected/stdlib/strtoul.stderr b/tests/expected/bins_dynamic/stdio/freopen.stderr similarity index 100% rename from tests/expected/stdlib/strtoul.stderr rename to tests/expected/bins_dynamic/stdio/freopen.stderr diff --git a/tests/expected/stdio/freopen.stdout b/tests/expected/bins_dynamic/stdio/freopen.stdout similarity index 100% rename from tests/expected/stdio/freopen.stdout rename to tests/expected/bins_dynamic/stdio/freopen.stdout diff --git a/tests/expected/stdlib/system.stderr b/tests/expected/bins_dynamic/stdio/fscanf.stderr similarity index 100% rename from tests/expected/stdlib/system.stderr rename to tests/expected/bins_dynamic/stdio/fscanf.stderr diff --git a/tests/expected/stdio/fscanf.stdout b/tests/expected/bins_dynamic/stdio/fscanf.stdout similarity index 100% rename from tests/expected/stdio/fscanf.stdout rename to tests/expected/bins_dynamic/stdio/fscanf.stdout diff --git a/tests/expected/string/mem.stderr b/tests/expected/bins_dynamic/stdio/fscanf_offby1.stderr similarity index 100% rename from tests/expected/string/mem.stderr rename to tests/expected/bins_dynamic/stdio/fscanf_offby1.stderr diff --git a/tests/expected/stdio/fscanf_offby1.stdout b/tests/expected/bins_dynamic/stdio/fscanf_offby1.stdout similarity index 100% rename from tests/expected/stdio/fscanf_offby1.stdout rename to tests/expected/bins_dynamic/stdio/fscanf_offby1.stdout diff --git a/tests/expected/string/strcat.stderr b/tests/expected/bins_dynamic/stdio/fseek.stderr similarity index 100% rename from tests/expected/string/strcat.stderr rename to tests/expected/bins_dynamic/stdio/fseek.stderr diff --git a/tests/expected/stdio/fseek.stdout b/tests/expected/bins_dynamic/stdio/fseek.stdout similarity index 100% rename from tests/expected/stdio/fseek.stdout rename to tests/expected/bins_dynamic/stdio/fseek.stdout diff --git a/tests/expected/string/strchr.stderr b/tests/expected/bins_dynamic/stdio/fwrite.stderr similarity index 100% rename from tests/expected/string/strchr.stderr rename to tests/expected/bins_dynamic/stdio/fwrite.stderr diff --git a/tests/expected/string/strcpy.stderr b/tests/expected/bins_dynamic/stdio/fwrite.stdout similarity index 100% rename from tests/expected/string/strcpy.stderr rename to tests/expected/bins_dynamic/stdio/fwrite.stdout diff --git a/tests/expected/string/strcspn.stderr b/tests/expected/bins_dynamic/stdio/getc_unget.stderr similarity index 100% rename from tests/expected/string/strcspn.stderr rename to tests/expected/bins_dynamic/stdio/getc_unget.stderr diff --git a/tests/expected/stdio/getc_unget.stdout b/tests/expected/bins_dynamic/stdio/getc_unget.stdout similarity index 100% rename from tests/expected/stdio/getc_unget.stdout rename to tests/expected/bins_dynamic/stdio/getc_unget.stdout diff --git a/tests/expected/string/strlen.stderr b/tests/expected/bins_dynamic/stdio/getline.stderr similarity index 100% rename from tests/expected/string/strlen.stderr rename to tests/expected/bins_dynamic/stdio/getline.stderr diff --git a/tests/expected/bins_dynamic/stdio/getline.stdout b/tests/expected/bins_dynamic/stdio/getline.stdout new file mode 100644 index 0000000000..ba753ef6b2 --- /dev/null +++ b/tests/expected/bins_dynamic/stdio/getline.stdout @@ -0,0 +1,41 @@ + 1: getline(NULL, NULL, stream) + => 22 (Invalid argument) - EINVAL + 2: getline(NULL, &n, stream) + => 22 (Invalid argument) - EINVAL + 3: getline(&lineptr, NULL, stream) + => 22 (Invalid argument) - EINVAL + 4: getline(NULL, NULL, NULL) + => 22 (Invalid argument) - EINVAL + 5: getline(&lineptr, NULL, NULL) + => 22 (Invalid argument) - EINVAL + 6: getline(NULL, &n, NULL) + => 22 (Invalid argument) - EINVAL + 7: getline(&lineptr, &n, NULL) + => 22 (Invalid argument) - EINVAL + 8: getdelim(&lineptr, &n, 25600, stream) + => 22 (Invalid argument) - EINVAL + + 1: status = 27, strlen = 27, feof = 0, ferror = 0 + |>Space: the final frontier. + 2: status = 1, strlen = 1, feof = 0, ferror = 0 + |> + 3: status = 50, strlen = 50, feof = 0, ferror = 0 + |>These are the voyages of the starship Enterprise. + 4: status = 24, strlen = 24, feof = 0, ferror = 0 + |>Its continuing mission: + 5: status = 33, strlen = 33, feof = 0, ferror = 0 + |>- to explore strange new worlds; + 6: status = 46, strlen = 46, feof = 0, ferror = 0 + |>- to seek out new life and new civilizations; + 7: status = 45, strlen = 45, feof = 0, ferror = 0 + |>- to boldly go where no one has gone before! + +overread 1, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + +overread 2, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + +overread 3, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + diff --git a/tests/expected/string/strncmp.stderr b/tests/expected/bins_dynamic/stdio/mutex.stderr similarity index 100% rename from tests/expected/string/strncmp.stderr rename to tests/expected/bins_dynamic/stdio/mutex.stderr diff --git a/tests/expected/string/strpbrk.stderr b/tests/expected/bins_dynamic/stdio/mutex.stdout similarity index 100% rename from tests/expected/string/strpbrk.stderr rename to tests/expected/bins_dynamic/stdio/mutex.stdout diff --git a/tests/expected/string/strrchr.stderr b/tests/expected/bins_dynamic/stdio/popen.stderr similarity index 100% rename from tests/expected/string/strrchr.stderr rename to tests/expected/bins_dynamic/stdio/popen.stderr diff --git a/tests/expected/bins_dynamic/stdio/popen.stdout b/tests/expected/bins_dynamic/stdio/popen.stdout new file mode 100644 index 0000000000..05f7216cbe --- /dev/null +++ b/tests/expected/bins_dynamic/stdio/popen.stdout @@ -0,0 +1,8 @@ +Line 1: 1-never-gonna-give-you-up +Line 2: 2-never-gonna-let-you-down +Line 3: 3-never-gonna-run-around +Line 4: 4-and-desert-you +Line 5: 5-never-gonna-make-you-cry +Line 6: 6-never-gonna-say-goodbye +Line 7: 7-never-gonna-tell-a-lie +Line 8: 8-and-hurt-you diff --git a/tests/expected/string/strsignal.stderr b/tests/expected/bins_dynamic/stdio/printf.stderr similarity index 100% rename from tests/expected/string/strsignal.stderr rename to tests/expected/bins_dynamic/stdio/printf.stderr diff --git a/tests/expected/bins_dynamic/stdio/printf.stdout b/tests/expected/bins_dynamic/stdio/printf.stdout new file mode 100644 index 0000000000..a13543aeb2 --- /dev/null +++ b/tests/expected/bins_dynamic/stdio/printf.stdout @@ -0,0 +1,78 @@ +percent: % +string: String +char: c +char: +int: -16 +uint: 32 +hex: beef +HEX: C0FFEE +string: end +%n returned 51, total len of write: 94 + +Padding madness: + 001 +2 + c + 0x00000ff + 001 +0 0x1 + 1 +(00123) ( 123) +(-0123) ( -123) +( ) +0xabcdef +(nil) + +Positional madness: +4 3 2 +00002 +|Fiz |Buz | Fiz| Tot| +int: 5 double: 0.100000 0.200000 0.300000 0.400000 +-1717986918 0.100000 +-1717986918 0.200000 + +Float madness: + 1.234568e+02 + 1.000000E-05 + 123.456789 + 0.000010 + -1.234568e+02 +-00000001.234568e+02 +%.5g: -123.46 +%.5f: -123.45679 +%.5e: -1.23457e+02 +%.*g: -1.2e+02 +%.*f: -123.46 +%.*e: -1.23e+02 +%.*2$g: -123.46 +%.*2$f: -123.45679 +%.*2$e: -1.23457e+02 +100000 +1e+06 +1.000000e+06 +0.0001 +1E-05 +1.000000E-05 +001234.56 +001234.56 + +Non-finite float madness: +%e: inf -inf nan -nan +%E: INF -INF NAN -NAN +%f: inf -inf nan -nan +%F: INF -INF NAN -NAN +%g: inf -inf nan -nan +%G: INF -INF NAN -NAN +Things that have been buggy ++05 +Testing asprintf... +printed: test string, value: 11 +printed: test string 2, value: 13 +printed: test string 2, value: 13 + +%m: Owner died + +C23: +Binary %b: 100 +Binary %b alternate: 0b100 +Binary %B: 100 +Binary %B alternate: 0B100 diff --git a/tests/expected/string/strspn.stderr b/tests/expected/bins_dynamic/stdio/printf_neg_pad.stderr similarity index 100% rename from tests/expected/string/strspn.stderr rename to tests/expected/bins_dynamic/stdio/printf_neg_pad.stderr diff --git a/tests/expected/stdio/printf_neg_pad.stdout b/tests/expected/bins_dynamic/stdio/printf_neg_pad.stdout similarity index 100% rename from tests/expected/stdio/printf_neg_pad.stdout rename to tests/expected/bins_dynamic/stdio/printf_neg_pad.stdout diff --git a/tests/expected/string/strstr.stderr b/tests/expected/bins_dynamic/stdio/printf_space_pad.stderr similarity index 100% rename from tests/expected/string/strstr.stderr rename to tests/expected/bins_dynamic/stdio/printf_space_pad.stderr diff --git a/tests/expected/stdio/printf_space_pad.stdout b/tests/expected/bins_dynamic/stdio/printf_space_pad.stdout similarity index 100% rename from tests/expected/stdio/printf_space_pad.stdout rename to tests/expected/bins_dynamic/stdio/printf_space_pad.stdout diff --git a/tests/expected/string/strtok.stderr b/tests/expected/bins_dynamic/stdio/putc_unlocked.stderr similarity index 100% rename from tests/expected/string/strtok.stderr rename to tests/expected/bins_dynamic/stdio/putc_unlocked.stderr diff --git a/tests/expected/string/strtok_r.stderr b/tests/expected/bins_dynamic/stdio/putc_unlocked.stdout similarity index 100% rename from tests/expected/string/strtok_r.stderr rename to tests/expected/bins_dynamic/stdio/putc_unlocked.stdout diff --git a/tests/expected/strings.stderr b/tests/expected/bins_dynamic/stdio/rename.stderr similarity index 100% rename from tests/expected/strings.stderr rename to tests/expected/bins_dynamic/stdio/rename.stderr diff --git a/tests/expected/strings.stdout b/tests/expected/bins_dynamic/stdio/rename.stdout similarity index 100% rename from tests/expected/strings.stdout rename to tests/expected/bins_dynamic/stdio/rename.stdout diff --git a/tests/expected/sys_mman.stderr b/tests/expected/bins_dynamic/stdio/renameat.stderr similarity index 100% rename from tests/expected/sys_mman.stderr rename to tests/expected/bins_dynamic/stdio/renameat.stderr diff --git a/tests/expected/time/asctime.stderr b/tests/expected/bins_dynamic/stdio/renameat.stdout similarity index 100% rename from tests/expected/time/asctime.stderr rename to tests/expected/bins_dynamic/stdio/renameat.stdout diff --git a/tests/expected/time/gmtime.stderr b/tests/expected/bins_dynamic/stdio/scanf.stderr similarity index 100% rename from tests/expected/time/gmtime.stderr rename to tests/expected/bins_dynamic/stdio/scanf.stderr diff --git a/tests/expected/stdio/scanf.stdout b/tests/expected/bins_dynamic/stdio/scanf.stdout similarity index 59% rename from tests/expected/stdio/scanf.stdout rename to tests/expected/bins_dynamic/stdio/scanf.stdout index 97bcc1af63..dca295ccb5 100644 --- a/tests/expected/stdio/scanf.stdout +++ b/tests/expected/bins_dynamic/stdio/scanf.stdout @@ -7,8 +7,10 @@ 2, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } 1, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } 0, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } -4, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: e, string2: o, string3: l, string4: d } -4, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } -4, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } -1, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: testbbbb, string2: e, string3: f, string4: dddddd } +2, { sa: 12, ia: 42, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: world, string2: , string3: , string4: } +2, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: world, string2: planet, string3: , string4: } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: e, string2: o, string3: l, string4: d } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } +1, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: testbbbb, string2: e, string3: f, string4: dddddd } 3 "https" "//" "redox-os.org" "" diff --git a/tests/expected/time/localtime.stderr b/tests/expected/bins_dynamic/stdio/setvbuf.stderr similarity index 100% rename from tests/expected/time/localtime.stderr rename to tests/expected/bins_dynamic/stdio/setvbuf.stderr diff --git a/tests/expected/stdio/setvbuf.stdout b/tests/expected/bins_dynamic/stdio/setvbuf.stdout similarity index 100% rename from tests/expected/stdio/setvbuf.stdout rename to tests/expected/bins_dynamic/stdio/setvbuf.stdout diff --git a/tests/expected/time/macros.stderr b/tests/expected/bins_dynamic/stdio/sprintf.stderr similarity index 100% rename from tests/expected/time/macros.stderr rename to tests/expected/bins_dynamic/stdio/sprintf.stderr diff --git a/tests/expected/stdio/sprintf.stdout b/tests/expected/bins_dynamic/stdio/sprintf.stdout similarity index 100% rename from tests/expected/stdio/sprintf.stdout rename to tests/expected/bins_dynamic/stdio/sprintf.stdout diff --git a/tests/expected/time/macros.stdout b/tests/expected/bins_dynamic/stdio/ungetc_ftell.stderr similarity index 100% rename from tests/expected/time/macros.stdout rename to tests/expected/bins_dynamic/stdio/ungetc_ftell.stderr diff --git a/tests/expected/stdio/ungetc_ftell.stdout b/tests/expected/bins_dynamic/stdio/ungetc_ftell.stdout similarity index 100% rename from tests/expected/stdio/ungetc_ftell.stdout rename to tests/expected/bins_dynamic/stdio/ungetc_ftell.stdout diff --git a/tests/expected/time/mktime.stderr b/tests/expected/bins_dynamic/stdio/ungetc_multiple.stderr similarity index 100% rename from tests/expected/time/mktime.stderr rename to tests/expected/bins_dynamic/stdio/ungetc_multiple.stderr diff --git a/tests/expected/stdio/ungetc_multiple.stdout b/tests/expected/bins_dynamic/stdio/ungetc_multiple.stdout similarity index 100% rename from tests/expected/stdio/ungetc_multiple.stdout rename to tests/expected/bins_dynamic/stdio/ungetc_multiple.stdout diff --git a/tests/expected/time/strftime.stderr b/tests/expected/bins_dynamic/stdlib/a64l.stderr similarity index 100% rename from tests/expected/time/strftime.stderr rename to tests/expected/bins_dynamic/stdlib/a64l.stderr diff --git a/tests/expected/stdlib/a64l.stdout b/tests/expected/bins_dynamic/stdlib/a64l.stdout similarity index 100% rename from tests/expected/stdlib/a64l.stdout rename to tests/expected/bins_dynamic/stdlib/a64l.stdout diff --git a/tests/expected/time/time.stderr b/tests/expected/bins_dynamic/stdlib/alloc.stderr similarity index 100% rename from tests/expected/time/time.stderr rename to tests/expected/bins_dynamic/stdlib/alloc.stderr diff --git a/tests/expected/stdlib/alloc.stdout b/tests/expected/bins_dynamic/stdlib/alloc.stdout similarity index 80% rename from tests/expected/stdlib/alloc.stdout rename to tests/expected/bins_dynamic/stdlib/alloc.stdout index e59a48d031..d5bc92dc00 100644 --- a/tests/expected/stdlib/alloc.stdout +++ b/tests/expected/bins_dynamic/stdlib/alloc.stdout @@ -7,6 +7,8 @@ calloc (overflowing): pointer: (nil), error value: 12 = Out of memory realloc (size 0): (OK) realloc: pointer: (not NULL), error value: 0 = Success realloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +reallocarray: pointer: (not NULL), error value: 0 = Success +reallocarray (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory memalign (size 0): (OK) memalign: pointer: (alignment OK), error value: 0 = Success memalign (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory @@ -22,3 +24,7 @@ posix_memalign (alignment 0): pointer: (nil), error value: 22 = Invalid argument posix_memalign (non-power-of-two multiple of sizeof(void *)): pointer: (nil), error value: 22 = Invalid argument posix_memalign (size 0): (OK) posix_memalign (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +pvalloc (size 0): (OK) +pvalloc: pointer: (alignment OK), error value: 0 = Success +pvalloc (2 pages): pointer: (alignment OK), error value: 0 = Success +pvalloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory diff --git a/tests/expected/time/time.stdout b/tests/expected/bins_dynamic/stdlib/atof.stderr similarity index 100% rename from tests/expected/time/time.stdout rename to tests/expected/bins_dynamic/stdlib/atof.stderr diff --git a/tests/expected/stdlib/atof.stdout b/tests/expected/bins_dynamic/stdlib/atof.stdout similarity index 71% rename from tests/expected/stdlib/atof.stdout rename to tests/expected/bins_dynamic/stdlib/atof.stdout index dd1835a6fb..3227d85385 100644 --- a/tests/expected/stdlib/atof.stdout +++ b/tests/expected/bins_dynamic/stdlib/atof.stdout @@ -1 +1,2 @@ -3.140000 +inf diff --git a/tests/expected/tls.stderr b/tests/expected/bins_dynamic/stdlib/atoi.stderr similarity index 100% rename from tests/expected/tls.stderr rename to tests/expected/bins_dynamic/stdlib/atoi.stderr diff --git a/tests/expected/stdlib/atoi.stdout b/tests/expected/bins_dynamic/stdlib/atoi.stdout similarity index 100% rename from tests/expected/stdlib/atoi.stdout rename to tests/expected/bins_dynamic/stdlib/atoi.stdout diff --git a/tests/expected/unistd/access.stderr b/tests/expected/bins_dynamic/stdlib/div.stderr similarity index 100% rename from tests/expected/unistd/access.stderr rename to tests/expected/bins_dynamic/stdlib/div.stderr diff --git a/tests/expected/unistd/access.stdout b/tests/expected/bins_dynamic/stdlib/div.stdout similarity index 100% rename from tests/expected/unistd/access.stdout rename to tests/expected/bins_dynamic/stdlib/div.stdout diff --git a/tests/expected/unistd/brk.stderr b/tests/expected/bins_dynamic/stdlib/env.stderr similarity index 100% rename from tests/expected/unistd/brk.stderr rename to tests/expected/bins_dynamic/stdlib/env.stderr diff --git a/tests/expected/stdlib/env.stdout b/tests/expected/bins_dynamic/stdlib/env.stdout similarity index 100% rename from tests/expected/stdlib/env.stdout rename to tests/expected/bins_dynamic/stdlib/env.stdout diff --git a/tests/expected/unistd/brk.stdout b/tests/expected/bins_dynamic/stdlib/getsubopt.stderr similarity index 100% rename from tests/expected/unistd/brk.stdout rename to tests/expected/bins_dynamic/stdlib/getsubopt.stderr diff --git a/tests/expected/unistd/dup.stderr b/tests/expected/bins_dynamic/stdlib/getsubopt.stdout similarity index 100% rename from tests/expected/unistd/dup.stderr rename to tests/expected/bins_dynamic/stdlib/getsubopt.stdout diff --git a/tests/expected/unistd/exec.stderr b/tests/expected/bins_dynamic/stdlib/mkostemps.stderr similarity index 100% rename from tests/expected/unistd/exec.stderr rename to tests/expected/bins_dynamic/stdlib/mkostemps.stderr diff --git a/tests/expected/stdlib/mkostemps.stdout b/tests/expected/bins_dynamic/stdlib/mkostemps.stdout similarity index 100% rename from tests/expected/stdlib/mkostemps.stdout rename to tests/expected/bins_dynamic/stdlib/mkostemps.stdout diff --git a/tests/expected/unistd/fchdir.stderr b/tests/expected/bins_dynamic/stdlib/ptsname.stderr similarity index 100% rename from tests/expected/unistd/fchdir.stderr rename to tests/expected/bins_dynamic/stdlib/ptsname.stderr diff --git a/tests/expected/unistd/fchdir.stdout b/tests/expected/bins_dynamic/stdlib/ptsname.stdout similarity index 100% rename from tests/expected/unistd/fchdir.stdout rename to tests/expected/bins_dynamic/stdlib/ptsname.stdout diff --git a/tests/expected/unistd/fork.stderr b/tests/expected/bins_dynamic/stdlib/qsort.stderr similarity index 100% rename from tests/expected/unistd/fork.stderr rename to tests/expected/bins_dynamic/stdlib/qsort.stderr diff --git a/tests/expected/bins_dynamic/stdlib/qsort.stdout b/tests/expected/bins_dynamic/stdlib/qsort.stdout new file mode 100644 index 0000000000..49755a611e --- /dev/null +++ b/tests/expected/bins_dynamic/stdlib/qsort.stdout @@ -0,0 +1,2 @@ +Before: 23 16 8 4 42 15 +After: 4 8 15 16 23 42 diff --git a/tests/expected/unistd/fsync.stderr b/tests/expected/bins_dynamic/stdlib/rand.stderr similarity index 100% rename from tests/expected/unistd/fsync.stderr rename to tests/expected/bins_dynamic/stdlib/rand.stderr diff --git a/tests/expected/stdlib/rand.stdout b/tests/expected/bins_dynamic/stdlib/rand.stdout similarity index 100% rename from tests/expected/stdlib/rand.stdout rename to tests/expected/bins_dynamic/stdlib/rand.stdout diff --git a/tests/expected/unistd/fsync.stdout b/tests/expected/bins_dynamic/stdlib/rand48.stderr similarity index 100% rename from tests/expected/unistd/fsync.stdout rename to tests/expected/bins_dynamic/stdlib/rand48.stderr diff --git a/tests/expected/stdlib/rand48.stdout b/tests/expected/bins_dynamic/stdlib/rand48.stdout similarity index 100% rename from tests/expected/stdlib/rand48.stdout rename to tests/expected/bins_dynamic/stdlib/rand48.stdout diff --git a/tests/expected/unistd/ftruncate.stderr b/tests/expected/bins_dynamic/stdlib/random.stderr similarity index 100% rename from tests/expected/unistd/ftruncate.stderr rename to tests/expected/bins_dynamic/stdlib/random.stderr diff --git a/tests/expected/stdlib/random.stdout b/tests/expected/bins_dynamic/stdlib/random.stdout similarity index 100% rename from tests/expected/stdlib/random.stdout rename to tests/expected/bins_dynamic/stdlib/random.stdout diff --git a/tests/expected/unistd/ftruncate.stdout b/tests/expected/bins_dynamic/stdlib/strtod.stderr similarity index 100% rename from tests/expected/unistd/ftruncate.stdout rename to tests/expected/bins_dynamic/stdlib/strtod.stderr diff --git a/tests/expected/bins_dynamic/stdlib/strtod.stdout b/tests/expected/bins_dynamic/stdlib/strtod.stdout new file mode 100644 index 0000000000..a92def04f3 --- /dev/null +++ b/tests/expected/bins_dynamic/stdlib/strtod.stdout @@ -0,0 +1,196 @@ +d: 0.000000 Endptr: "a 1 hello" +d: 1.000000 Endptr: " hello" +d: 1.000000 Endptr: " hello 2" +d: 10.123000 Endptr: "" +d: 10.123000 Endptr: "" +d: -5.300000 Endptr: "" +d: 16.071045 Endptr: "" +d: 1.136719 Endptr: "" +d: 3.128906 Endptr: "" +d: 100000.000000 Endptr: "" +d: 100000.000000 Endptr: "" +d: 0.000010 Endptr: "" +d: 100000.000000 Endptr: " " +d: 100000.000000 Endptr: " " +d: 0.000010 Endptr: " " +d: 10000000000.000000 Endptr: "" +d: 1.000000 Endptr: "eXXXX" +d: 1.000000 Endptr: "e" +d: 1.000000 Endptr: "e " +d: 10000000000.000000 Endptr: "" +d: 1.000000 Endptr: "e+XXXX" +d: 1.000000 Endptr: "e+" +d: 1.000000 Endptr: "e+ " +d: 0.000000 Endptr: "" +d: 1.000000 Endptr: "e-XXXX" +d: 1.000000 Endptr: "e-" +d: 1.000000 Endptr: "e- " +d: -100000.000000 Endptr: "" +d: -100000.000000 Endptr: "" +d: -0.000010 Endptr: "" +d: -100000.000000 Endptr: " " +d: -100000.000000 Endptr: " " +d: -0.000010 Endptr: " " +d: -10000000000.000000 Endptr: "" +d: -1.000000 Endptr: "eXXXX" +d: -1.000000 Endptr: "e" +d: -1.000000 Endptr: "e " +d: -10000000000.000000 Endptr: "" +d: -1.000000 Endptr: "e+XXXX" +d: -1.000000 Endptr: "e+" +d: -1.000000 Endptr: "e+ " +d: -0.000000 Endptr: "" +d: -1.000000 Endptr: "e-XXXX" +d: -1.000000 Endptr: "e-" +d: -1.000000 Endptr: "e- " +d: 1234000.000000 Endptr: "" +d: 1234000.000000 Endptr: "" +d: 0.000123 Endptr: "" +d: 1234000.000000 Endptr: " " +d: 1234000.000000 Endptr: " " +d: 0.000123 Endptr: " " +d: 123400000000.000000 Endptr: "" +d: 12.340000 Endptr: "eXXXX" +d: 12.340000 Endptr: "e" +d: 12.340000 Endptr: "e " +d: 123400000000.000000 Endptr: "" +d: 12.340000 Endptr: "e+XXXX" +d: 12.340000 Endptr: "e+" +d: 12.340000 Endptr: "e+ " +d: 0.000000 Endptr: "" +d: 12.340000 Endptr: "e-XXXX" +d: 12.340000 Endptr: "e-" +d: 12.340000 Endptr: "e- " +d: -1234000.000000 Endptr: "" +d: -1234000.000000 Endptr: "" +d: -0.000123 Endptr: "" +d: -1234000.000000 Endptr: " " +d: -1234000.000000 Endptr: " " +d: -0.000123 Endptr: " " +d: -123400000000.000000 Endptr: "" +d: -12.340000 Endptr: "eXXXX" +d: -12.340000 Endptr: "e" +d: -12.340000 Endptr: "e " +d: -123400000000.000000 Endptr: "" +d: -12.340000 Endptr: "e+XXXX" +d: -12.340000 Endptr: "e+" +d: -12.340000 Endptr: "e+ " +d: -0.000000 Endptr: "" +d: -12.340000 Endptr: "e-XXXX" +d: -12.340000 Endptr: "e-" +d: -12.340000 Endptr: "e- " +d: 192.000000 Endptr: "" +d: -192.000000 Endptr: "" +d: 0.005859 Endptr: "" +d: -0.005859 Endptr: "" +d: 10.000000 Endptr: "" +d: 0.156250 Endptr: "" +d: -10.000000 Endptr: "" +d: -0.156250 Endptr: "" +d: 16.062500 Endptr: "" +d: 16.062500 Endptr: "" +d: -16.062500 Endptr: "" +d: -16.062500 Endptr: "" +d: 0.500000 Endptr: "" +d: 5.000000 Endptr: "" +d: 50.000000 Endptr: "" +d: 500.000000 Endptr: "" +d: 5000.000000 Endptr: "" +d: 50000.000000 Endptr: "" +d: 500000.000000 Endptr: "" +d: 5000000.000000 Endptr: "" +d: 50000000.000000 Endptr: "" +d: 500000000.000000 Endptr: "" +d: 5000000000.000000 Endptr: "" +d: 50000000000.000000 Endptr: "" +d: 500000000000.000000 Endptr: "" +d: 5000000000000.000000 Endptr: "" +d: 50000000000000.000000 Endptr: "" +d: 500000000000000.000000 Endptr: "" +d: 5000000000000000.000000 Endptr: "" +d: 50000000000000000.000000 Endptr: "" +d: 500000000000000000.000000 Endptr: "" +d: 5000000000000000000.000000 Endptr: "" +d: 50000000000000000000.000000 Endptr: "" +d: 500000000000000000000.000000 Endptr: "" +d: 5000000000000000000000.000000 Endptr: "" +d: 49999999999999995805696.000000 Endptr: "" +d: 499999999999999991611392.000000 Endptr: "" +d: 5000000000000000452984832.000000 Endptr: "" +d: 50000000000000002382364672.000000 Endptr: "" +d: 500000000000000006643777536.000000 Endptr: "" +d: 4999999999999999791559868416.000000 Endptr: "" +d: 49999999999999995716575428608.000000 Endptr: "" +d: 500000000000000009942312419328.000000 Endptr: "" +d: 4999999999999999817948147482624.000000 Endptr: "" +d: 50000000000000002683081102196736.000000 Endptr: "" +d: 499999999999999972787615493521408.000000 Endptr: "" +d: 4999999999999999727876154935214080.000000 Endptr: "" +d: 49999999999999998431683053958987776.000000 Endptr: "" +d: 500000000000000021210318687008980992.000000 Endptr: "" +d: 4999999999999999769381329101060571136.000000 Endptr: "" +d: 49999999999999998874404911728017014784.000000 Endptr: "" +d: -0.500000 Endptr: "" +d: -5.000000 Endptr: "" +d: -50.000000 Endptr: "" +d: -500.000000 Endptr: "" +d: -5000.000000 Endptr: "" +d: -50000.000000 Endptr: "" +d: -500000.000000 Endptr: "" +d: -5000000.000000 Endptr: "" +d: -50000000.000000 Endptr: "" +d: -500000000.000000 Endptr: "" +d: -5000000000.000000 Endptr: "" +d: -50000000000.000000 Endptr: "" +d: -500000000000.000000 Endptr: "" +d: -5000000000000.000000 Endptr: "" +d: -50000000000000.000000 Endptr: "" +d: -500000000000000.000000 Endptr: "" +d: -5000000000000000.000000 Endptr: "" +d: -50000000000000000.000000 Endptr: "" +d: -500000000000000000.000000 Endptr: "" +d: -5000000000000000000.000000 Endptr: "" +d: -50000000000000000000.000000 Endptr: "" +d: -500000000000000000000.000000 Endptr: "" +d: -5000000000000000000000.000000 Endptr: "" +d: -49999999999999995805696.000000 Endptr: "" +d: -499999999999999991611392.000000 Endptr: "" +d: -5000000000000000452984832.000000 Endptr: "" +d: -50000000000000002382364672.000000 Endptr: "" +d: -500000000000000006643777536.000000 Endptr: "" +d: -4999999999999999791559868416.000000 Endptr: "" +d: -49999999999999995716575428608.000000 Endptr: "" +d: -500000000000000009942312419328.000000 Endptr: "" +d: -4999999999999999817948147482624.000000 Endptr: "" +d: -50000000000000002683081102196736.000000 Endptr: "" +d: -499999999999999972787615493521408.000000 Endptr: "" +d: -4999999999999999727876154935214080.000000 Endptr: "" +d: -49999999999999998431683053958987776.000000 Endptr: "" +d: -500000000000000021210318687008980992.000000 Endptr: "" +d: -4999999999999999769381329101060571136.000000 Endptr: "" +d: -49999999999999998874404911728017014784.000000 Endptr: "" +d: -0.000000 Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: " foobarbaz" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: " foobarbaz" +d: -inf Endptr: "" +d: -inf Endptr: "" +d: -inf Endptr: "" +d: -inf Endptr: " foobarbaz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" diff --git a/tests/expected/unistd/getopt.stderr b/tests/expected/bins_dynamic/stdlib/strtol.stderr similarity index 100% rename from tests/expected/unistd/getopt.stderr rename to tests/expected/bins_dynamic/stdlib/strtol.stderr diff --git a/tests/expected/bins_dynamic/stdlib/strtol.stdout b/tests/expected/bins_dynamic/stdlib/strtol.stdout new file mode 100644 index 0000000000..4c4177ba7b --- /dev/null +++ b/tests/expected/bins_dynamic/stdlib/strtol.stdout @@ -0,0 +1,26 @@ +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +38acf +endptr "g" +abcdef12 +endptr "" +cafebeef +endptr "" +731 +endptr "89" +731 +endptr "89" +0 +endptr "b" +0 +endptr "b" diff --git a/tests/expected/unistd/getopt_long.stderr b/tests/expected/bins_dynamic/stdlib/strtoul.stderr similarity index 100% rename from tests/expected/unistd/getopt_long.stderr rename to tests/expected/bins_dynamic/stdlib/strtoul.stderr diff --git a/tests/expected/bins_dynamic/stdlib/strtoul.stdout b/tests/expected/bins_dynamic/stdlib/strtoul.stdout new file mode 100644 index 0000000000..398260ab44 --- /dev/null +++ b/tests/expected/bins_dynamic/stdlib/strtoul.stdout @@ -0,0 +1,26 @@ +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +38acf +endptr "g" +abcdef12 +endptr "" +21000004 +endptr "" +731 +endptr "89" +731 +endptr "89" +0 +endptr "b" +0 +endptr "b" diff --git a/tests/expected/unistd/pipe.stderr b/tests/expected/bins_dynamic/stdlib/system.stderr similarity index 100% rename from tests/expected/unistd/pipe.stderr rename to tests/expected/bins_dynamic/stdlib/system.stderr diff --git a/tests/expected/stdlib/system.stdout b/tests/expected/bins_dynamic/stdlib/system.stdout similarity index 50% rename from tests/expected/stdlib/system.stdout rename to tests/expected/bins_dynamic/stdlib/system.stdout index a6d3f18a28..90f99da2e1 100644 --- a/tests/expected/stdlib/system.stdout +++ b/tests/expected/bins_dynamic/stdlib/system.stdout @@ -1 +1,2 @@ +shell found: 1 test of system diff --git a/tests/expected/unistd/rmdir.stderr b/tests/expected/bins_dynamic/string/mem.stderr similarity index 100% rename from tests/expected/unistd/rmdir.stderr rename to tests/expected/bins_dynamic/string/mem.stderr diff --git a/tests/expected/string/mem.stdout b/tests/expected/bins_dynamic/string/mem.stdout similarity index 100% rename from tests/expected/string/mem.stdout rename to tests/expected/bins_dynamic/string/mem.stdout diff --git a/tests/expected/unistd/rmdir.stdout b/tests/expected/bins_dynamic/string/memcpy.stderr similarity index 100% rename from tests/expected/unistd/rmdir.stdout rename to tests/expected/bins_dynamic/string/memcpy.stderr diff --git a/tests/expected/unistd/sleep.stderr b/tests/expected/bins_dynamic/string/memcpy.stdout similarity index 100% rename from tests/expected/unistd/sleep.stderr rename to tests/expected/bins_dynamic/string/memcpy.stdout diff --git a/tests/expected/unistd/swab.stderr b/tests/expected/bins_dynamic/string/memmem.stderr similarity index 100% rename from tests/expected/unistd/swab.stderr rename to tests/expected/bins_dynamic/string/memmem.stderr diff --git a/tests/expected/unistd/swab.stdout b/tests/expected/bins_dynamic/string/memmem.stdout similarity index 100% rename from tests/expected/unistd/swab.stdout rename to tests/expected/bins_dynamic/string/memmem.stdout diff --git a/tests/expected/unistd/write.stderr b/tests/expected/bins_dynamic/string/stpcpy.stderr similarity index 100% rename from tests/expected/unistd/write.stderr rename to tests/expected/bins_dynamic/string/stpcpy.stderr diff --git a/tests/expected/waitpid.stderr b/tests/expected/bins_dynamic/string/stpcpy.stdout similarity index 100% rename from tests/expected/waitpid.stderr rename to tests/expected/bins_dynamic/string/stpcpy.stdout diff --git a/tests/expected/waitpid.stdout b/tests/expected/bins_dynamic/string/stpncpy.stderr similarity index 100% rename from tests/expected/waitpid.stdout rename to tests/expected/bins_dynamic/string/stpncpy.stderr diff --git a/tests/expected/wchar/fwide.stderr b/tests/expected/bins_dynamic/string/stpncpy.stdout similarity index 100% rename from tests/expected/wchar/fwide.stderr rename to tests/expected/bins_dynamic/string/stpncpy.stdout diff --git a/tests/expected/wchar/mbrtowc.stderr b/tests/expected/bins_dynamic/string/strcat.stderr similarity index 100% rename from tests/expected/wchar/mbrtowc.stderr rename to tests/expected/bins_dynamic/string/strcat.stderr diff --git a/tests/expected/string/strcat.stdout b/tests/expected/bins_dynamic/string/strcat.stdout similarity index 100% rename from tests/expected/string/strcat.stdout rename to tests/expected/bins_dynamic/string/strcat.stdout diff --git a/tests/expected/wchar/mbsrtowcs.stderr b/tests/expected/bins_dynamic/string/strchr.stderr similarity index 100% rename from tests/expected/wchar/mbsrtowcs.stderr rename to tests/expected/bins_dynamic/string/strchr.stderr diff --git a/tests/expected/string/strchr.stdout b/tests/expected/bins_dynamic/string/strchr.stdout similarity index 53% rename from tests/expected/string/strchr.stdout rename to tests/expected/bins_dynamic/string/strchr.stdout index 02c9cb9551..43f3dcfdf0 100644 --- a/tests/expected/string/strchr.stdout +++ b/tests/expected/bins_dynamic/string/strchr.stdout @@ -1,3 +1,4 @@ ello ld -1 + +(nil) diff --git a/tests/expected/wchar/printf-on-wchars.stderr b/tests/expected/bins_dynamic/string/strchrnul.stderr similarity index 100% rename from tests/expected/wchar/printf-on-wchars.stderr rename to tests/expected/bins_dynamic/string/strchrnul.stderr diff --git a/tests/expected/wchar/putwchar.stderr b/tests/expected/bins_dynamic/string/strchrnul.stdout similarity index 100% rename from tests/expected/wchar/putwchar.stderr rename to tests/expected/bins_dynamic/string/strchrnul.stdout diff --git a/tests/expected/wchar/putwchar.stdout b/tests/expected/bins_dynamic/string/strcpy.stderr similarity index 100% rename from tests/expected/wchar/putwchar.stdout rename to tests/expected/bins_dynamic/string/strcpy.stderr diff --git a/tests/expected/bins_dynamic/string/strcpy.stdout b/tests/expected/bins_dynamic/string/strcpy.stdout new file mode 100644 index 0000000000..f08e831ac1 --- /dev/null +++ b/tests/expected/bins_dynamic/string/strcpy.stdout @@ -0,0 +1,7 @@ +strcpy works! +strncpy works! +strncpy shaaaaaaaaa +strlcpy works! +copied 14 +strlcpy works! and strlcat! +copied 27 diff --git a/tests/expected/wchar/wcrtomb.stderr b/tests/expected/bins_dynamic/string/strcspn.stderr similarity index 100% rename from tests/expected/wchar/wcrtomb.stderr rename to tests/expected/bins_dynamic/string/strcspn.stderr diff --git a/tests/expected/string/strcspn.stdout b/tests/expected/bins_dynamic/string/strcspn.stdout similarity index 100% rename from tests/expected/string/strcspn.stdout rename to tests/expected/bins_dynamic/string/strcspn.stdout diff --git a/tests/expected/wchar/wcscasecmp.stderr b/tests/expected/bins_dynamic/string/strlen.stderr similarity index 100% rename from tests/expected/wchar/wcscasecmp.stderr rename to tests/expected/bins_dynamic/string/strlen.stderr diff --git a/tests/expected/string/strlen.stdout b/tests/expected/bins_dynamic/string/strlen.stdout similarity index 100% rename from tests/expected/string/strlen.stdout rename to tests/expected/bins_dynamic/string/strlen.stdout diff --git a/tests/expected/wchar/wcscspn.stderr b/tests/expected/bins_dynamic/string/strncmp.stderr similarity index 100% rename from tests/expected/wchar/wcscspn.stderr rename to tests/expected/bins_dynamic/string/strncmp.stderr diff --git a/tests/expected/string/strncmp.stdout b/tests/expected/bins_dynamic/string/strncmp.stdout similarity index 100% rename from tests/expected/string/strncmp.stdout rename to tests/expected/bins_dynamic/string/strncmp.stdout diff --git a/tests/expected/wchar/wcscspn.stdout b/tests/expected/bins_dynamic/string/strpbrk.stderr similarity index 100% rename from tests/expected/wchar/wcscspn.stdout rename to tests/expected/bins_dynamic/string/strpbrk.stderr diff --git a/tests/expected/string/strpbrk.stdout b/tests/expected/bins_dynamic/string/strpbrk.stdout similarity index 100% rename from tests/expected/string/strpbrk.stdout rename to tests/expected/bins_dynamic/string/strpbrk.stdout diff --git a/tests/expected/wchar/wcsncasecmp.stderr b/tests/expected/bins_dynamic/string/strrchr.stderr similarity index 100% rename from tests/expected/wchar/wcsncasecmp.stderr rename to tests/expected/bins_dynamic/string/strrchr.stderr diff --git a/tests/expected/string/strrchr.stdout b/tests/expected/bins_dynamic/string/strrchr.stdout similarity index 100% rename from tests/expected/string/strrchr.stdout rename to tests/expected/bins_dynamic/string/strrchr.stdout diff --git a/tests/expected/wchar/wcsrchr.stderr b/tests/expected/bins_dynamic/string/strsep.stderr similarity index 100% rename from tests/expected/wchar/wcsrchr.stderr rename to tests/expected/bins_dynamic/string/strsep.stderr diff --git a/tests/expected/wchar/wcsrchr.stdout b/tests/expected/bins_dynamic/string/strsep.stdout similarity index 100% rename from tests/expected/wchar/wcsrchr.stdout rename to tests/expected/bins_dynamic/string/strsep.stdout diff --git a/tests/expected/wchar/wcsstr.stderr b/tests/expected/bins_dynamic/string/strsignal.stderr similarity index 100% rename from tests/expected/wchar/wcsstr.stderr rename to tests/expected/bins_dynamic/string/strsignal.stderr diff --git a/tests/expected/string/strsignal.stdout b/tests/expected/bins_dynamic/string/strsignal.stdout similarity index 100% rename from tests/expected/string/strsignal.stdout rename to tests/expected/bins_dynamic/string/strsignal.stdout diff --git a/tests/expected/wchar/wcsstr.stdout b/tests/expected/bins_dynamic/string/strspn.stderr similarity index 100% rename from tests/expected/wchar/wcsstr.stdout rename to tests/expected/bins_dynamic/string/strspn.stderr diff --git a/tests/expected/string/strspn.stdout b/tests/expected/bins_dynamic/string/strspn.stdout similarity index 100% rename from tests/expected/string/strspn.stdout rename to tests/expected/bins_dynamic/string/strspn.stdout diff --git a/tests/expected/wchar/wcstod.stderr b/tests/expected/bins_dynamic/string/strstr.stderr similarity index 100% rename from tests/expected/wchar/wcstod.stderr rename to tests/expected/bins_dynamic/string/strstr.stderr diff --git a/tests/expected/string/strstr.stdout b/tests/expected/bins_dynamic/string/strstr.stdout similarity index 100% rename from tests/expected/string/strstr.stdout rename to tests/expected/bins_dynamic/string/strstr.stdout diff --git a/tests/expected/wchar/wcstok.stderr b/tests/expected/bins_dynamic/string/strtok.stderr similarity index 100% rename from tests/expected/wchar/wcstok.stderr rename to tests/expected/bins_dynamic/string/strtok.stderr diff --git a/tests/expected/string/strtok.stdout b/tests/expected/bins_dynamic/string/strtok.stdout similarity index 100% rename from tests/expected/string/strtok.stdout rename to tests/expected/bins_dynamic/string/strtok.stdout diff --git a/tests/expected/wchar/wcstol.stderr b/tests/expected/bins_dynamic/string/strtok_r.stderr similarity index 100% rename from tests/expected/wchar/wcstol.stderr rename to tests/expected/bins_dynamic/string/strtok_r.stderr diff --git a/tests/expected/string/strtok_r.stdout b/tests/expected/bins_dynamic/string/strtok_r.stdout similarity index 100% rename from tests/expected/string/strtok_r.stdout rename to tests/expected/bins_dynamic/string/strtok_r.stdout diff --git a/tests/expected/wctype/towlower.stderr b/tests/expected/bins_dynamic/strings.stderr similarity index 100% rename from tests/expected/wctype/towlower.stderr rename to tests/expected/bins_dynamic/strings.stderr diff --git a/tests/expected/wctype/towupper.stderr b/tests/expected/bins_dynamic/strings.stdout similarity index 100% rename from tests/expected/wctype/towupper.stderr rename to tests/expected/bins_dynamic/strings.stdout diff --git a/tests/expected/bins_dynamic/sys_epoll/epollet.stdout b/tests/expected/bins_dynamic/sys_epoll/epollet.stdout new file mode 100644 index 0000000000..cc912d90cf --- /dev/null +++ b/tests/expected/bins_dynamic/sys_epoll/epollet.stdout @@ -0,0 +1,5 @@ +ctl ADD: 0 +wait for HUP, edge-triggered: 1 +wait for HUP again: 0 +ctl MOD: 0 +wait for HUP: 1 diff --git a/tests/expected/bins_dynamic/sys_mman.stderr b/tests/expected/bins_dynamic/sys_mman.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/sys_mman.stdout b/tests/expected/bins_dynamic/sys_mman.stdout similarity index 100% rename from tests/expected/sys_mman.stdout rename to tests/expected/bins_dynamic/sys_mman.stdout diff --git a/tests/expected/bins_dynamic/sys_socket/recv.stdout b/tests/expected/bins_dynamic/sys_socket/recv.stdout new file mode 100644 index 0000000000..cb2239126a --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/recv.stdout @@ -0,0 +1,2 @@ +send foo +recv foo diff --git a/tests/expected/bins_dynamic/sys_socket/recvfrom.stdout b/tests/expected/bins_dynamic/sys_socket/recvfrom.stdout new file mode 100644 index 0000000000..e9f413f935 --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/recvfrom.stdout @@ -0,0 +1,2 @@ +sendto bar +recvfrom bar diff --git a/tests/expected/bins_dynamic/sys_socket/unixpeername.stdout b/tests/expected/bins_dynamic/sys_socket/unixpeername.stdout new file mode 100644 index 0000000000..f9686e7295 --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/unixpeername.stdout @@ -0,0 +1,3 @@ +listen sockname [AF_UNIX]: unixpeername.sock +client peername [AF_UNIX]: unixpeername.sock +client sockname [AF_UNIX]: (unnamed) diff --git a/tests/expected/bins_dynamic/sys_socket/unixrecv.stdout b/tests/expected/bins_dynamic/sys_socket/unixrecv.stdout new file mode 100644 index 0000000000..e9636a2916 --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/unixrecv.stdout @@ -0,0 +1,2 @@ +send ipsum +recv ipsum diff --git a/tests/expected/bins_dynamic/sys_socket/unixrecvfrom.stdout b/tests/expected/bins_dynamic/sys_socket/unixrecvfrom.stdout new file mode 100644 index 0000000000..ad8e531910 --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/unixrecvfrom.stdout @@ -0,0 +1,2 @@ +send lorem +recvfrom lorem diff --git a/tests/expected/bins_dynamic/sys_socket/unixsocketpair.stdout b/tests/expected/bins_dynamic/sys_socket/unixsocketpair.stdout new file mode 100644 index 0000000000..bb7fc428fa --- /dev/null +++ b/tests/expected/bins_dynamic/sys_socket/unixsocketpair.stdout @@ -0,0 +1,6 @@ +SOCK_STREAM +child: ping +parent: pong +SOCK_DGRAM +child: ping +parent: pong diff --git a/tests/expected/bins_dynamic/sys_stat/chmod.stderr b/tests/expected/bins_dynamic/sys_stat/chmod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_stat/chmod.stdout b/tests/expected/bins_dynamic/sys_stat/chmod.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_stat/fstatat.stderr b/tests/expected/bins_dynamic/sys_stat/fstatat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_stat/fstatat.stdout b/tests/expected/bins_dynamic/sys_stat/fstatat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_stat/lstat.stderr b/tests/expected/bins_dynamic/sys_stat/lstat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_stat/lstat.stdout b/tests/expected/bins_dynamic/sys_stat/lstat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/sys_syslog/syslog.stderr b/tests/expected/bins_dynamic/sys_syslog/syslog.stderr new file mode 100644 index 0000000000..faddade2a6 --- /dev/null +++ b/tests/expected/bins_dynamic/sys_syslog/syslog.stderr @@ -0,0 +1,13 @@ +relibc_test: This is a test message with formatting: 5 +relibc_test: Hank Hill +relibc_test: Foo has been bar'd +relibc_test: And now, Eeveelutions +relibc_test: Espeon +relibc_test: Umbreon +relibc_test: Sylveon +relibc_test: Glaceon +relibc_test: Leafeon +relibc_test: Vaporeon +relibc_test: Flareon +relibc_test: Jolteon +relibc_test: Bye from relibc's syslog tests! diff --git a/tests/expected/bins_dynamic/sys_syslog/syslog.stdout b/tests/expected/bins_dynamic/sys_syslog/syslog.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/termios.stderr b/tests/expected/bins_dynamic/termios.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/termios.stdout b/tests/expected/bins_dynamic/termios.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/asctime.stderr b/tests/expected/bins_dynamic/time/asctime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/time/asctime.stdout b/tests/expected/bins_dynamic/time/asctime.stdout similarity index 100% rename from tests/expected/time/asctime.stdout rename to tests/expected/bins_dynamic/time/asctime.stdout diff --git a/tests/expected/bins_dynamic/time/constants.stderr b/tests/expected/bins_dynamic/time/constants.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/constants.stdout b/tests/expected/bins_dynamic/time/constants.stdout new file mode 100644 index 0000000000..cf14858d8c --- /dev/null +++ b/tests/expected/bins_dynamic/time/constants.stdout @@ -0,0 +1,2 @@ +(nil) +1000000 diff --git a/tests/expected/bins_dynamic/time/gmtime.stderr b/tests/expected/bins_dynamic/time/gmtime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/time/gmtime.stdout b/tests/expected/bins_dynamic/time/gmtime.stdout similarity index 100% rename from tests/expected/time/gmtime.stdout rename to tests/expected/bins_dynamic/time/gmtime.stdout diff --git a/tests/expected/bins_dynamic/time/localtime.stderr b/tests/expected/bins_dynamic/time/localtime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/time/localtime.stdout b/tests/expected/bins_dynamic/time/localtime.stdout similarity index 100% rename from tests/expected/time/localtime.stdout rename to tests/expected/bins_dynamic/time/localtime.stdout diff --git a/tests/expected/bins_dynamic/time/localtime_r.stderr b/tests/expected/bins_dynamic/time/localtime_r.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/localtime_r.stdout b/tests/expected/bins_dynamic/time/localtime_r.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/macros.stderr b/tests/expected/bins_dynamic/time/macros.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/macros.stdout b/tests/expected/bins_dynamic/time/macros.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/mktime.stderr b/tests/expected/bins_dynamic/time/mktime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/time/mktime.stdout b/tests/expected/bins_dynamic/time/mktime.stdout similarity index 100% rename from tests/expected/time/mktime.stdout rename to tests/expected/bins_dynamic/time/mktime.stdout diff --git a/tests/expected/bins_dynamic/time/strftime.stderr b/tests/expected/bins_dynamic/time/strftime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/time/strftime.stdout b/tests/expected/bins_dynamic/time/strftime.stdout similarity index 88% rename from tests/expected/time/strftime.stdout rename to tests/expected/bins_dynamic/time/strftime.stdout index 1317c413d0..7d12e13954 100644 --- a/tests/expected/time/strftime.stdout +++ b/tests/expected/bins_dynamic/time/strftime.stdout @@ -4,6 +4,11 @@ 11: 03:00:00 PM 5: 15:00 15: 15 1531839600 2 -6: 197 28 +6: 198 28 28: Tue Jul 17 15:00:00 UTC 2018 0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U +53 +52 +52 +1 +53 diff --git a/tests/expected/bins_dynamic/time/strptime.stderr b/tests/expected/bins_dynamic/time/strptime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/strptime.stdout b/tests/expected/bins_dynamic/time/strptime.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/time.stderr b/tests/expected/bins_dynamic/time/time.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/time.stdout b/tests/expected/bins_dynamic/time/time.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/timegm.stderr b/tests/expected/bins_dynamic/time/timegm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/timegm.stdout b/tests/expected/bins_dynamic/time/timegm.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/tzset.stderr b/tests/expected/bins_dynamic/time/tzset.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/tzset.stdout b/tests/expected/bins_dynamic/time/tzset.stdout new file mode 100644 index 0000000000..9962d8acc0 --- /dev/null +++ b/tests/expected/bins_dynamic/time/tzset.stdout @@ -0,0 +1 @@ +tzname[0] UTC, tzname[1] UTC, daylight 0, timezone 0 diff --git a/tests/expected/bins_dynamic/tls.stderr b/tests/expected/bins_dynamic/tls.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/tls.stdout b/tests/expected/bins_dynamic/tls.stdout similarity index 100% rename from tests/expected/tls.stdout rename to tests/expected/bins_dynamic/tls.stdout diff --git a/tests/expected/bins_dynamic/unistd/access.stderr b/tests/expected/bins_dynamic/unistd/access.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/access.stdout b/tests/expected/bins_dynamic/unistd/access.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/alarm.stderr b/tests/expected/bins_dynamic/unistd/alarm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/alarm.stdout b/tests/expected/bins_dynamic/unistd/alarm.stdout new file mode 100644 index 0000000000..4ed7f83631 --- /dev/null +++ b/tests/expected/bins_dynamic/unistd/alarm.stdout @@ -0,0 +1,4 @@ +alarm(0) baseline: ok +alarm(1) fires: ok +alarm(0) cancel: ok +alarm re-arm returns remaining: ok diff --git a/tests/expected/bins_dynamic/unistd/brk.stderr b/tests/expected/bins_dynamic/unistd/brk.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/brk.stdout b/tests/expected/bins_dynamic/unistd/brk.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/confstr.stderr b/tests/expected/bins_dynamic/unistd/confstr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/confstr.stdout b/tests/expected/bins_dynamic/unistd/confstr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/constants.stderr b/tests/expected/bins_dynamic/unistd/constants.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/constants.stdout b/tests/expected/bins_dynamic/unistd/constants.stdout new file mode 100644 index 0000000000..90c907f1ff --- /dev/null +++ b/tests/expected/bins_dynamic/unistd/constants.stdout @@ -0,0 +1,16 @@ +_POSIX_VERSION: 200809 +NULL: (nil) +R_OK: 4 +W_OK: 2 +X_OK: 1 +F_OK: 0 +SEEK_SET: 0 +SEEK_CUR: 1 +SEEK_END: 2 +F_LOCK: 1 +F_ULOCK: 0 +F_TEST: 3 +F_TLOCK: 2 +STDIN_FILENO: 0 +STDOUT_FILENO: 1 +STDERR_FILENO: 2 diff --git a/tests/expected/bins_dynamic/unistd/dup.stderr b/tests/expected/bins_dynamic/unistd/dup.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/dup.stdout b/tests/expected/bins_dynamic/unistd/dup.stdout similarity index 100% rename from tests/expected/unistd/dup.stdout rename to tests/expected/bins_dynamic/unistd/dup.stdout diff --git a/tests/expected/bins_dynamic/unistd/exec.stderr b/tests/expected/bins_dynamic/unistd/exec.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/exec.stdout b/tests/expected/bins_dynamic/unistd/exec.stdout similarity index 100% rename from tests/expected/unistd/exec.stdout rename to tests/expected/bins_dynamic/unistd/exec.stdout diff --git a/tests/expected/bins_dynamic/unistd/fchdir.stderr b/tests/expected/bins_dynamic/unistd/fchdir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/fchdir.stdout b/tests/expected/bins_dynamic/unistd/fchdir.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/fork.stderr b/tests/expected/bins_dynamic/unistd/fork.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/fork.stdout b/tests/expected/bins_dynamic/unistd/fork.stdout similarity index 100% rename from tests/expected/unistd/fork.stdout rename to tests/expected/bins_dynamic/unistd/fork.stdout diff --git a/tests/expected/bins_dynamic/unistd/fsync.stderr b/tests/expected/bins_dynamic/unistd/fsync.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/fsync.stdout b/tests/expected/bins_dynamic/unistd/fsync.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/ftruncate.stderr b/tests/expected/bins_dynamic/unistd/ftruncate.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/ftruncate.stdout b/tests/expected/bins_dynamic/unistd/ftruncate.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/getopt.stderr b/tests/expected/bins_dynamic/unistd/getopt.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/getopt.stdout b/tests/expected/bins_dynamic/unistd/getopt.stdout similarity index 100% rename from tests/expected/unistd/getopt.stdout rename to tests/expected/bins_dynamic/unistd/getopt.stdout diff --git a/tests/expected/bins_dynamic/unistd/getopt_long.stderr b/tests/expected/bins_dynamic/unistd/getopt_long.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/getopt_long.stdout b/tests/expected/bins_dynamic/unistd/getopt_long.stdout similarity index 100% rename from tests/expected/unistd/getopt_long.stdout rename to tests/expected/bins_dynamic/unistd/getopt_long.stdout diff --git a/tests/expected/bins_dynamic/unistd/pipe.stderr b/tests/expected/bins_dynamic/unistd/pipe.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/pipe.stdout b/tests/expected/bins_dynamic/unistd/pipe.stdout similarity index 100% rename from tests/expected/unistd/pipe.stdout rename to tests/expected/bins_dynamic/unistd/pipe.stdout diff --git a/tests/expected/bins_dynamic/unistd/readlinkat.stderr b/tests/expected/bins_dynamic/unistd/readlinkat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/readlinkat.stdout b/tests/expected/bins_dynamic/unistd/readlinkat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/rmdir.stderr b/tests/expected/bins_dynamic/unistd/rmdir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/rmdir.stdout b/tests/expected/bins_dynamic/unistd/rmdir.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/sleep.stderr b/tests/expected/bins_dynamic/unistd/sleep.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/sleep.stdout b/tests/expected/bins_dynamic/unistd/sleep.stdout similarity index 100% rename from tests/expected/unistd/sleep.stdout rename to tests/expected/bins_dynamic/unistd/sleep.stdout diff --git a/tests/expected/bins_dynamic/unistd/swab.stderr b/tests/expected/bins_dynamic/unistd/swab.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/swab.stdout b/tests/expected/bins_dynamic/unistd/swab.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/unistd/write.stderr b/tests/expected/bins_dynamic/unistd/write.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/unistd/write.stdout b/tests/expected/bins_dynamic/unistd/write.stdout similarity index 100% rename from tests/expected/unistd/write.stdout rename to tests/expected/bins_dynamic/unistd/write.stdout diff --git a/tests/expected/bins_dynamic/wchar/fgetwc.stderr b/tests/expected/bins_dynamic/wchar/fgetwc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/fgetwc.stdout b/tests/expected/bins_dynamic/wchar/fgetwc.stdout new file mode 100644 index 0000000000..e7cabf537e --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/fgetwc.stdout @@ -0,0 +1,3 @@ +êçã +こんにちは世界 +Привет мир \ No newline at end of file diff --git a/tests/expected/bins_dynamic/wchar/fwide.stderr b/tests/expected/bins_dynamic/wchar/fwide.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/fwide.stdout b/tests/expected/bins_dynamic/wchar/fwide.stdout similarity index 100% rename from tests/expected/wchar/fwide.stdout rename to tests/expected/bins_dynamic/wchar/fwide.stdout diff --git a/tests/expected/bins_dynamic/wchar/mbrtowc.stderr b/tests/expected/bins_dynamic/wchar/mbrtowc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/mbrtowc.stdout b/tests/expected/bins_dynamic/wchar/mbrtowc.stdout similarity index 100% rename from tests/expected/wchar/mbrtowc.stdout rename to tests/expected/bins_dynamic/wchar/mbrtowc.stdout diff --git a/tests/expected/bins_dynamic/wchar/mbsrtowcs.stderr b/tests/expected/bins_dynamic/wchar/mbsrtowcs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/mbsrtowcs.stdout b/tests/expected/bins_dynamic/wchar/mbsrtowcs.stdout similarity index 100% rename from tests/expected/wchar/mbsrtowcs.stdout rename to tests/expected/bins_dynamic/wchar/mbsrtowcs.stdout diff --git a/tests/expected/bins_dynamic/wchar/printf-on-wchars.stderr b/tests/expected/bins_dynamic/wchar/printf-on-wchars.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/printf-on-wchars.stdout b/tests/expected/bins_dynamic/wchar/printf-on-wchars.stdout similarity index 100% rename from tests/expected/wchar/printf-on-wchars.stdout rename to tests/expected/bins_dynamic/wchar/printf-on-wchars.stdout diff --git a/tests/expected/bins_dynamic/wchar/putwchar.stderr b/tests/expected/bins_dynamic/wchar/putwchar.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/putwchar.stdout b/tests/expected/bins_dynamic/wchar/putwchar.stdout new file mode 100644 index 0000000000..42d65d1da0 --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/putwchar.stdout @@ -0,0 +1 @@ +zß水🍌 \ No newline at end of file diff --git a/tests/expected/bins_dynamic/wchar/ungetwc.stderr b/tests/expected/bins_dynamic/wchar/ungetwc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/ungetwc.stdout b/tests/expected/bins_dynamic/wchar/ungetwc.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcpcpy.stderr b/tests/expected/bins_dynamic/wchar/wcpcpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcpcpy.stdout b/tests/expected/bins_dynamic/wchar/wcpcpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcpncpy.stderr b/tests/expected/bins_dynamic/wchar/wcpncpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcpncpy.stdout b/tests/expected/bins_dynamic/wchar/wcpncpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcrtomb.stderr b/tests/expected/bins_dynamic/wchar/wcrtomb.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/wcrtomb.stdout b/tests/expected/bins_dynamic/wchar/wcrtomb.stdout similarity index 100% rename from tests/expected/wchar/wcrtomb.stdout rename to tests/expected/bins_dynamic/wchar/wcrtomb.stdout diff --git a/tests/expected/bins_dynamic/wchar/wcscasecmp.stderr b/tests/expected/bins_dynamic/wchar/wcscasecmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/wcscasecmp.stdout b/tests/expected/bins_dynamic/wchar/wcscasecmp.stdout similarity index 100% rename from tests/expected/wchar/wcscasecmp.stdout rename to tests/expected/bins_dynamic/wchar/wcscasecmp.stdout diff --git a/tests/expected/bins_dynamic/wchar/wcschr.stderr b/tests/expected/bins_dynamic/wchar/wcschr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcschr.stdout b/tests/expected/bins_dynamic/wchar/wcschr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcscspn.stderr b/tests/expected/bins_dynamic/wchar/wcscspn.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcscspn.stdout b/tests/expected/bins_dynamic/wchar/wcscspn.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsdup.stderr b/tests/expected/bins_dynamic/wchar/wcsdup.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsdup.stdout b/tests/expected/bins_dynamic/wchar/wcsdup.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsncasecmp.stderr b/tests/expected/bins_dynamic/wchar/wcsncasecmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/wcsncasecmp.stdout b/tests/expected/bins_dynamic/wchar/wcsncasecmp.stdout similarity index 100% rename from tests/expected/wchar/wcsncasecmp.stdout rename to tests/expected/bins_dynamic/wchar/wcsncasecmp.stdout diff --git a/tests/expected/bins_dynamic/wchar/wcsnlen.stderr b/tests/expected/bins_dynamic/wchar/wcsnlen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsnlen.stdout b/tests/expected/bins_dynamic/wchar/wcsnlen.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsnrtombs.stderr b/tests/expected/bins_dynamic/wchar/wcsnrtombs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsnrtombs.stdout b/tests/expected/bins_dynamic/wchar/wcsnrtombs.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsrchr.stderr b/tests/expected/bins_dynamic/wchar/wcsrchr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsrchr.stdout b/tests/expected/bins_dynamic/wchar/wcsrchr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsrtombs.stderr b/tests/expected/bins_dynamic/wchar/wcsrtombs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsrtombs.stdout b/tests/expected/bins_dynamic/wchar/wcsrtombs.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsstr.stderr b/tests/expected/bins_dynamic/wchar/wcsstr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcsstr.stdout b/tests/expected/bins_dynamic/wchar/wcsstr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcstod.stderr b/tests/expected/bins_dynamic/wchar/wcstod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/wcstod.stdout b/tests/expected/bins_dynamic/wchar/wcstod.stdout similarity index 100% rename from tests/expected/wchar/wcstod.stdout rename to tests/expected/bins_dynamic/wchar/wcstod.stdout diff --git a/tests/expected/bins_dynamic/wchar/wcstoimax.stderr b/tests/expected/bins_dynamic/wchar/wcstoimax.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcstoimax.stdout b/tests/expected/bins_dynamic/wchar/wcstoimax.stdout new file mode 100644 index 0000000000..b9a14be845 --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/wcstoimax.stdout @@ -0,0 +1,6 @@ +-123 +255 +44027 +8 +10 +16 diff --git a/tests/expected/bins_dynamic/wchar/wcstok.stderr b/tests/expected/bins_dynamic/wchar/wcstok.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wchar/wcstok.stdout b/tests/expected/bins_dynamic/wchar/wcstok.stdout similarity index 100% rename from tests/expected/wchar/wcstok.stdout rename to tests/expected/bins_dynamic/wchar/wcstok.stdout diff --git a/tests/expected/bins_dynamic/wchar/wcstol.stderr b/tests/expected/bins_dynamic/wchar/wcstol.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcstol.stdout b/tests/expected/bins_dynamic/wchar/wcstol.stdout new file mode 100644 index 0000000000..55aa50779a --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/wcstol.stdout @@ -0,0 +1 @@ +The decimal equivalents are: 2001, 6340800, -3624224 and 7340031. diff --git a/tests/expected/bins_dynamic/wchar/wcstoumax.stderr b/tests/expected/bins_dynamic/wchar/wcstoumax.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcstoumax.stdout b/tests/expected/bins_dynamic/wchar/wcstoumax.stdout new file mode 100644 index 0000000000..8e372f7589 --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/wcstoumax.stdout @@ -0,0 +1,3 @@ +nptr = `10110134932` +wcstoumax = 10110134932 +Stopped scan at `` diff --git a/tests/expected/bins_dynamic/wchar/wcswidth.stderr b/tests/expected/bins_dynamic/wchar/wcswidth.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wcswidth.stdout b/tests/expected/bins_dynamic/wchar/wcswidth.stdout new file mode 100644 index 0000000000..7da79364d8 --- /dev/null +++ b/tests/expected/bins_dynamic/wchar/wcswidth.stdout @@ -0,0 +1 @@ +wcswidth(L"relibc", 6) = 6 diff --git a/tests/expected/bins_dynamic/wchar/wprintf.stderr b/tests/expected/bins_dynamic/wchar/wprintf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/stdio/printf.stdout b/tests/expected/bins_dynamic/wchar/wprintf.stdout similarity index 94% rename from tests/expected/stdio/printf.stdout rename to tests/expected/bins_dynamic/wchar/wprintf.stdout index 75c6a49414..724e64b5b5 100644 --- a/tests/expected/stdio/printf.stdout +++ b/tests/expected/bins_dynamic/wchar/wprintf.stdout @@ -1,13 +1,12 @@ percent: % -string: String +wstring: String char: c -char: int: -16 uint: 32 hex: beef HEX: C0FFEE string: end -%n returned 51, total len of write: 94 +%n returned 44, total len of write: 87 Padding madness: 001 +2 diff --git a/tests/expected/bins_dynamic/wchar/wscanf.stderr b/tests/expected/bins_dynamic/wchar/wscanf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/wchar/wscanf.stdout b/tests/expected/bins_dynamic/wchar/wscanf.stdout new file mode 100644 index 0000000000..90abc9a7f6 Binary files /dev/null and b/tests/expected/bins_dynamic/wchar/wscanf.stdout differ diff --git a/tests/expected/bins_dynamic/wctype/towlower.stderr b/tests/expected/bins_dynamic/wctype/towlower.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wctype/towlower.stdout b/tests/expected/bins_dynamic/wctype/towlower.stdout similarity index 100% rename from tests/expected/wctype/towlower.stdout rename to tests/expected/bins_dynamic/wctype/towlower.stdout diff --git a/tests/expected/bins_dynamic/wctype/towupper.stderr b/tests/expected/bins_dynamic/wctype/towupper.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/wctype/towupper.stdout b/tests/expected/bins_dynamic/wctype/towupper.stdout similarity index 100% rename from tests/expected/wctype/towupper.stdout rename to tests/expected/bins_dynamic/wctype/towupper.stdout diff --git a/tests/expected/bins_static/alloca.stderr b/tests/expected/bins_static/alloca.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/alloca.stdout b/tests/expected/bins_static/alloca.stdout new file mode 100644 index 0000000000..d0a7d827f2 --- /dev/null +++ b/tests/expected/bins_static/alloca.stdout @@ -0,0 +1 @@ +AAAAAAAAAAAAAAAA diff --git a/tests/expected/bins_static/args.stdout b/tests/expected/bins_static/args.stdout index 3026e24bea..deefb2f3da 100644 --- a/tests/expected/bins_static/args.stdout +++ b/tests/expected/bins_static/args.stdout @@ -1 +1 @@ -bins_static/args test args +test args diff --git a/tests/expected/bins_static/arpainet.stderr b/tests/expected/bins_static/arpainet.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/arpainet.stdout b/tests/expected/bins_static/arpainet.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/assert.stderr b/tests/expected/bins_static/assert.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/assert.stdout b/tests/expected/bins_static/assert.stdout new file mode 100644 index 0000000000..028678d987 --- /dev/null +++ b/tests/expected/bins_static/assert.stdout @@ -0,0 +1,2 @@ +yay! +groovy! diff --git a/tests/expected/bins_static/constructor.stderr b/tests/expected/bins_static/constructor.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/constructor.stdout b/tests/expected/bins_static/constructor.stdout new file mode 100644 index 0000000000..14e23d4cbb --- /dev/null +++ b/tests/expected/bins_static/constructor.stdout @@ -0,0 +1,6 @@ +constructor (101) +constructor (102) +constructor (103) +constructor (104) +constructor (no priority) +main diff --git a/tests/expected/bins_static/crypt/blowfish.stderr b/tests/expected/bins_static/crypt/blowfish.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/blowfish.stdout b/tests/expected/bins_static/crypt/blowfish.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/md5.stderr b/tests/expected/bins_static/crypt/md5.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/md5.stdout b/tests/expected/bins_static/crypt/md5.stdout new file mode 100644 index 0000000000..f985b46aff --- /dev/null +++ b/tests/expected/bins_static/crypt/md5.stdout @@ -0,0 +1 @@ +Success! diff --git a/tests/expected/bins_static/crypt/pbkdf2.stderr b/tests/expected/bins_static/crypt/pbkdf2.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/pbkdf2.stdout b/tests/expected/bins_static/crypt/pbkdf2.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/scrypt.stderr b/tests/expected/bins_static/crypt/scrypt.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/scrypt.stdout b/tests/expected/bins_static/crypt/scrypt.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/sha256.stderr b/tests/expected/bins_static/crypt/sha256.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/sha256.stdout b/tests/expected/bins_static/crypt/sha256.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/sha512.stderr b/tests/expected/bins_static/crypt/sha512.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/crypt/sha512.stdout b/tests/expected/bins_static/crypt/sha512.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/ctype.stderr b/tests/expected/bins_static/ctype.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/ctype.stdout b/tests/expected/bins_static/ctype.stdout new file mode 100644 index 0000000000..e83b1e5f11 --- /dev/null +++ b/tests/expected/bins_static/ctype.stdout @@ -0,0 +1 @@ +Success: 0 diff --git a/tests/expected/bins_static/destructor.stderr b/tests/expected/bins_static/destructor.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/destructor.stdout b/tests/expected/bins_static/destructor.stdout new file mode 100644 index 0000000000..1da7c29706 --- /dev/null +++ b/tests/expected/bins_static/destructor.stdout @@ -0,0 +1,6 @@ +main +destructor (no priority) +destructor (104) +destructor (103) +destructor (102) +destructor (101) diff --git a/tests/expected/bins_static/dirent/fdopendir.stderr b/tests/expected/bins_static/dirent/fdopendir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/dirent/fdopendir.stdout b/tests/expected/bins_static/dirent/fdopendir.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/dirent/scandir.stderr b/tests/expected/bins_static/dirent/scandir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/stdio/popen.stdout b/tests/expected/bins_static/dirent/scandir.stdout similarity index 86% rename from tests/expected/stdio/popen.stdout rename to tests/expected/bins_static/dirent/scandir.stdout index 988b6f4a43..4820bea846 100644 --- a/tests/expected/stdio/popen.stdout +++ b/tests/expected/bins_static/dirent/scandir.stdout @@ -1,6 +1,5 @@ 1-never-gonna-give-you-up 2-never-gonna-let-you-down -3-never-gonna-run-around 4-and-desert-you 5-never-gonna-make-you-cry 6-never-gonna-say-goodbye diff --git a/tests/expected/bins_static/err.stderr b/tests/expected/bins_static/err.stderr new file mode 100644 index 0000000000..b79d9c196f --- /dev/null +++ b/tests/expected/bins_static/err.stderr @@ -0,0 +1,6 @@ +err: Ran out of coffee: Owner died +err: Cat pulled out your ethernet cable +err: Eat 42 cookies: Permission denied +err: Potato, krumpli: Invalid exchange +err: I'm sorry, Dave. I'm afraid I can't do that.: Operation not permitted +err: Bye. It's crowded.: Too many users diff --git a/tests/expected/bins_static/err.stdout b/tests/expected/bins_static/err.stdout new file mode 100644 index 0000000000..8ef72cb5bc --- /dev/null +++ b/tests/expected/bins_static/err.stdout @@ -0,0 +1,2 @@ +err: Dang it, Bobby.: No such file or directory +Exiting due to error code: 87 diff --git a/tests/expected/bins_static/errno.stdout b/tests/expected/bins_static/errno.stdout index a8236cdb85..58419805ea 100644 --- a/tests/expected/bins_static/errno.stdout +++ b/tests/expected/bins_static/errno.stdout @@ -1,6 +1,136 @@ -bins_static/errno -bins_static/errno +E2BIG macro available +EACCES macro available +EADDRINUSE macro available +EADDRNOTAVAIL macro available +EADV macro available +EAFNOSUPPORT macro available +EAGAIN macro available +EALREADY macro available +EBADE macro available +EBADF macro available +EBADFD macro available +EBADMSG macro available +EBADR macro available +EBADRQC macro available +EBADSLT macro available +EBFONT macro available +EBUSY macro available +ECANCELED macro available +ECHILD macro available +ECHRNG macro available +ECOMM macro available +ECONNABORTED macro available +ECONNREFUSED macro available +ECONNRESET macro available +EDEADLK macro available +EDEADLOCK macro available +EDESTADDRREQ macro available +EDOM macro available +EDOTDOT macro available +EDQUOT macro available +EEXIST macro available +EFAULT macro available +EFBIG macro available +EHOSTDOWN macro available +EHOSTUNREACH macro available +EIDRM macro available +EILSEQ macro available +EINPROGRESS macro available +EINTR macro available +EINVAL macro available +EIO macro available +EISCONN macro available +EISDIR macro available +EISNAM macro available +EKEYEXPIRED macro available +EKEYREJECTED macro available +EKEYREVOKED macro available +EL2HLT macro available +EL2NSYNC macro available +EL3HLT macro available +EL3RST macro available +ELIBACC macro available +ELIBBAD macro available +ELIBEXEC macro available +ELIBMAX macro available +ELIBSCN macro available +ELNRNG macro available +ELOOP macro available +EMEDIUMTYPE macro available +EMFILE macro available +EMLINK macro available +EMSGSIZE macro available +EMULTIHOP macro available +ENAMETOOLONG macro available +ENAVAIL macro available +ENETDOWN macro available +ENETRESET macro available +ENETUNREACH macro available +ENFILE macro available +ENOANO macro available +ENOBUFS macro available +ENOCSI macro available +ENODATA macro available +ENODEV macro available +ENOENT macro available +ENOEXEC macro available +ENOKEY macro available +ENOLCK macro available +ENOLINK macro available +ENOMEDIUM macro available +ENOMEM macro available +ENOMSG macro available +ENONET macro available +ENOPKG macro available +ENOPROTOOPT macro available +ENOSPC macro available +ENOSR macro available +ENOSTR macro available +ENOSYS macro available +ENOTBLK macro available +ENOTCONN macro available +ENOTDIR macro available +ENOTEMPTY macro available +ENOTNAM macro available +ENOTRECOVERABLE macro available +ENOTSOCK macro available +ENOTSUP macro available +ENOTTY macro available +ENOTUNIQ macro available +ENXIO macro available +EOPNOTSUPP macro available +EOVERFLOW macro available +EOWNERDEAD macro available +EPERM macro available +EPFNOSUPPORT macro available +EPIPE macro available +EPROTO macro available +EPROTONOSUPPORT macro available +EPROTOTYPE macro available +ERANGE macro available +EREMCHG macro available +EREMOTE macro available +EREMOTEIO macro available +ERESTART macro available +EROFS macro available +ESHUTDOWN macro available +ESOCKTNOSUPPORT macro available +ESPIPE macro available +ESRCH macro available +ESRMNT macro available +ESTALE macro available +ESTRPIPE macro available +ETIME macro available +ETIMEDOUT macro available +ETOOMANYREFS macro available +ETXTBSY macro available +EUCLEAN macro available +EUNATCH macro available +EUSERS macro available +EWOULDBLOCK macro available +EXDEV macro available +EXFULL macro available errno -yes, you can change this -yes, you can change this -yes, you can change this +changed to argv[0] +changed to program_invocation_name +changed to program_invocation_short_name diff --git a/tests/expected/bins_static/error.stderr b/tests/expected/bins_static/error.stderr new file mode 100644 index 0000000000..4eb19d82f3 --- /dev/null +++ b/tests/expected/bins_static/error.stderr @@ -0,0 +1 @@ +perror: No such file or directory diff --git a/tests/expected/bins_static/error.stdout b/tests/expected/bins_static/error.stdout new file mode 100644 index 0000000000..4e744d1bfb --- /dev/null +++ b/tests/expected/bins_static/error.stdout @@ -0,0 +1,4 @@ +errno: 2 = No such file or directory +errno: 2 = No such file or directory, return: 0 +errno: 2 = No, return: 34 +errno: 2 = , return: 34 diff --git a/tests/expected/bins_static/fcntl/create.stderr b/tests/expected/bins_static/fcntl/create.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/create.stdout b/tests/expected/bins_static/fcntl/create.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/fcntl.stderr b/tests/expected/bins_static/fcntl/fcntl.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/fcntl.stdout b/tests/expected/bins_static/fcntl/fcntl.stdout new file mode 100644 index 0000000000..4fb8e07602 --- /dev/null +++ b/tests/expected/bins_static/fcntl/fcntl.stdout @@ -0,0 +1 @@ +duped fd is 1 greater than the original fd diff --git a/tests/expected/bins_static/fcntl/open.stderr b/tests/expected/bins_static/fcntl/open.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/open.stdout b/tests/expected/bins_static/fcntl/open.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/posix_fallocate.stderr b/tests/expected/bins_static/fcntl/posix_fallocate.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fcntl/posix_fallocate.stdout b/tests/expected/bins_static/fcntl/posix_fallocate.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fnmatch.stderr b/tests/expected/bins_static/fnmatch.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/fnmatch.stdout b/tests/expected/bins_static/fnmatch.stdout new file mode 100644 index 0000000000..48ba8aef2e --- /dev/null +++ b/tests/expected/bins_static/fnmatch.stdout @@ -0,0 +1,32 @@ +Should succeed: +"*World" matches "Hello World" +"*World" matches "World" +"Hello*" matches "Hello World" +"H[ae]llo?World" matches "Hallo+World" +"H[ae]llo?World" matches "Hello_World" +"[0-9][!a]" matches "1b" +"/a/*/d" matches "/a/b/c/d" +"/a/*/d" matches "/a/bc/d" +"*hello" matches ".hello" +"/*hello" matches "/.hello" +"[a!][a!]" matches "!a" +"[\]]" matches "]" +"[\\]" matches "\" +"hello[/+]world" matches "hello/world" +"hello world" matches "HELLO WORLD" + +Should fail: +"*World" doesn't match "Hello Potato" +"*World" doesn't match "Potato" +"H[ae]llo?World" doesn't match "Hillo+World" +"H[ae]llo?World" doesn't match "Hello__World" +"[0-9][!a]" doesn't match "ab" +"[0-9][!a]" doesn't match "2a" +"/a/*/d" doesn't match "/a/b/c/d" +"/a/*/d" doesn't match "/a/bc/d/" +"*hello" doesn't match ".hello" +"/*hello" doesn't match "/.hello" +"[a!][a!]" doesn't match "ab" +"hello[/+]world" doesn't match "hello/world" +"hello world" doesn't match "HELLO WORLD" +"********************a" doesn't match "xxxxxxxxxxxxxxxxxxxxb" diff --git a/tests/expected/bins_static/futimens.stderr b/tests/expected/bins_static/futimens.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/futimens.stdout b/tests/expected/bins_static/futimens.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/iso646.stderr b/tests/expected/bins_static/iso646.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/iso646.stdout b/tests/expected/bins_static/iso646.stdout new file mode 100644 index 0000000000..5f8a399fe8 --- /dev/null +++ b/tests/expected/bins_static/iso646.stdout @@ -0,0 +1,33 @@ +0 and 0: 0 +1 and 0: 0 +0 and 1: 0 +1 and 1: 1 +0 and_eq 0: 0 +1 and_eq 0: 0 +0 and_eq 1: 0 +1 and_eq 1: 1 +a bitand b: 8 +a bitor b: 14 +compl a: 245 +not 0: 1 +not 1: 0 +0 not_eq 0: 0 +1 not_eq 0: 1 +0 not_eq 1: 0 +1 not_eq 1: 1 +0 or 0: 0 +1 or 0: 1 +0 or 1: 1 +1 or 1: 1 +0 or_eq 0: 0 +1 or_eq 0: 1 +0 or_eq 1: 1 +1 or_eq 1: 1 +0 xor 0: 0 +1 xor 0: 1 +0 xor 1: 1 +1 xor 1: 0 +0 xor_eq 0: 0 +1 xor_eq 0: 1 +0 xor_eq 1: 1 +1 xor_eq 1: 0 diff --git a/tests/expected/bins_static/libgen.stderr b/tests/expected/bins_static/libgen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/libgen.stdout b/tests/expected/bins_static/libgen.stdout new file mode 100644 index 0000000000..6e32d2008d --- /dev/null +++ b/tests/expected/bins_static/libgen.stdout @@ -0,0 +1,21 @@ +dirname("") == "." +basename("") == "." +dirname(".") == "." +basename(".") == "." +dirname("..") == "." +basename("..") == ".." +dirname("/") == "/" +basename("/") == "/" +dirname("///") == "/" +basename("///") == "/" +dirname("//usr//lib//") == "//usr" +basename("//usr//lib//") == "lib" +dirname("/usr") == "/" +basename("/usr") == "usr" +dirname("/usr/") == "/" +basename("/usr/") == "usr" +dirname("/usr/lib") == "/usr" +basename("/usr/lib") == "lib" +dirname(NULL) == "." +basename(NULL) == "." +Success: 0 diff --git a/tests/expected/bins_static/locale/duplocale.stderr b/tests/expected/bins_static/locale/duplocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/duplocale.stdout b/tests/expected/bins_static/locale/duplocale.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/newlocale.stderr b/tests/expected/bins_static/locale/newlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/newlocale.stdout b/tests/expected/bins_static/locale/newlocale.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/setlocale.stderr b/tests/expected/bins_static/locale/setlocale.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/locale/setlocale.stdout b/tests/expected/bins_static/locale/setlocale.stdout new file mode 100644 index 0000000000..5d516f2e6d --- /dev/null +++ b/tests/expected/bins_static/locale/setlocale.stdout @@ -0,0 +1 @@ +success! diff --git a/tests/expected/bins_static/malloc/usable_size.stderr b/tests/expected/bins_static/malloc/usable_size.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/malloc/usable_size.stdout b/tests/expected/bins_static/malloc/usable_size.stdout new file mode 100644 index 0000000000..4188b67b66 --- /dev/null +++ b/tests/expected/bins_static/malloc/usable_size.stdout @@ -0,0 +1,2 @@ +malloc: 27 bytes +malloc_usable_size: 48 bytes diff --git a/tests/expected/bins_static/math.stderr b/tests/expected/bins_static/math.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/math.stdout b/tests/expected/bins_static/math.stdout new file mode 100644 index 0000000000..8654177967 --- /dev/null +++ b/tests/expected/bins_static/math.stdout @@ -0,0 +1 @@ +cos(3.140000) = -0.999999 diff --git a/tests/expected/bins_static/mkfifo.stderr b/tests/expected/bins_static/mkfifo.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/mkfifo.stdout b/tests/expected/bins_static/mkfifo.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/mknod.stderr b/tests/expected/bins_static/mknod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/mknod.stdout b/tests/expected/bins_static/mknod.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/mknodat.stderr b/tests/expected/bins_static/mknodat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/mknodat.stdout b/tests/expected/bins_static/mknodat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/ptrace.stderr b/tests/expected/bins_static/ptrace.stderr new file mode 100644 index 0000000000..289c82a876 --- /dev/null +++ b/tests/expected/bins_static/ptrace.stderr @@ -0,0 +1,4 @@ +This is printed to STDOUT. +Or, at least, that's what I thought. +But all write(...) syscalls are actually redirected to STDERR by the tracer. +Big surprise, right! diff --git a/tests/expected/bins_static/ptrace.stdout b/tests/expected/bins_static/ptrace.stdout new file mode 100644 index 0000000000..824e89accd --- /dev/null +++ b/tests/expected/bins_static/ptrace.stdout @@ -0,0 +1,5 @@ +Set regs +Set regs +Set regs +Set regs +Child exited with status 0 diff --git a/tests/expected/bins_static/pty/forkpty.stderr b/tests/expected/bins_static/pty/forkpty.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/pty/forkpty.stdout b/tests/expected/bins_static/pty/forkpty.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/regex.stderr b/tests/expected/bins_static/regex.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/regex.stdout b/tests/expected/bins_static/regex.stdout new file mode 100644 index 0000000000..07caf6fc01 --- /dev/null +++ b/tests/expected/bins_static/regex.stdout @@ -0,0 +1,3 @@ +Matching group: 25 - 36 +Matching group: 31 - 36 +Matching group: -1 - -1 diff --git a/tests/expected/bins_static/select.stderr b/tests/expected/bins_static/select.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/select.stdout b/tests/expected/bins_static/select.stdout new file mode 100644 index 0000000000..a4a5b784a2 --- /dev/null +++ b/tests/expected/bins_static/select.stdout @@ -0,0 +1,8 @@ +Testing select on file +Is set before? 1 +Amount of things ready: 1 +Is set after? 1 +Testing select on pipe +Is set before? 1 +Amount of things ready: 1 +Is set after? 1 diff --git a/tests/expected/bins_static/setjmp.stderr b/tests/expected/bins_static/setjmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/setjmp.stdout b/tests/expected/bins_static/setjmp.stdout new file mode 100644 index 0000000000..c7e6bcddd8 --- /dev/null +++ b/tests/expected/bins_static/setjmp.stdout @@ -0,0 +1,2 @@ +jumping... +hi from jump diff --git a/tests/expected/bins_static/sigaction.stderr b/tests/expected/bins_static/sigaction.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sigaction.stdout b/tests/expected/bins_static/sigaction.stdout new file mode 100644 index 0000000000..8a6d2448ef --- /dev/null +++ b/tests/expected/bins_static/sigaction.stdout @@ -0,0 +1,5 @@ +Raising... +Signal handler1 called! +Raising... +Signal handler2 called! +Raised. diff --git a/tests/expected/bins_static/sigaltstack.stderr b/tests/expected/bins_static/sigaltstack.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sigaltstack.stdout b/tests/expected/bins_static/sigaltstack.stdout new file mode 100644 index 0000000000..be1dfac342 --- /dev/null +++ b/tests/expected/bins_static/sigaltstack.stdout @@ -0,0 +1,101 @@ +SIGUSR2 handler00 +SIGUSR2 handler01 +SIGUSR2 handler02 +SIGUSR2 handler03 +SIGUSR2 handler04 +SIGUSR2 handler05 +SIGUSR2 handler06 +SIGUSR2 handler07 +SIGUSR2 handler08 +SIGUSR2 handler09 +SIGUSR2 handler10 +SIGUSR2 handler11 +SIGUSR2 handler12 +SIGUSR2 handler13 +SIGUSR2 handler14 +SIGUSR2 handler15 +SIGUSR2 handler16 +SIGUSR2 handler17 +SIGUSR2 handler18 +SIGUSR2 handler19 +SIGUSR2 handler20 +SIGUSR2 handler21 +SIGUSR2 handler22 +SIGUSR2 handler23 +SIGUSR2 handler24 +SIGUSR2 handler25 +SIGUSR2 handler26 +SIGUSR2 handler27 +SIGUSR2 handler28 +SIGUSR2 handler29 +SIGUSR2 handler30 +SIGUSR2 handler31 +SIGUSR2 handler32 +SIGUSR2 handler33 +SIGUSR2 handler34 +SIGUSR2 handler35 +SIGUSR2 handler36 +SIGUSR2 handler37 +SIGUSR2 handler38 +SIGUSR2 handler39 +SIGUSR2 handler40 +SIGUSR2 handler41 +SIGUSR2 handler42 +SIGUSR2 handler43 +SIGUSR2 handler44 +SIGUSR2 handler45 +SIGUSR2 handler46 +SIGUSR2 handler47 +SIGUSR2 handler48 +SIGUSR2 handler49 +SIGUSR2 handler50 +SIGUSR2 handler51 +SIGUSR2 handler52 +SIGUSR2 handler53 +SIGUSR2 handler54 +SIGUSR2 handler55 +SIGUSR2 handler56 +SIGUSR2 handler57 +SIGUSR2 handler58 +SIGUSR2 handler59 +SIGUSR2 handler60 +SIGUSR2 handler61 +SIGUSR2 handler62 +SIGUSR2 handler63 +SIGUSR2 handler64 +SIGUSR2 handler65 +SIGUSR2 handler66 +SIGUSR2 handler67 +SIGUSR2 handler68 +SIGUSR2 handler69 +SIGUSR2 handler70 +SIGUSR2 handler71 +SIGUSR2 handler72 +SIGUSR2 handler73 +SIGUSR2 handler74 +SIGUSR2 handler75 +SIGUSR2 handler76 +SIGUSR2 handler77 +SIGUSR2 handler78 +SIGUSR2 handler79 +SIGUSR2 handler80 +SIGUSR2 handler81 +SIGUSR2 handler82 +SIGUSR2 handler83 +SIGUSR2 handler84 +SIGUSR2 handler85 +SIGUSR2 handler86 +SIGUSR2 handler87 +SIGUSR2 handler88 +SIGUSR2 handler89 +SIGUSR2 handler90 +SIGUSR2 handler91 +SIGUSR2 handler92 +SIGUSR2 handler93 +SIGUSR2 handler94 +SIGUSR2 handler95 +SIGUSR2 handler96 +SIGUSR2 handler97 +SIGUSR2 handler98 +SIGUSR2 handler99 +SIGUSR2 handler00 diff --git a/tests/expected/bins_static/signal.stderr b/tests/expected/bins_static/signal.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/signal.stdout b/tests/expected/bins_static/signal.stdout new file mode 100644 index 0000000000..7d184e34c8 --- /dev/null +++ b/tests/expected/bins_static/signal.stdout @@ -0,0 +1,3 @@ +Raising... +Signal handler called! +Raised. diff --git a/tests/expected/bins_static/sigsetjmp.stderr b/tests/expected/bins_static/sigsetjmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sigsetjmp.stdout b/tests/expected/bins_static/sigsetjmp.stdout new file mode 100644 index 0000000000..eb99a60c8e --- /dev/null +++ b/tests/expected/bins_static/sigsetjmp.stdout @@ -0,0 +1,2 @@ +Starting jump... +Jump done. diff --git a/tests/expected/bins_static/stdio/all.stderr b/tests/expected/bins_static/stdio/all.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/all.stdout b/tests/expected/bins_static/stdio/all.stdout new file mode 100644 index 0000000000..92881132c2 --- /dev/null +++ b/tests/expected/bins_static/stdio/all.stdout @@ -0,0 +1,4 @@ +H +Jello World! + +Hello diff --git a/tests/expected/bins_static/stdio/buffer.stderr b/tests/expected/bins_static/stdio/buffer.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/buffer.stdout b/tests/expected/bins_static/stdio/buffer.stdout new file mode 100644 index 0000000000..4b124bc7c0 --- /dev/null +++ b/tests/expected/bins_static/stdio/buffer.stdout @@ -0,0 +1,5 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaTest +Hello +World +It works +No buffering issues here diff --git a/tests/expected/bins_static/stdio/dprintf.stderr b/tests/expected/bins_static/stdio/dprintf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/dprintf.stdout b/tests/expected/bins_static/stdio/dprintf.stdout new file mode 100644 index 0000000000..e12bc872a0 --- /dev/null +++ b/tests/expected/bins_static/stdio/dprintf.stdout @@ -0,0 +1,2 @@ +Hello, world +a diff --git a/tests/expected/bins_static/stdio/fgets.stderr b/tests/expected/bins_static/stdio/fgets.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fgets.stdout b/tests/expected/bins_static/stdio/fgets.stdout new file mode 100644 index 0000000000..a856e2de52 --- /dev/null +++ b/tests/expected/bins_static/stdio/fgets.stdout @@ -0,0 +1,31 @@ +Hello World! + +Line 2 + +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc +ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg +hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii +jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj +kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk +lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll +mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm +nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn +ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp +qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq +rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr +sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss +ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt +uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu +vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +EOF diff --git a/tests/expected/bins_static/stdio/fputs.stderr b/tests/expected/bins_static/stdio/fputs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fputs.stdout b/tests/expected/bins_static/stdio/fputs.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fread.stderr b/tests/expected/bins_static/stdio/fread.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fread.stdout b/tests/expected/bins_static/stdio/fread.stdout new file mode 100644 index 0000000000..f3fbf213be --- /dev/null +++ b/tests/expected/bins_static/stdio/fread.stdout @@ -0,0 +1,32 @@ +1 +22 +333 +4444 +55555 +666666 +7777777 +88888888 +999999999 +AAAAAAAAAA +BBBBBBBBBBB +CCCCCCCCCCCC +DDDDDDDDDDDDD +EEEEEEEEEEEEEE +FFFFFFFFFFFFFFF +0000000000000000 +11111111111111111 +222222222222222222 +3333333333333333333 +44444444444444444444 +555555555555555555555 +6666666666666666666666 +77777777777777777777777 +888888888888888888888888 +9999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00000000000000000000000000000000 diff --git a/tests/expected/bins_static/stdio/freopen.stderr b/tests/expected/bins_static/stdio/freopen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/freopen.stdout b/tests/expected/bins_static/stdio/freopen.stdout new file mode 100644 index 0000000000..1dd338fd1a --- /dev/null +++ b/tests/expected/bins_static/stdio/freopen.stdout @@ -0,0 +1,3 @@ +Hello +0 +0 diff --git a/tests/expected/bins_static/stdio/fscanf.stderr b/tests/expected/bins_static/stdio/fscanf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fscanf.stdout b/tests/expected/bins_static/stdio/fscanf.stdout new file mode 100644 index 0000000000..73f11e68cf --- /dev/null +++ b/tests/expected/bins_static/stdio/fscanf.stdout @@ -0,0 +1,14 @@ +1 2 3 9 +9 +16 +35 +48 +92 +109 +152 +201 +241 +276 +293 +299 +301 diff --git a/tests/expected/bins_static/stdio/fscanf_offby1.stderr b/tests/expected/bins_static/stdio/fscanf_offby1.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fscanf_offby1.stdout b/tests/expected/bins_static/stdio/fscanf_offby1.stdout new file mode 100644 index 0000000000..daf724a9e5 --- /dev/null +++ b/tests/expected/bins_static/stdio/fscanf_offby1.stdout @@ -0,0 +1 @@ +1234, 7, 32 diff --git a/tests/expected/bins_static/stdio/fseek.stderr b/tests/expected/bins_static/stdio/fseek.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fseek.stdout b/tests/expected/bins_static/stdio/fseek.stdout new file mode 100644 index 0000000000..b6163b2663 --- /dev/null +++ b/tests/expected/bins_static/stdio/fseek.stdout @@ -0,0 +1,2 @@ +Line 2 +ftello: 21 diff --git a/tests/expected/bins_static/stdio/fwrite.stderr b/tests/expected/bins_static/stdio/fwrite.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/fwrite.stdout b/tests/expected/bins_static/stdio/fwrite.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/getc_unget.stderr b/tests/expected/bins_static/stdio/getc_unget.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/getc_unget.stdout b/tests/expected/bins_static/stdio/getc_unget.stdout new file mode 100644 index 0000000000..f01a3f968d --- /dev/null +++ b/tests/expected/bins_static/stdio/getc_unget.stdout @@ -0,0 +1 @@ +Worked! diff --git a/tests/expected/bins_static/stdio/getline.stderr b/tests/expected/bins_static/stdio/getline.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/getline.stdout b/tests/expected/bins_static/stdio/getline.stdout new file mode 100644 index 0000000000..ba753ef6b2 --- /dev/null +++ b/tests/expected/bins_static/stdio/getline.stdout @@ -0,0 +1,41 @@ + 1: getline(NULL, NULL, stream) + => 22 (Invalid argument) - EINVAL + 2: getline(NULL, &n, stream) + => 22 (Invalid argument) - EINVAL + 3: getline(&lineptr, NULL, stream) + => 22 (Invalid argument) - EINVAL + 4: getline(NULL, NULL, NULL) + => 22 (Invalid argument) - EINVAL + 5: getline(&lineptr, NULL, NULL) + => 22 (Invalid argument) - EINVAL + 6: getline(NULL, &n, NULL) + => 22 (Invalid argument) - EINVAL + 7: getline(&lineptr, &n, NULL) + => 22 (Invalid argument) - EINVAL + 8: getdelim(&lineptr, &n, 25600, stream) + => 22 (Invalid argument) - EINVAL + + 1: status = 27, strlen = 27, feof = 0, ferror = 0 + |>Space: the final frontier. + 2: status = 1, strlen = 1, feof = 0, ferror = 0 + |> + 3: status = 50, strlen = 50, feof = 0, ferror = 0 + |>These are the voyages of the starship Enterprise. + 4: status = 24, strlen = 24, feof = 0, ferror = 0 + |>Its continuing mission: + 5: status = 33, strlen = 33, feof = 0, ferror = 0 + |>- to explore strange new worlds; + 6: status = 46, strlen = 46, feof = 0, ferror = 0 + |>- to seek out new life and new civilizations; + 7: status = 45, strlen = 45, feof = 0, ferror = 0 + |>- to boldly go where no one has gone before! + +overread 1, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + +overread 2, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + +overread 3, status = -1, feof = 1, ferror = 0 +|~- to boldly go where no one has gone before! + diff --git a/tests/expected/bins_static/stdio/mutex.stderr b/tests/expected/bins_static/stdio/mutex.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/mutex.stdout b/tests/expected/bins_static/stdio/mutex.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/popen.stderr b/tests/expected/bins_static/stdio/popen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/popen.stdout b/tests/expected/bins_static/stdio/popen.stdout new file mode 100644 index 0000000000..05f7216cbe --- /dev/null +++ b/tests/expected/bins_static/stdio/popen.stdout @@ -0,0 +1,8 @@ +Line 1: 1-never-gonna-give-you-up +Line 2: 2-never-gonna-let-you-down +Line 3: 3-never-gonna-run-around +Line 4: 4-and-desert-you +Line 5: 5-never-gonna-make-you-cry +Line 6: 6-never-gonna-say-goodbye +Line 7: 7-never-gonna-tell-a-lie +Line 8: 8-and-hurt-you diff --git a/tests/expected/bins_static/stdio/printf.stderr b/tests/expected/bins_static/stdio/printf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/printf.stdout b/tests/expected/bins_static/stdio/printf.stdout new file mode 100644 index 0000000000..a13543aeb2 --- /dev/null +++ b/tests/expected/bins_static/stdio/printf.stdout @@ -0,0 +1,78 @@ +percent: % +string: String +char: c +char: +int: -16 +uint: 32 +hex: beef +HEX: C0FFEE +string: end +%n returned 51, total len of write: 94 + +Padding madness: + 001 +2 + c + 0x00000ff + 001 +0 0x1 + 1 +(00123) ( 123) +(-0123) ( -123) +( ) +0xabcdef +(nil) + +Positional madness: +4 3 2 +00002 +|Fiz |Buz | Fiz| Tot| +int: 5 double: 0.100000 0.200000 0.300000 0.400000 +-1717986918 0.100000 +-1717986918 0.200000 + +Float madness: + 1.234568e+02 + 1.000000E-05 + 123.456789 + 0.000010 + -1.234568e+02 +-00000001.234568e+02 +%.5g: -123.46 +%.5f: -123.45679 +%.5e: -1.23457e+02 +%.*g: -1.2e+02 +%.*f: -123.46 +%.*e: -1.23e+02 +%.*2$g: -123.46 +%.*2$f: -123.45679 +%.*2$e: -1.23457e+02 +100000 +1e+06 +1.000000e+06 +0.0001 +1E-05 +1.000000E-05 +001234.56 +001234.56 + +Non-finite float madness: +%e: inf -inf nan -nan +%E: INF -INF NAN -NAN +%f: inf -inf nan -nan +%F: INF -INF NAN -NAN +%g: inf -inf nan -nan +%G: INF -INF NAN -NAN +Things that have been buggy ++05 +Testing asprintf... +printed: test string, value: 11 +printed: test string 2, value: 13 +printed: test string 2, value: 13 + +%m: Owner died + +C23: +Binary %b: 100 +Binary %b alternate: 0b100 +Binary %B: 100 +Binary %B alternate: 0B100 diff --git a/tests/expected/bins_static/stdio/printf_neg_pad.stderr b/tests/expected/bins_static/stdio/printf_neg_pad.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/printf_neg_pad.stdout b/tests/expected/bins_static/stdio/printf_neg_pad.stdout new file mode 100644 index 0000000000..f573a2b6d2 --- /dev/null +++ b/tests/expected/bins_static/stdio/printf_neg_pad.stdout @@ -0,0 +1,2 @@ +A BCC/ +AB CC/ diff --git a/tests/expected/bins_static/stdio/printf_space_pad.stderr b/tests/expected/bins_static/stdio/printf_space_pad.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/printf_space_pad.stdout b/tests/expected/bins_static/stdio/printf_space_pad.stdout new file mode 100644 index 0000000000..8e9107f351 --- /dev/null +++ b/tests/expected/bins_static/stdio/printf_space_pad.stdout @@ -0,0 +1 @@ +A B diff --git a/tests/expected/bins_static/stdio/putc_unlocked.stderr b/tests/expected/bins_static/stdio/putc_unlocked.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/putc_unlocked.stdout b/tests/expected/bins_static/stdio/putc_unlocked.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/rename.stderr b/tests/expected/bins_static/stdio/rename.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/rename.stdout b/tests/expected/bins_static/stdio/rename.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/renameat.stderr b/tests/expected/bins_static/stdio/renameat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/renameat.stdout b/tests/expected/bins_static/stdio/renameat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/scanf.stderr b/tests/expected/bins_static/stdio/scanf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/scanf.stdout b/tests/expected/bins_static/stdio/scanf.stdout new file mode 100644 index 0000000000..dca295ccb5 --- /dev/null +++ b/tests/expected/bins_static/stdio/scanf.stdout @@ -0,0 +1,16 @@ +2, { sa: 12, ia: 345, ib: 0, ic: 0, fa: 0.000000, da: 0.000000, ptr: (nil), char: a, string1: , string2: , string3: , string4: } +3, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.000000, da: 0.000000, ptr: (nil), char: a, string1: , string2: , string3: , string4: } +2, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: (nil), char: a, string1: , string2: , string3: , string4: } +1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string1: , string2: , string3: , string4: } +1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string1: Hello, string2: , string3: , string4: } +1, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string1: Hello, string2: , string3: , string4: } +2, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } +1, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } +0, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: elllo, string2: , string3: , string4: } +2, { sa: 12, ia: 42, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: world, string2: , string3: , string4: } +2, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: world, string2: planet, string3: , string4: } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: e, string2: o, string3: l, string4: d } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } +4, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: a, string2: e, string3: f, string4: dddddd } +1, { sa: 12, ia: 84, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string1: testbbbb, string2: e, string3: f, string4: dddddd } +3 "https" "//" "redox-os.org" "" diff --git a/tests/expected/bins_static/stdio/setvbuf.stderr b/tests/expected/bins_static/stdio/setvbuf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/setvbuf.stdout b/tests/expected/bins_static/stdio/setvbuf.stdout new file mode 100644 index 0000000000..ebda98d01a --- /dev/null +++ b/tests/expected/bins_static/stdio/setvbuf.stdout @@ -0,0 +1,4 @@ +H +Hello World! + +Hello diff --git a/tests/expected/bins_static/stdio/sprintf.stderr b/tests/expected/bins_static/stdio/sprintf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/sprintf.stdout b/tests/expected/bins_static/stdio/sprintf.stdout new file mode 100644 index 0000000000..4c76b1a825 --- /dev/null +++ b/tests/expected/bins_static/stdio/sprintf.stdout @@ -0,0 +1 @@ +This stri diff --git a/tests/expected/bins_static/stdio/ungetc_ftell.stderr b/tests/expected/bins_static/stdio/ungetc_ftell.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/ungetc_ftell.stdout b/tests/expected/bins_static/stdio/ungetc_ftell.stdout new file mode 100644 index 0000000000..e36f413519 --- /dev/null +++ b/tests/expected/bins_static/stdio/ungetc_ftell.stdout @@ -0,0 +1,28 @@ +#, 0 +i, 1 +n, 2 +h, -9 +e, -8 +l, -7 +l, -6 +o, -5 + , -4 +w, -3 +o, -2 +r, -1 +l, 0 +d, 1 + +, 2 +c, 3 +l, 4 +u, 5 +d, 6 +e, 7 + , 8 +<, 9 +s, 10 +t, 11 +d, 12 +i, 13 +o, 14 diff --git a/tests/expected/bins_static/stdio/ungetc_multiple.stderr b/tests/expected/bins_static/stdio/ungetc_multiple.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdio/ungetc_multiple.stdout b/tests/expected/bins_static/stdio/ungetc_multiple.stdout new file mode 100644 index 0000000000..3b18e512db --- /dev/null +++ b/tests/expected/bins_static/stdio/ungetc_multiple.stdout @@ -0,0 +1 @@ +hello world diff --git a/tests/expected/bins_static/stdlib/a64l.stderr b/tests/expected/bins_static/stdlib/a64l.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/a64l.stdout b/tests/expected/bins_static/stdlib/a64l.stdout new file mode 100644 index 0000000000..d22f10d973 --- /dev/null +++ b/tests/expected/bins_static/stdlib/a64l.stdout @@ -0,0 +1,42 @@ +Correct a64l: azAZ9. = 194301926 +Correct a64l: azA = 53222 +l64a(0): +l64a(1): / +l64a(2): 0 +l64a(11): 9 +l64a(12): A +l64a(37): Z +l64a(38): a +l64a(63): z +l64a(64): ./ +l64a(65): // +l64a(4095): zz +l64a(4096): ../ +l64a(262143): zzz +l64a(262144): .../ +l64a(16777215): zzzz +l64a(16777216): ..../ +l64a(1073741823): zzzzz +l64a(1073741824): ...../ +l64a(2147483647): zzzzz/ +a64l(l64a(0)): 0 +a64l(l64a(1)): 1 +a64l(l64a(2)): 2 +a64l(l64a(11)): 11 +a64l(l64a(12)): 12 +a64l(l64a(37)): 37 +a64l(l64a(38)): 38 +a64l(l64a(63)): 63 +a64l(l64a(64)): 64 +a64l(l64a(65)): 65 +a64l(l64a(4095)): 4095 +a64l(l64a(4096)): 4096 +a64l(l64a(262143)): 262143 +a64l(l64a(262144)): 262144 +a64l(l64a(16777215)): 16777215 +a64l(l64a(16777216)): 16777216 +a64l(l64a(1073741823)): 1073741823 +a64l(l64a(1073741824)): 1073741824 +a64l(l64a(2147483647)): 2147483647 +l64a(x) (lower 32 bits of x are 1985229328): E61Jq/ +a64l(l64a(x)) (lower 32 bits of x are 1985229328): 1985229328 diff --git a/tests/expected/bins_static/stdlib/alloc.stderr b/tests/expected/bins_static/stdlib/alloc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/alloc.stdout b/tests/expected/bins_static/stdlib/alloc.stdout new file mode 100644 index 0000000000..d5bc92dc00 --- /dev/null +++ b/tests/expected/bins_static/stdlib/alloc.stdout @@ -0,0 +1,30 @@ +malloc (size 0): (OK) +malloc: pointer: (not NULL), error value: 0 = Success +malloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +calloc (size 0): (OK) +calloc: pointer: (not NULL), error value: 0 = Success +calloc (overflowing): pointer: (nil), error value: 12 = Out of memory +realloc (size 0): (OK) +realloc: pointer: (not NULL), error value: 0 = Success +realloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +reallocarray: pointer: (not NULL), error value: 0 = Success +reallocarray (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +memalign (size 0): (OK) +memalign: pointer: (alignment OK), error value: 0 = Success +memalign (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +memalign (alignment 0): pointer: (nil), error value: 22 = Invalid argument +memalign (alignment 3): pointer: (nil), error value: 22 = Invalid argument +aligned_alloc (size % alignment == 0): pointer: (alignment OK), error value: 0 = Success +aligned_alloc (size % alignment != 0): pointer: (nil), error value: 22 = Invalid argument +valloc (size 0): (OK) +valloc: pointer: (alignment OK), error value: 0 = Success +valloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +posix_memalign: pointer: (alignment OK), error value: 0 = Success +posix_memalign (alignment 0): pointer: (nil), error value: 22 = Invalid argument +posix_memalign (non-power-of-two multiple of sizeof(void *)): pointer: (nil), error value: 22 = Invalid argument +posix_memalign (size 0): (OK) +posix_memalign (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory +pvalloc (size 0): (OK) +pvalloc: pointer: (alignment OK), error value: 0 = Success +pvalloc (2 pages): pointer: (alignment OK), error value: 0 = Success +pvalloc (SIZE_MAX): pointer: (nil), error value: 12 = Out of memory diff --git a/tests/expected/bins_static/stdlib/atof.stderr b/tests/expected/bins_static/stdlib/atof.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/atof.stdout b/tests/expected/bins_static/stdlib/atof.stdout new file mode 100644 index 0000000000..3227d85385 --- /dev/null +++ b/tests/expected/bins_static/stdlib/atof.stdout @@ -0,0 +1,2 @@ +-3.140000 +inf diff --git a/tests/expected/bins_static/stdlib/atoi.stderr b/tests/expected/bins_static/stdlib/atoi.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/stdlib/strtoul.stdout b/tests/expected/bins_static/stdlib/atoi.stdout similarity index 58% rename from tests/expected/stdlib/strtoul.stdout rename to tests/expected/bins_static/stdlib/atoi.stdout index 6f53efcf01..d90e37ccea 100644 --- a/tests/expected/stdlib/strtoul.stdout +++ b/tests/expected/bins_static/stdlib/atoi.stdout @@ -4,9 +4,3 @@ -42 555 1234567890 -38acf -abcdef12 -731 -731 -0 -0 diff --git a/tests/expected/bins_static/stdlib/div.stderr b/tests/expected/bins_static/stdlib/div.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/div.stdout b/tests/expected/bins_static/stdlib/div.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/env.stderr b/tests/expected/bins_static/stdlib/env.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/env.stdout b/tests/expected/bins_static/stdlib/env.stdout new file mode 100644 index 0000000000..a0596ab55e --- /dev/null +++ b/tests/expected/bins_static/stdlib/env.stdout @@ -0,0 +1,8 @@ +It's working!! +Updates accordingly. +in place +TEST=in place +Value overwritten and not in place because it's really long +TEST=in place +Value overwritten and not in place because it's really long +Value deleted successfully! diff --git a/tests/expected/bins_static/stdlib/getsubopt.stderr b/tests/expected/bins_static/stdlib/getsubopt.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/getsubopt.stdout b/tests/expected/bins_static/stdlib/getsubopt.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/mkostemps.stderr b/tests/expected/bins_static/stdlib/mkostemps.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/mkostemps.stdout b/tests/expected/bins_static/stdlib/mkostemps.stdout new file mode 100644 index 0000000000..f3562f6879 --- /dev/null +++ b/tests/expected/bins_static/stdlib/mkostemps.stdout @@ -0,0 +1,3 @@ +Start unchanged: 0 +End unchanged: 0 +Read & Write Successful diff --git a/tests/expected/bins_static/stdlib/ptsname.stderr b/tests/expected/bins_static/stdlib/ptsname.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/ptsname.stdout b/tests/expected/bins_static/stdlib/ptsname.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/qsort.stderr b/tests/expected/bins_static/stdlib/qsort.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/qsort.stdout b/tests/expected/bins_static/stdlib/qsort.stdout new file mode 100644 index 0000000000..49755a611e --- /dev/null +++ b/tests/expected/bins_static/stdlib/qsort.stdout @@ -0,0 +1,2 @@ +Before: 23 16 8 4 42 15 +After: 4 8 15 16 23 42 diff --git a/tests/expected/bins_static/stdlib/rand.stderr b/tests/expected/bins_static/stdlib/rand.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/rand.stdout b/tests/expected/bins_static/stdlib/rand.stdout new file mode 100644 index 0000000000..2a50461db2 --- /dev/null +++ b/tests/expected/bins_static/stdlib/rand.stdout @@ -0,0 +1,8 @@ +67141780 +201425341 +201425341 +67141780 +264204 +271585844 +264204 +271585844 diff --git a/tests/expected/bins_static/stdlib/rand48.stderr b/tests/expected/bins_static/stdlib/rand48.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/rand48.stdout b/tests/expected/bins_static/stdlib/rand48.stdout new file mode 100644 index 0000000000..fa871030ba --- /dev/null +++ b/tests/expected/bins_static/stdlib/rand48.stdout @@ -0,0 +1,14 @@ +lrand48 (uninitialized): 0 2116118 89401895 379337186 782977366 196130996 198207689 1046291021 1131187612 975888346 +drand48 (seeded with srand48): 0.750266 0.607593 0.567593 0.799563 0.984984 0.205670 0.625922 0.794426 0.369416 0.854100 +lrand48 (seeded with srand48): 1611183183 1304796356 1218897288 1717049088 2115236938 441672110 1344158015 1706017430 793314380 1834165927 +mrand48 (seeded with srand48): -1072600929 -1685374584 -1857172720 -860869119 -64493420 883344220 -1606651266 -882932436 1586628760 -626635441 +erand48: 0.210555 0.014158 0.111353 0.658369 0.103767 0.180385 0.945033 0.745768 0.290272 0.111716 +nrand48: 514983590 590935818 1480794144 1496813112 2133865028 1816766485 2095074020 126058208 909762120 14734916 +jrand48: -1066398599 903693914 -1922375113 -2090140830 1218074962 1662411059 -722435322 764426686 -874142666 -1454656015 +seed48_return: [40c0, 4d4f, daa6] +lrand48 (seeded with seed48): 386486647 2049879217 706208537 1265096744 1586830881 1884641178 1566935266 1256810805 875501172 1670187935 +xsubi restored froom seed48 return value: [40c0, 4d4f, daa6] +nrand48 (from xsubi value returned by seed48): 1654466549 509433115 2007628085 1022767111 1935848687 967444157 1967758021 686622820 989863078 1688030308 +lrand48 (with parameters from lcong48): 2015972364 2009368981 971134301 317520085 149004773 538917235 242519436 1066970146 991527304 1588277058 +lrand48 (seeded with srand48 after lcong48 call): 1611183183 1304796356 1218897288 1717049088 2115236938 441672110 1344158015 1706017430 793314380 1834165927 +lrand48 (seeded with seed48 after lcong48 call): 386486647 2049879217 706208537 1265096744 1586830881 1884641178 1566935266 1256810805 875501172 1670187935 diff --git a/tests/expected/bins_static/stdlib/random.stderr b/tests/expected/bins_static/stdlib/random.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/random.stdout b/tests/expected/bins_static/stdlib/random.stdout new file mode 100644 index 0000000000..9a754a9eb6 --- /dev/null +++ b/tests/expected/bins_static/stdlib/random.stdout @@ -0,0 +1,1011 @@ +Uninitialized: +262836907 +2022765545 +1985587709 +1559253607 +547725525 +1277054513 +317849018 +1695317205 +643446864 +1262735440 +964614733 +622582094 +280266812 +1939173898 +732650777 +1270232897 +336669216 +1669264236 +554325996 +1080138202 +1193538846 +2016671310 +1114491314 +2115358120 +2122955106 +982493572 +450262557 +439534734 +1723376538 +2066693252 +901041 +1986213445 +1941975150 +1986488750 +1397983404 +342217027 +1116059615 +1715832423 +2037534232 +1759506479 +831084215 +854665318 +234604926 +1111351027 +646355568 +967255703 +234100276 +983024785 +489036291 +788426273 +2063162987 +1682575137 +657613935 +1030170653 +1650449610 +633085394 +2012664225 +2100712167 +1072620128 +1588557116 +2019921771 +1073521169 +1427286913 +1814413273 +912526271 +677786670 +9146653 +2028585886 +246135445 +2046680885 + +Seed 1: +262836907 +2022765545 +1985587709 +1559253607 +547725525 +1277054513 +317849018 +1695317205 +643446864 +1262735440 +964614733 +622582094 +280266812 +1939173898 +732650777 +1270232897 +336669216 +1669264236 +554325996 +1080138202 +1193538846 +2016671310 +1114491314 +2115358120 +2122955106 +982493572 +450262557 +439534734 +1723376538 +2066693252 +901041 +1986213445 +1941975150 +1986488750 +1397983404 +342217027 +1116059615 +1715832423 +2037534232 +1759506479 +831084215 +854665318 +234604926 +1111351027 +646355568 +967255703 +234100276 +983024785 +489036291 +788426273 +2063162987 +1682575137 +657613935 +1030170653 +1650449610 +633085394 +2012664225 +2100712167 +1072620128 +1588557116 +2019921771 +1073521169 +1427286913 +1814413273 +912526271 +677786670 +9146653 +2028585886 +246135445 +2046680885 + +Seed 1337: +1124688561 +1792831582 +1708122057 +1060061055 +2060905921 +703006049 +867046930 +1599327113 +1946461956 +1851068511 +2107308161 +1080994526 +361067458 +2035914970 +1577981791 +72922558 +783590643 +1752267570 +1350489214 +1338091804 +225460930 +62532035 +540341638 +1391100769 +1535109757 +2039272559 +206716062 +1139547941 +462353593 +1176324546 +1766476968 +1587042155 +821672480 +1327115377 +499619562 +735094753 +2030121427 +1366666492 +186938219 +1829099735 +1070251355 +146762732 +762610613 +1431318813 +35194054 +193108756 +1504241371 +818784698 +1945376326 +707246937 +9392854 +23353608 +769778973 +549734493 +1414454377 +157405082 +441523404 +1621170440 +1296953024 +903876997 +650011338 +915946344 +343435504 +1471683818 +95578073 +843055066 +59294924 +2125699500 +62237911 +246233143 + +Seed 42, size 8: +1250496027 +1116302264 +1000676753 +1668674806 +908095735 +71666532 +896336333 +1736731266 +1314989459 +1535244752 +391441865 +1108520142 +1206814703 +534045436 +1974836613 +238077914 +1413854219 +705377000 +397905153 +1440974758 +1972995559 +282367380 +881784893 +1823504434 +879663491 +70219520 +1215814457 +1726604670 +318196447 +1939145516 +1030877685 +968547210 +1513076219 +1793402392 +1673312369 +1341335126 +481933015 +1376947140 +1980983981 +529748450 +192473459 +2072944688 +631833769 +202943022 +2012129743 +762679132 +1776566885 +562641722 +542512107 +293225800 +266051553 +1950339334 +562037703 +932343284 +1167153437 +829945746 +711923043 +392328544 +485452313 +748288734 +1303618751 +1013548940 +1115918037 +1967580906 +210226651 +149786744 +1677411153 +1109690294 +2007723191 +1000815652 + +Seed 42, size 31: +1250496027 +1116302264 +1000676753 +1668674806 +908095735 +71666532 +896336333 +1736731266 +1314989459 +1535244752 +391441865 +1108520142 +1206814703 +534045436 +1974836613 +238077914 +1413854219 +705377000 +397905153 +1440974758 +1972995559 +282367380 +881784893 +1823504434 +879663491 +70219520 +1215814457 +1726604670 +318196447 +1939145516 +1030877685 +968547210 +1513076219 +1793402392 +1673312369 +1341335126 +481933015 +1376947140 +1980983981 +529748450 +192473459 +2072944688 +631833769 +202943022 +2012129743 +762679132 +1776566885 +562641722 +542512107 +293225800 +266051553 +1950339334 +562037703 +932343284 +1167153437 +829945746 +711923043 +392328544 +485452313 +748288734 +1303618751 +1013548940 +1115918037 +1967580906 +210226651 +149786744 +1677411153 +1109690294 +2007723191 +1000815652 + +Seed 42, size 32: +1105844101 +1165395678 +461296413 +1259424640 +70225557 +1467961981 +315013290 +1176069659 +485874011 +776309703 +288010651 +556099569 +96788036 +603023941 +1732169228 +582662048 +1379333644 +2020179879 +1138761617 +1476121681 +475720173 +723447197 +2058783729 +1855053817 +596143428 +1050061698 +1183691850 +1071863601 +1773508895 +1094991931 +779433771 +222168675 +2145053629 +1963125621 +1294032277 +1771078876 +910633905 +2073466048 +1993247552 +908203886 +1889108021 +1139796181 +531799115 +652258278 +1065778581 +377563019 +1560462165 +807402954 +1517359200 +2092261280 +1459661233 +435654133 +322340651 +872639750 +1243057087 +1839699851 +817417382 +555234672 +127870336 +1139758033 +1427874422 +1370927423 +831974236 +97808156 +1926162096 +959844572 +1237566189 +1206552870 +183288347 +2069540425 + +Seed 42, size 63: +1105844101 +1165395678 +461296413 +1259424640 +70225557 +1467961981 +315013290 +1176069659 +485874011 +776309703 +288010651 +556099569 +96788036 +603023941 +1732169228 +582662048 +1379333644 +2020179879 +1138761617 +1476121681 +475720173 +723447197 +2058783729 +1855053817 +596143428 +1050061698 +1183691850 +1071863601 +1773508895 +1094991931 +779433771 +222168675 +2145053629 +1963125621 +1294032277 +1771078876 +910633905 +2073466048 +1993247552 +908203886 +1889108021 +1139796181 +531799115 +652258278 +1065778581 +377563019 +1560462165 +807402954 +1517359200 +2092261280 +1459661233 +435654133 +322340651 +872639750 +1243057087 +1839699851 +817417382 +555234672 +127870336 +1139758033 +1427874422 +1370927423 +831974236 +97808156 +1926162096 +959844572 +1237566189 +1206552870 +183288347 +2069540425 + +Seed 42, size 64: +2058979096 +1114567745 +1168098319 +1326828429 +585052544 +738633083 +1597984519 +103976400 +637164838 +949355699 +301019638 +1539653489 +829283131 +1994476715 +899306595 +810802043 +1925369788 +945984460 +125329241 +710381785 +1449014869 +899515740 +1003492141 +1640656979 +442529030 +743548669 +135718510 +965001641 +811994708 +1711301303 +374619698 +152505839 +1098490299 +1223819540 +1934201325 +1235732546 +2135248287 +991256780 +484430111 +926959142 +1670507811 +1806226321 +623744314 +1435739022 +999556678 +1374176376 +1526682215 +477688866 +1701508406 +1488226084 +576474982 +564239621 +1555496401 +2039926513 +819402007 +342426170 +1168843 +624913157 +2060652179 +912725209 +139417938 +1666100153 +2143789020 +1697813778 +1038556214 +1615031197 +31787170 +1587283572 +1479726437 +151644796 + +Seed 42, size 127: +2058979096 +1114567745 +1168098319 +1326828429 +585052544 +738633083 +1597984519 +103976400 +637164838 +949355699 +301019638 +1539653489 +829283131 +1994476715 +899306595 +810802043 +1925369788 +945984460 +125329241 +710381785 +1449014869 +899515740 +1003492141 +1640656979 +442529030 +743548669 +135718510 +965001641 +811994708 +1711301303 +374619698 +152505839 +1098490299 +1223819540 +1934201325 +1235732546 +2135248287 +991256780 +484430111 +926959142 +1670507811 +1806226321 +623744314 +1435739022 +999556678 +1374176376 +1526682215 +477688866 +1701508406 +1488226084 +576474982 +564239621 +1555496401 +2039926513 +819402007 +342426170 +1168843 +624913157 +2060652179 +912725209 +139417938 +1666100153 +2143789020 +1697813778 +1038556214 +1615031197 +31787170 +1587283572 +1479726437 +151644796 + +Seed 42, size 128: +1105844101 +1165395678 +461296413 +1259424640 +2024747114 +1114771942 +1792613078 +189454326 +466435881 +883763281 +1626567616 +1631629466 +1277725798 +1234887654 +657326752 +1836263319 +715263951 +471085233 +2086498417 +1163602145 +1547997976 +1405363974 +632333445 +201224285 +286947872 +1468210882 +103627486 +2147177614 +373040761 +1110293054 +1202766264 +1478884863 +128205084 +1664062677 +590825855 +5468550 +631350971 +235955286 +194922877 +1097786852 +1119718567 +1821490493 +581932670 +249960717 +908894499 +1239259422 +2086224037 +1624158450 +1710344655 +2025238806 +640276948 +1110858983 +1283119132 +1272610393 +1312083268 +1570067004 +593337627 +1415710754 +1569760970 +966378389 +378520161 +625043586 +297779604 +506725245 +141622615 +888605459 +512193796 +772973586 +1124560745 +707116673 + +Seed 42, size 255: +1105844101 +1165395678 +461296413 +1259424640 +2024747114 +1114771942 +1792613078 +189454326 +466435881 +883763281 +1626567616 +1631629466 +1277725798 +1234887654 +657326752 +1836263319 +715263951 +471085233 +2086498417 +1163602145 +1547997976 +1405363974 +632333445 +201224285 +286947872 +1468210882 +103627486 +2147177614 +373040761 +1110293054 +1202766264 +1478884863 +128205084 +1664062677 +590825855 +5468550 +631350971 +235955286 +194922877 +1097786852 +1119718567 +1821490493 +581932670 +249960717 +908894499 +1239259422 +2086224037 +1624158450 +1710344655 +2025238806 +640276948 +1110858983 +1283119132 +1272610393 +1312083268 +1570067004 +593337627 +1415710754 +1569760970 +966378389 +378520161 +625043586 +297779604 +506725245 +141622615 +888605459 +512193796 +772973586 +1124560745 +707116673 + +Seed 42, size 256: +2058979096 +1114567745 +1168098319 +1326828429 +585052544 +738633083 +1597984519 +103976400 +637164838 +949355699 +301019638 +1539653489 +829283131 +1994476715 +240955585 +1996759270 +1022456556 +1580994077 +1061370374 +875128855 +1125363953 +1573702147 +503131242 +1969480447 +1438211747 +91438056 +1120505602 +1956383039 +1858786240 +1571532334 +1295962411 +342691299 +190922750 +587812343 +1882440834 +1251108553 +1546539316 +237235092 +229958515 +1788514159 +1180131104 +1278005195 +1236529116 +1834489224 +2008240486 +153071308 +1122241977 +1443493489 +827253675 +1844422894 +1615942404 +1444719301 +1322997186 +2068652336 +1108669174 +1117641112 +329168079 +1033958612 +870068653 +1235189147 +1628422544 +186507460 +1238820988 +1150316436 +117400533 +1285498853 +464843634 +1049896178 +1788529262 +1239030133 + +Seed 42, other state array: +1105844101 +1165395678 +461296413 +1259424640 +70225557 +1467961981 +315013290 +1176069659 +485874011 +776309703 +288010651 +556099569 +96788036 +603023941 +1732169228 +582662048 +1379333644 +2020179879 +1138761617 +1476121681 +475720173 +723447197 +2058783729 +1855053817 +596143428 +1050061698 +1183691850 +1071863601 +1773508895 +1094991931 +779433771 +222168675 +2145053629 +1963125621 +1294032277 +1771078876 +910633905 +2073466048 +1993247552 +908203886 +1889108021 +1139796181 +531799115 +652258278 +1065778581 +377563019 +1560462165 +807402954 +1517359200 +2092261280 +1459661233 +435654133 +322340651 +872639750 +1243057087 +1839699851 +817417382 +555234672 +127870336 +1139758033 +1427874422 +1370927423 +831974236 +97808156 +1926162096 +959844572 +1237566189 +1206552870 +183288347 +2069540425 + +State data pointer restored correctly by setstate(). + +Seed 42, back to first state array: +1343006534 +1980171372 +782043423 +1083063062 +475232903 +1304516034 +1151509101 +1392464686 +1241740309 +116713217 +1697707295 +611594021 +1486722877 +464603182 +2038305329 +393952924 +215949723 +1654161471 +1745599527 +718621482 +527520873 +238823465 +1810355799 +958834563 +1301525862 +1492448612 +2080260955 +1815218141 +918843046 +317898715 +555133807 +785092322 +426122834 +1606253938 +736775485 +1973304601 +1660310177 +1521067015 +1674138324 +648896653 +2092390142 +772160169 +469099415 +2085041819 +1382277472 +557791010 +478959698 +1587628873 +557786337 +886954417 +1920913029 +643498035 +1878687182 +1359626079 +1546133539 +637470879 +1787787315 +1905187849 +1043203054 +1508046688 +410459218 +51504832 +1290534966 +486057852 +318745576 +1100789000 +36368414 +511601317 +1816117351 +820142804 + +Pointer returned by initstate with size < 8: (nil) diff --git a/tests/expected/bins_static/stdlib/strtod.stderr b/tests/expected/bins_static/stdlib/strtod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/strtod.stdout b/tests/expected/bins_static/stdlib/strtod.stdout new file mode 100644 index 0000000000..a92def04f3 --- /dev/null +++ b/tests/expected/bins_static/stdlib/strtod.stdout @@ -0,0 +1,196 @@ +d: 0.000000 Endptr: "a 1 hello" +d: 1.000000 Endptr: " hello" +d: 1.000000 Endptr: " hello 2" +d: 10.123000 Endptr: "" +d: 10.123000 Endptr: "" +d: -5.300000 Endptr: "" +d: 16.071045 Endptr: "" +d: 1.136719 Endptr: "" +d: 3.128906 Endptr: "" +d: 100000.000000 Endptr: "" +d: 100000.000000 Endptr: "" +d: 0.000010 Endptr: "" +d: 100000.000000 Endptr: " " +d: 100000.000000 Endptr: " " +d: 0.000010 Endptr: " " +d: 10000000000.000000 Endptr: "" +d: 1.000000 Endptr: "eXXXX" +d: 1.000000 Endptr: "e" +d: 1.000000 Endptr: "e " +d: 10000000000.000000 Endptr: "" +d: 1.000000 Endptr: "e+XXXX" +d: 1.000000 Endptr: "e+" +d: 1.000000 Endptr: "e+ " +d: 0.000000 Endptr: "" +d: 1.000000 Endptr: "e-XXXX" +d: 1.000000 Endptr: "e-" +d: 1.000000 Endptr: "e- " +d: -100000.000000 Endptr: "" +d: -100000.000000 Endptr: "" +d: -0.000010 Endptr: "" +d: -100000.000000 Endptr: " " +d: -100000.000000 Endptr: " " +d: -0.000010 Endptr: " " +d: -10000000000.000000 Endptr: "" +d: -1.000000 Endptr: "eXXXX" +d: -1.000000 Endptr: "e" +d: -1.000000 Endptr: "e " +d: -10000000000.000000 Endptr: "" +d: -1.000000 Endptr: "e+XXXX" +d: -1.000000 Endptr: "e+" +d: -1.000000 Endptr: "e+ " +d: -0.000000 Endptr: "" +d: -1.000000 Endptr: "e-XXXX" +d: -1.000000 Endptr: "e-" +d: -1.000000 Endptr: "e- " +d: 1234000.000000 Endptr: "" +d: 1234000.000000 Endptr: "" +d: 0.000123 Endptr: "" +d: 1234000.000000 Endptr: " " +d: 1234000.000000 Endptr: " " +d: 0.000123 Endptr: " " +d: 123400000000.000000 Endptr: "" +d: 12.340000 Endptr: "eXXXX" +d: 12.340000 Endptr: "e" +d: 12.340000 Endptr: "e " +d: 123400000000.000000 Endptr: "" +d: 12.340000 Endptr: "e+XXXX" +d: 12.340000 Endptr: "e+" +d: 12.340000 Endptr: "e+ " +d: 0.000000 Endptr: "" +d: 12.340000 Endptr: "e-XXXX" +d: 12.340000 Endptr: "e-" +d: 12.340000 Endptr: "e- " +d: -1234000.000000 Endptr: "" +d: -1234000.000000 Endptr: "" +d: -0.000123 Endptr: "" +d: -1234000.000000 Endptr: " " +d: -1234000.000000 Endptr: " " +d: -0.000123 Endptr: " " +d: -123400000000.000000 Endptr: "" +d: -12.340000 Endptr: "eXXXX" +d: -12.340000 Endptr: "e" +d: -12.340000 Endptr: "e " +d: -123400000000.000000 Endptr: "" +d: -12.340000 Endptr: "e+XXXX" +d: -12.340000 Endptr: "e+" +d: -12.340000 Endptr: "e+ " +d: -0.000000 Endptr: "" +d: -12.340000 Endptr: "e-XXXX" +d: -12.340000 Endptr: "e-" +d: -12.340000 Endptr: "e- " +d: 192.000000 Endptr: "" +d: -192.000000 Endptr: "" +d: 0.005859 Endptr: "" +d: -0.005859 Endptr: "" +d: 10.000000 Endptr: "" +d: 0.156250 Endptr: "" +d: -10.000000 Endptr: "" +d: -0.156250 Endptr: "" +d: 16.062500 Endptr: "" +d: 16.062500 Endptr: "" +d: -16.062500 Endptr: "" +d: -16.062500 Endptr: "" +d: 0.500000 Endptr: "" +d: 5.000000 Endptr: "" +d: 50.000000 Endptr: "" +d: 500.000000 Endptr: "" +d: 5000.000000 Endptr: "" +d: 50000.000000 Endptr: "" +d: 500000.000000 Endptr: "" +d: 5000000.000000 Endptr: "" +d: 50000000.000000 Endptr: "" +d: 500000000.000000 Endptr: "" +d: 5000000000.000000 Endptr: "" +d: 50000000000.000000 Endptr: "" +d: 500000000000.000000 Endptr: "" +d: 5000000000000.000000 Endptr: "" +d: 50000000000000.000000 Endptr: "" +d: 500000000000000.000000 Endptr: "" +d: 5000000000000000.000000 Endptr: "" +d: 50000000000000000.000000 Endptr: "" +d: 500000000000000000.000000 Endptr: "" +d: 5000000000000000000.000000 Endptr: "" +d: 50000000000000000000.000000 Endptr: "" +d: 500000000000000000000.000000 Endptr: "" +d: 5000000000000000000000.000000 Endptr: "" +d: 49999999999999995805696.000000 Endptr: "" +d: 499999999999999991611392.000000 Endptr: "" +d: 5000000000000000452984832.000000 Endptr: "" +d: 50000000000000002382364672.000000 Endptr: "" +d: 500000000000000006643777536.000000 Endptr: "" +d: 4999999999999999791559868416.000000 Endptr: "" +d: 49999999999999995716575428608.000000 Endptr: "" +d: 500000000000000009942312419328.000000 Endptr: "" +d: 4999999999999999817948147482624.000000 Endptr: "" +d: 50000000000000002683081102196736.000000 Endptr: "" +d: 499999999999999972787615493521408.000000 Endptr: "" +d: 4999999999999999727876154935214080.000000 Endptr: "" +d: 49999999999999998431683053958987776.000000 Endptr: "" +d: 500000000000000021210318687008980992.000000 Endptr: "" +d: 4999999999999999769381329101060571136.000000 Endptr: "" +d: 49999999999999998874404911728017014784.000000 Endptr: "" +d: -0.500000 Endptr: "" +d: -5.000000 Endptr: "" +d: -50.000000 Endptr: "" +d: -500.000000 Endptr: "" +d: -5000.000000 Endptr: "" +d: -50000.000000 Endptr: "" +d: -500000.000000 Endptr: "" +d: -5000000.000000 Endptr: "" +d: -50000000.000000 Endptr: "" +d: -500000000.000000 Endptr: "" +d: -5000000000.000000 Endptr: "" +d: -50000000000.000000 Endptr: "" +d: -500000000000.000000 Endptr: "" +d: -5000000000000.000000 Endptr: "" +d: -50000000000000.000000 Endptr: "" +d: -500000000000000.000000 Endptr: "" +d: -5000000000000000.000000 Endptr: "" +d: -50000000000000000.000000 Endptr: "" +d: -500000000000000000.000000 Endptr: "" +d: -5000000000000000000.000000 Endptr: "" +d: -50000000000000000000.000000 Endptr: "" +d: -500000000000000000000.000000 Endptr: "" +d: -5000000000000000000000.000000 Endptr: "" +d: -49999999999999995805696.000000 Endptr: "" +d: -499999999999999991611392.000000 Endptr: "" +d: -5000000000000000452984832.000000 Endptr: "" +d: -50000000000000002382364672.000000 Endptr: "" +d: -500000000000000006643777536.000000 Endptr: "" +d: -4999999999999999791559868416.000000 Endptr: "" +d: -49999999999999995716575428608.000000 Endptr: "" +d: -500000000000000009942312419328.000000 Endptr: "" +d: -4999999999999999817948147482624.000000 Endptr: "" +d: -50000000000000002683081102196736.000000 Endptr: "" +d: -499999999999999972787615493521408.000000 Endptr: "" +d: -4999999999999999727876154935214080.000000 Endptr: "" +d: -49999999999999998431683053958987776.000000 Endptr: "" +d: -500000000000000021210318687008980992.000000 Endptr: "" +d: -4999999999999999769381329101060571136.000000 Endptr: "" +d: -49999999999999998874404911728017014784.000000 Endptr: "" +d: -0.000000 Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: " foobarbaz" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: "" +d: inf Endptr: " foobarbaz" +d: -inf Endptr: "" +d: -inf Endptr: "" +d: -inf Endptr: "" +d: -inf Endptr: " foobarbaz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" +d: nan Endptr: "0.1e5" +d: nan Endptr: "-37" +d: nan Endptr: "1.05" +d: nan Endptr: " foo bar baz" diff --git a/tests/expected/bins_static/stdlib/strtol.stderr b/tests/expected/bins_static/stdlib/strtol.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/strtol.stdout b/tests/expected/bins_static/stdlib/strtol.stdout new file mode 100644 index 0000000000..4c4177ba7b --- /dev/null +++ b/tests/expected/bins_static/stdlib/strtol.stdout @@ -0,0 +1,26 @@ +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +38acf +endptr "g" +abcdef12 +endptr "" +cafebeef +endptr "" +731 +endptr "89" +731 +endptr "89" +0 +endptr "b" +0 +endptr "b" diff --git a/tests/expected/bins_static/stdlib/strtoul.stderr b/tests/expected/bins_static/stdlib/strtoul.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/strtoul.stdout b/tests/expected/bins_static/stdlib/strtoul.stdout new file mode 100644 index 0000000000..398260ab44 --- /dev/null +++ b/tests/expected/bins_static/stdlib/strtoul.stdout @@ -0,0 +1,26 @@ +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +-42 +endptr "" +555 +endptr "" +1234567890 +endptr " " +38acf +endptr "g" +abcdef12 +endptr "" +21000004 +endptr "" +731 +endptr "89" +731 +endptr "89" +0 +endptr "b" +0 +endptr "b" diff --git a/tests/expected/bins_static/stdlib/system.stderr b/tests/expected/bins_static/stdlib/system.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/stdlib/system.stdout b/tests/expected/bins_static/stdlib/system.stdout new file mode 100644 index 0000000000..90f99da2e1 --- /dev/null +++ b/tests/expected/bins_static/stdlib/system.stdout @@ -0,0 +1,2 @@ +shell found: 1 +test of system diff --git a/tests/expected/bins_static/string/mem.stderr b/tests/expected/bins_static/string/mem.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/mem.stdout b/tests/expected/bins_static/string/mem.stdout new file mode 100644 index 0000000000..a6c001081f --- /dev/null +++ b/tests/expected/bins_static/string/mem.stdout @@ -0,0 +1,5 @@ +# mem # +Correct memchr +Correct memrchr +Correct memccpy +Correct memcmp diff --git a/tests/expected/bins_static/string/memcpy.stderr b/tests/expected/bins_static/string/memcpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/memcpy.stdout b/tests/expected/bins_static/string/memcpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/memmem.stderr b/tests/expected/bins_static/string/memmem.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/memmem.stdout b/tests/expected/bins_static/string/memmem.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/stpcpy.stderr b/tests/expected/bins_static/string/stpcpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/stpcpy.stdout b/tests/expected/bins_static/string/stpcpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/stpncpy.stderr b/tests/expected/bins_static/string/stpncpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/stpncpy.stdout b/tests/expected/bins_static/string/stpncpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strcat.stderr b/tests/expected/bins_static/string/strcat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strcat.stdout b/tests/expected/bins_static/string/strcat.stdout new file mode 100644 index 0000000000..10bda51843 --- /dev/null +++ b/tests/expected/bins_static/string/strcat.stdout @@ -0,0 +1,2 @@ +hello world +hello world diff --git a/tests/expected/bins_static/string/strchr.stderr b/tests/expected/bins_static/string/strchr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strchr.stdout b/tests/expected/bins_static/string/strchr.stdout new file mode 100644 index 0000000000..43f3dcfdf0 --- /dev/null +++ b/tests/expected/bins_static/string/strchr.stdout @@ -0,0 +1,4 @@ +ello +ld + +(nil) diff --git a/tests/expected/bins_static/string/strchrnul.stderr b/tests/expected/bins_static/string/strchrnul.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strchrnul.stdout b/tests/expected/bins_static/string/strchrnul.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strcpy.stderr b/tests/expected/bins_static/string/strcpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strcpy.stdout b/tests/expected/bins_static/string/strcpy.stdout new file mode 100644 index 0000000000..f08e831ac1 --- /dev/null +++ b/tests/expected/bins_static/string/strcpy.stdout @@ -0,0 +1,7 @@ +strcpy works! +strncpy works! +strncpy shaaaaaaaaa +strlcpy works! +copied 14 +strlcpy works! and strlcat! +copied 27 diff --git a/tests/expected/bins_static/string/strcspn.stderr b/tests/expected/bins_static/string/strcspn.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strcspn.stdout b/tests/expected/bins_static/string/strcspn.stdout new file mode 100644 index 0000000000..2b24e31277 --- /dev/null +++ b/tests/expected/bins_static/string/strcspn.stdout @@ -0,0 +1,2 @@ +2 +6 diff --git a/tests/expected/bins_static/string/strlen.stderr b/tests/expected/bins_static/string/strlen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strlen.stdout b/tests/expected/bins_static/string/strlen.stdout new file mode 100644 index 0000000000..9ce90c8a65 --- /dev/null +++ b/tests/expected/bins_static/string/strlen.stdout @@ -0,0 +1,9 @@ +12 +0 +12 +12 +0 +12 +6 +12 +0 diff --git a/tests/expected/bins_static/string/strncmp.stderr b/tests/expected/bins_static/string/strncmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strncmp.stdout b/tests/expected/bins_static/string/strncmp.stdout new file mode 100644 index 0000000000..e385d8a988 --- /dev/null +++ b/tests/expected/bins_static/string/strncmp.stdout @@ -0,0 +1,6 @@ +-97 +-195 +1 +-255 +-2 +0 diff --git a/tests/expected/bins_static/string/strpbrk.stderr b/tests/expected/bins_static/string/strpbrk.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strpbrk.stdout b/tests/expected/bins_static/string/strpbrk.stdout new file mode 100644 index 0000000000..7a6bc8b089 --- /dev/null +++ b/tests/expected/bins_static/string/strpbrk.stdout @@ -0,0 +1,3 @@ +The quick drawn fix jumps over the lazy bug +lazy bug +NULL diff --git a/tests/expected/bins_static/string/strrchr.stderr b/tests/expected/bins_static/string/strrchr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strrchr.stdout b/tests/expected/bins_static/string/strrchr.stdout new file mode 100644 index 0000000000..836469707d --- /dev/null +++ b/tests/expected/bins_static/string/strrchr.stdout @@ -0,0 +1 @@ +strrch PASS diff --git a/tests/expected/bins_static/string/strsep.stderr b/tests/expected/bins_static/string/strsep.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strsep.stdout b/tests/expected/bins_static/string/strsep.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strsignal.stderr b/tests/expected/bins_static/string/strsignal.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strsignal.stdout b/tests/expected/bins_static/string/strsignal.stdout new file mode 100644 index 0000000000..833dc63089 --- /dev/null +++ b/tests/expected/bins_static/string/strsignal.stdout @@ -0,0 +1 @@ +# strsignal # diff --git a/tests/expected/bins_static/string/strspn.stderr b/tests/expected/bins_static/string/strspn.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strspn.stdout b/tests/expected/bins_static/string/strspn.stdout new file mode 100644 index 0000000000..54d9c92604 --- /dev/null +++ b/tests/expected/bins_static/string/strspn.stdout @@ -0,0 +1,3 @@ +5 +1 +0 diff --git a/tests/expected/bins_static/string/strstr.stderr b/tests/expected/bins_static/string/strstr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strstr.stdout b/tests/expected/bins_static/string/strstr.stdout new file mode 100644 index 0000000000..56fe13b7b1 --- /dev/null +++ b/tests/expected/bins_static/string/strstr.stdout @@ -0,0 +1,5 @@ +rust +libc we trust +(null) +(null) +RUST diff --git a/tests/expected/bins_static/string/strtok.stderr b/tests/expected/bins_static/string/strtok.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strtok.stdout b/tests/expected/bins_static/string/strtok.stdout new file mode 100644 index 0000000000..4428c25c54 --- /dev/null +++ b/tests/expected/bins_static/string/strtok.stdout @@ -0,0 +1 @@ +I'd_just_like_to_interject_for_a_moment._What_you're_referring_to_as_Linux,_is_in_fact,_GNU/Linux,_or_as_I've_recently_taken_to_calling_it,_GNU_plus_Linux. diff --git a/tests/expected/bins_static/string/strtok_r.stderr b/tests/expected/bins_static/string/strtok_r.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/string/strtok_r.stdout b/tests/expected/bins_static/string/strtok_r.stdout new file mode 100644 index 0000000000..4428c25c54 --- /dev/null +++ b/tests/expected/bins_static/string/strtok_r.stdout @@ -0,0 +1 @@ +I'd_just_like_to_interject_for_a_moment._What_you're_referring_to_as_Linux,_is_in_fact,_GNU/Linux,_or_as_I've_recently_taken_to_calling_it,_GNU_plus_Linux. diff --git a/tests/expected/bins_static/strings.stderr b/tests/expected/bins_static/strings.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/strings.stdout b/tests/expected/bins_static/strings.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_epoll/epollet.stdout b/tests/expected/bins_static/sys_epoll/epollet.stdout new file mode 100644 index 0000000000..cc912d90cf --- /dev/null +++ b/tests/expected/bins_static/sys_epoll/epollet.stdout @@ -0,0 +1,5 @@ +ctl ADD: 0 +wait for HUP, edge-triggered: 1 +wait for HUP again: 0 +ctl MOD: 0 +wait for HUP: 1 diff --git a/tests/expected/bins_static/sys_mman.stderr b/tests/expected/bins_static/sys_mman.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_mman.stdout b/tests/expected/bins_static/sys_mman.stdout new file mode 100644 index 0000000000..108b1819b0 --- /dev/null +++ b/tests/expected/bins_static/sys_mman.stdout @@ -0,0 +1,9 @@ +Page size: 4096 +Mapping 1 page of memory... +Mapping 3 pages of memory... +Randomizing mapping #1 +Randomizing mapping #2 +Unmapping page 2 of map2 +Randomizing page 1 of mapping #2 +Randomizing page 3 of mapping #2 +Unmapping it all at once! diff --git a/tests/expected/bins_static/sys_socket/recv.stdout b/tests/expected/bins_static/sys_socket/recv.stdout new file mode 100644 index 0000000000..cb2239126a --- /dev/null +++ b/tests/expected/bins_static/sys_socket/recv.stdout @@ -0,0 +1,2 @@ +send foo +recv foo diff --git a/tests/expected/bins_static/sys_socket/recvfrom.stdout b/tests/expected/bins_static/sys_socket/recvfrom.stdout new file mode 100644 index 0000000000..e9f413f935 --- /dev/null +++ b/tests/expected/bins_static/sys_socket/recvfrom.stdout @@ -0,0 +1,2 @@ +sendto bar +recvfrom bar diff --git a/tests/expected/bins_static/sys_socket/unixpeername.stdout b/tests/expected/bins_static/sys_socket/unixpeername.stdout new file mode 100644 index 0000000000..f9686e7295 --- /dev/null +++ b/tests/expected/bins_static/sys_socket/unixpeername.stdout @@ -0,0 +1,3 @@ +listen sockname [AF_UNIX]: unixpeername.sock +client peername [AF_UNIX]: unixpeername.sock +client sockname [AF_UNIX]: (unnamed) diff --git a/tests/expected/bins_static/sys_socket/unixrecv.stdout b/tests/expected/bins_static/sys_socket/unixrecv.stdout new file mode 100644 index 0000000000..e9636a2916 --- /dev/null +++ b/tests/expected/bins_static/sys_socket/unixrecv.stdout @@ -0,0 +1,2 @@ +send ipsum +recv ipsum diff --git a/tests/expected/bins_static/sys_socket/unixrecvfrom.stdout b/tests/expected/bins_static/sys_socket/unixrecvfrom.stdout new file mode 100644 index 0000000000..ad8e531910 --- /dev/null +++ b/tests/expected/bins_static/sys_socket/unixrecvfrom.stdout @@ -0,0 +1,2 @@ +send lorem +recvfrom lorem diff --git a/tests/expected/bins_static/sys_socket/unixsocketpair.stdout b/tests/expected/bins_static/sys_socket/unixsocketpair.stdout new file mode 100644 index 0000000000..bb7fc428fa --- /dev/null +++ b/tests/expected/bins_static/sys_socket/unixsocketpair.stdout @@ -0,0 +1,6 @@ +SOCK_STREAM +child: ping +parent: pong +SOCK_DGRAM +child: ping +parent: pong diff --git a/tests/expected/bins_static/sys_stat/chmod.stderr b/tests/expected/bins_static/sys_stat/chmod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_stat/chmod.stdout b/tests/expected/bins_static/sys_stat/chmod.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_stat/fstatat.stderr b/tests/expected/bins_static/sys_stat/fstatat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_stat/fstatat.stdout b/tests/expected/bins_static/sys_stat/fstatat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_stat/lstat.stderr b/tests/expected/bins_static/sys_stat/lstat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_stat/lstat.stdout b/tests/expected/bins_static/sys_stat/lstat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/sys_syslog/syslog.stderr b/tests/expected/bins_static/sys_syslog/syslog.stderr new file mode 100644 index 0000000000..faddade2a6 --- /dev/null +++ b/tests/expected/bins_static/sys_syslog/syslog.stderr @@ -0,0 +1,13 @@ +relibc_test: This is a test message with formatting: 5 +relibc_test: Hank Hill +relibc_test: Foo has been bar'd +relibc_test: And now, Eeveelutions +relibc_test: Espeon +relibc_test: Umbreon +relibc_test: Sylveon +relibc_test: Glaceon +relibc_test: Leafeon +relibc_test: Vaporeon +relibc_test: Flareon +relibc_test: Jolteon +relibc_test: Bye from relibc's syslog tests! diff --git a/tests/expected/bins_static/sys_syslog/syslog.stdout b/tests/expected/bins_static/sys_syslog/syslog.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/termios.stderr b/tests/expected/bins_static/termios.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/termios.stdout b/tests/expected/bins_static/termios.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/asctime.stderr b/tests/expected/bins_static/time/asctime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/asctime.stdout b/tests/expected/bins_static/time/asctime.stdout new file mode 100644 index 0000000000..f21cdff5f4 --- /dev/null +++ b/tests/expected/bins_static/time/asctime.stdout @@ -0,0 +1,5 @@ +Thu Jan 1 00:00:00 1970 +Sun Jan 1 00:00:00 1000 +Sat Dec 31 23:59:60 9999 +Sun Jan-99 00:00:00 -999 +Sat Dec999 99:99:99 9999 diff --git a/tests/expected/bins_static/time/constants.stderr b/tests/expected/bins_static/time/constants.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/constants.stdout b/tests/expected/bins_static/time/constants.stdout new file mode 100644 index 0000000000..cf14858d8c --- /dev/null +++ b/tests/expected/bins_static/time/constants.stdout @@ -0,0 +1,2 @@ +(nil) +1000000 diff --git a/tests/expected/bins_static/time/gmtime.stderr b/tests/expected/bins_static/time/gmtime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/gmtime.stdout b/tests/expected/bins_static/time/gmtime.stdout new file mode 100644 index 0000000000..a5da960f12 --- /dev/null +++ b/tests/expected/bins_static/time/gmtime.stdout @@ -0,0 +1,168 @@ +Unix epoch: + tm_sec = 0 + tm_min = 0 + tm_hour = 0 + tm_mday = 1 + tm_mon = 0 + tm_year = 70 + tm_wday = 4 + tm_yday = 0 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +1970-03-01 00:00:00: + tm_sec = 0 + tm_min = 0 + tm_hour = 0 + tm_mday = 1 + tm_mon = 2 + tm_year = 70 + tm_wday = 0 + tm_yday = 59 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +1970-12-31 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 31 + tm_mon = 11 + tm_year = 70 + tm_wday = 4 + tm_yday = 364 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +1972-02-29 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 29 + tm_mon = 1 + tm_year = 72 + tm_wday = 2 + tm_yday = 59 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +1972-12-31 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 31 + tm_mon = 11 + tm_year = 72 + tm_wday = 0 + tm_yday = 365 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +2000-02-29 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 29 + tm_mon = 1 + tm_year = 100 + tm_wday = 2 + tm_yday = 59 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +2000-12-31 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 31 + tm_mon = 11 + tm_year = 100 + tm_wday = 0 + tm_yday = 365 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +2100-03-01 00:00:00: + tm_sec = 0 + tm_min = 0 + tm_hour = 0 + tm_mday = 1 + tm_mon = 2 + tm_year = 200 + tm_wday = 1 + tm_yday = 59 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +2100-12-31 23:59:59: + tm_sec = 59 + tm_min = 59 + tm_hour = 23 + tm_mday = 31 + tm_mon = 11 + tm_year = 200 + tm_wday = 5 + tm_yday = 364 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +Year 2038, pre-i32 overflow: + tm_sec = 7 + tm_min = 14 + tm_hour = 3 + tm_mday = 19 + tm_mon = 0 + tm_year = 138 + tm_wday = 2 + tm_yday = 18 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +Year 2038, post-i32 overflow: + tm_sec = 8 + tm_min = 14 + tm_hour = 3 + tm_mday = 19 + tm_mon = 0 + tm_year = 138 + tm_wday = 2 + tm_yday = 18 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +Year 2106, pre-u32 overflow: + tm_sec = 15 + tm_min = 28 + tm_hour = 6 + tm_mday = 7 + tm_mon = 1 + tm_year = 206 + tm_wday = 0 + tm_yday = 37 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC + +Year 2106, post-u32 overflow: + tm_sec = 16 + tm_min = 28 + tm_hour = 6 + tm_mday = 7 + tm_mon = 1 + tm_year = 206 + tm_wday = 0 + tm_yday = 37 + tm_isdst = 0 + tm_gmtoff = 0 + tm_zone = UTC diff --git a/tests/expected/bins_static/time/localtime.stderr b/tests/expected/bins_static/time/localtime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/localtime.stdout b/tests/expected/bins_static/time/localtime.stdout new file mode 100644 index 0000000000..eb828a2b0d --- /dev/null +++ b/tests/expected/bins_static/time/localtime.stdout @@ -0,0 +1,9 @@ +Year 69, Day of year: 332, Month 10, Day of month: 29, Day of week: 6, 0:0:0 +Year 69, Day of year: 364, Month 11, Day of month: 31, Day of week: 3, 0:0:0 +Year 69, Day of year: 364, Month 11, Day of month: 31, Day of week: 3, 23:59:59 +Year 69, Day of year: 364, Month 11, Day of month: 31, Day of week: 3, 23:51:40 +Year 70, Day of year: 0, Month 0, Day of month: 1, Day of week: 4, 0:0:0 +Year 70, Day of year: 0, Month 0, Day of month: 1, Day of week: 4, 0:0:1 +Year 118, Day of year: 193, Month 6, Day of month: 13, Day of week: 5, 4:9:10 +Fri Jul 13 06:03:43 2018 +Fri Jul 13 06:03:43 2018 diff --git a/tests/expected/bins_static/time/localtime_r.stderr b/tests/expected/bins_static/time/localtime_r.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/localtime_r.stdout b/tests/expected/bins_static/time/localtime_r.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/macros.stderr b/tests/expected/bins_static/time/macros.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/macros.stdout b/tests/expected/bins_static/time/macros.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/mktime.stderr b/tests/expected/bins_static/time/mktime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/mktime.stdout b/tests/expected/bins_static/time/mktime.stdout new file mode 100644 index 0000000000..c9b309e63c --- /dev/null +++ b/tests/expected/bins_static/time/mktime.stdout @@ -0,0 +1,6 @@ +31536000 +-2851200 = -2851200 +-86400 = -86400 +-500 = -500 +0 = 0 +1531454950 = 1531454950 diff --git a/tests/expected/bins_static/time/strftime.stderr b/tests/expected/bins_static/time/strftime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/strftime.stdout b/tests/expected/bins_static/time/strftime.stdout new file mode 100644 index 0000000000..7d12e13954 --- /dev/null +++ b/tests/expected/bins_static/time/strftime.stdout @@ -0,0 +1,14 @@ +20: Tue Tuesday Jul July +16: The 21st century +11: 06:25:42 AM +11: 03:00:00 PM +5: 15:00 +15: 15 1531839600 2 +6: 198 28 +28: Tue Jul 17 15:00:00 UTC 2018 +0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U +53 +52 +52 +1 +53 diff --git a/tests/expected/bins_static/time/strptime.stderr b/tests/expected/bins_static/time/strptime.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/strptime.stdout b/tests/expected/bins_static/time/strptime.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/time.stderr b/tests/expected/bins_static/time/time.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/time.stdout b/tests/expected/bins_static/time/time.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/timegm.stderr b/tests/expected/bins_static/time/timegm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/timegm.stdout b/tests/expected/bins_static/time/timegm.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/tzset.stderr b/tests/expected/bins_static/time/tzset.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/tzset.stdout b/tests/expected/bins_static/time/tzset.stdout new file mode 100644 index 0000000000..9962d8acc0 --- /dev/null +++ b/tests/expected/bins_static/time/tzset.stdout @@ -0,0 +1 @@ +tzname[0] UTC, tzname[1] UTC, daylight 0, timezone 0 diff --git a/tests/expected/bins_static/tls.stderr b/tests/expected/bins_static/tls.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/tls.stdout b/tests/expected/bins_static/tls.stdout new file mode 100644 index 0000000000..d0b7b3c2e6 --- /dev/null +++ b/tests/expected/bins_static/tls.stdout @@ -0,0 +1,2 @@ +0 == 0 +1 == 1 diff --git a/tests/expected/bins_static/unistd/access.stderr b/tests/expected/bins_static/unistd/access.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/access.stdout b/tests/expected/bins_static/unistd/access.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/alarm.stderr b/tests/expected/bins_static/unistd/alarm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/alarm.stdout b/tests/expected/bins_static/unistd/alarm.stdout new file mode 100644 index 0000000000..4ed7f83631 --- /dev/null +++ b/tests/expected/bins_static/unistd/alarm.stdout @@ -0,0 +1,4 @@ +alarm(0) baseline: ok +alarm(1) fires: ok +alarm(0) cancel: ok +alarm re-arm returns remaining: ok diff --git a/tests/expected/bins_static/unistd/brk.stderr b/tests/expected/bins_static/unistd/brk.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/brk.stdout b/tests/expected/bins_static/unistd/brk.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/confstr.stderr b/tests/expected/bins_static/unistd/confstr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/confstr.stdout b/tests/expected/bins_static/unistd/confstr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/constants.stderr b/tests/expected/bins_static/unistd/constants.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/constants.stdout b/tests/expected/bins_static/unistd/constants.stdout new file mode 100644 index 0000000000..90c907f1ff --- /dev/null +++ b/tests/expected/bins_static/unistd/constants.stdout @@ -0,0 +1,16 @@ +_POSIX_VERSION: 200809 +NULL: (nil) +R_OK: 4 +W_OK: 2 +X_OK: 1 +F_OK: 0 +SEEK_SET: 0 +SEEK_CUR: 1 +SEEK_END: 2 +F_LOCK: 1 +F_ULOCK: 0 +F_TEST: 3 +F_TLOCK: 2 +STDIN_FILENO: 0 +STDOUT_FILENO: 1 +STDERR_FILENO: 2 diff --git a/tests/expected/bins_static/unistd/dup.stderr b/tests/expected/bins_static/unistd/dup.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/dup.stdout b/tests/expected/bins_static/unistd/dup.stdout new file mode 100644 index 0000000000..4fb8e07602 --- /dev/null +++ b/tests/expected/bins_static/unistd/dup.stdout @@ -0,0 +1 @@ +duped fd is 1 greater than the original fd diff --git a/tests/expected/bins_static/unistd/exec.stderr b/tests/expected/bins_static/unistd/exec.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/exec.stdout b/tests/expected/bins_static/unistd/exec.stdout new file mode 100644 index 0000000000..0f56119c73 --- /dev/null +++ b/tests/expected/bins_static/unistd/exec.stdout @@ -0,0 +1 @@ +exec works :D diff --git a/tests/expected/bins_static/unistd/fchdir.stderr b/tests/expected/bins_static/unistd/fchdir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/fchdir.stdout b/tests/expected/bins_static/unistd/fchdir.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/fork.stderr b/tests/expected/bins_static/unistd/fork.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/fork.stdout b/tests/expected/bins_static/unistd/fork.stdout new file mode 100644 index 0000000000..98f331c9de --- /dev/null +++ b/tests/expected/bins_static/unistd/fork.stdout @@ -0,0 +1,3 @@ +Hello from prepare +Hello from child +Hello from parent diff --git a/tests/expected/bins_static/unistd/fsync.stderr b/tests/expected/bins_static/unistd/fsync.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/fsync.stdout b/tests/expected/bins_static/unistd/fsync.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/ftruncate.stderr b/tests/expected/bins_static/unistd/ftruncate.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/ftruncate.stdout b/tests/expected/bins_static/unistd/ftruncate.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/getopt.stderr b/tests/expected/bins_static/unistd/getopt.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/getopt.stdout b/tests/expected/bins_static/unistd/getopt.stdout new file mode 100644 index 0000000000..aba2d44d3f --- /dev/null +++ b/tests/expected/bins_static/unistd/getopt.stdout @@ -0,0 +1,42 @@ +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 1 +errflg: 0 +ifile: +ofile: arg +result: 0 +bflg: 0 +aflg: 0 +errflg: 0 +ifile: +ofile: +result: 0 diff --git a/tests/expected/bins_static/unistd/getopt_long.stderr b/tests/expected/bins_static/unistd/getopt_long.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/getopt_long.stdout b/tests/expected/bins_static/unistd/getopt_long.stdout new file mode 100644 index 0000000000..d2f352b822 --- /dev/null +++ b/tests/expected/bins_static/unistd/getopt_long.stdout @@ -0,0 +1,20 @@ +--- Running: test --test0 -a +getopt_long returned 1, argument test0=(null) +Option -a with value (null) +--- Running: test --test1 -a +getopt_long returned 0, set flag to 2, argument test1=(null) +Option -a with value (null) +--- Running: test --test2 -a +getopt_long returned 3, argument test2=(null) +Option -a with value (null) +--- Running: test --test2=arg -a +getopt_long returned 3, argument test2=arg +Option -a with value (null) +--- Running: test --test3 -a +getopt_long returned 4, argument test3=-a +--- Running: test --test3=arg -a +getopt_long returned 4, argument test3=arg +Option -a with value (null) +--- Running: test --test3 arg -a +getopt_long returned 4, argument test3=arg +Option -a with value (null) diff --git a/tests/expected/bins_static/unistd/pipe.stderr b/tests/expected/bins_static/unistd/pipe.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/pipe.stdout b/tests/expected/bins_static/unistd/pipe.stdout new file mode 100644 index 0000000000..980a0d5f19 --- /dev/null +++ b/tests/expected/bins_static/unistd/pipe.stdout @@ -0,0 +1 @@ +Hello World! diff --git a/tests/expected/bins_static/unistd/readlinkat.stderr b/tests/expected/bins_static/unistd/readlinkat.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/readlinkat.stdout b/tests/expected/bins_static/unistd/readlinkat.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/rmdir.stderr b/tests/expected/bins_static/unistd/rmdir.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/rmdir.stdout b/tests/expected/bins_static/unistd/rmdir.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/sleep.stderr b/tests/expected/bins_static/unistd/sleep.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/sleep.stdout b/tests/expected/bins_static/unistd/sleep.stdout new file mode 100644 index 0000000000..25867863a2 --- /dev/null +++ b/tests/expected/bins_static/unistd/sleep.stdout @@ -0,0 +1 @@ +unslept: 0 diff --git a/tests/expected/bins_static/unistd/swab.stderr b/tests/expected/bins_static/unistd/swab.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/swab.stdout b/tests/expected/bins_static/unistd/swab.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/write.stderr b/tests/expected/bins_static/unistd/write.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/unistd/write.stdout b/tests/expected/bins_static/unistd/write.stdout new file mode 100644 index 0000000000..980a0d5f19 --- /dev/null +++ b/tests/expected/bins_static/unistd/write.stdout @@ -0,0 +1 @@ +Hello World! diff --git a/tests/expected/bins_static/wchar/fgetwc.stderr b/tests/expected/bins_static/wchar/fgetwc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/fgetwc.stdout b/tests/expected/bins_static/wchar/fgetwc.stdout new file mode 100644 index 0000000000..e7cabf537e --- /dev/null +++ b/tests/expected/bins_static/wchar/fgetwc.stdout @@ -0,0 +1,3 @@ +êçã +こんにちは世界 +Привет мир \ No newline at end of file diff --git a/tests/expected/bins_static/wchar/fwide.stderr b/tests/expected/bins_static/wchar/fwide.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/fwide.stdout b/tests/expected/bins_static/wchar/fwide.stdout new file mode 100644 index 0000000000..44e0be8e35 --- /dev/null +++ b/tests/expected/bins_static/wchar/fwide.stdout @@ -0,0 +1,4 @@ +0 +0 +0 +0 diff --git a/tests/expected/bins_static/wchar/mbrtowc.stderr b/tests/expected/bins_static/wchar/mbrtowc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/mbrtowc.stdout b/tests/expected/bins_static/wchar/mbrtowc.stdout new file mode 100644 index 0000000000..8ea12e1952 --- /dev/null +++ b/tests/expected/bins_static/wchar/mbrtowc.stdout @@ -0,0 +1,2 @@ +Processing 11 UTF-8 code units: [ 0x7a 0xc3 0x9f 0xe6 0xb0 0xb4 0xf0 0x9f 0x8d 0x8c 0 ] +into 5 wchar_t units: [ 0x7a 0xdf 0x6c34 0x1f34c 0 ] diff --git a/tests/expected/bins_static/wchar/mbsrtowcs.stderr b/tests/expected/bins_static/wchar/mbsrtowcs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/mbsrtowcs.stdout b/tests/expected/bins_static/wchar/mbsrtowcs.stdout new file mode 100644 index 0000000000..e8f8258f2e --- /dev/null +++ b/tests/expected/bins_static/wchar/mbsrtowcs.stdout @@ -0,0 +1 @@ +The length, including '\0': 5 diff --git a/tests/expected/bins_static/wchar/printf-on-wchars.stderr b/tests/expected/bins_static/wchar/printf-on-wchars.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/printf-on-wchars.stdout b/tests/expected/bins_static/wchar/printf-on-wchars.stdout new file mode 100644 index 0000000000..b14ed25516 --- /dev/null +++ b/tests/expected/bins_static/wchar/printf-on-wchars.stdout @@ -0,0 +1,4 @@ +This is a few one-byte chars: 1 2 a b +Long one-byte string: Hello World +This is a few multi-byte chars: ❤ R 😠 C +Long multi-byte string: 👉😎👉 Zoop! diff --git a/tests/expected/bins_static/wchar/putwchar.stderr b/tests/expected/bins_static/wchar/putwchar.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/putwchar.stdout b/tests/expected/bins_static/wchar/putwchar.stdout new file mode 100644 index 0000000000..42d65d1da0 --- /dev/null +++ b/tests/expected/bins_static/wchar/putwchar.stdout @@ -0,0 +1 @@ +zß水🍌 \ No newline at end of file diff --git a/tests/expected/bins_static/wchar/ungetwc.stderr b/tests/expected/bins_static/wchar/ungetwc.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/ungetwc.stdout b/tests/expected/bins_static/wchar/ungetwc.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcpcpy.stderr b/tests/expected/bins_static/wchar/wcpcpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcpcpy.stdout b/tests/expected/bins_static/wchar/wcpcpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcpncpy.stderr b/tests/expected/bins_static/wchar/wcpncpy.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcpncpy.stdout b/tests/expected/bins_static/wchar/wcpncpy.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcrtomb.stderr b/tests/expected/bins_static/wchar/wcrtomb.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcrtomb.stdout b/tests/expected/bins_static/wchar/wcrtomb.stdout new file mode 100644 index 0000000000..ed85da3696 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcrtomb.stdout @@ -0,0 +1,2 @@ +Processing 5 wchar_t units: [ 0x7a 0xdf 0x6c34 0x1f34c 0 ] +into 11 UTF-8 code units: [ 0x7a 0xc3 0x9f 0xe6 0xb0 0xb4 0xf0 0x9f 0x8d 0x8c 0 ] diff --git a/tests/expected/bins_static/wchar/wcscasecmp.stderr b/tests/expected/bins_static/wchar/wcscasecmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcscasecmp.stdout b/tests/expected/bins_static/wchar/wcscasecmp.stdout new file mode 100644 index 0000000000..fdd11f6552 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcscasecmp.stdout @@ -0,0 +1,4 @@ +wcscasecmp(s1, s1) = 0 +wcscasecmp(s1, s2) = -1 +wcscasecmp(s2, s1) = 1 +wcscasecmp(s2, s2) = 0 diff --git a/tests/expected/bins_static/wchar/wcschr.stderr b/tests/expected/bins_static/wchar/wcschr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcschr.stdout b/tests/expected/bins_static/wchar/wcschr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcscspn.stderr b/tests/expected/bins_static/wchar/wcscspn.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcscspn.stdout b/tests/expected/bins_static/wchar/wcscspn.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsdup.stderr b/tests/expected/bins_static/wchar/wcsdup.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsdup.stdout b/tests/expected/bins_static/wchar/wcsdup.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsncasecmp.stderr b/tests/expected/bins_static/wchar/wcsncasecmp.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsncasecmp.stdout b/tests/expected/bins_static/wchar/wcsncasecmp.stdout new file mode 100644 index 0000000000..6a4bd43fc4 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcsncasecmp.stdout @@ -0,0 +1,5 @@ +wcsncasecmp(s1, s1, 17) = 0 +wcsncasecmp(s1, s2, 17) = -1 +wcsncasecmp(s2, s1, 17) = 1 +wcsncasecmp(s2, s1, 15) = 0 +wcsncasecmp(s1, s2, 0) = 0 diff --git a/tests/expected/bins_static/wchar/wcsnlen.stderr b/tests/expected/bins_static/wchar/wcsnlen.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsnlen.stdout b/tests/expected/bins_static/wchar/wcsnlen.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsnrtombs.stderr b/tests/expected/bins_static/wchar/wcsnrtombs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsnrtombs.stdout b/tests/expected/bins_static/wchar/wcsnrtombs.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsrchr.stderr b/tests/expected/bins_static/wchar/wcsrchr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsrchr.stdout b/tests/expected/bins_static/wchar/wcsrchr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsrtombs.stderr b/tests/expected/bins_static/wchar/wcsrtombs.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsrtombs.stdout b/tests/expected/bins_static/wchar/wcsrtombs.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsstr.stderr b/tests/expected/bins_static/wchar/wcsstr.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcsstr.stdout b/tests/expected/bins_static/wchar/wcsstr.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstod.stderr b/tests/expected/bins_static/wchar/wcstod.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstod.stdout b/tests/expected/bins_static/wchar/wcstod.stdout new file mode 100644 index 0000000000..0292990d95 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcstod.stdout @@ -0,0 +1,6 @@ +strtod(1.2345wowzah) = (1.234500, wowzah) +strtod(53) = (53.000000, ) +strtod(-254352.5...) = (-254352.500000, ...) +strtod( 19.2 wat) = (19.200000, wat) +strtod(365.24 29.53) = (365.240000, 29.53) +strtod( 29.53) = (29.530000, ) diff --git a/tests/expected/bins_static/wchar/wcstoimax.stderr b/tests/expected/bins_static/wchar/wcstoimax.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstoimax.stdout b/tests/expected/bins_static/wchar/wcstoimax.stdout new file mode 100644 index 0000000000..b9a14be845 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcstoimax.stdout @@ -0,0 +1,6 @@ +-123 +255 +44027 +8 +10 +16 diff --git a/tests/expected/bins_static/wchar/wcstok.stderr b/tests/expected/bins_static/wchar/wcstok.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstok.stdout b/tests/expected/bins_static/wchar/wcstok.stdout new file mode 100644 index 0000000000..bb7ba090dd --- /dev/null +++ b/tests/expected/bins_static/wchar/wcstok.stdout @@ -0,0 +1,12 @@ +Hello +from +the +otter +slide. +Must +have +gone +down +a +thousand +tiiiiiimeeeees... diff --git a/tests/expected/bins_static/wchar/wcstol.stderr b/tests/expected/bins_static/wchar/wcstol.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstol.stdout b/tests/expected/bins_static/wchar/wcstol.stdout new file mode 100644 index 0000000000..55aa50779a --- /dev/null +++ b/tests/expected/bins_static/wchar/wcstol.stdout @@ -0,0 +1 @@ +The decimal equivalents are: 2001, 6340800, -3624224 and 7340031. diff --git a/tests/expected/bins_static/wchar/wcstoumax.stderr b/tests/expected/bins_static/wchar/wcstoumax.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcstoumax.stdout b/tests/expected/bins_static/wchar/wcstoumax.stdout new file mode 100644 index 0000000000..8e372f7589 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcstoumax.stdout @@ -0,0 +1,3 @@ +nptr = `10110134932` +wcstoumax = 10110134932 +Stopped scan at `` diff --git a/tests/expected/bins_static/wchar/wcswidth.stderr b/tests/expected/bins_static/wchar/wcswidth.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wcswidth.stdout b/tests/expected/bins_static/wchar/wcswidth.stdout new file mode 100644 index 0000000000..7da79364d8 --- /dev/null +++ b/tests/expected/bins_static/wchar/wcswidth.stdout @@ -0,0 +1 @@ +wcswidth(L"relibc", 6) = 6 diff --git a/tests/expected/bins_static/wchar/wprintf.stderr b/tests/expected/bins_static/wchar/wprintf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wprintf.stdout b/tests/expected/bins_static/wchar/wprintf.stdout new file mode 100644 index 0000000000..724e64b5b5 --- /dev/null +++ b/tests/expected/bins_static/wchar/wprintf.stdout @@ -0,0 +1,67 @@ +percent: % +wstring: String +char: c +int: -16 +uint: 32 +hex: beef +HEX: C0FFEE +string: end +%n returned 44, total len of write: 87 + +Padding madness: + 001 +2 + c + 0x00000ff + 001 +0 0x1 + 1 +(00123) ( 123) +(-0123) ( -123) +( ) +0xabcdef +(nil) + +Positional madness: +4 3 2 +00002 +|Fiz |Buz | Fiz| Tot| +int: 5 double: 0.100000 0.200000 0.300000 0.400000 +-1717986918 0.100000 +-1717986918 0.200000 + +Float madness: + 1.234568e+02 + 1.000000E-05 + 123.456789 + 0.000010 + -1.234568e+02 +-00000001.234568e+02 +%.5g: -123.46 +%.5f: -123.45679 +%.5e: -1.23457e+02 +%.*g: -1.2e+02 +%.*f: -123.46 +%.*e: -1.23e+02 +%.*2$g: -123.46 +%.*2$f: -123.45679 +%.*2$e: -1.23457e+02 +100000 +1e+06 +1.000000e+06 +0.0001 +1E-05 +1.000000E-05 + +Non-finite float madness: +%e: inf -inf nan -nan +%E: INF -INF NAN -NAN +%f: inf -inf nan -nan +%F: INF -INF NAN -NAN +%g: inf -inf nan -nan +%G: INF -INF NAN -NAN +Things that have been buggy ++05 +Testing asprintf... +printed: test string, value: 11 +printed: test string 2, value: 13 +printed: test string 2, value: 13 diff --git a/tests/expected/bins_static/wchar/wscanf.stderr b/tests/expected/bins_static/wchar/wscanf.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wchar/wscanf.stdout b/tests/expected/bins_static/wchar/wscanf.stdout new file mode 100644 index 0000000000..90abc9a7f6 Binary files /dev/null and b/tests/expected/bins_static/wchar/wscanf.stdout differ diff --git a/tests/expected/bins_static/wctype/towlower.stderr b/tests/expected/bins_static/wctype/towlower.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wctype/towlower.stdout b/tests/expected/bins_static/wctype/towlower.stdout new file mode 100644 index 0000000000..fdbf17836f --- /dev/null +++ b/tests/expected/bins_static/wctype/towlower.stdout @@ -0,0 +1,2 @@ +HaLf WiDe ChAr StRiNg! +half wide char string! diff --git a/tests/expected/bins_static/wctype/towupper.stderr b/tests/expected/bins_static/wctype/towupper.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/wctype/towupper.stdout b/tests/expected/bins_static/wctype/towupper.stdout new file mode 100644 index 0000000000..a09771e7bc --- /dev/null +++ b/tests/expected/bins_static/wctype/towupper.stdout @@ -0,0 +1,2 @@ +HaLf WiDe ChAr StRiNg! +HALF WIDE CHAR STRING! diff --git a/tests/expected/dlfcn.stdout b/tests/expected/dlfcn.stdout deleted file mode 100644 index 36ae1185bd..0000000000 --- a/tests/expected/dlfcn.stdout +++ /dev/null @@ -1 +0,0 @@ -add(22, 33) = 55 diff --git a/tests/expected/endian.stderr b/tests/expected/endian.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/endian.stdout b/tests/expected/endian.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/features.stderr b/tests/expected/features.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/features.stdout b/tests/expected/features.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/glob.stderr b/tests/expected/glob.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/glob.stdout b/tests/expected/glob.stdout new file mode 100644 index 0000000000..ef1351361b --- /dev/null +++ b/tests/expected/glob.stdout @@ -0,0 +1,70 @@ +Pattern = eample_dir/* +(NOMATCH) + +Pattern = eample_dir/* +glob_err("eample_dir", "No such file or directory") -> 0 +(ABORTED) + +Pattern = ./example_dir/* +(Matched 8) +0 - ./example_dir/1-never-gonna-give-you-up +1 - ./example_dir/2-never-gonna-let-you-down +2 - ./example_dir/3-never-gonna-run-around +3 - ./example_dir/4-and-desert-you +4 - ./example_dir/5-never-gonna-make-you-cry +5 - ./example_dir/6-never-gonna-say-goodbye +6 - ./example_dir/7-never-gonna-tell-a-lie +7 - ./example_dir/8-and-hurt-you + +Pattern = example_dir/*never* +(Matched 6) +0 - example_dir/1-never-gonna-give-you-up +1 - example_dir/2-never-gonna-let-you-down +2 - example_dir/3-never-gonna-run-around +3 - example_dir/5-never-gonna-make-you-cry +4 - example_dir/6-never-gonna-say-goodbye +5 - example_dir/7-never-gonna-tell-a-lie + +Pattern = example_dir/?-and* +(Matched 8) +0 - example_dir/1-never-gonna-give-you-up +1 - example_dir/2-never-gonna-let-you-down +2 - example_dir/3-never-gonna-run-around +3 - example_dir/5-never-gonna-make-you-cry +4 - example_dir/6-never-gonna-say-goodbye +5 - example_dir/7-never-gonna-tell-a-lie +6 - example_dir/4-and-desert-you +7 - example_dir/8-and-hurt-you + +Pattern = example_dir/*never* +(Matched 6) (with 4 gl_offs) +0 - NULL +1 - NULL +2 - NULL +3 - NULL +4 - example_dir/1-never-gonna-give-you-up +5 - example_dir/2-never-gonna-let-you-down +6 - example_dir/3-never-gonna-run-around +7 - example_dir/5-never-gonna-make-you-cry +8 - example_dir/6-never-gonna-say-goodbye +9 - example_dir/7-never-gonna-tell-a-lie + +Pattern = example_dir/?-and* +(Matched 8) (with 4 gl_offs) +0 - +1 - NULL +2 - NULL +3 - NULL +4 - example_dir/1-never-gonna-give-you-up +5 - example_dir/2-never-gonna-let-you-down +6 - example_dir/3-never-gonna-run-around +7 - example_dir/5-never-gonna-make-you-cry +8 - example_dir/6-never-gonna-say-goodbye +9 - example_dir/7-never-gonna-tell-a-lie +10 - example_dir/4-and-desert-you +11 - example_dir/8-and-hurt-you + +Pattern = example_dir +(Matched 1) +0 - example_dir/ + diff --git a/tests/expected/netdb/netdb.stdout b/tests/expected/netdb/netdb.stdout deleted file mode 100644 index 26a2f0f906..0000000000 --- a/tests/expected/netdb/netdb.stdout +++ /dev/null @@ -1 +0,0 @@ -No visible errors occurred! diff --git a/tests/expected/ptrace.stdout b/tests/expected/ptrace.stdout deleted file mode 100644 index c2e762dd87..0000000000 --- a/tests/expected/ptrace.stdout +++ /dev/null @@ -1,30 +0,0 @@ ------ Pre-syscall ----- -Wait... -Get regs -Set regs -Post-syscall -Wait... ------ Pre-syscall ----- -Wait... -Get regs -Set regs -Post-syscall -Wait... ------ Pre-syscall ----- -Wait... -Get regs -Set regs -Post-syscall -Wait... ------ Pre-syscall ----- -Wait... -Get regs -Set regs -Post-syscall -Wait... ------ Pre-syscall ----- -Wait... -Get regs -Post-syscall -Wait... -Child exited with status 0 diff --git a/tests/expected/stdlib/strtod.stdout b/tests/expected/stdlib/strtod.stdout deleted file mode 100644 index 68d889ec36..0000000000 --- a/tests/expected/stdlib/strtod.stdout +++ /dev/null @@ -1,9 +0,0 @@ -d: 0.000000 Endptr: "a 1 hello" -d: 1.000000 Endptr: " hello" -d: 1.000000 Endptr: " hello 2" -d: 10.123000 Endptr: "" -d: 10.123000 Endptr: "" -d: -5.300000 Endptr: "" -d: 16.071045 Endptr: "" -d: 1.136719 Endptr: "" -d: 3.128906 Endptr: "" diff --git a/tests/expected/stdlib/strtol.stdout b/tests/expected/stdlib/strtol.stdout deleted file mode 100644 index 5ac06ec251..0000000000 --- a/tests/expected/stdlib/strtol.stdout +++ /dev/null @@ -1,13 +0,0 @@ --42 -555 -1234567890 --42 -555 -1234567890 -38acf -abcdef12 -cafebeef -731 -731 -0 -0 diff --git a/tests/expected/string/strcpy.stdout b/tests/expected/string/strcpy.stdout deleted file mode 100644 index 84caf120d6..0000000000 --- a/tests/expected/string/strcpy.stdout +++ /dev/null @@ -1,3 +0,0 @@ -strcpy works! -strncpy works! -strncpy shaaaaaaaaa diff --git a/tests/expected/wchar/wcstol.stdout b/tests/expected/wchar/wcstol.stdout deleted file mode 100644 index ec5ae58779..0000000000 --- a/tests/expected/wchar/wcstol.stdout +++ /dev/null @@ -1 +0,0 @@ -The decimal equivalents are: 2001, 6340800, -3624224 and 0. diff --git a/tests/fcntl/open.c b/tests/fcntl/open.c new file mode 100644 index 0000000000..ed8a999f3a --- /dev/null +++ b/tests/fcntl/open.c @@ -0,0 +1,296 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +__attribute__((nonnull)) +int cloexec_test(char path[], int argc, char* argv[]) { + assert(argc >= 1); + + int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + perror("open (O_CLOEXEC)"); + return -1; + } + + pid_t child = fork(); + if (child == -1) { + perror("fork"); + close(fd); + return -1; + } else if (child == 0) { + // In child process where fd should be closed after execv. + char fd_str[8] = {0}; + sprintf(fd_str, "%d", fd); + + char* new_argv[] = { + argv[0], + fd_str, + NULL + }; + + if (execv(argv[0], new_argv) == -1) { + perror("execv"); + } + + exit(EXIT_FAILURE); + } else { + // In parent where fd should remain open. + int status = -1; + if (waitpid(child, &status, 0) == -1) { + perror("waitpid"); + return -1; + } + + if (fcntl(fd, F_GETFD) < 0) { + fputs( + "File descriptor closed in parent after exec with O_CLOEXEC\n", + stderr + ); + kill(child, SIGKILL); + waitpid(child, NULL, 0); + + return -1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { + return 0; + } else { + fputs( + "Child process to test O_CLOEXEC failed\n", + stderr + ); + return -1; + } + } + + // Unreachable + return -1; +} + +int cloexec_validate(int argc, char* argv[]) { + assert(argc == 2); + + char* end = NULL; + int fd = (int) strtol(argv[1], &end, 0); + + if (*end == '\0') { + if (fcntl(fd, F_GETFD) >= 0) { + fputs( + "File descriptor still open after exec with O_CLOEXEC\n", + stderr + ); + + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } else { + fputs( + "Invalid file descriptor number passed to cloxec_validate\n", + stderr + ); + return EXIT_FAILURE; + } +} + +int main(int argc, char* argv[]) { + // The test runner inserts two fixed arg, so argc == 2 is fine + if (argc == 2) { + return cloexec_validate(argc, argv); + } else if (argc == 0) { + fprintf( + stderr, + "Invalid number of arguments: %d\n", + argc + ); + return EXIT_FAILURE; + } + + int result = EXIT_FAILURE; + + char dir_template[] = "open_tests.XXXXXX"; + size_t dlen = sizeof(dir_template) - 1; + if (!mkdtemp(dir_template)) { + perror("mkdtemp"); + goto bye; + } + + char file_path[PATH_MAX] = {0}; + const char file_name[] = "peanut"; + memcpy(file_path, dir_template, dlen); + file_path[dlen] = '/'; + memcpy(&file_path[dlen + 1], file_name, sizeof(file_name)); + + int fd = open(file_path, O_CREAT, 0644); + if (fd == -1) { + perror("open (creating file in temp dir)"); + goto clean_opened_dir; + } + if (close(fd) == -1) { + perror("close (file)"); + goto clean_opened_dir; + } + + // Check that close actually closed fd + if (fcntl(fd, F_GETFD) >= 0) { + fputs( + "File descriptor was not closed by close\n", + stderr + ); + goto clean_opened_file; + } + + // Opening a file with O_DIRECTORY should fail + fd = open(file_path, O_DIRECTORY); + if (fd != -1) { + fputs( + "File successfully opened with O_DIRECTORY\n", + stderr + ); + close(fd); + goto clean_opened_file; + } + + // O_CREAT | O_EXCL should fail on existing file + fd = open(file_path, O_CREAT | O_EXCL, 0644); + if (fd != -1) { + fputs( + "Existing file created with O_CREAT | O_EXCL\n", + stderr + ); + close(fd); + goto clean_opened_file; + } + + // O_PATH + fd = open(file_path, O_PATH | O_WRONLY); + // O_PATH should ignore O_WRONLY but NOT fail. + if (fd == -1) { + perror("open (O_PATH)"); + goto clean_opened_file; + } + +// TODO: There is no special handling to O_PATH at kernel level +#ifndef __redox__ + // Writing this buf should fail. + const char buf[] = "Valencia peanuts"; + if (write(fd, buf, sizeof(buf)) != -1) { + fputs( + "Writing to an O_PATH fd succeeded\n", + stderr + ); + close(fd); + goto clean_opened_file; + } +#endif + + // But fstat should succeed with O_PATH + struct stat stat = {0}; + if (fstat(fd, &stat) == -1) { + perror("fstat (O_PATH)"); + close(fd); + goto clean_opened_file; + } + + // O_NOATIME (disabled since Redox doesn't have it) + // struct timespec atime_expected = stat.st_atim; + + close(fd); + /* fd = open(file_path, O_NOATIME); */ + /* if (fd == -1) { */ + /* perror("open (O_NOATIME)"); */ + /* close(fd); */ + /* goto clean_opened_file; */ + /* } */ + /**/ + /* if (fstat(fd, &stat) == -1) { */ + /* perror("fstat (O_PATH)"); */ + /* close(fd); */ + /* goto clean_opened_file; */ + /* } */ + /**/ + /* struct timespec atime_actual = stat.st_atim; */ + /* if ( */ + /* (atime_expected.tv_nsec != atime_actual.tv_nsec) */ + /* || (atime_expected.tv_sec != atime_actual.tv_sec) */ + /* ) { */ + /* fputs( */ + /* "Access time changed with O_NOATIME\n", */ + /* stderr */ + /* ); */ + /* close(fd); */ + /* goto clean_opened_file; */ + /* } */ + /* close(fd); */ + + // O_NOFOLLOW + char sym_path[PATH_MAX] = {0}; + const char sym_name[] = "cashew"; + memcpy(sym_path, dir_template, dlen); + sym_path[dlen] = '/'; + memcpy(&sym_path[dlen + 1], sym_name, sizeof(sym_name)); + + if (symlink(file_path, sym_path) == -1) { + perror("symlink"); + goto clean_opened_file; + } + fd = open(sym_path, O_NOFOLLOW); + if (fd != -1) { + fputs( + "Symlink opened with O_NOFOLLOW\n", + stderr + ); + close(fd); + goto clean_symlink; + } + + // O_CLOEXEC + cloexec_test(file_path, argc, argv); + + // Opening existing directory + int dirfd = open(dir_template, O_RDONLY); + if (dirfd == -1) { + perror("open (existing dir)"); + goto clean_symlink; + } + close(dirfd); + + // Opening existing directory (read/write) + dirfd = open(dir_template, O_RDWR); + if (dirfd != -1) { + fputs( + "Opening a directory with O_RDWR incorrectly succeeded\n", + stderr + ); + goto clean_symlink; + } + close(dirfd); + + // Opening existing directory (write only; should fail) + dirfd = -1; + dirfd = open(dir_template, O_WRONLY); + if (dirfd != -1) { + fputs( + "Opening a directory with O_WRONLY incorrectly succeeded\n", + stderr + ); + goto clean_symlink; + } + + result = EXIT_SUCCESS; +clean_symlink: + unlink(sym_path); +clean_opened_file: + unlink(file_path); +clean_opened_dir: + rmdir(dir_template); +bye: + return result; +} diff --git a/tests/fcntl/posix_fallocate.c b/tests/fcntl/posix_fallocate.c new file mode 100644 index 0000000000..bb5fdded95 --- /dev/null +++ b/tests/fcntl/posix_fallocate.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +__attribute__((nonnull)) +int run_test( + int filefd, + const char error_msg[], + off_t offset, + off_t length, + int dirfd, + const char file_name[], + off_t expected_size +) { + // posix_fallocate does not set errno. + int result = posix_fallocate(filefd, offset, length); + if (result != 0) { + const char* error = strerror(result); + fprintf(stderr, "%s: %s\n", error_msg, error); + return -1; + } + + struct stat stat = {0}; + if (fstatat(dirfd, file_name, &stat, 0) == -1) { + perror("fstatat"); + return -1; + } + + if (stat.st_size != expected_size) { + fprintf( + stderr, + "Expected: %zd\nActual: %zd\n", + expected_size, + stat.st_size + ); + return -1; + } + + return 0; +} + +int main(void) { + int result = EXIT_FAILURE; + + char dir_template[] = "/tmp/pfalloctest.XXXXXX"; + size_t dlen = sizeof(dir_template) - 1; + if (!mkdtemp(dir_template)) { + perror("mkdtemp"); + goto bye; + } + int dirfd = open(dir_template, O_DIRECTORY); + if (dirfd == -1) { + perror("open"); + goto rmtemp; + } + + // Failure conditions. + // These are mostly a gut check for whatever backing syscall is used + // in relibc. If it ever changes, we have to make sure the correct + // errors are returned. If they're not, we have to add checks to our + // relibc implementation. Linux's backing syscall (SYS_fallocate) + // already does these checks. + // + // Directory: + if (posix_fallocate(dirfd, 0, 1000) == 0) { + fputs("posix_fallocate succeeded on a directory\n", stderr); + goto rmtemp; + } + + // Socket: + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + perror("socket"); + goto rmtemp; + } + if (posix_fallocate(sock, 0, 1000) == 0) { + fputs("posix_fallocate succeeded on a socket\n", stderr); + close(sock); + goto rmtemp; + } + close(sock); + + // Pipe: + int pipefd[2] = {0}; + if (pipe(pipefd) == -1) { + perror("pipe"); + goto rmtemp; + } + if (posix_fallocate(pipefd[0], 0, 1000) == 0) { + fputs("posix_fallocate succeeded on a pipe\n", stderr); + close(pipefd[0]); + close(pipefd[1]); + goto rmtemp; + } + close(pipefd[0]); + close(pipefd[1]); + + // Success conditions. + const char name[] = "commander_keen"; + char path[PATH_MAX] = {0}; + memcpy(path, dir_template, dlen); + path[dlen] = '/'; + memcpy(&path[dlen + 1], name, sizeof(name)); + + int file = open(path, O_CREAT | O_RDWR); + if (file == -1) { + perror("open"); + goto rmtemp; + } + + // Expand an empty file. + if ( + run_test( + file, + "posix_fallocate failed expanding an empty file", + 0, + 1000, + dirfd, + name, + 1000 + ) == -1 + ) { + fputs("FAILED: Expanding empty file test\n", stderr); + goto rmtempfile; + } + + // Don't shrink a file. + if ( + run_test( + file, + "posix_fallocate failed on already allocated file", + 0, + 1, + dirfd, + name, + 1000 + ) == -1 + ) { + fputs("FAILED: posix_fallocate should not shrink files\n", stderr); + goto rmtempfile; + } + + // Don't overwrite allocated byte ranges. + if (ftruncate(file, 0) == -1) { + perror("ftruncate"); + goto rmtempfile; + } + const char msg[] = "If you're reading this you must play Commander Keen"; + if (write(file, msg, sizeof(msg)) != sizeof(msg)) { + perror("write"); + goto rmtempfile; + } + + if ( + run_test( + file, + "posix_fallocate failed on an allocated range", + 0, + sizeof(msg), + dirfd, + name, + sizeof(msg) + ) == -1 + ) { + fputs( + "FAILED: posix_fallocate don't overwrite allocated ranges test\n", + stderr + ); + goto rmtempfile; + } + // And now check that the range wasn't overwritten. + char buf[sizeof(msg)] = {0}; + if (lseek(file, 0, SEEK_SET) == -1) { + perror("lseek"); + goto rmtempfile; + } + if (read(file, buf, sizeof(msg)) != sizeof(msg)) { + perror("read"); + goto rmtempfile; + } + if (strncmp(msg, buf, sizeof(msg)) != 0) { + fputs("FAILED: posix_fallocate overwrote/destroyed a file\n", stderr); + goto rmtempfile; + } + + // Offset + length that goes beyond file end expands file. + if ( + run_test( + file, + "posix_fallocate failed on file expansion test", + sizeof(msg), + 1000, + dirfd, + name, + sizeof(msg) + 1000 + ) == -1 + ) { + fputs("FAILED: posix_fallocate file expansion test\n", stderr); + goto rmtempfile; + } + + result = EXIT_SUCCESS; +rmtempfile: + close(file); + unlink(path); +rmtemp: + rmdir(dir_template); +bye: + return result; +} + diff --git a/tests/features.c b/tests/features.c new file mode 100644 index 0000000000..d33bda8429 --- /dev/null +++ b/tests/features.c @@ -0,0 +1,50 @@ +// These tests are primarily to ensure the macros compile without +// causing any funny business. +// TODO: does not compile with glibc + +#include +#include +#include +#include + +#ifndef __GLIBC__ +__deprecated +#endif +static void legacy(void) {} + +#ifndef __GLIBC__ +__deprecatedNote("Sometimes deletes user's home (oops); use foobar") +#endif +static void legacy_notes(void) {} + +#ifndef __GLIBC__ +__nodiscard +#endif +static uint8_t the_answer(void) { + return 42; +} + +// GCC bug +/* #pragma GCC diagnostic push */ +/* #pragma GCC diagnostic ignored "-Wattributes" */ +/* __noreturn */ +/* static void foobar(void) { */ + // The test suite isn't picking up noreturn in the headers for exit, abort + // Those functions (and this test) works fine in both Redox itself and Linux + // Using _Exit instead works for the tests in CI. Why? I dunno. +/* _Exit(0); */ +/* } */ +/* #pragma GCC diagnostic pop */ + +int main(void) { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + legacy(); + legacy_notes(); + #pragma GCC diagnostic pop + const int answer = the_answer(); + char buf[40] = {0}; + sprintf(buf, "Hey, -Werror, I'm using answer: %d\n", answer); + /* foobar(); */ + return 0; +} diff --git a/tests/fnmatch.c b/tests/fnmatch.c index a126cbd152..ab88cfd3ad 100644 --- a/tests/fnmatch.c +++ b/tests/fnmatch.c @@ -44,4 +44,5 @@ int main(void) { test("[a!][a!]", "ab", 0); test("hello[/+]world", "hello/world", FNM_PATHNAME); test("hello world", "HELLO WORLD", 0); + test("********************a", "xxxxxxxxxxxxxxxxxxxxb", 0); } diff --git a/tests/futimens.c b/tests/futimens.c index 78e7f4c96b..20b20ab15b 100644 --- a/tests/futimens.c +++ b/tests/futimens.c @@ -6,20 +6,46 @@ #include #include #include +#include #include -int main(int argc, char** argv) { +int check_mtime(char *path, int expected_sec, int expected_nsec, int err_gap) { + // Checks whether the moditication time of the file located at *path* match the provided times. + // When err_gap is set, only checks for a match on sec with a margin of error of +/- err_gap. + struct stat sb; + if (stat(path, &sb) != 0) { + fprintf(stderr, "stat: %s\n", strerror(errno)); + return -1; + } + if (err_gap > 0) { + if (sb.st_mtim.tv_sec < expected_sec + err_gap && sb.st_mtim.tv_sec > expected_sec - err_gap) { + return 0; + } + } else { + if (sb.st_mtim.tv_sec == expected_sec && sb.st_mtim.tv_nsec == expected_nsec) { + return 0; + } + } + fprintf(stderr, "Wrong modified time: %d.%d\n", sb.st_mtim.tv_sec, sb.st_mtim.tv_nsec); + return -1; +} + + +int main(void) { char temp[] = "/tmp/stattest-XXXXXX"; const char file[] = "/mkfifo_fifo"; int len = sizeof(temp) + sizeof(int); - char* path = malloc(len * sizeof(char)); + char* path = calloc(len, sizeof(char)); if (path == NULL) { fprintf(stderr, "Could not allocate: %s\n", strerror(errno)); exit(1); } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" strncat(path, mktemp(temp), sizeof(temp)); + #pragma GCC diagnostic pop strncat(path, file, sizeof(file)); if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); @@ -47,21 +73,28 @@ int main(int argc, char** argv) { fprintf(stderr, "futimens: %s\n", strerror(errno)); exit(1); } + if (check_mtime(path, 20, 0, 0) != 0) { + exit(1); + } + // Access times are not flushed to disk, so atime checks can't be (currently) performed - struct stat sb; - if (stat(path, &sb) != 0) { - fprintf(stderr, "stat: %s\n", strerror(errno)); + const struct timespec omit_times[] = { { 25, UTIME_OMIT }, { 12, UTIME_OMIT } }; + if (futimens(fd, omit_times) == -1) { + fprintf(stderr, "futimens: %s\n", strerror(errno)); exit(1); } - if (sb.st_mtim.tv_sec != 20 || sb.st_mtim.tv_nsec != 0) { - fprintf(stderr, "Wrong modified time: %d.%d\n", sb.st_mtim.tv_sec, sb.st_mtim.tv_nsec); + if (check_mtime(path, 20, 0, 0) != 0) { + exit(1); + } + + const struct timespec now_times[] = { { 25, UTIME_NOW }, { 12, UTIME_NOW } }; + if (futimens(fd, now_times) == -1) { + fprintf(stderr, "futimens: %s\n", strerror(errno)); + exit(1); + } + int now_ts = time(NULL); + if (check_mtime(path, now_ts, 0, 1) != 0) { exit(1); } - // Access times are not flushed to disk, so this check can't be (currently) performed - /* - * if (sb.st_atim.tv_sec != 10 || sb.st_atim.tv_nsec != 0) { - * fprintf(stderr, "Wrong accessed time: %d.%d\n", sb.st_atim.tv_sec, sb.st_atim.tv_nsec); - * exit(1); - * } - */ } + diff --git a/tests/glob.c b/tests/glob.c new file mode 100644 index 0000000000..b7e545dd6e --- /dev/null +++ b/tests/glob.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include + + +typedef int (*err_func_t)(const char *epath, int eerrno); + + +static char *gl_offs_test_string = ""; + + +int glob_err(const char *epath, int eerrno) { + printf("glob_err(\"%s\", \"%s\") -> %d\n", epath, strerror(eerrno), 0); + return 0; +} + +void glob_test(const char *pattern, int flags, err_func_t errfunc, glob_t *pglob) { + printf("Pattern = %s\n", pattern); + + int retval = glob(pattern, flags, errfunc, pglob); + + if (retval != 0) { + if (retval == GLOB_ABORTED) { + puts("(ABORTED)\n"); + } + else if (retval == GLOB_NOMATCH) { + puts("(NOMATCH)\n"); + } + else if (retval == GLOB_NOSPACE) { + puts("(NOSPACE)\n"); + } + else { + printf(" (Unknown retval %d!)\n", retval); + } + return; + } + + size_t gl_offs = flags & GLOB_DOOFFS ? pglob->gl_offs : 0; + + printf("(Matched %lu)", pglob->gl_pathc); + + if (flags & GLOB_DOOFFS) { + printf(" (with %lu gl_offs)\n", gl_offs); + } + else { + puts(""); + } + + for(unsigned int i = 0; i < (gl_offs + pglob->gl_pathc); i++) { + if (!pglob->gl_pathv[i]) { + printf("%d - NULL\n", i); + } + else { + printf("%d - %s\n", i, pglob->gl_pathv[i]); + } + } + printf("%s", "\n"); +} + +int main(void) { + glob_t pglob = {0}; + + glob_test("eample_dir/*", 0, NULL, &pglob); + globfree(&pglob); + + glob_test("eample_dir/*", GLOB_ERR, glob_err, &pglob); + globfree(&pglob); + + glob_test("./example_dir/*", 0, glob_err, &pglob); + globfree(&pglob); + + glob_test("example_dir/*never*", 0, glob_err, &pglob); + glob_test("example_dir/?-and*", GLOB_APPEND, glob_err, &pglob); + globfree(&pglob); + + pglob.gl_offs = 4; + glob_test("example_dir/*never*", GLOB_DOOFFS, glob_err, &pglob); + pglob.gl_pathv[0] = gl_offs_test_string; + glob_test("example_dir/?-and*", GLOB_DOOFFS | GLOB_APPEND, glob_err, &pglob); + globfree(&pglob); + + glob_test("example_dir", GLOB_MARK, glob_err, &pglob); + globfree(&pglob); +} diff --git a/tests/grp/getgrgid_r.c b/tests/grp/getgrgid_r.c new file mode 100644 index 0000000000..ed2fea5dcc --- /dev/null +++ b/tests/grp/getgrgid_r.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +void test_getgrgid(gid_t gid) { + struct group *out = getgrgid(gid); + + if (out == NULL) { + printf("Did not find a group %d\n", gid); + return; + } + + printf("getgrgid\n"); + + printf(" %d = %s, GID: %d\n", gid, out->gr_name, out->gr_gid); +} + +void test_getgrgid_r(gid_t gid) { + char buf[100]; + + struct group grp; + struct group *tmp; + + int status = getgrgid_r(gid, &grp, buf, sizeof(buf), &tmp); + + if (tmp == NULL) { + const char *reason = status != 0 ? strerror(status) : "(not found)"; + printf("Did not find a group %d: %s\n", gid, reason); + return; + } + + printf("getgrgid_r\n"); + + printf(" %d = %s, GID: %d\n", gid, grp.gr_name, grp.gr_gid); +} + +int main(void) { + test_getgrgid(1050); + test_getgrgid_r(1050); +} diff --git a/tests/grp/getgrnam_r.c b/tests/grp/getgrnam_r.c new file mode 100644 index 0000000000..694d4fe1c9 --- /dev/null +++ b/tests/grp/getgrnam_r.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +void test_getgrnam(const char *gr_name) { + struct group* out = getgrnam(gr_name); + + if (out == NULL) { + printf("Did not find a group '%s'\n", gr_name); + return; + } + + printf("getgrnam\n"); + + printf(" '%s' = %d\n", gr_name, out->gr_gid); +} + +void test_getgrnam_r(const char *gr_name) { + char buf[100]; + + struct group grp; + struct group* out = &grp; + + int status = getgrnam_r(gr_name, &grp, buf, sizeof(buf), &out); + + if (out == NULL) { + const char *reason = (status != 0) ? strerror(status) : "(not found)"; + printf("Did not find a group %s: %s\n", gr_name, reason); + return; + } + + printf("getgrnam_r\n"); + + printf(" '%s' = %d\n", gr_name, out->gr_gid); +} + +int main(void) { + test_getgrnam("lcake"); + test_getgrnam_r("lcake"); +} diff --git a/tests/grp/getgrouplist.c b/tests/grp/getgrouplist.c new file mode 100644 index 0000000000..61e11d1a9a --- /dev/null +++ b/tests/grp/getgrouplist.c @@ -0,0 +1,15 @@ +#include +#include + +int main(void) { + gid_t groups[20]; + int ngroup = 20; + int num_groups = getgrouplist("user", 1000, groups, &ngroup); + + printf("Num Groups: %d\n", num_groups); + + for (int i = 0; i < num_groups; i++) + printf("i: %d\n", groups[i]); + + return 0; +} \ No newline at end of file diff --git a/tests/grp/getgroups.c b/tests/grp/getgroups.c new file mode 100644 index 0000000000..7b06f293e2 --- /dev/null +++ b/tests/grp/getgroups.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +int main() { + gid_t primary_gid = getegid(); + struct group *pg = getgrgid(primary_gid); + + printf("getegid: %u (%s)\n", primary_gid, pg ? pg->gr_name : "?"); + + int count = getgroups(0, NULL); + gid_t *list = malloc(sizeof(gid_t) * count); + getgroups(count, list); + + printf("getgroups: %d\n", count); + for (int i = 0; i < count; i++) { + struct group *sg = getgrgid(list[i]); + + printf(" - %u (%s)\n", list[i], sg ? sg->gr_name : "?"); + } + + free(list); + return 0; +} \ No newline at end of file diff --git a/tests/grp/gr_iter.c b/tests/grp/gr_iter.c new file mode 100644 index 0000000000..bf3e2e5090 --- /dev/null +++ b/tests/grp/gr_iter.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) { + puts("getgrent\n"); + + for (struct group* grp = getgrent(); grp != NULL; grp = getgrent()) + printf(" %s = %d\n", grp->gr_name, grp->gr_gid); +} diff --git a/tests/includes.c b/tests/includes.c new file mode 100644 index 0000000000..014235d740 --- /dev/null +++ b/tests/includes.c @@ -0,0 +1,91 @@ +// This test file exist only to check dependency graph, using `redoxer cc includes.c -H` + +// generate: find src/header -mindepth 1 -maxdepth 1 -type d -not -name "_*" -printf "#include <%f.h>\n" | sed -e 's|_|/|g' | sort + +// TODO: No include guard in these headers, probably expected? +// #include +// #include +// #include + +#include +#include +#ifndef __GLIBC__ +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { +} diff --git a/tests/iso646.c b/tests/iso646.c new file mode 100644 index 0000000000..82f544807f --- /dev/null +++ b/tests/iso646.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#include "test_helpers.h" + +int main() { + uint8_t a_bits = 0xa; // 0b0000_1010 + uint8_t b_bits = 0xc; // 0b0000_1100 + + uint8_t c_bits; + int c_bool; + + printf("0 and 0: %d\n", 0 and 0); + printf("1 and 0: %d\n", 1 and 0); + printf("0 and 1: %d\n", 0 and 1); + printf("1 and 1: %d\n", 1 and 1); + + c_bool = 0; + c_bool and_eq 0; + printf("0 and_eq 0: %d\n", c_bool); + c_bool = 1; + c_bool and_eq 0; + printf("1 and_eq 0: %d\n", c_bool); + c_bool = 0; + c_bool and_eq 1; + printf("0 and_eq 1: %d\n", c_bool); + c_bool = 1; + c_bool and_eq 1; + printf("1 and_eq 1: %d\n", c_bool); + + c_bits = a_bits bitand b_bits; + printf("a bitand b: %d\n", c_bits); + + c_bits = a_bits bitor b_bits; + printf("a bitor b: %d\n", c_bits); + + c_bits = compl a_bits; + printf("compl a: %d\n", c_bits); + + printf("not 0: %d\n", not 0); + printf("not 1: %d\n", not 1); + + c_bool = 0; + c_bool not_eq 0; + printf("0 not_eq 0: %d\n", c_bool); + c_bool = 1; + c_bool not_eq 0; + printf("1 not_eq 0: %d\n", c_bool); + c_bool = 0; + c_bool not_eq 1; + printf("0 not_eq 1: %d\n", c_bool); + c_bool = 1; + c_bool not_eq 1; + printf("1 not_eq 1: %d\n", c_bool); + + printf("0 or 0: %d\n", 0 or 0); + printf("1 or 0: %d\n", 1 or 0); + printf("0 or 1: %d\n", 0 or 1); + printf("1 or 1: %d\n", 1 or 1); + + c_bool = 0; + c_bool or_eq 0; + printf("0 or_eq 0: %d\n", c_bool); + c_bool = 1; + c_bool or_eq 0; + printf("1 or_eq 0: %d\n", c_bool); + c_bool = 0; + c_bool or_eq 1; + printf("0 or_eq 1: %d\n", c_bool); + c_bool = 1; + c_bool or_eq 1; + printf("1 or_eq 1: %d\n", c_bool); + + printf("0 xor 0: %d\n", 0 xor 0); + printf("1 xor 0: %d\n", 1 xor 0); + printf("0 xor 1: %d\n", 0 xor 1); + printf("1 xor 1: %d\n", 1 xor 1); + + c_bool = 0; + c_bool xor_eq 0; + printf("0 xor_eq 0: %d\n", c_bool); + c_bool = 1; + c_bool xor_eq 0; + printf("1 xor_eq 0: %d\n", c_bool); + c_bool = 0; + c_bool xor_eq 1; + printf("0 xor_eq 1: %d\n", c_bool); + c_bool = 1; + c_bool xor_eq 1; + printf("1 xor_eq 1: %d\n", c_bool); +} diff --git a/tests/kill-waitpid.c b/tests/kill-waitpid.c new file mode 100644 index 0000000000..000029127c --- /dev/null +++ b/tests/kill-waitpid.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) { + int err; + + int fds[2]; + err = pipe(fds); + ERROR_IF(pipe, err, == -1); + + int child = fork(); + ERROR_IF(fork, child, == -1); + + if (child == 0) { + // block forever + char buf[1]; + read(fds[0], buf, 1); + } + + err = kill(child, SIGTERM); + ERROR_IF(kill, err, == -1); + + int status; + err = waitpid(child, &status, 0); + ERROR_IF(waitpid, err, == -1); + printf("STATUS %d\n", status); + assert(WIFSIGNALED(status)); + assert(WTERMSIG(status) == SIGTERM); +} diff --git a/tests/libfoo.c b/tests/libfoo.c new file mode 100644 index 0000000000..b2106a4ec5 --- /dev/null +++ b/tests/libfoo.c @@ -0,0 +1,3 @@ +char *FOO = "foo"; + +void a() {} diff --git a/tests/libfoobar.c b/tests/libfoobar.c new file mode 100644 index 0000000000..67cef36df6 --- /dev/null +++ b/tests/libfoobar.c @@ -0,0 +1,4 @@ +char *BAR = "bar"; + +void a(); +void b() { a(); } diff --git a/tests/libgen.c b/tests/libgen.c index 0cdf8b8ade..8cec95a78b 100644 --- a/tests/libgen.c +++ b/tests/libgen.c @@ -111,7 +111,7 @@ int safe_strcmp(char *s1, char *s2) { int main(void) { int retval = EXIT_SUCCESS; - for(int i = 0; i < num_test_cases; ++i) { + for(size_t i = 0; i < num_test_cases; ++i) { struct test_case tc = test_cases[i]; CHECK_TEST(tc, dirname, retval); CHECK_TEST(tc, basename, retval); diff --git a/tests/limits.c b/tests/limits.c new file mode 100644 index 0000000000..f59585ff7c --- /dev/null +++ b/tests/limits.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +int main() { + char c_max = CHAR_MAX; + char c_min = CHAR_MIN; + signed char sc_max = SCHAR_MAX; + signed char sc_min = SCHAR_MIN; + short s_max = SHRT_MAX; + short s_min = SHRT_MIN; + int i_max = INT_MAX; + int i_min = INT_MIN; + long l_max = LONG_MAX; + long l_min = LONG_MIN; + long long ll_max = LLONG_MAX; + long long ll_min = LLONG_MIN; + ssize_t ss_max = SSIZE_MAX; + unsigned char uc_max = UCHAR_MAX; + unsigned short us_max = USHRT_MAX; + unsigned int ui_max = UINT_MAX; + unsigned long ul_max = ULONG_MAX; + unsigned long long ull_max = ULLONG_MAX; +#ifndef __GLIBC__ + int long_bit = LONG_BIT; + int word_bit = WORD_BIT; +#endif + + printf("CHAR : [%d, %d]\n", c_min, c_max); + printf("SCHAR : [%d, %d]\n", sc_min, sc_max); + printf("SHRT : [%d, %d]\n", s_min, s_max); + printf("INT : [%d, %d]\n", i_min, i_max); + printf("LONG : [%ld, %ld]\n", l_min, l_max); + printf("LLONG : [%lld, %lld]\n", ll_min, ll_max); + printf("SSIZE_MAX : %zd\n\n", ss_max); + printf("UCHAR_MAX : %u\n", uc_max); + printf("USHRT_MAX : %u\n", us_max); + printf("UINT_MAX : %u\n", ui_max); + printf("ULONG_MAX : %lu\n", ul_max); + printf("ULLONG_MAX : %llu\n\n", ull_max); +#ifndef __GLIBC__ + printf("LONG_BIT : %d\n", long_bit); + printf("WORD_BIT : %d\n", word_bit); +#endif + + return 0; +} diff --git a/tests/locale/duplocale.c b/tests/locale/duplocale.c new file mode 100644 index 0000000000..735fbbe353 --- /dev/null +++ b/tests/locale/duplocale.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +int main() { + struct lconv *lconv0 = localeconv(); + + locale_t locale0 = uselocale(NULL); + assert(locale0 == LC_GLOBAL_LOCALE); + + locale_t locale1 = uselocale(LC_GLOBAL_LOCALE); + assert(locale1 == LC_GLOBAL_LOCALE); + + struct lconv *lconv1 = localeconv(); + assert(lconv0 == lconv1); + + locale_t locale2 = newlocale(LC_ALL_MASK, "C", (locale_t)0); + assert(locale2 != LC_GLOBAL_LOCALE); + + struct lconv *lconv2 = localeconv(); + assert(lconv2 == lconv1); + + locale_t locale3 = duplocale(locale2); + assert(locale3 != locale2); + assert(locale3 != LC_GLOBAL_LOCALE); + + locale_t locale4 = uselocale(locale3); + assert(locale4 == locale1); + + struct lconv *lconv3 = localeconv(); + assert(lconv3 != lconv2); + + // should not crash + freelocale(locale3); + freelocale(locale2); + freelocale(locale1); + return 0; +} \ No newline at end of file diff --git a/tests/locale/newlocale.c b/tests/locale/newlocale.c new file mode 100644 index 0000000000..c9d16b2cc6 --- /dev/null +++ b/tests/locale/newlocale.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +int main() { + locale_t locale0 = newlocale(LC_ALL_MASK, "C", (locale_t)0); + assert(locale0 != (locale_t)0); + + locale_t locale1 = newlocale(LC_ALL_MASK, "non-existent-locale", (locale_t)0); + assert(locale1 == (locale_t)0); + + // TODO: locale files inside redoxer (linux and redox)? + // locale_t locale2 = newlocale(LC_ALL_MASK, "en_US", (locale_t)0); + // assert(locale2 != (locale_t)0); + + return 0; +} diff --git a/tests/locale.c b/tests/locale/setlocale.c similarity index 100% rename from tests/locale.c rename to tests/locale/setlocale.c diff --git a/tests/malloc/usable_size.c b/tests/malloc/usable_size.c new file mode 100644 index 0000000000..933ebf03f2 --- /dev/null +++ b/tests/malloc/usable_size.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +int main(void) { + size_t request_size = 27; + char *ptr = (char *)malloc(request_size); + + if (!ptr) { + fprintf(stderr, "Allocation failed\n"); + return 1; + } + + size_t actual_size = malloc_usable_size(ptr); + + printf("malloc: %zu bytes\n", request_size); + printf("malloc_usable_size: %zu bytes\n", actual_size); + + assert(actual_size >= request_size); + memset(ptr, 'A', actual_size); + ptr[actual_size - 1] = '\0'; + + assert(ptr[0] == 'A'); + assert(ptr[actual_size - 2] == 'A'); + assert(ptr[actual_size - 1] == '\0'); + free(ptr); + + return 0; +} diff --git a/tests/mkfifo.c b/tests/mkfifo.c index 6fb40ceef3..318419a507 100644 --- a/tests/mkfifo.c +++ b/tests/mkfifo.c @@ -7,18 +7,24 @@ #include #include -int main(int argc, char** argv) { +int main(void) { char temp[] = "/tmp/stattest-XXXXXX"; const char file[] = "/mkfifo_fifo"; int len = sizeof(temp) + sizeof(file); + char* path = malloc(len * sizeof(char)); + path[0] = '\0'; if (path == NULL) { fprintf(stderr, "Could not allocate: %s\n", strerror(errno)); exit(1); } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" path = strncat(path, mktemp(temp), sizeof(temp)); + #pragma GCC diagnostic pop path = strncat(path, file, sizeof(file)); if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); diff --git a/tests/mknod.c b/tests/mknod.c new file mode 100644 index 0000000000..50d533a2a2 --- /dev/null +++ b/tests/mknod.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(void) { + char temp[] = "/tmp/stattest-XXXXXX"; + const char file[] = "/mknod"; + int len = sizeof(temp) + sizeof(file); + + char* path = malloc(len * sizeof(char)); + path[0] = '\0'; + + if (path == NULL) { + fprintf(stderr, "Could not allocate: %s\n", strerror(errno)); + exit(1); + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + if(!mktemp(temp)) { + fprintf(stderr, "Unable to create a unique dir name %s: %s\n", temp, strerror(errno)); + exit(1); + } + #pragma GCC diagnostic pop + + path = strncat(path, temp, strlen(temp)); + path = strncat(path, file, strlen(file)); + if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { + fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); + exit(1); + } + if (mknod(path, S_IFREG, S_IRUSR) == -1) { + fprintf(stderr, "mknod %s: %s\n", path, strerror(errno)); + exit(1); + } + struct stat sb; + if (stat(path, &sb) != 0) { + fprintf(stderr, "stat: %s\n", strerror(errno)); + exit(1); + } + if (!(sb.st_mode & S_IFREG)) { + fprintf(stderr, "Expected S_IFREG flag to be set, got mode: %d\n", sb.st_mode); + exit(1); + } +} diff --git a/tests/mknodat.c b/tests/mknodat.c new file mode 100644 index 0000000000..ad2798624f --- /dev/null +++ b/tests/mknodat.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(void) { + char temp[] = "/tmp/stattest-XXXXXX"; + const char separator[] = "/"; + const char file[] = "mknod"; // relative + int len = sizeof(temp) + sizeof(file) + sizeof(separator); + + char* path = malloc(len * sizeof(char)); + path[0] = '\0'; + + if (path == NULL) { + fprintf(stderr, "Could not allocate: %s\n", strerror(errno)); + exit(1); + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + if(!mktemp(temp)) { + fprintf(stderr, "Unable to create a unique dir name %s: %s\n", temp, strerror(errno)); + exit(1); + } + #pragma GCC diagnostic pop + + if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { + fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); + exit(1); + } + + int dir_fd = open(temp, O_RDONLY); + if(dir_fd == -1) { + fprintf(stderr, "unable to open temp directory: %s with error: %s\n", temp, strerror(errno)); + exit(1); + } + + if (mknodat(dir_fd, file, S_IFREG, S_IRUSR) == -1) { + fprintf(stderr, "mknod %s: %s\n", path, strerror(errno)); + exit(1); + } + + path = strncat(path, temp, strlen(temp)); + path = strncat(path, separator, strlen(temp)); + path = strncat(path, file, strlen(file)); + + struct stat sb; + if (stat(path, &sb) != 0) { + fprintf(stderr, "stat for %s: %s\n", path, strerror(errno)); + exit(1); + } + + if (!(sb.st_mode & S_IFREG)) { + fprintf(stderr, "Expected S_IFREG flag to be set, got mode: %d\n", sb.st_mode); + exit(1); + } +} diff --git a/tests/net/if.c b/tests/net/if.c new file mode 100644 index 0000000000..5cce1465f4 --- /dev/null +++ b/tests/net/if.c @@ -0,0 +1,43 @@ +#include +#include + +#include "../test_helpers.h" + +#define assert_eq(value, expected) \ + { \ + if (value != expected) { \ + fprintf(stderr, "%s:%d: failed\n", __FILE__, __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } + +int main(void) { + const struct if_nameindex *list = if_nameindex(); + + // Currently always returning a stub + const struct if_nameindex *first = &(list[0]); + assert_eq(first->if_index, 1); + assert_eq(strcmp(first->if_name, "stub"), 0); + + // Last item with 0 values determines the end of the list + const struct if_nameindex *second = &(list[1]); + assert_eq(second->if_index, 0); + assert_eq(second->if_name, 0); + + unsigned idx; + idx = if_nametoindex(0); + assert_eq(idx, 0); + idx = if_nametoindex("any"); + assert_eq(idx, 0); + idx = if_nametoindex("stub"); + assert_eq(idx, 1); + + const char *name; + name = if_indextoname(0, 0); + assert_eq(name, 0); + + name = if_indextoname(1, 0); + assert_eq(strcmp(name, "stub"), 0); + + printf("OK\n"); +} diff --git a/tests/netdb/getaddrinfo.c b/tests/netdb/getaddrinfo.c index 9c4968d560..e1bb13562f 100644 --- a/tests/netdb/getaddrinfo.c +++ b/tests/netdb/getaddrinfo.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "test_helpers.h" diff --git a/tests/netdb/getaddrinfo_null.c b/tests/netdb/getaddrinfo_null.c new file mode 100644 index 0000000000..01b69cb860 --- /dev/null +++ b/tests/netdb/getaddrinfo_null.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int test_getaddrinfo(int ai_flags, size_t* ipv4_addr) { + struct addrinfo hints, *res; + int status; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = ai_flags; + + if ((status = getaddrinfo(NULL, "8080", &hints, &res)) != 0) { + return status; + } + + *ipv4_addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr; + freeaddrinfo(res); + return 0; +} + +int main() { + size_t ipv4_addr; + char addrstr[INET_ADDRSTRLEN]; + + int status = test_getaddrinfo(0, &ipv4_addr); + ERROR_IF(getaddrinfo, status, != 0); + ERROR_IF(getaddrinfo_ai_addr, ipv4_addr, == 0); + inet_ntop(AF_INET, &ipv4_addr, addrstr, INET_ADDRSTRLEN); + printf("local IPv4 address 1: %s\n", addrstr); + + status = test_getaddrinfo(AI_PASSIVE, &ipv4_addr); + ERROR_IF(getaddrinfo, status, != 0); + ERROR_IF(getaddrinfo_ai_addr, ipv4_addr, != 0); + inet_ntop(AF_INET, &ipv4_addr, addrstr, INET_ADDRSTRLEN); + printf("local IPv4 address 2: %s\n", addrstr); +} diff --git a/tests/netdb/netdb.c b/tests/netdb/netdb.c index d675fc0102..2ccf70ec00 100644 --- a/tests/netdb/netdb.c +++ b/tests/netdb/netdb.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,8 @@ #include "test_helpers.h" +#define ADDR_SIZE sizeof(uint8_t) * 4 + int error_count; static void output_servent (const char *call, struct servent *sptr) @@ -229,12 +232,38 @@ test_network (void) while (nptr != NULL); setnetent (0); } +static void +test_h_errno (void) { + const uint8_t addr[] = {0, 0, 0, 0}; + struct hostent *host = gethostbyaddr(addr, ADDR_SIZE, AF_INET); + if (host) { + ++error_count; + } + + int err = h_errno; + herror("Prefix test"); + if (err != HOST_NOT_FOUND) { + ++error_count; + } + + host = gethostbyname(""); + if (host) { + ++error_count; + } + + err = h_errno; + herror(""); + if (err != HOST_NOT_FOUND) { + ++error_count; + } +} static int do_test (void) { /* setdb ("db"); */ + test_h_errno (); test_hosts (); test_network (); test_protocols (); diff --git a/tests/psignal.c b/tests/psignal.c new file mode 100644 index 0000000000..375d1b0552 --- /dev/null +++ b/tests/psignal.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) { + puts("------psignal------"); + psignal(SIGUSR1, "a prefix"); + puts("------ end ------"); + puts("------psiginfo-----"); + siginfo_t info = { 0 }; + info.si_code = SI_USER; + info.si_pid = 42; + info.si_uid = 1337; + info.si_addr = (void *)0xdeadbeef; + info.si_value.sival_ptr = (void *)0xfedface; + psiginfo(&info, "another prefix"); + puts("------ -----"); +} diff --git a/tests/pthread/barrier.c b/tests/pthread/barrier.c new file mode 100644 index 0000000000..34be540015 --- /dev/null +++ b/tests/pthread/barrier.c @@ -0,0 +1,124 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include +#include + +// Same test logic as test_barrier in rustc/library/std/sync/barrier/tests.rs + +#define N 10 + +struct arg { + pthread_barrier_t *barrier; + volatile _Atomic(unsigned) *count; + bool is_leader; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + + int status = pthread_barrier_wait(arg->barrier); + + arg->is_leader = status == PTHREAD_BARRIER_SERIAL_THREAD; + + if (!arg->is_leader) + ERROR_IF(pthread_barrier_wait, status, != 0); + + // We can now modify the counter. + atomic_fetch_add_explicit(arg->count, 1, memory_order_relaxed); + + return NULL; +} + +int main(void) { + int status; + + pthread_barrier_t barrier; + + pthread_barrierattr_t attr; + status = pthread_barrierattr_init(&attr); + ERROR_IF(pthread_barrierattr_init, status, != 0); + + int pshared; + + // + // BARRIER ATTR + // + + status = pthread_barrierattr_getpshared(&attr, &pshared); + + // PTHREAD_PROCESS_PRIVATE is default according to POSIX. + assert(pshared == PTHREAD_PROCESS_PRIVATE); + + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_barrierattr_setpshared, status, != 0); + + status = pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + status = pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_barrierattr_setpshared, status, != 0); + + status = pthread_barrierattr_getpshared(&attr, &pshared); + assert(pshared == PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_barrierattr_getpshared, status, != 0); + + // + // BARRIER + // + + status = pthread_barrier_init(&barrier, &attr, N); + ERROR_IF(pthread_barrier_init, status, != 0); + + status = pthread_barrierattr_destroy(&attr); + ERROR_IF(pthread_barrierattr_destroy, status, != 0); + + // + // CREATE THREAD + // + + pthread_t threads[N - 1]; + struct arg args[N - 1]; + _Atomic(unsigned) count = false; + + for (size_t i = 0; i < N - 1; i++) { + args[i] = (struct arg){ .count = &count, .barrier = &barrier, .is_leader = false }; + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + // Must not be set before having waited for the barrier. This is part of the + // test. Normally spawned threads run before the scheduler returns to the + // parent thread, so it should at least partially verify that barriers work. + unsigned value = atomic_load_explicit(&count, memory_order_relaxed); + + UNEXP_IF(count_before_barrier_wait, value, > 0); + + status = pthread_barrier_wait(&barrier); + + bool leader_found = status == PTHREAD_BARRIER_SERIAL_THREAD; + + if (!leader_found) { + ERROR_IF(pthread_barrier_wait, status, != 0); + } + + for (size_t i = 0; i < N - 1; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + + // SAFETY: pthread_create and pthread_join are Acquire-Release + leader_found |= args[i].is_leader; + } + + assert(leader_found); + + status = pthread_barrier_destroy(&barrier); + ERROR_IF(pthread_barrier_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/cleanup.c b/tests/pthread/cleanup.c new file mode 100644 index 0000000000..015b56eda2 --- /dev/null +++ b/tests/pthread/cleanup.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include + +// TODO: glibc requires arg to be not const, but not relibc +#ifndef __GLIBC__ +const char *msg1 = "first"; +const char *msg2 = "second"; +const char *msg3 = "third"; +#else +char *msg1 = "first"; +char *msg2 = "second"; +char *msg3 = "third"; +#endif + +void cleanup1(void *arg) { + printf("Running %s cleanup callback\n", (const char *)arg); +} +void cleanup2(void *arg) { + fprintf(stderr, "Running %s cleanup callback, to stderr\n", (const char *)arg); +} +void cleanup3(void *arg) { + printf("Running final (%s) callback\n", (const char *)arg); +} + +void *routine(void *arg) { + assert(arg == NULL); + + puts("1"); + pthread_cleanup_push(cleanup1, msg1); + puts("2"); + pthread_cleanup_push(cleanup2, msg2); + puts("3"); + pthread_cleanup_push(cleanup3, msg3); + puts("4"); + pthread_cleanup_pop(true); + puts("5"); + //exit(EXIT_SUCCESS); + pthread_exit(NULL); + puts("6"); + pthread_cleanup_pop(true); + pthread_cleanup_pop(true); + return NULL; +} + +int main(void) { + int result; + + puts("Main thread started"); + pthread_t second_thread; + if ((result = pthread_create(&second_thread, NULL, routine, NULL)) != 0) { + fprintf(stderr, "thread creation failed: %s\n", strerror(result)); + return EXIT_FAILURE; + } + if ((result = pthread_join(second_thread, NULL)) != 0) { + fprintf(stderr, "failed to join thread: %s\n", strerror(result)); + return EXIT_FAILURE; + } + puts("Main thread about to exit"); + return EXIT_SUCCESS; +} diff --git a/tests/pthread/common.h b/tests/pthread/common.h new file mode 100644 index 0000000000..2c2dbc305a --- /dev/null +++ b/tests/pthread/common.h @@ -0,0 +1,24 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include +#include + +int fail(int status, const char *reason) { + fprintf(stderr, "%s failed: %s\n", reason, strerror(status)); + + return EXIT_FAILURE; +} + +uintptr_t black_box_uintptr_t(uintptr_t arg) { + // Rust implements this by putting the value behind a volatile pointer and then simply loading it. + + uintptr_t arg_slot = arg; + volatile uintptr_t *ptr = &arg_slot; + + return *ptr; +} + +#endif // _COMMON_H diff --git a/tests/pthread/customstack.c b/tests/pthread/customstack.c new file mode 100644 index 0000000000..ebbc5eed10 --- /dev/null +++ b/tests/pthread/customstack.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define STACKSIZE 128 * 1024 + +struct retval { + uintptr_t some_stack_pointer; +}; +struct arg { + int status; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + + struct retval *retval = malloc(sizeof(struct retval)); + + if (retval == NULL) { + arg->status = ENOMEM; + return NULL; + } + + char some_array[256] = { 0 }; + retval->some_stack_pointer = black_box_uintptr_t((uintptr_t)some_array); + + return retval; +} + +int main(void) { + int status; + static char stack[STACKSIZE]; + + pthread_attr_t attr; + if ((status = pthread_attr_init(&attr)) != 0) { + return fail(status, "attr init"); + } + if ((status = pthread_attr_setstack(&attr, stack, STACKSIZE)) != 0) { + return fail(status, "attr setstack"); + } + + void *stack_again; + size_t stacksize_again; + + if ((status = pthread_attr_getstack(&attr, &stack_again, &stacksize_again)) != 0) { + return fail(status, "attr getstack"); + } + + assert(stack_again == stack); + assert(stacksize_again == STACKSIZE); + + pthread_t thread; + + if ((status = pthread_create(&thread, &attr, routine, NULL)) != 0) { + return fail(status, "pthread create"); + } + + if ((status = pthread_attr_destroy(&attr)) != 0) { + return fail(status, "attr destroy"); + } + + void *retval_raw; + + if ((status = pthread_join(thread, &retval_raw)) != 0) { + return fail(status, "pthread join"); + } + + struct retval *retval = retval_raw; + + assert(retval->some_stack_pointer >= (uintptr_t)stack); + assert(retval->some_stack_pointer < ((uintptr_t)stack) + STACKSIZE); + + free(retval); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/exit.c b/tests/pthread/exit.c new file mode 100644 index 0000000000..5538a0f593 --- /dev/null +++ b/tests/pthread/exit.c @@ -0,0 +1,31 @@ +#include +#include +#include + +#include + +#include "common.h" + +void *routine(void *arg) { + assert(arg == NULL); + + usleep(100000); + + puts("Thread succeeded"); + + return NULL; +} + +int main(void) { + int status; + pthread_t thread; + + if ((status = pthread_create(&thread, NULL, routine, NULL)) != 0) { + return fail(status, "failed to create thread"); + } + if ((status = pthread_detach(thread)) != 0) { + return fail(status, "failed to detach thread"); + } + + pthread_exit(NULL); +} diff --git a/tests/pthread/extjoin.c b/tests/pthread/extjoin.c new file mode 100644 index 0000000000..da19101075 --- /dev/null +++ b/tests/pthread/extjoin.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +struct arg2 { + int status; + pthread_t t1; +}; + +void *routine1(void *arg) { + assert(arg == NULL); + puts("Thread 1 spawned, waiting 1s."); + usleep(100000); + puts("Thread 1 finished."); + return strdup("message from thread 1"); +} +void *routine2(void *arg_raw) { + struct arg2 *arg = arg_raw; + + puts("Thread 2 spawned, awaiting thread 1."); + + void *retval_raw; + int status; + + if ((status = pthread_join(arg->t1, &retval_raw)) != 0) { + arg->status = fail(status, "t1 join from thread 2"); + return NULL; + } + char *retval = retval_raw; + + assert(strcmp(retval, "message from thread 1") == 0); + + free(retval); + + return NULL; +} + +int main(void) { + pthread_t t1; + pthread_t t2; + + int status; + + puts("Main thread."); + + if ((status = pthread_create(&t1, NULL, routine1, NULL)) != 0) { + return fail(status, "t1 create"); + } + + puts("Created thread 1."); + + struct arg2 arg = { .status = 0, .t1 = t1 }; + + if ((status = pthread_create(&t2, NULL, routine2, &arg)) != 0) { + return fail(status, "t2 create"); + } + + if ((status = pthread_join(t2, NULL)) != 0) { + return fail(status, "t2 join"); + } + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/main.c b/tests/pthread/main.c new file mode 100644 index 0000000000..186e306c2b --- /dev/null +++ b/tests/pthread/main.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +void *thread_main(void *arg) { + puts("Thread main"); + + assert(arg == NULL); + + return NULL; +} + +int main(void) { + int status; + + puts("Start, sleeping 1 second"); + usleep(100000); + pthread_t thread; + void *arg = NULL; + if ((status = pthread_create(&thread, NULL, thread_main, arg)) != 0) { + return fail(status, "create thread"); + } + puts("Started"); + void *retval; + if ((status = pthread_join(thread, &retval)) != 0) { + return fail(status, "join thread"); + } + assert(retval == NULL); + puts("Joined"); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/mutex_recursive.c b/tests/pthread/mutex_recursive.c new file mode 100644 index 0000000000..2e22a30f58 --- /dev/null +++ b/tests/pthread/mutex_recursive.c @@ -0,0 +1,108 @@ +#include +#include +#include + +#include "../test_helpers.h" + +#define N 10 +#define M 10000 + +struct arg { + pthread_mutex_t *mutex; + unsigned *protected; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + int status; + + unsigned depth = 0; + unsigned i = 0; + + while (i < M) { + // Bad random distribution, but should work. + bool lock_again = (depth == 0) || (random_bool() && random_bool()); + + if (lock_again) { + status = pthread_mutex_lock(arg->mutex); + ERROR_IF(pthread_mutex_lock, status, != 0); + + depth += 1; + } else { + status = pthread_mutex_unlock(arg->mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + depth -= 1; + } + if (depth == 0) { + continue; + } + + unsigned value = *arg->protected; + *arg->protected = value + 1; + + i += 1; + } + while (depth > 0) { + status = pthread_mutex_unlock(arg->mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + depth--; + } + + return NULL; +} + +int main(void) { + int status; + pthread_mutex_t mutex; + pthread_mutexattr_t attr; + + status = pthread_mutexattr_init(&attr); + ERROR_IF(pthread_mutexattr_init, status, != 0); + + status = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + ERROR_IF(pthread_mutexattr_settype, status, != 0); + + status = pthread_mutex_init(&mutex, &attr); + ERROR_IF(pthread_mutex_init, status, != 0); + + status = pthread_mutexattr_destroy(&attr); + ERROR_IF(pthread_mutexattr_destroy, status, != 0); + + status = pthread_mutex_lock(&mutex); + ERROR_IF(pthread_mutex_lock, status, != 0); + + status = pthread_mutex_trylock(&mutex); + ERROR_IF(pthread_mutex_trylock, status, != 0); + + status = pthread_mutex_unlock(&mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + // Still locked with count = 1. + + pthread_t threads[N]; + struct arg args[N]; + unsigned protected = 0; + + for (size_t i = 0; i < N; i++) { + args[i] = (struct arg){ .mutex = &mutex, .protected = &protected }; + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + protected = 1; + + status = pthread_mutex_unlock(&mutex); + ERROR_IF(pthread_mutex_unlock, status, != 0); + + for (size_t i = 0; i < N; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + } + + status = pthread_mutex_destroy(&mutex); + ERROR_IF(pthread_mutex_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/once.c b/tests/pthread/once.c new file mode 100644 index 0000000000..40a9c5ce20 --- /dev/null +++ b/tests/pthread/once.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "../test_helpers.h" + +_Thread_local size_t this_thread_count = 0; + +#define COUNT 1024 +#define THREADS 4 + +static void constructor(void) { + this_thread_count++; +} + +struct arg { + pthread_barrier_t *barrier; + pthread_once_t *onces; + size_t count; + size_t index; +}; + +void *routine(void *arg_raw) { + int status; + struct arg *arg = arg_raw; + + if (arg->index == THREADS - 1) { + printf("main thread at %zu\n", arg->index); + } else { + printf("spawned %zu\n", arg->index); + } + + status = pthread_barrier_wait(arg->barrier); + + printf("waited %zu leader=%s\n", arg->index, (status == PTHREAD_BARRIER_SERIAL_THREAD) ? "true" : "false"); + + if (status != PTHREAD_BARRIER_SERIAL_THREAD) { + ERROR_IF(pthread_barrier_wait, status, != 0); + return NULL; + } + + for (size_t i = 0; i < COUNT; i++) { + status = pthread_once(&arg->onces[i], constructor); + ERROR_IF(pthread_once, status, != 0); + } + + arg->count = this_thread_count; + + return NULL; +} + +int main(void) { + // TODO: Better test to simulate contention? + + int status; + + pthread_once_t onces[COUNT]; + + for (size_t i = 0; i < COUNT; i++) { + onces[i] = PTHREAD_ONCE_INIT; + } + + pthread_barrier_t barrier; + + printf("Barrier at %p, onces at %p\n", &barrier, onces); + + status = pthread_barrier_init(&barrier, NULL, THREADS); + ERROR_IF(pthread_barrier_init, status, != 0); + + pthread_t threads[THREADS]; + struct arg args[THREADS]; + + for (size_t i = 0; i < THREADS; i++) { + args[i] = (struct arg){ .barrier = &barrier, .onces = onces, .count = 0, .index = i }; + printf("spawning %zu\n", i); + + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + size_t total_count = 0; + + for (size_t i = 0; i < THREADS; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + + total_count += args[i].count; + } + + status = pthread_barrier_destroy(&barrier); + ERROR_IF(pthread_barrier_destroy, status, != 0); + + assert(total_count == COUNT); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/rwlock_randtest.c b/tests/pthread/rwlock_randtest.c new file mode 100644 index 0000000000..e263efd472 --- /dev/null +++ b/tests/pthread/rwlock_randtest.c @@ -0,0 +1,64 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include + +// Same test logic as frob in rustc/library/std/sync/rwlock/tests.rs + +#define N 10 +//#define M 1000 +#define M 100000 + +struct arg { + pthread_rwlock_t *rwlock; +}; + +void *routine(void *arg_raw) { + struct arg *arg = arg_raw; + int status; + + for (uint64_t i = 0; i < M; i++) { + if (random_bool()) { + status = pthread_rwlock_wrlock(arg->rwlock); + ERROR_IF(pthread_rwlock_wrlock, status, != 0); + } else { + status = pthread_rwlock_rdlock(arg->rwlock); + ERROR_IF(pthread_rwlock_rdlock, status, != 0); + } + status = pthread_rwlock_unlock(arg->rwlock); + ERROR_IF(pthread_rwlock_unlock, status, != 0); + } + + return NULL; +} + +int main(void) { + int status; + + pthread_rwlock_t rwlock; + + status = pthread_rwlock_init(&rwlock, NULL); + ERROR_IF(pthread_rwlock_init, status, != 0); + + pthread_t threads[N]; + struct arg args[N]; + + for (size_t i = 0; i < N; i++) { + args[i] = (struct arg){ .rwlock = &rwlock }; + + status = pthread_create(&threads[i], NULL, routine, &args[i]); + ERROR_IF(pthread_create, status, != 0); + } + + for (size_t i = 0; i < N; i++) { + status = pthread_join(threads[i], NULL); + ERROR_IF(pthread_join, status, != 0); + } + + status = pthread_rwlock_destroy(&rwlock); + ERROR_IF(pthread_rwlock_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/rwlock_trylock.c b/tests/pthread/rwlock_trylock.c new file mode 100644 index 0000000000..8a8274e53f --- /dev/null +++ b/tests/pthread/rwlock_trylock.c @@ -0,0 +1,58 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include +#include + +// Adapted from test_rwlock_try_write in rustc/library/std/sync/rwlock/tests.rs + +int main(void) { + int status; + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + + pthread_rwlockattr_t attr; + status = pthread_rwlockattr_init(&attr); + ERROR_IF(pthread_rwlockattr_init, status, != 0); + + // Call setpshared twice to check both constants work. + status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + ERROR_IF(pthread_rwlockattr_setpshared, status, != 0); + status = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + ERROR_IF(pthread_rwlockattr_setpshared, status, != 0); + + status = pthread_rwlock_init(&rwlock, &attr); + ERROR_IF(pthread_rwlock_init, status, != 0); + + status = pthread_rwlockattr_destroy(&attr); + ERROR_IF(pthread_rwlockattr_destroy, status, != 0); + + status = pthread_rwlock_rdlock(&rwlock); + ERROR_IF(pthread_rwlock_rdlock, status, != 0); + + status = pthread_rwlock_trywrlock(&rwlock); + UNEXP_IF(pthread_rwlock_trywrlock, status, != EBUSY); + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 200 * 1000000; + status = pthread_rwlock_timedwrlock(&rwlock, &ts); + UNEXP_IF(pthread_rwlock_timedwrlock, status, != ETIMEDOUT); + + status = pthread_rwlock_unlock(&rwlock); + ERROR_IF(pthread_rwlock_unlock, status, != 0); + + status = pthread_rwlock_trywrlock(&rwlock); + ERROR_IF(pthread_rwlock_rdlock, status, != 0); + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 200 * 1000000; + status = pthread_rwlock_timedrdlock(&rwlock, &ts); + UNEXP_IF(pthread_rwlock_timedrdlock, status, != ETIMEDOUT); + + status = pthread_rwlock_destroy(&rwlock); + ERROR_IF(pthread_rwlock_destroy, status, != 0); + + return 0; +} diff --git a/tests/pthread/thread_fork.c b/tests/pthread/thread_fork.c new file mode 100644 index 0000000000..e4b0ee9c2e --- /dev/null +++ b/tests/pthread/thread_fork.c @@ -0,0 +1,51 @@ +/// test fork inside thread + +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +#define NUM_THREADS 4 +#define ITERATIONS 3 + + +void* thread_func(void* arg) { + long tid = (long)arg; + + for (int i = 0; i < ITERATIONS; i++) { + printf("thread %ld loop %i\n", tid, i); + + pid_t pid = fork(); + ERROR_IF(fork, pid, < 0); + + if (pid == 0) { + _exit(0); + } else { + waitpid(pid, NULL, 0); + } + + usleep(1000); + } + + return NULL; +} + +int main() { + pthread_t threads[NUM_THREADS]; + int status; + + for (long i = 0; i < NUM_THREADS; i++) { + status = pthread_create(&threads[i], NULL, thread_func, (void*)i); + ERROR_IF(pthread_create, status, != 0); + } + + printf("joining threads\n"); + for (int i = 0; i < NUM_THREADS; i++) { + pthread_join(threads[i], NULL); + } + + return 0; +} diff --git a/tests/pthread/timedwait.c b/tests/pthread/timedwait.c new file mode 100644 index 0000000000..d0d909b67e --- /dev/null +++ b/tests/pthread/timedwait.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +pthread_mutex_t lock; +pthread_cond_t cond; +struct timespec start_time; +int ready = 0; + +struct timespec get_timeout(long msec) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += msec * 1000000; + return ts; +} + +void print_timed(char* msg) { + struct timespec end_time; + clock_gettime(CLOCK_MONOTONIC, &end_time); + printf("[%.3f]: ", (end_time.tv_nsec - start_time.tv_nsec) / 1000000000.0 + + (end_time.tv_sec - start_time.tv_sec)); + printf("%s\n", msg); + fflush(NULL); +} + +void* signaler_thread(void* arg) { + (void)arg; + + usleep(150000); // 150ms + ready = 1; + print_timed("signaler_thread"); + pthread_cond_signal(&cond); + return NULL; +} + +void test_timeout_case() { + pthread_mutex_lock(&lock); + struct timespec ts = get_timeout(500); + print_timed("test_timeout_case start"); + int rc = pthread_cond_timedwait(&cond, &lock, &ts); + UNEXP_IF(pthread_cond_timedwait, rc, != ETIMEDOUT); + print_timed("test_timeout_case end"); + pthread_mutex_unlock(&lock); +} + +void test_success_case() { + ready = 0; + pthread_t thread; + pthread_create(&thread, NULL, signaler_thread, NULL); + pthread_mutex_lock(&lock); + struct timespec ts = get_timeout(50000); + print_timed("test_success_case start"); + while (!ready) { + print_timed("test_success_case waiting"); + int rc = pthread_cond_timedwait(&cond, &lock, &ts); + UNEXP_IF(pthread_cond_timedwait, rc, != 0); + } + print_timed("test_success_case end"); + pthread_mutex_unlock(&lock); + pthread_join(thread, NULL); +} + +int main() { + clock_gettime(CLOCK_MONOTONIC, &start_time); + if (pthread_mutex_init(&lock, NULL) != 0) { + perror("mutex init failed"); + return 1; + } + if (pthread_cond_init(&cond, NULL) != 0) { + perror("cond init failed"); + return 1; + } + test_timeout_case(); + // TODO: "rlct_clone not implemented for aarch64 yet" +#if defined(__linux__) && defined(__aarch64__) + printf("test_success_case skipped"); +#else + test_success_case(); +#endif + return 0; +} diff --git a/tests/pthread/timeout.c b/tests/pthread/timeout.c new file mode 100644 index 0000000000..eea85e1380 --- /dev/null +++ b/tests/pthread/timeout.c @@ -0,0 +1,91 @@ +#include "../test_helpers.h" +#include "common.h" + +#include +#include +#include +#include +#include + +#include +#include + +struct arg { + int status; + bool completed; + pthread_barrier_t barrier; + pthread_mutex_t mutex; +}; + +void *routine(void *arg_raw) { + struct arg *arg = (struct arg *)arg_raw; + + int barrier_status = pthread_barrier_wait(&arg->barrier); + + if (barrier_status != 0 && barrier_status != PTHREAD_BARRIER_SERIAL_THREAD) { + arg->status = barrier_status; + fputs("failed to wait for barrier\n", stderr); + return NULL; + } + puts("thread waited"); + + struct timespec abstime; + if ((arg->status = clock_gettime(CLOCK_MONOTONIC, &abstime)) != 0) { + fputs("failed to get current time\n", stderr); + return NULL; + } + abstime.tv_sec += 1; + + if ((arg->status = pthread_mutex_timedlock(&arg->mutex, &abstime)) != ETIMEDOUT) { + fputs("failed to fail at locking mutex\n", stderr); + return NULL; + } + arg->status = 0; + + return NULL; +} + +int main(void) { + int status; + + struct arg arg; + arg.completed = false; + + status = pthread_barrier_init(&arg.barrier, NULL, 2); + ERROR_IF2(pthread_barrier_init, status, != 0); + + status = pthread_mutex_init(&arg.mutex, NULL); + ERROR_IF2(pthread_mutex_init, status, != 0); + + status = pthread_mutex_trylock(&arg.mutex); + ERROR_IF2(pthread_mutex_trylock, status, != 0); + + pthread_t thread; + status = pthread_create(&thread, NULL, routine, &arg); + ERROR_IF2(pthread_create, status, != 0); + + status = pthread_barrier_wait(&arg.barrier); + if (status != PTHREAD_BARRIER_SERIAL_THREAD) { + ERROR_IF2(pthread_barrier_wait, status, != 0); + } + puts("main waited"); + + status = pthread_join(thread, NULL); + ERROR_IF2(pthread_join, status, != 0); + + status = pthread_mutex_unlock(&arg.mutex); + ERROR_IF2(pthread_mutex_unlock, status, != 0); + + if (arg.status != 0) { + fprintf(stderr, "thread failed: %s\n", strerror(arg.status)); + return EXIT_FAILURE; + } + + status = pthread_mutex_destroy(&arg.mutex); + ERROR_IF2(pthread_mutex_destroy, status, != 0); + + status = pthread_barrier_destroy(&arg.barrier); + ERROR_IF2(pthread_barrier_destroy, status, != 0); + + return EXIT_SUCCESS; +} diff --git a/tests/pthread/tls.c b/tests/pthread/tls.c new file mode 100644 index 0000000000..21a4a614c8 --- /dev/null +++ b/tests/pthread/tls.c @@ -0,0 +1,99 @@ +#include +// PTHREAD_KEYS_MAX, PTHREAD_DESTRUCTOR_ITERATIONS +#include +#include +#include +#include +#include + +pthread_key_t key_a, key_b, key_c, key_d, key_e; +size_t total_destructor_runs = 0; + +void drop_key_a(void *val_a) { + assert(pthread_getspecific(key_a) == NULL && val_a == (void *)0xaaaa); + total_destructor_runs++; + + void *val_b = pthread_getspecific(key_b); + printf("drop_key_a(a=%p, b=%p)\n", val_a, val_b); + + if (total_destructor_runs <= 2) { + pthread_setspecific(key_a, val_a); + return; + } + + if (val_b != NULL) { + assert(val_b == (void *)0xbbbb); + } else { + pthread_setspecific(key_b, (void *)0xbbbb); + } +} + +void drop_key_b(void *val_b) { + assert(pthread_getspecific(key_b) == NULL && val_b == (void *)0xbbbb); + total_destructor_runs++; + + void *val_a = pthread_getspecific(key_a); + printf("drop_key_b(a=%p, b=%p)\n", val_a, val_b); + + if (total_destructor_runs <= 2) { + pthread_setspecific(key_b, val_b); + return; + } + + if (val_a != NULL) { + assert(val_a == (void *)0xaaaa); + } else { + pthread_setspecific(key_a, (void *)0xaaaa); + } +} + +void drop_unreachable(void *val_c) { + // Should never be called. + (void)val_c; + assert(false); +} + +void drop_key_d(void *val_d) { + assert(pthread_getspecific(key_d) == NULL && val_d == (void *)0xdddd); + // [`pthread_key_{create,setspecific,getspecific,delete}`] can still be used + // inside the destructor itself. + printf("drop_key_d(d=%p)\n", val_d); + pthread_key_t key_g; + assert(!pthread_key_create(&key_g, drop_unreachable)); + assert(!pthread_setspecific(key_g, &key_g)); + assert(pthread_getspecific(key_g) == &key_g); + assert(!pthread_key_delete(key_g)); +} + +void *test_tls_destructors(void *arg) { + (void)arg; + assert(!pthread_key_create(&key_a, drop_key_a)); + assert(!pthread_key_create(&key_b, drop_key_b)); + assert(!pthread_key_create(&key_c, drop_unreachable)); + assert(!pthread_key_create(&key_d, drop_key_d)); + assert(!pthread_key_create(&key_e, drop_unreachable)); + assert(!pthread_setspecific(key_a, (void *)0xaaaa)); + assert(!pthread_setspecific(key_b, (void *)0xbbbb)); + assert(!pthread_setspecific(key_c, (void *)0xcccc)); + assert(!pthread_setspecific(key_d, (void *)0xdddd)); + assert(!pthread_key_delete(key_c)); + return NULL; +} + +int main(void) { + pthread_key_t key_f; + assert(!pthread_key_create(&key_f, NULL)); + assert(!pthread_setspecific(key_f, &key_f)); + assert(pthread_getspecific(key_f) == &key_f); + assert(!pthread_key_delete(key_f)); + + pthread_t t1; + assert(!pthread_create(&t1, NULL, test_tls_destructors, NULL)); + assert(!pthread_join(t1, NULL)); + printf("total_destructor_iterations: %zu\n", total_destructor_runs); + // There are two destructors (i.e., `drop_key_{a, b}`), so the total number + // of iterations will be `2 * PTHREAD_DESTRUCTOR_ITERATIONS`. + assert((total_destructor_runs / 2) == PTHREAD_DESTRUCTOR_ITERATIONS); + + return EXIT_SUCCESS; +} diff --git a/tests/ptrace.c b/tests/ptrace.c index bb918d1112..23d3c833f1 100644 --- a/tests/ptrace.c +++ b/tests/ptrace.c @@ -10,12 +10,12 @@ #ifdef __linux__ -const int SYS_write = 1; +#define SYS_write 1 #endif #ifdef __redox__ -const int SYS_write = 0x21000004; +#define SYS_write 0x21000004 #endif @@ -26,7 +26,7 @@ int main() { if (pid == 0) { // Test behavior on Redox when TRACEME hasn't been activated // before waitpid is invoked! - sleep(1); + usleep(100000); int result = ptrace(PTRACE_TRACEME, 0, NULL, NULL); ERROR_IF(ptrace, result, == -1); @@ -48,17 +48,18 @@ int main() { int status; while (true) { - puts("----- Pre-syscall -----"); + // puts("----- Pre-syscall -----"); result = ptrace(PTRACE_SYSCALL, pid, NULL, NULL); ERROR_IF(ptrace, result, == -1); UNEXP_IF(ptrace, result, != 0); - puts("Wait..."); + // puts("Wait..."); result = waitpid(pid, &status, 0); ERROR_IF(waitpid, result, == -1); - if (WIFEXITED(status)) { break; } + if (WIFEXITED(status)) + break; struct user_regs_struct regs; - puts("Get regs"); + // puts("Get regs"); result = ptrace(PTRACE_GETREGS, pid, NULL, ®s); ERROR_IF(ptrace, result, == -1); @@ -69,14 +70,15 @@ int main() { ERROR_IF(ptrace, result, == -1); } - puts("Post-syscall"); + // puts("Post-syscall"); result = ptrace(PTRACE_SYSCALL, pid, NULL, NULL); ERROR_IF(ptrace, result, == -1); UNEXP_IF(ptrace, result, != 0); - puts("Wait..."); + // puts("Wait..."); result = waitpid(pid, &status, 0); ERROR_IF(waitpid, result, == -1); - if (WIFEXITED(status)) { break; } + if (WIFEXITED(status)) + break; } printf("Child exited with status %d\n", WEXITSTATUS(status)); } diff --git a/tests/pty/forkpty.c b/tests/pty/forkpty.c new file mode 100644 index 0000000000..740acd457a --- /dev/null +++ b/tests/pty/forkpty.c @@ -0,0 +1,38 @@ +/* Test of pty.h and forkpty function. + Copyright (C) 2009-2011 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Simon Josefsson , 2009. */ + +#include +#include +#include + +int main () { + int res = 0; + int amaster = 0; + + res = forkpty (&amaster, NULL, NULL, NULL); + if (res == 0) { + printf("This is child process\n"); + } else if (res > 0) { + printf("This is parent process\n"); + wait(NULL); + } else { + printf ("forkpty returned %d\n", res); + return 1; + } + return 0; +} \ No newline at end of file diff --git a/tests/pwd.c b/tests/pwd.c index 13761aae92..7d3942d930 100644 --- a/tests/pwd.c +++ b/tests/pwd.c @@ -85,7 +85,7 @@ int main(void) { errno = 0; struct passwd *entry = NULL; - for (int i = 1; entry = getpwent(); ++i) { + for (int i = 1; (entry = getpwent()); ++i) { int backup = errno; printf("--- getpwent #%d ---\n", i); if (backup != 0) { diff --git a/tests/sa_restart.c b/tests/sa_restart.c new file mode 100644 index 0000000000..75b086813f --- /dev/null +++ b/tests/sa_restart.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +void action(int sig, siginfo_t *info, void *context) { + assert (sig == SIGUSR1); + (void)info; + (void)context; + char *msg = "Signal handler\n"; + write(1, msg, strlen(msg)); + _exit(0); +} + +int main(void) { + int status; + + struct sigaction act; + act.sa_sigaction = action; + act.sa_flags = SA_RESTART; + sigemptyset(&act.sa_mask); + + status = sigaction(SIGUSR1, &act, NULL); + ERROR_IF(sigaction, status, == -1); + + int fds[2]; + status = pipe(fds); + ERROR_IF(pipe, status, == -1); + + int parent = getpid(); + + status = fork(); + ERROR_IF(fork, status, == -1); + + if (status == 0) { + for (int i = 0; i < 1000; i++) { + status = kill(parent, SIGUSR1); + ERROR_IF(kill, status, == -1); + } + return 0; + } + + char buffer[1]; + status = read(fds[0], buffer, 1); + ERROR_IF(read, status, == -1); + + return 1; +} diff --git a/tests/sharedlib.c b/tests/sharedlib.c new file mode 100644 index 0000000000..fe2dbf8256 --- /dev/null +++ b/tests/sharedlib.c @@ -0,0 +1,10 @@ +#include + +int global_var = 42; +_Thread_local int tls_var = 21; + +void print() +{ + fprintf(stdout, "sharedlib: global_var == %d\n", global_var); + fprintf(stdout, "sharedlib: tls_var == %d\n", tls_var); +} diff --git a/tests/sigaction.c b/tests/sigaction.c index 245ad3b7cc..b332f3902d 100644 --- a/tests/sigaction.c +++ b/tests/sigaction.c @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include @@ -7,23 +9,46 @@ #include "test_helpers.h" void handler1(int sig) { - ERROR_IF(handler, sig, != SIGUSR1); - puts("Signal handler1 called!"); + assert(sig == SIGUSR1); + char *str = "Signal handler1 called!\n"; + write(STDOUT_FILENO, str, strlen(str)); } -void handler2(int sig) { - ERROR_IF(handler, sig, != SIGUSR1); - puts("Signal handler2 called!"); +sigset_t the_set = { 0 }; + +void handler2(int sig, siginfo_t *info, void *context_raw) { + assert(sig == SIGUSR1); + char *str = "Signal handler2 called!\n"; + write(STDOUT_FILENO, str, strlen(str)); + + assert(info != NULL); + assert(info->si_signo == SIGUSR1); +#ifndef __linux + // TODO: SI_TKILL? + assert(info->si_code == SI_USER); + assert(info->si_pid == getpid()); + assert(info->si_uid == getuid()); +#endif + + ucontext_t *context = context_raw; + assert(context != NULL); +#ifndef __linux__ // TODO + assert(memcmp(&context->uc_sigmask, &the_set, sizeof(sigset_t))); + assert(context->uc_link == NULL); +#endif } int main(void) { struct sigaction sa1 = { .sa_handler = handler1 }; - struct sigaction sa2 = { .sa_handler = handler2 }; + struct sigaction sa2 = { .sa_sigaction = handler2, .sa_flags = SA_SIGINFO }; struct sigaction saold = {0}; sigemptyset(&sa1.sa_mask); sigemptyset(&sa2.sa_mask); + int status = sigprocmask(SIG_SETMASK, NULL, &the_set); + ERROR_IF(sigprocmask, status, == -1); + int rcode = sigaction(SIGUSR1, &sa1, NULL); ERROR_IF(signal, rcode, != 0); diff --git a/tests/sigaltstack.c b/tests/sigaltstack.c new file mode 100644 index 0000000000..c0dbe7a188 --- /dev/null +++ b/tests/sigaltstack.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +size_t stack_size; +void *stack_base; + +volatile sig_atomic_t counter = 0; + +void action(int sig, siginfo_t *info, void *context_raw) { + assert(sig == SIGUSR2); + + ucontext_t *context = context_raw; + assert(context->uc_stack.ss_sp == stack_base); + assert(context->uc_stack.ss_size == stack_size); + + // TODO: Technically an implementation detail, but safe to check here. + assert((size_t)info >= (size_t)stack_base); + assert((size_t)info <= ((size_t)stack_base + stack_size)); + + int c = counter++; + + char str[] = "SIGUSR2 handlerXX\n"; + size_t len = strlen(str); + str[len - 2] = '0' + (c % 10); + str[len - 3] = '0' + ((c / 10) % 10); + write(STDOUT_FILENO, str, len); + + if (c < 100) { + raise(SIGUSR2); + } +} + +int main(void) { + int status; + + stack_size = 1024 * 1024; // TODO? + // in glibc MINSIGSTKSZ is a sysconf(MINSIGSTKSZ) macro + assert(stack_size >= (size_t)MINSIGSTKSZ * 100); + stack_base = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ERROR_IF(mmap, stack_base, == MAP_FAILED); + + stack_t old_stack; + stack_t stack = (stack_t) { .ss_sp = stack_base, .ss_size = stack_size, .ss_flags = 0 }; + + status = sigaltstack(&stack, &old_stack); + ERROR_IF(sigaltstack, status, == -1); + + assert((old_stack.ss_flags & SS_ONSTACK) == 0); + + stack_t same; + + status = sigaltstack(&old_stack, &same); + ERROR_IF(sigaltstack, status, == -1); + assert(same.ss_sp == stack.ss_sp); + assert(same.ss_size == stack.ss_size); + assert((same.ss_flags & SS_ONSTACK) == 0); + + status = sigaltstack(&stack, NULL); + ERROR_IF(sigaltstack, status, == -1); + + status = sigaltstack(NULL, &same); + ERROR_IF(sigaltstack, status, == -1); + assert(same.ss_sp == stack.ss_sp); + assert(same.ss_size == stack.ss_size); + assert((same.ss_flags & SS_ONSTACK) == 0); + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_NODEFER; + sa.sa_sigaction = action; + + status = sigaction(SIGUSR2, &sa, NULL); + ERROR_IF(sigaction, status, == -1); + + raise(SIGUSR2); + + return 0; +} diff --git a/tests/sigchld.c b/tests/sigchld.c new file mode 100644 index 0000000000..6822bc01de --- /dev/null +++ b/tests/sigchld.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +volatile sig_atomic_t atomic = 0; +volatile sig_atomic_t atomic2 = 0; + +void action(int sig, siginfo_t *info, void *context) { + (void)context; + + assert(sig == SIGCHLD); + assert(info != NULL); + atomic += 1; +} +void handler(int sig) { + assert(sig == SIGPIPE); + atomic2 += 1; +} + +int main(void) { + int fds[2]; + int status = pipe(fds); + ERROR_IF(pipe, status, == -1); + + int child = fork(); + ERROR_IF(fork, child, == -1); + + sigset_t set; + sigemptyset(&set); + status = sigprocmask(SIG_SETMASK, &set, NULL); + ERROR_IF(sigprocmask, status, == -1); + + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_sigaction = action; + status = sigaction(SIGCHLD, &sa, NULL); + ERROR_IF(sigaction, status, == -1); + + sa.sa_handler = handler; + status = sigaction(SIGPIPE, &sa, NULL); + ERROR_IF(sigaction, status, == -1); + + if (child == 0) { + status = close(fds[1]); + ERROR_IF(close, status, == -1); + + char buf[1]; + status = read(fds[0], buf, 1); + ERROR_IF(read, status, == -1); + puts("Child exiting."); + return EXIT_SUCCESS; + } else { + int waitpid_stat; + + close(fds[0]); + ERROR_IF(close, status, == -1); + + puts("Sending SIGSTOP..."); + status = kill(child, SIGSTOP); + ERROR_IF(kill, status, == -1); + + while (atomic == 0) { + status = sched_yield(); + ERROR_IF(sched_yield, status, == -1); + } + puts("First handler ran, checking status."); + + status = waitpid(child, &waitpid_stat, WUNTRACED); + ERROR_IF(waitpid, status, == -1); + assert(WIFSTOPPED(waitpid_stat)); + assert(WSTOPSIG(waitpid_stat) == SIGSTOP); + puts("Correct, sending SIGCONT..."); + + status = write(fds[1], "C", 1); + ERROR_IF(write, status, == -1); + + status = kill(child, SIGCONT); + ERROR_IF(kill, status, == -1); + + while (atomic == 1) { + status = sched_yield(); + ERROR_IF(sched_yield, status, == -1); + } + puts("Second handler ran, checking status."); + + status = waitpid(child, &waitpid_stat, 0); + ERROR_IF(waitpid, status, == -1); + assert(WIFEXITED(waitpid_stat)); + assert(WEXITSTATUS(waitpid_stat) == 0); + puts("Child exited."); + + puts("Writing to (broken) pipe."); + status = write(fds[1], "B", 1); + assert(status == -1); + assert(errno == EPIPE); + + /*while (atomic2 == 0) { + status = sched_yield(); + ERROR_IF(sched_yield, status, == -1); + }*/ + puts("SIGSTOP handler successfully executed."); + return EXIT_SUCCESS; + } +} diff --git a/tests/signal.c b/tests/signal.c index b322201b8c..cdc3004821 100644 --- a/tests/signal.c +++ b/tests/signal.c @@ -7,6 +7,7 @@ #include "test_helpers.h" void handler(int sig) { + UNEXP_IF(signal, sig, != SIGUSR1); puts("Signal handler called!"); } diff --git a/tests/signals/kill-child.c b/tests/signals/kill-child.c new file mode 100644 index 0000000000..c2644bd670 --- /dev/null +++ b/tests/signals/kill-child.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +/* + * Test signal catching when being signalled from parent. + * Skip SIGKILL and SIGSTOP as these are not catchable. + */ + +volatile sig_atomic_t sig_handled = 0; + +void sig_handler(int signo) +{ + (void) signo; + sig_handled = 1; +} + +void child_proc(int signum) +{ + sigset_t sig_set; + int status; + + sig_handled = 0; + + status = sigemptyset(&sig_set); + ERROR_IF(sigemptyset, status, == -1); + + status = sigaddset(&sig_set, signum); + ERROR_IF(sigaddset, status, == -1); + + struct sigaction act; + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(signum, &act, NULL); + + status = usleep(200000); + ERROR_IF(usleep, status, == 0); + + assert(sig_handled != 0); + + exit(EXIT_SUCCESS); +} + +void parent(int signum, pid_t pid) +{ + int status; + + usleep(100000); + status = kill(pid, signum); + ERROR_IF(kill, status, != 0); + + pid = wait(&status); + ERROR_IF(wait, pid, == (pid_t)-1); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) + { + printf("Child did not exit normally.\n"); + exit(EXIT_FAILURE); + } + else + { + return; + } +} + +void kill_child(int signum) +{ + int pid; + + if ((pid = fork()) == 0) + { + child_proc(signum); + } + else + { + parent(signum, pid); + } +} + +int main() +{ + for (long unsigned int i = 1; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + printf("Testing for signal %s (%d)\n", strsignal(sig), sig); + kill_child(sig); + } + return EXIT_SUCCESS; +} diff --git a/tests/signals/kill-group.c b/tests/signals/kill-group.c new file mode 100644 index 0000000000..80059ef874 --- /dev/null +++ b/tests/signals/kill-group.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// this test is to make sure that when a negative pid is supplied to kill, all the processes in that group will be killed + +int handler_called = 0; +void sig_handler(int signo) +{ + (void) signo; + handler_called = 1; + return; +} + +int kill_group(int signum) +{ + int pgrp; + struct sigaction act; + int status; + + act.sa_handler=sig_handler; + act.sa_flags=0; + status = sigemptyset(&act.sa_mask); + ERROR_IF(sigemptyset, status, == -1); + + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, == -1); + + status = getpgrp(); + pgrp = status; + ERROR_IF(getpgrp, status, == -1); + + status = kill(-pgrp, signum); + ERROR_IF(kill, status, != 0); + + ERROR_IF(kill, handler_called, !=1); + handler_called = 0; + + return EXIT_SUCCESS; +} + +int main() +{ + // Ensure we don't kill what was already in the process group. + int status = setpgid(0, 0); + ERROR_IF(setpgid, status, == -1); + + for (unsigned int i = 0; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + kill_group(sig); + } + return EXIT_SUCCESS; +} diff --git a/tests/signals/kill-invalid.c b/tests/signals/kill-invalid.c new file mode 100644 index 0000000000..8cf8e7d3e6 --- /dev/null +++ b/tests/signals/kill-invalid.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +//this test ensures that sending an invalid signal will set errno to esrch + +int main() +{ + + /* + * ESRCH + */ + int status; + status = kill(999999, 0); + ERROR_IF(kill, status, != -1); + ERROR_IF(kill, errno, != ESRCH); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/kill-permission.c b/tests/signals/kill-permission.c new file mode 100644 index 0000000000..eab0aef545 --- /dev/null +++ b/tests/signals/kill-permission.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +// makes sure that a process is not killed if the user doesn't have permission to kill the process + +int main(void) +{ + int status; + // This is added in case user is root. If user is normal user, then it has no effect on the tests + setuid(1000); + status = kill(1, 0); + ERROR_IF(kill, status, != -1); + ERROR_IF(kill, errno, != EPERM); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/kill-self.c b/tests/signals/kill-self.c new file mode 100644 index 0000000000..f05ec12a9f --- /dev/null +++ b/tests/signals/kill-self.c @@ -0,0 +1,56 @@ +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +/* + * Test signal catching when signalling self. + * Ensure all signals can be caught (other than SIGKILL and SIGSTOP). + */ + +volatile sig_atomic_t handler_called = 0; + +void sig_handler(int sig) +{ + (void) sig; + handler_called = 1; +} + +int kill_self(int sig) +{ + struct sigaction act; + int status; + + handler_called = 0; + + act.sa_handler = sig_handler; + act.sa_flags = 0; + + status = sigemptyset(&act.sa_mask); + ERROR_IF(sigemptyset, status, == -1); + + status = sigaction(sig, &act, NULL); + ERROR_IF(sigaction, status, == -1); + + status = kill(getpid(), sig); + ERROR_IF(kill, status, != 0); + + assert(handler_called == 1); + + return EXIT_SUCCESS; +} + +int main() +{ + + for (unsigned int i = 1; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + kill_self(sig); + } + return EXIT_SUCCESS; +} diff --git a/tests/signals/kill0-self.c b/tests/signals/kill0-self.c new file mode 100644 index 0000000000..ad76980233 --- /dev/null +++ b/tests/signals/kill0-self.c @@ -0,0 +1,16 @@ +#include +#include "../test_helpers.h" + +/* + * Send signal 0 to self. This just reports whether a signal can be sent. + */ + +int main() +{ + int status = 0; + + status = kill(getpid(), 0); + ERROR_IF(kill, status, != 0); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/killpg-child.c b/tests/signals/killpg-child.c new file mode 100644 index 0000000000..d1ef669ca1 --- /dev/null +++ b/tests/signals/killpg-child.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// test killing a child process + +void sig_handler (int signo) { + (void) signo; + exit(EXIT_SUCCESS); +} + +int killpg_test2(int signum) +{ + int child_pid, child_pgid; + printf("we are on signal %d\n ", signum); + + if ((child_pid = fork()) == 0) { + /* child here */ + struct sigaction act; + act.sa_handler=sig_handler; + act.sa_flags=0; + sigemptyset(&act.sa_mask); + sigaction(signum, &act, 0); + + /* change child's process group id */ + setpgrp(); + + sigpause(SIGABRT); + + return EXIT_FAILURE; + } else { + /* parent here */ + int i; + sigignore(signum); + + usleep(100000); + + child_pgid = getpgid(child_pid); + ERROR_IF(getpgid, child_pgid, == -1); + + int status; + status = killpg(child_pgid, signum); + ERROR_IF(killpg, status, != 0); + + status = wait(&i); + ERROR_IF(wait, status, == -1); + + if (WEXITSTATUS(i) == EXIT_SUCCESS) { + printf("Child exited normally\n"); + printf("Test PASSED\n"); + return EXIT_SUCCESS; + } else { + printf("Child did not exit normally.\n"); + printf("Test FAILED\n"); + exit(EXIT_FAILURE); + } + } + + return EXIT_FAILURE; +} + +int main(){ + int x; + for (unsigned int i = 1; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCHLD || sig == SIGINT || sig == SIGQUIT) + { + continue; + } + x = killpg_test2(sig); + if (x == EXIT_FAILURE){ + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; +} + diff --git a/tests/signals/killpg-esrch.c b/tests/signals/killpg-esrch.c new file mode 100644 index 0000000000..ab1e62cd96 --- /dev/null +++ b/tests/signals/killpg-esrch.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +// if a user tries to kill a process that does not exist the esrch error will be returned +int main() +{ + + int status; + status = killpg(999999, 0); + ERROR_IF(killpg, status, !=-1); + + ERROR_IF(killpg, errno, != ESRCH); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/killpg-invalid.c b/tests/signals/killpg-invalid.c new file mode 100644 index 0000000000..b1f89dc023 --- /dev/null +++ b/tests/signals/killpg-invalid.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +// the test makes sure that if an invalid signal is passed it will return the EINVAL error +int main() +{ + int pgrp; + + pgrp = getpgrp(); + ERROR_IF(getpgrp, pgrp, == -1); + + int status; + status = killpg(pgrp, -1); + ERROR_IF(killpg, status, != -1); + + ERROR_IF(killpg, errno, != EINVAL); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/killpg-self.c b/tests/signals/killpg-self.c new file mode 100644 index 0000000000..afd4865112 --- /dev/null +++ b/tests/signals/killpg-self.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// Test that the killpg() function shall send signal sig to the process +// group specified by prgp. + +void sig_handler(int signo) +{ + // printf("Caught signal %d being tested!\n", signo); + // printf("Test PASSED\n"); + (void) signo; + return; +} + +int killpg_test1(int signum) +{ + int pgrp; + struct sigaction act; + + act.sa_handler=sig_handler; + act.sa_flags=0; + int status = sigemptyset(&act.sa_mask); + ERROR_IF(sigemptyset, status, == -1); + + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, == -1); + + pgrp = getpgrp(); + ERROR_IF(getpgrp, pgrp, == -1); + + status = killpg(pgrp, signum); + ERROR_IF(killpg, status, != 0); + + return EXIT_SUCCESS; +} + +int main(){ + // UB if pg == 1, so set it here first + int status = setpgid(0, 0); + ERROR_IF(setpgid, status, == -1); + + for (unsigned int i = 0; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + killpg_test1(sig); + } + return EXIT_SUCCESS; +} + diff --git a/tests/signals/killpg0-self.c b/tests/signals/killpg0-self.c new file mode 100644 index 0000000000..e319660eca --- /dev/null +++ b/tests/signals/killpg0-self.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include "../test_helpers.h" + +// test sending signal 0 to self will killpg + +int main() +{ + // UB if pg == 1, so set it here first + int err = setpgid(0, 0); + ERROR_IF(setpgid, err, == -1); + + int pgrp; + + pgrp = getpgrp(); + ERROR_IF(getpgrp, pgrp, == -1); + + int status; + status = killpg(pgrp, 0); + ERROR_IF(killpg, status, != 0); + + return EXIT_SUCCESS; +} diff --git a/tests/signals/pthread_kill-child.c b/tests/signals/pthread_kill-child.c new file mode 100644 index 0000000000..04eb8a68aa --- /dev/null +++ b/tests/signals/pthread_kill-child.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +//test with pthread_kill to kill a child process + +void * thread_function(void *arg) +{ + /* Does nothing */ + (void) arg; + pthread_exit((void*)0); + + /* To please some compilers */ + return NULL; +} + +int main() +{ + pthread_t child_thread; + pthread_t invalid_tid; + + int rc; + + rc = pthread_create(&child_thread, NULL, + thread_function, NULL); + ERROR_IF(pthread_create, rc, != 0); + + rc = pthread_join(child_thread, NULL); + ERROR_IF(pthread_join, rc, != 0); + + // Now the child_thread exited, it is an invalid tid + memcpy(&invalid_tid, &child_thread, + sizeof(pthread_t)); + usleep(300); + + int status; + status = pthread_kill(invalid_tid, 0); + ERROR_IF(pthread_kill, status, != ESRCH); + + exit(EXIT_SUCCESS); +} \ No newline at end of file diff --git a/tests/signals/pthread_kill-invalid.c b/tests/signals/pthread_kill-invalid.c new file mode 100644 index 0000000000..053f817557 --- /dev/null +++ b/tests/signals/pthread_kill-invalid.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +//test with pthread_kill making sure sending an invalid signal returns einval + +int main() +{ + pthread_t main_thread; + + main_thread = pthread_self(); + + int status; + status = pthread_kill(main_thread, -1); + ERROR_IF(pthread_kill, status, != EINVAL); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/pthread_kill-self.c b/tests/signals/pthread_kill-self.c new file mode 100644 index 0000000000..dcc42b85c2 --- /dev/null +++ b/tests/signals/pthread_kill-self.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +//test pthread_kill on self + +# define INTHREAD 0 +# define INMAIN 1 +# define SIGTOTEST SIGABRT + +int sem1; /* Manual semaphore */ +volatile sig_atomic_t handler_called = 0; +int count = 1; + +struct signal { + int signum; +}; + +void handler() { + handler_called = 1; + return; +} + +void *a_thread_func( void *arg) +{ + + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + sigaction(((struct signal *)arg)->signum, &act, 0); + + sem1=INMAIN; + + while(sem1==INMAIN) + usleep(100000); + + // sleep(50); + + handler_called=-1; + pthread_exit(0); + return NULL; +} + +int pthread_kill_test1(int signum) +{ + pthread_t new_th; + + sem1=INTHREAD; + + struct signal arg; + arg.signum = signum; + + int status; + status = pthread_create(&new_th, NULL, a_thread_func, &arg); + ERROR_IF(pthread_create, status, != 0); + + while(sem1==INTHREAD) + usleep(100000); + + status = pthread_kill(new_th, signum); + ERROR_IF(pthread_kill, status, != 0); + + usleep(100000); + sem1=INTHREAD; + + while(handler_called==0) + usleep(100000); + + ERROR_IF(pthread_kill, handler_called, == -1); + ERROR_IF(pthread_kill, handler_called, == 0); + + handler_called = 0; + return EXIT_SUCCESS; +} + +int main(){ + for (unsigned int i = 0; i < sizeof(signals_list)/sizeof(signals_list[0]); i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + pthread_kill_test1(sig); + } + return EXIT_SUCCESS; +} + diff --git a/tests/signals/pthread_kill0-self.c b/tests/signals/pthread_kill0-self.c new file mode 100644 index 0000000000..a4723a9e94 --- /dev/null +++ b/tests/signals/pthread_kill0-self.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +// test sending 0 with pthread_kill to self + +int main() +{ + pthread_t main_thread; + + main_thread = pthread_self(); + + int status; + status = pthread_kill(main_thread, 0); + ERROR_IF(pthread_kill, status, != 0); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/raise-compliance.c b/tests/signals/raise-compliance.c new file mode 100644 index 0000000000..6722729398 --- /dev/null +++ b/tests/signals/raise-compliance.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// The raise() function shall send the signal sig to the executing [CX] [Option Start] thread or process. [Option End] If a signal handler is called, the raise() function shall not return until after the signal handler does. + +// [CX] [Option Start] The effect of the raise() function shall be equivalent to calling: pthread_kill(pthread_self(), sig); + +void sig_hand(int i) + +{ + if (i < 1 || i > 32){ + printf("an invalid signal was given: %d", i); + } + static int count = 1; + + count++; + printf("%d \n", count); + if (count == 32) { + printf("reached 32nd signal\n"); + return; + } + else{ + printf("count is %d\n", count); + } +} + +void raise_test(int sig){ + signal(sig, sig_hand); + raise(sig); +} + +int main(void) +{ + for (int i = 0; i < N_SIGNALS; i++) + { + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP) + { + continue; + } + raise_test(sig); + } + return EXIT_SUCCESS; +} + diff --git a/tests/signals/sigaddset-add.c b/tests/signals/sigaddset-add.c new file mode 100644 index 0000000000..bcbad4aba7 --- /dev/null +++ b/tests/signals/sigaddset-add.c @@ -0,0 +1,39 @@ + +#define _OPEN_SYS +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// The sigaddset() function adds the individual signal specified by the signo to the signal set pointed to by set. + +// Applications shall call either sigemptyset() or sigfillset() at least once for each object of type sigset_t prior to any other use of that object. If such an object is not initialized in this way, but is nonetheless supplied as an argument to any of pthread_sigmask(), sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(), sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or sigwaitinfo(), the results are undefined. + +void addset_test(sigset_t *sigset, int signal) +{ + int status; + + status = sigismember(sigset, signal); + ERROR_IF(sigismember, status, != 0); + + status = sigaddset(sigset, signal); + ERROR_IF(sigaddset, status, != 0); + + status = sigismember(sigset, signal); + ERROR_IF(sigismember, status, != 1); +} + +int main() +{ + sigset_t sigset; + + for (int i = 1; i < N_SIGNALS; i++) + { + sigemptyset(&sigset); + + int sig = signals_list[i - 1].signal; + + addset_test(&sigset, sig); + } +} diff --git a/tests/signals/sigdelset-delete.c b/tests/signals/sigdelset-delete.c new file mode 100644 index 0000000000..bfab99121a --- /dev/null +++ b/tests/signals/sigdelset-delete.c @@ -0,0 +1,36 @@ +#define _OPEN_SYS +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// The sigdelset() function deletes the individual signal specified by signo from the signal set pointed to by set. + +// Applications should call either sigemptyset() or sigfillset() at least once for each object of type sigset_t prior to any other use of that object. If such an object is not initialized in this way, but is nonetheless supplied as an argument to any of pthread_sigmask(), sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(), sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or sigwaitinfo(), the results are undefined. + +void delset_test(sigset_t *sigset, int signal) +{ + int status; + status = sigismember(sigset, signal); + ERROR_IF(sigismember, status, != 1); + + status = sigdelset(sigset, signal); + ERROR_IF(sigdelset, status, != 0); + + status = sigismember(sigset, signal); + ERROR_IF(sigismember, status, != 0); +} + +int main() +{ + sigset_t sigset; + + for (int i = 1; i < N_SIGNALS; i++) + { + sigfillset(&sigset); + + int sig = signals_list[i - 1].signal; + delset_test(&sigset, sig); + } +} diff --git a/tests/signals/sigismember-invalid.c b/tests/signals/sigismember-invalid.c new file mode 100644 index 0000000000..80c45fa236 --- /dev/null +++ b/tests/signals/sigismember-invalid.c @@ -0,0 +1,21 @@ +#define _OPEN_SYS +#include +#include +#include +#include +#include "../test_helpers.h" + +// test to make sure that if you pass an invalid signal to sigismember it will return EINVAL + +int main() { + sigset_t sigset; + int status; + + sigfillset(&sigset); + status = sigismember(&sigset, -1); + ERROR_IF(sigismember, status, != -1); + ERROR_IF(sigismember, errno, != EINVAL); + + return EXIT_SUCCESS; + +} \ No newline at end of file diff --git a/tests/signals/sigismember-valid.c b/tests/signals/sigismember-valid.c new file mode 100644 index 0000000000..e6c54f5894 --- /dev/null +++ b/tests/signals/sigismember-valid.c @@ -0,0 +1,42 @@ +#define _OPEN_SYS +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// The sigismember() function shall test whether the signal specified by signo is a member of the set pointed to by set. + +// Applications should call either sigemptyset() or sigfillset() at least once for each object of type sigset_t prior to any other use of that object. If such an object is not initialized in this way, but is nonetheless supplied as an argument to any of pthread_sigmask(), sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(), sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or sigwaitinfo(), the results are undefined. + +void check_full(sigset_t set, int signum) { + if (!sigismember(&set, signum)) { + exit(EXIT_FAILURE); + } + +} + +void check_empty(sigset_t set, int signum) { + if (sigismember(&set, signum)) { + exit(EXIT_FAILURE); + } + +} + + + +int main() { + sigset_t sigset; + + sigfillset(&sigset); + for (int i=1; i + +/* + * This is a test to ensure all required items for signal.h are defined. + * The definitions follow the order described in + * + */ + +void handler(int sig_num) +{ + (void)sig_num; +} + +void action(int sig_num, siginfo_t *info, void *context) +{ + (void)sig_num; + (void)info; + (void)context; +} + +int main() +{ + int (*sh)(int) __attribute__((unused)) = sighold; + int (*sigig)(int) __attribute__((unused)) = sigignore; + int (*sigintr)(int, int) __attribute__((unused)) = siginterrupt; + int (*paws)(int) __attribute__((unused)) = sigpause; + int (*srls)(int) __attribute__((unused)) = sigrelse; + void (*(*sset)(int, void (*)(int)))(int) __attribute__((unused)) = sigset; + + return EXIT_SUCCESS; +} diff --git a/tests/signals/signal-h.c b/tests/signals/signal-h.c new file mode 100644 index 0000000000..8ec09b374d --- /dev/null +++ b/tests/signals/signal-h.c @@ -0,0 +1,232 @@ +#include "../test_helpers.h" +#include + +/* + * This is a test to ensure all required items for signal.h are defined. + * The definitions follow the order described in + * + */ + +void handler(int sig_num) +{ + (void)sig_num; +} + +void action(int sig_num, siginfo_t *info, void *context) +{ + (void)sig_num; + (void)info; + (void)context; +} + +int main() +{ + void (*sig_dfl)(int) __attribute__((unused)) = SIG_DFL; + void (*sig_err)(int) __attribute__((unused)) = SIG_ERR; + void (*sig_ign)(int) __attribute__((unused)) = SIG_IGN; + + pthread_t pthread_num __attribute__((unused)) = 0; + size_t size __attribute__((unused)) = 0; + uid_t uid __attribute__((unused)) = 0; + + sig_atomic_t atomic __attribute__((unused)) = 0; + sigset_t sig_set __attribute__((unused)) ; + pid_t pid __attribute__((unused)) = 0; + + pthread_attr_t *attr __attribute__((unused)) = NULL; + + // struct sigevent sev; + union sigval sv; + + sv.sival_int = (int)0; + sv.sival_ptr = (void *)0; + + // sev.sigev_notify = (int)0; + // sev.sigev_notify = SIGEV_NONE; + // sev.sigev_notify = SIGEV_SIGNAL; + // sev.sigev_notify = SIGEV_THREAD; + + // sev.sigev_signo = (int)0; + // sev.sigev_value = sv; + // sev.sigev_value.sival_int = (int)0; + // sev.sigev_value.sival_ptr = (void *)0; + // sev.sigev_notify_function = (void (*)(union sigval))0; + // sev.sigev_notify_attributes = (pthread_attr_t *)0; + + int rt_sig_num __attribute__((unused)) = SIGRTMIN; + rt_sig_num = SIGRTMAX; + + // rt_sig_num = SIG2STR_MAX; + + // sev.sigev_signo = SIGABRT; + // sev.sigev_signo = SIGALRM; + // sev.sigev_signo = SIGBUS; + // sev.sigev_signo = SIGCHLD; + // sev.sigev_signo = SIGCONT; + // sev.sigev_signo = SIGFPE; + // sev.sigev_signo = SIGHUP; + // sev.sigev_signo = SIGILL; + // sev.sigev_signo = SIGINT; + // sev.sigev_signo = SIGKILL; + // sev.sigev_signo = SIGPIPE; + // sev.sigev_signo = SIGQUIT; + // sev.sigev_signo = SIGSEGV; + // sev.sigev_signo = SIGSTOP; + // sev.sigev_signo = SIGTERM; + // sev.sigev_signo = SIGTSTP; + // sev.sigev_signo = SIGTTIN; + // sev.sigev_signo = SIGTTOU; + // sev.sigev_signo = SIGUSR1; + // sev.sigev_signo = SIGUSR2; + // sev.sigev_signo = SIGWINCH; + // sev.sigev_signo = SIGPOLL; + // sev.sigev_signo = SIGPROF; + // sev.sigev_signo = SIGSYS; + // sev.sigev_signo = SIGTRAP; + // sev.sigev_signo = SIGURG; + // sev.sigev_signo = SIGVTALRM; + // sev.sigev_signo = SIGXCPU; + // sev.sigev_signo = SIGXFSZ; + + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sa.sa_handler = SIG_DFL; + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = (int)0; + sa.sa_sigaction = action; + +#ifndef SA_NOCLDSTOP +#error "Required constant not defined SA_NOCLDSTOP" +#endif +#ifndef SIG_BLOCK +#error "Required constant not defined SIG_BLOCK" +#endif +#ifndef SIG_UNBLOCK +#error "Required constant not defined SIG_UNBLOCK" +#endif +#ifndef SIG_SETMASK +#error "Required constant not defined SIG_SETMASK" +#endif +#ifndef SA_ONSTACK +#error "Required constant not defined SA_ONSTACK" +#endif +#ifndef SA_RESETHAND +#error "Required constant not defined SA_RESETHAND" +#endif +#ifndef SA_RESTART +#error "Required constant not defined SA_RESTART" +#endif +#ifndef SA_SIGINFO +#error "Required constant not defined SA_SIGINFO" +#endif +#ifndef SA_NOCLDWAIT +#error "Required constant not defined SA_NOCLDWAIT" +#endif +#ifndef SA_NODEFER +#error "Required constant not defined SA_NODEFER" +#endif +#ifndef SS_ONSTACK +#error "Required constant not defined SS_ONSTACK" +#endif +#ifndef SS_DISABLE +#error "Required constant not defined SS_DISABLE" +#endif +#ifndef MINSIGSTKSZ +#error "Required constant not defined MINSIGSTKSZ" +#endif +#ifndef SIGSTKSZ +#error "Required constant not defined SIGSTKSZ" +#endif + + // struct ucontext_t uc; + mcontext_t mc __attribute__((unused)); + + // add parts to ucontext_t + // uc.uc_link = NULL; + // uc.uc_sigmask = 0; + // uc.uc_stack = NULL; + // uc.uc_mcontext = NULL; + + + stack_t st __attribute__((unused)); + st.ss_sp = NULL; + st.ss_size = (size_t)0; + st.ss_flags = (int)0; + + siginfo_t si __attribute__((unused)); + si.si_signo = SIGHUP; + si.si_code = (int)0; + si.si_errno = (int)0; + si.si_pid = (pid_t)0; + si.si_uid = (uid_t)0; + si.si_addr = NULL; + si.si_status = (int)0; + si.si_value = sv; + + // si.si_code = ILL_ILLOPC; + // si.si_code = ILL_ILLOPN; + // si.si_code = ILL_ILLADR; + // si.si_code = ILL_ILLTRP; + // si.si_code = ILL_PRVOPC; + // si.si_code = ILL_PRVREG; + // si.si_code = ILL_COPROC; + // si.si_code = ILL_BADSTK; + // si.si_code = FPE_INTDIV; + // si.si_code = FPE_INTOVF; + // si.si_code = FPE_FLTDIV; + // si.si_code = FPE_FLTOVF; + // si.si_code = FPE_FLTUND; + // si.si_code = FPE_FLTRES; + // si.si_code = FPE_FLTINV; + // si.si_code = FPE_FLTSUB; + // si.si_code = SEGV_MAPERR; + // si.si_code = SEGV_ACCERR; + // si.si_code = BUS_ADRALN; + // si.si_code = BUS_ADRERR; + // si.si_code = BUS_OBJERR; + // si.si_code = TRAP_BRKPT; + // si.si_code = TRAP_TRACE; + // si.si_code = CLD_EXITED; + // si.si_code = CLD_KILLED; + // si.si_code = CLD_DUMPED; + // si.si_code = CLD_TRAPPED; + // si.si_code = CLD_STOPPED; + // si.si_code = CLD_CONTINUED; + // si.si_code = SI_USER; + // si.si_code = SI_QUEUE; + // si.si_code = SI_TIMER; + // si.si_code = SI_ASYNCIO; + // si.si_code = SI_MESGQ; + + // void (*(*bs)(int, void (*)(int)))(int) = bsd_signal; + int (*k)(pid_t, int) __attribute__((unused))= kill; + int (*kpg)(pid_t, int) __attribute__((unused))= killpg; + void (*psig)(const siginfo_t *, const char *) __attribute__((unused))= psiginfo; + void (*ps)(int, const char *) __attribute__((unused))= psignal; + int (*ptk)(pthread_t, int) __attribute__((unused))= pthread_kill; + int (*ptsm)(int, const sigset_t *, sigset_t *) __attribute__((unused))= pthread_sigmask; + int (*r)(int) __attribute__((unused))= raise; + // int (*s2s)(int, char*) = sig2str; + int (*sact)(int, const struct sigaction *restrict, + struct sigaction *restrict) __attribute__((unused)) = sigaction; + int (*sas)(sigset_t *, int) __attribute__((unused))= sigaddset; + int (*sastk)(const stack_t *restrict, stack_t *restrict) __attribute__((unused))= sigaltstack; + int (*sds)(sigset_t *, int) __attribute__((unused))= sigdelset; + int (*ses)(sigset_t *) __attribute__((unused))= sigemptyset; + int (*sfs)(sigset_t *) __attribute__((unused))= sigfillset; + int (*ismem)(const sigset_t *, int) __attribute__((unused))= sigismember; + void ( *(*sgnl)(int, void (*)(int)))(int) __attribute__((unused))= signal; + int (*pend)(sigset_t *) __attribute__((unused))= sigpending; + int (*spmsk)(int, const sigset_t *restrict, sigset_t *restrict) __attribute__((unused))= sigprocmask; + int (*sigq)(pid_t, int, const union sigval) __attribute__((unused))= sigqueue; + int (*susp)(const sigset_t *) __attribute__((unused))= sigsuspend; + int (*stmwt)(const sigset_t *restrict, siginfo_t *restrict, + const struct timespec *restrict) __attribute__((unused))= sigtimedwait; + int (*swt)(const sigset_t *restrict, int *restrict) __attribute__((unused))= sigwait; + int (*swtinfo)(const sigset_t *restrict, siginfo_t *restrict) __attribute__((unused))= sigwaitinfo; + // int (*str2s)(const char *restrict, int *restrict) = str2sig; + + return EXIT_SUCCESS; +} diff --git a/tests/signals/signal-handle_return.c b/tests/signals/signal-handle_return.c new file mode 100644 index 0000000000..933f992078 --- /dev/null +++ b/tests/signals/signal-handle_return.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include "../test_helpers.h" + +void SIGUSR1_handler(int signo) +{ + (void) signo; + printf("do nothing useful\n"); +} + +void SIGUSR2_handler(int signo) +{ + (void) signo; + printf("do nothing useful\n"); +} + +int main() +{ + void (*status) (int); + status = signal(SIGUSR1, SIGUSR1_handler); + ERROR_IF(signal, status, == SIG_ERR); + + status = signal(SIGUSR2, SIGUSR2_handler); + ERROR_IF(signal, status, == SIG_ERR); + + status = signal(SIGUSR1,SIG_IGN); + // printf("status is %d\n", status); + // printf("SIGUSR1_handler is %d\n", SIGUSR1_handler); + ERROR_IF(signal, status, != SIGUSR1_handler); + + //this seems to be a weird comparison error + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/signal-handler.c b/tests/signals/signal-handler.c new file mode 100644 index 0000000000..f6eba8e168 --- /dev/null +++ b/tests/signals/signal-handler.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + printf("SIGCHLD called. Inside handler\n"); + handler_called = 1; +} + +int main() +{ + void (*status) (int); + status = signal(SIGCHLD, sig_handler); + ERROR_IF(signal, status, == SIG_ERR); + + status = signal(SIGCHLD,SIG_DFL); + ERROR_IF(signal, status, != sig_handler); + + //same comparison error from handle_return + + raise(SIGCHLD); + + if (handler_called == 1) { + return EXIT_FAILURE; + } + handler_called = 0; + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/signal-handler2.c b/tests/signals/signal-handler2.c new file mode 100644 index 0000000000..7cc8394bdd --- /dev/null +++ b/tests/signals/signal-handler2.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + // printf("%d called. Inside handler\n", signo); + handler_called = 1; +} + +int signal_test3(int signum) +{ + void (*status) (int); + status = signal(signum, sig_handler); + ERROR_IF(signal, status, == SIG_ERR); + + raise(signum); + + ERROR_IF(raise, handler_called, != 1); + return EXIT_SUCCESS; +} + +int main(){ + for (unsigned int i = 0; i < sizeof(signals_list)/sizeof(signals_list[0]); i++){ + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP){ + continue; + } + signal_test3(sig); + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/signal-ignore.c b/tests/signals/signal-ignore.c new file mode 100644 index 0000000000..37a7c744bf --- /dev/null +++ b/tests/signals/signal-ignore.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + printf("%d called. Inside handler\n", signo); + handler_called = 1; +} + +int signal_test2(int signum) +{ + void (*status) (int); + status = signal(signum, sig_handler); + ERROR_IF(signal, status, == SIG_ERR); + + status = signal(signum, SIG_IGN); + ERROR_IF(signal, status, != sig_handler); + + // same issue as handle_return + + raise(signum); + + ERROR_IF(raise, handler_called, == 1); + handler_called = 0; + return EXIT_SUCCESS; +} + +int main(){ + for (unsigned int i = 0; i < sizeof(signals_list)/sizeof(signals_list[0]); i++){ + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP){ + continue; + } + signal_test2(sig); + } + return EXIT_SUCCESS; +} + + + diff --git a/tests/signals/signal-invalid.c b/tests/signals/signal-invalid.c new file mode 100644 index 0000000000..afad26247a --- /dev/null +++ b/tests/signals/signal-invalid.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include "../test_helpers.h" + +// test to make sure sending an invalid signal sets errno + +void sig_handler(int signo) +{ + (void) signo; + printf("handler does nothing useful.\n"); +} + +int main() +{ + errno = -1; + + void (*status) (int); + status = signal(-1, sig_handler); + ERROR_IF(signal, status, != SIG_ERR); + ERROR_IF(signal, errno, <= 0); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/signal-uncatchable.c b/tests/signals/signal-uncatchable.c new file mode 100644 index 0000000000..1bf85db82c --- /dev/null +++ b/tests/signals/signal-uncatchable.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include "../test_helpers.h" + +//make sure you can't catch uncatchable signals + +void sig_handler(int signo) +{ + (void) signo; + printf("handler does nothing useful.\n"); +} + +int main() +{ + errno = -1; + void (*status) (int); + status = signal(SIGKILL, sig_handler); + ERROR_IF(signal, status, != SIG_ERR); + + ERROR_IF(signal, errno, <= 0); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/signals_list.h b/tests/signals/signals_list.h new file mode 100644 index 0000000000..75bf3cd847 --- /dev/null +++ b/tests/signals/signals_list.h @@ -0,0 +1,79 @@ +#ifndef _SIGNALS_LIST +#define _SIGNALS_LIST 1 + +#include + +#ifdef SIGSTKFLT +#endif + +const int N_SIGNALS = (28 +#ifdef SIGSTKFLT + + 1 +#endif +#ifdef SIGWINCH + + 1 +#endif +#ifdef SIGIO + + 1 +#endif +#ifdef SIGPWR + + 1 +#endif +#ifdef SIGUNUSED + + 1 +#endif +); + +struct signalAction +{ + int signal; + char action; +}; + +const struct signalAction signals_list[] = { + {SIGABRT, 'A'}, + {SIGALRM, 'T'}, + {SIGBUS, 'A'}, + {SIGCHLD, 'I'}, + {SIGCONT, 'C'}, + {SIGFPE, 'A'}, + {SIGHUP, 'T'}, + {SIGILL, 'A'}, + {SIGINT, 'T'}, + {SIGKILL, 'T'}, + {SIGPIPE, 'T'}, + {SIGQUIT, 'A'}, + {SIGSEGV, 'A'}, + {SIGSTOP, 'S'}, + {SIGTERM, 'T'}, + {SIGTSTP, 'S'}, + {SIGTTIN, 'S'}, + {SIGTTOU, 'S'}, + {SIGUSR1, 'T'}, + {SIGUSR2, 'T'}, + // {SIGPOLL, 'T'}, + {SIGPROF, 'T'}, + {SIGSYS, 'A'}, + {SIGTRAP, 'A'}, + {SIGURG, 'I'}, + {SIGVTALRM, 'T'}, + {SIGXCPU, 'A'}, + {SIGXFSZ, 'A'}, +#ifdef SIGSTKFLT + {SIGSTKFLT, 'T'}, +#endif +#ifdef SIGWINCH + {SIGWINCH, 'I'}, +#endif +#ifdef SIGIO + {SIGIO, 'T'}, +#endif +#ifdef SIGPWR + {SIGPWR, 'T'}, +#endif +#ifdef SIGUNUSED + {SIGUNUSED, 'A'}, +#endif +}; + +#endif /* _SIGNALS_LIST */ diff --git a/tests/signals/sigpause-error.c b/tests/signals/sigpause-error.c new file mode 100644 index 0000000000..b7299bf092 --- /dev/null +++ b/tests/signals/sigpause-error.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// This program verifies that sigpause() returns -1 and sets errno to EINTR +// when it returns. + +#define INMAIN 0 +#define INTHREAD 1 + +int handler_called = 0; +int returned = 0; +int return_value = 2; +int result = 2; +int sem = INMAIN; + +void handler() { + // printf("signal was called\n"); + handler_called = 1; + return; +} + +void *d_thread_func(void *sig) +{ + + int signum = *(int *)sig; + printf("%d Pausing signal \n", signum); + int return_value = 0; + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + sigaction(signum, &act, 0); + return_value = sigpause(signum); + ERROR_IF(sigpause, return_value, != -1); + ERROR_IF(sigpause, errno, != EINTR); + result = 0; + if (return_value == -1) { + if (errno == EINTR) { + printf ("Test PASSED: sigpause returned -1 and set errno to EINTR\n"); + result = 0; + } else { + printf ("Test FAILED: sigpause did not set errno to EINTR\n"); + result = 1; + } + } else { + if (errno == EINTR) { + printf ("Test FAILED: sigpause did not return -1\n"); + } + printf ("Test FAILED: sigpause did not set errno to EINTR\n"); + printf ("Test FAILED: sigpause did not return -1\n"); + result = 1; + + } + sem = INMAIN; + return NULL; +} + + +int sigpause_error(int signum){ + pthread_t new_th; + + int status; + status = pthread_create(&new_th, NULL, d_thread_func, (void *)&signum); + ERROR_IF(pthread_create, status, != 0); + + usleep(100000); + + status = pthread_kill(new_th, signum); + ERROR_IF(pthread_kill, status, != 0); + + sem = INTHREAD; + while (sem == INTHREAD) + usleep(100000); + + if(result == 2) { + exit(EXIT_FAILURE); + } + if(result == 1) { + exit(EXIT_FAILURE); + } + + printf("Test PASSED\n"); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include +#include + +// This program verifies that sigpause() returns -1 and sets errno to EINVAL +// if passed an invalid signal number. + +#define INMAIN 0 +#define INTHREAD 1 + +volatile sig_atomic_t handler_called = 0; +int returned = 0; +int return_value = 2; +int result = 2; +int sem = INMAIN; + +void handler() { + // printf("signal was called\n"); + handler_called = 1; + return; +} + +int sigpause_invalid(){ + int return_value = 0; + + return_value = sigpause(-1); + + if (return_value == -1) { + if (errno == EINVAL) { + printf ("Test PASSED: sigpause returned -1 and set errno to EINVAL\n"); + return EXIT_SUCCESS; + } else { + printf ("Test FAILED: sigpause did not set errno to EINVAL\n"); + exit(EXIT_FAILURE); + } + } else { + printf ("Test FAILED: sigpause did not return -1\n"); + if (errno == EINVAL) { + printf ("Test FAILED: sigpause did not set errno to EINVAL\n"); + } + exit(EXIT_FAILURE); + } + return EXIT_SUCCESS; + +} + +int main(){ + sigpause_invalid(); + return EXIT_SUCCESS; +} + diff --git a/tests/signals/sigpause-pause.c b/tests/signals/sigpause-pause.c new file mode 100644 index 0000000000..e2a24c763e --- /dev/null +++ b/tests/signals/sigpause-pause.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// This program verifies that sigpause() removes sig from the signal mask. + +int handler_called = 0; + +void handler() { + handler_called = 1; + return; +} + +void *a_thread_func(void *sig) +{ + int status; + int signum = *(int *)sig; + printf("Pausing signal %s\n", signum); + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = handler; + status = sigemptyset(&act.sa_mask); + ERROR_IF(sigemptyset, status, != 0); + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, != 0); + status = sighold(signum); + ERROR_IF(sighold, status, != 0); + status = sigpause(signum); + ERROR_IF(sigpause, status, != 0); + + return NULL; +} + + + +int sigpause_basic(int signum) +{ + pthread_t new_th; + int status; + status = pthread_create(&new_th, NULL, a_thread_func, (void *)&signum); + ERROR_IF(pthread_create, status, != 0); + + usleep(100000); + + status = pthread_kill(new_th, signum); + ERROR_IF(pthread_kill, status, != 0); + + usleep(100000); + + if (handler_called != 1){ + prinft("handler wasn't called\n"); + exit(EXIT_FAILURE); + } + handler_called = 0; + + return EXIT_SUCCESS; +} + + + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// This program verifies that sigpause() restores sig to the signal mask before +// returning. + +volatile sig_atomic_t handler_called = false; + +void handler() { + handler_called = true; + return; +} + +void *c_thread_func(void *sig) +{ + int signum = *(int *)sig; + printf("%d !!!\n", signum); + + sigset_t pendingset; + + puts("before sigpause"); + + assert(!handler_called); + + if ((sigpause(signum) != -1) || (errno != EINTR)) { + puts("Test UNRESOLVED: sigpause didn't return -1 and/or didn't set errno correctly."); + exit(2); + return NULL; + } + assert(handler_called); + handler_called = false; + + int status = raise(signum); + ERROR_IF(raise, status, == -1); + + assert(!handler_called); + + sigpending(&pendingset); + if (sigismember(&pendingset, signum) == 1) { + puts("Test PASSED: signal mask was restored when sigpause returned."); + } + + return NULL; + +} + +int sigpause_revert(int signum) { + pthread_t new_th; + int status; + struct sigaction act; + + // Ensure thread inherits mask with signum blocked. + status = sighold(signum); + ERROR_IF(sighold, status, == -1); + + act.sa_flags = 0; + act.sa_handler = handler; + status = sigemptyset(&act.sa_mask); + ERROR_IF(sigemptyset, status, == -1); + status = sigaction(signum, &act, NULL); + ERROR_IF(sigaction, status, == -1); + + if((status = pthread_create(&new_th, NULL, c_thread_func, (void *)&signum)) != 0) + { + errno = status; + perror("Error creating thread"); + exit(EXIT_FAILURE); + } + + usleep(100000); + + if((status = pthread_kill(new_th, signum)) != 0) + { + errno = status; + perror("Test UNRESOLVED: Couldn't send signal to thread"); + exit(EXIT_FAILURE); + } + if ((status = pthread_join(new_th, NULL)) != 0) { + errno = status; + perror("failed to join thread"); + return EXIT_FAILURE; + } + + puts("Test PASSED"); + return EXIT_SUCCESS; +} + + + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// This program verifies that sigpause() suspends the calling process +// until it receives a signal. + +#define INMAIN 0 +#define INTHREAD 1 + +volatile sig_atomic_t handler_called = 0; +volatile atomic_bool completed = false; + +void handler() { + handler_called = 1; + return; +} +void *b_thread_func(void *code_raw) { + int *code = code_raw; + printf("Pausing signal %s\n", strsignal(*code)); + sigpause(*code); + assert(handler_called != 0); + *code = 0; + atomic_store(&completed, true); + + return NULL; +} + + +int sigpause_suspend(int signum) +{ + atomic_store(&completed, false); + int status; + + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, == -1); + + pthread_t new_th; + + int code = signum; + if ((status = pthread_create(&new_th, NULL, b_thread_func, (void *)&code)) != 0) { + errno = status; + perror("failed to create thread"); + return EXIT_FAILURE; + } + usleep(100000); + assert(!atomic_load(&completed)); + + if ((status = pthread_kill(new_th, signum)) != 0) { + errno = status; + perror("failed to kill thread"); + return EXIT_FAILURE; + } + + if ((status = pthread_join(new_th, NULL)) != 0) { + errno = status; + perror("failed to join thread"); + return EXIT_FAILURE; + } + + assert(code == 0); + assert(atomic_load(&completed)); + + return EXIT_SUCCESS; +} + + +int main(){ + // TODO: upper limit for i (gives OOB otherwise) + for (int i=1; i<31; i++){ + int sig = signals_list[i].signal; + if (sig == SIGKILL || sig == SIGSTOP){ + continue; + } + printf("For signal %s (%d)\n", strsignal(sig), sig); + sigpause_suspend(sig); + } + return EXIT_SUCCESS; +} + + + diff --git a/tests/signals/sigprocmask-10.c b/tests/signals/sigprocmask-10.c new file mode 100644 index 0000000000..f14c593bdf --- /dev/null +++ b/tests/signals/sigprocmask-10.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include "../test_helpers.h" + +// The thread's signal mask shall not be changed, if sigprocmask( ) fails. + +#define NUMSIGNALS 24 + +int is_changed(sigset_t set) { + + int i; + int siglist[] = {SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + for (i=0; i +#include +#include +#include "../test_helpers.h" + +// sigprocmask( ) shall return 0, Upon successful completion; otherwise, it shall return -1 +// and errno shall be set to indicate the error, and the process' signal mask shall be unchanged. + +int main() +{ + + sigset_t set; + sigaddset (&set, SIGABRT); + + int status; + status = sigprocmask(SIG_SETMASK, &set, NULL); + ERROR_IF(sigprocmask, status, != 0); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/sigprocmask-3.c b/tests/signals/sigprocmask-3.c new file mode 100644 index 0000000000..aee43032ca --- /dev/null +++ b/tests/signals/sigprocmask-3.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +// The resulting set shall be the union of the current set and the signal +// set pointed to by set, if the value of the argument how is SIG_BLOCK. + +volatile sig_atomic_t handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + handler_called = 1; +} + +int sigprocmask_block(int signum) +{ + int defaultsig = SIGALRM; + if (signum == SIGALRM) { + defaultsig = SIGHUP; + } + struct sigaction act; + sigset_t set1, set2, pending_set; + sigemptyset(&set1); + sigemptyset(&set2); + sigaddset(&set1, defaultsig); + sigaddset(&set2, signum); + + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + int status; + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, == -1); + + status = sigaction(defaultsig, &act, 0); + ERROR_IF(sigaction, status, == -1); + + status = sigprocmask(SIG_SETMASK, &set1, NULL); + ERROR_IF(sigprocmask, status, == -1); + + status = sigprocmask(SIG_UNBLOCK, &set2, NULL); + ERROR_IF(sigprocmask, status, == -1); + + printf("Raising %s\n", strsignal(signum)); + status = raise(signum); + ERROR_IF(raise, status, == -1); + + ERROR_IF(raise, handler_called, != 1); + handler_called = 0; + + printf("Raising %s\n", strsignal(defaultsig)); + status = raise(defaultsig); + ERROR_IF(raise, defaultsig, == -1); + + ERROR_IF(raise, handler_called, == 1); + + status = sigpending(&pending_set); + ERROR_IF(sigpending, status, == -1); + + status = sigismember(&pending_set, defaultsig); + ERROR_IF(sigismember, status, != 1); + + status = sigismember(&pending_set, signum); + ERROR_IF(sigismemeber, status, != 0); + + // printf("Test PASSED: signal was added to the process's signal mask\n"); + act.sa_handler = SIG_IGN; + sigaction(signum, &act, 0); + sigaction(defaultsig, &act, 0); + + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include + +// The previous mask shall be stored in the location pointed to by oset, if the argument oset is not a null pointer. + + +#define NUMSIGNALS 26 + +int main() +{ + sigset_t oactl, tempset; + int i, j, test_failed=0; + + int siglist[] = {SIGABRT, SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + for (i=0; i 0) { + for (j=0; j +#include +#include + +// The value of the argument how is not significant and the process's signal mask shall be unchanged, and +// thus the call can be used to enquire about currently blocked signals, if the argument set is a null +// pointer. + +#define NUMSIGNALS 25 + +int is_changed(sigset_t set, int sig) { + + int i; + int siglist[] = {SIGABRT, SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + if (sigismember(&set, sig) != 1) { + return 1; + } + for (i=0; i +#include +#include + + +#define NUMSIGNALS 25 + +int is_changed(sigset_t set, int sig) { + + int i; + int siglist[] = {SIGABRT, SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + if (sigismember(&set, sig) != 1) { + return 1; + } + for (i=0; i +#include +#include + +#define NUMSIGNALS 25 + +int is_changed(sigset_t set, int sig) { + + int i; + int siglist[] = {SIGABRT, SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + if (sigismember(&set, sig) != 1) { + return 1; + } + for (i=0; i +#include +#include +#include "../test_helpers.h" + +// After the call to sigprocmask(), if there are any pending unblocked signals, at least one of those +// signals shall be delivered before the call to sigprocmask() returns. + +int handler_called = 0; +int sigprocmask_return_val = 1; /* some value that's not a 1 or 0 */ + +void sig_handler(int signo) +{ + (void) signo; + handler_called = 1; + if (sigprocmask_return_val != 1) { + printf("FAIL: sigprocmask() returned before signal was delivered.\n"); + exit(EXIT_FAILURE); + } +} + +int main() +{ + struct sigaction act; + sigset_t blocked_set1; + sigemptyset(&blocked_set1); + sigaddset(&blocked_set1, SIGABRT); + + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + int status; + + status = sigaction(SIGABRT, &act, 0); + ERROR_IF(sigaction, status, == -1); + + status = sigprocmask(SIG_SETMASK, &blocked_set1, NULL); + ERROR_IF(sigprocmask, status, == -1); + + status = raise(SIGABRT); + ERROR_IF(raise, status, == -1); + + sigprocmask_return_val = sigprocmask(SIG_UNBLOCK, &blocked_set1, NULL); + + ERROR_IF(sigprocmask, sigprocmask_return_val, != 0); + + ERROR_IF(raise, handler_called, != 1); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/sigprocmask-9.c b/tests/signals/sigprocmask-9.c new file mode 100644 index 0000000000..168eed88c4 --- /dev/null +++ b/tests/signals/sigprocmask-9.c @@ -0,0 +1,28 @@ +#include "../test_helpers.h" +#include +#include +#include +#include + +// Attempt to add SIGKILL and SIGSTOP to the process's signal mask and +// verify that: +// - They do not get added. +// - sigprocmask() does not return -1. + +int main() { + sigset_t set1, set2; + int ret; + + sigemptyset(&set1); + sigemptyset(&set2); + sigaddset(&set1, SIGKILL); + sigaddset(&set1, SIGSTOP); + + ret = sigprocmask(SIG_SETMASK, &set1, NULL); + ERROR_IF(sigprocmask, ret, == -1); + ret = sigprocmask(SIG_SETMASK, NULL, &set2); + ERROR_IF(sigprocmask, ret, == -1); + + assert(!sigismember(&set2, SIGKILL)); + assert(!sigismember(&set2, SIGSTOP)); +} diff --git a/tests/signals/sigprocmask-blocksingle.c b/tests/signals/sigprocmask-blocksingle.c new file mode 100644 index 0000000000..5dd3d5a3c0 --- /dev/null +++ b/tests/signals/sigprocmask-blocksingle.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + handler_called = 1; +} + +int sigprocmask_block(int signum) +{ + struct sigaction act; + sigset_t blocked_set, pending_set; + sigemptyset(&blocked_set); + sigaddset(&blocked_set, signum); + + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + int status; + status = sigaction(signum, &act, 0); + ERROR_IF(sigaction, status, == -1); + + status = sigprocmask(SIG_SETMASK, &blocked_set, NULL); + ERROR_IF(sigprocmask, status, == -1); + + status = raise(signum); + ERROR_IF(raise, status, == -1); + + if (handler_called) { + printf("FAIL: Signal was not blocked\n"); + exit(EXIT_FAILURE); + } + + status = sigpending(&pending_set); + ERROR_IF(sigpending, status, == -1); + + status = sigismember(&pending_set, signum); + ERROR_IF(sigismember, status, != 1); + + act.sa_handler = SIG_IGN; + sigaction(signum, &act, 0); + + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +volatile sig_atomic_t handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + handler_called = 1; +} + +int sigrelse_test(int signum) +{ + // needs to be reset + handler_called = 0; + + struct sigaction act; + + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + int status; + status = sigaction(signum, &act, NULL); + ERROR_IF(sigaction, status, == -1); + + status = sighold(signum); + ERROR_IF(sighold, status, == -1); + + assert(handler_called == 0); + + status = raise(signum); + ERROR_IF(raise, status, == -1); + + assert(handler_called == 0); + + status = sigrelse(signum); + ERROR_IF(sigrelse, status, == -1); + + assert(handler_called == 1); + // if (handler_called) { + // printf("PASS: %d successfully removed from signal mask\n", signum); + // handler_called = 0; + // return EXIT_SUCCESS; + // } + // printf("FAIL\n"); + // exit(EXIT_FAILURE); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include "../test_helpers.h" + +int main() +{ + int status; + status = (int)sigrelse(SIGABRT); + ERROR_IF(sigrelse, status, != 0); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/sigrelse-3.c b/tests/signals/sigrelse-3.c new file mode 100644 index 0000000000..980bca35bd --- /dev/null +++ b/tests/signals/sigrelse-3.c @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include "../test_helpers.h" + +int main() +{ + int status; + status = (int)sigrelse(100000); + ERROR_IF(sigrelse, status, != -1); + ERROR_IF(sigrelse, errno, != EINVAL); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/signals/sigset-1.c b/tests/signals/sigset-1.c new file mode 100644 index 0000000000..fe2f04df1c --- /dev/null +++ b/tests/signals/sigset-1.c @@ -0,0 +1,46 @@ +#include +#include +#include + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + printf("SIGURG called. Inside handler\n"); + handler_called = 1; +} + +int sigset_test(int signum) +{ + + struct sigaction act; + act.sa_handler = sig_handler; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + + if (sigaction(signum, &act, 0) != 0) { + perror("Unexpected error while using sigaction()"); + exit(EXIT_FAILURE); + } + + if (sigset(signum,SIG_DFL) != sig_handler) { + perror("Unexpected error while using signal()"); + exit(EXIT_FAILURE); + } + + raise(signum); + + if (handler_called == 1) { + printf("Test FAILED: handler was called even though default was expected\n"); + exit(EXIT_FAILURE); + } + printf("test passed, the signal was ignored\n"); + return EXIT_SUCCESS; +} + +int main(){ + sigset_test(SIGURG); + return EXIT_SUCCESS; +} + diff --git a/tests/signals/sigset-10.c b/tests/signals/sigset-10.c new file mode 100644 index 0000000000..68f8d37808 --- /dev/null +++ b/tests/signals/sigset-10.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include "../test_helpers.h" + +int main() +{ + void (*status) (int); + status = sigset(SIGKILL,SIG_IGN); + assert(status == SIG_ERR); + assert(errno == EINVAL); + // if (sigset(SIGKILL,SIG_IGN) == SIG_ERR) { + // if (errno != EINVAL) { + // printf("Test FAILED: sigset() returned SIG_ERR but didn't set errno to EINVAL\n"); + // exit(EXIT_FAILURE); + // } + // } else { + // printf("Test FAILED: sigset() didn't return SIG_ERROR even though SIGKILL was passed to it\n"); + // exit(EXIT_FAILURE); + // } + // printf("test passed: error was set successfully\n"); + return EXIT_SUCCESS; +} diff --git a/tests/signals/sigset-2.c b/tests/signals/sigset-2.c new file mode 100644 index 0000000000..17ba64392b --- /dev/null +++ b/tests/signals/sigset-2.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + printf("SIGUSR1 called. Inside handler\n"); + handler_called = 1; +} + +int sigset_test2(int signum) +{ + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = sig_handler; + sigemptyset(&act.sa_mask); + + if (sigaction(signum, &act, 0) != 0) { + perror("Unexpected error while using sigaction()"); + exit(EXIT_FAILURE); + } + + if (sigset(signum,SIG_IGN) != sig_handler) { + perror("Unexpected error while using signal()"); + exit(EXIT_FAILURE); + } + + raise(signum); + + if (handler_called == 1) { + printf("Test FAILED: handler was called even though ignore was expected\n"); + exit(EXIT_FAILURE); + } + printf("test %d passed, the signal was ignored\n", signum); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int handler_called = 0; + +void sig_handler(int signo) +{ + (void) signo; + // printf("SIGCHLD called. Inside handler\n"); + handler_called = 1; +} + +int sigset_test3(int signum) +{ + if (sigset(signum, sig_handler) == SIG_ERR) { + perror("Unexpected error while using sigset()"); + exit(EXIT_FAILURE); + } + + raise(signum); + + if (handler_called != 1) { + printf("Test FAILED: handler wasn't called even though it was expected\n"); + exit(EXIT_FAILURE); + } + printf("test %d passed, handler was called\n", signum); + handler_called = 0; + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +int signal_blocked = 0; +int count = 1; + +void sig_handler(int signo) +{ + (void) signo; + // printf("SIGCHLD called. Inside handler\n"); + sigset_t mask; + sigprocmask(SIG_SETMASK, NULL, &mask); + if (count == SIGKILL || count == SIGSTOP){ + count++; + } + if(sigismember(&mask, count)) { + signal_blocked = 1; + } + count++; +} + +int sigset_test4(int signum) +{ + if (sigset(signum, sig_handler) == SIG_ERR) { + perror("Unexpected error while using sigset()"); + exit(EXIT_FAILURE); + } + + raise(signum); + + if (signal_blocked != 1) { + printf("Test FAILED: signal was not added to the mask before the handler was executed\n"); + exit(EXIT_FAILURE); + } + signal_blocked = 0; + printf("test %d passed: the singal was added to the mask before the handler was executed\n", signum); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +#define NUMSIGNALS 26 + +bool is_empty(sigset_t *set) { + + int i; + int siglist[25] = {SIGABRT, SIGALRM, SIGBUS, SIGCHLD, + SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, + SIGPIPE, SIGQUIT, SIGSEGV, + SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, + SIGUSR1, SIGUSR2, SIGPROF, SIGSYS, + SIGTRAP, SIGURG, SIGVTALRM, SIGXCPU, SIGXFSZ }; + + for (i=0; i<25; i++) { + if (sigismember(set, siglist[i]) != 0) + return false; + } + return true; +} + +void sig_handler(int signo) +{ + printf("%d called. Inside handler\n", signo); +} + +int sigset_test5(int signum) +{ + sigset_t mask; + sigemptyset(&mask); + + sigprocmask(SIG_SETMASK, &mask, NULL); + void (*status) (int); + + status = sigset(signum, sig_handler); + ERROR_IF(sigset, status, == SIG_ERR); + // if (sigset(signum, sig_handler) == SIG_ERR) { + // perror("Unexpected error while using sigset()"); + // exit(EXIT_FAILURE); + // } + + raise(signum); + sigprocmask(SIG_SETMASK, NULL, &mask); + bool status1 = is_empty(&mask); + assert(status1); + // if (is_empty(&mask) != 1) { + // printf("Test FAILED: signal mask should be empty\n"); + // exit(EXIT_FAILURE); + // } + // printf("sig %d was successfully removed from the mask when handler returned\n", signum); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include "signals_list.h" +#include "../test_helpers.h" + +void sig_handler(int signo) +{ + (void) signo; + printf("SIGUSR1 called. Inside handler\n"); +} + +int sigset_test9(int signum) +{ + struct sigaction act; + act.sa_flags = 0; + act.sa_handler = sig_handler; + sigemptyset(&act.sa_mask); + + if (sigaction(signum, &act, 0) != 0) { + perror("Unexpected error while using sigaction()"); + exit(EXIT_FAILURE); + } + + if (sigset(signum,SIG_DFL) != sig_handler) { + printf("Test FAILED: sigset didn't return myhandler even though it was SIGUSR1's original disposition\n"); + exit(EXIT_FAILURE); + } + printf("test %d passed \n", signum); + return EXIT_SUCCESS; +} + +int main(){ + for (int i=1; i +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +#define THE_SIG SIGRTMIN + +volatile sig_atomic_t num = 0; + +int parent; + +void validate(int sig, const siginfo_t *info) +{ + assert(sig == THE_SIG); + assert(info != NULL); + assert(info->si_signo == THE_SIG); + assert(info->si_value.sival_int == num); + assert(info->si_code == SI_QUEUE); + assert(info->si_pid == parent); +} + +void action(int sig, siginfo_t *info, void *context) +{ + (void)context; + assert(context != NULL); + validate(sig, info); + num++; +} + +int main(void) +{ + + int status, fds[2]; + + struct utsname utsname; + + status = uname(&utsname); + if (status == 0) { + if (strncmp(utsname.sysname, "Linux", 6) == 0) { + printf("Test is not supported on Linux, relibc's siginfo_t is not compatible.\n"); + return EXIT_SUCCESS; + } + } + status = pipe(fds); + ERROR_IF(pipe, status, == -1); + + parent = getpid(); + assert(parent != 0); + + sigset_t set, mask; + status = sigfillset(&mask); + ERROR_IF(sigfillset, status, == -1); + status = sigdelset(&mask, SIGSEGV); + ERROR_IF(sigdelset, status, == -1); + status = sigdelset(&mask, SIGBUS); + ERROR_IF(sigdelset, status, == -1); + status = sigdelset(&mask, SIGILL); + ERROR_IF(sigdelset, status, == -1); + status = sigdelset(&mask, SIGFPE); + ERROR_IF(sigdelset, status, == -1); + status = sigdelset(&mask, SIGINT); + ERROR_IF(sigdelset, status, == -1); + status = sigprocmask(SIG_SETMASK, &mask, NULL); + ERROR_IF(sigprocmask, status, == -1); + + status = sigemptyset(&set); + ERROR_IF(sigemptyset, status, == -1); + status = sigaddset(&set, THE_SIG); + ERROR_IF(sigaddset, status, == -1); + + sigset_t empty_set; + status = sigemptyset(&empty_set); + ERROR_IF(sigemptyset, status, == -1); + + int child = fork(); + ERROR_IF(fork, child, == -1); + + status = close(fds[child == 0 ? 0 : 1]); + ERROR_IF(close, status, == -1); + + struct sigaction sa; + memcpy(&sa.sa_mask, &set, sizeof(sigset_t)); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = action; + + status = sigaction(THE_SIG, &sa, NULL); + ERROR_IF(sigaction, status, == -1); + + if (child == 0) + { + assert(num == 0); + siginfo_t info; + struct timespec t = (struct timespec){.tv_sec = 1, .tv_nsec = 200000000}; + status = sigtimedwait(&set, &info, &t); + ERROR_IF(sigtimedwait, status, == -1); + assert(status == THE_SIG); + validate(THE_SIG, &info); + assert(num == 0); // ensure no signal handler ran + + num++; + + // TODO: check status + status = sigsuspend(&empty_set); + if (status == -1) + { + UNEXP_IF(sigsuspend, errno, != EINTR); + } + + assert(num == 2); // ensure signal handler ran + + status = sigprocmask(SIG_SETMASK, &empty_set, NULL); + ERROR_IF(sigprocmask, status, == -1); + + while (num < 31) + { + } + + status = write(fds[1], "A", 1); + ERROR_IF(write, status, == -1); + } + else + { + struct timespec t = (struct timespec){.tv_sec = 0, .tv_nsec = 100000000}; + status = nanosleep(&t, NULL); + ERROR_IF(nanosleep, status, < 0); + + for (int n = 0; n <= 31; n++) + { + status = sigqueue(child, THE_SIG, (union sigval){.sival_int = n}); + ERROR_IF(sigqueue, status, == -1); + } + char buf[1]; + status = read(fds[0], buf, 1); + ERROR_IF(read, status, == -1); + + pid_t wait_pid = 0; + int wait_status = 0; + wait_pid = wait(&wait_status); + ERROR_IF(wait, wait_pid, < 0); + UNEXP_IF(wait, wait_pid, != child); + if (!WIFEXITED(wait_status) || WEXITSTATUS(wait_status) != EXIT_SUCCESS) + { + fprintf(stderr, "Unexpected result, WIFEXITED %s, WEXITSTATUS %d\n", + WIFEXITED(wait_status) ? "true" : "false", WEXITSTATUS(wait_status)); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/tests/sigsetjmp.c b/tests/sigsetjmp.c new file mode 100644 index 0000000000..0063d21528 --- /dev/null +++ b/tests/sigsetjmp.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int main() { +sigjmp_buf jb; + +if (sigsetjmp(jb, 1)) { +printf("Jump done.\n"); +} else { +printf ("Starting jump...\n"); +siglongjmp(jb, 1); +} +return 0; +} diff --git a/tests/src/main.rs b/tests/src/main.rs new file mode 100644 index 0000000000..69f3024766 --- /dev/null +++ b/tests/src/main.rs @@ -0,0 +1,315 @@ +#![feature(exit_status_error)] + +use std::{ + env, fs, + io::{self, Read, Write}, + path::{Path, PathBuf}, + process::{self, Command, ExitStatus, Stdio}, + sync::mpsc, + thread, + time::{Duration, Instant}, +}; + +fn find_expected_dir() -> Option { + let mut current_dir = env::current_exe() + .ok() + .and_then(|p| p.parent().map(|parent| parent.to_path_buf())) + .unwrap_or_else(|| PathBuf::from(".")); + + let mut found_expected_dir = None; + + while current_dir.pop() { + let check = current_dir.join("expected"); + if check.is_dir() { + found_expected_dir = Some(check); + break; + } + } + + if found_expected_dir.is_none() { + if let Ok(cwd) = env::current_dir() { + let check = cwd.join("expected"); + if check.is_dir() { + found_expected_dir = Some(check); + } + } + } + + found_expected_dir +} + +fn expected( + expected_dir: &Path, + bin: &str, + kind: &str, + generated: &[u8], + status: ExitStatus, +) -> Result<(), String> { + let expect_file = Path::new(bin).with_added_extension(kind); + let components: Vec<_> = expect_file + .components() + .filter_map(|c| c.as_os_str().to_str()) + .rev() + .collect(); + + let mut expected_file = None; + for i in 0..components.len() { + let sub_path: Vec<_> = components[0..=i] + .iter() + .rev() + .map(|s| s.to_string()) + .collect(); + + let check_file = expected_dir.join(sub_path.join("/")); + if check_file.is_file() { + expected_file = Some(fs::read(check_file)); + break; + } + } + + let expect_name = components.first().unwrap(); + let expected = match expected_file { + Some(Ok(ok)) => ok, + Some(Err(err)) => { + return Err(format!("{} failed to read {}: {}", bin, expect_name, err)); + } + None => { + if kind == "stderr" { + // missing stderr file, assume test expect none emitted + vec![] + } else { + return Err(format!("{} expected file not found: {}", bin, expect_name)); + } + } + }; + + if expected != generated { + println!("# {}: {}: expected #", bin, kind); + io::stdout().write(&expected).unwrap(); + + println!("# {}: {}: generated #", bin, kind); + io::stdout().write(generated).unwrap(); + + return Err(format!( + "{} failed - retcode {}, {} mismatch", + bin, status, kind + )); + } + + Ok(()) +} + +const STATUS_ONLY: &str = "-s"; + +fn print_tabbed(output: Vec, name: &str) { + if let Ok(stdout) = String::from_utf8(output) { + let stdout: Vec = stdout.trim().lines().map(|p| format!(" {}", p)).collect(); + let stdout = stdout.join("\n"); + if stdout.as_str() == "" { + println!("{name}: empty content"); + } else { + println!("{name}:\n{}", stdout); + } + } else { + println!("can't print out {name}: not utf8"); + } +} + +#[cfg(target_os = "redox")] +fn print_kernel_version() { + let Ok(uname) = fs::read_to_string("/scheme/sys/uname") else { + eprintln!("kernel version: unable to get one"); + return; + }; + eprintln!( + "kernel version: {}", + uname + .lines() + .last() + .map(|s| s.get(..8)) + .flatten() + .unwrap_or("?") + ); +} + +fn main() { + let mut failures = Vec::new(); + let timeout = Duration::from_secs(10); + let slowtime = Duration::from_secs(1); + let bins: Vec = env::args().skip(1).collect(); + let single_test = bins.len() == 1; + let expected_dir = find_expected_dir(); + + #[cfg(target_os = "redox")] + print_kernel_version(); + + for bin in bins { + let status_only = bin.starts_with(STATUS_ONLY); + let bin = if bin.starts_with(STATUS_ONLY) { + bin.strip_prefix(STATUS_ONLY).unwrap().to_string() + } else { + bin + }; + + println!("# {} #", bin); + + let start_time = Instant::now(); + + let (tx, rx) = mpsc::channel(); + let bin_for_spawn = bin.clone(); + + // There's an issue when pthread hangs, spawn() also hangs, so let's use separate thread to spawn + thread::spawn(move || { + let result = Command::new(&bin_for_spawn) + .arg("test") + .arg("args") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(); + tx.send(result).expect("Can't send"); + }); + + let mut status = None; + let mut child = None; + + loop { + if start_time.elapsed() > timeout { + let failure = format!( + "\x1b[0;91;49m{}: assumed hangs after {}ms\x1b[0m", + bin, + start_time.elapsed().as_millis() + ); + println!("{}", failure); + failures.push(failure); + break; + } + + if start_time.elapsed() > slowtime { + println!("# waiting {}ms", start_time.elapsed().as_millis()); + } + + let c = match &mut child { + Some(child) => child, + None => match rx.try_recv() { + Ok(Ok(c)) => { + child = Some(c); + continue; + } + Ok(Err(err)) => { + let failure = format!("{}: failed to execute: {}", bin, err); + println!("{}", failure); + failures.push(failure); + break; + } + Err(mpsc::TryRecvError::Disconnected) => { + let failure = format!("{}: failed to execute: thread died", bin); + println!("{}", failure); + failures.push(failure); + break; + } + Err(mpsc::TryRecvError::Empty) => { + thread::sleep(Duration::from_millis(if start_time.elapsed() < slowtime { + 25 + } else { + 500 + })); + continue; + } + }, + }; + + match c.try_wait() { + Ok(Some(s)) => { + status = Some(s); + break; + } + Ok(None) => { + thread::sleep(Duration::from_millis(if start_time.elapsed() < slowtime { + 25 + } else { + 500 + })); + } + Err(e) => { + failures.push(format!("{}: error waiting: {}", bin, e)); + break; + } + } + } + + match (status, child) { + (exit_status, Some(mut child)) => { + if exit_status.is_none() { + match child.kill() { + Ok(_) => {} + Err(e) => { + // if can't be killed then getting the output will hang + println!("Unable to kill, can't get output: {:?}", e); + continue; + } + } + } + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + child.stdout.unwrap().read_to_end(&mut stdout).unwrap(); + child.stderr.unwrap().read_to_end(&mut stderr).unwrap(); + + let Some(exit_status) = exit_status else { + // hangs + print_tabbed(stdout, "stdout"); + print_tabbed(stderr, "stderr"); + continue; + }; + + if !status_only { + let Some(expected_dir) = &expected_dir else { + eprintln!("Expected directory not found"); + process::exit(1); + }; + + if let Err(failure) = + expected(expected_dir, &bin, "stdout", &stdout, exit_status) + { + println!("{}", failure); + failures.push(failure); + } + if let Err(failure) = + expected(expected_dir, &bin, "stderr", &stderr, exit_status) + { + println!("{}", failure); + failures.push(failure); + } + } + + if let Err(e) = exit_status.exit_ok() { + let failure = format!("# {}: {}", bin, e); + println!("{}", failure); + failures.push(failure); + } + + if single_test { + print_tabbed(stdout, "stdout"); + print_tabbed(stderr, "stderr"); + } + } + (_, _) => { + continue; + } + } + + if start_time.elapsed() > slowtime { + println!( + "\x1b[0;93;49m test exection took too long: {}ms\x1b[0m", + start_time.elapsed().as_millis() + ); + } + } + + if !failures.is_empty() { + println!("\x1b[1;91;49m# FAILURES #\x1b[0m"); + for failure in failures { + println!("{}", failure); + } + process::exit(1); + } +} diff --git a/tests/stdio/ctermid.c b/tests/stdio/ctermid.c new file mode 100644 index 0000000000..5a291cf607 --- /dev/null +++ b/tests/stdio/ctermid.c @@ -0,0 +1,22 @@ +#include +#include + +#define DEVTTY "/dev/tty" + +int main(void) +{ + char *name = ctermid(NULL); + if(strcmp(name, DEVTTY) != 0) { + printf("ctermid name differs: expected %s, got: %s\n", DEVTTY, name); + return 1; + } + + char name2[L_ctermid]; + ctermid(name2); + if(strcmp(name, DEVTTY) != 0) { + printf("ctermid name2 differs: expected %s, got: %s\n", DEVTTY, name2); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/tests/stdio/dprintf.c b/tests/stdio/dprintf.c new file mode 100644 index 0000000000..b61d5cfa97 --- /dev/null +++ b/tests/stdio/dprintf.c @@ -0,0 +1,23 @@ +#include +#include + +#include "test_helpers.h" + +int main(void) { + const char *msg = "Hello, %s"; + + int fd = dup(STDOUT_FILENO); + ERROR_IF(dup, fd, == -1); + + int result = dprintf(fd, msg, "world"); + ERROR_IF(dprintf, result, != sizeof("Hello, world") - 1); + UNEXP_IF(dprintf, result, < 0); + + result = dprintf(fd, "\na\n"); + UNEXP_IF(dprintf, result, < 0); + ERROR_IF(dprintf, result, != sizeof("\na\n") - 1); + + close(fd); + + return 0; +} diff --git a/tests/stdio/fread.c b/tests/stdio/fread.c index 75eb7ff8f6..cc084d1cd4 100644 --- a/tests/stdio/fread.c +++ b/tests/stdio/fread.c @@ -10,9 +10,15 @@ int main(void) { char buf[33] = { 0 }; for (int i = 1; i <= 32; ++i) { - if (fread(buf, 1, i, fp) < 0) { - perror("fread"); - exit(EXIT_FAILURE); + size_t nread = fread(buf, 1, i, fp); + if (nread == 0) { + if (feof(fp)) { + fprintf(stderr, "early EOF\n"); + return EXIT_FAILURE; + } else { + perror("fread"); + return EXIT_FAILURE; + } } buf[i] = 0; diff --git a/tests/stdio/freopen.c b/tests/stdio/freopen.c index 3fb6069cbf..f1415399bc 100644 --- a/tests/stdio/freopen.c +++ b/tests/stdio/freopen.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -32,7 +33,7 @@ int main(void) { &test_reopen_opens_file, &test_reopen_resets_orientation, }; - for(int i=0; i +#include +#include +#include + +#include "test_helpers.h" + +const char *INFILE_NAME = "stdio/getline.in"; +const size_t INFILE_LINES = 7; +const size_t OVERREAD_LINES = 3; + +void test_null_args(); + +void test_ferror(); + +void test_read_and_overread(); + +int main(void) { + setbuf(stdout, NULL); + + // test if supplying NULL for either pointer correctly yields EINVAL + test_null_args(); + + // test reading stream with error flag set + test_ferror(); + + // "normal" case - read all `INFILE_LINES` lines of the test file and then overread by 3 + test_read_and_overread(); +} + +void test_null_args() { + + + // Basics: NULL handling + // Test all combinations of NULL pointers since we fail on any one. + // + // I can't explicitly find that stream can't be NULL but nothing else + // makes sense, so let's return EINVAL +#define TEST_FOR_EINVAL(x) \ + do \ + { \ + size_t n = 0; \ + char *lineptr = NULL; \ + ssize_t status = 0; \ + FILE *stream = NULL; \ + \ + stream = fopen(INFILE_NAME, "r"); \ + ERROR_IF(fopen, stream, == NULL); \ + ERROR_IF(fopen, ferror(stream), ); \ + \ + status = x; \ + printf("%3zu: %s\n => ", ++counter, #x); \ + CHECK_AND_PRINT_ERRNO(EINVAL); \ + assert(status == -1 && errno == EINVAL); \ + \ + if (stream != NULL) \ + fclose(stream); \ + if (lineptr != NULL) \ + free(lineptr); \ + stream = NULL; \ + errno = 0; \ + (void)n; \ + } while (0); + + static size_t counter = 0; + + TEST_FOR_EINVAL(getline(NULL, NULL, stream)); + + TEST_FOR_EINVAL(getline(NULL, &n, stream)); + TEST_FOR_EINVAL(getline(&lineptr, NULL, stream)); + + // don't always use, as glibc doesn't tolerate stream being NULL or delim being out-of-range for char +#ifdef _RELIBC_STDIO_H + TEST_FOR_EINVAL(getline(NULL, NULL, NULL)); + TEST_FOR_EINVAL(getline(&lineptr, NULL, NULL)); + TEST_FOR_EINVAL(getline(NULL, &n, NULL)); + TEST_FOR_EINVAL(getline(&lineptr, &n, NULL)); + + // test if using delim out of char range correctly causes EINVAL + // POSIX specifies UB, so we try to be more helpful + TEST_FOR_EINVAL(getdelim(&lineptr, &n, 25600, stream)); +#endif + +#undef TEST_FOR_EINVAL + printf("\n"); +} + +void test_ferror() { + // TODO test behavior on error flag set +} + +void test_read_and_overread() { + // Basic use cases + // Read all INFILE_LINES lines of sample input, then overread OVERREAD_LINES times + + size_t n = 0; + char *lineptr = NULL; + ssize_t status; + FILE *stream = NULL; + + stream = fopen(INFILE_NAME, "r"); + + for (size_t i = 0; i < INFILE_LINES; ++i) { + /// "Upon successful completion, the getline() and getdelim() functions + /// shall return the number of bytes written into the buffer, including + /// the delimiter character if one was encountered before EOF, but + /// excluding the terminating NUL character." + printf("%3zu: ", i + 1); + status = getline(&lineptr, &n, stream); + + /// "If the end-of-file indicator for the stream is set, or if no + /// characters were read and the stream is at end-of-file, the + /// end-of-file indicator for the stream shall be set and the function + /// shall return -1." + assert(!(status == -1 && feof(stream))); + + /// "If an error occurs, the error indicator for the stream shall be + /// set, and the function shall return -1 and set errno to indicate the + /// error." + ERROR_IF(getline, status, == -1 && ferror(stream)); + + // This should only execute if the other error cases (with stream flags set) did not abort. + UNEXP_IF(getline, status, == -1); + + // Print length and content to verify. Also test if the buffer is big + // enough and if strlen of input matches the return value. + // Although this doesn't HAVE to be true, as the input could contain + // NUL bytes, ours doesn't. + printf("status = %zi, strlen = %zu, feof = %s, ferror = %s\n |>%s", + status, strlen(lineptr), feof(stream) ? "1" : "0", ferror(stream) ? "1" : "0", lineptr); + // fflush(stdout); + + assert(strlen(lineptr) == (size_t) status); // we can cast to size_t since we + assert(n >= (size_t) status + 1); // UNEXP_IF against status being -1 + } + printf("\n"); + + // OVERREAD + + for (size_t i = 0; i < OVERREAD_LINES; ++i) { + /// "If the end-of-file indicator for the stream is set, or if no + /// characters were read and the stream is at end-of-file, the + /// end-of-file indicator for the stream shall be set and the function + /// shall return -1." + status = getline(&lineptr, &n, stream); + printf("overread %zu, status = %zi, feof = %s, ferror = %s\n", + i + 1, + status, + feof(stream) ? "1" : "0", ferror(stream) ? "1" : "0"); + if (i == 0) { + assert(status == -1); + assert(feof(stream)); + assert(!ferror(stream)); + } + printf("|~%s\n", lineptr); + } + + // cleanup + fclose(stream); + free(lineptr); +} diff --git a/tests/stdio/getline.in b/tests/stdio/getline.in new file mode 100644 index 0000000000..8399a9b4cb --- /dev/null +++ b/tests/stdio/getline.in @@ -0,0 +1,7 @@ +Space: the final frontier. + +These are the voyages of the starship Enterprise. +Its continuing mission: +- to explore strange new worlds; +- to seek out new life and new civilizations; +- to boldly go where no one has gone before! diff --git a/tests/stdio/mutex.c b/tests/stdio/mutex.c index 3d9be9d8db..dc729d3c5f 100644 --- a/tests/stdio/mutex.c +++ b/tests/stdio/mutex.c @@ -1,30 +1,47 @@ #include #include +#include #include "test_helpers.h" -int main(void) { - FILE *f = fopen("stdio/stdio.in", "r"); - ERROR_IF(fopen, f, == NULL); +FILE *f; - flockfile(f); +void* thread_wontlock(void* arg) { + (void)arg; - // Commenting this out should cause a deadlock: - // flockfile(f); + int result = ftrylockfile(f); + UNEXP_IF(ftrylockfile, result, == 0); - if (!ftrylockfile(f)) { - puts("Mutex unlocked but it shouldn't be"); - exit(EXIT_FAILURE); - } - funlockfile(f); - - if (ftrylockfile(f)) { - puts("Mutex locked but it shouldn't be"); - exit(EXIT_FAILURE); - } - if (!ftrylockfile(f)) { - puts("Mutex unlocked but it shouldn't be"); - exit(EXIT_FAILURE); - } - funlockfile(f); + return NULL; +} + +void* thread_willlock(void* arg) { + (void)arg; + + int result = ftrylockfile(f); + UNEXP_IF(ftrylockfile, result, != 0); + + return NULL; +} + +int main(void) { + f = fopen("stdio/stdio.in", "r"); + ERROR_IF(fopen, f, == NULL); + flockfile(f); + flockfile(f); + thread_willlock(NULL); + + pthread_t thread; + for (int i = 1; i <= 3; i++) { + pthread_create(&thread, NULL, thread_wontlock, NULL); + pthread_join(thread, NULL); + funlockfile(f); + } + + pthread_create(&thread, NULL, thread_willlock, NULL); + pthread_join(thread, NULL); + + // TODO: glibc refuses to quit test without this + // but in relibc, it will result in EPERM + // funlockfile(f); } diff --git a/tests/stdio/popen.c b/tests/stdio/popen.c index 56a2ab1043..0b9cc519bb 100644 --- a/tests/stdio/popen.c +++ b/tests/stdio/popen.c @@ -7,9 +7,11 @@ int main(void) { FILE *fp = popen("ls -1 example_dir", "r"); ERROR_IF(popen, fp, == NULL); + int lineno = 0; char path[256] = { 0 }; while (fgets(path, 256, fp) != NULL) { - printf("%s", path); + lineno++; + printf("Line %d: %s", lineno, path); } int status = pclose(fp); diff --git a/tests/stdio/printf.c b/tests/stdio/printf.c index 073a609ca4..f794782793 100644 --- a/tests/stdio/printf.c +++ b/tests/stdio/printf.c @@ -1,3 +1,4 @@ +#include #include #include // free() #include // INFINITY, NAN constants @@ -64,6 +65,9 @@ int main(void) { printf("%G\n", 0.00001); printf("%E\n", 0.00001); + printf("%2$0*1$.*3$lf\n", 9, 1234.56, 2); + printf("%2$0*1$.*3$Lf\n", 9, 1234.56L, 2); + double nonfinites[] = {INFINITY, -INFINITY, NAN, -NAN}; char *float_formats[] = {"%e", "%E", "%f", "%F", "%g", "%G"}; puts("\nNon-finite float madness:"); @@ -90,4 +94,15 @@ int main(void) { res = asprintf(&s, "test %s %d", "string", 2); printf("printed: %s, value: %d\n", s, res); free(s); + + errno = EOWNERDEAD; + printf("\n%%m: %m\n"); + + puts("\nC23:"); + printf("Binary %%b: %b\n", 4); + printf("Binary %%b alternate: %#b\n", 4); + printf("Binary %%B: %B\n", 4); + printf("Binary %%B alternate: %#B\n", 4); + + return EXIT_SUCCESS; } diff --git a/tests/stdio/putc_unlocked.c b/tests/stdio/putc_unlocked.c new file mode 100644 index 0000000000..693bdafafe --- /dev/null +++ b/tests/stdio/putc_unlocked.c @@ -0,0 +1,23 @@ +#include +#include + +#include "test_helpers.h" + +int main(void) +{ + FILE* fp = tmpfile(); + assert(fp != NULL); + flockfile(fp); + int c = 'c', r = 0; + r = putc_unlocked(c, fp); + ERROR_IF(putc_unlocked, r, == EOF); + r = fflush(fp); + ERROR_IF(fflush, r, == EOF); + funlockfile(fp); + // make sure unlock works + r = putc(c, fp); + ERROR_IF(putc, r, == EOF); + r = fflush(fp); + ERROR_IF(fflush, r, == EOF); + return 0; +} diff --git a/tests/stdio/rename.c b/tests/stdio/rename.c index 840916c901..293a9fb302 100644 --- a/tests/stdio/rename.c +++ b/tests/stdio/rename.c @@ -1,5 +1,7 @@ -#include +#include #include +#include +#include #include #include #include @@ -10,6 +12,84 @@ static char oldpath[] = "old-name.out"; static char newpath[] = "new-name.out"; static char str[] = "Hello, World!"; +// Test that renaming a broken symbolic link works. +// Symlinks should not be resolved by rename. +// One of the problems that arises when links are resolved is that +// broken links can't be renamed. +// +// This test returns EXIT_FAILURE/EXIT_SUCCESS because it needs to clean +// up temporary files on failure. The test helpers call _exit. +int rename_broken_symlink(void) { + int result = EXIT_FAILURE; + + char dir_template[] = "/tmp/sltest.XXXXXX"; + size_t dlen = sizeof(dir_template) - 1; + char* temp_dir = mkdtemp(dir_template); + if (!temp_dir) { + perror("mkdtemp"); + return EXIT_FAILURE; + } + + // TODO: Almost all of the code below can be vastly simplified + // with openat/symlinkat later. + + // Broken link to be created + const char link_name[] = "sym"; + char link_path[PATH_MAX] = {0}; + memcpy(link_path, temp_dir, dlen); + link_path[dlen] = '/'; + memcpy(&link_path[dlen + 1], link_name, sizeof(link_name)); + + // Non-existing target + const char link_target[] = "target"; + char target_path[PATH_MAX] = {0}; + memcpy(target_path, temp_dir, dlen); + target_path[dlen] = '/'; + memcpy(&target_path[dlen + 1], link_target, sizeof(link_target)); + + // New name of link (i.e. mv sym symrename) + const char link_rename[] = "symrename"; + char rename_path[PATH_MAX] = {0}; + memcpy(rename_path, temp_dir, dlen); + rename_path[dlen] = '/'; + memcpy(&rename_path[dlen + 1], link_rename, sizeof(link_rename)); + + // Target most definitely does NOT exist. + // This is a sanity check that shouldn't fail as test uses temp dirs. + int target_fd = open(target_path, O_RDONLY); + if (target_fd != -1) { + fprintf(stderr, + "Target exists when it shouldn't: %s\n", + target_path + ); + // Skip clean up on the very exceptional case that the + // randomized dir and file exists. + goto skip_cleanup; + } + + // Create a broken symlink in a temp directory + if (symlink(target_path, link_path) < 0) { + perror("symlink"); + goto cleanup; + } + + // Rename the link; this should work even if target doesn't exist + if (rename(link_path, rename_path) < 0) { + perror("rename"); + goto cleanup; + } + + // TODO: Assert paths exist (needs openat) + + result = EXIT_SUCCESS; +cleanup: + unlink(link_path); + unlink(rename_path); + rmdir(temp_dir); +skip_cleanup: + return result; +} + int main(void) { char buf[14] = { 0 }; @@ -53,4 +133,7 @@ int main(void) { puts("Comparison failed!"); exit(EXIT_FAILURE); } + + int broken_symlink_res = rename_broken_symlink(); + assert(broken_symlink_res == EXIT_SUCCESS); } diff --git a/tests/stdio/renameat.c b/tests/stdio/renameat.c new file mode 100644 index 0000000000..9fba5fc762 --- /dev/null +++ b/tests/stdio/renameat.c @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include + +static int temp_file( + const char dir[], + size_t dlen, + char buf[PATH_MAX], + const char name[], + size_t nlen, + bool create +) { + memcpy(buf, dir, dlen); + buf[dlen] = '/'; + memcpy(&buf[dlen + 1], name, nlen); + + if (create) { + int fd = open(buf, O_CREAT); + if (fd == -1) { + perror("open (file A)"); + close(fd); + return -1; + } + close(fd); + } + + return 0; +} + +int main(void) { + int result = EXIT_FAILURE; + + char dir_template[] = "/tmp/mvattest.XXXXXX"; + size_t dlen = sizeof(dir_template) - 1; + if (!mkdtemp(dir_template)) { + perror("mkdtemp"); + goto bye; + } + int dirfd = open(dir_template, O_DIRECTORY | O_PATH); + if (dirfd == -1) { + perror("open (temp directory)"); + goto bye; + } + + char dir_template2[] = "/tmp/mvattest2.XXXXXX"; + size_t dlen2 = sizeof(dir_template2) - 1; + if (!mkdtemp(dir_template2)) { + perror("mkdtemp"); + goto close_dir1; + } + int dirfd2 = open(dir_template2, O_DIRECTORY | O_PATH); + if (dirfd2 == -1) { + perror("open (temp directory)"); + goto close_dir1; + } + + // File A + const char file_a[] = "power"; + char file_a_path[PATH_MAX] = {0}; + if (temp_file(dir_template, dlen, file_a_path, file_a, sizeof(file_a), true) == -1) { + goto close_dir2; + } + + // File B + const char file_b[] = "nyanko"; + char file_b_path[PATH_MAX] = {0}; + if ( + temp_file( + dir_template, + dlen, + file_b_path, + file_b, + sizeof(file_b), + false + ) == -1 + ) { + goto remove_ab; + } + + // File A but in directory 2 + char file_a_dir2[PATH_MAX] = {0}; + if ( + temp_file( + dir_template2, + dlen2, + file_a_dir2, + file_a, + sizeof(file_a), + false + ) == -1 + ) { + goto remove_all; + } + + // Move file A to file B normally (same dir) + if (renameat(dirfd, file_a, dirfd, file_b) == -1) { + perror("renameat (A -> B, basic)"); + goto remove_all; + } + if (access(file_b_path, F_OK) == -1) { + perror("access (file B, basic)"); + goto remove_all; + } + + // Move file B to A (absolute path; same dir) + if (renameat(dirfd, file_b_path, dirfd, file_a) == -1) { + perror("renameat (B -> A, absolute path)"); + goto remove_all; + } + if (access(file_a_path, F_OK) == -1) { + perror("access (file A, absolute path)"); + goto remove_all; + } + + // Move A to B (both absolute) + if (renameat(dirfd, file_a_path, dirfd, file_b_path) == -1) { + perror("renameat (A -> B, both absolute)"); + goto remove_all; + } + if (access(file_b_path, F_OK) == -1) { + perror("access (file B, both absolute)"); + goto remove_all; + } + + // Move B to B + if (renameat(dirfd, file_b, dirfd, file_b) == -1) { + perror("renameat (B -> B)"); + goto remove_all; + } + if (access(file_b_path, F_OK) == -1) { + perror("access (file B)"); + goto remove_all; + } + + // Move file B to A (AT_FDCWD) + char cwd[PATH_MAX] = {0}; + if (!getcwd(cwd, PATH_MAX)) { + perror("getcwd"); + goto remove_all; + } + if (chdir(dir_template) == -1) { + perror("chdir"); + goto remove_all; + } + + if (renameat(AT_FDCWD, file_b, dirfd, file_a) == -1) { + perror("renameat (B -> A, AT_FDCWD)"); + goto remove_all; + } + if (access(file_a_path, F_OK) == -1) { + perror("access (file A, AT_FDCWD)"); + goto remove_all; + } + + // Reset, though it doesn't really matter. + if (chdir(cwd) == -1) { + perror("chdir"); + goto remove_all; + } + + // Move to different directory + if (renameat(dirfd, file_a, dirfd2, file_a) == -1) { + perror("renameat (A -> B, different dirs)"); + goto remove_all; + } + if (access(file_a_dir2, F_OK) == -1) { + perror("access (file A in dir2)"); + goto remove_all; + } + + // Move non-existing file + if (renameat(dirfd, "aki", dirfd, "denji") == 0) { + // Wut? + fputs("renameat succeeded at moving a non-existing file\n", stderr); + goto remove_all; + } + + // RENAME_NOREPLACE + // Create file B in dir 1 first because we moved it earlier. + int fd = open(file_b_path, O_CREAT); + if (fd == -1) { + perror("open (file B in dir 1)"); + close(fd); + goto remove_all; + } + close(fd); + + if (renameat2(dirfd, file_b, dirfd2, file_a, RENAME_NOREPLACE) == 0) { + fputs("renameat2 (B -> A, noreplace) succeeded\n", stderr); + goto remove_all; + } + if (access(file_a_dir2, F_OK) == -1) { + fputs("RENAME_NOREPLACE ate file A in dir 2\n", stderr); + goto remove_all; + } + if (access(file_b_path, F_OK) == -1) { + fputs("RENAME_NOREPLACE ate file B in dir 1\n", stderr); + goto remove_all; + } + + // TODO: RENAME_EXCHANGE (Needs frename support in Redox) + // Notes for later: + // * Write a message to both files + // * Swap + // * Check if the files swapped correctly by strcmp messages + + result = EXIT_SUCCESS; +remove_all: + remove(file_a_dir2); +remove_ab: + remove(file_a_path); + remove(file_b_path); + rmdir(dir_template); + rmdir(dir_template2); +close_dir2: + close(dirfd2); +close_dir1: + close(dirfd); +bye: + return result; +} diff --git a/tests/stdio/scanf.c b/tests/stdio/scanf.c index 0bf195b3dd..6a02557383 100644 --- a/tests/stdio/scanf.c +++ b/tests/stdio/scanf.c @@ -33,7 +33,7 @@ void test(char* fmt_in, char* input, struct params *p, ...) { int main(void) { struct params p = { .c = 'a' }; - test("%hhd %d", "12 345", &p, &p.sa, &p.ia); + test("%hd %d", "12 345", &p, &p.sa, &p.ia); test("%x %i %i", "12 0x345 010", &p, &p.ia, &p.ib, &p.ic); test("%f.%lf", "0.1.0.2", &p, &p.fa, &p.da); test("%p", "0xABCDEF", &p, &p.ptr); @@ -42,6 +42,8 @@ int main(void) { test("%c%3c", "hello", &p, &p.c, &p.string1); test("test: %2i%n", "test: 0xFF", &p, &p.ia, &p.ib); test("hello world%%", "hello world%", &p); + test("hello %5ls %d", "hello world 42", &p, &p.string1, &p.ia); + test("bye %ls %d", "bye planet 84", &p, &p.string2, &p.ia); test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "hello world", &p, &p.string1, &p.string2, &p.string3, &p.string4); test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "halle worfdddddd", &p, &p.string1, &p.string2, &p.string3, &p.string4); test("h%1[ae]ll%1[^a] wor%1[^\n]%[d]", "halle worfdddddd", &p, &p.string1, &p.string2, &p.string3, &p.string4); diff --git a/tests/stdlib/alloc.c b/tests/stdlib/alloc.c index 3666036375..2e51c144c6 100644 --- a/tests/stdlib/alloc.c +++ b/tests/stdlib/alloc.c @@ -1,4 +1,4 @@ -#include +#include /* for pvalloc() */ #include #include #include @@ -89,7 +89,7 @@ int main(void) { size_t nonpow2_mul_voidptr_size = 3*sizeof(void *); size_t pow2_mul_voidptr_size = 4*sizeof(void *); - int i; + size_t i; errno = 0; char * ptr_zerosize_malloc = (char *)malloc(zero_size); @@ -165,7 +165,26 @@ int main(void) { printf("realloc (SIZE_MAX): "); test_cannot_alloc(ptr_realloc_maxsize, realloc_maxsize_errno); free(ptr_realloc_maxsize); + + errno = 0; + char * ptr_reallocarray_maxsize = (char *)malloc(sample_alloc_size); + ptr_reallocarray_maxsize = (char *)reallocarray(ptr_reallocarray_maxsize, 2, sample_alloc_size); + int reallocarray_errno = errno; + printf("reallocarray: "); + test_non_null(ptr_reallocarray_maxsize, reallocarray_errno); + for(i = 0; i < sample_realloc_size; i++) { + ptr_realloc[i] = (char)i; + } + errno = 0; + ptr_reallocarray_maxsize = (char *)reallocarray(ptr_reallocarray_maxsize, 2, max_size); + reallocarray_errno = errno; + printf("reallocarray (SIZE_MAX): "); + test_cannot_alloc(ptr_reallocarray_maxsize, reallocarray_errno); + free(ptr_reallocarray_maxsize); + // Warning for deprecated functions is expected so silence -Werror + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" errno = 0; char * ptr_memalign_size0 = (char *)memalign(aligned_alloc_alignment, zero_size); int memalign_size0_errno = errno; @@ -203,6 +222,8 @@ int main(void) { printf("memalign (alignment 3): "); test_invalid_aligned(ptr_memalign_align3, memalign_align3_errno); free(ptr_memalign_align3); + + #pragma GCC diagnostic pop errno = 0; char * ptr_aligned_alloc_goodsize = (char *)aligned_alloc(aligned_alloc_alignment, aligned_alloc_goodsize); @@ -218,6 +239,10 @@ int main(void) { test_invalid_aligned(ptr_aligned_alloc_badsize, aligned_alloc_badsize_errno); free(ptr_aligned_alloc_badsize); + // Warning for deprecated functions is expected so silence -Werror + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + errno = 0; char * ptr_valloc_size0 = (char *)valloc(zero_size); int valloc_size0_errno = errno; @@ -273,4 +298,44 @@ int main(void) { printf("posix_memalign (SIZE_MAX): "); test_cannot_alloc(ptr_posix_memalign_maxsize, posix_memalign_maxsize_return); free(ptr_posix_memalign_maxsize); + + errno = 0; + char * ptr_pvalloc_size0 = (char *)pvalloc(zero_size); + int pvalloc_size0_errno = errno; + printf("pvalloc (size 0): "); + test_size_zero(ptr_pvalloc_size0, page_size, pvalloc_size0_errno); + free(ptr_pvalloc_size0); + + errno = 0; + char * ptr_pvalloc = (char *)pvalloc(sample_alloc_size); + int pvalloc_errno = errno; + printf("pvalloc: "); + test_valid_aligned(ptr_pvalloc, page_size, pvalloc_errno); + /* Check that the alloc size is rounded up to nearest page-size + * multiple */ + for(i = 0; i < page_size; i++) { + ptr_pvalloc[i] = (char)i; + } + free(ptr_pvalloc); + + errno = 0; + char * ptr_pvalloc_multipage = (char *)pvalloc(page_size + 1); + int pvalloc_multipage_errno = errno; + printf("pvalloc (2 pages): "); + test_valid_aligned(ptr_pvalloc_multipage, page_size, pvalloc_multipage_errno); + /* Check that the alloc size is rounded up to nearest page-size + * multiple */ + for(i = 0; i < 2*page_size; i++) { + ptr_pvalloc_multipage[i] = (char)i; + } + free(ptr_pvalloc_multipage); + + errno = 0; + char * ptr_pvalloc_maxsize = (char *)pvalloc(max_size); + int pvalloc_maxsize_errno = errno; + printf("pvalloc (SIZE_MAX): "); + test_cannot_alloc(ptr_pvalloc_maxsize, pvalloc_maxsize_errno); + free(ptr_pvalloc_maxsize); + + #pragma GCC diagnostic pop } diff --git a/tests/stdlib/atof.c b/tests/stdlib/atof.c index ec945ef697..f9783467b7 100644 --- a/tests/stdlib/atof.c +++ b/tests/stdlib/atof.c @@ -6,4 +6,7 @@ int main(void) { double d = atof("-3.14"); printf("%f\n", d); + + d = atof("INF"); + printf("%f\n", d); } diff --git a/tests/stdlib/getsubopt.c b/tests/stdlib/getsubopt.c new file mode 100644 index 0000000000..4717f65fcd --- /dev/null +++ b/tests/stdlib/getsubopt.c @@ -0,0 +1,55 @@ +#include "test_helpers.h" + +int main(void) { + char *const tokens[] = { + "ro", + "rw", + "foo", + "baz", + NULL + }; + + // getsubopt modifies the string in-place + char opt_str[] = "ro,foo=bar,bool,baz=,rw"; + char *options = opt_str; + char *value = NULL; + int idx; + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != 0); + if (value != NULL) { + printf("getsubopt failed: expected NULL value for 'ro', got '%s'\n", value); + exit(EXIT_FAILURE); + } + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != 2); + if (value == NULL || strcmp(value, "bar") != 0) { + printf("getsubopt failed: expected 'bar', got '%s'\n", value ? value : "NULL"); + exit(EXIT_FAILURE); + } + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != -1); + if (value == NULL || strcmp(value, "bool") != 0) { + printf("getsubopt failed: expected 'bool' in value, got '%s'\n", value ? value : "NULL"); + exit(EXIT_FAILURE); + } + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != 3); + if (value == NULL || strcmp(value, "") != 0) { + printf("getsubopt failed: expected empty string value, got '%s'\n", value ? value : "NULL"); + exit(EXIT_FAILURE); + } + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != 1); + if (value != NULL) { + printf("getsubopt failed: expected NULL value for 'rw', got '%s'\n", value); + exit(EXIT_FAILURE); + } + + idx = getsubopt(&options, tokens, &value); + UNEXP_IF(getsubopt, idx, != -1); +} diff --git a/tests/stdlib/mktemp.c b/tests/stdlib/mktemp.c index 7d43078e4b..577fb51fc2 100644 --- a/tests/stdlib/mktemp.c +++ b/tests/stdlib/mktemp.c @@ -7,7 +7,10 @@ int main(void) { char* string = (char*) calloc(20, sizeof(char)); strcpy(string, "tempXXXXXX"); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" mktemp(string); + #pragma GCC diagnostic pop printf("%s\n",string); free(string); } diff --git a/tests/stdlib/ptsname.c b/tests/stdlib/ptsname.c new file mode 100644 index 0000000000..82d2687d84 --- /dev/null +++ b/tests/stdlib/ptsname.c @@ -0,0 +1,89 @@ +/* Test for ptsname/ptsname_r. + Copyright (C) 2014-2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +#define DEV_TTY "/dev/tty" +#define PTSNAME_EINVAL "./ptsname-einval" + +int do_single_test(int fd, char * buf, size_t buflen, int expected_err) { + + int ret = ptsname_r(fd, buf, buflen); + int err = errno; + + if (expected_err == 0) { + if (ret != 0) { + printf("ptsname_r: expected: return = 0\n"); + printf(" got: return = %d, errno = %d (%s)\n", + ret, err, strerror(err)); + return 1; + } + } else { + if (ret == 0 || errno != expected_err) { + printf("ptsname_r: expected: return = %d, errno = %d (%s)\n", + -1, expected_err, strerror(expected_err)); + printf(" got: return = %d, errno = %d (%s)\n", + ret, err, strerror(err)); + return 1; + } + } + + return 0; +} + +int main(void) { + char buf[512] = {0}; + int result = 0; + errno = 0; + /* Tests with a real PTS master. */ + int fd = posix_openpt(O_RDWR); + if (fd != -1) { + result |= do_single_test(fd, buf, sizeof(buf), 0); + result |= do_single_test(fd, buf, 1, ERANGE); + close(fd); + } else + printf("posix_openpt (O_RDWR) failed\nerrno %d (%s)\n", + errno, strerror(errno)); + + // TODO: open(DEV_TTY, O_RDONLY) gives error on CI + // /* Test with a terminal device which is not a PTS master. */ + // fd = open(DEV_TTY, O_RDONLY); + // if (fd != -1) { + // result |= do_single_test(fd, buf, sizeof(buf), ENOTTY); + // close(fd); + // } else + // printf("open (\"%s\", O_RDWR) failed\nerrno %d (%s)\n", + // DEV_TTY, errno, strerror(errno)); + + /* Test with a file. */ + fd = open(PTSNAME_EINVAL, O_RDWR | O_CREAT, 0600); + if (fd != -1) { + result |= do_single_test(fd, buf, sizeof(buf), ENOTTY); + close(fd); + unlink(PTSNAME_EINVAL); + } else + printf("open (\"%s\", O_RDWR | OCREAT) failed\nerrno %d (%s)\n", + PTSNAME_EINVAL, errno, strerror(errno)); + + return result; +} \ No newline at end of file diff --git a/tests/stdlib/qsort.c b/tests/stdlib/qsort.c new file mode 100644 index 0000000000..0de6d141b5 --- /dev/null +++ b/tests/stdlib/qsort.c @@ -0,0 +1,33 @@ +#include +#include +#include + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +int values[] = { 23, 16, 8, 4, 42, 15 }; + +int cmpfunc (const void * a_ptr, const void * b_ptr) { + int a = *(const int *)a_ptr; + int b = *(const int *)b_ptr; + return a - b; +} + +int main () { + size_t i; + + printf("Before: "); + for(i = 0; i < ARRAY_SIZE(values); i++) { + printf("%d ", values[i]); + } + printf("\n"); + + qsort(values, ARRAY_SIZE(values), sizeof(int), cmpfunc); + + printf("After: "); + for(i = 0; i < ARRAY_SIZE(values); i++) { + printf("%d ", values[i]); + } + printf("\n"); + + return 0; +} diff --git a/tests/stdlib/strtod.c b/tests/stdlib/strtod.c index d6826f6761..8b0450259a 100644 --- a/tests/stdlib/strtod.c +++ b/tests/stdlib/strtod.c @@ -1,3 +1,4 @@ +#include #include #include @@ -10,9 +11,70 @@ int main(void) { char* inputs[] = { "a 1 hello", " 1 hello", "1 hello 2", "10.123", "010.123", "-5.3", - "0x10.123", "0x1.23", "0x3.21" + "0x10.123", "0x1.23", "0x3.21", + + "1e5", "1e+5", "1e-5", + "1e5 ", "1e+5 ", "1e-5 ", + + "1e10", "1eXXXX", "1e", "1e ", + "1e+10", "1e+XXXX", "1e+", "1e+ ", + "1e-10", "1e-XXXX", "1e-", "1e- ", + + "-1e5", "-1e+5", "-1e-5", + "-1e5 ", "-1e+5 ", "-1e-5 ", + + "-1e10", "-1eXXXX", "-1e", "-1e ", + "-1e+10", "-1e+XXXX", "-1e+", "-1e+ ", + "-1e-10", "-1e-XXXX", "-1e-", "-1e- ", + + "12.34e5", "12.34e+5", "12.34e-5", + "12.34e5 ", "12.34e+5 ", "12.34e-5 ", + + "12.34e10", "12.34eXXXX", "12.34e", "12.34e ", + "12.34e+10", "12.34e+XXXX", "12.34e+", "12.34e+ ", + "12.34e-10", "12.34e-XXXX", "12.34e-", "12.34e- ", + + "-12.34e5", "-12.34e+5", "-12.34e-5", + "-12.34e5 ", "-12.34e+5 ", "-12.34e-5 ", + + "-12.34e10", "-12.34eXXXX", "-12.34e", "-12.34e ", + "-12.34e+10", "-12.34e+XXXX", "-12.34e+", "-12.34e+ ", + "-12.34e-10", "-12.34e-XXXX", "-12.34e-", "-12.34e- ", + + "0x0.3p10", "-0x0.3p10", "0x0.3p-5", "-0x0.3p-5", + "0x1.4p3", "0x1.4p-3", "-0x1.4p3", "-0x1.4p-3", + "0x10.1p0", "0x10.1p-0", "-0x10.1p0", "-0x10.1p-0", + + "0.5e0", "0.5e1", "0.5e2", "0.5e3", "0.5e4", + "0.5e5", "0.5e6", "0.5e7", "0.5e8", "0.5e9", + "0.5e10", "0.5e11", "0.5e12", "0.5e13", "0.5e14", + "0.5e15", "0.5e16", "0.5e17", "0.5e18", "0.5e19", + "0.5e20", "0.5e21", "0.5e22", "0.5e23", "0.5e24", + "0.5e25", "0.5e26", "0.5e27", "0.5e28", "0.5e29", + "0.5e30", "0.5e31", "0.5e32", "0.5e33", "0.5e34", + "0.5e35", "0.5e36", "0.5e37", "0.5e38", + + "-0.5e0", "-0.5e1", "-0.5e2", "-0.5e3", "-0.5e4", + "-0.5e5", "-0.5e6", "-0.5e7", "-0.5e8", "-0.5e9", + "-0.5e10", "-0.5e11", "-0.5e12", "-0.5e13", "-0.5e14", + "-0.5e15", "-0.5e16", "-0.5e17", "-0.5e18", "-0.5e19", + "-0.5e20", "-0.5e21", "-0.5e22", "-0.5e23", "-0.5e24", + "-0.5e25", "-0.5e26", "-0.5e27", "-0.5e28", "-0.5e29", + "-0.5e30", "-0.5e31", "-0.5e32", "-0.5e33", "-0.5e34", + "-0.5e35", "-0.5e36", "-0.5e37", "-0.5e38", + + "-0", + + "INF", "inf", "iNf", "Inf foobarbaz", + "+INF", "+inf", "+iNf", "+Inf foobarbaz", + "-INF", "-inf", "-iNf", "-Inf foobarbaz", + + "NaN0.1e5", "nan-37", "nAn1.05", "Nan foo bar baz", + "+NaN0.1e5", "+nan-37", "+nAn1.05", "+Nan foo bar baz", + "-NaN0.1e5", "-nan-37", "-nAn1.05", "-Nan foo bar baz", + }; - for (int i = 0; i < sizeof(inputs) / sizeof(char*); i += 1) { + for (size_t i = 0; i < sizeof(inputs) / sizeof(char*); i += 1) { d = strtod(inputs[i], &endptr); printf("d: %f Endptr: \"%s\"\n", d, endptr); } diff --git a/tests/stdlib/strtol.c b/tests/stdlib/strtol.c index 17c4155de6..118d635de4 100644 --- a/tests/stdlib/strtol.c +++ b/tests/stdlib/strtol.c @@ -5,27 +5,42 @@ #include "test_helpers.h" int main(void) { - printf("%ld\n", strtol(" -42", NULL, 0)); - printf("%ld\n", strtol(" +555", NULL, 0)); - printf("%ld\n", strtol(" 1234567890 ", NULL, 0)); + char *endptr; - printf("%ld\n", strtol(" -42", NULL, 10)); - printf("%ld\n", strtol(" +555", NULL, 10)); - printf("%ld\n", strtol(" 1234567890 ", NULL, 10)); + printf("%ld\n", strtol(" -42", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtol(" +555", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtol(" 1234567890 ", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); - printf("%lx\n", strtol(" 0x38Acfg", NULL, 0)); - printf("%lx\n", strtol("0Xabcdef12", NULL, 16)); - printf("%lx\n", strtol("cafebeef", NULL, 16)); + printf("%ld\n", strtol(" -42", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtol(" +555", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtol(" 1234567890 ", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); - printf("%lo\n", strtol(" 073189", NULL, 0)); - printf("%lo\n", strtol(" 073189", NULL, 8)); + printf("%lx\n", strtol(" 0x38Acfg", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%lx\n", strtol("0Xabcdef12", &endptr, 16)); + printf("endptr \"%s\"\n", endptr); + printf("%lx\n", strtol("cafebeef", &endptr, 16)); + printf("endptr \"%s\"\n", endptr); - printf("%lo\n", strtol(" 0b", NULL, 8)); + printf("%lo\n", strtol(" 073189", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%lo\n", strtol(" 073189", &endptr, 8)); + printf("endptr \"%s\"\n", endptr); + + printf("%lo\n", strtol(" 0b", &endptr, 8)); if(errno != 0) { printf("errno is not 0 (%d), something went wrong\n", errno); } - printf("%lo\n", strtol(" 0b", NULL, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%lo\n", strtol(" 0b", &endptr, 0)); if(errno != 0) { printf("errno is not 0 (%d), something went wrong\n", errno); } + printf("endptr \"%s\"\n", endptr); } diff --git a/tests/stdlib/strtoul.c b/tests/stdlib/strtoul.c index 365f268c91..b558f0c170 100644 --- a/tests/stdlib/strtoul.c +++ b/tests/stdlib/strtoul.c @@ -5,26 +5,42 @@ #include "test_helpers.h" int main(void) { - printf("%ld\n", strtoul(" -42", NULL, 0)); - printf("%ld\n", strtoul(" +555", NULL, 0)); - printf("%ld\n", strtoul(" 1234567890 ", NULL, 0)); + char *endptr; - printf("%ld\n", strtoul(" -42", NULL, 10)); - printf("%ld\n", strtoul(" +555", NULL, 10)); - printf("%ld\n", strtoul(" 1234567890 ", NULL, 10)); + printf("%ld\n", strtoul(" -42", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtoul(" +555", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtoul(" 1234567890 ", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); - printf("%lx\n", strtoul(" 0x38Acfg", NULL, 0)); - printf("%lx\n", strtoul("0Xabcdef12", NULL, 16)); + printf("%ld\n", strtoul(" -42", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtoul(" +555", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); + printf("%ld\n", strtoul(" 1234567890 ", &endptr, 10)); + printf("endptr \"%s\"\n", endptr); - printf("%lo\n", strtoul(" 073189", NULL, 0)); - printf("%lo\n", strtoul(" 073189", NULL, 8)); + printf("%lx\n", strtoul(" 0x38Acfg", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%lx\n", strtoul("0Xabcdef12", &endptr, 16)); + printf("endptr \"%s\"\n", endptr); + printf("%lx\n", strtoul("0x21000004", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); - printf("%lo\n", strtoul(" 0b", NULL, 8)); + printf("%lo\n", strtoul(" 073189", &endptr, 0)); + printf("endptr \"%s\"\n", endptr); + printf("%lo\n", strtoul(" 073189", &endptr, 8)); + printf("endptr \"%s\"\n", endptr); + + printf("%lo\n", strtoul(" 0b", &endptr, 8)); if(errno != 0) { printf("errno is not 0 (%d), something went wrong\n", errno); } + printf("endptr \"%s\"\n", endptr); printf("%lo\n", strtoul(" 0b", NULL, 0)); if(errno != 0) { printf("errno is not 0 (%d), something went wrong\n", errno); } + printf("endptr \"%s\"\n", endptr); } diff --git a/tests/stdlib/system.c b/tests/stdlib/system.c index 8b7d02201a..821c216f6c 100644 --- a/tests/stdlib/system.c +++ b/tests/stdlib/system.c @@ -3,6 +3,16 @@ #include "test_helpers.h" int main(void) { - int status = system("echo test of system"); + // testing shell detection + // this means, because we don't detect if a shell actually exists but just assume it does, this test case breaks in + // environments without `sh`. I think that is a reasonable tradeoff. + // (And if there isn't a shell, system() won't work anyways) + int status = system(NULL); + printf("shell found: %i\n", status); + fflush(stdout); + ERROR_IF(system, status, == 0); + + // base case + status = system("echo test of system"); ERROR_IF(system, status, == -1); } diff --git a/tests/string/mem.c b/tests/string/mem.c index 7529a3bb1b..ed27237237 100644 --- a/tests/string/mem.c +++ b/tests/string/mem.c @@ -21,7 +21,7 @@ int main(void) { puts("Correct memrchr"); char arr2[51]; memset(arr2, 0, 51); // Compiler builtin, should work - memccpy((void *)arr2, (void *)arr, 1, 100); + memccpy((void *)arr2, (void *)arr, 1, 51); if (arr[50] != 1) { puts("Incorrect memccpy"); exit(EXIT_FAILURE); diff --git a/tests/string/memcpy.c b/tests/string/memcpy.c new file mode 100644 index 0000000000..f9978dd697 --- /dev/null +++ b/tests/string/memcpy.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "test_helpers.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +int main(void) { + const uint8_t UNTOUCHED_BYTE = 0x00; + const uint8_t TOUCHED_BYTE = 0xff; + // In order to fully exercise the implementation, this should be at least 3 times the largest possible chunk size + const size_t BUFFER_LEN = 64; + + uint8_t *s1_buffer = malloc(BUFFER_LEN); + uint8_t *s2_buffer = malloc(BUFFER_LEN); + + // Loop through all possible combinations of s1 and s2 alignments and slice length within the buffers allocated + for (size_t s1_offset = 0; s1_offset < BUFFER_LEN; s1_offset++) { + for (size_t s2_offset = 0; s2_offset < BUFFER_LEN; s2_offset++) { + size_t n_max = BUFFER_LEN - MAX(s1_offset, s2_offset); + for (size_t n = 1; n <= n_max; n++) { + // Clear buffers + memset(s1_buffer, UNTOUCHED_BYTE, BUFFER_LEN); + memset(s2_buffer, UNTOUCHED_BYTE, BUFFER_LEN); + + // Fill s2 subslice + memset(s2_buffer + s2_offset, TOUCHED_BYTE, n); + + // Do the actual memcpy of the slice of interest + memcpy(s1_buffer + s1_offset, s2_buffer + s2_offset, n); + + // Check that area below the slice of interest is untouched + for (size_t i = 0; i < s1_offset; i++) { + assert(s1_buffer[i] == UNTOUCHED_BYTE); + } + + // Check that the slice of interest was copied + assert(memcmp(s1_buffer + s1_offset, s2_buffer + s2_offset, n) == 0); + + // Check that area above the slice of interest is untouched + for (size_t i = s1_offset + n; i < BUFFER_LEN; i++) { + assert(s1_buffer[i] == UNTOUCHED_BYTE); + } + } + } + } + + free(s1_buffer); + free(s2_buffer); +} \ No newline at end of file diff --git a/tests/string/memmem.c b/tests/string/memmem.c new file mode 100644 index 0000000000..c3ff546428 --- /dev/null +++ b/tests/string/memmem.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) { + uint8_t haystack[] = {1, 1, 2, 1, 2, 3, 1, 2, 3, 4}; + uint8_t present_needle[] = {1, 2, 3}; + uint8_t absent_needle[] = {1, 2, 3, 4, 5}; + uint8_t long_needle[] = {1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1}; + + size_t haystacklen = sizeof(haystack); + size_t present_needlelen = sizeof(present_needle); + size_t absent_needlelen = sizeof(absent_needle); + size_t long_needlelen = sizeof(long_needle); + + uint8_t *present_needle_match_ptr = memmem(haystack, haystacklen, present_needle, present_needlelen); + assert(present_needle_match_ptr == haystack + 3); + + uint8_t *absent_needle_match_ptr = memmem(haystack, haystacklen, absent_needle, absent_needlelen); + assert(absent_needle_match_ptr == NULL); + + // Explicitly specified to return haystack for needlelen == 0. + uint8_t *zero_needle_match_ptr = memmem(haystack, haystacklen, present_needle, 0); + assert(zero_needle_match_ptr == haystack); + + uint8_t *long_needle_match_ptr = memmem(haystack, haystacklen, long_needle, long_needlelen); + assert(long_needle_match_ptr == NULL); +} diff --git a/tests/string/stpcpy.c b/tests/string/stpcpy.c new file mode 100644 index 0000000000..9d10d41199 --- /dev/null +++ b/tests/string/stpcpy.c @@ -0,0 +1,26 @@ +#include +#include + +void test_stpcpy(const char *src, char *dest) { + char *end = stpcpy(dest, src); + assert(strcmp(dest, src) == 0); + assert(*end == '\0'); + assert(end == dest + strlen(dest)); +} + +int main() { + char dest[20]; + + test_stpcpy("Hello, World!", dest); + + // Test case 2: Empty string + test_stpcpy("", dest); + + // Test case 3: String with special characters + test_stpcpy("Special chars: !@#$%^&*()", dest); + + // Test case 4: String with spaces + test_stpcpy("A string with spaces", dest); + + return 0; +} diff --git a/tests/string/stpncpy.c b/tests/string/stpncpy.c new file mode 100644 index 0000000000..27b558f088 --- /dev/null +++ b/tests/string/stpncpy.c @@ -0,0 +1,40 @@ +#include +#include + +int main() { + // Define some test strings + const char *src = "Hello, World!"; + char dest[50]; + + // Test 1: Copy exactly 5 characters from src to dest + char *result = stpncpy(dest, src, 5); + dest[5] = '\0'; // Ensure the destination string is null-terminated + assert(strcmp(dest, "Hello") == 0); // Verify the string in dest + assert(result == &dest[5]); // Ensure the return pointer points to the null terminator + + // Test 2: Copy 15 characters from src to dest (more than the length of src) + result = stpncpy(dest, src, 15); + dest[13] = '\0'; // Ensure the destination string is null-terminated + assert(strcmp(dest, "Hello, World!") == 0); // Verify the string in dest + assert(result == &dest[13]); // Ensure the return pointer points to the null terminator + + // Test 3: Copy 3 characters from src to dest + result = stpncpy(dest, src, 3); + dest[3] = '\0'; // Ensure the destination string is null-terminated + assert(strcmp(dest, "Hel") == 0); // Verify the string in dest + assert(result == &dest[3]); // Ensure the return pointer points to the null terminator + + // Test 4: Copy 0 characters from src to dest + result = stpncpy(dest, src, 0); + dest[0] = '\0'; // Ensure the destination is explicitly null-terminated + assert(dest[0] == '\0'); // Ensure the destination is an empty string + assert(result == dest); // Ensure the return pointer points to the start of dest + + // Test 5: Copy exactly the length of the source string + result = stpncpy(dest, src, strlen(src)); + dest[strlen(src)] = '\0'; // Ensure the destination string is null-terminated + assert(strcmp(dest, "Hello, World!") == 0); // Verify the string in dest + assert(result == &dest[strlen(src)]); // Ensure the return pointer points to the null terminator + + return 0; +} diff --git a/tests/string/strchr.c b/tests/string/strchr.c index ebe973a51a..51f4b7aa56 100644 --- a/tests/string/strchr.c +++ b/tests/string/strchr.c @@ -6,5 +6,6 @@ int main(void) { printf("%s\n", strchr("hello", 'e')); // should be ello printf("%s\n", strchr("world", 'l')); // should be ld - printf("%i\n", strchr("world", 0) == NULL); // should be 1 + printf("%s\n", strchr("world", '\0')); // should be an empty, nul-terminated string + printf("%p\n", strchr("world", 'x')); // should be a null pointer } diff --git a/tests/string/strchrnul.c b/tests/string/strchrnul.c new file mode 100644 index 0000000000..9911e03608 --- /dev/null +++ b/tests/string/strchrnul.c @@ -0,0 +1,31 @@ +#include +#include + + +int main() { + // Test 1: Character is present in the string + const char *str1 = "Hello, World!"; + const char *result1 = strchrnul(str1, 'o'); + assert(result1 == &str1[4]); // 'o' is at position 4 in "Hello, World!" + + // Test 2: Character is not in the string (should return the null terminator) + const char *str2 = "Hello, World!"; + const char *result2 = strchrnul(str2, 'z'); + assert(result2 == &str2[13]); // 'z' is not present, so it returns the null terminator + + // Test 3: Character is the first character in the string + const char *str3 = "abcdef"; + const char *result3 = strchrnul(str3, 'a'); + assert(result3 == &str3[0]); // 'a' is at position 0 + + // Test 4: Character is the last character in the string + const char *str4 = "abcdef"; + const char *result4 = strchrnul(str4, 'f'); + assert(result4 == &str4[5]); // 'f' is at position 5, the last character + + // Test 5: Searching for the null terminator itself + const char *str5 = "abcdef"; + const char *result5 = strchrnul(str5, '\0'); + assert(result5 == &str5[6]); // The null terminator is at position 6 (end of the string) + return 0; +} diff --git a/tests/string/strcpy.c b/tests/string/strcpy.c index d78d26e51b..25b0276317 100644 --- a/tests/string/strcpy.c +++ b/tests/string/strcpy.c @@ -16,4 +16,14 @@ int main(void) { dst[19] = 0; strncpy(dst, "strncpy should work here too", 10); puts(dst); + + // The string should be properly terminated regardless + char ndst[28]; + + size_t r = strlcpy(ndst, "strlcpy works!", 28); + puts(ndst); + printf("copied %lu\n", r); + r = strlcat(ndst, " and strlcat!", 28); + puts(ndst); + printf("copied %lu\n", r); } diff --git a/tests/string/strlen.c b/tests/string/strlen.c index f7fd46f5b5..d5125c52dc 100644 --- a/tests/string/strlen.c +++ b/tests/string/strlen.c @@ -48,6 +48,8 @@ int main(void) { exit(EXIT_FAILURE); } +// no strnlen_s on glibc +#ifndef __GLIBC__ dest1_len = strnlen_s(dest1, 6); printf("%d\n", dest1_len); if(dest1_len != 6) { @@ -68,6 +70,7 @@ int main(void) { puts("strnlen_s(NULL, 100) failed"); exit(EXIT_FAILURE); } +#endif return 0; } diff --git a/tests/string/strsep.c b/tests/string/strsep.c new file mode 100644 index 0000000000..bfa6c4a11b --- /dev/null +++ b/tests/string/strsep.c @@ -0,0 +1,68 @@ +#include +#include + +int main() { + // Test case 1: Basic case with multiple tokens + char str1[] = "apple,orange,banana"; + char *delim = ","; + char *token = str1; + char *result = NULL; + + // First token should be "apple" + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "apple") == 0); + + // Second token should be "orange" + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "orange") == 0); + + // Third token should be "banana" + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "banana") == 0); + + // No more tokens + result = strsep(&token, delim); + assert(result == NULL); + + // Test case 2: Empty string + char str2[] = ""; + token = str2; + result = strsep(&token, delim); + + // Test case 3: String with no delimiter + char str3[] = "apple"; + token = str3; + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "apple") == 0); + assert(token == NULL); // No more tokens + + // Test case 4: String starts with delimiter + char str4[] = ",apple,orange"; + token = str4; + result = strsep(&token, delim); + assert(result != NULL && strlen(result) == 0); // First token should be an empty string ("") + + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "apple") == 0); + + // Test case 5: Multiple delimiters in a row + char str5[] = "apple,,orange"; + token = str5; + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "apple") == 0); + result = strsep(&token, delim); + assert(result != NULL && strlen(result) == 0); // Empty token due to consecutive delimiters + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "orange") == 0); + + // Test case 6: Delimiters at the end of the string + char str6[] = "apple,orange,"; + token = str6; + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "apple") == 0); + result = strsep(&token, delim); + assert(result != NULL && strcmp(result, "orange") == 0); + result = strsep(&token, delim); + + return 0; +} diff --git a/tests/strings.c b/tests/strings.c index 6d3c4742cb..ede5d6289c 100644 --- a/tests/strings.c +++ b/tests/strings.c @@ -40,4 +40,8 @@ int main(void) { char* str = "hihih"; assert(index(str, 'i') == str + 1); assert(rindex(str, 'i') == str + 3); + + char buf[] = "password"; + explicit_bzero(buf, sizeof(buf)); + assert(buf[0] == 0); } diff --git a/tests/sys_epoll/epoll.c b/tests/sys_epoll/epoll.c index 3d89341de7..fa367e07cf 100644 --- a/tests/sys_epoll/epoll.c +++ b/tests/sys_epoll/epoll.c @@ -3,6 +3,9 @@ #include #include #include +#include + +#include "../test_helpers.h" int reader(int fd) { // Create an epoll file @@ -21,8 +24,27 @@ int reader(int fd) { return 1; } - // Process exactly 1024 events struct epoll_event events[8]; + + // Check that epoll returns error on a zero or negative number of events + int nfds0 = epoll_wait(epollfd, events, 0, -1); + if (nfds0 != -1 || errno != EINVAL) { + perror("epoll_wait"); + return 1; + } + +#ifndef __GLIBC__ + int nfds_n1 = epoll_wait(epollfd, events, -1, -1); +#else + // glibc does not support events len -1 + int nfds_n1 = epoll_wait(epollfd, events, 8, -1); +#endif + if (nfds_n1 != -1 || errno != EINVAL) { + perror("epoll_wait"); + return 1; + } + + // Process exactly 1024 events for (int i = 0; i < 1024; i++) { // Wait for the next event int nfds = epoll_wait(epollfd, events, sizeof(events)/sizeof(struct epoll_event), -1); @@ -37,12 +59,12 @@ int reader(int fd) { if (events[n].data.fd == fd) { // Read the current event count int writer_i; - int count = read(fd, &writer_i, sizeof(writer_i)); - if (count < 0) { - perror("read"); - return 1; - } else if (count < sizeof(writer_i)) { - fprintf(stderr, "read %d instead of %d\n", count, sizeof(writer_i)); + ssize_t status = read(fd, &writer_i, sizeof(writer_i)); + ERROR_IF(read, status, == -1); + size_t count = (size_t)status; + + if (count < sizeof(writer_i)) { + fprintf(stderr, "read %zu instead of %d\n", count, sizeof(writer_i)); return 1; } // Make sure the writer's event count matches our own @@ -94,12 +116,12 @@ int writer(int fd) { // If the event is the writer file if (events[n].data.fd == fd) { // Write the current event count - int count = write(fd, &i, sizeof(i)); - if (count < 0) { - perror("write"); - return 1; - } else if (count < sizeof(i)) { - fprintf(stderr, "wrote %d instead of %d\n", count, sizeof(i)); + ssize_t status = write(fd, &i, sizeof(i)); + ERROR_IF(write, status, == -1); + size_t count = (size_t)status; + + if (count < sizeof(i)) { + fprintf(stderr, "wrote %zu instead of %d\n", count, sizeof(i)); return 1; } } else { @@ -113,7 +135,7 @@ int writer(int fd) { return 0; } -int main(int argc, char **argv) { +int main(void) { // Create a non-blocking pipe to use for epoll testing int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) < 0) { diff --git a/tests/sys_epoll/epollet.c b/tests/sys_epoll/epollet.c new file mode 100644 index 0000000000..7c0646730b --- /dev/null +++ b/tests/sys_epoll/epollet.c @@ -0,0 +1,46 @@ +// test code found on dbus, currently hangs on redox + +#include +#include +#include +#include + +#define _DBUS_ZERO(object) (memset (&(object), '\0', sizeof ((object)))) + +int +main (void) +{ + struct epoll_event input; + struct epoll_event output; + int epfd = epoll_create1 (EPOLL_CLOEXEC); + + int pipefds[2]; + pipe(pipefds); + close(pipefds[1]); + int fd = pipefds[0]; + + int ret; + + _DBUS_ZERO (input); + + input.events = EPOLLHUP | EPOLLET; + ret = epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &input); + printf ("ctl ADD: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, -1); + printf ("wait for HUP, edge-triggered: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, 100); + printf ("wait for HUP again: %d\n", ret); + + input.events = EPOLLHUP; + ret = epoll_ctl (epfd, EPOLL_CTL_MOD, fd, &input); + printf ("ctl MOD: %d\n", ret); + + ret = epoll_wait (epfd, &output, 1, -1); + printf ("wait for HUP: %d\n", ret); + + close(fd); + close(epfd); + return 0; +} \ No newline at end of file diff --git a/tests/sys_mman/fmap.c b/tests/sys_mman/fmap.c new file mode 100644 index 0000000000..002f5cc5c4 --- /dev/null +++ b/tests/sys_mman/fmap.c @@ -0,0 +1,104 @@ +// copied from autoconf mmap test, currently panicking kernel + +/* Thanks to Mike Haertel and Jim Avera for this test. + Here is a matrix of mmap possibilities: + mmap private not fixed + mmap private fixed at somewhere currently unmapped + mmap private fixed at somewhere already mapped + mmap shared not fixed + mmap shared fixed at somewhere currently unmapped + mmap shared fixed at somewhere already mapped + For private mappings, we should verify that changes cannot be read() + back from the file, nor mmap's back from the file at a different + address. (There have been systems where private was not correctly + implemented like the infamous i386 svr4.0, and systems where the + VM page cache was not coherent with the file system buffer cache + like early versions of FreeBSD and possibly contemporary NetBSD.) + For shared mappings, we should conversely verify that changes get + propagated back to all the places they're supposed to be. + + Grep wants private fixed already mapped. + The main things grep needs to know about mmap are: + * does it exist and is it safe to write into the mmap'd area + * how to use it (BSD variants) */ + +#include +#include +#include +#include +#include +#include + +int +main (void) +{ + char *data, *data2, *data3; + const char *cdata2; + int i, pagesize; + int fd, fd2; + + pagesize = sysconf(_SC_PAGESIZE); + + /* First, make a file with some known garbage in it. */ + data = (char *) malloc (pagesize); + if (!data) + return 1; + for (i = 0; i < pagesize; ++i) + *(data + i) = rand (); + umask (0); + fd = creat ("conftest.mmap", 0600); + if (fd < 0) + return 2; + if (write (fd, data, pagesize) != pagesize) + return 3; + close (fd); + + /* Next, check that the tail of a page is zero-filled. File must have + non-zero length, otherwise we risk SIGBUS for entire page. */ + fd2 = open ("conftest.txt", O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd2 < 0) + return 4; + cdata2 = ""; + if (write (fd2, cdata2, 1) != 1) + return 5; + data2 = (char *) mmap (0, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0L); + if (data2 == MAP_FAILED) + return 6; + for (i = 0; i < pagesize; ++i) + if (*(data2 + i)) + return 7; + close (fd2); + if (munmap (data2, pagesize)) + return 8; + + /* Next, try to mmap the file at a fixed address which already has + something else allocated at it. If we can, also make sure that + we see the same garbage. */ + fd = open ("conftest.mmap", O_RDWR); + if (fd < 0) + return 9; + if (data2 != mmap (data2, pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED, fd, 0L)) + return 10; + for (i = 0; i < pagesize; ++i) + if (*(data + i) != *(data2 + i)) + return 11; + + /* Finally, make sure that changes to the mapped area do not + percolate back to the file as seen by read(). (This is a bug on + some variants of i386 svr4.0.) */ + for (i = 0; i < pagesize; ++i) + *(data2 + i) = *(data2 + i) + 1; + data3 = (char *) malloc (pagesize); + if (!data3) + return 12; + if (read (fd, data3, pagesize) != pagesize) + return 13; + for (i = 0; i < pagesize; ++i) + if (*(data + i) != *(data3 + i)) + return 14; + close (fd); + free (data); + free (data3); + return 0; +} \ No newline at end of file diff --git a/tests/sys_mman.c b/tests/sys_mman/mmap.c similarity index 88% rename from tests/sys_mman.c rename to tests/sys_mman/mmap.c index 2e3ddaa68e..81b868ca74 100644 --- a/tests/sys_mman.c +++ b/tests/sys_mman/mmap.c @@ -12,11 +12,13 @@ int main() { puts("Mapping 1 page of memory..."); char *map1 = mmap((void *) 0x200000000, (size_t) page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); puts("Mapping 3 pages of memory..."); - char *map2 = mmap(map1 + page_size, (size_t) page_size * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + char *map2 = mmap(map1 + page_size, (size_t) page_size * 4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); ERROR_IF(mmap, map1, == MAP_FAILED); ERROR_IF(mmap, map2, == MAP_FAILED); + (void)madvise(map1 + page_size + 3, page_size, PROT_NONE); + puts("Randomizing mapping #1"); for (int i = 0; i < page_size; ++i) { map1[i] = (char) (rand() & 0xFF); @@ -43,7 +45,7 @@ int main() { // map2[page_size] = 0; puts("Unmapping it all at once!"); - munmap(map1, (size_t) page_size * 4); + munmap(map1, (size_t) page_size * 5); // Page fault: // *map2 = 0; diff --git a/tests/sys_resource/constants.c b/tests/sys_resource/constants.c new file mode 100644 index 0000000000..7a93b7f4d5 --- /dev/null +++ b/tests/sys_resource/constants.c @@ -0,0 +1,27 @@ +#include +#include + +int main(void) { + // Checks availability of constants specified in + // https://pubs.opengroup.org/onlinepubs/7908799/xsh/sysresource.h.html + printf("PRIO_PROCESS: %d\n", PRIO_PROCESS); + printf("PRIO_PGRP: %d\n", PRIO_PGRP); + printf("PRIO_USER: %d\n", PRIO_USER); + + printf("RLIM_INFINITY: %lld\n", RLIM_INFINITY); + printf("RLIM_SAVED_MAX: %lld\n", RLIM_SAVED_MAX); + printf("RLIM_SAVED_CUR: %lld\n", RLIM_SAVED_CUR); + + printf("RUSAGE_SELF: %lld\n", RUSAGE_SELF); + printf("RUSAGE_CHILDREN: %lld\n", RUSAGE_CHILDREN); + + printf("RLIMIT_CORE: %d\n", RLIMIT_CORE); + printf("RLIMIT_CPU: %d\n", RLIMIT_CPU); + printf("RLIMIT_DATA: %d\n", RLIMIT_DATA); + printf("RLIMIT_FSIZE: %d\n", RLIMIT_FSIZE); + printf("RLIMIT_NOFILE: %d\n", RLIMIT_NOFILE); + printf("RLIMIT_STACK: %d\n", RLIMIT_STACK); + printf("RLIMIT_AS: %d\n", RLIMIT_AS); + + return 0; +} diff --git a/tests/resource/getrusage.c b/tests/sys_resource/getrusage.c similarity index 100% rename from tests/resource/getrusage.c rename to tests/sys_resource/getrusage.c diff --git a/tests/sys_resource/rlimit_roundtrip.c b/tests/sys_resource/rlimit_roundtrip.c new file mode 100644 index 0000000000..c90a6b794a --- /dev/null +++ b/tests/sys_resource/rlimit_roundtrip.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include + +int main(void) { + struct rlimit original; + struct rlimit current; + struct rlimit invalid; + + errno = 0; + assert(getrlimit(RLIMIT_NOFILE, &original) == 0); + + errno = 0; + assert(getrlimit(RLIMIT_NLIMITS, ¤t) == -1); + assert(errno == EINVAL); + + errno = 0; + assert(getrlimit(RLIMIT_NOFILE, NULL) == -1); + assert(errno == EFAULT); + + errno = 0; + assert(getrusage(RUSAGE_SELF, NULL) == -1); + assert(errno == EFAULT); + + errno = 0; + assert(setrlimit(RLIMIT_NOFILE, NULL) == -1); + assert(errno == EFAULT); + + invalid.rlim_cur = original.rlim_max; + invalid.rlim_max = original.rlim_cur; + errno = 0; + assert(setrlimit(RLIMIT_NOFILE, &invalid) == -1); + assert(errno == EINVAL); + + if (original.rlim_max != RLIM_INFINITY) { + invalid.rlim_cur = original.rlim_max + 1; + invalid.rlim_max = original.rlim_max + 1; + errno = 0; + assert(setrlimit(RLIMIT_NOFILE, &invalid) == -1); + assert(errno == EPERM); + } + + current.rlim_cur = original.rlim_cur > 16 ? original.rlim_cur - 16 : original.rlim_cur; + current.rlim_max = original.rlim_max; + errno = 0; + assert(setrlimit(RLIMIT_NOFILE, ¤t) == 0); + + struct rlimit roundtrip; + errno = 0; + assert(getrlimit(RLIMIT_NOFILE, &roundtrip) == 0); + assert(roundtrip.rlim_cur == current.rlim_cur); + assert(roundtrip.rlim_max == current.rlim_max); + + long open_max = sysconf(_SC_OPEN_MAX); + if (current.rlim_cur == RLIM_INFINITY) { + assert(open_max == -1); + } else if (current.rlim_cur > LONG_MAX) { + assert(open_max == LONG_MAX); + } else { + assert(open_max == (long)current.rlim_cur); + } + + if (current.rlim_cur > INT_MAX) { + assert(getdtablesize() == INT_MAX); + } else { + assert(getdtablesize() == (int)current.rlim_cur); + } + + errno = 0; + assert(setrlimit(RLIMIT_NOFILE, &original) == 0); + assert(getrlimit(RLIMIT_NOFILE, &roundtrip) == 0); + assert(roundtrip.rlim_cur == original.rlim_cur); + assert(roundtrip.rlim_max == original.rlim_max); + + puts("rlimit roundtrip ok"); + return 0; +} diff --git a/tests/sys_socket/getpeername.c b/tests/sys_socket/getpeername.c new file mode 100644 index 0000000000..98eb2fa28a --- /dev/null +++ b/tests/sys_socket/getpeername.c @@ -0,0 +1,77 @@ +#include + +#include "test_helpers.h" + +void print_sockaddr(char *ctx, struct sockaddr *addr) { + char ip_string[INET6_ADDRSTRLEN]; + void *raw_ip_addr; + char *fam; + in_port_t port; + + if (addr->sa_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr; + raw_ip_addr = &(ipv4->sin_addr); + port = ntohs(ipv4->sin_port); + fam = "AF_INET"; + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr; + raw_ip_addr = &(ipv6->sin6_addr); + port = ntohs(ipv6->sin6_port); + fam = "AF_INET6"; + } else { + printf("Unknown address family: %d\n", addr->sa_family); + exit(1); + } + + if (inet_ntop(addr->sa_family, raw_ip_addr, ip_string, sizeof(ip_string)) == NULL) { + perror("inet_ntop failed"); + exit(1); + } + + printf("%s [%s]: %s:%u\n", ctx, fam, ip_string, port); +} + +int main(void) +{ + int status; + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + ERROR_IF(socket, listen_fd, == -1); + + struct sockaddr_in addr = + { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(0x7F000001 /* 127.0.0.1 */) }, + .sin_port = htons(0), + }; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + status = bind(listen_fd, saddr, addr_len); + ERROR_IF(bind, status, == -1); + + status = listen(listen_fd, 1); + ERROR_IF(bind, status, == -1); + + status = getsockname(listen_fd, saddr, &addr_len); + ERROR_IF(getsockname, status, == -1); + print_sockaddr("listen sockname", saddr); + + int client_fd = socket(AF_INET, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, saddr, addr_len); + ERROR_IF(connect, status, == -1); + + struct sockaddr_in peer; + struct sockaddr* speer = (struct sockaddr*)&peer; + socklen_t peer_len = sizeof(struct sockaddr_in); + status = getpeername(client_fd, speer, &peer_len); + ERROR_IF(getpeername, status, == -1); + print_sockaddr("client peername", speer); + + status = memcmp(saddr, speer, sizeof(struct sockaddr_in)); + UNEXP_IF(memcmp, status, != 0); + + status = getsockname(client_fd, saddr, &addr_len); + ERROR_IF(getsockname, status, == -1); + print_sockaddr("client sockname", saddr); +} diff --git a/tests/sys_socket/recv.c b/tests/sys_socket/recv.c new file mode 100644 index 0000000000..058fea14d9 --- /dev/null +++ b/tests/sys_socket/recv.c @@ -0,0 +1,50 @@ +#include + +#include "test_helpers.h" + +int main(void) +{ + int status; + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + ERROR_IF(socket, listen_fd, == -1); + + struct sockaddr_in addr = + { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(0x7F000001 /* 127.0.0.1 */) }, + .sin_port = htons(0), + }; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + status = bind(listen_fd, saddr, addr_len); + ERROR_IF(bind, status, == -1); + + status = listen(listen_fd, 1); + ERROR_IF(listen, status, == -1); + + status = getsockname(listen_fd, saddr, &addr_len); + ERROR_IF(getsockname, status, == -1); + + int client_fd = socket(AF_INET, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, saddr, addr_len); + ERROR_IF(connect, status, == -1); + + int server_fd = accept(listen_fd, NULL, NULL); + ERROR_IF(accept, status, == -1); + + char *c = "foo"; + status = send(server_fd, c, 4, 0); + ERROR_IF(send, status, == -1); + + char x[4]; + ssize_t amount = recv(client_fd, x, 4, 0); + ERROR_IF(recv, amount, == -1); + UNEXP_IF(recv, amount, != 4); + + status = strcmp(c, x); + printf("send %s\n", c); + printf("recv %s\n", x); + UNEXP_IF(strcmp, status, != 0); +} diff --git a/tests/sys_socket/recvfrom.c b/tests/sys_socket/recvfrom.c new file mode 100644 index 0000000000..2b57597280 --- /dev/null +++ b/tests/sys_socket/recvfrom.c @@ -0,0 +1,56 @@ +#include + +#include +#include + +#include "test_helpers.h" + +int main(void) +{ + int status; + int server_fd = socket(AF_INET, SOCK_DGRAM, 0); + ERROR_IF(socket, server_fd, == -1); + + struct sockaddr_in addr = + { + .sin_family = AF_INET, + .sin_addr = { .s_addr = htonl(0x7F000001 /* 127.0.0.1 */) }, + .sin_port = htons(0), + }; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + status = bind(server_fd, saddr, addr_len); + ERROR_IF(bind, status, == -1); + + status = getsockname(server_fd, saddr, &addr_len); + ERROR_IF(getsockname, status, == -1); + + int client_fd = socket(AF_INET, SOCK_DGRAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, saddr, addr_len); + ERROR_IF(connect, status, == -1); + + struct sockaddr_in name; + struct sockaddr* sname = (struct sockaddr*)&name; + socklen_t name_len = sizeof(struct sockaddr_in); + status = getsockname(client_fd, sname, &name_len); + ERROR_IF(getsockname, status, == -1); + + char *c = "bar"; + status = sendto(server_fd, c, 4, 0, sname, name_len); + ERROR_IF(sendto, status, == -1); + + struct sockaddr_in from; + struct sockaddr* sfrom = (struct sockaddr*)&from; + socklen_t from_len = sizeof(struct sockaddr_in); + char x[4]; + ssize_t amount = recvfrom(client_fd, x, 4, 0, sfrom, &from_len); + ERROR_IF(recvfrom, amount, == -1); + UNEXP_IF(recvfrom, amount, != 4); + + status = strcmp(c, x); + printf("sendto %s\n", c); + printf("recvfrom %s\n", x); + UNEXP_IF(strcmp, status, != 0); +} diff --git a/tests/sys_socket/shutdown.c b/tests/sys_socket/shutdown.c new file mode 100644 index 0000000000..9653633ddc --- /dev/null +++ b/tests/sys_socket/shutdown.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) { + int sv[2]; + pid_t pid; + char buf[64]; + const char *parent_msg = "ping"; + const char *child_msg = "pong"; + + int status = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + ERROR_IF(socketpair, status, == -1); + + pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + close(sv[0]); + + memset(buf, 0, sizeof(buf)); + ssize_t n = recv(sv[1], buf, sizeof(buf), 0); + ERROR_IF(recv, n, == -1); + printf("child: received '%s'\n", buf); + + n = recv(sv[1], buf, sizeof(buf), 0); + if (n != 0) { + fprintf( + stderr, + "FAILURE: Expected EOF (0) after parent shutdown, got %ld\n", + (long)n); + exit(EXIT_FAILURE); + } + printf("child: verified EOF from parent (SHUT_WR worked)\n"); + + status = send(sv[1], child_msg, 5, 0); + ERROR_IF(send, status, == -1); + + close(sv[1]); + exit(0); + } else { + close(sv[1]); + + status = send(sv[0], parent_msg, 5, 0); + ERROR_IF(send, status, == -1); + + status = shutdown(sv[0], SHUT_WR); + ERROR_IF(shutdown, status, == -1); + printf("parent: shutdown(SHUT_WR) performed\n"); + + memset(buf, 0, sizeof(buf)); + ssize_t n = recv(sv[0], buf, sizeof(buf), 0); + ERROR_IF(recv, n, == -1); + + if (n == 0) { + fprintf(stderr, + "FAILURE: Parent received EOF, but expected 'pong'\n"); + exit(EXIT_FAILURE); + } + printf("parent: received '%s' (Shutdown allows reading)\n", buf); + + status = send(sv[0], "garbage", 7, MSG_NOSIGNAL); + if (status != -1 || errno != EPIPE) { + fprintf(stderr, + "WARNING: Expected EPIPE on write after shutdown, got " + "status %d\n", + status); + } else { + printf("parent: verified EPIPE on write after shutdown\n"); + } + + close(sv[0]); + wait(NULL); + } + + return 0; +} diff --git a/tests/sys_socket/unixpeername.c b/tests/sys_socket/unixpeername.c new file mode 100644 index 0000000000..cef0c0453e --- /dev/null +++ b/tests/sys_socket/unixpeername.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +void print_sockaddr(char *ctx, struct sockaddr *addr, socklen_t len) { + if (addr->sa_family == AF_UNIX) { + struct sockaddr_un *un = (struct sockaddr_un *)addr; + if (len <= sizeof(sa_family_t)) { + printf("%s [AF_UNIX]: (unnamed)\n", ctx); + } else { + printf("%s [AF_UNIX]: %s\n", ctx, un->sun_path); + } + } else { + printf("%s: Unknown address family %d\n", ctx, addr->sa_family); + exit(1); + } +} + +int main(void) +{ + int status; + // TODO: in linux the sockname shown exactly like this string, not the case with redox. + const char* socket_path = "unixpeername.sock"; + unlink(socket_path); + + int listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, listen_fd, == -1); + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + struct sockaddr* saddr = (struct sockaddr*)&addr; + socklen_t addr_len = sizeof(struct sockaddr_un); + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + status = bind(listen_fd, saddr, addr_len); + ERROR_IF(bind, status, == -1); + + // TODO: If this commented out, connect() should error with connection refused + status = listen(listen_fd, 1); + ERROR_IF(listen, status, == -1); + + struct sockaddr_un check; + struct sockaddr* scheck = (struct sockaddr*)✓ + status = getsockname(listen_fd, scheck, &addr_len); + ERROR_IF(getsockname, status, == -1); + print_sockaddr("listen sockname", scheck, addr_len); + + int client_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, saddr, addr_len); + ERROR_IF(connect, status, == -1); + + struct sockaddr_un peer; + struct sockaddr* speer = (struct sockaddr*)&peer; + socklen_t peer_len = sizeof(struct sockaddr_un); + status = getpeername(client_fd, speer, &peer_len); + ERROR_IF(getpeername, status, == -1); + print_sockaddr("client peername", speer, peer_len); + + status = strcmp(check.sun_path, peer.sun_path); + UNEXP_IF(strcmp, status, != 0); + + status = getsockname(client_fd, scheck, &addr_len); + ERROR_IF(getsockname, status, == -1); + print_sockaddr("client sockname", scheck, addr_len); + + close(client_fd); + close(listen_fd); + unlink(socket_path); + + return 0; +} diff --git a/tests/sys_socket/unixrecv.c b/tests/sys_socket/unixrecv.c new file mode 100644 index 0000000000..5d02f896f4 --- /dev/null +++ b/tests/sys_socket/unixrecv.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) +{ + int status; + const char* socket_path = "unix_stream.sock"; + unlink(socket_path); + + int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, server_fd, == -1); + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + status = bind(server_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(bind, status, == -1); + + status = listen(server_fd, 5); + ERROR_IF(listen, status, == -1); + + pid_t pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + // to test that blocking in accept() works + usleep(500000); + + int client_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(connect, status, == -1); + + char *msg = "ipsum"; + printf("send %s\n", msg); + status = send(client_fd, msg, 6, 0); + ERROR_IF(send, status, == -1); + + close(client_fd); + return 0; + } else { + int accepted_fd = accept(server_fd, NULL, NULL); + ERROR_IF(accept, accepted_fd, == -1); + + char x[6]; + ssize_t amount = recv(accepted_fd, x, 6, 0); + ERROR_IF(recv, amount, == -1); + + printf("recv %s\n", x); + close(accepted_fd); + close(server_fd); + + wait(NULL); + unlink(socket_path); + } + + return 0; +} \ No newline at end of file diff --git a/tests/sys_socket/unixrecvearly.c b/tests/sys_socket/unixrecvearly.c new file mode 100644 index 0000000000..6c496f53f2 --- /dev/null +++ b/tests/sys_socket/unixrecvearly.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +// similar to "unixrecv", but this test if recv() works before accept(). +int main(void) +{ + int status; + const char* socket_path = "unix_read.sock"; + unlink(socket_path); + + int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, server_fd, == -1); + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + status = bind(server_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(bind, status, == -1); + + status = listen(server_fd, 5); + ERROR_IF(listen, status, == -1); + + pid_t pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + int client_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(connect, status, == -1); + + char x[6]; + ssize_t amount = recv(client_fd, x, 6, 0); + ERROR_IF(recv, amount, == -1); + printf("recv %s\n", x); + + close(client_fd); + return 0; + } else { + // to test that blocking in recv() works + usleep(500000); + + int accepted_fd = accept(server_fd, NULL, NULL); + ERROR_IF(accept, accepted_fd, == -1); + + char *msg = "ipsum"; + printf("send %s\n", msg); + status = send(accepted_fd, msg, 6, 0); + ERROR_IF(send, status, == -1); + + close(accepted_fd); + close(server_fd); + + wait(NULL); + unlink(socket_path); + } + + return 0; +} \ No newline at end of file diff --git a/tests/sys_socket/unixrecvfrom.c b/tests/sys_socket/unixrecvfrom.c new file mode 100644 index 0000000000..8d11bd32b7 --- /dev/null +++ b/tests/sys_socket/unixrecvfrom.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) +{ + int status; + const char* socket_path = "unixrecvfrom.sock"; + + unlink(socket_path); + + int server_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + ERROR_IF(socket, server_fd, == -1); + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + status = bind(server_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(bind, status, == -1); + + int client_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); + ERROR_IF(connect, status, == -1); + + char *c = "lorem"; + status = send(client_fd, c, 6, 0); + ERROR_IF(send, status, == -1); + + char x[6]; + struct sockaddr_un from; + socklen_t from_len = sizeof(struct sockaddr_un); + + ssize_t amount = recvfrom(server_fd, x, 6, 0, (struct sockaddr*)&from, &from_len); + ERROR_IF(recvfrom, amount, == -1); + UNEXP_IF(recvfrom, amount, != 6); + + status = strcmp(c, x); + printf("send %s\n", c); + printf("recvfrom %s\n", x); + UNEXP_IF(strcmp, status, != 0); + + close(server_fd); + close(client_fd); + unlink(socket_path); +} \ No newline at end of file diff --git a/tests/sys_socket/unixsocketpair.c b/tests/sys_socket/unixsocketpair.c new file mode 100644 index 0000000000..0f5aa0f618 --- /dev/null +++ b/tests/sys_socket/unixsocketpair.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +void socketpair_test(int flags) { + int sv[2]; + pid_t pid; + char buf[64]; + const char *parent_msg = "ping"; + const char *child_msg = "pong"; + + int status = socketpair(AF_UNIX, flags, 0, sv); + ERROR_IF(socketpair, status, == -1); + + pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + close(sv[0]); + + ssize_t n = recv(sv[1], buf, sizeof(buf), 0); + ERROR_IF(recv, n, == -1); + printf("child: %s\n", buf); + + status = send(sv[1], child_msg, 5, 0); + ERROR_IF(send, status, == -1); + + close(sv[1]); + exit(0); + } else { + close(sv[1]); + + status = send(sv[0], parent_msg, 5, 0); + ERROR_IF(send, status, == -1); + + ssize_t n = recv(sv[0], buf, sizeof(buf), 0); + ERROR_IF(recv, n, == -1); + printf("parent: %s\n", buf); + + close(sv[0]); + wait(NULL); + } +} + +int main(void) { + printf("SOCK_STREAM\n"); + socketpair_test(SOCK_STREAM); + printf("SOCK_DGRAM\n"); + socketpair_test(SOCK_DGRAM); + return 0; +} \ No newline at end of file diff --git a/tests/sys_socket/unixwrite.c b/tests/sys_socket/unixwrite.c new file mode 100644 index 0000000000..4386c66be2 --- /dev/null +++ b/tests/sys_socket/unixwrite.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +// similar to "unixrecv", but this test if write() works before accept(). +// "unixrecvfrom" is the similar test to this one, but using SOCK_DGRAM. +// "unixrecvearly" is also the similar test to this one, but using recv(). +int main() { + int status; + const char* socket_path = "unix_write.sock"; + unlink(socket_path); + + int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, server_fd, == -1); + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + unlink(socket_path); + + status = bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)); + ERROR_IF(bind, status, == -1); + + status = listen(server_fd, 1); + ERROR_IF(listen, status, == -1); + + int client_fd = socket(AF_UNIX, SOCK_STREAM, 0); + ERROR_IF(socket, client_fd, == -1); + + status = connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)); + ERROR_IF(connect, status, == -1); + + char *msg = "yes"; + ssize_t ret = write(client_fd, msg, 4); + ERROR_IF(write, ret, == -1); + UNEXP_IF(raise, ret, != 4); + + int accepted_fd = accept(server_fd, NULL, NULL); + ERROR_IF(accept, accepted_fd, == -1); + + char x[4]; + ssize_t amount = read(accepted_fd, x, 4); + ERROR_IF(read, amount, == -1); + + close(accepted_fd); + close(client_fd); + close(server_fd); + return 0; +} diff --git a/tests/sys_stat/chmod.c b/tests/sys_stat/chmod.c new file mode 100644 index 0000000000..30be2a6f19 --- /dev/null +++ b/tests/sys_stat/chmod.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +/* #if defined(__linux__) && !defined(__redox__) */ +/* #include */ +/* #else */ +/* #define KERNEL_VERSION(x,y,z) (0) */ +/* #endif */ + +__attribute__((nonnull(2))) +static bool check_mode( + int dir, + const char path[], + int flags, + mode_t expected +) { + struct stat stat = {0}; + if (fstatat(dir, path, &stat, flags) == -1) { + perror("fstatat"); + return false; + } + + if ((stat.st_mode & 0777) != expected) { + fprintf( + stderr, + "Mode mismatch\n\tExpected: %o\n\tActual: %o\n", + expected, + stat.st_mode & 0777 + ); + return false; + } + + return true; +} + +int main(void) { + int status = EXIT_FAILURE; + + char template[] = "/tmp/chmtest.XXXXXX"; + if (!mkdtemp(template)) { + perror("mkdtemp"); + goto bye; + } + size_t len = sizeof(template) - 1; + + // Create a file and a link to chmod. + const char file_name[] = "king_crimson"; + char file_path[PATH_MAX] = {0}; + memcpy(file_path, template, len); + file_path[len] = '/'; + memcpy(&file_path[len + 1], file_name, sizeof(file_name)); + + int filefd = open(file_path, O_CREAT); + if (filefd == -1) { + perror("open (file, O_CREAT)"); + goto rmtempdir; + } + + const char link_name[] = "red"; + char link_path[PATH_MAX] = {0}; + memcpy(link_path, template, len); + link_path[len] = '/'; + memcpy(&link_path[len + 1], link_name, sizeof(link_name)); + + if (symlink(file_path, link_path) == -1) { + perror("symlink"); + goto rmfiles; + } + #ifdef __redox__ + int linkfd = open(link_path, O_PATH | O_NOFOLLOW | O_SYMLINK); + if (linkfd == -1) { + perror("open (link, O_NOFOLLOW)"); + goto rmfiles; + } + #endif + + int dir = open(template, O_DIRECTORY); + if (dir == -1) { + perror("open (temp dir)"); + goto closelink; + } + + // chmod + + // File + if (chmod(file_path, 0666) == -1) { + perror("chmod file 0666"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0666)) { + fprintf(stderr, "Context: chmod %s\n", file_name); + goto closetemp; + } + + // Link (deferenced) + if (chmod(link_path, 0777) == -1) { + perror("chmod link 0777"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0777)) { + fprintf(stderr, "Context: chmod %s\n", link_name); + goto closetemp; + } + + // Dir + if (chmod(template, 0766) == -1) { + perror("chmod directory 0766"); + goto closetemp; + } + if (!check_mode(dir, "", AT_EMPTY_PATH, 0766)) { + fprintf(stderr, "Context: chmod %s\n", template); + goto closetemp; + } + + // fchmod + + // File + if (fchmod(filefd, 0644) == -1) { + perror("fchmod file 0644"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0644)) { + fprintf(stderr, "Context: fchmod %s\n", file_name); + goto closetemp; + } + + // Link (not followed) + #ifdef __redox__ + if (fchmod(linkfd, 0666) == -1) { + perror("fchmod link 0666"); + goto closetemp; + } + if (!check_mode(dir, link_name, AT_SYMLINK_NOFOLLOW, 0666)) { + fprintf(stderr, "Context: fchmod %s\n", link_name); + goto closetemp; + } + #endif + + // Directory + if (fchmod(dir, 0777) == -1) { + perror("fchmod directory 0777"); + goto closetemp; + } + if (!check_mode(dir, "", AT_EMPTY_PATH, 0777)) { + fprintf(stderr, "Context: fchmod %s\n", template); + goto closetemp; + } + + // fchmodat + + // File (relative) + if (fchmodat(dir, file_name, 0666, 0) == -1) { + perror("fchmodat directory 0666"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0666)) { + fprintf(stderr, "Context: fchmodat %s\n", file_name); + goto closetemp; + } + + // File (absolute) + if (fchmodat(dir, file_path, 0777, 0) == -1) { + perror("fchmodat file 0777"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0777)) { + fprintf(stderr, "Context: fchmodat %s\n", file_path); + } + + // Link (followed) + if (fchmodat(dir, link_name, 0666, 0) == -1) { + perror("fchmodat link 0666"); + goto closetemp; + } + if (!check_mode(dir, file_name, 0, 0666)) { + fprintf(stderr, "Context: fchmodat %s\n", link_name); + goto closetemp; + } + + // Link (not followed) + #ifdef __redox__ + if (fchmodat(dir, link_name, 0777, AT_SYMLINK_NOFOLLOW) == -1) { + perror("fchmodat link 0777"); + goto closetemp; + } + if (!check_mode(dir, link_name, AT_SYMLINK_NOFOLLOW, 0777)) { + fprintf(stderr, "Context: fchmodat %s\n", link_name); + goto closetemp; + } + #endif + + // Directory + // AT_EMPTY_PATH support is relatively new so this fails in CI. + #if defined(__redox__) // || LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0) + if (fchmodat(dir, "", 0700, AT_EMPTY_PATH) == -1) { + perror("fchmodat directory 0700"); + goto closetemp; + } + if (!check_mode(dir, "", AT_EMPTY_PATH, 0700)) { + fprintf(stderr, "Context: fchmodat %s\n", template); + goto closetemp; + } + + // Directory (cwd) + char old_cwd[PATH_MAX] = {0}; + if (!getcwd(old_cwd, PATH_MAX)) { + perror("getcwd"); + goto closetemp; + } + if (chdir(template) == -1) { + perror("chdir (temp dir)"); + goto closetemp; + } + + if (fchmodat(AT_FDCWD, "", 0777, AT_EMPTY_PATH) == -1) { + perror("fchmodat cwd 0777"); + goto closetemp; + } + if (!check_mode(AT_FDCWD, "", AT_EMPTY_PATH, 0777)) { + fprintf(stderr, "Context: fchmodat %s\n", template); + goto closetemp; + } + + if (chdir(old_cwd) == -1) { + perror("chdir (old cwd)"); + goto closetemp; + } + #endif + + status = EXIT_SUCCESS; +closetemp: + close(dir); +closelink: + #ifdef __redox__ + close(linkfd); + #endif +rmfiles: + close(filefd); + unlink(file_path); + unlink(link_path); +rmtempdir: + rmdir(template); +bye: + return status; +} diff --git a/tests/sys_stat/fstatat.c b/tests/sys_stat/fstatat.c new file mode 100644 index 0000000000..a3a2b075c0 --- /dev/null +++ b/tests/sys_stat/fstatat.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +// Create file `dir/name` and fill it with `contents`. +__attribute__((nonnull)) +static const char* mktestfile( + const char dir[], + const char name[], + const char contents[] +) { + size_t dir_len = strlen(dir); + size_t name_len = strlen(name); + + // dir + / name + \0 + char* path = malloc(dir_len + name_len + 2); + if (!path) { + perror("malloc"); + return NULL; + } + // memcpy is faster/recommended but I'm lazy. + strcpy(path, dir); + strcat(path, "/"); + strcat(path, name); + + int fd = creat(path, 0700); + if (fd < 0) { + perror("creat"); + free(path); + return NULL; + } + + size_t contents_len = strlen(contents); + // Assumes that the handful of bytes of contents are fully written. + if (write(fd, contents, contents_len) < 0) { + perror("write"); + free(path); + close(fd); + return NULL; + } + + close(fd); + return path; +} + +// Create a symlink, `dir/name`, to `target` and return its path. +__attribute__((nonnull)) +static const char* mktestlink( + const char dir[], + const char name[], + const char target[] +) { + char* path = malloc(strlen(dir) + strlen(name) + 2); + if (!path) { + perror("malloc"); + return NULL; + } + + strcpy(path, dir); + strcat(path, "/"); + strcat(path, name); + + if (symlink(target, path) == -1) { + perror("symlink"); + free(path); + return NULL; + } + + return path; +} + +__attribute__((nonnull)) +static int run_test( + int fd, + int flags, + const char name[], + const char path[], + size_t expected, + mode_t mode +) { + struct stat stat = {0}; + if (fstatat(fd, name, &stat, flags) == -1) { + perror("fstatat"); + return -1; + } + + if ((size_t) stat.st_size != expected) { + fprintf( + stderr, + "fstatat invalid stats\n\tFile: %s\n\tExpected: %zu Actual: %ld\n", + path, + expected, + stat.st_size + ); + return -1; + } + if ((stat.st_mode & S_IFMT) != mode) { + fprintf( + stderr, + "fstatat invalid mode\n\tFile: %s\n", + path + ); + return -1; + } + + // We create the temp dirs/files therefore we should own them. + uid_t uid = getuid(); + gid_t gid = getgid(); + if (stat.st_uid != uid || stat.st_gid != gid) { + fputs("fstatat invalid UID or GID", stderr); + fprintf( + stderr, + "\n\tExpected UID: %u\n\tActual UID: %u\n", + uid, stat.st_uid + ); + fprintf( + stderr, + "\n\tExpected GID: %u\n\tActual GID: %u\n", + gid, stat.st_gid + ); + + } + + return 0; +} + +int main(void) { + int status = EXIT_FAILURE; + + char dir_template_A[] = "/tmp/fsatest.XXXXXXX"; + char* dir_A = mkdtemp(dir_template_A); + if (!dir_A) { + perror("mkdtemp (dir A)"); + goto bye; + } + + char dir_template_B[] = "/tmp/fsatest.XXXXXXX"; + char* dir_B = mkdtemp(dir_template_B); + if (!dir_B) { + perror("mkdtemp (dir B)"); + goto clean_dir_a; + } + + // File in directory A + const char name[] = "file"; + const char cont_A[] = "Flying Dutchman"; + const char* file_A = mktestfile(dir_A, name, cont_A); + if (!file_A) { + goto clean_dir_b; + } + // Link from directory A to file A + const char link_A_name[] = "alink"; + const char* link_A = mktestlink(dir_A, link_A_name, file_A); + if (!link_A) { + goto clean_file_a; + } + + // File in directory B + const char cont_B[] = "Every Villain is Lemons"; + const char* file_B = mktestfile(dir_B, name, cont_B); + if (!file_B) { + goto unlink_link_A; + } + const char link_B_name[] = "blink"; + // Link from directory A to file B in directory B + const char* link_B = mktestlink(dir_A, link_B_name, file_B); + if (!link_B) { + goto unlink_file_B; + } + + // TESTS + // The size of the file is used as a proxy for checking that stat worked. + int dir_a_fd = open(dir_A, O_DIRECTORY); + if (dir_a_fd == -1) { + perror("open (dir A)"); + goto unlink_link_B; + } + + // fstatat works (basic) + size_t len_cont_A = strlen(cont_A); + if (run_test(dir_a_fd, 0, name, file_A, len_cont_A, S_IFREG) == -1) { + goto close_dir_a_fd; + } + + // fstatat follows symlinks (same dir) + if (run_test(dir_a_fd, 0, link_A_name, link_A, len_cont_A, S_IFREG) == -1) { + fprintf(stderr, "Context: link %s -> %s\n", link_A, file_A); + goto close_dir_a_fd; + } + + // fstatat follows symlinks (to diff dir) + size_t len_cont_B = strlen(cont_B); + if (run_test(dir_a_fd, 0, link_B_name, link_B, len_cont_B, S_IFREG) == -1) { + fprintf(stderr, "Context: link %s -> %s\n", link_B, file_B); + goto close_dir_a_fd; + } + + // AT_SYMLINK_NOFOLLOW + if ( + run_test( + dir_a_fd, + AT_SYMLINK_NOFOLLOW, + link_A_name, + link_A, + strlen(file_A), + S_IFLNK + ) == -1 + ) { + fprintf(stderr, "Context: link %s (no follow)\n", link_A); + goto close_dir_a_fd; + } + + // TODO: O_SEARCH (no Redox support) + + // AT_FDCWD + char old_cwd[PATH_MAX] = {0}; + if (!getcwd(old_cwd, PATH_MAX)) { + perror("getcwd"); + goto close_dir_a_fd; + } + if (chdir(dir_A) == -1) { + perror("chdir"); + goto close_dir_a_fd; + } + if (run_test(AT_FDCWD, 0, name, "./", len_cont_A, S_IFREG) == -1) { + fputs("Context: AT_FDCWD\n", stderr); + goto close_dir_a_fd; + } + if (chdir(old_cwd) == -1) { + perror("chdir"); + goto close_dir_a_fd; + } + + // Absolute path + if (run_test(dir_a_fd, 0, file_A, file_A, len_cont_A, S_IFREG) == -1) { + fprintf(stderr, "Context: absolute path %s\n", file_A); + goto close_dir_a_fd; + } + + // Relative path that traverses dir boundaries + // The path isn't resolved beneath the directory but rather resolved + // relative to it. Directory traversal is allowed if AT_RESOLVE_BENEATH + // isn't used. + const char nested_name[] = "nested"; + char* nested_dir = malloc(strlen(dir_A) + strlen(nested_name) + 2); + if (!nested_dir) { + perror("malloc"); + goto close_dir_a_fd; + } + strcpy(nested_dir, dir_A); + strcat(nested_dir, "/"); + strcat(nested_dir, nested_name); + if (mkdir(nested_dir, 0700) == -1) { + perror("mkdir"); + goto clean_nested_dir; + } + + int nested_fd = open(nested_dir, O_DIRECTORY); + if (nested_fd < 0) { + perror("open"); + goto remove_nested_dir; + } + char rel_nested[sizeof(name) + 5] = "../"; + strcat(rel_nested, name); + if ( + run_test( + nested_fd, + 0, + rel_nested, + rel_nested, + len_cont_A, + S_IFREG + ) == -1 + ) { + fprintf( + stderr, + "Context: relative path from %s to ../%s\n", + nested_dir, + name + ); + goto close_nested_dir; + } + + // TODO: AT_RESOLVE_BENEATH + + // AT_EMPTY_PATH (empty path) + struct stat stat = {0}; + if (fstatat(dir_a_fd, "", &stat, AT_EMPTY_PATH) == -1) { + fputs("Context: AT_EMPTY_PATH with empty path\n", stderr); + goto close_dir_a_fd; + } + if ((stat.st_mode & S_IFMT) != S_IFDIR) { + fputs("Context: AT_EMPTY_PATH should stat dir (empty path)\n", stderr); + goto close_dir_a_fd; + } + + // AT_EMPTY_PATH (non-empty path) + if ( + run_test( + dir_a_fd, + AT_EMPTY_PATH, + link_A_name, + link_A, + len_cont_A, + S_IFREG + ) == -1 + ) { + fputs("Context: AT_EMPTY_PATH with path should stat path\n", stderr); + goto close_dir_a_fd; + } + + // TODO: Swapped directories resolves correctly + + // Failure conditions: + // Empty path without AT_EMPTY_PATH + stat = (struct stat) {0}; + if (fstatat(dir_a_fd, "", &stat, 0) == 0) { + fputs("Context: empty path should fail\n", stderr); + goto close_dir_a_fd; + } + + // Clean up is LIFO where the last created resource is the first cleaned. + status = EXIT_SUCCESS; +close_nested_dir: + close(nested_fd); +remove_nested_dir: + rmdir(nested_dir); +clean_nested_dir: + free((void*) nested_dir); +close_dir_a_fd: + close(dir_a_fd); +unlink_link_B: + unlink(link_B); + free((void*) link_B); +unlink_file_B: + unlink(file_B); + free((void*) file_B); +unlink_link_A: + unlink(link_A); + free((void*) link_A); +clean_file_a: + unlink(file_A); + free((void*) file_A); +clean_dir_b: + rmdir(dir_B); +clean_dir_a: + rmdir(dir_A); +bye: + return status; +} diff --git a/tests/sys_stat/lstat.c b/tests/sys_stat/lstat.c new file mode 100644 index 0000000000..9dd4dd05c1 --- /dev/null +++ b/tests/sys_stat/lstat.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int main(void) { + int status = EXIT_FAILURE; + + char tempdir_path[] = "/tmp/lstat_test.XXXXXX"; + if (!mkdtemp(tempdir_path)) { + perror("mkdtemp"); + goto bye; + } + int tempdir = open(tempdir_path, O_DIRECTORY); + if (tempdir == -1) { + perror("open (tempdir)"); + goto rmtemp; + } + + // Source + const char src_name[] = "kerouac"; + char src_buf[PATH_MAX] = {0}; + strcpy(src_buf, tempdir_path); + strcat(src_buf, "/"); + strcat(src_buf, src_name); + // TODO: Use openat + int source = open(src_buf, O_CREAT); + if (source == -1) { + perror("open"); + goto close_dir; + } + + // Dest (link) + const char dst_name[] = "on_the_road"; + char dst_buf[PATH_MAX] = {0}; + strcpy(dst_buf, tempdir_path); + strcat(dst_buf, "/"); + strcat(dst_buf, dst_name); + if (symlink(src_buf, dst_buf) == -1) { + perror("symlink"); + goto rm_source; + } + + struct stat src_stat = {0}; + if (fstatat(tempdir, dst_name, &src_stat, 0) == -1) { + perror("fstatat"); + goto rm_dest; + } + // Quick consistency check + uid_t uid = getuid(); + gid_t gid = getgid(); + if (src_stat.st_uid != uid) { + fprintf( + stderr, + "uid:\n\tExpected %ld\n\tActual: %ld\n", + (intmax_t) uid, + (intmax_t) gid + ); + goto rm_dest; + } + if (src_stat.st_nlink != 1) { + fputs("nlink: Expected one hard link\n", stderr); + goto rm_dest; + } + if ((src_stat.st_mode & S_IFMT) != S_IFREG) { + fputs("mode: Expected a normal file\n", stderr); + goto rm_dest; + } + + struct stat dst_stat = {0}; + if (lstat(dst_buf, &dst_stat) == -1) { + perror("lstat"); + goto rm_dest; + } + if (memcmp(&src_stat, &dst_stat, sizeof(struct stat)) == 0) { + fputs("lstat incorrectly followed the symlink\n", stderr); + goto rm_dest; + } + if ((dst_stat.st_mode & S_IFMT) != S_IFLNK) { + fputs("lstat did not stat a link\n", stderr); + goto rm_dest; + } + + status = EXIT_SUCCESS; +rm_dest: + unlink(dst_buf); +rm_source: + unlink(src_buf); +close_dir: + close(tempdir); +rmtemp: + rmdir(tempdir_path); +bye: + return status; +} diff --git a/tests/unistd/stat.c b/tests/sys_stat/stat.c similarity index 76% rename from tests/unistd/stat.c rename to tests/sys_stat/stat.c index be856646bd..95fadb1751 100644 --- a/tests/unistd/stat.c +++ b/tests/sys_stat/stat.c @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -9,9 +9,9 @@ int main(void) { printf("sizeof(struct stat): %ld\n", sizeof(struct stat)); - struct stat buf; + struct stat buf = {0}; - int stat_status = stat("unistd/stat.c", &buf); + int stat_status = stat("sys_stat/stat.c", &buf); ERROR_IF(stat, stat_status, == -1); UNEXP_IF(stat, stat_status, != 0); @@ -23,4 +23,9 @@ int main(void) { printf("st_nlink: %lu\n", buf.st_nlink); printf("st_uid: %u\n", buf.st_uid); printf("st_gid: %u\n", buf.st_gid); + + assert(buf.st_size > 600); + assert(buf.st_nlink == 1); + + return EXIT_SUCCESS; } diff --git a/tests/sys_statvfs/statvfs.c b/tests/sys_statvfs/statvfs.c new file mode 100644 index 0000000000..62785f1bfa --- /dev/null +++ b/tests/sys_statvfs/statvfs.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +int test_statvfs(const char *path) { + struct statvfs buf; + + printf("Testing statvfs on: %s\n", path); + + if (statvfs(path, &buf) != 0) { + perror("statvfs failed"); + return EXIT_FAILURE; + } + + printf(" Filesystem block size: %lu\n", buf.f_bsize); + printf(" Fragment size: %lu\n", buf.f_frsize); + printf(" Total blocks: %lu\n", buf.f_blocks); + printf(" Free blocks: %lu\n", buf.f_bfree); + printf(" Available blocks: %lu\n", buf.f_bavail); + printf(" Total inodes: %lu\n", buf.f_files); + printf(" Free inodes: %lu\n", buf.f_ffree); + + if (buf.f_bsize == 0 || buf.f_frsize == 0) { + fprintf(stderr, "ERROR: Block size or fragment size is zero.\n"); + return EXIT_FAILURE; + } + + if (buf.f_blocks == 0) { + fprintf(stderr, "ERROR: Total number of blocks is zero.\n"); + return EXIT_FAILURE; + } + + return 0; +} + +int main(void) { + const char *test_path = "/"; + + return test_statvfs(test_path); +} diff --git a/tests/sys_syslog/syslog.c b/tests/sys_syslog/syslog.c new file mode 100644 index 0000000000..f7a5a2e75a --- /dev/null +++ b/tests/sys_syslog/syslog.c @@ -0,0 +1,46 @@ +#include +#include + +int main(void) { + // Test that syslog succeeds without explicitly calling openlog + syslog(LOG_INFO, "Testing syslog; disregard"); + closelog(); + + // The rest of the tests use LOG_PERROR so we get verifiable output on stderr + openlog("relibc_test", + LOG_CONS | LOG_PERROR | LOG_NDELAY, + LOG_LOCAL2 + ); + + // Basic + syslog(LOG_EMERG, "This is a test message with formatting: %d", 5); + + // Only alert should print + setlogmask(LOG_MASK(LOG_ALERT)); + syslog(LOG_ALERT, "Hank Hill"); + syslog(LOG_EMERG, "Sells propane and propane accessories"); + + // Squelch all logs with a priority less than WARNING. + setlogmask(LOG_UPTO(LOG_WARNING)); + // First line should be emitted while the second shouldn't. + syslog(LOG_WARNING, "Foo has been bar'd"); + syslog(LOG_NOTICE, "I am a very spammy log message. Ha!"); + + // All of these should print + setlogmask(LOG_UPTO(LOG_DEBUG)); + syslog(LOG_DEBUG, "And now, Eeveelutions"); + syslog(LOG_DEBUG, "Espeon"); + syslog(LOG_INFO, "Umbreon"); + syslog(LOG_NOTICE, "Sylveon"); + syslog(LOG_WARNING, "Glaceon"); + syslog(LOG_ERR, "Leafeon"); + syslog(LOG_CRIT, "Vaporeon"); + syslog(LOG_ALERT, "Flareon"); + syslog(LOG_EMERG, "Jolteon"); + + // The log file should automatically open even if closed. + closelog(); + syslog(LOG_INFO, "Bye from relibc's syslog tests!"); + + return EXIT_SUCCESS; +} diff --git a/tests/termios.c b/tests/termios.c new file mode 100644 index 0000000000..36436811d6 --- /dev/null +++ b/tests/termios.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include + +int main(void) { + // Check that the tty supports VDISABLE + int vdisable = pathconf("/dev/tty", _PC_VDISABLE); + assert(vdisable == _POSIX_VDISABLE); + + int termfd = open("/dev/tty", O_RDWR, O_NDELAY | O_NOCTTY); + if (termfd < 0) { + perror("open"); + return EXIT_FAILURE; + } + + // Currently set/default options + struct termios termios = {0}; + if (tcgetattr(termfd, &termios) < 0) { + perror("tcgetattr"); + close(termfd); + return EXIT_FAILURE; + } + struct termios term_restore = termios; + + const cc_t verase = termios.c_cc[VERASE]; + assert(verase != _POSIX_VDISABLE); + // Disable backspace key control char + termios.c_cc[VERASE] = _POSIX_VDISABLE; + + if (tcsetattr(termfd, TCSANOW, &termios) < 0) { + perror("tcsetattr (setting VDISABLE)"); + close(termfd); + return EXIT_FAILURE; + } + + // Check that it was actually set + struct termios term_vdisable = {0}; + if (tcgetattr(termfd, &term_vdisable) < 0) { + perror("tcgetattr (after setting VDISABLE)"); + close(termfd); + return EXIT_FAILURE; + } + assert(term_vdisable.c_cc[VERASE] == _POSIX_VDISABLE); + + // Restore old config + if (tcsetattr(termfd, TCSAFLUSH, &term_restore) < 0) { + perror("tcsetattr (restoring settings)"); + close(termfd); + return EXIT_FAILURE; + } + + close(termfd); + return EXIT_SUCCESS; +} diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 68705d8673..de4a360951 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -2,6 +2,7 @@ #define _TEST_HELPERS #include +#include #include #include #include @@ -46,6 +47,15 @@ } \ } while(0) +#define ERROR_IF2(func, status, condition) \ + do { \ + if (status condition) { \ + fprintf(stderr, "%s:%s:%d: '%s' failed: %s (%d)\n", \ + __FILE__, __func__, __LINE__, #func, strerror(status), status); \ + _exit(EXIT_FAILURE); \ + } \ + } while(0) + // Throws errors on API return values not defined by the standards. // // Do not pass functions as the status or condition arguments, they might be @@ -71,6 +81,7 @@ fprintf(stderr, _Generic((status), \ char *: "char*(%p) = \"%1$s\"", \ void *: "void*(%p)", \ + ssize_t: "%li", \ default: "%i" \ ), status); \ fprintf(stderr, "\n"); \ @@ -88,4 +99,34 @@ _exit(code); \ } while(0) +// Duplicate of lrand48() logic but suitable for multithreaded use +int random_bool() { + _Thread_local static uint64_t xsubi = 0; + xsubi = (0x5deece66d * xsubi + 0xb) & (0xffffffffffff); + return (xsubi >> 17) % 2 == 0; +} + +// Quick helper for checking desired errno status. +// Use as macro: CHECK_AND_PRINT_ERRNO(); +// If errno is as expected, prints this; otherwise, prints expected vs. actual and exit. +void printf_errno(int errnoval, char *errnoname) { + + printf("%d (%s)", + errno, strerror(errno)); + if (errno != errnoval) { + printf("\n^^^^^ FAILURE ^^^^^ (SHOULD BE %d - %s (%s))\n", + errnoval, errnoname, strerror(errnoval)); + } else { + printf(" - %s\n", errnoname); + } +} + +#define CHECK_AND_PRINT_ERRNO(errnoval) \ + do { \ + printf_errno(errnoval, #errnoval); \ + if (errnoval != errno) { \ + exit(EXIT_FAILURE); \ + } \ + } while(0); + #endif /* _TEST_HELPERS */ diff --git a/tests/time/constants.c b/tests/time/constants.c new file mode 100644 index 0000000000..b29f13f253 --- /dev/null +++ b/tests/time/constants.c @@ -0,0 +1,12 @@ +#include +#include + +int main(void) { + /* TODO: ensure that it is really time.h supplying the NULL constant */ + printf("%p\n", NULL); + + /* Cast to long to avoid format string mismatch in case CLOCKS_PER_SEC is + defined as some other type. The expected value (1 million) will always fit + in a long and will always have that value on conforming systems. */ + printf("%ld\n", (long)CLOCKS_PER_SEC); +} diff --git a/tests/time/localtime.c b/tests/time/localtime.c index fd7af735ce..9d1098fd40 100644 --- a/tests/time/localtime.c +++ b/tests/time/localtime.c @@ -1,3 +1,4 @@ +#include #include #include @@ -6,7 +7,7 @@ int main(void) { int day = 60 * 60 * 24; time_t inputs[] = { -(day * 33), -day, -1, -500, 0, 1, 1531454950 }; - for (int i = 0; i < (sizeof(inputs) / sizeof(time_t)); i += 1) { + for (size_t i = 0; i < (sizeof(inputs) / sizeof(time_t)); i += 1) { struct tm* t = localtime(&inputs[i]); printf( @@ -24,8 +25,7 @@ int main(void) { char *ctime_r_result = ctime_r(&input, ctime_r_buffer); if (ctime_r_result == ctime_r_buffer) { fputs(ctime_r_result, stdout); - } - else { + } else { printf("Unexpected pointer from ctime_r: %p\n", ctime_r_result); } } diff --git a/tests/time/localtime_r.c b/tests/time/localtime_r.c new file mode 100644 index 0000000000..a874daee1e --- /dev/null +++ b/tests/time/localtime_r.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +void test_localtime_r_epoch() { + time_t t = 0; // Unix epoch (1970-01-01 00:00:00) + struct tm result; + + setenv("TZ", "UTC", 1); + tzset(); + + assert(localtime_r(&t, &result) != NULL); + assert(result.tm_year == 70); // Year 1970 - 1900 + assert(result.tm_mon == 0); // January + assert(result.tm_mday == 1); // 1st of January + assert(result.tm_hour == 0); // Midnight + assert(result.tm_min == 0); + assert(result.tm_sec == 0); + + assert(result.tm_isdst == -1 || result.tm_isdst == 0); +} + +void test_localtime_r_non_epoch() { + time_t t = 1609459200; // January 1, 2021 00:00:00 UTC (New Year 2021) + struct tm result; + + setenv("TZ", "UTC", 1); + tzset(); + + assert(localtime_r(&t, &result) != NULL); + assert(result.tm_year == 121); // Year 2021 - 1900 + assert(result.tm_mon == 0); // January + assert(result.tm_mday == 1); // 1st of January + assert(result.tm_hour == 0); // Midnight + assert(result.tm_min == 0); + assert(result.tm_sec == 0); +} + +void test_localtime_r_dst() { + time_t t = 1615708800; // March 14 2021 08:00:00 UTC + struct tm result; + + setenv("TZ", "UTC", 1); + tzset(); + + assert(localtime_r(&t, &result) != NULL); // Ensure localtime_r does not fail + assert(result.tm_year == 121); // Year 2021 - 1900 + assert(result.tm_mon == 2); // March + assert(result.tm_mday == 14); // 14th + assert(result.tm_hour == 8); // 08:00:00 local time + assert(result.tm_min == 0); + assert(result.tm_sec == 0); +} + +void test_localtime_r_large_time() { + time_t t = 32503680000; // A large value: 1 January 3000 (UTC) + struct tm result; + + setenv("TZ", "UTC", 1); + tzset(); + + assert(localtime_r(&t, &result) != NULL); // Ensure localtime_r does not fail + assert(result.tm_year == 1100); // Year 3000 - 1900 = 1100 + assert(result.tm_mon == 0); // January + assert(result.tm_mday == 1); // 1st of January + assert(result.tm_hour == 0); // Midnight + assert(result.tm_min == 0); + assert(result.tm_sec == 0); +} + +void test_dst_transition() { + time_t t = 1615809600; // March 15 2021, 08:00:00 UTC + struct tm result; + + setenv("TZ", "America/New_York", 1); + tzset(); + + localtime_r(&t, &result); + + assert(result.tm_year == 121); // Year 2021 - 1900 = 121 + assert(result.tm_mon == 2); // March (0-based index) + assert(result.tm_mday == 15); // 15th + assert(result.tm_hour == 8); // 00:00:00 local time + assert(result.tm_min == 0); + assert(result.tm_sec == 0); + + assert(result.tm_isdst == 1); // DST should be active + + assert(strcmp(tzname[0], "EST") == 0 || strcmp(tzname[0], "EDT") == 0); // Standard or DST name + assert(strcmp(tzname[1], "EDT") == 0); // Should be the DST version of the timezone +} + +void test_standard_time() { + time_t t = 1609459200; // January 1, 2021, 00:00:00 UTC + struct tm result; + + setenv("TZ", "Asia/Tokyo", 1); + tzset(); + + localtime_r(&t, &result); + + assert(result.tm_year == 121); // Year 2021 - 1900 = 121 + assert(result.tm_mon == 0); // January (0-based index) + assert(result.tm_mday == 1); // 1st + assert(result.tm_hour == 9); // 09:00:00 local time + assert(result.tm_min == 0); + assert(result.tm_sec == 0); + + assert(result.tm_isdst == 0); // DST should NOT be active + + assert(strcmp(tzname[0], "JST") == 0); // Standard time name + assert(strcmp(tzname[1], "EST") != 0); // Should NOT be in DST +} + +int main() { + test_localtime_r_epoch(); + test_localtime_r_non_epoch(); + test_localtime_r_dst(); + test_localtime_r_large_time(); + test_dst_transition(); + test_standard_time(); + return 0; +} diff --git a/tests/time/strftime.c b/tests/time/strftime.c index c9299e0b21..629f86f838 100644 --- a/tests/time/strftime.c +++ b/tests/time/strftime.c @@ -1,14 +1,150 @@ #include #include #include - +#include #include "test_helpers.h" +#define PRINT_BUFSZ 50 +#define V_BUFSZ 3 +#define BUFS 30 + void print(time_t timestamp, char* fmt) { - char* out = malloc(50); - size_t n = strftime(out, 50, fmt, localtime(×tamp)); + char out[PRINT_BUFSZ] = {0}; + size_t n = strftime(out, PRINT_BUFSZ, fmt, localtime(×tamp)); printf("%zu: %s\n", n, out); - free(out); + +} + +void strftime_mix(void) { + setenv("TZ", "Australia/Melbourne", 1); + tzset(); + + // Initialize a struct tm with a known fixed date and time. + // December 31, 2020 23:59:59, which is a Thursday. + struct tm t = {0}; + t.tm_year = 2020 - 1900; // Years since 1900. + t.tm_mon = 11; // December (0-based, so 11 means December). + t.tm_mday = 31; + t.tm_hour = 23; + t.tm_min = 59; + t.tm_sec = 59; + t.tm_wday = 4; // Thursday (0 = Sunday, so Thursday = 4). + t.tm_yday = 365; + t.tm_gmtoff = 39600; // GMT offset for AEDT (UTC+11). + t.tm_isdst = 11; // Daylight saving time is in effect. + t.tm_zone = "AEDT"; // Time zone name. + char buf[BUFS]; + + strftime(buf, BUFS, "%a", &t); + assert(strcmp(buf, "Thu") == 0); + + strftime(buf, BUFS, "%A", &t); + assert(strcmp(buf, "Thursday") == 0); + + strftime(buf, BUFS, "%b", &t); + assert(strcmp(buf, "Dec") == 0); + + strftime(buf, BUFS, "%B", &t); + assert(strcmp(buf, "December") == 0); + + strftime(buf, BUFS, "%d", &t); + assert(strcmp(buf, "31") == 0); + + strftime(buf, BUFS, "%H", &t); + assert(strcmp(buf, "23") == 0); + + strftime(buf, BUFS, "%I", &t); + assert(strcmp(buf, "11") == 0); + + // Day of the year as a zero-padded decimal number (001-366). + strftime(buf, BUFS, "%j", &t); + // Even though t.tm_yday is 365 (zero-based), %j is one-based: "366" + assert(strcmp(buf, "366") == 0); + + strftime(buf, BUFS, "%m", &t); + assert(strcmp(buf, "12") == 0); + + strftime(buf, BUFS, "%M", &t); + assert(strcmp(buf, "59") == 0); + + strftime(buf, BUFS, "%p", &t); + assert(strcmp(buf, "PM") == 0); + + strftime(buf, BUFS, "%S", &t); + assert(strcmp(buf, "59") == 0); + + strftime(buf, BUFS, "%U", &t); + assert(strcmp(buf, "52") == 0); + + strftime(buf, BUFS, "%w", &t); + assert(strcmp(buf, "4") == 0); + + strftime(buf, BUFS, "%W", &t); + assert(strcmp(buf, "52") == 0); + + strftime(buf, BUFS, "%y", &t); + assert(strcmp(buf, "20") == 0); + + strftime(buf, BUFS, "%Y", &t); + assert(strcmp(buf, "2020") == 0); + + strftime(buf, BUFS, "%Z", &t); + assert(strcmp(buf, "AEDT") == 0); + + strftime(buf, BUFS, "%z", &t); + assert(strcmp(buf, "+1100") == 0); + + strftime(buf, BUFS, "%F", &t); + assert(strcmp(buf, "2020-12-31") == 0); + + strftime(buf, BUFS, "%R", &t); + assert(strcmp(buf, "23:59") == 0); + + strftime(buf, BUFS, "%r", &t); + assert(strcmp(buf, "11:59:59 PM") == 0); + + strftime(buf, BUFS, "%T", &t); + assert(strcmp(buf, "23:59:59") == 0); +} + +void test_v(void) { + char buf[V_BUFSZ] = {0}; + struct tm time = {0}; + + // Example dates copied from Wikipedia + // Saturday 2005-01-01 + time.tm_yday = 0; + time.tm_wday = 6; + time.tm_year = 2005; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Saturday 2005-12-31 + time.tm_yday = 365; + time.tm_wday = 6; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Sunday 2006-01-01 + time.tm_yday = 0; + time.tm_wday = 0; + time.tm_year = 2006; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Sunday 2008-12-28 + time.tm_yday = 362; + time.tm_wday = 0; + time.tm_year = 2008; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); + + // Friday 2010-01-01 + time.tm_yday = 0; + time.tm_wday = 5; + time.tm_year = 2010; + strftime(buf, V_BUFSZ, "%V", &time); + puts(buf); } int main(void) { @@ -21,4 +157,11 @@ int main(void) { print(1531839600, "%j %U"); print(1531839600, "%+"); print(1533669431, "%+%+%+%+%+"); // will overflow 50 characters + + // ISO-8601 tests + test_v(); + + strftime_mix(); + + return EXIT_SUCCESS; } diff --git a/tests/time/strptime.c b/tests/time/strptime.c new file mode 100644 index 0000000000..af96064513 --- /dev/null +++ b/tests/time/strptime.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +__attribute__((nonnull)) +static void strptime_test(const char* restrict time, + const char* restrict format, + struct tm expected, + const char* restrict exp_str) { + struct tm actual = {0}; + const char* result = strptime(time, format, &actual); + + // This struct is packed and zeroed beforehand so it shouldn't + // have any holes to throw off memcmp. + // + // If relibc implements the GNU extensions that store timezone, + // then this will need to be modified to memcmp up to + // sizeof(struct tm) - sizeof(char*) followed by a strcmp. + // + // glibc and musl differ a bit which is why some calls to + // this function comment out tm_yday and tm_wday. + // glibc seems to set these fields even if they're not specified, + // which is a cool extra but implementation specific behavior. + if(memcmp(&expected, &actual, sizeof(struct tm))) { + puts("struct tm expected versus actual\n"); + printf("%-8s %d %4d\n", "tm_year", expected.tm_year, actual.tm_year); + printf("%-8s %d %4d\n", "tm_mon", expected.tm_mon, actual.tm_mon); + printf("%-8s %d %4d\n", "tm_mday", expected.tm_mday, actual.tm_mday); + printf("%-8s %d %4d\n", "tm_hour", expected.tm_hour, actual.tm_hour); + printf("%-8s %d %4d\n", "tm_min", expected.tm_min, actual.tm_min); + printf("%-8s %d %4d\n", "tm_sec", expected.tm_sec, actual.tm_sec); + printf("%-8s %d %4d\n", "tm_wday", expected.tm_wday, actual.tm_wday); + printf("%-8s %d %4d\n", "tm_yday", expected.tm_yday, actual.tm_yday); + printf("%-8s %d %4d\n", "tm_isdst", expected.tm_isdst, actual.tm_isdst); + + exit(EXIT_FAILURE); + } + + // Safety: + // `time` our static string which definitely ends with NUL + // `result` is from strptime and ends with a NUL because `time` does + size_t diff = strlen(time) - strlen(result); + assert(!strncmp(exp_str, result, diff)); +} + +int main(void) { + const char emi[] = "02:24:14"; + struct tm emi_expect = { + .tm_hour = 2, + .tm_min = 24, + .tm_sec = 14, + }; + strptime_test(emi, "%T", emi_expect, ""); + + const char daydream[] = "1981-11-18 Daydream Nation"; + struct tm daydream_expect = { + .tm_year = 81, + .tm_mon = 10, + .tm_mday = 18, + /* .tm_wday = 3, */ + /* .tm_yday = 321, */ + }; + const char* daydream_rem = &daydream[10]; + strptime_test(daydream, + "%Y-%m-%d", + daydream_expect, + daydream_rem + ); + + // strptime(3): "(This is the American style date, very confusing to + // non-Americans [...])" + const char america[] = "07/04/76 AMERICA! (Can't use 1776 here :( ))"; + struct tm america_expect = { + .tm_year = 76, + .tm_mon = 6, + .tm_mday = 4, + /* .tm_yday = 185, */ + }; + const char* america_rem = &america[9]; + strptime_test(america, + "%D%n", + america_expect, + america_rem + ); + + const char percent[] = "%"; + struct tm percent_expect = {0}; + strptime_test(percent, + "%%", + percent_expect, + "" + ); + + // TODO: Locale offset + const char redox[] = "Mon Oct 31 11:19:57 2016"; + struct tm redox_expect = { + .tm_year = 116, + .tm_mon = 9, + .tm_mday = 31, + .tm_hour = 11, + .tm_min = 19, + .tm_sec = 57, + /* .tm_yday = 304, */ + .tm_wday = 1, + }; + strptime_test(redox, + "%a%t%b%t%d%t%T%t%Y", + redox_expect, + ""); + + // Roundtrip + const char roundtrip[] = "2012-01-19 13:37:00"; + const char roundtrip_fmt[] = "%Y-%m-%d %H:%M:%S"; + struct tm roundtrip_tm = {0}; + strptime(roundtrip, roundtrip_fmt, &roundtrip_tm); + + char rt_actual[32]; + size_t rt_res = strftime(rt_actual, 32, roundtrip_fmt, &roundtrip_tm); + size_t rt_len = strnlen(roundtrip, 32); + assert(rt_res == rt_len); + assert(!strncmp(roundtrip, rt_actual, rt_len)); + + return EXIT_SUCCESS; +} diff --git a/tests/time/time.c b/tests/time/time.c index 263e9596d5..0655cc50a3 100644 --- a/tests/time/time.c +++ b/tests/time/time.c @@ -1,19 +1,44 @@ -#include -#include +#include #include +#include #include "test_helpers.h" int main(void) { - struct timespec tm = {0, 0}; + struct timespec tm = {0}; int cgt = clock_gettime(CLOCK_REALTIME, &tm); ERROR_IF(clock_gettime, cgt, == -1); + cgt = clock_getres(CLOCK_REALTIME, &tm); + ERROR_IF(clock_getres, cgt, == -1); + + cgt = timespec_get(&tm, TIME_UTC); + if (cgt != TIME_UTC) { + errx( + EXIT_FAILURE, + "timespec_get should have returned %d but returned %d\n", + TIME_UTC, + cgt + ); + } + + cgt = timespec_getres(&tm, TIME_UTC); + if (cgt != TIME_UTC) { + errx( + EXIT_FAILURE, + "timespec_getres should have returned %d but returned %d\n", + TIME_UTC, + cgt + ); + } + time_t t = time(NULL); ERROR_IF(time, t, == (time_t)-1); // TODO: Support clock() on Redox // clock_t c = clock(); // ERROR_IF(clock, c, == (clock_t)-1); + + return EXIT_SUCCESS; } diff --git a/tests/time/timegm.c b/tests/time/timegm.c new file mode 100644 index 0000000000..9fbfa89fac --- /dev/null +++ b/tests/time/timegm.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +int main(void) { + setenv("TZ", "UTC", 1); + tzset(); + + struct { + time_t timestamp; + const char *expected_asctime; + } test_cases[] = { + {0, "Thu Jan 1 00:00:00 1970\n"}, + {5097600, "Sun Mar 1 00:00:00 1970\n"}, + {31535999, "Thu Dec 31 23:59:59 1970\n"}, + {68255999, "Tue Feb 29 23:59:59 1972\n"}, // 1972 is a leap year + {94694399, "Sun Dec 31 23:59:59 1972\n"}, + {951868799, "Tue Feb 29 23:59:59 2000\n"}, // 2000 is a leap year + {978307199, "Sun Dec 31 23:59:59 2000\n"}, + {4107542400, "Mon Mar 1 00:00:00 2100\n"}, // 2100 is not a leap year + {4133980799, "Fri Dec 31 23:59:59 2100\n"}, + {2147483647, "Tue Jan 19 03:14:07 2038\n"}, // 32-bit overflow point + {2147483648, "Tue Jan 19 03:14:08 2038\n"}, + {4294967295, "Sun Feb 7 06:28:15 2106\n"}, // Unsigned 32-bit overflow + {4294967296, "Sun Feb 7 06:28:16 2106\n"} + }; + int num_tests = sizeof(test_cases) / sizeof(test_cases[0]); + + for (int i = 0; i < num_tests; i++) { + time_t orig = test_cases[i].timestamp; + const char *expected_asctime = test_cases[i].expected_asctime; + + struct tm *tm_ptr = gmtime(&orig); + assert(tm_ptr != NULL); + + struct tm local_tm; + memcpy(&local_tm, tm_ptr, sizeof(struct tm)); + + char *asc_time = asctime(&local_tm); + assert(asc_time != NULL); + assert(strcmp(asc_time, expected_asctime) == 0); + + time_t computed = timegm(&local_tm); + assert(computed == orig); + } + + return 0; +} diff --git a/tests/time/tzset.c b/tests/time/tzset.c new file mode 100644 index 0000000000..87fa15f719 --- /dev/null +++ b/tests/time/tzset.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +extern int daylight; +extern long timezone; +extern char *tzname[2]; +void tzset(void); + +int main(void) { + tzset(); + printf("tzname[0] %s, tzname[1] %s, daylight %d, timezone %ld\n", + tzname[0], tzname[1], daylight, timezone); + return 0; +} diff --git a/tests/tls.c b/tests/tls.c index 0257427e5c..2be0006d3e 100644 --- a/tests/tls.c +++ b/tests/tls.c @@ -3,7 +3,7 @@ _Thread_local int tbss = 0; _Thread_local int tdata = 1; -int main(int argc, char ** argv) { +int main(void) { printf("%d == 0\n", tbss); printf("%d == 1\n", tdata); return 0; diff --git a/tests/unistd/alarm.c b/tests/unistd/alarm.c new file mode 100644 index 0000000000..4b0b2f96d6 --- /dev/null +++ b/tests/unistd/alarm.c @@ -0,0 +1,80 @@ +/* + * Tests for alarm(2) - POSIX: https://pubs.opengroup.org/onlinepubs/9799919799/functions/alarm.html + * + * Verifies: + * 1. alarm(0) with no pending alarm returns 0 + * 2. alarm(1) delivers SIGALRM after ~1 second (tested via pause()) + * 3. alarm(0) cancels a pending alarm + * 4. re-arming alarm returns the remaining seconds from the previous alarm + */ + +#include +#include +#include +#include + +#include "test_helpers.h" + +static volatile sig_atomic_t alarm_count = 0; + +static void handler(int sig) { + (void)sig; + alarm_count++; +} + +int main(void) { + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + int r = sigaction(SIGALRM, &sa, NULL); + ERROR_IF(sigaction, r, == -1); + + /* alarm(0) with no existing alarm must return 0 */ + unsigned prev = alarm(0); + UNEXP_IF(alarm, (int)prev, != 0); + puts("alarm(0) baseline: ok"); + + /* alarm(1): SIGALRM must fire; pause() blocks until the signal arrives */ + alarm_count = 0; + alarm(1); + pause(); + if (alarm_count != 1) { + fprintf(stderr, "SIGALRM did not fire (count=%d)\n", (int)alarm_count); + return EXIT_FAILURE; + } + puts("alarm(1) fires: ok"); + + /* alarm(0) must cancel a pending alarm and return remaining secs > 0 */ + alarm_count = 0; + alarm(10); + unsigned remaining = alarm(0); + if (remaining == 0) { + fprintf(stderr, "alarm(0) cancel: expected remaining > 0\n"); + return EXIT_FAILURE; + } + /* Wait longer than original alarm; SIGALRM must NOT fire */ + sleep(2); + if (alarm_count != 0) { + fprintf(stderr, "SIGALRM fired after alarm(0) cancel\n"); + return EXIT_FAILURE; + } + puts("alarm(0) cancel: ok"); + + /* + * Re-arming: arm with 10s, sleep 1s, then re-arm with 3s. + * The return value must be the remaining time (~9s) which is > 0. + */ + alarm_count = 0; + alarm(10); + sleep(1); + remaining = alarm(3); + if (remaining == 0) { + fprintf(stderr, "re-arm: expected remaining > 0, got 0\n"); + return EXIT_FAILURE; + } + alarm(0); /* disarm so SIGALRM doesn't fire after the test */ + puts("alarm re-arm returns remaining: ok"); + + return EXIT_SUCCESS; +} diff --git a/tests/unistd/confstr.c b/tests/unistd/confstr.c new file mode 100644 index 0000000000..d9abde8811 --- /dev/null +++ b/tests/unistd/confstr.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +#define CSPATHSZ 9 +#define BADSIZ 4 + +int main(void) { + char actual[CSPATHSZ] = {0}; + assert(confstr(_CS_PATH, actual, CSPATHSZ) == CSPATHSZ); + const char expected[] = "/usr/bin"; + assert(strncmp(expected, actual, CSPATHSZ) == 0); + + // The constants other than _CS_PATH just return an empty str (no support). + char empty[] = ""; + assert( + confstr( + _CS_POSIX_V6_LP64_OFF64_LIBS, + empty, + 0 + ) == 1 + ); + + // Buffers that are too small should return the expected size. + char small[BADSIZ] = {0}; + assert(confstr(_CS_PATH, small, BADSIZ) == CSPATHSZ); + + // Null buffer and zero length should return the expected size + // for the constant. + assert(confstr(_CS_PATH, NULL, 0) == CSPATHSZ); + + return EXIT_SUCCESS; +} diff --git a/tests/unistd/constants.c b/tests/unistd/constants.c new file mode 100644 index 0000000000..46549c54a3 --- /dev/null +++ b/tests/unistd/constants.c @@ -0,0 +1,123 @@ +#include +#include + +#include "test_helpers.h" + +int main(void) { + // Constants specified in https://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html + + printf("_POSIX_VERSION: %ld\n", _POSIX_VERSION); + /* TODO + printf("_POSIX2_VERSION: %ld\n", _POSIX2_VERSION); + printf("_POSIX2_C_VERSION: %ld\n", _POSIX2_C_VERSION); + printf("_XOPEN_VERSION: %d\n", _XOPEN_VERSION); + + printf("_XOPEN_XCU_VERSION: %d\n", _XOPEN_XCU_VERSION); + + printf("_XOPEN_XPG2: %d\n", _XOPEN_XPG2); + printf("_XOPEN_XPG3: %d\n", _XOPEN_XPG3); + printf("_XOPEN_XPG4: %d\n", _XOPEN_XPG4); + printf("_XOPEN_UNIX: %d\n", _XOPEN_UNIX); + + printf("_POSIX_CHOWN_RESTRICTED: %d\n", _POSIX_CHOWN_RESTRICTED); + printf("_POSIX_NO_TRUNC: %d\n", _POSIX_NO_TRUNC); + printf("_POSIX_VDISABLE: %d\n", _POSIX_VDISABLE); + printf("_POSIX_SAVED_IDS: %d\n", _POSIX_SAVED_IDS); + printf("_POSIX_JOB_CONTROL: %d\n", _POSIX_JOB_CONTROL); + printf("_POSIX_THREADS: %ld\n", _POSIX_THREADS); + printf("_POSIX_THREAD_ATTR_STACKADDR: %ld\n", _POSIX_THREAD_ATTR_STACKADDR); + printf("_POSIX_THREAD_ATTR_STACKSIZE: %ld\n", _POSIX_THREAD_ATTR_STACKSIZE); + printf("_POSIX_THREAD_PROCESS_SHARED: %ld\n", _POSIX_THREAD_PROCESS_SHARED); + printf("_POSIX_THREAD_SAFE_FUNCTIONS: %ld\n", _POSIX_THREAD_SAFE_FUNCTIONS); + + printf("_POSIX2_C_BIND: %ld\n", _POSIX2_C_BIND); + printf("_POSIX2_C_DEV: %ld\n", _POSIX2_C_DEV); + printf("_POSIX2_CHAR_TERM: %ld\n", _POSIX2_CHAR_TERM); + printf("_POSIX2_FORT_DEV: %d\n", _POSIX2_FORT_DEV); + printf("_POSIX2_FORT_RUN: %d\n", _POSIX2_FORT_RUN); + printf("_POSIX2_LOCALEDEF: %ld\n", _POSIX2_LOCALEDEF); + printf("_POSIX2_SW_DEV: %ld\n", _POSIX2_SW_DEV); + printf("_POSIX2_UPE: %d\n", _POSIX2_UPE); + printf("_XOPEN_CRYPT: %d\n", _XOPEN_CRYPT); + printf("_XOPEN_ENH_I18N: %d\n", _XOPEN_ENH_I18N); + printf("_XOPEN_LEGACY: %d\n", _XOPEN_LEGACY); + printf("_XOPEN_REALTIME: %d\n", _XOPEN_REALTIME); + printf("_XOPEN_REALTIME_THREADS: %d\n", _XOPEN_REALTIME_THREADS); + printf("_XOPEN_SHM: %d\n", _XOPEN_SHM); + printf("_XBS5_ILP32_OFF32: %d\n", _XBS5_ILP32_OFF32); + printf("_XBS5_ILP32_OFFBIG: %d\n", _XBS5_ILP32_OFFBIG); + printf("_XBS5_LP64_OFF64: %d\n", _XBS5_LP64_OFF64); + printf("_XBS5_LPBIG_OFFBIG: %d\n", _XBS5_LPBIG_OFFBIG); + + printf("_POSIX_ASYNCHRONOUS_IO: %ld\n", _POSIX_ASYNCHRONOUS_IO); + printf("_POSIX_MEMLOCK: %ld\n", _POSIX_MEMLOCK); + printf("_POSIX_MEMLOCK_RANGE: %ld\n", _POSIX_MEMLOCK_RANGE); + printf("_POSIX_MESSAGE_PASSING: %ld\n", _POSIX_MESSAGE_PASSING); + printf("_POSIX_PRIORITY_SCHEDULING: %ld\n", _POSIX_PRIORITY_SCHEDULING); + printf("_POSIX_REALTIME_SIGNALS: %ld\n", _POSIX_REALTIME_SIGNALS); + printf("_POSIX_SEMAPHORES: %ld\n", _POSIX_SEMAPHORES); + printf("_POSIX_SHARED_MEMORY_OBJECTS: %ld\n", _POSIX_SHARED_MEMORY_OBJECTS); + printf("_POSIX_SYNCHRONIZED_IO: %ld\n", _POSIX_SYNCHRONIZED_IO); + printf("_POSIX_TIMERS: %ld\n", _POSIX_TIMERS); + + printf("_POSIX_FSYNC: %ld\n", _POSIX_FSYNC); + printf("_POSIX_MAPPED_FILES: %ld\n", _POSIX_MAPPED_FILES); + printf("_POSIX_MEMORY_PROTECTION: %ld\n", _POSIX_MEMORY_PROTECTION); + + printf("_POSIX_PRIORITIZED_IO: %ld\n", _POSIX_PRIORITIZED_IO); + + printf("_POSIX_THREAD_PRIORITY_SCHEDULING: %ld\n", _POSIX_THREAD_PRIORITY_SCHEDULING); + printf("_POSIX_THREAD_PRIO_INHERIT: %ld\n", _POSIX_THREAD_PRIO_INHERIT); + printf("_POSIX_THREAD_PRIO_PROTECT: %ld\n", _POSIX_THREAD_PRIO_PROTECT); + + printf("_POSIX_ASYNC_IO: %d\n", _POSIX_ASYNC_IO); + printf("_POSIX_PRIO_IO: %d\n", _POSIX_PRIO_IO); + printf("_POSIX_SYNC_IO: %d\n", _POSIX_SYNC_IO); + */ + + printf("NULL: %p\n", NULL); + + printf("R_OK: %d\n", R_OK); + printf("W_OK: %d\n", W_OK); + printf("X_OK: %d\n", X_OK); + printf("F_OK: %d\n", F_OK); + + /* TODO: confstr() constants: + printf("_CS_PATH: %d\n", _CS_PATH); + printf("_CS_XBS5_ILP32_OFF32_CFLAGS: %d\n", _CS_XBS5_ILP32_OFF32_CFLAGS); + printf("_CS_XBS5_ILP32_OFF32_LDFLAGS: %d\n", _CS_XBS5_ILP32_OFF32_LDFLAGS); + printf("_CS_XBS5_ILP32_OFF32_LIBS: %d\n", _CS_XBS5_ILP32_OFF32_LIBS); + printf("_CS_XBS5_ILP32_OFF32_LINTFLAGS: %d\n", _CS_XBS5_ILP32_OFF32_LINTFLAGS); + printf("_CS_XBS5_ILP32_OFFBIG_CFLAGS: %d\n", _CS_XBS5_ILP32_OFFBIG_CFLAGS); + printf("_CS_XBS5_ILP32_OFFBIG_LDFLAGS: %d\n", _CS_XBS5_ILP32_OFFBIG_LDFLAGS); + printf("_CS_XBS5_ILP32_OFFBIG_LIBS: %d\n", _CS_XBS5_ILP32_OFFBIG_LIBS); + printf("_CS_XBS5_ILP32_OFFBIG_LINTFLAGS: %d\n", _CS_XBS5_ILP32_OFFBIG_LINTFLAGS); + printf("_CS_XBS5_LP64_OFF64_CFLAGS: %d\n", _CS_XBS5_LP64_OFF64_CFLAGS); + printf("_CS_XBS5_LP64_OFF64_LDFLAGS: %d\n", _CS_XBS5_LP64_OFF64_LDFLAGS); + printf("_CS_XBS5_LP64_OFF64_LIBS: %d\n", _CS_XBS5_LP64_OFF64_LIBS); + printf("_CS_XBS5_LP64_OFF64_LINTFLAGS: %d\n", _CS_XBS5_LP64_OFF64_LINTFLAGS); + printf("_CS_XBS5_LPBIG_OFFBIG_CFLAGS: %d\n", _CS_XBS5_LPBIG_OFFBIG_CFLAGS); + printf("_CS_XBS5_LPBIG_OFFBIG_LDFLAGS: %d\n", _CS_XBS5_LPBIG_OFFBIG_LDFLAGS); + printf("_CS_XBS5_LPBIG_OFFBIG_LIBS: %d\n", _CS_XBS5_LPBIG_OFFBIG_LIBS); + printf("_CS_XBS5_LPBIG_OFFBIG_LINTFLAGS: %d\n", _CS_XBS5_LPBIG_OFFBIG_LINTFLAGS); + */ + + printf("SEEK_SET: %d\n", SEEK_SET); + printf("SEEK_CUR: %d\n", SEEK_CUR); + printf("SEEK_END: %d\n", SEEK_END); + + // sysconf() constants (_SC_*) are tested separately + + printf("F_LOCK: %d\n", F_LOCK); + printf("F_ULOCK: %d\n", F_ULOCK); + printf("F_TEST: %d\n", F_TEST); + printf("F_TLOCK: %d\n", F_TLOCK); + + // pathconf() constants (_PC_*) are tested separately + + printf("STDIN_FILENO: %d\n", STDIN_FILENO); + printf("STDOUT_FILENO: %d\n", STDOUT_FILENO); + printf("STDERR_FILENO: %d\n", STDERR_FILENO); + + return 0; +} diff --git a/tests/unistd/dup.c b/tests/unistd/dup.c index af1fb1f140..cb7449e577 100644 --- a/tests/unistd/dup.c +++ b/tests/unistd/dup.c @@ -27,7 +27,8 @@ int main(void) { ERROR_IF(close, c2, == -1); UNEXP_IF(close, c2, != 0); - int fd3 = open("dup.out", 0x0002, 0x1000); + int fd3 = open("dup.out", O_RDWR); + ERROR_IF(open, fd3, == -1); UNEXP_IF(open, fd3, < 0); @@ -35,7 +36,9 @@ int main(void) { ERROR_IF(dup2, fd4, == -1); UNEXP_IF(dup2, fd4, < 0); - printf("hello fd %d", fd3); + int p = printf("hello fd %d", fd3); + ERROR_IF(printf, p, == -1); + UNEXP_IF(printf, p, < 0); int c3 = close(fd3); ERROR_IF(close, c3, == -1); diff --git a/tests/unistd/fchdir.c b/tests/unistd/fchdir.c index 9279dbb934..d80db6ea25 100644 --- a/tests/unistd/fchdir.c +++ b/tests/unistd/fchdir.c @@ -6,7 +6,7 @@ #include "test_helpers.h" int main(void) { - int fd = open("..", O_DIRECTORY); + int fd = open("/usr", O_DIRECTORY); ERROR_IF(open, fd, == -1); UNEXP_IF(open, fd, < 0); diff --git a/tests/unistd/fork.c b/tests/unistd/fork.c index eb9b84ac2d..225ead36e2 100644 --- a/tests/unistd/fork.c +++ b/tests/unistd/fork.c @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/tests/unistd/getopt.c b/tests/unistd/getopt.c index ff41b517aa..2505788a25 100644 --- a/tests/unistd/getopt.c +++ b/tests/unistd/getopt.c @@ -60,7 +60,7 @@ int runner(int argc, char *argv[]) { return 0; } -int main(int argc, const char *argv[]) { +int main(void) { RUN("test", "-ao", "arg", "path", "path"); RUN("test", "-a", "-o", "arg", "path", "path"); RUN("test", "-o", "arg", "-a", "path", "path"); diff --git a/tests/unistd/getopt_long.c b/tests/unistd/getopt_long.c index 9af233cf50..92716068ce 100644 --- a/tests/unistd/getopt_long.c +++ b/tests/unistd/getopt_long.c @@ -31,7 +31,7 @@ void runner(int argc, char *argv[]) { }; int option_index = 0; - char c; + int c; while((c = getopt_long(argc, argv, ":a", long_options, &option_index)) != -1) { switch(c) { case 'a': @@ -55,7 +55,7 @@ void runner(int argc, char *argv[]) { } } -int main(int argc, const char *argv[]) { +int main(void) { RUN("test", "--test0", "-a"); RUN("test", "--test1", "-a"); RUN("test", "--test2", "-a"); diff --git a/tests/unistd/getpagesize.c b/tests/unistd/getpagesize.c index f26f7be21c..e7f2b43924 100644 --- a/tests/unistd/getpagesize.c +++ b/tests/unistd/getpagesize.c @@ -5,10 +5,7 @@ #include "test_helpers.h" int main(void) { - errno = 0; int getpagesize_result = getpagesize(); - int getpagesize_errno = errno; - - printf("getpagesize(): %d, errno: %d = %s\n", getpagesize_result, - getpagesize_errno, strerror(getpagesize_errno)); + + printf("Page size: %d\n", getpagesize_result); } diff --git a/tests/unistd/getpass.c b/tests/unistd/getpass.c new file mode 100644 index 0000000000..07c8e5c9a5 --- /dev/null +++ b/tests/unistd/getpass.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +// #include "test_helpers.h" + +int main(void) +{ + const char *pass = "pass"; + const char *prompt = "Enter password: "; + + char *result = getpass(prompt); + + if(strcmp(pass, result)) { + printf("incorrect password\n"); + exit(EXIT_FAILURE); + } + + const char *pass_127_chars = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + result = getpass(prompt); + + if(strcmp(pass_127_chars, result)) { + printf("incorrect password\n"); + exit(EXIT_FAILURE); + } + + const char *pass_empty = ""; + result = getpass(prompt); + + if(strcmp(pass_empty, result)) { + printf("incorrect password\n"); + exit(EXIT_FAILURE); + } + + printf("matching passwords\n", result); + + return 0; +} \ No newline at end of file diff --git a/tests/unistd/getpass.exp b/tests/unistd/getpass.exp new file mode 100644 index 0000000000..a707a04655 --- /dev/null +++ b/tests/unistd/getpass.exp @@ -0,0 +1,18 @@ +#!/usr/bin/expect + +set testgetpass [lindex $argv 0]; + +spawn $testgetpass +expect "Enter password: " +send -- "pass\r" + +expect "Enter password: " +send -- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r" + +expect "Enter password: " +send -- "\r" + +expect { + "incorrect password" { exit 123 } + eof +} \ No newline at end of file diff --git a/tests/unistd/pipe.c b/tests/unistd/pipe.c index 85e6db5abf..9f4b79a62e 100644 --- a/tests/unistd/pipe.c +++ b/tests/unistd/pipe.c @@ -26,11 +26,11 @@ int main(void) { UNEXP_IF(close, cr, != 0); // send 7 characters in the string, including end-of-string - int bytes = write(pip[1], outstring, strlen(outstring)); + ssize_t bytes = write(pip[1], outstring, strlen(outstring)); ERROR_IF(write, bytes, == -1); // check result - if (bytes != strlen(outstring)) { + if ((size_t)bytes != strlen(outstring)) { fprintf(stderr, "pipe write: %d != %ld\n", bytes, strlen(outstring)); exit(EXIT_FAILURE); } @@ -52,11 +52,11 @@ int main(void) { memset(instring, 0, sizeof(instring)); // read from the pipe - int bytes = read(pip[0], instring, sizeof(instring) - 1); + ssize_t bytes = read(pip[0], instring, sizeof(instring) - 1); ERROR_IF(read, bytes, == -1); // check result - if (bytes != strlen(outstring)) { + if ((size_t)bytes != strlen(outstring)) { fprintf(stderr, "pipe read: %d != %ld\n", bytes, strlen(outstring)); exit(EXIT_FAILURE); } else if (memcmp(instring, outstring, strlen(outstring)) != 0) { diff --git a/tests/unistd/readlinkat.c b/tests/unistd/readlinkat.c new file mode 100644 index 0000000000..9009543438 --- /dev/null +++ b/tests/unistd/readlinkat.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include + +__attribute__((nonnull)) +static int run_test( + int dir, + const char name[], + char buf[PATH_MAX], + const char expected[] +) { + if (readlinkat(dir, name, buf, PATH_MAX) == -1) { + perror("readlinkat"); + return -1; + } + if (strncmp(expected, buf, PATH_MAX) != 0) { + fprintf( + stderr, + "readlinkat mismatch\n\tExpected: %s\n\tActual: %s\n", + expected, + buf + ); + return -1; + } + + return 0; +} + +int main(void) { + int status = EXIT_FAILURE; + + char template[] = "/tmp/rlatest.XXXXXX"; + if (!mkdtemp(template)) { + perror("mkdtemp"); + goto bye; + } + int dir = open(template, O_DIRECTORY); + if (dir == -1) { + perror("open"); + goto rmtempdir; + } + + // Set up file and link. + size_t len = sizeof(template) - 1; + const char file_name[] = "miku"; + char file_path[PATH_MAX] = {0}; + memcpy(file_path, template, len); + file_path[len] = '/'; + memcpy(&file_path[len + 1], file_name, sizeof(file_name)); + int file = open(file_path, O_CREAT | O_WRONLY); + if (file == -1) { + perror("open"); + goto close_dir; + } + + // Writing a sentinel message to the target file is useful in case + // readlinkat reads the file instead of the link. If that happens, + // it will self-evident instead of printing an empty string. + // (n.b. this totally happened to me so I KNOW it's useful) + const char msg[] = "readlinkat read the file instead of the link...oops"; + const ssize_t msg_len = sizeof(msg); + assert(msg_len < PATH_MAX); + if (write(file, msg, msg_len) < msg_len) { + perror("write"); + goto rmfiles; + } + if (close(file) == -1) { + perror("close"); + goto rmfiles; + } + file = -1; + + const char link_name[] = "link"; + char link_path[PATH_MAX] = {0}; + memcpy(link_path, template, len); + link_path[len] = '/'; + memcpy(&link_path[len + 1], link_name, len); + if (symlink(file_path, link_path) == -1) { + perror("symlink"); + goto rmfiles; + } + + // Relative path + char buf[PATH_MAX] = {0}; + if (run_test(dir, link_name, buf, file_path) == -1) { + fputs("Context: Basic test (relative path)\n", stderr); + goto rmfiles; + } + + // Absolute path + memset(buf, 0, PATH_MAX); + if (run_test(dir, link_path, buf, file_path) == -1) { + fputs("Context: Absolute path\n", stderr); + goto rmfiles; + } + + // AT_FDCWD + memset(buf, 0, PATH_MAX); + char old_cwd[PATH_MAX] = {0}; + if (!getcwd(old_cwd, PATH_MAX)) { + perror("getcwd"); + goto rmfiles; + } + if (chdir(template) == -1) { + perror("chdir"); + goto rmfiles; + } + if (run_test(AT_FDCWD, link_name, buf, file_path) == -1) { + fputs("Context: AT_FDCWD\n", stderr); + goto rmfiles; + } + if (chdir(old_cwd) == -1) { + perror("chdir"); + goto rmfiles; + } + + // Not a dir + memset(buf, 0, PATH_MAX); + file = open(file_path, O_PATH); + if (file == -1) { + perror("open"); + goto rmfiles; + } + if (readlinkat(file, "", buf, PATH_MAX) != -1) { + fputs("Context: Using a file for dirfd should fail\n", stderr); + fprintf(stderr, "readlinkat wrote this into buf: %s\n", buf); + goto close_file; + } + + status = EXIT_SUCCESS; +close_file: + close(file); +rmfiles: + unlink(file_path); + unlink(link_path); +close_dir: + close(dir); +rmtempdir: + rmdir(template); +bye: + return status; +} diff --git a/tests/unistd/sleep.c b/tests/unistd/sleep.c index 2d7457e2d2..92b254e79f 100644 --- a/tests/unistd/sleep.c +++ b/tests/unistd/sleep.c @@ -1,15 +1,38 @@ #include #include #include +#include #include "test_helpers.h" -int main(void) { +int usleep(useconds_t); + +void handler(int _s) +{ + (void)_s; +} + +int main(void) +{ // sleep has no error codes and doesn't set errno - unsigned int unslept = sleep(2); + unsigned int unslept = sleep(1); printf("unslept: %u\n", unslept); - int us_status = usleep(1000); + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigaction(SIGALRM, &sa, NULL); + // TODO: This test is unreliable, overlapping use of alarm and sleep is not recommended. + // alarm(1); + + // unslept = sleep(10); + // if (unslept < 7) + // { + // printf("after alarm, unslept too short: %u\n", unslept); + // exit(EXIT_FAILURE); + // } + + int us_status = usleep(100000); ERROR_IF(usleep, us_status, == -1); UNEXP_IF(usleep, us_status, != 0); diff --git a/tests/unistd/swab.c b/tests/unistd/swab.c index d7927b66fa..dd4603913a 100644 --- a/tests/unistd/swab.c +++ b/tests/unistd/swab.c @@ -1,23 +1,24 @@ #include #include -int main(int argc, char ** argv) { - +int main(void) { const char source[] = {0, 1, 2, 3, 4, 5, 6}; char destination[] = {0, 0, 0, 0, 0, 0}; const char flipped_source[] = {1, 0, 3, 2, 5, 4}; const char first_two_source_bytes_flipped[] = {1, 0}; +#ifndef __GLIBC__ swab(source, destination, /* nbytes */ -3); - for (int i = 0; i < sizeof(destination); ++i) { + for (size_t i = 0; i < sizeof(destination); ++i) { if (destination[i] != 0) { puts("If nbytes is negative destionation shouldn't be modified"); return 1; } } +#endif swab(source, destination, /* nbytes */ 0); - for (int i = 0; i < sizeof(destination); ++i) { + for (size_t i = 0; i < sizeof(destination); ++i) { if (destination[i] != 0) { puts("If nbytes is zero destionation shouldn't be modified"); return 1; @@ -25,7 +26,7 @@ int main(int argc, char ** argv) { } swab(source, destination, /* nbytes */ 3); - for (int i = 0; i < sizeof(first_two_source_bytes_flipped); ++i) { + for (size_t i = 0; i < sizeof(first_two_source_bytes_flipped); ++i) { if (destination[i] != first_two_source_bytes_flipped[i]) { puts("copied bytes don't match expected values"); return 1; @@ -33,7 +34,7 @@ int main(int argc, char ** argv) { } // If nbytes is not even it's not specified what should happen to the // last byte so the third byte in destination is not checked. - for (int i = sizeof(first_two_source_bytes_flipped) + 1; i < sizeof(destination); ++i) { + for (size_t i = sizeof(first_two_source_bytes_flipped) + 1; i < sizeof(destination); ++i) { if (destination[i] != 0) { puts("swab modified too many bytes in destination"); return 1; @@ -41,7 +42,7 @@ int main(int argc, char ** argv) { } swab(source, destination, /* nbytes */ sizeof(destination)); - for (int i = 0; i < sizeof(destination); ++i) { + for (size_t i = 0; i < sizeof(destination); ++i) { if (destination[i] != flipped_source[i]) { puts("copied bytes don't match expected values"); return 1; diff --git a/tests/unistd/sysconf.c b/tests/unistd/sysconf.c index ecb20a53aa..05b1ed1b66 100644 --- a/tests/unistd/sysconf.c +++ b/tests/unistd/sysconf.c @@ -25,4 +25,8 @@ int main(void) { SC(TTY_NAME_MAX); SC(SYMLOOP_MAX); SC(HOST_NAME_MAX); + SC(NPROCESSORS_CONF); + SC(NPROCESSORS_ONLN); + SC(PHYS_PAGES); + SC(AVPHYS_PAGES); } diff --git a/tests/verify.sh b/tests/verify.sh deleted file mode 100755 index 26ebff0495..0000000000 --- a/tests/verify.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -rm -rf gen || exit 1 -mkdir -p gen || exit 1 - -summary="" - -while [ "$#" -gt 0 ] -do - bin="$1" - shift - - echo "# ${bin} #" - mkdir -p "gen/$(dirname ${bin})" || exit 1 - - "${bin}" test args > "gen/${bin}.stdout" 2> "gen/${bin}.stderr" - retcode="$?" - status="" - - for output in stdout stderr - do - gen="$(sha256sum "gen/${bin}.${output}" | cut -d " " -f 1)" - - # look for expected output file that is specific to binary type (either static or dynamic) - expected_file="expected/${bin}.${output}" - if [ ! -e $expected_file ] - then - # if unable to find above, the expected output file is the same to both static and dynamic binary - name=$(echo $bin | cut -d "/" -f2-) - expected_file="expected/${name}.${output}" - fi - expected="$(sha256sum "${expected_file}" | cut -d " " -f 1)" - if [ "${gen}" != "${expected}" ] - then - echo "# ${bin}: ${output}: expected #" - cat "${expected_file}" - - echo "# ${bin}: ${output}: generated #" - cat "gen/${bin}.${output}" - - # FIXME: Make diff available on Redox - if [ $(uname) != "Redox" ] - then - echo "# ${bin}: ${output}: diff #" - diff --color -u "${expected_file}" "gen/${bin}.${output}" - fi - - status="${bin} failed - retcode ${retcode}, ${output} mismatch" - summary="${summary}${status}\n" - fi - done - - if [ -n "${status}" ] - then - echo "# ${status} #" - fi -done - -if [ -n "$summary" ] -then - echo -e "$summary" - exit 1 -fi diff --git a/tests/waitid.c b/tests/waitid.c new file mode 100644 index 0000000000..5dc7063ccd --- /dev/null +++ b/tests/waitid.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +static void wait_until_child_exits(pid_t pid) { + siginfo_t info; + for (int i = 0; i < 50; ++i) { + info.si_pid = 0; + int ret = waitid(P_PID, pid, &info, WEXITED | WNOHANG | WNOWAIT); + ERROR_IF(waitid, ret, == -1); + if (info.si_pid == pid) { + assert(info.si_code == CLD_EXITED); + assert(info.si_status == 42); + return; + } + usleep(10000); + } + assert(!"waitid never observed child exit"); +} + +int main(void) { + pid_t pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + usleep(50000); + _Exit(42); + } + + siginfo_t info; + info.si_pid = 0; + int ret = waitid(P_PID, pid, &info, WEXITED | WNOHANG | WNOWAIT); + ERROR_IF(waitid, ret, == -1); + assert(info.si_pid == 0); + + wait_until_child_exits(pid); + + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + ERROR_IF(waitpid, waited, == -1); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 42); + + return EXIT_SUCCESS; +} diff --git a/tests/waitpid.c b/tests/waitpid.c index ec4c6dedc6..4d89c078ca 100644 --- a/tests/waitpid.c +++ b/tests/waitpid.c @@ -1,21 +1,38 @@ +#include #include #include #include #include "test_helpers.h" -int main(void) { +void for_code(int code) { pid_t pid = fork(); ERROR_IF(fork, pid, == -1); + // Testing successful exit if (pid == 0) { // child - sleep(1); - exit(EXIT_SUCCESS); - } else { - // parent - int stat_loc; - pid_t wid = waitpid(pid, &stat_loc, 0); - ERROR_IF(waitpid, wid, == -1); + usleep(100000); + _Exit(code); } + printf("Testing waitpid of child %d for code %d\n", pid, code); + // parent + int status; + pid_t wid = waitpid(pid, &status, 0); + ERROR_IF(waitpid, wid, == -1); + + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == code); + + puts("Success"); +} + +int main(void) { + for_code(EXIT_SUCCESS); + for_code(EXIT_FAILURE); + for_code(42); + for_code(255); + // TODO: Also add coverage for e.g. WIFSTOPPED, WSTOPSIG, WTERMSIG, etc + + return EXIT_SUCCESS; } diff --git a/tests/waitpid_multiple.c b/tests/waitpid_multiple.c new file mode 100644 index 0000000000..f787c84e04 --- /dev/null +++ b/tests/waitpid_multiple.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +int main(void) { + // Spawn three childrens, one with same pgid and two with pgid set to its own + // pid, so the exit order is two different pgid, then followed with same pgid + + pid_t pid_samepgid = fork(); + ERROR_IF(fork, pid_samepgid, == -1); + + if (pid_samepgid == 0) { + // child + usleep(300000); + _Exit(2); + } + pid_t pid_diffpgids[2]; + for (int i = 0; i < 2; i++) { + pid_diffpgids[i] = fork(); + ERROR_IF(fork, pid_diffpgids[i], == -1); + + if (pid_diffpgids[i] == 0) { + int ret = setpgid(0, 0); + ERROR_IF(setpgid, ret, == -1); + + // child + usleep((i + 1) * 100000); + _Exit(i); + } + } + int status; + pid_t wid; + + // First, check that the first different-pgid proc is recognized. + wid = waitpid(-1, &status, 0); + ERROR_IF(waitpid, wid, == -1); + assert(wid == pid_diffpgids[0]); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 0); + + // Then, check that the longest-waiting proc with the same pgid is properly matched. + wid = waitpid(0, &status, 0); + ERROR_IF(waitpid, wid, == -1); + assert(wid == pid_samepgid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 2); + + // Finally, the last different-pgid must have completed. + wid = waitpid(-1, &status, WNOHANG); + ERROR_IF(waitpid, wid, == -1); + assert(wid == pid_diffpgids[1]); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 1); +} diff --git a/tests/wchar/fgetwc.c b/tests/wchar/fgetwc.c new file mode 100644 index 0000000000..1aeeff16ff --- /dev/null +++ b/tests/wchar/fgetwc.c @@ -0,0 +1,19 @@ +#include +#include +#include + +int main(void) +{ + setlocale(LC_ALL, ""); + wint_t wc; + FILE *fp = fopen("wchar/fgetwc.in", "r"); + if (!fp) { + return 1; + } + + while (WEOF != (wc = fgetwc(fp))) + printf("%lc", wc); + + fclose(fp); + return 0; +} diff --git a/tests/wchar/fgetwc.in b/tests/wchar/fgetwc.in new file mode 100644 index 0000000000..e7cabf537e --- /dev/null +++ b/tests/wchar/fgetwc.in @@ -0,0 +1,3 @@ +êçã +こんにちは世界 +Привет мир \ No newline at end of file diff --git a/tests/wchar/fwide.c b/tests/wchar/fwide.c index 6511e56778..12eceb75a0 100644 --- a/tests/wchar/fwide.c +++ b/tests/wchar/fwide.c @@ -53,7 +53,7 @@ int main() { &test_manual_wchar_orientation, &test_orientation_after_fprintf, }; - for(int i=0; i +#include +#include +#include +#include +#include + +int main() +{ + setlocale(LC_ALL, ""); + FILE *stream; + wint_t wc; + wint_t wc2; + + if (NULL == (stream = fopen("wchar/ungetwc.in", "r+"))) + return 1; + + while (WEOF != (wc = fgetwc(stream)) && iswdigit(wc)) {} + + if (WEOF != wc) + ungetwc(wc, stream); + + wc2 = fgetwc(stream); + assert(WEOF != wc2); + assert(wc == wc2); + + return 0; +} \ No newline at end of file diff --git a/tests/wchar/ungetwc.in b/tests/wchar/ungetwc.in new file mode 100644 index 0000000000..a86807323c --- /dev/null +++ b/tests/wchar/ungetwc.in @@ -0,0 +1 @@ +123çA \ No newline at end of file diff --git a/tests/wchar/wcpcpy.c b/tests/wchar/wcpcpy.c new file mode 100644 index 0000000000..c65421220d --- /dev/null +++ b/tests/wchar/wcpcpy.c @@ -0,0 +1,16 @@ +#include +#include +#include + +int main() { + wchar_t src[] = L"こんにちは世界!"; + wchar_t dest[9]; + + wchar_t* result = wcpcpy(dest, src); + + assert(wcscmp(dest, src) == 0); + assert(*result == L'\0'); + assert(result == dest + wcslen(src)); + + return 0; +} \ No newline at end of file diff --git a/tests/wchar/wcpncpy.c b/tests/wchar/wcpncpy.c new file mode 100644 index 0000000000..62c0bdb4e2 --- /dev/null +++ b/tests/wchar/wcpncpy.c @@ -0,0 +1,37 @@ +#include +#include +#include + +int main() { + wchar_t src[] = L"Привет мир"; + + size_t n_input = 10; + size_t n_short = 6; + size_t n_long = 15; + + // Initialize with sentinel values to detect exactly how much is overwritten + wchar_t dest_short[] = L"\x12\x34\x56\x78\x90\x12\x34\x56\x78\x90\x12\x34\x56\x78"; + wchar_t dest_long[] = L"\x12\x34\x56\x78\x90\x12\x34\x56\x78\x90\x12\x34\x56\x78"; + + wchar_t expected_short[] = L"Привет\x34\x56\x78\x90\x12\x34\x56\x78"; + + // The "short" test should copy exactly n_short characters without terminating null + wchar_t* result_short = wcpncpy(dest_short, src, n_short); + assert(wcsncmp(dest_short, src, n_short) == 0); + assert(result_short == dest_short + n_short); + for (size_t i = n_short; i < n_long; i++) { + // Check that the sentinel characters have not been overwritten + assert(dest_short[i] == expected_short[i]); + } + + // The "long" test should write the input string, with nulls appended up to n_long + wchar_t* result_long = wcpncpy(dest_long, src, n_long); + assert(wcsncmp(dest_long, src, n_long) == 0); + assert(result_long == dest_long + n_input); + for (size_t i = n_input; i < n_long; i++) { + // Check that nulls are written up to n_long + assert(dest_long[i] == L'\0'); + } + + return 0; +} diff --git a/tests/wchar/wcschr.c b/tests/wchar/wcschr.c new file mode 100644 index 0000000000..2b396d4763 --- /dev/null +++ b/tests/wchar/wcschr.c @@ -0,0 +1,13 @@ +#include +#include + +int main(void) { + wchar_t *haystack = L"Hello World!"; + + assert(wcschr(haystack, L'H') == haystack); + assert(wcschr(haystack, L'W') == &haystack[6]); + assert(wcschr(haystack, L'\0') == &haystack[12]); // the terminating nul is considered part of the string + assert(wcschr(haystack, L'X') == NULL); + + return 0; +} diff --git a/tests/wchar/wcsdup.c b/tests/wchar/wcsdup.c new file mode 100644 index 0000000000..27d4a2c580 --- /dev/null +++ b/tests/wchar/wcsdup.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +int main() { + wchar_t src[] = L"こんにちは世界Привет мир"; + + wchar_t* dup = wcsdup(src); + + assert(wcscmp(dup, src) == 0); + assert(dup != src); + free(dup); + + return 0; +} diff --git a/tests/wchar/wcsnlen.c b/tests/wchar/wcsnlen.c new file mode 100644 index 0000000000..ff1c705fbf --- /dev/null +++ b/tests/wchar/wcsnlen.c @@ -0,0 +1,9 @@ +#include +#include + +int main() { + // size is 17 + wchar_t* str1 = L"こんにちは世界Привет мир"; + assert(wcsnlen(str1, 10) == 10); + assert(wcsnlen(str1, 100) == 17); +} \ No newline at end of file diff --git a/tests/wchar/wcsnrtombs.c b/tests/wchar/wcsnrtombs.c new file mode 100644 index 0000000000..eb2a859267 --- /dev/null +++ b/tests/wchar/wcsnrtombs.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +int main() { + setlocale(LC_ALL, ""); + + const wchar_t *src = L"こんにちは世界Привет мир"; + char dst[20] = {0}; + mbstate_t ps; + + size_t result = wcsnrtombs(dst, &src, 4, sizeof(dst), &ps); + assert(result == 12); + assert(strcmp(dst, "こんにち") == 0); + + return 0; +} diff --git a/tests/wchar/wcsrtombs.c b/tests/wchar/wcsrtombs.c new file mode 100644 index 0000000000..087a8862ae --- /dev/null +++ b/tests/wchar/wcsrtombs.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +int main() { + const wchar_t *wcs1 = L""; + size_t mb_len1 = wcsrtombs(NULL, &wcs1, 0, NULL); + assert(mb_len1 == 0); + + const wchar_t *wcs2 = L"Hello, world!"; + size_t mb_len2 = wcsrtombs(NULL, &wcs2, 0, NULL); + assert(mb_len2 == strlen("Hello, world!")); + + // wcsrtombs resets wcs2 to zero, so we need to fill it again + wcs2 = L"Hello, world!"; + char *mbs2 = malloc(mb_len2 + 1); + wcsrtombs(mbs2, &wcs2, mb_len2 + 1, NULL); + assert(strcmp(mbs2, "Hello, world!") == 0); + free(mbs2); + + return 0; +} \ No newline at end of file diff --git a/tests/wchar/wcstod.c b/tests/wchar/wcstod.c index c51d51823c..dd71e42191 100644 --- a/tests/wchar/wcstod.c +++ b/tests/wchar/wcstod.c @@ -1,5 +1,9 @@ #include #include +// TODO: remove glibc? +#ifdef __GLIBC__ +#include +#endif void attempt(wchar_t *s) { wchar_t *end; diff --git a/tests/wchar/wcstoimax.c b/tests/wchar/wcstoimax.c new file mode 100644 index 0000000000..546314d063 --- /dev/null +++ b/tests/wchar/wcstoimax.c @@ -0,0 +1,13 @@ +#include +#include +#include + +int main(void) { + wchar_t* endptr; + wprintf(L"%ld\n", wcstoimax(L" -123junk", &endptr, 10)); /* base 10 */ + wprintf(L"%ld\n", wcstoimax(L"11111111", &endptr, 2)); /* base 2 */ + wprintf(L"%ld\n", wcstoimax(L"XyZ", &endptr, 36)); /* base 36 */ + wprintf(L"%ld\n", wcstoimax(L"010", &endptr, 0)); /* octal auto-detection */ + wprintf(L"%ld\n", wcstoimax(L"10", &endptr, 0)); /* decimal auto-detection */ + wprintf(L"%ld\n", wcstoimax(L"0x10", &endptr, 0)); /* hexadecimal auto-detection */ +} \ No newline at end of file diff --git a/tests/wchar/wcstoumax.c b/tests/wchar/wcstoumax.c new file mode 100644 index 0000000000..8f4c80b8aa --- /dev/null +++ b/tests/wchar/wcstoumax.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int main(void) { + wchar_t *nptr; + wchar_t *endptr; + uintmax_t j; + int base = 10; + nptr = L"10110134932"; + printf("nptr = `%ls`\n", nptr); + j = wcstoumax(nptr, &endptr, base); + printf("wcstoumax = %ju\n", j); + printf("Stopped scan at `%ls`\n", endptr); +} \ No newline at end of file diff --git a/tests/wchar/wcswidth.c b/tests/wchar/wcswidth.c new file mode 100644 index 0000000000..79aeb921cb --- /dev/null +++ b/tests/wchar/wcswidth.c @@ -0,0 +1,10 @@ +#include +#include + +int main () { + wchar_t *wcs = L"relibc"; + size_t len = wcslen(wcs); + int width = wcswidth(wcs, len); + printf("wcswidth(L\"%ls\", %d) = %d\n", wcs, len, width); + return 0; +} diff --git a/tests/wchar/wprintf.c b/tests/wchar/wprintf.c new file mode 100644 index 0000000000..c635fff661 --- /dev/null +++ b/tests/wchar/wprintf.c @@ -0,0 +1,93 @@ +#include +#include // free() +#include +#include // INFINITY, NAN constants + +int main(void) { + int sofar = 0; + int len = wprintf( + L"percent: %%\nwstring: %ls\nchar: %lc\nint: %d\n%nuint: %u\nhex: %x\nHEX: %X\nstring: %s\n", + L"String", + L'c', + -16, + &sofar, + 32, + 0xbeef, + 0xC0FFEE, + "end" + ); + wprintf(L"%%n returned %d, total len of write: %d\n", sofar, len); + + wprintf(L"\nPadding madness:\n"); + wprintf(L"% -5.3d %+3d\n", 1, 2); + wprintf(L"%4c\n", 'c'); + wprintf(L"%#10.7x\n", 0xFF); + wprintf(L"%#4.3o\n", 01); + wprintf(L"%#x %#x\n", 0, 1); + wprintf(L"%.0d %.0d\n", 0, 1); + wprintf(L"(%05d) (%5d)\n", 123, 123); + wprintf(L"(%05d) (%5d)\n", -123, -123); + wprintf(L"(%13.0d)\n", 0); + wprintf(L"%p\n", (void*) 0xABCDEF); + wprintf(L"%p\n", (void*) 0); + + wprintf(L"\nPositional madness:\n"); + wprintf(L"%3$d %2$d %1$d\n", 2, 3, 4); + wprintf(L"%.*3$d\n", 2, 0, 5); + wprintf(L"|%-*6$.*5$s|%-*6$.*5$s|%*6$.*5$s|%*6$.*5$s|\n", "Fizz", "Buzz", "FizzBuzz", "TotalBuzz", 3, 8); + wprintf(L"int: %*6$d double: %lf %lf %lf %lf\n", 5, 0.1, 0.2, 0.3, 0.4, 10); + wprintf(L"%1$d %1$lf\n", 5, 0.1); + wprintf(L"%1$d %lf\n", 5, 0.2); + //wprintf(L"int: %*6$d no info on middle types\n", 5, 0.1, 0.2, 0.3, 0.4, 10); + + wprintf(L"\nFloat madness:\n"); + wprintf(L"%20e\n", 123.456789123); + wprintf(L"%20E\n", 0.00001); + wprintf(L"%20f\n", 123.456789123); + wprintf(L"%20F\n", 0.00001); + wprintf(L"%20e\n", -123.456789123); + wprintf(L"%020e\n", -123.456789123); + wprintf(L"%%.5g: %.5g\n", -123.456789123); + wprintf(L"%%.5f: %.5f\n", -123.456789123); + wprintf(L"%%.5e: %.5e\n", -123.456789123); + wprintf(L"%%.*g: %.*g\n", 2, -123.456789123); + wprintf(L"%%.*f: %.*f\n", 2, -123.456789123); + wprintf(L"%%.*e: %.*e\n", 2, -123.456789123); + wprintf(L"%%.*2$g: %.*2$g\n", -123.456789123, 5); + wprintf(L"%%.*2$f: %.*2$f\n", -123.456789123, 5); + wprintf(L"%%.*2$e: %.*2$e\n", -123.456789123, 5); + + wprintf(L"%g\n", 100000.0); + wprintf(L"%g\n", 1000000.0); + wprintf(L"%e\n", 1000000.0); + wprintf(L"%G\n", 0.0001); + wprintf(L"%G\n", 0.00001); + wprintf(L"%E\n", 0.00001); + + double nonfinites[] = {INFINITY, -INFINITY, NAN, -NAN}; + wchar_t *float_formats[] = {L"%e", L"%E", L"%f", L"%F", L"%g", L"%G"}; + wprintf(L"\nNon-finite float madness:\n"); + for (size_t i = 0; i < sizeof(float_formats)/sizeof(char *); i++) { + wprintf(L"%ls:", float_formats[i]); + for (size_t j = 0; j < sizeof(nonfinites)/sizeof(double); j++) { + wprintf(L" "); + wprintf(float_formats[i], nonfinites[j]); + } + wprintf(L"\n"); + } + + wprintf(L"Things that have been buggy\n"); + wprintf(L"%s%0*lu\n", "+", 2, 5l); // this format string was found in GDB + + wprintf(L"Testing asprintf...\n"); + char *s = NULL; + int res = asprintf(&s, "test string"); + wprintf(L"printed: %s, value: %d\n", s, res); + free(s); + res = asprintf(&s, "test string %d", 2); + wprintf(L"printed: %s, value: %d\n", s, res); + free(s); + res = asprintf(&s, "test %s %d", "string", 2); + wprintf(L"printed: %s, value: %d\n", s, res); + free(s); +} diff --git a/tests/wchar/wscanf.c b/tests/wchar/wscanf.c new file mode 100644 index 0000000000..01962484f0 --- /dev/null +++ b/tests/wchar/wscanf.c @@ -0,0 +1,102 @@ +/* swscanf example */ +#include +#include +// TODO: remove glibc? +#ifdef __GLIBC__ +#include +#endif + +#include "test_helpers.h" + +struct params { + short sa; + int ia; + int ib; + int ic; + float fa; + double da; + int *ptr; + char c; + wchar_t wc; + char string1[20]; + char string2[20]; + char string3[20]; + char string4[20]; + wchar_t wstring1[20]; + wchar_t wstring2[20]; + wchar_t wstring3[20]; + wchar_t wstring4[20]; +}; + + +void test(wchar_t* fmt_in, wchar_t* input, struct params *p, ...) { + va_list args; + va_start(args, p); + wint_t ret = vswscanf(input, fmt_in, args); + va_end(args); + + wprintf( + L"%d, { sa: %hhd, ia: %d, ib: %d, ic: %d, fa: %f, da: %lf, ptr: %p, char: %c, wide char: %lc, string1: %s, string2: %s, string3: %s, string4: %s, wstring1: %ls, wstring2: %ls, wstring3: %ls, wstring4: %ls }\n", + ret, p->sa, p->ia, p->ib, p->ic, p->fa, p->da, p->ptr, p->c, p->wc, p->string1, p->string2, p->string3, p->string4, p->wstring1, p->wstring2, p->wstring3, p->wstring4 + ); +} + +int main () +{ + struct params p = { .c = 'a' }; + + test(L"%hd %d", L"12 345", &p, &p.sa, &p.ia); + test(L"%x %i %i", L"12 0x345 010", &p, &p.ia, &p.ib, &p.ic); + test(L"%f.%lf", L"0.1.0.2", &p, &p.fa, &p.da); + test(L"%p", L"0xABCDEF", &p, &p.ptr); + test(L"%s", L"Hello World", &p, &p.string1); + test(L"%3i", L"0xFF", &p, &p.ia); + test(L"%c%3c", L"hello", &p, &p.c, &p.string1); + test(L"%lc", L"β", &p, &p.wc); + test(L"%lc %f", L"π 3.14", &p, &p.wc, &p.fa); + test(L"test: %2i%n", L"test: 0xFF", &p, &p.ia, &p.ib); + test(L"hello world%%", L"hello world%", &p); + test(L"hello %5ls %d", L"hello world 42", &p, &p.wstring1, &p.ia); + test(L"bye %ls %d", L"bye planet 84", &p, &p.wstring2, &p.ia); + test(L"h%1[ae]ll%1[^a] wor%1[^\n]%[d]", L"hello world", &p, &p.string1, &p.string2, &p.string3, &p.string4); + test(L"h%1[ae]ll%1[^a] wor%1[^\n]%[d]", L"halle worfdddddd", &p, &p.string1, &p.string2, &p.string3, &p.string4); + test(L"%[^a]%[b]", L"testbbbb", &p, &p.string1, &p.string2); + test(L"%ls %ls", L"Привет мир", &p, &p.wstring1, &p.wstring2); + test(L"%ls %ls", L"こんにちは 世界", &p, &p.wstring1, &p.wstring2); + test(L"%ls %d %ls %d", L"αβγ 123 δεζ 456", &p, &p.wstring1, &p.ia, &p.wstring2, &p.ib); + test(L"%ls %s %ls %s", L"αβγ test1 δεζ test2", &p, &p.wstring1, &p.string1, &p.wstring2, &p.string2); + test(L"%ls %ls %ls %ls", L"z ß 水 🍌", &p, &p.wstring1, &p.wstring2, &p.wstring3, &p.wstring4); + + // Scanf stolen from the url parsing in curl + wchar_t protobuf[16]; + wchar_t slashbuf[4]; + wchar_t hostbuf[100]; + wchar_t pathbuf[100]; + + // don't push NUL, make sure scanf does that + memset(protobuf, 97, 16); + memset(slashbuf, 97, 4); + memset(hostbuf, 97, 100); + memset(pathbuf, 97, 100); + + int ret = swscanf( + L"https://redox-os.org\0# extra garbage for nul test", L"%15[^\n/:]:%3[/]%[^\n/?#]%[^\n]", + &protobuf, &slashbuf, &hostbuf, &pathbuf + ); + if (ret < 4) { + *pathbuf = 0; + } + if (ret < 3) { + *hostbuf = 0; + } + if (ret < 2) { + *slashbuf = 0; + } + if (ret < 1) { + *protobuf = 0; + } + + wprintf(L"%d \"%s\" \"%s\" \"%s\" \"%s\"\n", ret, &protobuf, &slashbuf, &hostbuf, &pathbuf); + + return 0; +} diff --git a/tests/wctype/towlower.c b/tests/wctype/towlower.c index a99d4252bd..93767c82c9 100644 --- a/tests/wctype/towlower.c +++ b/tests/wctype/towlower.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,8 +6,8 @@ int main() { wchar_t *str = L"HaLf WiDe ChAr StRiNg!\n"; fputws(str, stdout); - for (int i = 0; i < wcslen(str); i++) { - putwchar(towlower(str[i])); + for (size_t i = 0; i < wcslen(str); i++) { + putwchar(towctrans(str[i], wctrans("tolower"))); } return 0; -} \ No newline at end of file +} diff --git a/tests/wctype/towupper.c b/tests/wctype/towupper.c index 61fb09edf4..a9bb4fbfe5 100644 --- a/tests/wctype/towupper.c +++ b/tests/wctype/towupper.c @@ -1,12 +1,13 @@ #include +#include #include #include int main() { wchar_t *str = L"HaLf WiDe ChAr StRiNg!\n"; fputws(str, stdout); - for (int i = 0; i < wcslen(str); i++) { - putwchar(towupper(str[i])); + for (size_t i = 0; i < wcslen(str); i++) { + putwchar(towctrans(str[i], wctrans("toupper"))); } return 0; -} \ No newline at end of file +}