2

I'm trying to get Rust running in Docker to use it for 32-bit musl builds. Ever since I updated it to use the new URL to pull rustup, I'm hitting this issue when running the container interactively using bash:

root@2c3549fe3169:/sample# cargo
error: command failed: 'cargo'
info: caused by: No such file or directory (os error 2)

The weird thing is, I can see the executables

root@2c3549fe3169:/sample# ls -l /root/.cargo/bin/
total 101440
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 cargo
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 cargo-clippy
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 cargo-fmt
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rls
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rust-gdb
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rust-lldb
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rustc
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rustdoc
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rustfmt
-rwxr-xr-x 10 root root 10383380 Feb 17 21:34 rustup
root@2c3549fe3169:/sample# date
Sun Feb 17 21:34:21 UTC 2019
root@2c3549fe3169:/sample# file /root/.cargo/bin/cargo
/root/.cargo/bin/cargo: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.9, with debug_info, not stripped
root@2c3549fe3169:/sample# cargo
error: command failed: 'cargo'
info: caused by: No such file or directory (os error 2)

It is installed via:

RUN curl https://sh.rustup.rs -sSf | sh -s -- \
--default-toolchain 1.32.0 \
-y && \
~/.cargo/bin/rustup target add i686-unknown-linux-musl && \
echo "[build]\ntarget = \"i686-unknown-linux-musl\"" > ~/.cargo/config

I can see the file but I cannot seem to run it, even when I switch into that directory:

root@2c3549fe3169:~/.cargo/bin# ./cargo
error: command failed: 'cargo'
info: caused by: No such file or directory (os error 2)

This is what I see when running ldd:

root@4e21c8d37266:/volume# ldd /root/.cargo/bin/cargo
linux-gate.so.1 (0xf7f41000)
libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xf774c000)
librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xf7742000)
libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xf7723000)
libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xf7705000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7529000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xf7427000)
/lib/ld-linux.so.2 (0xf7f43000)

This is my complete Dockerfile

FROM i386/ubuntu

