├── .cargo └── config.toml ├── .github ├── create_aio_sharun.sh └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── img ├── sharun.gif └── tree.png ├── lib4bin ├── rust-toolchain.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "x86_64-unknown-linux-musl" 3 | rustflags = ["-Z", "remap-cwd-prefix="] 4 | 5 | [unstable] 6 | build-std = ["std", "panic_abort"] 7 | build-std-features = ["panic_immediate_abort"] 8 | -------------------------------------------------------------------------------- /.github/create_aio_sharun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | ARCH="$(uname -m)" 5 | 6 | apk add bash file binutils patchelf findutils grep sed coreutils strace which wget tar gzip 7 | 8 | BINS="bash patchelf strip strace find file grep sed awk md5sum \ 9 | xargs kill rm cp ln mv sleep echo readlink chmod sort tr printenv \ 10 | cut mkdir basename dirname uname wget tail date tar gzip ls" 11 | 12 | BINS_PATHS= 13 | for bin in $BINS 14 | do BINS_PATHS="$BINS_PATHS $(which "$bin")" 15 | done 16 | 17 | export WRAPPE="$PWD/wrappe" 18 | wget "https://github.com/VHSgunzo/wrappe/releases/latest/download/wrappe-$ARCH" -O "$WRAPPE" 19 | chmod +x "$WRAPPE" 20 | 21 | SHARUN="$PWD/sharun-$ARCH" \ 22 | "$PWD/lib4bin" -k -o -c 22 -s -g $BINS_PATHS "$WRAPPE" 23 | 24 | mv sharun "sharun-$ARCH-aio" 25 | rm -f wrappe* 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - action 7 | tags: 8 | - '*' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build_and_release: 13 | name: sharun 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup toolchain 22 | uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: nightly 25 | override: true 26 | target: x86_64-unknown-linux-musl 27 | 28 | - name: Install deps 29 | run: | 30 | sudo bash -c 'apt update && apt install upx binutils qemu-user-static -y' 31 | rustup component add rust-src --toolchain nightly 32 | cargo install cross 33 | 34 | - name: Build x86_64 35 | run: | 36 | cargo clean 37 | cargo build --release 38 | mv target/x86_64-unknown-linux-musl/release/sharun sharun-x86_64 39 | cargo build --release --no-default-features 40 | mv target/x86_64-unknown-linux-musl/release/sharun sharun-x86_64-lite 41 | 42 | - name: Build aarch64 43 | run: | 44 | cargo clean 45 | cross build --release --target aarch64-unknown-linux-musl 46 | mv target/aarch64-unknown-linux-musl/release/sharun sharun-aarch64 47 | cross build --release --target aarch64-unknown-linux-musl --no-default-features 48 | mv target/aarch64-unknown-linux-musl/release/sharun sharun-aarch64-lite 49 | 50 | - name: Strip 51 | run: | 52 | (git clone https://github.com/aunali1/super-strip.git && cd super-strip 53 | make 54 | sudo mv -fv sstrip /usr/bin/) 55 | sstrip sharun-* 56 | 57 | - name: UPX 58 | run: | 59 | ls sharun-*|xargs -I {} upx -9 --best {} -o {}-upx 60 | sstrip sharun-*-upx 61 | 62 | - name: AIO sharun x86_64 63 | run: | 64 | docker run --rm -v "${{ github.workspace }}:/root" -w /root --platform=linux/amd64 alpine:latest /root/.github/create_aio_sharun.sh 65 | 66 | - name: AIO sharun aarch64 67 | run: | 68 | docker run --rm -v "${{ github.workspace }}:/root" -w /root --platform=linux/arm64 alpine:latest /root/.github/create_aio_sharun.sh 69 | 70 | - name: Release 71 | uses: softprops/action-gh-release@v1 72 | if: startsWith(github.ref, 'refs/tags/') 73 | with: 74 | files: sharun* 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | Cargo.lock 4 | test 5 | sharun* 6 | wrappe* 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sharun" 3 | version = "0.6.9" 4 | readme = "README.md" 5 | license = "MIT" 6 | repository = "https://github.com/VHSgunzo/sharun" 7 | description = "Run dynamically linked ELF binaries everywhere" 8 | edition = "2021" 9 | 10 | [profile.release] 11 | lto = true 12 | panic = "abort" 13 | opt-level = "z" 14 | strip = "symbols" 15 | debug = false 16 | codegen-units = 1 17 | 18 | [profile.dev] 19 | panic = "abort" 20 | opt-level = 0 21 | 22 | [features] 23 | default = [ 24 | "elf32", 25 | "setenv", 26 | "lib4bin", 27 | "pyinstaller" 28 | ] 29 | elf32 = [] 30 | setenv = [] 31 | lib4bin = [] 32 | pyinstaller = [] 33 | 34 | [dependencies] 35 | cfg-if = "1.0.0" 36 | goblin = "0.8.2" 37 | walkdir = "2.5.0" 38 | flate2 = "1.0.35" 39 | userland-execve = "0.2.0" 40 | include_file_compress = "0.1.3" 41 | nix = { version = "0.29.0", features = [ "fs" ] } 42 | dotenv = { git = "https://github.com/VHSgunzo/dotenv.git" } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 VHSgunzo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sharun 2 | Run dynamically linked ELF binaries everywhere (musl and glibc are supported). 3 | 4 | ![sharun](img/sharun.gif) 5 | 6 | * Its works with [userland-execve](https://github.com/io12/userland-execve-rust) by mapping the interpreter (such as ld-linux-x86-64.so.2) into memory, creating a stack for it (containing the auxiliary vector, arguments, and environment variables), and then jumping to the entry point with the new stack. 7 | * [lib4bin](https://github.com/VHSgunzo/sharun/blob/main/lib4bin) pulls out the binary file and all the libraries on which it depends, strip it and forms the `bin`, `shared/{bin,lib,lib32}` directories (see [screenshots](https://github.com/VHSgunzo/sharun?tab=readme-ov-file#screenshots)) and generate a file `shared/{lib,lib32}/lib.path` with a list of all directories that contain libraries for pass it to interpreter `--library-path`. The paths in this file are specified on a new line with a `+` at the beginning and relative to the directory in which it is located. 8 | 9 | ## Supported architectures: 10 | * aarch64 11 | * x86_64 12 | 13 | ## To get started: 14 | * **Download the latest revision** 15 | ``` 16 | git clone https://github.com/VHSgunzo/sharun.git && cd sharun 17 | ``` 18 | 19 | * **Compile a binary** 20 | ``` 21 | rustup default nightly 22 | rustup target add $(uname -m)-unknown-linux-musl 23 | rustup component add rust-src --toolchain nightly 24 | cargo build --release 25 | cp ./target/$(uname -m)-unknown-linux-musl/release/sharun . 26 | ./sharun --help 27 | ./sharun lib4bin --help 28 | ``` 29 | * Or take an already precompiled binary file from the [releases](https://github.com/VHSgunzo/sharun/releases) 30 | * You can also use all in one sharun (`sharun-$ARCH-aio`) version which contains all the necessary dependencies for `lib4bin`. See [create_aio_sharun.sh](https://github.com/VHSgunzo/sharun/blob/main/.github/create_aio_sharun.sh) 31 | 32 | ## Usage sharun: 33 | ``` 34 | [ Usage ]: sharun [OPTIONS] [EXEC ARGS]... 35 | Use lib4bin for create 'bin' and 'shared' dirs 36 | 37 | [ Arguments ]: 38 | [EXEC ARGS]... Command line arguments for execution 39 | 40 | [ Options ]: 41 | l, lib4bin [ARGS] Launch the built-in lib4bin 42 | -g, --gen-lib-path Generate a lib.path file 43 | -v, --version Print version 44 | -h, --help Print help 45 | 46 | [ Environments ]: 47 | SHARUN_WORKING_DIR=/path Specifies the path to the working directory 48 | SHARUN_ALLOW_SYS_VKICD=1 Enables breaking system vulkan/icd.d for vulkan loader 49 | SHARUN_ALLOW_LD_PRELOAD=1 Enables breaking LD_PRELOAD env variable 50 | SHARUN_PRINTENV=1 Print environment variables to stderr 51 | SHARUN_LDNAME=ld.so Specifies the name of the interpreter 52 | SHARUN_DIR Sharun directory 53 | ``` 54 | 55 | ## Usage lib4bin: 56 | ``` 57 | [ Usage ]: lib4bin [OPTIONS] /path/executable -- [STRACE MODE EXEC ARGS] 58 | 59 | [ Options ]: 60 | -d, --dst-dir '/path' Destination directory (env: DST_DIR='/path') 61 | -e, --strace-mode Use strace for get libs (env: STRACE_MODE=1) 62 | -t, --strace-time 5 Specify the time in seconds for strace mode (env: STRACE_TIME=5) 63 | -g, --gen-lib-path Generate a lib.path file (env: GEN_LIB_PATH=1) 64 | -h, --help Show this message 65 | -i, --patch-interpreter Patch INTERPRETER to a relative path (env: PATCH_INTERPRETER=1) 66 | -k, --with-hooks Pack additional files required for libraries (env: WITH_HOOKS=1) 67 | -l, --libs-only Pack only libraries without executables (env: LIBS_ONLY=1) 68 | -n, --not-one-dir Separate directories for each executable (env: ONE_DIR=0) 69 | -p, --hard-links Pack sharun and create hard links (env: HARD_LINKS=1) 70 | -q, --quiet-mode Show only errors (env: QUIET_MODE=1) 71 | -r, --patch-rpath Patch RPATH to a relative path (env: PATCH_RPATH=1) 72 | -s, --strip Strip binaries and libraries (env: STRIP=1) 73 | -v, --verbose Verbose mode (env: VERBOSE=1) 74 | -w, --with-sharun Pack sharun from PATH or env or download 75 | (env: WITH_SHARUN=1, SHARUN=/path|URL, SHARUN_URL=URL, UPX_SHARUN=1) 76 | -o, --with-wrappe Pack with wrappe from PATH or env or download 77 | (env: WITH_WRAPPE=1, WRAPPE=/path|URL, WRAPPE_URL=URL) 78 | -c, --wrappe-clvl 0-22 Specify the compression level for wrappe (env: WRAPPE_CLVL=0-22) (default: 8) 79 | -x, --wrappe-exec name Specify the name of the wrappe packaged executable (env: WRAPPE_EXEC=name) 80 | -m, --wrappe-args 'args' Specify the args for the wrappe packaged executable (env: WRAPPE_ARGS='args') 81 | -z, --wrappe-dir '/path' Specify path to the sharun dir for packing with wrappe (env: WRAPPE_DIR='/path') 82 | -u, --wrappe-no-cleanup Disable cleanup the wrappe unpack directory after exit (env: WRAPPE_CLEANUP=0) 83 | It can also be set at runtime (env: STARTPE_CLEANUP=0) 84 | -y, --with-python Pack python using uv from PATH or env or download 85 | (env: WITH_PYTHON=1, UV=/path|URL, UV_URL=URL) 86 | -pp, --python-pkg 'pkg' Specify the python package or '/path/requirements.txt' (env: PYTHON_PKG='pkg') 87 | -pv, --python-ver 3.12 Specify the python version for packing (env: PYTHON_VER=3.12) 88 | -pi, --python-pip Leave pip after install python package (env: PYTHON_LEAVE_PIP=1) 89 | -pw, --python-wheel Leave wheel after install python package (env: PYTHON_LEAVE_WHEEL=1) 90 | -ps, --python-setuptools Leave setuptools after install python package (env: PYTHON_LEAVE_SETUPTOOLS=1) 91 | ``` 92 | 93 | ## Examples: 94 | ``` 95 | # run lib4bin with the paths to the binary files that you want to make portable: 96 | ./sharun lib4bin --with-sharun --dst-dir test /bin/bash 97 | 98 | # or for correct /proc/self/exe you can use --hard-links flag: 99 | ./sharun lib4bin --hard-links --dst-dir test /bin/bash 100 | # this will create hard links from 'test/sharun' in the 'test/bin' directory 101 | 102 | # now you can move 'test' dir to other linux system and run binaries from the 'bin' dir: 103 | ./test/bin/bash --version 104 | 105 | # or specify them as an argument to 'sharun': 106 | ./test/sharun bash --version 107 | ``` 108 | ### Packing the `sharun directory` with your applications into a single executable with [wrappe](https://github.com/Systemcluster/wrappe): 109 | ``` 110 | # packing one executable file /bin/bash to the test/bash executable: 111 | ./sharun lib4bin --with-wrappe --dst-dir test /bin/bash 112 | 113 | # packing several executable files to the test/sharun multicall executable: 114 | ./sharun lib4bin --with-wrappe --dst-dir test /bin/bash /bin/env /bin/ls 115 | 116 | # packing several executable files with bash entrypoint to the test/bash executable: 117 | ./sharun lib4bin --wrappe-exec bash --dst-dir test /bin/bash /bin/env /bin/ls 118 | ``` 119 | 120 | ### Packing the `sharun directory` with your python application into a single executable with [wrappe](https://github.com/Systemcluster/wrappe): 121 | ``` 122 | # packing python to the test/sharun multicall executable: 123 | ./sharun lib4bin --with-python --with-wrappe --strip --dst-dir test 124 | 125 | # packing python with python entrypoint to the test/python executable: 126 | ./sharun lib4bin --with-python --wrappe-exec python --strip --dst-dir test 127 | 128 | # packing python 3.14 and awscli package with aws entrypoint to the test/aws executable in strace mode: 129 | ./sharun lib4bin --wrappe-exec aws --strip --with-hooks --python-ver 3.14 --python-pkg awscli --dst-dir test sharun -- aws s3 ls --no-sign-request s3://globalnightlight 130 | 131 | # packing python 3.13 and pygame package with examples.aliens entrypoint to the test/python executable in strace mode: 132 | ./sharun lib4bin --wrappe-exec python -m '-m pygame.examples.aliens' --strip --with-hooks --python-ver 3.13 --python-pkg pygame --dst-dir test sharun -- python -m pygame.examples.aliens 133 | ``` 134 | 135 | ### Packing the [PyInstaller](https://pyinstaller.org) `onedir` app into a single executable with [wrappe](https://github.com/Systemcluster/wrappe): 136 | ``` 137 | # download python script: 138 | wget https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/refs/heads/master/files/docker/systemctl3.py 139 | 140 | # Create PyInstaller onedir app: 141 | pyinstaller --name systemctl --onedir systemctl3.py 142 | 143 | # download sharun aio: 144 | wget https://github.com/VHSgunzo/sharun/releases/latest/download/sharun-$(uname -m)-aio -O ./sharun 145 | chmod +x ./sharun 146 | 147 | # packing PyInstaller onedir app with strace mode into a single portable executable: 148 | ./sharun lib4bin --with-wrappe --with-hooks --strip ./dist/systemctl/systemctl -- --help 149 | 150 | # test it: 151 | ./systemctl --help 152 | ``` 153 | 154 | ## Additional options: 155 | * You can create a hard link from `sharun` to `AppRun` and write the name of the executable file from the `bin` directory to the `.app` file for compatibility with [AppImage](https://appimage.org) `AppDir`. If the `.app` file does not exist, the `*.desktop` file will be used. 156 | 157 | * Additional env var can be specified in the `.env` file (see [dotenv](https://crates.io/crates/dotenv)). Env var can also be deleted using `unset ENV_VAR` in the end of the `.env` file. 158 | 159 | * You can preload libraries using `.preload` file. Specify the necessary libraries in it from a new line. You can use the full paths to libraries or only their names if they are located in `shared/{lib,lib32}/` 160 | This can be useful, for example, to use [ld-preload-open](https://github.com/fritzw/ld-preload-open) library to reassign paths. 161 | 162 | ## Screenshots: 163 | ![tree](img/tree.png) 164 | 165 | ## Environment variables that are set if sharun finds a directory or file: 166 | ||| 167 | |---|---| 168 | |`PATH` | `${SHARUN_DIR}/bin` | 169 | |`PYTHONDONTWRITEBYTECODE` (if $SHARUN_DIR is not writable) | `${SHARUN_DIR}/shared/$LIB/python*` | 170 | |`PERLLIB` | `${SHARUN_DIR}/shared/$LIB/perl*` | 171 | |`GCONV_PATH` | `${SHARUN_DIR}/shared/$LIB/gconv` | 172 | |`GIO_MODULE_DIR` | `${SHARUN_DIR}/shared/$LIB/gio/modules`| 173 | |`GTK_PATH`, `GTK_EXE_PREFIX` and `GTK_DATA_PREFIX` | `${SHARUN_DIR}/shared/$LIB/gtk-*`| 174 | |`QT_PLUGIN_PATH` | `${SHARUN_DIR}/shared/$LIB/qt*/plugins`| 175 | |`BABL_PATH` | `${SHARUN_DIR}/shared/$LIB/babl-*`| 176 | |`GEGL_PATH` | `${SHARUN_DIR}/shared/$LIB/gegl-*`| 177 | |`TCL_LIBRARY` | `${SHARUN_DIR}/shared/$LIB/tcl*`| 178 | |`TK_LIBRARY` | `${SHARUN_DIR}/shared/$LIB/tk*`| 179 | |`GST_PLUGIN_PATH`, `GST_PLUGIN_SYSTEM_PATH`, `GST_PLUGIN_SYSTEM_PATH_1_0`, and `GST_PLUGIN_SCANNER` | `${SHARUN_DIR}/shared/$LIB/gstreamer-*`| 180 | |`GDK_PIXBUF_MODULEDIR` and `GDK_PIXBUF_MODULE_FILE` | `${SHARUN_DIR}/shared/$LIB/gdk-pixbuf-*`| 181 | |`LIBDECOR_PLUGIN_DIR` | `${SHARUN_DIR}/shared/$LIB/libdecor/plugins-1`| 182 | |`GTK_IM_MODULE_FILE` | `${SHARUN_DIR}/shared/$LIB/gtk-*/*/immodules.cache`| 183 | |`LIBGL_DRIVERS_PATH` | `${SHARUN_DIR}/shared/$LIB/dri`| 184 | |`SPA_PLUGIN_DIR` | `${SHARUN_DIR}/shared/$LIB/spa-*`| 185 | |`PIPEWIRE_MODULE_DIR` | `${SHARUN_DIR}/shared/$LIB/pipewire-*`| 186 | |`GI_TYPELIB_PATH` | `${SHARUN_DIR}/shared/$LIB/girepository-*`| 187 | |`GBM_BACKENDS_PATH` | `${SHARUN_DIR}/shared/$LIB/gbm`| 188 | |`XTABLES_LIBDIR` | `${SHARUN_DIR}/shared/$LIB/xtables`| 189 | |`FOLKS_BACKEND_PATH` | `${SHARUN_DIR}/shared/$LIB/folks/*/backends`| 190 | ||| 191 | |---|---| 192 | |`XDG_DATA_DIRS` | `${SHARUN_DIR}/share`| 193 | |`VK_DRIVER_FILES` | `${SHARUN_DIR}/share/vulkan/icd.d`| 194 | |`__EGL_VENDOR_LIBRARY_DIRS` | `${SHARUN_DIR}/share/glvnd/egl_vendor.d`| 195 | |`XKB_CONFIG_ROOT` (if no /usr/share/X11/xkb) | `${SHARUN_DIR}/share/X11/xkb`| 196 | |`GSETTINGS_SCHEMA_DIR` | `${SHARUN_DIR}/share/glib-2.0/schemas`| 197 | |`TERMINFO` | `${SHARUN_DIR}/share/terminfo`| 198 | |`MAGIC` | `${SHARUN_DIR}/share/file/misc/magic.mgc`| 199 | |`LIBTHAI_DICTDIR` | `${SHARUN_DIR}/share/libthai/thbrk.tri`| 200 | ||| 201 | |---|---| 202 | |`FONTCONFIG_FILE` (if no /etc/fonts/fonts.conf) | `${SHARUN_DIR}/etc/fonts/fonts.conf`| 203 | |---|---| 204 | |`GIO_LAUNCH_DESKTOP` | `${SHARUN_DIR}/bin/gio-launch-desktop`| 205 | 206 | ## Projects that use sharun: 207 | * [SoarPkgs](https://github.com/pkgforge/soarpkgs) 208 | * [pelfCreator](https://github.com/xplshn/pelf/blob/pelf-ng/pelfCreator) 209 | * [AppBundleHUB](https://github.com/xplshn/AppBundleHUB) 210 | * [AnyLinux-AppImages](https://github.com/pkgforge-dev/Anylinux-AppImages) 211 | * [RMG](https://github.com/Rosalie241/RMG) 212 | * [PrusaSlicer.AppImage](https://github.com/probonopd/PrusaSlicer.AppImage) 213 | * [goverlay](https://github.com/benjamimgois/goverlay) 214 | * [ghostty-appimage](https://github.com/psadi/ghostty-appimage) 215 | * [WayVR Dashboard](https://github.com/olekolek1000/wayvr-dashboard) 216 | * [interstellar](https://github.com/jwr1/interstellar) 217 | * [LibreSprite](https://github.com/LibreSprite/LibreSprite) 218 | * [QDiskInfo](https://github.com/edisionnano/QDiskInfo) 219 | * [mangojuice](https://github.com/radiolamp/mangojuice) 220 | * [CPU-X](https://github.com/TheTumultuousUnicornOfDarkness/CPU-X) 221 | * [eden](https://git.eden-emu.dev/eden-emu/eden) 222 | 223 | ## References 224 | * [userland-execve](https://crates.io/crates/userland-execve) 225 | * https://brioche.dev/blog/portable-dynamically-linked-packages-on-linux 226 | -------------------------------------------------------------------------------- /img/sharun.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VHSgunzo/sharun/e4f7b0105d549e2200b4179a8431a82d6dff097e/img/sharun.gif -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VHSgunzo/sharun/e4f7b0105d549e2200b4179a8431a82d6dff097e/img/tree.png -------------------------------------------------------------------------------- /lib4bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | shopt -s extglob 3 | 4 | # deps: apt update && apt install bash file binutils patchelf findutils grep sed coreutils strace -y 5 | # deps: apk add bash file binutils patchelf findutils grep sed coreutils strace 6 | # deps: dnf install bash file binutils patchelf findutils grep sed coreutils strace -y 7 | # deps: pacman -Sy bash file binutils patchelf findutils grep sed coreutils strace --noconfirm 8 | # deps: xbps-install -Sy bash file binutils patchelf findutils grep sed coreutils strace 9 | 10 | RED='\033[1;91m' 11 | BLUE='\033[1;94m' 12 | GREEN='\033[1;92m' 13 | YELLOW='\033[1;33m' 14 | RESETCOLOR='\033[1;00m' 15 | 16 | ONE_DIR=${ONE_DIR:=1} 17 | DST_DIR="${DST_DIR:=.}" 18 | TMPDIR="${TMPDIR:=/tmp}" 19 | CREATE_LINKS=${CREATE_LINKS:=1} 20 | 21 | [[ -z "$PYINSTALL_DIR" && -d "$HOME/.local/share/uv/python" ]] && \ 22 | PYINSTALL_DIR="$HOME/.local/share/uv/python" 23 | PYINSTALL_DIR="${PYINSTALL_DIR:=$TMPDIR/pyinstall}" 24 | 25 | ARCH="$(uname -m)" 26 | GIT_SHARUN_RELEASE="https://github.com/VHSgunzo/sharun/releases/latest/download/sharun-${ARCH}" 27 | GIT_WRAPPE_RELEASE="https://github.com/VHSgunzo/wrappe/releases/latest/download/wrappe-${ARCH}" 28 | GIT_UV_RELEASE="https://github.com/astral-sh/uv/releases/latest/download/uv-${ARCH}-unknown-linux-musl.tar.gz" 29 | 30 | STRIP=${STRIP:=0} 31 | VERBOSE=${VERBOSE:=0} 32 | LIBS_ONLY=${LIBS_ONLY:=0} 33 | QUIET_MODE=${QUIET_MODE:=0} 34 | HARD_LINKS=${HARD_LINKS:=0} 35 | WITH_HOOKS=${WITH_HOOKS:=0} 36 | WITH_SHARUN=${WITH_SHARUN:=0} 37 | WITH_WRAPPE=${WITH_WRAPPE:=0} 38 | WRAPPE_CLVL=${WRAPPE_CLVL:=8} 39 | PATCH_RPATH=${PATCH_RPATH:=0} 40 | STRACE_MODE=${STRACE_MODE:=0} 41 | STRACE_TIME=${STRACE_TIME:=5} 42 | GEN_LIB_PATH=${GEN_LIB_PATH:=0} 43 | ANY_EXECUTABLE=${ANY_EXECUTABLE:=0} 44 | WRAPPE_CLEANUP=${WRAPPE_CLEANUP:=1} 45 | MAX_INTERP_LEN=${MAX_INTERP_LEN:=256} 46 | PATCH_INTERPRETER=${PATCH_INTERPRETER:=0} 47 | 48 | XDG_OPEN_WRAPPER='#!/bin/sh 49 | # xdg-open and gio-launch-desktop wrapper for sharun 50 | # unsets env variables that cause issues to child processes 51 | 52 | CURRENTDIR="$(readlink -f "$(dirname "$0")")" 53 | APPDIR="${APPDIR:-${SHARUN_DIR:-$(dirname "$CURRENTDIR")}}" 54 | PATH="$(echo "$PATH" | sed "s|$CURRENTDIR||g")" 55 | export PATH 56 | 57 | problematic_vars="BABL_PATH GBM_BACKENDS_PATH GCONV_PATH GDK_PIXBUF_MODULEDIR \ 58 | GDK_PIXBUF_MODULE_FILE GEGL_PATH GIO_MODULE_DIR GI_TYPELIB_PATH \ 59 | GSETTINGS_SCHEMA_DIR GST_PLUGIN_PATH GST_PLUGIN_SCANNER GST_PLUGIN_SYSTEM_PATH \ 60 | GST_PLUGIN_SYSTEM_PATH_1_0 GTK_DATA_PREFIX GTK_EXE_PREFIX GTK_IM_MODULE_FILE \ 61 | GTK_PATH LIBDECOR_PLUGIN_DIR LIBGL_DRIVERS_PATH PERLLIB PIPEWIRE_MODULE_DIR \ 62 | QT_PLUGIN_PATH SPA_PLUGIN_DIR TCL_LIBRARY TK_LIBRARY XTABLES_LIBDIR" 63 | 64 | for var in $problematic_vars; do 65 | checkvar="$(printenv "$var" 2>/dev/null)" 66 | if [ -n "$checkvar" ] && echo "$checkvar" | grep -q "$APPDIR"; then 67 | unset "$var" 68 | >&2 echo "unset $var to prevent issues" 69 | fi 70 | done 71 | 72 | if [ "$(basename "$0")" = "gio-launch-desktop" ]; then 73 | export GIO_LAUNCHED_DESKTOP_FILE_PID=$$ 74 | exec "$@" 75 | else 76 | exec xdg-open "$@" 77 | fi' 78 | 79 | usage() { 80 | echo -e "[ Usage ]: lib4bin [OPTIONS] /path/executable -- [STRACE MODE EXEC ARGS] 81 | 82 | [ Options ]: 83 | -d, --dst-dir '/path' Destination directory (env: DST_DIR='/path') 84 | -e, --strace-mode Use strace for get libs (env: STRACE_MODE=1) 85 | -t, --strace-time 5 Specify the time in seconds for strace mode (env: STRACE_TIME=5) 86 | -g, --gen-lib-path Generate a lib.path file (env: GEN_LIB_PATH=1) 87 | -h, --help Show this message 88 | -i, --patch-interpreter Patch INTERPRETER to a relative path (env: PATCH_INTERPRETER=1) 89 | -k, --with-hooks Pack additional files required for libraries (env: WITH_HOOKS=1) 90 | -l, --libs-only Pack only libraries without executables (env: LIBS_ONLY=1) 91 | -n, --not-one-dir Separate directories for each executable (env: ONE_DIR=0) 92 | -p, --hard-links Pack sharun and create hard links (env: HARD_LINKS=1) 93 | -q, --quiet-mode Show only errors (env: QUIET_MODE=1) 94 | -r, --patch-rpath Patch RPATH to a relative path (env: PATCH_RPATH=1) 95 | -s, --strip Strip binaries and libraries (env: STRIP=1) 96 | -v, --verbose Verbose mode (env: VERBOSE=1) 97 | -w, --with-sharun Pack sharun from PATH or env or download 98 | (env: WITH_SHARUN=1, SHARUN=/path|URL, SHARUN_URL=URL, UPX_SHARUN=1) 99 | -o, --with-wrappe Pack with wrappe from PATH or env or download 100 | (env: WITH_WRAPPE=1, WRAPPE=/path|URL, WRAPPE_URL=URL) 101 | -c, --wrappe-clvl 0-22 Specify the compression level for wrappe (env: WRAPPE_CLVL=0-22) (default: 8) 102 | -x, --wrappe-exec name Specify the name of the wrappe packaged executable (env: WRAPPE_EXEC=name) 103 | -m, --wrappe-args 'args' Specify the args for the wrappe packaged executable (env: WRAPPE_ARGS='args') 104 | -z, --wrappe-dir '/path' Specify path to the sharun dir for packing with wrappe (env: WRAPPE_DIR='/path') 105 | -u, --wrappe-no-cleanup Disable cleanup the wrappe unpack directory after exit (env: WRAPPE_CLEANUP=0) 106 | It can also be set at runtime (env: STARTPE_CLEANUP=0) 107 | -y, --with-python Pack python using uv from PATH or env or download 108 | (env: WITH_PYTHON=1, UV=/path|URL, UV_URL=URL) 109 | -pp, --python-pkg 'pkg' Specify the python package or '/path/requirements.txt' (env: PYTHON_PKG='pkg') 110 | -pv, --python-ver 3.12 Specify the python version for packing (env: PYTHON_VER=3.12) 111 | -pi, --python-pip Leave pip after install python package (env: PYTHON_LEAVE_PIP=1) 112 | -pw, --python-wheel Leave wheel after install python package (env: PYTHON_LEAVE_WHEEL=1) 113 | -ps, --python-setuptools Leave setuptools after install python package (env: PYTHON_LEAVE_SETUPTOOLS=1)" 114 | exit 1 115 | } 116 | 117 | error_msg() { 118 | echo -e "${RED}[ ERROR ][$(date +"%Y.%m.%d %T")]: $@ $RESETCOLOR" 1>&2 119 | return 1 120 | } 121 | 122 | info_msg() { 123 | if [ "$QUIET_MODE" != 1 ] 124 | then echo -e "${GREEN}[ INFO ][$(date +"%Y.%m.%d %T")]: $@ $RESETCOLOR" 1>&2 125 | fi 126 | } 127 | 128 | hook_msg() { info_msg "$YELLOW[ HOOK ]:$GREEN $@" ; } 129 | 130 | skip_msg() { 131 | if [ "$QUIET_MODE" != 1 ] 132 | then echo -e "${YELLOW}[ SKIPPED ][$(date +"%Y.%m.%d %T")]: $@ $RESETCOLOR" 1>&2 133 | fi 134 | } 135 | 136 | which_exe() { command -v "$@" ; } 137 | 138 | is_exe_exist() { which_exe "$@" &>/dev/null ; } 139 | 140 | check_deps() { 141 | local ret=0 142 | local binaries=(file patchelf find grep sed) 143 | [ "$STRIP" != 1 ]||binaries+=(strip) 144 | [ "$STRACE_MODE" != 1 ]||binaries+=(strace) 145 | for bin in "${binaries[@]}" 146 | do 147 | if ! is_exe_exist $bin 148 | then 149 | error_msg "$BLUE[$bin]$YELLOW not found!" 150 | ret=1 151 | fi 152 | done 153 | if [ "$ret" != 0 ] 154 | then 155 | info_msg "You need to install ${BLUE}lib4bin${GREEN} dependencies: ${BLUE}file binutils patchelf findutils grep sed coreutils strace" 156 | exit 1 157 | fi 158 | } 159 | 160 | try_strip() { 161 | if [[ "$STRIP" == 1 && ! "$FILE_INFO" =~ 'no section header' ]] 162 | then 163 | info_msg "$YELLOW[ STRIP ]: $BLUE[$1]" 164 | strip -s -R .comment --strip-unneeded "$1" 165 | fi 166 | } 167 | 168 | try_remove_fullrpath() { 169 | if [ "$PATCH_RPATH" != 1 ] 170 | then 171 | local rpath="$(print_rpath "$1")" 172 | if grep -qE '^/|:/'<<<"$rpath" 173 | then 174 | info_msg "$YELLOW[ REMOVE RPATH ]: $BLUE[$1 -> $rpath]" 175 | patchelf $pvarg --remove-rpath "$1"||exit 1 176 | fi 177 | fi 178 | } 179 | 180 | try_set_rpath() { 181 | info_msg "$YELLOW[ SET RPATH ]: $BLUE[$1 -> $rpath]" 182 | if [ -n "$2" ] 183 | then local rpath="$2" 184 | else local rpath='$ORIGIN/../lib:$ORIGIN/../lib32' 185 | fi 186 | patchelf $pvarg --remove-rpath "$1"||exit 1 187 | patchelf $pvarg --set-rpath "$rpath" --force-rpath "$1"||exit 1 188 | patchelf $pvarg --no-default-lib "$1"||exit 1 189 | } 190 | 191 | get_md5sum() { md5sum<<<"$1"|awk '{print$1}'; } 192 | 193 | print_needed() { patchelf --print-needed "$1" 2>/dev/null ; } 194 | 195 | print_rpath() { patchelf --print-rpath "$1" 2>/dev/null ; } 196 | 197 | ldd_libs() { 198 | ldd "$1" 2>/dev/null|grep -v 'error while loading shared libraries'|\ 199 | grep '/lib'|cut -d'>' -f2|sed 's| (.*)||g'|sed 's|^[[:space:]]*||g'|sort -u 200 | } 201 | 202 | get_libs() { 203 | unset libs libs4libs 204 | local libs libs4libs needed_libs 205 | if [ "$STRACE_MODE" == 1 ] && ! is_so && \ 206 | ([[ -z "$STRACE_EXE" || "$STRACE_EXE" == "$1" ]]||\ 207 | [[ "$STRACE_EXE" == 'sharun' && "$1" == "$dst_dir/sharun" && "$binary_name" == 'sharun' ]]) 208 | then 209 | local libs_file="${TMPDIR}/libs.$$" 210 | [[ "$(strace --help)" =~ always-show-pid ]] && \ 211 | STRACE_ARGS='--always-show-pid'||unset STRACE_ARGS 212 | info_msg "$YELLOW[ STRACE ]: $BLUE[$1 ${STRACE_CMD_ARGS[@]}] ${GREEN}..." 213 | strace -f -e trace=openat,open $STRACE_ARGS -o "$libs_file" "$(readlink -f "$1")" "${STRACE_CMD_ARGS[@]}" 1>&2 & 214 | sleep $STRACE_TIME 215 | local pids="$(cut -d ' ' -f1<"$libs_file"|sort -u)" 216 | kill $pids 2>/dev/null 217 | libs="$(\ 218 | sed '/nvidia/d;/libcuda/d;/ENOENT/d;/unfinished/d;/lib-dynload/d;/.wrappe/d;/_internal/d'<"$libs_file"|\ 219 | grep -Eo '".*lib.*\.so(\.[0-9].*)?"'|sed -u 's|"||g')\n" 220 | rm -f "$libs_file" 221 | fi 222 | [ -n "$2" ] && needed_libs="$2"||\ 223 | needed_libs="$(print_needed "$1")" 224 | libs+="$(([ -z "$needed_libs" ]||\ 225 | grep -E "$(tr '\n' '|'<<<"$needed_libs"|sed 's|\||$\||g')libpthread.so.[0-9]|libdl.so.[0-9]|librt.so.[0-9]|libm.so.[0-9]$"<<<"$ALL_LIBS" ; \ 226 | ldd_libs "$1")|sort -u)" 227 | libs="$(echo -e "$libs"|sort -u)" 228 | [ -n "$IS_ELF32" ] && \ 229 | libs="$(grep -vE '/lib/|/lib64/|/.*64-linux-gnu/'<<<"$libs")"||\ 230 | libs="$(grep -vE '/lib32/|/i386-linux-gnu/|/arm-linux-gnu/'<<<"$libs")" 231 | OLD_IFS="$IFS" 232 | IFS=$'\n' 233 | for lib in $libs 234 | do libs4libs="$(echo -e "$(ldd_libs "$lib")\n$libs4libs")" 235 | done 236 | IFS="$OLD_IFS" 237 | echo -e "$libs\n$libs4libs"|sort -u|sed '/^$/d' 238 | } 239 | 240 | repath_needed_libs() { 241 | local needed_libs 242 | [ -n "$2" ] && needed_libs="$2"||\ 243 | needed_libs="$(print_needed "$1")" 244 | local patch_needed_libs="$(grep '^/'<<<"$needed_libs")" 245 | if [ -n "$patch_needed_libs" ] 246 | then 247 | for lib in $patch_needed_libs 248 | do 249 | local relib="$(basename "$lib")" 250 | info_msg "$YELLOW[ REPATH ]: $BLUE[$lib -> $relib]" 251 | patchelf $pvarg --replace-needed "$lib" "$relib" "$1"||exit 1 252 | done 253 | fi 254 | } 255 | 256 | try_mkdir() { 257 | if [ ! -d "$1" ] 258 | then mkdir $varg -p "$1"||exit 1 259 | fi 260 | } 261 | 262 | try_ln() { 263 | if [ ! -L "$2" ] 264 | then 265 | [ "$VERBOSE" == 1 ] && \ 266 | echo -n "ln: " 267 | ln $varg -sf "$1" "$2"||exit 1 268 | fi 269 | } 270 | 271 | try_cp_exe() { 272 | if [ ! -f "$2" ] 273 | then 274 | [ "$VERBOSE" == 1 ] && \ 275 | echo -n "cp: " 276 | cp $varg -f "$1" "$2"||exit 1 277 | chmod $varg 755 "$2"||exit 1 278 | fi 279 | } 280 | 281 | try_cp() { 282 | [ "$VERBOSE" == 1 ] && \ 283 | echo -n "cp: " 284 | cp $varg -rf "$@"||exit 1 285 | } 286 | 287 | try_mv() { 288 | [ "$VERBOSE" == 1 ] && \ 289 | echo -n "mv: " 290 | mv $varg -f "$@"||exit 1 291 | } 292 | 293 | try_cd() { 294 | [ "$VERBOSE" == 1 ] && \ 295 | echo "cd: '$1'" 296 | cd "$1"||exit 1 297 | } 298 | 299 | get_relative_path() { 300 | local start_dir="$1" 301 | local target_dir="$2" 302 | start_dir=$(readlink -f "$start_dir") 303 | target_dir=$(readlink -f "$target_dir") 304 | local common_path='' 305 | local i=0 306 | while [ $i -lt ${#start_dir} ] && \ 307 | [ $i -lt ${#target_dir} ] && \ 308 | [ "${start_dir:$i:1}" = "${target_dir:$i:1}" ] 309 | do 310 | common_path+="${start_dir:$i:1}" 311 | i=$((i+1)) 312 | done 313 | if [ -n "$common_path" ] && \ 314 | [ "${common_path: -1}" != "/" ] 315 | then common_path=$(dirname "$common_path") 316 | fi 317 | local up_path='' 318 | local remaining_start="${start_dir#"$common_path"}" 319 | if [ -n "$remaining_start" ] 320 | then 321 | for dir in $(echo "$remaining_start" | tr "/" "\n" | grep -v "^$") 322 | do up_path+="../" 323 | done 324 | fi 325 | local down_path="${target_dir#"$common_path"}" 326 | local relative_path="$up_path${down_path#/}" 327 | echo "$relative_path" 328 | } 329 | 330 | find_exe() { 331 | [ -n "$1" ] && local path="$1" && shift 332 | find "$path" -maxdepth 1 -not -type d -executable "$@" 2>/dev/null|sort 333 | } 334 | 335 | find_so() { find "$@" -name '*.so' -o -name '*.so.*' 2>/dev/null ; } 336 | 337 | is_so() { [ -n "$IS_SO" ] && [[ "${binary_name,,}" =~ .*(\.so$|\.so\..*) || "${binary_real_name,,}" =~ .*(\.so$|\.so\..*) ]] ; } 338 | 339 | check_url_stat_code() { 340 | set -o pipefail 341 | if is_exe_exist curl 342 | then curl -sL -o /dev/null -I -w "%{http_code}" "$1" 2>/dev/null 343 | elif is_exe_exist wget 344 | then wget --no-check-certificate --server-response \ 345 | --spider "$1"|& awk '/^ HTTP/{print$2}'|tail -1 346 | else 347 | error_msg "Failed to check URL $BLUE[$1]: ${RED}curl and wget not found!" 348 | return 1 349 | fi 350 | } 351 | 352 | is_url() { 353 | [ -z "$1" ] && \ 354 | return 1 355 | if [ -n "$2" ] 356 | then [ "$(check_url_stat_code "$1")" == "$2" ] 357 | else [ "$(check_url_stat_code "$1")" == "200" ] 358 | fi 359 | } 360 | 361 | find_py_unneeded() { 362 | OLD_IFS="$IFS" 363 | IFS=$'\n' 364 | PY_UNNEEDED_LIST=($(find "$1" -type f -iname '*.cmd' \ 365 | -o -iname '*.md' -o -name 'LICENSE*' -o -iname '*.ps1' \ 366 | -o -iname '*.bat' -o -iname '*.exe' -o -iname '*.whl')) 367 | IFS="$OLD_IFS" 368 | } 369 | 370 | is_net_conn() { 371 | if is_exe_exist nc 372 | then nc -zw1 github.com 443 &>/dev/null 373 | elif is_exe_exist curl 374 | then curl -Ifs github.com &>/dev/null 375 | elif is_exe_exist wget 376 | then wget -q --spider github.com &>/dev/null 377 | elif is_exe_exist ping 378 | then ping -c 2 github.com &>/dev/null 379 | else 380 | error_msg "Failed to check internet connection: nc, curl, wget and ping not found!" 381 | return 1 382 | fi 383 | } 384 | 385 | try_dl() { 386 | if is_net_conn 387 | then 388 | if [ -n "$1" ] 389 | then 390 | URL="$1" 391 | if [ -n "$2" ] 392 | then 393 | if [ -d "$2" ] 394 | then 395 | FILEDIR="$2" 396 | FILENAME="$(basename "$1")" 397 | else 398 | FILEDIR="$(dirname "$2")" 399 | FILENAME="$(basename "$2")" 400 | fi 401 | else 402 | FILEDIR="." 403 | FILENAME="$(basename "$1")" 404 | fi 405 | if is_url "$URL" 406 | then 407 | WGET_ARGS=(-q --no-check-certificate -t 3 -T 5 -w 0.5 "$URL" -O "$FILEDIR/$FILENAME") 408 | try_mkdir "$FILEDIR" 409 | if [ "$NO_ARIA2C" != 1 ] && is_exe_exist aria2c 410 | then 411 | aria2c --no-conf -R -x 13 -s 13 --allow-overwrite -d "$FILEDIR" -o "$FILENAME" "$URL" 412 | elif is_exe_exist curl 413 | then 414 | curl -R --progress-bar --insecure --fail -L "$URL" -o "$FILEDIR/$FILENAME" 415 | elif is_exe_exist wget2 416 | then 417 | wget2 --force-progress "${WGET_ARGS[@]}" 418 | elif is_exe_exist wget 419 | then 420 | wget --show-progress "${WGET_ARGS[@]}" 421 | else 422 | error_msg "Downloader not found!" 423 | fi 424 | else 425 | error_msg "$FILENAME not found in $(echo "$URL"|awk -F/ '{print$3"/"$4}')" 426 | fi 427 | else 428 | error_msg "Specify download URL!" 429 | fi 430 | else 431 | error_msg "There is no internet connection?" 432 | fi 433 | return $? 434 | } 435 | 436 | parse_arg_options() { 437 | [ "$1" == 'dash' ] && shift && \ 438 | local _ALLOW_DASH=1||unset _ALLOW_DASH 439 | if [ -n "$3" ] && [[ "$_ALLOW_DASH" == 1 || "$3" != -* ]] 440 | then eval "${1}='$3'" 441 | else 442 | error_msg "${YELLOW}Option ${BLUE}$2 ${YELLOW}requires a non-empty argument!\n" 443 | usage 444 | fi 445 | } 446 | 447 | while [[ "$#" -gt 0 ]]; do 448 | case $1 in 449 | -h|--help) usage ;; 450 | -s|--strip) STRIP=1; shift ;; 451 | -v|--verbose) VERBOSE=1; shift ;; 452 | -n|--not-one-dir) ONE_DIR=0; shift ;; 453 | -l|--libs-only) LIBS_ONLY=1; shift ;; 454 | -k|--with-hooks) WITH_HOOKS=1; shift ;; 455 | -q|--quiet-mode) QUIET_MODE=1; shift ;; 456 | -p|--hard-links) HARD_LINKS=1; shift ;; 457 | -e|--strace-mode) STRACE_MODE=1; shift ;; 458 | -w|--with-sharun) WITH_SHARUN=1; shift ;; 459 | -o|--with-wrappe) WITH_WRAPPE=1; shift ;; 460 | -y|--with-python) WITH_PYTHON=1; shift ;; 461 | -r|--patch-rpath) PATCH_RPATH=1; shift ;; 462 | -pi|--python-pip) PYTHON_LEAVE_PIP=1; shift ;; 463 | -pw|--python-wheel) PYTHON_LEAVE_WHEEL=1; shift ;; 464 | -ps|--python-setuptools) PYTHON_LEAVE_SETUPTOOLS=1; shift ;; 465 | -g|--gen-lib-path) GEN_LIB_PATH=1; shift ;; 466 | -u|--wrappe-no-cleanup) WRAPPE_CLEANUP=0; shift ;; 467 | -i|--patch-interpreter) PATCH_INTERPRETER=1; shift ;; 468 | -d|--dst-dir) parse_arg_options DST_DIR "$1" "$2"; shift 2 ;; 469 | -pv|--python-ver) parse_arg_options PYTHON_VER "$1" "$2"; shift 2 ;; 470 | -pp|--python-pkg) parse_arg_options PYTHON_PKG "$1" "$2"; shift 2 ;; 471 | -t|--strace-time) parse_arg_options STRACE_TIME "$1" "$2"; shift 2 ;; 472 | -x|--wrappe-exec) parse_arg_options WRAPPE_EXEC "$1" "$2"; shift 2 ;; 473 | -c|--wrappe-clvl) parse_arg_options WRAPPE_CLVL "$1" "$2"; shift 2 ;; 474 | -z|--wrappe-dir) parse_arg_options WRAPPE_DIR "$1" "$2"; shift 2 ;; 475 | -m|--wrappe-args) parse_arg_options dash WRAPPE_ARGS "$1" "$2"; shift 2 ;; 476 | -*) error_msg "Unknown parameter: ${BLUE}$1\n"; usage ;; 477 | *) break ;; 478 | esac 479 | done 480 | 481 | if [ "$VERBOSE" == 1 ] 482 | then 483 | varg='-v' 484 | pvarg='--debug' 485 | else 486 | unset varg pvarg 487 | fi 488 | 489 | if [[ -n "$WRAPPE_DIR" && ! -x "$WRAPPE_DIR/sharun" ]] 490 | then error_msg 'sharun was not found in specified wrappe directory!'; exit 1 491 | fi 492 | 493 | [[ "$STRACE_TIME" =~ ^[0-9]+$ ]]||\ 494 | STRACE_TIME=5 495 | 496 | [[ -n "$WRAPPE_EXEC" || -n "$WRAPPE_ARGS" || -d "$WRAPPE_DIR" ]] && \ 497 | WITH_WRAPPE=1 498 | (( WRAPPE_CLVL >= 0 && WRAPPE_CLVL <= 22 ))||\ 499 | WRAPPE_CLVL=8 500 | [ -d "$WRAPPE_DIR" ] && \ 501 | WRAPPE_DIR="$(readlink -f "$WRAPPE_DIR")" 502 | [ "$WITH_WRAPPE" == 1 ] && \ 503 | WITH_SHARUN=1 GEN_LIB_PATH=1 504 | 505 | [[ -n "$PYTHON_PKG" ]] && WITH_PYTHON=1 506 | [[ "$PYTHON_VER" =~ ^[0-9]+\.[0-9]+$ ]] && \ 507 | WITH_PYTHON=1||unset PYTHON_VER 508 | 509 | if [ "$WITH_PYTHON" == 1 ] 510 | then 511 | ONE_DIR=1 512 | WITH_SHARUN=1 513 | TMP_UV="${TMPDIR}/uv-${ARCH}-unknown-linux-musl/uv" 514 | UV="${UV:="$(readlink -f "$(which_exe uv)")"}" 515 | UV="${UV:="$TMP_UV"}" 516 | if [ ! -x "$UV" ] 517 | then 518 | if [[ "${UV,,}" =~ ^http ]] 519 | then 520 | UV_URL="$UV" 521 | UV="$TMP_UV" 522 | fi 523 | UV_URL="${UV_URL:=$GIT_UV_RELEASE}" 524 | info_msg "Downloading uv -> '$UV'..." 525 | info_msg "$UV_URL" 526 | if NO_ARIA2C=1 try_dl "$UV_URL" '/dev/stdout'|tar -zxvf- -C "$TMPDIR" 527 | then chmod $varg +x "$UV" 528 | else 529 | error_msg "Failed to download uv!" 530 | exit 1 531 | fi 532 | fi 533 | if [ ! -x "$UV" ] 534 | then 535 | error_msg "uv not found!" 536 | exit 1 537 | fi 538 | fi 539 | 540 | BINARY_LIST=() 541 | unset STRACE_EXE 542 | declare -A DST_DIRS 543 | [ -n "$DST_DIR" ] && \ 544 | DST_DIR="$(readlink -f "$DST_DIR")" 545 | if [[ ! -d "$WRAPPE_DIR" || "$WITH_PYTHON" == 1 ]] 546 | then 547 | if [ "$2" == '--' ] 548 | then 549 | STRACE_MODE=1 550 | STRACE_EXE="$1"; shift 2 551 | STRACE_CMD_ARGS=("$@"); shift "${#@}" 552 | fi 553 | 554 | check_deps 555 | 556 | if [ "$WITH_PYTHON" == 1 ] 557 | then 558 | PYTMP_DIR="$DST_DIR/pytmp-$$" 559 | info_msg "$YELLOW[ PYTHON INSTALL ]: $BLUE[$PYINSTALL_DIR] ${GREEN}..." 560 | "$UV" python install --install-dir "$PYINSTALL_DIR" $PYTHON_VER||exit 1 561 | CPYTHON_VER="$(ls -r1 "$PYINSTALL_DIR"|grep -m1 "cpython-${PYTHON_VER}.*-linux-${ARCH}.*")" 562 | CPYTHON_DIR="$PYINSTALL_DIR/$CPYTHON_VER" 563 | if [[ -n "$CPYTHON_VER" && -d "$CPYTHON_DIR" ]] 564 | then 565 | info_msg "$YELLOW[ PYTHON VERSION ]: $BLUE[$CPYTHON_VER]" 566 | try_mkdir "$PYTMP_DIR" 567 | try_cp -T "$PYINSTALL_DIR/$CPYTHON_VER" "$PYTMP_DIR" 568 | find_py_unneeded "$PYTMP_DIR/" 569 | rm -rf "$PYTMP_DIR"/{.lock,share} "$PYTMP_DIR/lib"/!(pkgconfig|*python*) "${PY_UNNEEDED_LIST[@]}" 570 | if [ -n "$PYTHON_PKG" ] 571 | then 572 | PYTHON_PKGS=(pip setuptools wheel) 573 | info_msg "$YELLOW[ PIP INSTALL ]: $BLUE[$PYTHON_PKG -> $PYTMP_DIR] ${GREEN}..." 574 | PYTHON_PKG_NAME="$(basename "$PYTHON_PKG")" 575 | [[ -f "$PYTHON_PKG" && "${PYTHON_PKG_NAME,,}" == 'requirements.txt' ]] && \ 576 | PYTHON_PKGS+=(-r "$PYTHON_PKG")||PYTHON_PKGS+=("$PYTHON_PKG") 577 | "$UV" pip install --prefix "$PYTMP_DIR" --compile-bytecode --refresh --upgrade \ 578 | --python "$PYTMP_DIR/bin/python" --link-mode=copy --prerelease=allow \ 579 | "${PYTHON_PKGS[@]}"||exit 1 580 | find_py_unneeded "$PYTMP_DIR/" 581 | PY_UNINSTALL_LIST=() 582 | if [ "$PYTHON_LEAVE_PIP" != 1 ] 583 | then 584 | PY_UNINSTALL_LIST+=(pip) 585 | PY_UNNEEDED_LIST+=( 586 | "$PYTMP_DIR/lib/pkgconfig" 587 | "$PYTMP_DIR"/{.lock,include} 588 | "$PYTMP_DIR"/lib/python*/ensurepip 589 | "$PYTMP_DIR/bin"/{pip,python*-config}* 590 | "$PYTMP_DIR/lib"/python*/config-*-linux* 591 | "$PYTMP_DIR"/lib/python*/site-packages/{pip,pip-*} 592 | ) 593 | 594 | fi 595 | [ "$PYTHON_LEAVE_WHEEL" == 1 ]||PY_UNINSTALL_LIST+=(wheel) 596 | [ "$PYTHON_LEAVE_SETUPTOOLS" == 1 ]||PY_UNINSTALL_LIST+=(setuptools) 597 | if [ -n "$PY_UNINSTALL_LIST" ] 598 | then 599 | "$UV" pip uninstall --prefix "$PYTMP_DIR" --python "$PYTMP_DIR/bin/python" \ 600 | "${PY_UNINSTALL_LIST[@]}"||exit 1 601 | fi 602 | rm -rf "$PYTMP_DIR/share/man" "${PY_UNNEEDED_LIST[@]}" 603 | fi 604 | else 605 | error_msg "python $CPYTHON_VER not found!" 606 | exit 1 607 | fi 608 | unset PY_UNNEEDED_LIST 609 | [ "$WITH_WRAPPE" == 1 ] && \ 610 | dst_dir="$DST_DIR/wrappe-$$"||\ 611 | dst_dir="$DST_DIR" 612 | try_mkdir "$dst_dir" 613 | try_cp -T "$PYTMP_DIR" "$dst_dir" 614 | rm -rf "$PYTMP_DIR" 615 | OLD_IFS="$IFS" 616 | IFS=$'\n' 617 | for script in $(grep -m1 "^'''exec.*python" -lr "$dst_dir/bin") 618 | do sed -i '1s|.*|#!/usr/bin/env python|;2,3d' "$script" 619 | done 620 | BINARY_LIST+=( 621 | $(find_exe "$dst_dir/bin/") 622 | #$(find_so "$dst_dir") 623 | ) 624 | IFS="$OLD_IFS" 625 | fi 626 | 627 | if [ -n "$STRACE_EXE" ] 628 | then BINARY_LIST+=("$STRACE_EXE") 629 | elif [ -n "$1" ] 630 | then BINARY_LIST+=("$@") 631 | else 632 | if [ -z "$BINARY_LIST" ] 633 | then 634 | error_msg "Specify the executable or shared object!\n" 635 | usage 636 | fi 637 | fi 638 | 639 | ALL_LIBS="$(find_so \ 640 | /usr/lib /usr/libexec /usr/lib64 \ 641 | /usr/lib32 /lib /lib64 /lib32 \ 642 | |sort -u \ 643 | )" 644 | 645 | binary_number=1 646 | declare -A BINARIES 647 | declare -A LIBRARIES 648 | for binary in "${BINARY_LIST[@]}" 649 | do 650 | unset binary_real_name IS_PYINSTELF 651 | if [ -L "$binary" ] 652 | then 653 | binary_src_pth="$(readlink -f "$binary")" 654 | binary_real_name="$(basename "$binary_src_pth")" 655 | else 656 | binary_src_pth="$binary" 657 | fi 658 | if [[ "${BINARIES["$binary"]}" != 1 ]] 659 | then 660 | binary_name="$(basename "$binary")" 661 | binary_readlink_name="$(basename "$(readlink "$binary")")" 662 | if [ "$WITH_PYTHON" != 1 ] 663 | then 664 | if [ "$ONE_DIR" == 1 ] 665 | then 666 | [ "$WITH_WRAPPE" == 1 ] && \ 667 | dst_dir="$DST_DIR/wrappe-$$"||\ 668 | dst_dir="$DST_DIR" 669 | else 670 | [ "$WITH_WRAPPE" == 1 ] && \ 671 | dst_dir="$DST_DIR/${binary_name}/wrappe-$$"||\ 672 | dst_dir="$DST_DIR/${binary_name}" 673 | fi 674 | fi 675 | if [ "$binary" == 'sharun' ] 676 | then binary_src_pth="$dst_dir/sharun" 677 | fi 678 | dst_dir_pth="${dst_dir}/shared" 679 | sharun_bin_dir_pth="${dst_dir}/bin" 680 | [[ -f "$dst_dir_pth" || -L "$dst_dir_pth" ]] && \ 681 | dst_dir_pth="${dst_dir_pth}.dir" 682 | DST_DIRS["$dst_dir_pth"]= 683 | bin_dir_pth="${dst_dir_pth}/bin" 684 | FILE_INFO="$(file "$binary_src_pth" 2>/dev/null|cut -d':' -f2-)" 685 | IS_ELF="$(grep -o 'ELF'<<<"$FILE_INFO")" 686 | IS_STATIC="$(grep -o 'static'<<<"$FILE_INFO")" 687 | IS_SCRIPT="$(grep -o 'script'<<<"$FILE_INFO")" 688 | IS_ELF32="$(grep -o 'ELF 32-bit'<<<"$FILE_INFO")" 689 | IS_SO="$(grep -o 'shared object'<<<"$FILE_INFO")" 690 | IS_EXECUTABLE="$(grep -o 'executable'<<<"$FILE_INFO")" 691 | IS_EXECUTABLE="${IS_EXECUTABLE:=$(([ -n "$IS_SO" ] && ! is_so) && echo executable)}" 692 | IS_SHARUN="$(find "$binary_src_pth" -xdev -samefile "${dst_dir}/sharun" 2>/dev/null)" 693 | info_msg "$YELLOW[ $binary_number ]: $BLUE[$binary_name] ${GREEN}..." 694 | if [ "$HARD_LINKS" == 1 ] && [[ -n "$IS_SCRIPT" || -n "$IS_STATIC" ]] 695 | then 696 | hard_links=0 697 | with_sharun=1 698 | fi 699 | hard_links=${HARD_LINKS:=0} 700 | with_sharun=${WITH_SHARUN:=0} 701 | create_links=${CREATE_LINKS:=1} 702 | patch_interpreter=${PATCH_INTERPRETER:=0} 703 | if ([ -n "$IS_EXECUTABLE" ] || is_so) 704 | then 705 | needed_libs="$(print_needed "$binary_src_pth")" 706 | LIBS="$(get_libs "$binary_src_pth" "$needed_libs")" 707 | is_so && LIBS="$(echo -e "$LIBS\n$binary"|sort -u|sed '/^$/d')" 708 | if [[ -n "$LIBS" && -z "$IS_SCRIPT" && -z "$IS_STATIC" ]] 709 | then 710 | INTERPRETER="$(basename "$(grep -Em1 'ld-(linux|musl).*\.so'<<<"$LIBS"|cut -d'=' -f1|sed 's|\t||')")" 711 | [[ "$create_links" == 1 && "$hard_links" == 1 && ! -x "${dst_dir}/sharun" ]] && \ 712 | with_sharun=1 713 | else 714 | create_links=0 715 | bin_dir_pth="$sharun_bin_dir_pth" 716 | fi 717 | if [ "$LIBS_ONLY" != 1 ] && ! is_so && [[ "$binary_name" != 'sharun' && \ 718 | "$binary_readlink_name" != 'sharun' ]] 719 | then 720 | if [[ "$binary_real_name" != 'sharun' && -z "$IS_SHARUN" ]] 721 | then 722 | if [[ "$with_sharun" == 1 && ! -x "${dst_dir}/sharun" ]] 723 | then 724 | TMP_SHARUN="${TMPDIR}/sharun-${ARCH}$([ "$UPX_SHARUN" != 1 ]||echo -upx)" 725 | SHARUN="${SHARUN:="$(readlink -f "$(which_exe sharun)")"}" 726 | SHARUN="${SHARUN:="$TMP_SHARUN"}" 727 | if [ ! -x "$SHARUN" ] 728 | then 729 | if [[ "${SHARUN,,}" =~ ^http ]] 730 | then 731 | SHARUN_URL="$SHARUN" 732 | SHARUN="$TMP_SHARUN" 733 | fi 734 | SHARUN_URL="${SHARUN_URL:=${GIT_SHARUN_RELEASE}$([ "$UPX_SHARUN" != 1 ]||echo -upx)}" 735 | info_msg "Downloading sharun -> '$SHARUN'..." 736 | info_msg "$SHARUN_URL" 737 | if try_dl "$SHARUN_URL" "$SHARUN" 738 | then chmod $varg +x "$SHARUN" 739 | else 740 | error_msg "Failed to download sharun!" 741 | exit 1 742 | fi 743 | fi 744 | if [ -x "$SHARUN" ] 745 | then 746 | try_mkdir "$dst_dir" 747 | try_cp_exe "$SHARUN" "${dst_dir}/sharun" 748 | else 749 | error_msg "sharun not found!" 750 | exit 1 751 | fi 752 | fi 753 | try_mkdir "$bin_dir_pth" 754 | [ -n "$binary_real_name" ] && \ 755 | binary_dst_pth="$bin_dir_pth/$binary_real_name"||\ 756 | binary_dst_pth="$bin_dir_pth/$binary_name" 757 | try_cp_exe "$binary_src_pth" "$binary_dst_pth" 758 | if [ -n "$IS_SCRIPT" ] 759 | then 760 | for intep in python bash sh ash zsh fish dash perl ruby go node 761 | do 762 | if grep -qo "^#!.*bin/$intep" "$binary_dst_pth" 763 | then 764 | sed -i "1s|^#!.*bin/$intep|#!/usr/bin/env $intep|" "$binary_dst_pth" 765 | break 766 | fi 767 | done 768 | fi 769 | if [[ "${BINARIES["$binary_dst_pth"]}" != 1 ]] 770 | then 771 | if [ -n "$IS_ELF" ] 772 | then try_strip "$binary_dst_pth" 773 | fi 774 | if [[ -n "$LIBS" && -z "$IS_SCRIPT" && -z "$IS_STATIC" ]] 775 | then 776 | repath_needed_libs "$binary_dst_pth" "$needed_libs" 777 | try_remove_fullrpath "$binary_dst_pth" 778 | IS_PYINSTELF="$(grep -aom1 'PyInstaller' "$binary_src_pth")" 779 | if [ -n "$IS_PYINSTELF" ] 780 | then 781 | pyinternal_dir="$(readlink -f "$(dirname "$binary_src_pth")/_internal")" 782 | if [ -d "$pyinternal_dir" ] 783 | then 784 | rm -rf "$bin_dir_pth/_internal" 785 | try_cp -T "$pyinternal_dir" "$bin_dir_pth/_internal" 786 | if [ "$dst_dir" == "$(dirname "$pyinternal_dir")" ] 787 | then rm -rf "$pyinternal_dir" "$binary_src_pth" 788 | fi 789 | else patch_interpreter=1 790 | fi 791 | export PATCH_RPATH_$(get_md5sum "${dst_dir_pth}")=1 792 | fi 793 | fi 794 | BINARIES["$binary_dst_pth"]=1 795 | fi 796 | else 797 | bin_dir_pth="${dst_dir_pth}/bin" 798 | binary_real_name="$binary_readlink_name" 799 | fi 800 | if [[ -n "$binary_real_name" && "$binary_name" != "$binary_real_name" ]] 801 | then 802 | (try_cd "$bin_dir_pth" 803 | try_ln "$binary_real_name" "$binary_name")||exit 1 804 | fi 805 | if [ "$create_links" == 1 ]||[[ "$binary_name" != 'sharun' && \ 806 | "$binary_readlink_name" != 'sharun' && -n "$IS_SHARUN" ]] 807 | then 808 | try_mkdir "$sharun_bin_dir_pth" 809 | [ "$hard_links" == 1 ] && \ 810 | ln_args='-f'||ln_args='-sf' 811 | (try_cd "$sharun_bin_dir_pth" 812 | [ "$VERBOSE" != 1 ]||echo -n "ln: " 813 | ln $varg $ln_args ../sharun "$binary_name"||exit 1 814 | if [ -n "$binary_real_name" ] 815 | then 816 | [ "$VERBOSE" != 1 ]||echo -n "ln: " 817 | ln $varg $ln_args ../sharun "$binary_real_name"||exit 1 818 | fi)||exit 1 819 | fi 820 | fi 821 | for lib_src_pth in $LIBS 822 | do 823 | if [[ "${LIBRARIES["$lib_src_pth"]}" != 1 ]] 824 | then 825 | unset lib_src_real_pth lib_src_real_name 826 | if [ -L "$lib_src_pth" ] 827 | then 828 | lib_src_real_pth="$(readlink -f "$lib_src_pth")" 829 | lib_src_real_name="$(basename "$lib_src_real_pth")" 830 | lib_src_dirname_pth="$(readlink -f "$(dirname "$lib_src_real_pth")")" 831 | FILE_INFO="$(file "$lib_src_real_pth" 2>/dev/null)" 832 | else 833 | lib_src_dirname_pth="$(readlink -f "$(dirname "$lib_src_pth")")" 834 | FILE_INFO="$(file "$lib_src_pth" 2>/dev/null)" 835 | fi 836 | if [[ "$FILE_INFO" =~ 'shared object' ]] 837 | then 838 | lib_src_name="$(basename "$lib_src_pth")" 839 | grep -qE '/lib32|/i386-linux-gnu|/arm-linux-gnu'<<<"$lib_src_dirname_pth" && \ 840 | lib_dir="lib32"||lib_dir="lib" 841 | lib_dst_dir_pth="${dst_dir_pth}/${lib_dir}$(sed \ 842 | "s|$dst_dir||;s|/shared||;s|^/usr||;s|^/opt||;s|^/lib64||;s|^/lib32||;s|^/lib||;s|^/.*-linux-gnu||"<<<"$lib_src_dirname_pth")" 843 | [ -n "$lib_src_real_name" ] && \ 844 | lib_dst_pth="$lib_dst_dir_pth/$lib_src_real_name"||\ 845 | lib_dst_pth="$lib_dst_dir_pth/$lib_src_name" 846 | if [[ ! -d "${dst_dir_pth}/${lib_dir}" && -d "${dst_dir}/${lib_dir}" \ 847 | && ! -L "${dst_dir_pth}/${lib_dir}" ]] 848 | then 849 | (try_mkdir "$dst_dir_pth" 850 | try_cd "$dst_dir_pth" 851 | try_ln ../$lib_dir $lib_dir)||exit 1 852 | fi 853 | try_mkdir "$lib_dst_dir_pth" 854 | if [[ -d "$bin_dir_pth/_internal" && ! -L "$bin_dir_pth/_internal" ]] 855 | then 856 | try_cp -nT "$bin_dir_pth/_internal" "$(readlink -f "${dst_dir_pth}/${lib_dir}")" 857 | rm -rf "$bin_dir_pth/_internal" 858 | (try_cd "$bin_dir_pth" 859 | try_ln ../${lib_dir} _internal)||exit 1 860 | fi 861 | try_cp_exe "$lib_src_pth" "$lib_dst_pth" 862 | if [[ -n "$lib_src_real_name" && "$lib_src_name" != "$lib_src_real_name" ]] 863 | then 864 | (try_cd "$lib_dst_dir_pth" 865 | try_ln "$lib_src_real_name" "$lib_src_name")||exit 1 866 | fi 867 | if [[ "${LIBRARIES["$lib_dst_pth"]}" != 1 ]] 868 | then 869 | if ! is_so && [[ ! -d "${dst_dir}/${lib_dir}" && \ 870 | ! -L "${dst_dir}/${lib_dir}" ]] 871 | then 872 | (try_cd "$dst_dir" 873 | try_ln shared/$lib_dir $lib_dir)||exit 1 874 | fi 875 | repath_needed_libs "$lib_dst_pth" 876 | try_strip "$lib_dst_pth" 877 | try_remove_fullrpath "$lib_dst_pth" 878 | if [ "$WITH_HOOKS" == 1 ] 879 | then 880 | if [ ! -f "$dst_dir/bin/xdg-open" ] 881 | then 882 | hook_msg "adding xdg-open wrapper..." 883 | try_mkdir "$dst_dir/bin" 884 | echo "$XDG_OPEN_WRAPPER" > "$dst_dir/bin/xdg-open" 885 | chmod $varg +x "$dst_dir/bin/xdg-open" 886 | fi 887 | case "$lib_dst_pth" in 888 | */gio/modules/*.so) 889 | sys_giom_cache="${lib_src_pth/modules\/*\.so/modules\/giomodule.cache}" 890 | dst_giom_dir="$(dirname "$lib_dst_pth")" 891 | dst_giom_cache="$dst_giom_dir/giomodule.cache" 892 | if [[ -f "$sys_giom_cache" && ! -f "$dst_giom_cache" ]] 893 | then 894 | hook_msg "copy giomodule.cache..." 895 | try_mkdir "$dst_giom_dir" 896 | try_cp "$sys_giom_cache" "$dst_giom_cache" 897 | fi ;; 898 | */libgio-*.so*) 899 | if [ ! -f "$dst_dir/bin/gio-launch-desktop" ] 900 | then 901 | hook_msg "make gio-launch-desktop wrapper..." 902 | try_ln 'xdg-open' "$dst_dir/bin/gio-launch-desktop" 903 | fi ;; 904 | */libglib-*.so*) 905 | glib="$(grep -o 'glib-.*\.so'<<<"$lib_src_name"|sed "s|\.so$||")" 906 | sys_glib_schemas="/usr/share/$glib/schemas" 907 | dst_glib_schemas="$dst_dir/share/$glib/schemas" 908 | if [[ -d "$sys_glib_schemas" && ! -d "$dst_glib_schemas" ]] 909 | then 910 | hook_msg "copy glib schemas..." 911 | try_mkdir "$dst_glib_schemas" 912 | try_cp -T "$sys_glib_schemas" "$dst_glib_schemas" 913 | fi ;; 914 | */gdk-pixbuf-*/*/loaders/*.so) 915 | sys_pixbufl_cache="${lib_src_pth/loaders\/*\.so/loaders.cache}" 916 | dst_pixbufl_dir="$(dirname "$lib_dst_pth")" 917 | dst_pixbufl_cache="${dst_pixbufl_dir}.cache" 918 | if [[ -f "$sys_pixbufl_cache" && ! -f "$dst_pixbufl_cache" ]] 919 | then 920 | hook_msg "copy gdk pixbuf loaders.cache..." 921 | try_mkdir "$dst_pixbufl_dir" 922 | try_cp "$sys_pixbufl_cache" "$dst_pixbufl_cache" 923 | sed -i 's|/usr/lib/.*/loaders/||g' "$dst_pixbufl_cache" 924 | fi ;; 925 | */gtk-*/*/immodules/*.so) 926 | sys_gtkimm_cache="${lib_src_pth/immodules\/*\.so/immodules.cache}" 927 | dst_gtkimm_dir="$(dirname "$lib_dst_pth")" 928 | dst_gtkimm_cache="${dst_gtkimm_dir}.cache" 929 | if [[ -f "$sys_gtkimm_cache" && ! -f "$dst_gtkimm_cache" ]] 930 | then 931 | hook_msg "copy gtk immodules.cache..." 932 | try_mkdir "$dst_gtkimm_dir" 933 | try_cp "$sys_gtkimm_cache" "$dst_gtkimm_cache" 934 | sed -i 's|/usr/lib/.*/immodules/||g' "$dst_gtkimm_cache" 935 | fi ;; 936 | */libfontconfig.so*) 937 | sys_fcfg='/etc/fonts/fonts.conf' 938 | dst_fcfg="$dst_dir/etc/fonts/fonts.conf" 939 | if [[ -f "$sys_fcfg" && ! -f "$dst_fcfg" ]] 940 | then 941 | hook_msg "copy fonts.conf..." 942 | try_mkdir "$dst_dir/etc/fonts" 943 | try_cp "$sys_fcfg" "$dst_fcfg" 944 | fi ;; 945 | */libfolks*.so*) 946 | sys_folks_dir="$(dirname "$lib_src_pth")/folks" 947 | dst_folks_dir="$(dirname "$lib_dst_pth")/folks" 948 | if [[ -d "$sys_folks_dir" && ! -d "$dst_folks_dir" ]] 949 | then 950 | hook_msg "copy folks lib dir..." 951 | try_cp -T "$sys_folks_dir" "$dst_folks_dir" 952 | fi ;; 953 | */libthai*.so*) 954 | sys_libthai_dir='/usr/share/libthai' 955 | dst_libthai_dir="$dst_dir/share/libthai" 956 | if [[ -d "$sys_libthai_dir" && ! -d "$dst_libthai_dir" ]] 957 | then 958 | hook_msg "copy libthai..." 959 | try_mkdir "$dst_libthai_dir" 960 | try_cp -T "$sys_libthai_dir" "$dst_libthai_dir" 961 | fi ;; 962 | */libxkbcommon*.so*) 963 | sys_xcb_dir='/usr/share/X11/xkb' 964 | dst_xcb_dir="$dst_dir/share/X11/xkb" 965 | if [[ -d "$sys_xcb_dir" && ! -d "$dst_xcb_dir" ]] 966 | then 967 | hook_msg "copy X11 xkb..." 968 | try_mkdir "$dst_xcb_dir" 969 | try_cp -T "$sys_xcb_dir" "$dst_xcb_dir" 970 | fi ;; 971 | */libgbm.so*) 972 | sys_gbm_dir="$(dirname "$lib_src_pth")/gbm" 973 | dst_gbm_dir="$(dirname "$lib_dst_pth")/gbm" 974 | if [[ -d "$sys_gbm_dir" && ! -d "$dst_gbm_dir" ]] 975 | then 976 | hook_msg "copy gbm lib dir..." 977 | try_cp -T "$sys_gbm_dir" "$dst_gbm_dir" 978 | fi ;; 979 | */libEGL_mesa.so*) 980 | sys_glvnd_dir='/usr/share/glvnd/egl_vendor.d' 981 | dst_glvnd_dir="$dst_dir/share/glvnd/egl_vendor.d" 982 | if [[ -d "$sys_glvnd_dir" && ! -d "$dst_glvnd_dir" ]] 983 | then 984 | hook_msg "copy EGL vendors..." 985 | try_mkdir "$dst_glvnd_dir" 986 | try_cp -T "$sys_glvnd_dir" "$dst_glvnd_dir" 987 | fi ;; 988 | */libvulkan.so*) 989 | sys_vk_icd_dir='/usr/share/vulkan/icd.d' 990 | dst_vk_icd_dir="$dst_dir/share/vulkan/icd.d" 991 | if [[ -d "$sys_vk_icd_dir" && ! -d "$dst_vk_icd_dir" ]] 992 | then 993 | hook_msg "copy Vulkan ICD..." 994 | try_mkdir "$dst_vk_icd_dir" 995 | try_cp -T "$sys_vk_icd_dir" "$dst_vk_icd_dir" 996 | sed -i 's|/usr/lib||g;s|/.*-linux-gnu||g;s|"/|"|g' "$dst_vk_icd_dir"/* 997 | fi ;; 998 | */libwebkit*gtk-*.so*) 999 | webkit="$(grep -Eo 'webkit(2)?gtk-[0-9]*\.[0-9]*'<<<"$lib_src_name")" 1000 | dst_webkit_lib_dir="$(dirname "$lib_dst_pth")/$webkit" 1001 | hook_msg "hack webkitgtk to be portable..." 1002 | sed -i 's|/usr|././|g' "$lib_dst_pth" 1003 | for sys_webkit_lib_dir in "${lib_src_dirname_pth}/$webkit" \ 1004 | "${lib_src_dirname_pth}exec/$webkit" 1005 | do 1006 | if [ -d "$sys_webkit_lib_dir" ] && \ 1007 | [[ ! -d "$dst_webkit_lib_dir/injected-bundle" || \ 1008 | ! -f "$dst_webkit_lib_dir/WebKitWebProcess" ]] 1009 | then try_cp -T "$sys_webkit_lib_dir" "$dst_webkit_lib_dir" 1010 | fi 1011 | done 1012 | for bin_to_wrap in "$dst_webkit_lib_dir"/* 1013 | do 1014 | if [[ -f "$bin_to_wrap" && ! -L "$bin_to_wrap" ]] && \ 1015 | ! grep -q '.*\.so.*'<<<"$bin_to_wrap" 1016 | then 1017 | try_mkdir "$bin_dir_pth" 1018 | try_mv "$bin_to_wrap" "$bin_dir_pth" 1019 | ln $varg -sr "$dst_dir/sharun" "$dst_webkit_lib_dir/$(basename "$bin_to_wrap")" 1020 | fi 1021 | done 1022 | if [ "$(basename "$lib_src_dirname_pth")" != "$lib_dir" ] 1023 | then try_ln . "$dst_dir_pth/$lib_dir/$(basename "$lib_src_dirname_pth")" 1024 | fi 1025 | if ! grep -q 'SHARUN_WORKING_DIR=${SHARUN_DIR}' "$dst_dir/.env" 2>/dev/null 1026 | then echo 'SHARUN_WORKING_DIR=${SHARUN_DIR}' >> "$dst_dir/.env" 1027 | fi ;; 1028 | */libwebkit*gtkinjectedbundle.so) 1029 | sed -i 's|/usr|././|g' "$lib_dst_pth" ;; 1030 | */libncursesw.so*|*/libcursesw.so*|*/libcurses.so*) 1031 | dst_terminfo_dir="$dst_dir/share/terminfo" 1032 | for terminfo_dir in '/etc' '/usr/share' 1033 | do 1034 | sys_terminfo_dir="$terminfo_dir/terminfo" 1035 | if [[ -d "$sys_terminfo_dir" && ! -d "$dst_terminfo_dir" ]] 1036 | then 1037 | hook_msg "copy terminfo..." 1038 | try_mkdir "$dst_terminfo_dir" 1039 | try_cp -T "$sys_terminfo_dir" "$dst_terminfo_dir" 1040 | break 1041 | fi 1042 | done 1043 | sys_tabset_dir='/usr/share/tabset' 1044 | dst_tabset_dir="$dst_dir/share/tabset" 1045 | if [[ -d "$sys_tabset_dir" && ! -d "$dst_tabset_dir" ]] 1046 | then 1047 | hook_msg "copy tabset..." 1048 | try_mkdir "$dst_tabset_dir" 1049 | try_cp -T "$sys_tabset_dir" "$dst_tabset_dir" 1050 | fi ;; 1051 | */qt*/plugins/*.so) 1052 | qt_conf="$sharun_bin_dir_pth/qt.conf" 1053 | if [ ! -f "$qt_conf" ] 1054 | then 1055 | hook_msg "create qt.conf..." 1056 | qt="$(grep -Eo 'qt([0-9])?'<<<"$lib_src_pth")" 1057 | echo -e \ 1058 | "[Paths]\nPrefix = ../shared/${lib_dir}/${qt}\nPlugins = plugins\nImports = qml\nQml2Imports = qml" \ 1059 | > "$qt_conf" 1060 | fi ;; 1061 | */libmagic.so*) 1062 | dst_magic_file="$dst_dir/share/file/misc/magic.mgc" 1063 | for magic_file_dir in 'file' 'file/misc' 'misc' 1064 | do 1065 | sys_magic_file="$(readlink -f "/usr/share/$magic_file_dir/magic.mgc")" 1066 | if [[ -f "$sys_magic_file" && ! -f "$dst_magic_file" ]] 1067 | then 1068 | hook_msg "copy magic file..." 1069 | try_mkdir "$(dirname "$dst_magic_file")" 1070 | try_cp "$sys_magic_file" "$dst_magic_file" 1071 | break 1072 | fi 1073 | done ;; 1074 | */libgirepository-*.so*) 1075 | girepository="$(grep -o 'girepository-.*\.so'<<<"$lib_src_name"|sed "s|\.so$||")" 1076 | sys_girepository="${lib_src_dirname_pth}/$girepository" 1077 | dst_girepository="$lib_dst_dir_pth/$girepository" 1078 | if [[ -d "$sys_girepository" && ! -d "$dst_girepository" ]] 1079 | then 1080 | hook_msg "copy girepository..." 1081 | try_mkdir "$dst_girepository" 1082 | try_cp -T "$sys_girepository" "$dst_girepository" 1083 | find "$lib_src_dirname_pth" -type f -wholename "*/$girepository/*.typelib" 2>/dev/null|\ 1084 | grep -v "^$sys_girepository"|while IFS=$'\n' read -r sys_typelib 1085 | do 1086 | dst_typelib="$dst_girepository/$(basename "$sys_typelib")" 1087 | if [[ -f "$sys_typelib" && ! -f "$dst_typelib" ]] 1088 | then try_cp "$sys_typelib" "$dst_typelib" 1089 | fi 1090 | done 1091 | fi ;; 1092 | */gconv/*.so) 1093 | sys_gconv_modules_file="$(dirname "$lib_src_pth")/gconv-modules" 1094 | dst_gconv_modules_file="$(dirname "$lib_dst_pth")/gconv-modules" 1095 | if [[ -f "$sys_gconv_modules_file" && ! -f "$dst_gconv_modules_file" ]] 1096 | then 1097 | hook_msg "copy gconv-modules file..." 1098 | try_cp "$sys_gconv_modules_file" "$dst_gconv_modules_file" 1099 | fi ;; 1100 | esac 1101 | fi 1102 | LIBRARIES["$lib_dst_pth"]=1 1103 | fi 1104 | else 1105 | skip_msg "$BLUE[$lib_src_pth]$YELLOW not shared object!" 1106 | fi 1107 | LIBRARIES["$lib_src_pth"]=1 1108 | fi 1109 | done 1110 | if [ -n "$INTERPRETER" ] 1111 | then 1112 | interpreter_pth="${dst_dir}/shared/$lib_dir/$INTERPRETER" 1113 | if [[ "$patch_interpreter" == 1 && "${LIBRARIES["${INTERPRETER}_patched"]}" != 1 ]] 1114 | then 1115 | if [ "$LIBS_ONLY" != 1 ] && ! is_so 1116 | then 1117 | (try_cd "$bin_dir_pth" 1118 | if [ -f "../$lib_dir/$INTERPRETER" ] 1119 | then 1120 | if [ -n "$IS_PYINSTELF" ] 1121 | then new_interp="$(printf "%${MAX_INTERP_LEN}s"|tr ' ' "i")" 1122 | else new_interp="../$lib_dir/$INTERPRETER" 1123 | fi 1124 | info_msg "$YELLOW[ SET INTERPRETER ]: $BLUE[$bin_dir_pth/$binary_name]" 1125 | patchelf $pvarg --set-interpreter "$new_interp" "$binary_name"||exit 1 1126 | fi)||exit 1 1127 | fi 1128 | info_msg "$YELLOW[ PATCH INTERPRETER ]: $BLUE[$interpreter_pth]" 1129 | sed -i 's|/usr|/XXX|g;s|/lib|/XXX|g;s|/etc|/XXX|g' "$interpreter_pth"||exit 1 1130 | LIBRARIES["${INTERPRETER}_patched"]=1 1131 | elif [[ "${LIBRARIES["${INTERPRETER}_sedpreload"]}" != 1 ]] 1132 | then 1133 | info_msg "$YELLOW[ REMOVE INTERPRETER PRELOAD ]: $BLUE[$interpreter_pth]" 1134 | sed -i 's|/etc/ld.so.preload|/XXX/ld.so.preload|g' "$interpreter_pth"||exit 1 1135 | LIBRARIES["${INTERPRETER}_sedpreload"]=1 1136 | fi 1137 | fi 1138 | info_msg "[ DONE ]" 1139 | binary_number=$(( $binary_number + 1 )) 1140 | else 1141 | if [ -e "$binary_src_pth" ] 1142 | then skip_msg "$BLUE[$binary]$YELLOW not executable or shared object!" 1143 | else 1144 | if [ "$STRACE_EXE" == "$binary" ] 1145 | then 1146 | error_msg "$BLUE[$binary]$RED executable for strace not found!" 1147 | exit 1 1148 | else skip_msg "$BLUE[$binary]$YELLOW not found!" 1149 | fi 1150 | fi 1151 | fi 1152 | BINARIES["$binary"]=1 1153 | fi 1154 | done 1155 | unset BINARIES LIBRARIES 1156 | else 1157 | DST_DIRS["$WRAPPE_DIR/shared"]= 1158 | fi 1159 | 1160 | if [ "$WITH_WRAPPE" == 1 ] 1161 | then 1162 | TMP_WRAPPE="${TMPDIR}/wrappe-${ARCH}" 1163 | WRAPPE="${WRAPPE:="$(readlink -f "$(which_exe wrappe)")"}" 1164 | WRAPPE="${WRAPPE:="$TMP_WRAPPE"}" 1165 | if [ ! -x "$WRAPPE" ] 1166 | then 1167 | if [[ "${WRAPPE,,}" =~ ^http ]] 1168 | then 1169 | WRAPPE_URL="$WRAPPE" 1170 | WRAPPE="$TMP_WRAPPE" 1171 | fi 1172 | WRAPPE_URL="${WRAPPE_URL:=$GIT_WRAPPE_RELEASE}" 1173 | info_msg "Downloading wrappe -> '$WRAPPE'..." 1174 | info_msg "$WRAPPE_URL" 1175 | if try_dl "$WRAPPE_URL" "$WRAPPE" 1176 | then chmod $varg +x "$WRAPPE" 1177 | else 1178 | error_msg "Failed to download wrappe!" 1179 | exit 1 1180 | fi 1181 | fi 1182 | if [ ! -x "$WRAPPE" ] 1183 | then 1184 | error_msg "wrappe not found!" 1185 | exit 1 1186 | fi 1187 | fi 1188 | 1189 | for dst_dir in "${!DST_DIRS[@]}" 1190 | do 1191 | unset binpatched 1192 | if [[ "$GEN_LIB_PATH" == 1 || "$PATCH_RPATH" == 1 || -n "$(echo "${!PATCH_RPATH_@}")" ]] 1193 | then 1194 | for lib_dir in lib lib32 1195 | do 1196 | lib_dir="${dst_dir}/${lib_dir}" 1197 | if [ -d "$lib_dir" ] 1198 | then 1199 | LIBS="$(find_so "$lib_dir/")" 1200 | if [ -n "$LIBS" ] 1201 | then 1202 | if [ "$GEN_LIB_PATH" == 1 ] 1203 | then 1204 | info_msg "$YELLOW[ GEN LIB PATH ]: $BLUE[${lib_dir}/lib.path]" 1205 | sed "s|${lib_dir}|+|g"<<<"$(xargs -I {} dirname "{}"<<<"$LIBS"|\ 1206 | sort -u)"|grep -v '+/lib-dynload' > "${lib_dir}/lib.path"||exit 1 1207 | fi 1208 | if [[ "$PATCH_RPATH" == 1 || "$(printenv PATCH_RPATH_$(get_md5sum "$dst_dir"))" == 1 ]] 1209 | then 1210 | patch_files="$LIBS" 1211 | if [ "$binpatched" != 1 ] 1212 | then 1213 | binpatched=1 1214 | patch_files+="\n$(find_exe "$(readlink -f "$lib_dir"/../bin)" -not -type l)" 1215 | fi 1216 | echo -e "$patch_files"|grep -v 'lib-dynload'|sort -u|while IFS=$'\n' read -r file 1217 | do 1218 | abs_path=$(readlink -f "$file") 1219 | file_dirname=$(dirname "$abs_path") 1220 | rpath='' 1221 | for subdir in $(find "$lib_dir" -type d|grep -v 'lib-dynload') 1222 | do 1223 | relative_path=$(get_relative_path "$file_dirname" "$subdir") 1224 | if [ -n "$relative_path" ] && \ 1225 | [ "$relative_path" != "." ] 1226 | then 1227 | [ -n "$rpath" ] && \ 1228 | rpath="$rpath:\$ORIGIN/$relative_path"||\ 1229 | rpath="\$ORIGIN/$relative_path" 1230 | fi 1231 | done 1232 | if [[ ! "$abs_path" =~ /ld-(linux|musl).*\.so && \ 1233 | ! "$abs_path" =~ ld-[0-9]\.[0-9]+\.so ]] 1234 | then try_set_rpath "$abs_path" "$rpath" 1235 | fi 1236 | done 1237 | fi 1238 | fi 1239 | fi 1240 | done 1241 | fi 1242 | src_dir="${dst_dir%/shared}" 1243 | if [[ "$WITH_WRAPPE" == 1 && -x "$src_dir/sharun" ]] 1244 | then 1245 | src_bins=($(find_exe "$src_dir/bin/" -printf "%f\n")) 1246 | if [ -n "$src_bins" ] 1247 | then 1248 | if [ "${#src_bins[@]}" == 1 ] 1249 | then wrappe_exec="$src_dir/bin/$src_bins" 1250 | else 1251 | if [[ -n "$WRAPPE_EXEC" && "$WRAPPE_EXEC" != 'sharun' ]] 1252 | then wrappe_exec="$src_dir/bin/$WRAPPE_EXEC" 1253 | else wrappe_exec="$src_dir/sharun" 1254 | fi 1255 | fi 1256 | wexec_name="${wrappe_exec##*/}" 1257 | IS_SCRIPT="$(file "$(readlink -f "$wrappe_exec")"|grep -o script)" 1258 | if [ -n "$IS_SCRIPT" ] 1259 | then 1260 | wrappe_exec="$src_dir/sharun" 1261 | [[ -n "$WRAPPE_EXEC" && "$WRAPPE_EXEC" != 'sharun' ]] && \ 1262 | wexec_name="$WRAPPE_EXEC" 1263 | fi 1264 | if [[ -L "$wrappe_exec" && "$(readlink -f "$wrappe_exec")" =~ sharun$ ]]||\ 1265 | [ -n "$IS_SCRIPT" ] 1266 | then wrappe_args="$wexec_name $WRAPPE_ARGS" 1267 | else wrappe_args="$WRAPPE_ARGS" 1268 | fi 1269 | [[ ! "$wout_dir" =~ ^"$DST_DIR" ]] && \ 1270 | wout_dir="$DST_DIR"||\ 1271 | wout_dir="$(dirname "$src_dir")" 1272 | try_mkdir "$wout_dir" 1273 | info_msg "$YELLOW[ PACKING WITH WRAPPE ]: $BLUE[$src_dir]" 1274 | "$WRAPPE" --unpack-target temp --unpack-directory .wrappe \ 1275 | $([ "$WRAPPE_CLEANUP" == 0 ]||echo '--cleanup') --verification none \ 1276 | --show-information none --compression "$WRAPPE_CLVL" "$src_dir" \ 1277 | "$wrappe_exec" "$wout_dir/$wexec_name" -- $wrappe_args||exit 1 1278 | if [ ! -d "$WRAPPE_DIR" ] 1279 | then rm -rf "$src_dir" 1280 | fi 1281 | fi 1282 | fi 1283 | done 1284 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | str::FromStr, 4 | path::{Path, PathBuf}, 5 | ffi::{CString, OsStr}, 6 | process::{Command, exit}, 7 | fs::{File, write, read_to_string}, 8 | os::unix::{fs::{MetadataExt, PermissionsExt}, process::CommandExt}, 9 | io::{Read, Result, Error, Write, BufRead, BufReader, ErrorKind::{InvalidData, NotFound}} 10 | }; 11 | 12 | use cfg_if::cfg_if; 13 | use walkdir::WalkDir; 14 | use nix::unistd::{access, AccessFlags}; 15 | use goblin::elf::{Elf, program_header::PT_INTERP}; 16 | 17 | 18 | const SHARUN_NAME: &str = env!("CARGO_PKG_NAME"); 19 | 20 | 21 | fn get_interpreter(library_path: &str) -> Result { 22 | let mut interpreters = Vec::new(); 23 | if let Ok(ldname) = env::var("SHARUN_LDNAME") { 24 | if !ldname.is_empty() { 25 | interpreters.push(ldname) 26 | } 27 | } else { 28 | #[cfg(target_arch = "x86_64")] // target x86_64-unknown-linux-musl 29 | interpreters.append(&mut vec![ 30 | "ld-linux-x86-64.so.2".into(), 31 | "ld-musl-x86_64.so.1".into(), 32 | "ld-linux.so.2".into() 33 | ]); 34 | #[cfg(target_arch = "aarch64")] // target aarch64-unknown-linux-musl 35 | interpreters.append(&mut vec![ 36 | "ld-linux-aarch64.so.1".into(), 37 | "ld-musl-aarch64.so.1".into() 38 | ]); 39 | } 40 | for interpreter in interpreters { 41 | let interpreter_path = Path::new(library_path).join(interpreter); 42 | if interpreter_path.exists() { 43 | return Ok(interpreter_path) 44 | } 45 | } 46 | Err(Error::last_os_error()) 47 | } 48 | 49 | fn realpath(path: &str) -> String { 50 | Path::new(path).canonicalize().unwrap_or_default().to_str().unwrap_or_default().to_string() 51 | } 52 | 53 | fn basename(path: &str) -> String { 54 | let pieces: Vec<&str> = path.rsplit('/').collect(); 55 | pieces.first().unwrap_or(&"").to_string() 56 | } 57 | 58 | fn dirname(path: &str) -> String { 59 | let mut pieces: Vec<&str> = path.split('/').collect(); 60 | if pieces.len() == 1 || path.is_empty() { 61 | // return ".".to_string(); 62 | } else if !path.starts_with('/') && 63 | !path.starts_with('.') && 64 | !path.starts_with('~') { 65 | pieces.insert(0, "."); 66 | } else if pieces.len() == 2 && path.starts_with('/') { 67 | pieces.insert(0, ""); 68 | }; 69 | pieces.pop(); 70 | pieces.join(&'/'.to_string()) 71 | } 72 | 73 | fn is_hardlink(path1: &Path, path2: &Path) -> bool { 74 | if let Ok(metadata1) = path1.metadata() { 75 | if let Ok(metadata2) = path2.metadata() { 76 | return metadata1.ino() == metadata2.ino() 77 | } 78 | } 79 | false 80 | } 81 | 82 | fn is_same_rootdir(rootdir: &Path, path1: &Path, path2: &Path) -> bool { 83 | if let Ok(abs_path1) = path1.canonicalize() { 84 | if let Ok(abs_path2) = path2.canonicalize() { 85 | if let Ok(abs_rootdir) = &rootdir.canonicalize() { 86 | return abs_path1.starts_with(abs_rootdir) && abs_path2.starts_with(abs_rootdir) 87 | } 88 | } 89 | } 90 | false 91 | } 92 | 93 | fn is_writable(path: &str) -> bool { 94 | access(path, AccessFlags::W_OK).is_ok() 95 | } 96 | 97 | fn is_dir(path: &str) -> bool { 98 | Path::new(path).is_dir() 99 | } 100 | 101 | fn is_file(path: &Path) -> bool { 102 | if let Ok(metadata) = path.metadata() { 103 | return metadata.is_file() 104 | } 105 | false 106 | } 107 | 108 | fn is_exe(path: &Path) -> bool { 109 | if let Ok(metadata) = path.metadata() { 110 | return metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 111 | } 112 | false 113 | } 114 | 115 | fn which(executable: &str) -> Option { 116 | if let Ok(path) = env::var("PATH") { 117 | for dir in path.split(':') { 118 | let full_path = Path::new(dir).join(executable); 119 | if is_exe(&full_path) { 120 | return Some(full_path) 121 | } 122 | } 123 | } 124 | None 125 | } 126 | 127 | fn is_script(path: &PathBuf) -> Result { 128 | let mut file = File::open(path)?; 129 | let mut buffer = [0; 2]; 130 | file.read_exact(&mut buffer)?; 131 | Ok(&buffer[0..2] == b"#!") 132 | } 133 | 134 | fn read_first_line(path: &PathBuf) -> Result { 135 | let file = File::open(path)?; 136 | let mut reader = BufReader::new(file); 137 | let mut line = String::new(); 138 | reader.read_line(&mut line)?; 139 | Ok(line) 140 | } 141 | 142 | fn exec_script(path: &PathBuf, exec_args: &[String]) -> Result<()> { 143 | let first_line = read_first_line(path)?; 144 | if !first_line.starts_with("#!") { 145 | return Err(Error::new(NotFound, "Script does not have a valid shebang!")) 146 | } 147 | let shebang = first_line[2..].trim(); 148 | let parts: Vec<&str> = shebang.split_whitespace().collect(); 149 | if parts.is_empty() { 150 | return Err(Error::new(NotFound, "Invalid shebang: no interpreter specified!")) 151 | } 152 | let interpreter_path = parts[0]; 153 | let mut command = if interpreter_path.ends_with("/env") { 154 | if parts.len() < 2 { 155 | return Err(Error::new(NotFound, "No interpreter specified after env!")) 156 | } 157 | let interpreter = parts[1]; 158 | let interpreter_path = match which(interpreter) { 159 | Some(path) => path, 160 | None => return Err(Error::new(NotFound, 161 | format!("Interpreter '{interpreter}' not found in PATH")) 162 | ) 163 | }; 164 | let mut command = Command::new(&interpreter_path); 165 | for arg in &parts[2..] { 166 | command.arg(arg); 167 | } 168 | command 169 | } else { 170 | let interpreter_name = Path::new(interpreter_path) 171 | .file_name() 172 | .unwrap_or_default() 173 | .to_string_lossy() 174 | .to_string(); 175 | let interpreter_path = match which(&interpreter_name) { 176 | Some(path) => path, 177 | None => PathBuf::from(interpreter_path) 178 | }; 179 | if !interpreter_path.exists() { 180 | return Err(Error::new(NotFound, 181 | format!("Interpreter '{}' not found", interpreter_path.display())) 182 | ) 183 | } 184 | let mut command = Command::new(&interpreter_path); 185 | for arg in &parts[1..] { 186 | command.arg(arg); 187 | } 188 | command 189 | }; 190 | let err = command.arg(path).args(exec_args).exec(); 191 | Err(Error::new(InvalidData, err)) 192 | } 193 | 194 | #[cfg(feature = "elf32")] 195 | fn is_elf32(path: &String) -> Result { 196 | let mut file = File::open(path)?; 197 | let mut elf_bytes = [0; 5]; 198 | file.read_exact(&mut elf_bytes)?; 199 | if &elf_bytes[0..4] != b"\x7fELF" { 200 | return Ok(false) 201 | } 202 | Ok(elf_bytes[4] == 1) 203 | } 204 | 205 | #[cfg(feature = "pyinstaller")] 206 | fn get_elf(path: &String, is_elf32: bool) -> Result> { 207 | let mut file = File::open(path)?; 208 | if is_elf32 { 209 | let mut headers_bytes = Vec::new(); 210 | file.read_to_end(&mut headers_bytes)?; 211 | Ok(headers_bytes) 212 | } else { 213 | let mut elf_header_raw = [0; 64]; 214 | file.read_exact(&mut elf_header_raw)?; 215 | let section_table_offset = u64::from_le_bytes(elf_header_raw[40..48].try_into().unwrap()); // e_shoff 216 | let section_count = u16::from_le_bytes(elf_header_raw[60..62].try_into().unwrap()); // e_shnum 217 | let section_table_size = section_count as u64 * 64; 218 | let required_bytes = section_table_offset + section_table_size; 219 | let mut headers_bytes = vec![0; required_bytes as usize]; 220 | std::io::Seek::seek(&mut file, std::io::SeekFrom::Start(0))?; 221 | file.read_exact(&mut headers_bytes)?; 222 | Ok(headers_bytes) 223 | } 224 | } 225 | 226 | #[cfg(feature = "pyinstaller")] 227 | fn is_elf_section(elf_bytes: &[u8], section_name: &str) -> Result { 228 | if let Ok(elf) = Elf::parse(elf_bytes) { 229 | if let Some(section_headers) = elf.section_headers.as_slice().get(..) { 230 | for section_header in section_headers { 231 | if let Some(name) = elf.shdr_strtab.get_at(section_header.sh_name) { 232 | if name == section_name { 233 | return Ok(true) 234 | } 235 | } 236 | } 237 | } 238 | } 239 | Ok(false) 240 | } 241 | 242 | fn write_file(elf_path: &String, bytes: &[u8]) -> Result { 243 | let mut file = File::create(elf_path)?; 244 | file.write_all(bytes)?; 245 | Ok(true) 246 | } 247 | 248 | fn set_interp(mut elf_bytes: Vec, elf_path: &String, new_interp: &str) -> Result { 249 | let elf = Elf::parse(&elf_bytes) 250 | .map_err(|err| Error::new(InvalidData, err))?; 251 | let interp_header = elf.program_headers.iter().find(|header| header.p_type == PT_INTERP); 252 | match interp_header { 253 | Some(header) => { 254 | let start = header.p_offset as usize; 255 | let end = start + header.p_filesz as usize; 256 | let interp_slice = &mut elf_bytes[start..end]; 257 | if interp_slice.last() != Some(&0) { 258 | return Err(Error::new(InvalidData, "Current INTERP not NUL terminated")); 259 | } 260 | if new_interp.len() > (header.p_filesz as usize) - 1 { 261 | return Err(Error::new(InvalidData, "Current INTERP too small")); 262 | } 263 | let new_interp_bytes = new_interp.as_bytes(); 264 | interp_slice[..new_interp_bytes.len()].copy_from_slice(new_interp_bytes); 265 | for byte in interp_slice.iter_mut().take((header.p_filesz as usize) - 1).skip(new_interp_bytes.len()) { *byte = 0 } 266 | interp_slice[(header.p_filesz as usize) - 1] = 0; 267 | write_file(elf_path, &elf_bytes)?; 268 | } 269 | None => { 270 | return Err(Error::new(InvalidData, "Failed to find PT_INTERP header")); 271 | } 272 | } 273 | Ok(true) 274 | } 275 | 276 | fn get_env_var>(key: K) -> String { 277 | env::var(key).unwrap_or("".into()) 278 | } 279 | 280 | fn add_to_env, V: AsRef>(key: K, val: V) { 281 | let (key, val) = (key.as_ref(), val.as_ref().to_str().unwrap()); 282 | let old_val = get_env_var(key); 283 | if old_val.is_empty() { 284 | env::set_var(key, val) 285 | } else if !old_val.contains(val) { 286 | env::set_var(key, format!("{val}:{old_val}")) 287 | } 288 | } 289 | 290 | fn read_dotenv(dotenv_dir: &str) -> Vec { 291 | let mut unset_envs = Vec::new(); 292 | let dotenv_path = PathBuf::from(format!("{dotenv_dir}/.env")); 293 | if dotenv_path.exists() { 294 | dotenv::from_path(&dotenv_path).ok(); 295 | let data = read_to_string(&dotenv_path).unwrap_or_else(|err|{ 296 | eprintln!("Failed to read .env file: {}: {err}", dotenv_path.display()); 297 | exit(1) 298 | }); 299 | for string in data.trim().split("\n") { 300 | let string = string.trim(); 301 | if string.starts_with("unset ") { 302 | for var_name in string.split_whitespace().skip(1) { 303 | unset_envs.push(var_name.into()); 304 | } 305 | } 306 | } 307 | } 308 | unset_envs 309 | } 310 | 311 | #[cfg(feature = "setenv")] 312 | fn add_to_xdg_data_env(xdg_data_dirs: &str, env: &str, path: &str) { 313 | for xdg_data_dir in xdg_data_dirs.rsplit(":") { 314 | let env_data_dir = Path::new(xdg_data_dir).join(path); 315 | if env_data_dir.exists() { 316 | add_to_env(env, env_data_dir) 317 | } 318 | } 319 | } 320 | 321 | fn gen_library_path(library_path: &str, lib_path_file: &String) { 322 | let mut new_paths: Vec = Vec::new(); 323 | let skip_dirs = ["lib-dynload".to_string()]; 324 | WalkDir::new(library_path) 325 | .into_iter() 326 | .filter_map(|entry| entry.ok()) 327 | .for_each(|entry| { 328 | let name = entry.file_name().to_string_lossy(); 329 | if name.ends_with(".so") || name.contains(".so.") { 330 | if let Some(parent) = entry.path().parent() { 331 | if let Some(parent_str) = parent.to_str() { 332 | if parent_str != library_path && parent.is_dir() && 333 | !new_paths.contains(&parent_str.into()) && 334 | !skip_dirs.contains(&basename(parent_str)) { 335 | new_paths.push(parent_str.into()); 336 | } 337 | } 338 | } 339 | } 340 | }); 341 | if let Err(err) = write(lib_path_file, 342 | format!("+:{}", &new_paths.join(":")) 343 | .replace(":", "\n") 344 | .replace(library_path, "+") 345 | ) { 346 | eprintln!("Failed to write lib.path: {lib_path_file}: {err}"); 347 | exit(1) 348 | } else { 349 | eprintln!("Write lib.path: {lib_path_file}") 350 | } 351 | } 352 | 353 | fn print_usage() { 354 | println!("[ {} ] 355 | 356 | [ Usage ]: {SHARUN_NAME} [OPTIONS] [EXEC ARGS]...", 357 | env!("CARGO_PKG_DESCRIPTION")); 358 | #[cfg(feature = "lib4bin")] 359 | println!(" Use lib4bin for create 'bin' and 'shared' dirs"); 360 | println!(" 361 | [ Arguments ]: 362 | [EXEC ARGS]... Command line arguments for execution 363 | 364 | [ Options ]:"); 365 | #[cfg(feature = "lib4bin")] 366 | println!(" l, lib4bin [ARGS] Launch the built-in lib4bin"); 367 | println!(" -g, --gen-lib-path Generate a lib.path file 368 | -v, --version Print version 369 | -h, --help Print help 370 | 371 | [ Environments ]: 372 | SHARUN_WORKING_DIR=/path Specifies the path to the working directory 373 | SHARUN_ALLOW_SYS_VKICD=1 Enables breaking system vulkan/icd.d for vulkan loader 374 | SHARUN_ALLOW_LD_PRELOAD=1 Enables breaking LD_PRELOAD env variable 375 | SHARUN_PRINTENV=1 Print environment variables to stderr 376 | SHARUN_LDNAME=ld.so Specifies the name of the interpreter 377 | SHARUN_DIR Sharun directory"); 378 | } 379 | 380 | fn main() { 381 | let sharun = env::current_exe().unwrap(); 382 | let mut exec_args: Vec = env::args().collect(); 383 | 384 | let mut sharun_dir = realpath(&get_env_var("SHARUN_DIR")); 385 | if sharun_dir.is_empty() || 386 | !(is_dir(&sharun_dir) && { 387 | let sharun_dir_path = Path::new(&sharun_dir); 388 | let sharun_path = sharun_dir_path.join(SHARUN_NAME); 389 | sharun_dir_path.join("shared").is_dir() && is_exe(&sharun_path) && 390 | is_same_rootdir(sharun_dir_path, &sharun, &sharun_path) 391 | }) 392 | { 393 | sharun_dir = sharun.parent().unwrap().to_str().unwrap().to_string(); 394 | let lower_dir = &format!("{sharun_dir}/../"); 395 | if basename(&sharun_dir) == "bin" && 396 | is_dir(&format!("{lower_dir}shared")) { 397 | sharun_dir = realpath(lower_dir) 398 | } 399 | env::set_var("SHARUN_DIR", &sharun_dir) 400 | } 401 | 402 | let bin_dir = &format!("{sharun_dir}/bin"); 403 | let shared_dir = &format!("{sharun_dir}/shared"); 404 | let shared_bin = &format!("{shared_dir}/bin"); 405 | let shared_lib = format!("{shared_dir}/lib"); 406 | let shared_lib32 = format!("{shared_dir}/lib32"); 407 | 408 | let arg0 = PathBuf::from(exec_args.remove(0)); 409 | let arg0_name = arg0.file_name().unwrap().to_str().unwrap(); 410 | let arg0_dir = PathBuf::from(dirname(arg0.to_str().unwrap())).canonicalize() 411 | .unwrap_or_else(|_|{ 412 | if let Some(which_arg0) = which(arg0_name) { 413 | which_arg0.parent().unwrap().to_path_buf() 414 | } else { 415 | eprintln!("Failed to find ARG0 dir!"); 416 | exit(1) 417 | } 418 | }); 419 | 420 | let arg0_path = arg0_dir.join(arg0_name); 421 | let arg0_full_path = arg0_path.canonicalize().unwrap(); 422 | let arg0_full_path_name = arg0_full_path.file_name().unwrap().to_string_lossy().to_string(); 423 | let mut bin_name = if arg0_path.is_symlink() && 424 | arg0_full_path == Path::new(&sharun_dir).join(SHARUN_NAME) { 425 | arg0_name.into() 426 | } else if arg0_path.is_symlink() && Path::new(&shared_bin).join(&arg0_full_path_name).exists() { 427 | arg0_full_path_name 428 | } else { 429 | sharun.file_name().unwrap().to_string_lossy().to_string() 430 | }; 431 | drop(arg0_dir); 432 | drop(arg0_full_path); 433 | 434 | if bin_name == SHARUN_NAME { 435 | if !exec_args.is_empty() { 436 | match exec_args[0].as_str() { 437 | "-v" | "--version" => { 438 | println!("v{}", env!("CARGO_PKG_VERSION")); 439 | return 440 | } 441 | "-h" | "--help" => { 442 | print_usage(); 443 | return 444 | } 445 | "-g" | "--gen-lib-path" => { 446 | for library_path in [shared_lib, shared_lib32] { 447 | if Path::new(&library_path).exists() { 448 | let lib_path_file = &format!("{library_path}/lib.path"); 449 | gen_library_path(&library_path, lib_path_file) 450 | } 451 | } 452 | return 453 | } 454 | #[cfg(feature = "lib4bin")] 455 | "l" | "lib4bin" => { 456 | let lib4bin_compressed = include_file_compress::include_file_compress_deflate!("lib4bin", 9); 457 | let mut decoder = flate2::read::DeflateDecoder::new(&lib4bin_compressed[..]); 458 | let mut lib4bin = Vec::new(); 459 | decoder.read_to_end(&mut lib4bin).unwrap(); 460 | drop(decoder); 461 | exec_args.remove(0); 462 | add_to_env("PATH", bin_dir); 463 | let cmd = Command::new("bash") 464 | .env("SHARUN", sharun) 465 | .envs(env::vars()) 466 | .stdin(std::process::Stdio::piped()) 467 | .arg("-s").arg("--") 468 | .args(exec_args) 469 | .spawn(); 470 | match cmd { 471 | Ok(mut bash) => { 472 | bash.stdin.take().unwrap().write_all(&lib4bin).unwrap_or_else(|err|{ 473 | eprintln!("Failed to write lib4bin to bash stdin: {err}"); 474 | exit(1) 475 | }); 476 | exit(bash.wait().unwrap().code().unwrap()) 477 | } 478 | Err(err) => { 479 | eprintln!("Failed to run bash: {err}"); 480 | exit(1) 481 | } 482 | } 483 | } 484 | _ => { 485 | bin_name = exec_args.remove(0); 486 | let bin_path = PathBuf::from(bin_dir).join(&bin_name); 487 | if let Ok(bin_full_path) = bin_path.canonicalize() { 488 | let bin_full_path_name = bin_full_path.file_name().unwrap().to_string_lossy().to_string(); 489 | if bin_path.is_symlink() && Path::new(&shared_bin).join(&bin_full_path_name).exists() { 490 | bin_name = bin_full_path_name 491 | } 492 | if is_exe(&bin_full_path) && 493 | (is_hardlink(&sharun, &bin_full_path) || 494 | !Path::new(&shared_bin).join(&bin_name).exists() || 495 | bin_full_path != sharun) 496 | { 497 | add_to_env("PATH", bin_dir); 498 | match is_script(&bin_path) { 499 | Ok(true) => { 500 | if let Err(err) = exec_script(&bin_path, &exec_args) { 501 | eprintln!("Error executing script: {err}"); 502 | exit(1); 503 | } 504 | } 505 | Ok(false) => { 506 | let err = Command::new(&bin_path) 507 | .envs(env::vars()) 508 | .args(exec_args) 509 | .exec(); 510 | eprintln!("Error executing file {:?}: {err}", &bin_path); 511 | exit(1) 512 | } 513 | Err(err) => { 514 | eprintln!("Error reading file {:?}: {err}", &bin_path); 515 | exit(1) 516 | } 517 | } 518 | } 519 | } 520 | } 521 | } 522 | } else { 523 | eprintln!("Specify the executable from: '{bin_dir}'"); 524 | if let Ok(dir) = Path::new(bin_dir).read_dir() { 525 | for bin in dir.flatten() { 526 | if is_exe(&bin.path()) { 527 | println!("{}", bin.file_name().to_str().unwrap()) 528 | } 529 | } 530 | } 531 | exit(1) 532 | } 533 | } else if bin_name == "AppRun" { 534 | let appname_file = &format!("{sharun_dir}/.app"); 535 | let mut appname: String = "".into(); 536 | if !Path::new(appname_file).exists() { 537 | if let Ok(dir) = Path::new(&sharun_dir).read_dir() { 538 | for entry in dir.flatten() { 539 | let path = entry.path(); 540 | if is_file(&path) { 541 | let name = entry.file_name(); 542 | let name = name.to_str().unwrap(); 543 | if name.ends_with(".desktop") { 544 | let data = read_to_string(path).unwrap_or_else(|err|{ 545 | eprintln!("Failed to read desktop file: {name}: {err}"); 546 | exit(1) 547 | }); 548 | appname = data.split("\n").filter_map(|string| { 549 | if string.starts_with("Exec=") { 550 | Some(string.replace("Exec=", "").split_whitespace().next().unwrap_or("").into()) 551 | } else {None} 552 | }).next().unwrap_or_else(||"".into()) 553 | } 554 | } 555 | } 556 | } 557 | } 558 | 559 | if appname.is_empty() { 560 | appname = read_to_string(appname_file).unwrap_or_else(|err|{ 561 | eprintln!("Failed to read .app file: {appname_file}: {err}"); 562 | exit(1) 563 | }) 564 | } 565 | 566 | if let Some(name) = appname.trim().split("\n").next() { 567 | appname = basename(name) 568 | .replace("'", "").replace("\"", "") 569 | } else { 570 | eprintln!("Failed to get app name: {appname_file}"); 571 | exit(1) 572 | } 573 | let app = &format!("{bin_dir}/{appname}"); 574 | 575 | add_to_env("PATH", bin_dir); 576 | if get_env_var("ARGV0").is_empty() { 577 | env::set_var("ARGV0", &arg0) 578 | } 579 | if get_env_var("APPDIR").is_empty() { 580 | env::set_var("APPDIR", &sharun_dir) 581 | } 582 | 583 | let err = Command::new(app) 584 | .envs(env::vars()) 585 | .args(exec_args) 586 | .exec(); 587 | eprintln!("Failed to run App: {app}: {err}"); 588 | exit(1) 589 | } 590 | let bin = format!("{shared_bin}/{bin_name}"); 591 | 592 | cfg_if! { 593 | if #[cfg(feature = "elf32")] { 594 | let is_elf32_bin = is_elf32(&bin).unwrap_or_else(|err|{ 595 | eprintln!("Failed to check ELF class: {bin}: {err}"); 596 | exit(1) 597 | }); 598 | } else { 599 | let is_elf32_bin = false; 600 | } 601 | } 602 | 603 | cfg_if! { 604 | if #[cfg(feature = "pyinstaller")] { 605 | let elf_bytes = get_elf(&bin, is_elf32_bin).unwrap_or_else(|err|{ 606 | eprintln!("Failed to read ELF: {}: {err}", &bin); 607 | exit(1) 608 | }); 609 | } else { 610 | let elf_bytes = vec![]; 611 | } 612 | } 613 | 614 | let mut library_path = if is_elf32_bin { 615 | shared_lib32 616 | } else { 617 | shared_lib 618 | }; 619 | 620 | let unset_envs = read_dotenv(&sharun_dir); 621 | 622 | if get_env_var("SHARUN_ALLOW_LD_PRELOAD") != "1" { 623 | env::remove_var("LD_PRELOAD") 624 | } 625 | env::remove_var("SHARUN_ALLOW_LD_PRELOAD"); 626 | 627 | let interpreter = get_interpreter(&library_path).unwrap_or_else(|_|{ 628 | eprintln!("Interpreter not found!"); 629 | exit(1) 630 | }); 631 | 632 | let working_dir = &get_env_var("SHARUN_WORKING_DIR"); 633 | if !working_dir.is_empty() { 634 | env::set_current_dir(working_dir).unwrap_or_else(|err|{ 635 | eprintln!("Failed to change working directory: {working_dir}: {err}"); 636 | exit(1) 637 | }); 638 | env::remove_var("SHARUN_WORKING_DIR") 639 | } 640 | 641 | #[cfg(feature = "setenv")] 642 | { 643 | let gio_launch_desktop = PathBuf::from(&bin_dir).join("gio-launch-desktop"); 644 | if is_exe(&gio_launch_desktop) { 645 | env::set_var("GIO_LAUNCH_DESKTOP", gio_launch_desktop) 646 | } 647 | if let Ok(dir) = PathBuf::from(&library_path).read_dir() { 648 | for entry in dir.flatten() { 649 | let entry_path = entry.path(); 650 | if entry_path.is_dir() { 651 | let name = entry.file_name(); 652 | if let Some(name) = name.to_str() { 653 | if name.starts_with("girepository-") { 654 | env::set_var("GI_TYPELIB_PATH", entry_path) 655 | } 656 | } 657 | } 658 | } 659 | } 660 | } 661 | 662 | let lib_path_file = &format!("{library_path}/lib.path"); 663 | if !Path::new(lib_path_file).exists() && is_writable(&library_path) { 664 | gen_library_path(&library_path, lib_path_file) 665 | } 666 | 667 | add_to_env("PATH", bin_dir); 668 | 669 | let mut lib_path_data = read_to_string(lib_path_file).unwrap_or_default(); 670 | 671 | #[cfg(feature = "setenv")] 672 | { 673 | if !lib_path_data.is_empty() { 674 | let dirs: std::collections::HashSet<&str> = lib_path_data.split("\n").map(|string|{ 675 | string.split("/").nth(1).unwrap_or("") 676 | }).collect(); 677 | for dir in dirs { 678 | let dir_path = &format!("{library_path}/{dir}"); 679 | if dir.starts_with("python") && !is_writable(&sharun_dir) { 680 | env::set_var("PYTHONDONTWRITEBYTECODE", "1") 681 | } 682 | if dir.starts_with("perl") { 683 | add_to_env("PERLLIB", dir_path) 684 | } 685 | if dir == "gconv" { 686 | add_to_env("GCONV_PATH", dir_path) 687 | } 688 | if dir == "gio" { 689 | let modules = &format!("{dir_path}/modules"); 690 | if Path::new(modules).exists() { 691 | env::set_var("GIO_MODULE_DIR", modules) 692 | } 693 | } 694 | if dir == "dri" { 695 | env::set_var("LIBGL_DRIVERS_PATH", dir_path) 696 | } 697 | if dir == "gbm" { 698 | env::set_var("GBM_BACKENDS_PATH", dir_path) 699 | } 700 | if dir == "xtables" { 701 | env::set_var("XTABLES_LIBDIR", dir_path) 702 | } 703 | if dir.starts_with("spa-") { 704 | env::set_var("SPA_PLUGIN_DIR", dir_path) 705 | } 706 | if dir.starts_with("pipewire-") { 707 | env::set_var("PIPEWIRE_MODULE_DIR", dir_path) 708 | } 709 | if dir.starts_with("gtk-") { 710 | add_to_env("GTK_PATH", dir_path); 711 | env::set_var("GTK_EXE_PREFIX", &sharun_dir); 712 | env::set_var("GTK_DATA_PREFIX", &sharun_dir); 713 | for entry in WalkDir::new(dir_path).into_iter().flatten() { 714 | let path = entry.path(); 715 | if is_file(path) && entry.file_name().to_string_lossy() == "immodules.cache" { 716 | env::set_var("GTK_IM_MODULE_FILE", path); 717 | break 718 | } 719 | } 720 | } 721 | if dir == "folks" { 722 | for entry in WalkDir::new(dir_path).into_iter().flatten() { 723 | let path = entry.path(); 724 | if path.is_dir() && entry.file_name().to_string_lossy() == "backends" { 725 | env::set_var("FOLKS_BACKEND_PATH", path); 726 | break 727 | } 728 | } 729 | } 730 | if dir.starts_with("qt") { 731 | let qt_conf = &format!("{bin_dir}/qt.conf"); 732 | let plugins = &format!("{dir_path}/plugins"); 733 | if Path::new(plugins).exists() && ! Path::new(qt_conf).exists() { 734 | add_to_env("QT_PLUGIN_PATH", plugins) 735 | } 736 | } 737 | if dir.starts_with("babl-") { 738 | env::set_var("BABL_PATH", dir_path) 739 | } 740 | if dir.starts_with("gegl-") { 741 | env::set_var("GEGL_PATH", dir_path) 742 | } 743 | if dir == "libdecor" { 744 | let plugins = &format!("{dir_path}/plugins-1"); 745 | if Path::new(plugins).exists() { 746 | env::set_var("LIBDECOR_PLUGIN_DIR", plugins) 747 | } 748 | } 749 | if dir.starts_with("tcl") && Path::new(&format!("{dir_path}/msgs")).exists() { 750 | add_to_env("TCL_LIBRARY", dir_path); 751 | let tk = &format!("{library_path}/{}", dir.replace("tcl", "tk")); 752 | if Path::new(&tk).exists() { 753 | add_to_env("TK_LIBRARY", tk) 754 | } 755 | } 756 | if dir.starts_with("gstreamer-") { 757 | add_to_env("GST_PLUGIN_PATH", dir_path); 758 | add_to_env("GST_PLUGIN_SYSTEM_PATH", dir_path); 759 | add_to_env("GST_PLUGIN_SYSTEM_PATH_1_0", dir_path); 760 | let gst_scanner = &format!("{dir_path}/gst-plugin-scanner"); 761 | if Path::new(gst_scanner).exists() { 762 | env::set_var("GST_PLUGIN_SCANNER", gst_scanner) 763 | } 764 | } 765 | if dir.starts_with("gdk-pixbuf-") { 766 | let mut is_loaders = false; 767 | let mut is_loaders_cache = false; 768 | for entry in WalkDir::new(dir_path).into_iter().flatten() { 769 | let path = entry.path(); 770 | let name = entry.file_name().to_string_lossy(); 771 | if name == "loaders" && path.is_dir() { 772 | env::set_var("GDK_PIXBUF_MODULEDIR", path); 773 | is_loaders = true 774 | } 775 | if name == "loaders.cache" && is_file(path) { 776 | env::set_var("GDK_PIXBUF_MODULE_FILE", path); 777 | is_loaders_cache = true 778 | } 779 | if is_loaders && is_loaders_cache { 780 | break 781 | } 782 | } 783 | } 784 | } 785 | } 786 | 787 | let share_dir = PathBuf::from(format!("{sharun_dir}/share")); 788 | if share_dir.exists() { 789 | if let Ok(dir) = share_dir.read_dir() { 790 | add_to_env("XDG_DATA_DIRS", "/usr/local/share"); 791 | add_to_env("XDG_DATA_DIRS", "/usr/share"); 792 | add_to_env("XDG_DATA_DIRS", format!("{}/.local/share", get_env_var("HOME"))); 793 | add_to_env("XDG_DATA_DIRS", &share_dir); 794 | let xdg_data_dirs = &get_env_var("XDG_DATA_DIRS"); 795 | for entry in dir.flatten() { 796 | let entry_path = entry.path(); 797 | if entry_path.is_dir() { 798 | let name = entry.file_name(); 799 | match name.to_str().unwrap() { 800 | "glvnd" => { 801 | add_to_xdg_data_env(xdg_data_dirs, 802 | "__EGL_VENDOR_LIBRARY_DIRS", "glvnd/egl_vendor.d") 803 | } 804 | "vulkan" => { 805 | let vk_dir = "vulkan/icd.d"; 806 | let vk_env = "VK_DRIVER_FILES"; 807 | if get_env_var("SHARUN_ALLOW_SYS_VKICD") == "1" { 808 | env::remove_var("SHARUN_ALLOW_SYS_VKICD"); 809 | add_to_xdg_data_env(xdg_data_dirs, vk_env, vk_dir) 810 | } else { 811 | for xdg_data_dir in xdg_data_dirs.rsplit(":") { 812 | let vk_icd_dir = Path::new(xdg_data_dir).join(vk_dir); 813 | if vk_icd_dir.exists() { 814 | if xdg_data_dir.starts_with(share_dir.to_str().unwrap()) { 815 | add_to_env(vk_env, vk_icd_dir); 816 | } else if let Ok(dir) = vk_icd_dir.read_dir() { 817 | for entry in dir.flatten() { 818 | let path = entry.path(); 819 | if is_file(&path) && 820 | entry.file_name().to_string_lossy().contains("nvidia") { 821 | add_to_env(vk_env, path) 822 | } 823 | } 824 | } 825 | } 826 | } 827 | } 828 | } 829 | "X11" => { 830 | let xkb = &entry_path.join("xkb"); 831 | if !Path::new("/usr/share/X11/xkb").exists() && xkb.exists() { 832 | env::set_var("XKB_CONFIG_ROOT", xkb) 833 | } 834 | } 835 | "libthai" => { 836 | if entry_path.join("thbrk.tri").exists() { 837 | env::set_var("LIBTHAI_DICTDIR", entry_path) 838 | } 839 | } 840 | "glib-2.0" => { 841 | add_to_xdg_data_env(xdg_data_dirs, 842 | "GSETTINGS_SCHEMA_DIR", "glib-2.0/schemas") 843 | } 844 | "terminfo" => { 845 | env::set_var("TERMINFO", entry_path) 846 | } 847 | "file" => { 848 | let magic_file = &entry_path.join("misc/magic.mgc"); 849 | if magic_file.exists() { 850 | env::set_var("MAGIC", magic_file) 851 | } 852 | } 853 | _ => {} 854 | } 855 | } 856 | } 857 | } 858 | } 859 | 860 | let etc_dir = PathBuf::from(format!("{sharun_dir}/etc")); 861 | if etc_dir.exists() { 862 | if let Ok(dir) = etc_dir.read_dir() { 863 | for entry in dir.flatten() { 864 | let entry_path = entry.path(); 865 | if entry_path.is_dir() { 866 | let name = entry.file_name(); 867 | match name.to_str().unwrap() { 868 | "fonts" => { 869 | let fonts_conf = entry_path.join("fonts.conf"); 870 | if !Path::new("/etc/fonts/fonts.conf").exists() && fonts_conf.exists() { 871 | env::set_var("FONTCONFIG_FILE", fonts_conf) 872 | } 873 | } 874 | _ => {} 875 | } 876 | } 877 | } 878 | } 879 | } 880 | } 881 | 882 | if !lib_path_data.is_empty() { 883 | lib_path_data = lib_path_data.trim().into(); 884 | library_path = lib_path_data 885 | .replace("\n", ":") 886 | .replace("+", &library_path) 887 | } 888 | 889 | drop(lib_path_data); 890 | 891 | let ld_library_path_env = &get_env_var("LD_LIBRARY_PATH"); 892 | if !ld_library_path_env.is_empty() { 893 | library_path += &format!(":{ld_library_path_env}") 894 | } 895 | 896 | for var_name in unset_envs { 897 | env::remove_var(var_name) 898 | } 899 | 900 | if get_env_var("SHARUN_PRINTENV") == "1" { 901 | env::remove_var("SHARUN_PRINTENV"); 902 | for (k, v) in env::vars() { 903 | eprintln!("{k}={v}") 904 | } 905 | } 906 | 907 | cfg_if! { 908 | if #[cfg(feature = "pyinstaller")] { 909 | let is_pyinstaller_elf = is_elf_section(&elf_bytes, "pydata").unwrap_or(false); 910 | let is_pyinstaller_dir = Path::new(&shared_bin).join("_internal").exists(); 911 | } else { 912 | let is_pyinstaller_elf = false; 913 | let is_pyinstaller_dir = false; 914 | } 915 | } 916 | 917 | let mut interpreter_args: Vec = Vec::new(); 918 | if !is_pyinstaller_elf || is_pyinstaller_dir || is_elf32_bin { 919 | interpreter_args.append(&mut vec![ 920 | CString::from_str(&interpreter.to_string_lossy()).unwrap(), 921 | CString::new("--library-path").unwrap(), 922 | CString::new(&*library_path).unwrap(), 923 | CString::new("--argv0").unwrap() 924 | ]); 925 | 926 | if is_pyinstaller_elf || is_elf32_bin { 927 | interpreter_args.push(CString::new(&*bin).unwrap()) 928 | } else { 929 | interpreter_args.push(CString::new(arg0_path.to_str().unwrap()).unwrap()) 930 | } 931 | 932 | let preload_path = PathBuf::from(format!("{sharun_dir}/.preload")); 933 | if preload_path.exists() { 934 | let data = read_to_string(&preload_path).unwrap_or_else(|err|{ 935 | eprintln!("Failed to read .preload file: {}: {err}", preload_path.display()); 936 | exit(1) 937 | }); 938 | let mut preload: Vec = vec![]; 939 | for string in data.trim().split("\n") { 940 | preload.push(string.trim().into()); 941 | } 942 | if !preload.is_empty() { 943 | interpreter_args.append(&mut vec![ 944 | CString::new("--preload").unwrap(), 945 | CString::new(preload.join(" ")).unwrap() 946 | ]) 947 | } 948 | } 949 | 950 | interpreter_args.push(CString::new(&*bin).unwrap()); 951 | for arg in &exec_args { 952 | interpreter_args.push(CString::from_str(arg).unwrap()) 953 | } 954 | } 955 | 956 | if is_pyinstaller_elf || is_elf32_bin { 957 | let err = if is_pyinstaller_dir || (!is_pyinstaller_elf && is_elf32_bin) { 958 | drop(elf_bytes); 959 | let interpreter_args: Vec = interpreter_args.iter() 960 | .map(|s| s.clone().into_string().unwrap()).skip(1).collect(); 961 | Command::new(interpreter) 962 | .args(interpreter_args) 963 | .envs(env::vars()) 964 | .exec() 965 | } else { 966 | set_interp(elf_bytes, &bin, interpreter.to_str().unwrap()) 967 | .unwrap_or_else(|err|{ 968 | eprintln!("Failed to set ELF interpreter: {}: {err}", &bin); 969 | exit(1) 970 | }); 971 | Command::new(&bin) 972 | .args(exec_args) 973 | .envs(env::vars()) 974 | .exec() 975 | }; 976 | eprint!("Failed to exec: {bin}: {err}"); 977 | exit(1) 978 | } else { 979 | drop(elf_bytes); 980 | let envs: Vec = env::vars() 981 | .map(|(key, value)| CString::new( 982 | format!("{key}={value}") 983 | ).unwrap()).collect(); 984 | 985 | userland_execve::exec( 986 | interpreter.as_path(), 987 | &interpreter_args, 988 | &envs, 989 | ) 990 | } 991 | } 992 | --------------------------------------------------------------------------------