├── .github └── workflows │ └── main.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── build.rs ├── c-tests └── src │ ├── basic.c │ ├── hello.c │ └── more.c ├── ci ├── aarch64-o-largefile.patch ├── getsockopt-timeouts.patch ├── more-sockopts.patch ├── s390x-stat-have-nsec.patch ├── tcgets2-tcsets2.patch └── translate-errno.patch ├── rust-toolchain.toml ├── src └── lib.rs └── tests └── c-tests.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ${{ matrix.os }} 13 | env: 14 | QEMU_BUILD_VERSION: 8.1.0 15 | strategy: 16 | matrix: 17 | # TODO: i686-linux, aarch64-linux, riscv64-linux. The tests/c-tests.rs 18 | # script needs several changes to be cross-compilation aware. 19 | build: [ubuntu] 20 | include: 21 | - build: ubuntu 22 | os: ubuntu-latest 23 | host_target: x86_64-unknown-linux-gnu 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Configure Cargo target 27 | run: | 28 | echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV 29 | rustup target add ${{ matrix.target }} 30 | if: matrix.target != '' 31 | 32 | - uses: actions/cache@v3 33 | with: 34 | path: ${{ runner.tool_cache }}/qemu 35 | key: qemu-${{ matrix.target }}-${{ env.QEMU_BUILD_VERSION }}-patched 36 | if: matrix.target != '' && matrix.os == 'ubuntu-latest' 37 | 38 | - name: Install cross-compilation tools 39 | run: | 40 | set -ex 41 | sudo apt-get update 42 | sudo apt-get install -y ${{ matrix.gcc_package }} ninja-build 43 | upcase=$(echo ${{ matrix.host_target }} | awk '{ print toupper($0) }' | sed 's/-/_/g') 44 | echo CARGO_TARGET_${upcase}_LINKER=${{ matrix.gcc }} >> $GITHUB_ENV 45 | echo CC_${{ matrix.target }}=${{ matrix.gcc }} >> $GITHUB_ENV 46 | if: matrix.gcc_package != '' && matrix.os == 'ubuntu-latest' 47 | 48 | - name: Install cross-compilation libraries 49 | run: | 50 | set -ex 51 | sudo apt-get update 52 | sudo apt-get install -y ${{ matrix.libc_package }} 53 | if: matrix.libc_package != '' && matrix.os == 'ubuntu-latest' 54 | 55 | - name: Install qemu 56 | run: | 57 | set -ex 58 | 59 | # Configure Cargo for cross compilation and tell it how it can run 60 | # cross executables 61 | upcase=$(echo ${{ matrix.host_target }} | awk '{ print toupper($0) }' | sed 's/-/_/g') 62 | echo CARGO_TARGET_${upcase}_RUNNER=${{ runner.tool_cache }}/qemu/bin/${{ matrix.qemu }} >> $GITHUB_ENV 63 | 64 | # See if qemu is already in the cache 65 | if [ -f ${{ runner.tool_cache }}/qemu/bin/${{ matrix.qemu }} ]; then 66 | exit 0 67 | fi 68 | 69 | # Download and build qemu from source since the most recent release is 70 | # way faster at arm emulation than the current version github actions' 71 | # ubuntu image uses. Disable as much as we can to get it to build 72 | # quickly. 73 | cd 74 | curl https://download.qemu.org/qemu-$QEMU_BUILD_VERSION.tar.xz | tar xJf - 75 | cd qemu-$QEMU_BUILD_VERSION 76 | patch -p1 < $GITHUB_WORKSPACE/ci/translate-errno.patch 77 | patch -p1 < $GITHUB_WORKSPACE/ci/getsockopt-timeouts.patch 78 | patch -p1 < $GITHUB_WORKSPACE/ci/s390x-stat-have-nsec.patch 79 | patch -p1 < $GITHUB_WORKSPACE/ci/aarch64-o-largefile.patch 80 | patch -p1 < $GITHUB_WORKSPACE/ci/tcgets2-tcsets2.patch 81 | ./configure --target-list=${{ matrix.qemu_target }} --prefix=${{ runner.tool_cache }}/qemu --disable-tools --disable-slirp --disable-fdt --disable-capstone --disable-docs 82 | ninja -C build install 83 | if: matrix.qemu != '' && matrix.os == 'ubuntu-latest' 84 | 85 | - name: cargo test 86 | run: cargo test 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eyra-c" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # Build eyra as a libc.a 8 | [lib] 9 | name = "c" 10 | crate-type = ["staticlib"] 11 | 12 | [dependencies] 13 | std = { version = "0.19.0", package = "eyra", features = ["todo", "deprecated-and-unimplemented", "extra-syscalls"] } 14 | 15 | [dev-dependencies] 16 | cc = "1.0" 17 | target-lexicon = "0.12.12" 18 | pretty_assertions = "1.4.0" 19 | 20 | [build-dependencies] 21 | camino = "1.1.6" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eyra-c 2 | 3 | Support for compiling C programs with Eyra. 4 | 5 | Specifically, this repo compiles [Eyra] into a [staticlib] to make a libc.a: 6 | 7 | ```sh 8 | $ cargo build 9 | Compiling eyra-c v0.0.0 (/home/eyra/ecosystem/eyra-c) 10 | Finished dev [unoptimized + debuginfo] target(s) in 0.00s 11 | $ ls target/debug/libc.a 12 | target/debug/libc.a 13 | $ 14 | ``` 15 | 16 | This libc.a can be used by C compilers, with -nostdlib to disable the 17 | system libraries: 18 | 19 | ```sh 20 | $ cat c-tests/src/hello.c 21 | #include 22 | 23 | int main(void) { 24 | printf("Hello, world!\n"); 25 | return 0; 26 | } 27 | $ cc c-tests/src/hello.c -nostdlib target/debug/libc.a 28 | $ ./a.out 29 | Hello, world! 30 | $ 31 | ``` 32 | 33 | 💃 34 | 35 | Amusingly, even though this libc.a is built entirely from Rust, it cannot be 36 | used by Rust programs, because a staticlib library is meant to be linked into 37 | a C program, so it includes the Rust standard library. If linked into a Rust 38 | program, it would conflict with the Rust standard library. 39 | 40 | To use Eyra in a Rust program, [use Eyra as a dependency]. 41 | 42 | [Eyra]: https://github.com/sunfishcode/eyra 43 | [staticlib]: https://doc.rust-lang.org/reference/linkage.html#linkage 44 | [use Eyra as a dependency]: https://github.com/sunfishcode/eyra#quick-start 45 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use camino::Utf8Path; 2 | use std::env::var; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::PathBuf; 6 | 7 | fn main() { 8 | // Pass -nostartfiles to the linker. 9 | println!("cargo:rustc-link-arg=-nostartfiles"); 10 | 11 | let out_dir = 12 | PathBuf::from(var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); 13 | let output = out_dir.join("c-tests-list.rs"); 14 | let mut output = File::create(output).unwrap(); 15 | 16 | // Find all the tests in c-tests/src. 17 | let src = Utf8Path::new("c-tests/src"); 18 | for entry in src.read_dir_utf8().unwrap() { 19 | let entry = entry.unwrap(); 20 | let file_name = entry.file_name(); 21 | if file_name.starts_with('.') { 22 | continue; 23 | } 24 | 25 | let test_name = file_name.to_owned(); 26 | let test_name = test_name.replace(".", "_"); 27 | let test_name = test_name.replace("-", "_"); 28 | 29 | writeln!(&mut output, "#[test]").unwrap(); 30 | writeln!(&mut output, "fn test_{}() {{", test_name).unwrap(); 31 | writeln!(&mut output, " test(\"{}\");", file_name).unwrap(); 32 | writeln!(&mut output, "}}").unwrap(); 33 | writeln!(&mut output).unwrap(); 34 | 35 | println!("cargo:rerun-if-changed={}", src.join(file_name)); 36 | } 37 | println!("cargo:rerun-if-changed={}", src); 38 | 39 | let inc = Utf8Path::new("c-tests/include"); 40 | for entry in src.read_dir_utf8().unwrap() { 41 | let entry = entry.unwrap(); 42 | let file_name = entry.file_name(); 43 | if file_name.starts_with('.') { 44 | continue; 45 | } 46 | 47 | println!("cargo:rerun-if-changed={}", inc.join(file_name)); 48 | } 49 | println!("cargo:rerun-if-changed={}", inc); 50 | } 51 | -------------------------------------------------------------------------------- /c-tests/src/basic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | assert(strlen("hello") == 5); 6 | assert(strcmp(strrchr("hello", 'l'), "lo") == 0); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /c-tests/src/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) { 4 | printf("Hello, world!\n"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /c-tests/src/more.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() { 3 | printf("hello world: %f\n", 3.2); 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /ci/aarch64-o-largefile.patch: -------------------------------------------------------------------------------- 1 | From Dan Gohman 2 | Subject: [PATCH] Correct the definition of `O_LARGEFILE` on aarch64 3 | 4 | This fixes `fcntl` with `F_GETFL` from spuriously returning `O_NOFOLLOW` 5 | on hosts such as x86_64. 6 | 7 | diff -ur a/linux-user/aarch64/target_fcntl.h b/linux-user/aarch64/target_fcntl.h 8 | --- a/linux-user/aarch64/target_fcntl.h 9 | +++ b/linux-user/aarch64/target_fcntl.h 10 | @@ -11,6 +11,7 @@ 11 | #define TARGET_O_DIRECTORY 040000 /* must be a directory */ 12 | #define TARGET_O_NOFOLLOW 0100000 /* don't follow links */ 13 | #define TARGET_O_DIRECT 0200000 /* direct disk access hint */ 14 | +#define TARGET_O_LARGEFILE 0400000 15 | 16 | #include "../generic/fcntl.h" 17 | #endif 18 | -------------------------------------------------------------------------------- /ci/getsockopt-timeouts.patch: -------------------------------------------------------------------------------- 1 | From: Dan Gohman 2 | Subject: [PATCH] Avoid storing unexpected values for `SO_RCVTIMEO_NEW` etc. 3 | 4 | This issue is reported upstream [here]. 5 | 6 | [here]: https://gitlab.com/qemu-project/qemu/-/issues/885 7 | 8 | --- 9 | linux-user/generic/sockbits.h | 2 ++ 10 | linux-user/mips/sockbits.h | 2 ++ 11 | linux-user/sparc/sockbits.h | 2 ++ 12 | linux-user/syscall.c | 6 ++++++ 13 | 4 files changed, 12 insertions(+) 14 | 15 | diff --git a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h 16 | index b3b4a8e44c..f95747e3cc 100644 17 | --- a/linux-user/generic/sockbits.h 18 | +++ b/linux-user/generic/sockbits.h 19 | @@ -36,6 +36,8 @@ 20 | #define TARGET_SO_SNDLOWAT 19 21 | #define TARGET_SO_RCVTIMEO 20 22 | #define TARGET_SO_SNDTIMEO 21 23 | +#define TARGET_SO_RCVTIMEO_NEW 66 24 | +#define TARGET_SO_SNDTIMEO_NEW 67 25 | 26 | /* Security levels - as per NRL IPv6 - don't actually do anything */ 27 | #define TARGET_SO_SECURITY_AUTHENTICATION 22 28 | diff --git a/linux-user/mips/sockbits.h b/linux-user/mips/sockbits.h 29 | index 562cad88e2..4d411f7b61 100644 30 | --- a/linux-user/mips/sockbits.h 31 | +++ b/linux-user/mips/sockbits.h 32 | @@ -39,6 +39,8 @@ 33 | #define TARGET_SO_RCVLOWAT 0x1004 /* receive low-water mark */ 34 | #define TARGET_SO_SNDTIMEO 0x1005 /* send timeout */ 35 | #define TARGET_SO_RCVTIMEO 0x1006 /* receive timeout */ 36 | +#define TARGET_SO_RCVTIMEO_NEW 66 37 | +#define TARGET_SO_SNDTIMEO_NEW 67 38 | #define TARGET_SO_ACCEPTCONN 0x1009 39 | #define TARGET_SO_PROTOCOL 0x1028 /* protocol type */ 40 | #define TARGET_SO_DOMAIN 0x1029 /* domain/socket family */ 41 | diff --git a/linux-user/sparc/sockbits.h b/linux-user/sparc/sockbits.h 42 | index 0a822e3e1f..8420ef9953 100644 43 | --- a/linux-user/sparc/sockbits.h 44 | +++ b/linux-user/sparc/sockbits.h 45 | @@ -26,6 +26,8 @@ 46 | #define TARGET_SO_SNDLOWAT 0x1000 47 | #define TARGET_SO_RCVTIMEO 0x2000 48 | #define TARGET_SO_SNDTIMEO 0x4000 49 | +#define TARGET_SO_RCVTIMEO_NEW 68 50 | +#define TARGET_SO_SNDTIMEO_NEW 69 51 | #define TARGET_SO_ACCEPTCONN 0x8000 52 | 53 | #define TARGET_SO_SNDBUF 0x1001 54 | diff --git a/linux-user/syscall.c b/linux-user/syscall.c 55 | index a8eae3c4ac..8326e03a19 100644 56 | --- a/linux-user/syscall.c 57 | +++ b/linux-user/syscall.c 58 | @@ -2348,6 +2348,9 @@ set_timeout: 59 | case TARGET_SO_SNDTIMEO: 60 | optname = SO_SNDTIMEO; 61 | goto set_timeout; 62 | + case TARGET_SO_RCVTIMEO_NEW: 63 | + case TARGET_SO_SNDTIMEO_NEW: 64 | + return -TARGET_ENOPROTOOPT; 65 | case TARGET_SO_ATTACH_FILTER: 66 | { 67 | struct target_sock_fprog *tfprog; 68 | @@ -2595,6 +2598,9 @@ get_timeout: 69 | case TARGET_SO_SNDTIMEO: 70 | optname = SO_SNDTIMEO; 71 | goto get_timeout; 72 | + case TARGET_SO_RCVTIMEO_NEW: 73 | + case TARGET_SO_SNDTIMEO_NEW: 74 | + return -TARGET_ENOPROTOOPT; 75 | case TARGET_SO_PEERCRED: { 76 | struct ucred cr; 77 | socklen_t crlen; 78 | -- 79 | 2.32.0 80 | 81 | -------------------------------------------------------------------------------- /ci/more-sockopts.patch: -------------------------------------------------------------------------------- 1 | From Dan Gohman 2 | Subject: [PATCH] Implement various socket options. 3 | 4 | This implements the `SO_INCOMING_CPU`, `SO_COOKIE`, and `SO_PROTOCOL` 5 | socket options. 6 | 7 | diff -ur -x roms -x build a/linux-user/generic/sockbits.h b/linux-user/generic/sockbits.h 8 | --- a/linux-user/generic/sockbits.h 9 | +++ b/linux-user/generic/sockbits.h 10 | @@ -60,4 +60,10 @@ 11 | 12 | #define TARGET_SO_PROTOCOL 38 13 | #define TARGET_SO_DOMAIN 39 14 | +#ifndef TARGET_SO_INCOMING_CPU 15 | +#define TARGET_SO_INCOMING_CPU 49 16 | +#endif 17 | +#ifndef TARGET_SO_COOKIE 18 | +#define TARGET_SO_COOKIE 57 19 | +#endif 20 | #endif 21 | diff -ur -x roms -x build a/linux-user/syscall.c b/linux-user/syscall.c 22 | --- a/linux-user/syscall.c 23 | +++ b/linux-user/syscall.c 24 | @@ -2476,6 +2476,9 @@ 25 | case TARGET_SO_RCVLOWAT: 26 | optname = SO_RCVLOWAT; 27 | break; 28 | + case TARGET_SO_INCOMING_CPU: 29 | + optname = SO_INCOMING_CPU; 30 | + break; 31 | default: 32 | goto unimplemented; 33 | } 34 | @@ -2534,6 +2537,7 @@ 35 | { 36 | abi_long ret; 37 | int len, val; 38 | + int64_t val64; 39 | socklen_t lv; 40 | 41 | switch(level) { 42 | @@ -2733,6 +2737,27 @@ 43 | case TARGET_SO_DOMAIN: 44 | optname = SO_DOMAIN; 45 | goto int_case; 46 | + case TARGET_SO_INCOMING_CPU: 47 | + optname = SO_INCOMING_CPU; 48 | + goto int_case; 49 | + case TARGET_SO_COOKIE: 50 | + optname = SO_COOKIE; 51 | + if (get_user_u32(len, optlen)) 52 | + return -TARGET_EFAULT; 53 | + if (len < 0) 54 | + return -TARGET_EINVAL; 55 | + lv = sizeof(val64); 56 | + ret = get_errno(getsockopt(sockfd, level, optname, &val64, &lv)); 57 | + if (ret < 0) 58 | + return ret; 59 | + if (len > lv) 60 | + len = lv; 61 | + assert(len == 8); 62 | + if (put_user_u64(val64, optval_addr)) 63 | + return -TARGET_EFAULT; 64 | + if (put_user_u32(len, optlen)) 65 | + return -TARGET_EFAULT; 66 | + break; 67 | default: 68 | goto int_case; 69 | } 70 | @@ -2756,6 +2781,9 @@ 71 | case SO_ERROR: 72 | val = host_to_target_errno(val); 73 | break; 74 | + case SO_PROTOCOL: 75 | + val = host_to_target_errno(val); 76 | + break; 77 | } 78 | if (level == SOL_SOCKET && optname == SO_ERROR) { 79 | val = host_to_target_errno(val); 80 | -------------------------------------------------------------------------------- /ci/s390x-stat-have-nsec.patch: -------------------------------------------------------------------------------- 1 | From: Dan Gohman 2 | Subject: [PATCH] Define `TARGET_STAT_HAVE_NSEC` for s390x 3 | 4 | Without this, The `fstat` syscall sets `st_mtime_nsec` and the other `_nsec` 5 | fields to 0. Libc `fstat` will sometimes use the `fstatat` or `fstat64` 6 | syscalls instead, which aren't affected, but the libc `fstat` on ubuntu-20.04 7 | on Github Actions appears to be affected. 8 | 9 | This can be seen in the `st_mtime_nsec` assert in tests/fs/futimens.rs. 10 | 11 | It's not yet known why upstream qemu doesn't define this. 12 | 13 | --- 14 | linux-user/generic/sockbits.h | 1 + 15 | 1 files changed, 1 insertions(+) 16 | 17 | diff -ur a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h 18 | --- a/linux-user/syscall_defs.h 2021-08-24 10:35:41.000000000 -0700 19 | +++ b/linux-user/syscall_defs.h 2022-04-12 13:23:25.291064887 -0700 20 | @@ -2040,6 +2040,7 @@ 21 | abi_long __unused[3]; 22 | }; 23 | #elif defined(TARGET_S390X) 24 | +#define TARGET_STAT_HAVE_NSEC 25 | struct target_stat { 26 | abi_ulong st_dev; 27 | abi_ulong st_ino; 28 | -------------------------------------------------------------------------------- /ci/tcgets2-tcsets2.patch: -------------------------------------------------------------------------------- 1 | From Dan Gohman 2 | Subject: [PATCH] Implement `TCGETS2` and `TCSETS2`. 3 | 4 | This implements the `TCGETS2` and `TCSETS2` ioctls. 5 | 6 | diff -ur a/linux-user/ioctls.h b/linux-user/ioctls.h 7 | --- a/linux-user/ioctls.h 8 | +++ b/linux-user/ioctls.h 9 | @@ -2,6 +2,8 @@ 10 | 11 | IOCTL(TCGETS, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios))) 12 | IOCTL(TCSETS, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) 13 | + IOCTL(TCGETS2, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios2))) 14 | + IOCTL(TCSETS2, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios2))) 15 | IOCTL(TCSETSF, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) 16 | IOCTL(TCSETSW, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) 17 | IOCTL(TIOCGWINSZ, IOC_R, MK_PTR(MK_STRUCT(STRUCT_winsize))) 18 | diff -ur a/linux-user/ppc/termbits.h b/linux-user/ppc/termbits.h 19 | --- a/linux-user/ppc/termbits.h 20 | +++ b/linux-user/ppc/termbits.h 21 | @@ -20,6 +20,17 @@ 22 | target_speed_t c_ospeed; /* output speed */ 23 | }; 24 | 25 | +struct target_termios2 { 26 | + target_tcflag_t c_iflag; /* input mode flags */ 27 | + target_tcflag_t c_oflag; /* output mode flags */ 28 | + target_tcflag_t c_cflag; /* control mode flags */ 29 | + target_tcflag_t c_lflag; /* local mode flags */ 30 | + target_cc_t c_cc[TARGET_NCCS]; /* control characters */ 31 | + target_cc_t c_line; /* line discipline */ 32 | + target_speed_t c_ispeed; /* input speed */ 33 | + target_speed_t c_ospeed; /* output speed */ 34 | +}; 35 | + 36 | /* c_cc character offsets */ 37 | #define TARGET_VINTR 0 38 | #define TARGET_VQUIT 1 39 | @@ -95,6 +106,8 @@ 40 | 41 | /* c_cflag bit meaning */ 42 | #define TARGET_CBAUD 0000377 43 | +#define TARGET_CIBAUD 077600000 44 | +#define TARGET_IBSHIFT 16 45 | #define TARGET_B0 0000000 /* hang up */ 46 | #define TARGET_B50 0000001 47 | #define TARGET_B75 0000002 48 | @@ -129,6 +142,7 @@ 49 | #define TARGET_B3000000 00034 50 | #define TARGET_B3500000 00035 51 | #define TARGET_B4000000 00036 52 | +#define TARGET_BOTHER 00037 53 | 54 | #define TARGET_CSIZE 00001400 55 | #define TARGET_CS5 00000000 56 | @@ -178,6 +192,8 @@ 57 | #define TARGET_TCSETS TARGET_IOW('t', 20, struct target_termios) 58 | #define TARGET_TCSETSW TARGET_IOW('t', 21, struct target_termios) 59 | #define TARGET_TCSETSF TARGET_IOW('t', 22, struct target_termios) 60 | +#define TARGET_TCGETS2 TARGET_TCGETS 61 | +#define TARGET_TCSETS2 TARGET_TCSETS 62 | 63 | #define TARGET_TCGETA TARGET_IOR('t', 23, struct target_termio) 64 | #define TARGET_TCSETA TARGET_IOW('t', 24, struct target_termio) 65 | diff -ur a/linux-user/strace.c b/linux-user/strace.c 66 | --- a/linux-user/strace.c 67 | +++ b/linux-user/strace.c 68 | @@ -1821,13 +1821,16 @@ 69 | } 70 | 71 | qemu_log("c_cflag = "); 72 | + if (cflags & TARGET_CIBAUD) { 73 | + print_enums(termios_cflags_CBAUD, (cflags & TARGET_CIBAUD) >> TARGET_IBSHIFT, 0); 74 | + } 75 | if (cflags & TARGET_CBAUD) { 76 | print_enums(termios_cflags_CBAUD, cflags & TARGET_CBAUD, 0); 77 | } 78 | if (cflags & TARGET_CSIZE) { 79 | print_enums(termios_cflags_CSIZE, cflags & TARGET_CSIZE, 0); 80 | } 81 | - target_tcflag_t cflags_clean = cflags & ~(TARGET_CBAUD | TARGET_CSIZE); 82 | + target_tcflag_t cflags_clean = cflags & ~(TARGET_CBAUD | TARGET_CIBAUD | TARGET_CSIZE); 83 | print_flags(termios_cflags, cflags_clean, 0); 84 | 85 | qemu_log("c_lflag = "); 86 | @@ -1841,6 +1844,73 @@ 87 | 88 | qemu_log("}"); 89 | } 90 | + 91 | +void 92 | +print_termios2(void *arg) 93 | +{ 94 | + const struct target_termios2 *target = arg; 95 | + 96 | + target_tcflag_t iflags = tswap32(target->c_iflag); 97 | + target_tcflag_t oflags = tswap32(target->c_oflag); 98 | + target_tcflag_t cflags = tswap32(target->c_cflag); 99 | + target_tcflag_t lflags = tswap32(target->c_lflag); 100 | + 101 | + qemu_log("{"); 102 | + 103 | + qemu_log("c_iflag = "); 104 | + print_flags(termios_iflags, iflags, 0); 105 | + 106 | + qemu_log("c_oflag = "); 107 | + target_tcflag_t oflags_clean = oflags & ~(TARGET_NLDLY | TARGET_CRDLY | 108 | + TARGET_TABDLY | TARGET_BSDLY | 109 | + TARGET_VTDLY | TARGET_FFDLY); 110 | + print_flags(termios_oflags, oflags_clean, 0); 111 | + if (oflags & TARGET_NLDLY) { 112 | + print_enums(termios_oflags_NLDLY, oflags & TARGET_NLDLY, 0); 113 | + } 114 | + if (oflags & TARGET_CRDLY) { 115 | + print_enums(termios_oflags_CRDLY, oflags & TARGET_CRDLY, 0); 116 | + } 117 | + if (oflags & TARGET_TABDLY) { 118 | + print_enums(termios_oflags_TABDLY, oflags & TARGET_TABDLY, 0); 119 | + } 120 | + if (oflags & TARGET_BSDLY) { 121 | + print_enums(termios_oflags_BSDLY, oflags & TARGET_BSDLY, 0); 122 | + } 123 | + if (oflags & TARGET_VTDLY) { 124 | + print_enums(termios_oflags_VTDLY, oflags & TARGET_VTDLY, 0); 125 | + } 126 | + if (oflags & TARGET_FFDLY) { 127 | + print_enums(termios_oflags_FFDLY, oflags & TARGET_FFDLY, 0); 128 | + } 129 | + 130 | + qemu_log("c_cflag = "); 131 | + if (cflags & TARGET_CIBAUD) { 132 | + print_enums(termios_cflags_CBAUD, (cflags & TARGET_CIBAUD) >> TARGET_IBSHIFT, 0); 133 | + } 134 | + if (cflags & TARGET_CBAUD) { 135 | + print_enums(termios_cflags_CBAUD, cflags & TARGET_CBAUD, 0); 136 | + } 137 | + if (cflags & TARGET_CSIZE) { 138 | + print_enums(termios_cflags_CSIZE, cflags & TARGET_CSIZE, 0); 139 | + } 140 | + target_tcflag_t cflags_clean = cflags & ~(TARGET_CBAUD | TARGET_CIBAUD | TARGET_CSIZE); 141 | + print_flags(termios_cflags, cflags_clean, 0); 142 | + 143 | + qemu_log("c_lflag = "); 144 | + print_flags(termios_lflags, lflags, 0); 145 | + 146 | + qemu_log("c_cc = "); 147 | + qemu_log("\"%s\",", target->c_cc); 148 | + 149 | + qemu_log("c_line = "); 150 | + print_raw_param("\'%c\'", target->c_line, 0); 151 | + 152 | + print_raw_param("c_ispeed = %" PRIu32, tswap64(target->c_ispeed), 0); 153 | + print_raw_param("c_ospeed = %" PRIu32, tswap64(target->c_ospeed), 1); 154 | + 155 | + qemu_log("}"); 156 | +} 157 | 158 | #undef UNUSED 159 | 160 | diff -ur a/linux-user/syscall.c b/linux-user/syscall.c 161 | --- a/linux-user/syscall.c 162 | +++ b/linux-user/syscall.c 163 | @@ -84,6 +84,7 @@ 164 | #endif 165 | 166 | #define termios host_termios 167 | +#define termios2 host_termios2 168 | #define winsize host_winsize 169 | #define termio host_termio 170 | #define sgttyb host_sgttyb /* same as target */ 171 | @@ -5871,6 +5872,28 @@ 172 | { TARGET_CBAUD, TARGET_B115200, CBAUD, B115200 }, 173 | { TARGET_CBAUD, TARGET_B230400, CBAUD, B230400 }, 174 | { TARGET_CBAUD, TARGET_B460800, CBAUD, B460800 }, 175 | + { TARGET_CBAUD, TARGET_BOTHER, CBAUD, BOTHER }, 176 | + { TARGET_CIBAUD, TARGET_B0 << TARGET_IBSHIFT, CIBAUD, B0 << IBSHIFT }, 177 | + { TARGET_CIBAUD, TARGET_B50 << TARGET_IBSHIFT, CIBAUD, B50 << IBSHIFT }, 178 | + { TARGET_CIBAUD, TARGET_B75 << TARGET_IBSHIFT, CIBAUD, B75 << IBSHIFT }, 179 | + { TARGET_CIBAUD, TARGET_B110 << TARGET_IBSHIFT, CIBAUD, B110 << IBSHIFT }, 180 | + { TARGET_CIBAUD, TARGET_B134 << TARGET_IBSHIFT, CIBAUD, B134 << IBSHIFT }, 181 | + { TARGET_CIBAUD, TARGET_B150 << TARGET_IBSHIFT, CIBAUD, B150 << IBSHIFT }, 182 | + { TARGET_CIBAUD, TARGET_B200 << TARGET_IBSHIFT, CIBAUD, B200 << IBSHIFT }, 183 | + { TARGET_CIBAUD, TARGET_B300 << TARGET_IBSHIFT, CIBAUD, B300 << IBSHIFT }, 184 | + { TARGET_CIBAUD, TARGET_B600 << TARGET_IBSHIFT, CIBAUD, B600 << IBSHIFT }, 185 | + { TARGET_CIBAUD, TARGET_B1200 << TARGET_IBSHIFT, CIBAUD, B1200 << IBSHIFT }, 186 | + { TARGET_CIBAUD, TARGET_B1800 << TARGET_IBSHIFT, CIBAUD, B1800 << IBSHIFT }, 187 | + { TARGET_CIBAUD, TARGET_B2400 << TARGET_IBSHIFT, CIBAUD, B2400 << IBSHIFT }, 188 | + { TARGET_CIBAUD, TARGET_B4800 << TARGET_IBSHIFT, CIBAUD, B4800 << IBSHIFT }, 189 | + { TARGET_CIBAUD, TARGET_B9600 << TARGET_IBSHIFT, CIBAUD, B9600 << IBSHIFT }, 190 | + { TARGET_CIBAUD, TARGET_B19200 << TARGET_IBSHIFT, CIBAUD, B19200 << IBSHIFT }, 191 | + { TARGET_CIBAUD, TARGET_B38400 << TARGET_IBSHIFT, CIBAUD, B38400 << IBSHIFT }, 192 | + { TARGET_CIBAUD, TARGET_B57600 << TARGET_IBSHIFT, CIBAUD, B57600 << IBSHIFT }, 193 | + { TARGET_CIBAUD, TARGET_B115200 << TARGET_IBSHIFT, CIBAUD, B115200 << IBSHIFT }, 194 | + { TARGET_CIBAUD, TARGET_B230400 << TARGET_IBSHIFT, CIBAUD, B230400 << IBSHIFT }, 195 | + { TARGET_CIBAUD, TARGET_B460800 << TARGET_IBSHIFT, CIBAUD, B460800 << IBSHIFT }, 196 | + { TARGET_CIBAUD, TARGET_BOTHER << TARGET_IBSHIFT, CIBAUD, BOTHER << IBSHIFT }, 197 | { TARGET_CSIZE, TARGET_CS5, CSIZE, CS5 }, 198 | { TARGET_CSIZE, TARGET_CS6, CSIZE, CS6 }, 199 | { TARGET_CSIZE, TARGET_CS7, CSIZE, CS7 }, 200 | @@ -5938,6 +5961,43 @@ 201 | host->c_cc[VEOL2] = target->c_cc[TARGET_VEOL2]; 202 | } 203 | 204 | +static void target_to_host_termios2 (void *dst, const void *src) 205 | +{ 206 | + struct host_termios2 *host = dst; 207 | + const struct target_termios2 *target = src; 208 | + 209 | + host->c_iflag = 210 | + target_to_host_bitmask(tswap32(target->c_iflag), iflag_tbl); 211 | + host->c_oflag = 212 | + target_to_host_bitmask(tswap32(target->c_oflag), oflag_tbl); 213 | + host->c_cflag = 214 | + target_to_host_bitmask(tswap32(target->c_cflag), cflag_tbl); 215 | + host->c_lflag = 216 | + target_to_host_bitmask(tswap32(target->c_lflag), lflag_tbl); 217 | + host->c_line = target->c_line; 218 | + 219 | + memset(host->c_cc, 0, sizeof(host->c_cc)); 220 | + host->c_cc[VINTR] = target->c_cc[TARGET_VINTR]; 221 | + host->c_cc[VQUIT] = target->c_cc[TARGET_VQUIT]; 222 | + host->c_cc[VERASE] = target->c_cc[TARGET_VERASE]; 223 | + host->c_cc[VKILL] = target->c_cc[TARGET_VKILL]; 224 | + host->c_cc[VEOF] = target->c_cc[TARGET_VEOF]; 225 | + host->c_cc[VTIME] = target->c_cc[TARGET_VTIME]; 226 | + host->c_cc[VMIN] = target->c_cc[TARGET_VMIN]; 227 | + host->c_cc[VSWTC] = target->c_cc[TARGET_VSWTC]; 228 | + host->c_cc[VSTART] = target->c_cc[TARGET_VSTART]; 229 | + host->c_cc[VSTOP] = target->c_cc[TARGET_VSTOP]; 230 | + host->c_cc[VSUSP] = target->c_cc[TARGET_VSUSP]; 231 | + host->c_cc[VEOL] = target->c_cc[TARGET_VEOL]; 232 | + host->c_cc[VREPRINT] = target->c_cc[TARGET_VREPRINT]; 233 | + host->c_cc[VDISCARD] = target->c_cc[TARGET_VDISCARD]; 234 | + host->c_cc[VWERASE] = target->c_cc[TARGET_VWERASE]; 235 | + host->c_cc[VLNEXT] = target->c_cc[TARGET_VLNEXT]; 236 | + host->c_cc[VEOL2] = target->c_cc[TARGET_VEOL2]; 237 | + host->c_ispeed = tswap32(target->c_ispeed); 238 | + host->c_ospeed = tswap32(target->c_ospeed); 239 | +} 240 | + 241 | static void host_to_target_termios (void *dst, const void *src) 242 | { 243 | struct target_termios *target = dst; 244 | @@ -5971,6 +6031,50 @@ 245 | target->c_cc[TARGET_VWERASE] = host->c_cc[VWERASE]; 246 | target->c_cc[TARGET_VLNEXT] = host->c_cc[VLNEXT]; 247 | target->c_cc[TARGET_VEOL2] = host->c_cc[VEOL2]; 248 | +#if defined(TARGET_PPC) || defined(TARGET_PPC64) 249 | + // On PowerPC, `termios` is an alias for `termios2`, so it has the 250 | + // `c_ispeed` and `c_ospeed` fields. Our host may not have those fields 251 | + // though, so just set them to zero so that they're not uninitialized. 252 | + target->c_ispeed = 0; 253 | + target->c_ospeed = 0; 254 | +#endif 255 | +} 256 | + 257 | +static void host_to_target_termios2 (void *dst, const void *src) 258 | +{ 259 | + struct target_termios2 *target = dst; 260 | + const struct host_termios2 *host = src; 261 | + 262 | + target->c_iflag = 263 | + tswap32(host_to_target_bitmask(host->c_iflag, iflag_tbl)); 264 | + target->c_oflag = 265 | + tswap32(host_to_target_bitmask(host->c_oflag, oflag_tbl)); 266 | + target->c_cflag = 267 | + tswap32(host_to_target_bitmask(host->c_cflag, cflag_tbl)); 268 | + target->c_lflag = 269 | + tswap32(host_to_target_bitmask(host->c_lflag, lflag_tbl)); 270 | + target->c_line = host->c_line; 271 | + 272 | + memset(target->c_cc, 0, sizeof(target->c_cc)); 273 | + target->c_cc[TARGET_VINTR] = host->c_cc[VINTR]; 274 | + target->c_cc[TARGET_VQUIT] = host->c_cc[VQUIT]; 275 | + target->c_cc[TARGET_VERASE] = host->c_cc[VERASE]; 276 | + target->c_cc[TARGET_VKILL] = host->c_cc[VKILL]; 277 | + target->c_cc[TARGET_VEOF] = host->c_cc[VEOF]; 278 | + target->c_cc[TARGET_VTIME] = host->c_cc[VTIME]; 279 | + target->c_cc[TARGET_VMIN] = host->c_cc[VMIN]; 280 | + target->c_cc[TARGET_VSWTC] = host->c_cc[VSWTC]; 281 | + target->c_cc[TARGET_VSTART] = host->c_cc[VSTART]; 282 | + target->c_cc[TARGET_VSTOP] = host->c_cc[VSTOP]; 283 | + target->c_cc[TARGET_VSUSP] = host->c_cc[VSUSP]; 284 | + target->c_cc[TARGET_VEOL] = host->c_cc[VEOL]; 285 | + target->c_cc[TARGET_VREPRINT] = host->c_cc[VREPRINT]; 286 | + target->c_cc[TARGET_VDISCARD] = host->c_cc[VDISCARD]; 287 | + target->c_cc[TARGET_VWERASE] = host->c_cc[VWERASE]; 288 | + target->c_cc[TARGET_VLNEXT] = host->c_cc[VLNEXT]; 289 | + target->c_cc[TARGET_VEOL2] = host->c_cc[VEOL2]; 290 | + target->c_ispeed = tswap32(host->c_ispeed); 291 | + target->c_ospeed = tswap32(host->c_ospeed); 292 | } 293 | 294 | static const StructEntry struct_termios_def = { 295 | @@ -5980,6 +6084,13 @@ 296 | .print = print_termios, 297 | }; 298 | 299 | +static const StructEntry struct_termios2_def = { 300 | + .convert = { host_to_target_termios2, target_to_host_termios2 }, 301 | + .size = { sizeof(struct target_termios2), sizeof(struct host_termios2) }, 302 | + .align = { __alignof__(struct target_termios2), __alignof__(struct host_termios2) }, 303 | + .print = print_termios2, 304 | +}; 305 | + 306 | /* If the host does not provide these bits, they may be safely discarded. */ 307 | #ifndef MAP_SYNC 308 | #define MAP_SYNC 0 309 | diff -ur a/linux-user/syscall_types.h b/linux-user/syscall_types.h 310 | --- a/linux-user/syscall_types.h 311 | +++ b/linux-user/syscall_types.h 312 | @@ -1,4 +1,5 @@ 313 | STRUCT_SPECIAL(termios) 314 | +STRUCT_SPECIAL(termios2) 315 | 316 | STRUCT(winsize, 317 | TYPE_SHORT, TYPE_SHORT, TYPE_SHORT, TYPE_SHORT) 318 | diff -ur a/linux-user/user-internals.h b/linux-user/user-internals.h 319 | --- a/linux-user/user-internals.h 320 | +++ b/linux-user/user-internals.h 321 | @@ -131,6 +131,7 @@ 322 | #endif /* TARGET_ABI_BITS != 32 */ 323 | 324 | void print_termios(void *arg); 325 | +void print_termios2(void *arg); 326 | 327 | /* ARM EABI and MIPS expect 64bit types aligned even on pairs or registers */ 328 | #ifdef TARGET_ARM 329 | -------------------------------------------------------------------------------- /ci/translate-errno.patch: -------------------------------------------------------------------------------- 1 | From: Dan Gohman 2 | Subject: [PATCH] Translate errno codes from host to target for `SO_ERROR`. 3 | 4 | This issue is reported upstream [here]. 5 | 6 | [here]: https://gitlab.com/qemu-project/qemu/-/issues/872 7 | 8 | --- 9 | linux-user/syscall.c | 3 +++ 10 | 1 file changed, 3 insertions(+) 11 | 12 | diff --git a/linux-user/syscall.c b/linux-user/syscall.c 13 | index b9b18a7eaf..a8eae3c4ac 100644 14 | --- a/linux-user/syscall.c 15 | +++ b/linux-user/syscall.c 16 | @@ -2767,6 +2767,9 @@ get_timeout: 17 | if (optname == SO_TYPE) { 18 | val = host_to_target_sock_type(val); 19 | } 20 | + if (level == SOL_SOCKET && optname == SO_ERROR) { 21 | + val = host_to_target_errno(val); 22 | + } 23 | if (len > lv) 24 | len = lv; 25 | if (len == 4) { 26 | -- 27 | 2.32.0 28 | 29 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-10-12" 3 | components = ["rustc", "cargo", "rust-std", "rust-src", "rustfmt"] 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Nothing here. This crate just depends on Eyra to build it as a staticlib. 2 | -------------------------------------------------------------------------------- /tests/c-tests.rs: -------------------------------------------------------------------------------- 1 | use pretty_assertions::assert_eq; 2 | use std::env::var; 3 | use std::process::Command; 4 | use std::sync::Once; 5 | 6 | include!(concat!(env!("OUT_DIR"), "/c-tests-list.rs")); 7 | 8 | fn test(name: &str) { 9 | let out_dir = var("OUT_DIR").unwrap(); 10 | 11 | // It appears "cargo test" doesn't build our staticlib target. So build it. 12 | static INIT: Once = Once::new(); 13 | INIT.call_once(|| { 14 | let mut cargo = Command::new("cargo"); 15 | cargo.arg("build"); 16 | #[cfg(not(debug_assertions))] 17 | cargo.arg("--release"); 18 | assert!(cargo.status().unwrap().success()); 19 | }); 20 | 21 | // We're not running in build.rs here, so we need to configure some things 22 | // in `cc` manually. 23 | let mut build = cc::Build::new(); 24 | build 25 | .target(&target_lexicon::HOST.to_string()) 26 | .opt_level(0) 27 | .host(&target_lexicon::HOST.to_string()); 28 | 29 | let source = format!("c-tests/src/{}", name); 30 | build 31 | .clone() 32 | .file(source) 33 | .include("c-tests/include") 34 | .compile(name); 35 | 36 | // Link the archive into an executable to produce the reference 37 | // executable. 38 | let mut compiler = build.clone().get_compiler().to_command(); 39 | let exe = format!("{}/{}.reference", out_dir, name); 40 | compiler.arg(&format!("{}/lib{}.a", out_dir, name)); 41 | compiler.arg("-o"); 42 | compiler.arg(&exe); 43 | let output = compiler.output().unwrap(); 44 | if !output.status.success() { 45 | eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 46 | eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 47 | eprintln!("status: {}", output.status); 48 | } 49 | assert!(output.status.success()); 50 | 51 | // Run the reference executable. 52 | eprintln!(); 53 | eprintln!("Running reference executable {}:", exe); 54 | let mut command = Command::new(&exe); 55 | let reference_output = command.output().unwrap(); 56 | if !reference_output.status.success() { 57 | eprintln!( 58 | "stdout: {}", 59 | String::from_utf8_lossy(&reference_output.stdout) 60 | ); 61 | eprintln!( 62 | "stderr: {}", 63 | String::from_utf8_lossy(&reference_output.stderr) 64 | ); 65 | eprintln!("status: {}", reference_output.status); 66 | } 67 | assert!(reference_output.status.success()); 68 | 69 | // Link the archive into an executable to produce the Eyra executable. 70 | let mut compiler = build.clone().get_compiler().to_command(); 71 | let exe = format!("{}/{}.eyra", out_dir, name); 72 | compiler.arg(&format!("{}/lib{}.a", out_dir, name)); 73 | compiler.arg("-nostdlib"); 74 | 75 | #[cfg(debug_assertions)] 76 | compiler.arg("target/debug/libc.a"); 77 | #[cfg(not(debug_assertions))] 78 | compiler.arg("target/release/libc.a"); 79 | 80 | compiler.arg("-Wl,--require-defined=main"); 81 | compiler.arg("-o"); 82 | compiler.arg(&exe); 83 | for lib_dir in var("LD_LIBRARY_PATH").unwrap().split(':') { 84 | compiler.arg("-L"); 85 | compiler.arg(lib_dir); 86 | } 87 | 88 | let output = compiler.output().unwrap(); 89 | if !output.status.success() { 90 | eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 91 | eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 92 | eprintln!("status: {}", output.status); 93 | } 94 | assert!(output.status.success()); 95 | 96 | // Run the Eyra executable. 97 | eprintln!(); 98 | eprintln!("Running Eyra executable {}:", exe); 99 | let mut command = Command::new(&exe); 100 | let eyra_output = command.output().unwrap(); 101 | if !eyra_output.status.success() { 102 | eprintln!("stdout: {}", String::from_utf8_lossy(&eyra_output.stdout)); 103 | eprintln!("stderr: {}", String::from_utf8_lossy(&eyra_output.stderr)); 104 | eprintln!("status: {}", eyra_output.status); 105 | } 106 | assert!(eyra_output.status.success()); 107 | 108 | // Compare the output. 109 | eprintln!(); 110 | assert_eq!( 111 | String::from_utf8(reference_output.stderr).unwrap(), 112 | String::from_utf8(eyra_output.stderr).unwrap() 113 | ); 114 | assert_eq!( 115 | String::from_utf8(reference_output.stdout).unwrap(), 116 | String::from_utf8(eyra_output.stdout).unwrap() 117 | ); 118 | } 119 | --------------------------------------------------------------------------------