RUN apt-get update && apt-get install -y \
  cmake \
  curl \
  file \
  git \
  g++ \
  python \
  make \
  nano \
  ca-certificates \
  xz-utils \
  musl-tools \
  pkg-config \
  apt-file \
  xutils-dev \
  --no-install-recommends && \
  rm -rf /var/lib/apt/lists/*


RUN curl https://sh.rustup.rs -sSf | sh -s -- \
  --default-toolchain 1.32.0 \
  -y && \
  ~/.cargo/bin/rustup target add i686-unknown-linux-musl && \
  echo "[build]\ntarget = \"i686-unknown-linux-musl\"" > ~/.cargo/config


# Compile C libraries with musl-gcc
ENV SSL_VER=1.0.2j \
    CURL_VER=7.52.1 \
    CC=musl-gcc \
    PREFIX=/usr/local \
    PATH=/usr/local/bin:$PATH \
    PKG_CONFIG_PATH=/usr/local/lib/pkgconfig

RUN curl -sL http://www.openssl.org/source/openssl-$SSL_VER.tar.gz | tar xz && \
    cd openssl-$SSL_VER && \
    ./Configure no-shared --prefix=$PREFIX --openssldir=$PREFIX/ssl no-zlib -m32 linux-elf -fPIC -fno-stack-protector && \
    make depend 2> /dev/null && make -j$(nproc) && make install && \
    cd .. && rm -rf openssl-$SSL_VER

RUN curl https://curl.haxx.se/download/curl-$CURL_VER.tar.gz | tar xz && \
    cd curl-$CURL_VER && \
    ./configure --enable-shared=no --enable-static=ssl --enable-optimize --prefix=$PREFIX --host=i686-pc-linux-gnu CFLAGS=-m32 \
      --with-ca-path=/etc/ssl/certs/ --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt --without-ca-fallback && \
    make -j$(nproc) && make install && \
    cd .. && rm -rf curl-$CURL_VER

ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
    SSL_CERT_DIR=/etc/ssl/certs \
    OPENSSL_LIB_DIR=$PREFIX/lib \
    OPENSSL_INCLUDE_DIR=$PREFIX/include \
    OPENSSL_DIR=$PREFIX \
    OPENSSL_STATIC=1 \
    PATH=/usr/local/bin:/root/.cargo/bin:$PATH

RUN echo $PATH

And strace'ing the cargo binary as per comments:

root@156da6108ff8:~/.cargo/bin# strace -f -e trace=execve cargo               
execve("/root/.cargo/bin/cargo", ["cargo"], 0xfffdd8fc /* 20 vars */) = 0
execve("/root/.rustup/toolchains/1.32.0-x86_64-unknown-linux-gnu/bin/cargo", ["/root/.rustup/toolchains/1.32.0-"...], 0x57d95620 /* 25 vars */) = -1 ENOENT (No such file or directory)
error: command failed: 'cargo'
info: caused by: No such file or directory (os error 2)
+++ exited with 1 +++
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
opensourcegeek
  • 5,552
  • 7
  • 43
  • 64
  • 1
    `(os error 2)` is `ENOENT`, which, per `man 2 execve`, may be returned not only when file is actually not found, but also when it **can not be executed for some reason** (binary format is not supported by the kernel, etc.). That is 99% your case, but I can't spot the exact reason from first glance. Can you share full `Dockerfile` (or at least the base image you use) and the output of `uname -a` at your host? – Danila Kiver Feb 18 '19 at 13:30
  • 1
    Also, it looks like the problem is not in the `cargo` binary itself, because `os error 2` seems to be a Rust standard library output, and this means that `cargo` itself _gets successfully started_, but some _subsequent_ `execve()` fails. Try to install `strace` and run: `strace -f -e trace=execve cargo`, then look for the call which returns `-ENOENT`. This will show which binary causes the failure. – Danila Kiver Feb 18 '19 at 13:38
  • Let's pretend that not everyone has memorized what the "old" and "new" URLs are for rustup — can you share **what you actually changed**? – Shepmaster Feb 18 '19 at 15:35
  • @Shepmaster thanks/sorry - I'm at day job currently, I'll get to replying these messages tonight. Not sure about down votes but fair enough – opensourcegeek Feb 18 '19 at 17:17
  • Sorry @DanilaKiver I'll get to it tonight – opensourcegeek Feb 18 '19 at 17:18
  • Remember that downvotes are directed at the *question*, not the person behind the question. A downvoted question is an indicator to people that it isn't currently in a good state to be answered (among other possibilities). When the question is editied, votes can be revisited and changed. – Shepmaster Feb 18 '19 at 19:09
  • One thing that strikes me is `ELF 32-bit LSB shared object` — does your Docker container have the ability to run 32-bit applications? How and why did you choose to install a 32-bit binary? – Shepmaster Feb 18 '19 at 19:11
  • @Shepmaster docker container doesn't looks like it does at the moment, I need to check where it's gone wrong - I've used it in the past to build 32-bit stand alone binaries by using this container. Main purpose is, my target machine is 32-bit. thanks – opensourcegeek Feb 18 '19 at 23:05
  • @DanilaKiver I've updated the question with Dockerfile and strace as well - running `uname -a` shows it's 64-bit kernel (host?). Running `file /bin/bash` shows `/bin/bash: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=dc05f677715b7c5526272815f4f663d15924f737, stripped `. Thanks – opensourcegeek Feb 18 '19 at 23:10
  • 2
    @Shepmaster sorry, but the problem is a bit of different plane. Running containers with i386 environment and binaries on x86_64 kernels is perfectly OK (you can easily check it by playing with `docker run -it i386/ubuntu` on x86_64 box). However, what I see from the updated question and from local tests on my 64-bit machine is that _in 32-bit container with 32-bit linker and libraries 32-bit rustup tries to run 64-bit (!) cargo_, thus it fails. – Danila Kiver Feb 19 '19 at 02:45
  • @DanilaKiver Running x86 on x86_64 depends on the correct kernel options and having the correct libraries installed; it may be true that the original image has this support (the fact that bash is 32-bit seems to be a dead giveaway...). I will rewrite my comment to be more accurate. – Shepmaster Feb 19 '19 at 02:49
  • *Ever since I updated it* — you still haven't told us what you **changed**. What was it **before**, and what was it **after**? – Shepmaster Feb 19 '19 at 02:50
  • @DanilaKiver your findings seem like enough of an answer to me though. – Shepmaster Feb 19 '19 at 02:52
  • @Shepmaster The question is how 64-bit `cargo` got into this 32-bit env. What I see is that the installer script sees `x86_64` from `uname -m` but then recognizes that it is "i686 env on 64-bit Linux" (see `get_bitness()` function in the script). It downloads 32-bit `rustup-init`, which then suddenly decides that it runs on x86_64 (probably because of `uname` again - looks like it gets called before the `execve`), hence the 64-bit `cargo`. It seems to be a bit inconsistent behavior of `rustup` installer. The workaround is to use `setarch linux32` before the installation. – Danila Kiver Feb 19 '19 at 03:03
  • @DanilaKiver see [my (now deleted) answer](https://gist.github.com/shepmaster/688160ccecc7ac6bd28bb7761326df9c) with another workaround. Either way, you should answer. – Shepmaster Feb 19 '19 at 03:03
  • @Shepmaster I will write the detailed answer with explanations a bit later, thanks. – Danila Kiver Feb 19 '19 at 03:05
  • @DanilaKiver The shell script just downloads a Rust binary that does the real work of installing things. I'd bet that the Rust code doesn't account for a 32-on-64 situation. – Shepmaster Feb 19 '19 at 03:05
  • @Shepmaster The change was update to the rustup URL and I used to be able to do `--with-target=i686-unknown-linux-musl` which didn't work for some reason and I presumed that option is gone in new installer so I started doing this as a separate step now `rustup target add`. Could this have caused the rustup installer to behave differently? – opensourcegeek Feb 19 '19 at 07:56
  • @DanilaKiver Thanks - that explains the odd behavior. – opensourcegeek Feb 19 '19 at 07:58

1 Answers1

4

So, here is the summary of our investigations.

The base image used for build is i386/ubuntu with 32-bit environment inside, however, this image does nothing to appropriately mask the results of uname(2) calls (by having something like setarch linux32 as entrypoint), so, when running on 64-bit system (your case), any process inside the build container calling uname(2) or uname(1) sees x86_64 instead of i686. This is the root of the problem.

When you install cargo, you download and run the installation script, which detects the platform it runs on and downloads the appropriate version of rustup-init. The platform detection in this script recognizes correctly that it runs in 32-bit environment but on 64-bit kernel, so the script downloads 32-bit version of rustup-init. However, rustup-init decides that it runs on x86_64 (probably it sees x86_64 returned by uname(2), but does not perform the check for "32-bit environment on 64-bit kernel" case, like the installer script does). You can see it during the installation without -y:

Current installation options:

    default host triple: x86_64-unknown-linux-gnu
    default toolchain: stable
    modify PATH variable: yes

So, rustup installs 64-bit toolchain, and you end up with situation when calling cargo results in running 64-bit binary in 32-bit environment, so you see the error.

I still feel some sort of inconsistent behavior here, because both the installation script and rustup-init are parts of the same project and I don't really see the reason why should they detect the platform differently in the same environment (why can't rustup-init just be as smart as the installation script is?).

As @Shepmaster noticed, this is a known issue (Rustup installs 64bit compiler on a 32bit Docker instance). There are two workarounds possible:

  • force the platform for the default toolchain by passing --default-host i686-unknown-linux-gnu to the installer;
  • fool the installer by running it under setarch linux32 so that its call to uname(2) will see i686 instead of x86_64.

Personally, I would choose the first option, as it seems to be less hacky.

Danila Kiver
  • 3,418
  • 1
  • 21
  • 31