Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ on:
name: rust-ci

env:
RUST_MSRV: "1.63"
RUST_MSRV: &RUST_MSRV "1.63"

jobs:
codespell:
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MSRV }}
toolchain: *RUST_MSRV
- uses: taiki-e/install-action@cargo-hack
- name: cargo check
run: >-
Expand Down Expand Up @@ -145,6 +145,25 @@ jobs:
run: cargo install --force cbindgen
- run: make validate-cbindgen

validate-elf-symbols:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust-version:
- *RUST_MSRV
- 1.72 # loongarch64 global_asm! stabilised
- 1.84 # arm64ex / s390x global_asm! stabilised
- 1.91 # loongarch32 global_asm! stabilised
- stable
- nightly
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust-version }}
- run: make validate-elf-symbols

rustdoc:
name: cargo doc
runs-on: ubuntu-latest
Expand Down Expand Up @@ -478,6 +497,7 @@ jobs:
- clippy
- check-lint-nohack
- validate-cbindgen
- validate-elf-symbols
- rustdoc
- doctest
- nextest
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ validate-cbindgen:
exit 1 ; \
fi

.PHONY: validate-elf-symbols
validate-elf-symbols: release
./hack/check-elf-symbols.sh ./target/release/libpathrs.so

.PHONY: test-rust-doctest
test-rust-doctest:
$(CARGO_LLVM_COV) --no-report --branch --doc
Expand Down
99 changes: 99 additions & 0 deletions hack/check-elf-symbols.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/bash
# SPDX-License-Identifier: MPL-2.0 OR LGPL-3.0-or-later
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 SUSE LLC
# Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
#
# == MPL-2.0 ==
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
# Alternatively, this Source Code Form may also (at your option) be used
# under the terms of the GNU Lesser General Public License Version 3, as
# described below:
#
# == LGPL-3.0-or-later ==
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

# check-elf-symbols.sh -- make sure that libpathrs.so only contains pathrs_*
# symbols with versions included, to ensure we do not regress our symbol
# versioning again.

set -Eeuo pipefail

SRC_ROOT="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")/..")"
ELF_FILE="$1"

function bail() {
echo "ERROR:" "$@" >&2
exit 1
}

# TODO: Do we actually want to also check the nodes referenced in src/capi/*?
# TODO: Also, once we make the old symbols optional this script will break.
readarray -t EXPECTED_VERSION_NODES < <(
grep -Eo "LIBPATHRS_[0-9]+(\.[0-9]+)+" "$SRC_ROOT/build.rs" | sort -u
)

# Make sure that we at least have LIBPATHRS_0.[12].
for node in LIBPATHRS_0.{1,2}; do
printf "%s\n" "${EXPECTED_VERSION_NODES[@]}" | grep -Fxq "$node" \
|| bail "$node node not found in build.rs"
done

echo "== ELF SYMBOL VERSION NODES =="

for node in "${EXPECTED_VERSION_NODES[@]}"; do
readelf -WV "$ELF_FILE" | grep -Fwq "$node" || \
bail "$node node not found in $ELF_FILE"
echo "$node node present in $ELF_FILE"
done

echo "== SYMBOL VERSIONS SUMMARY =="

SYMBOL_OBJDUMP="$(objdump -T "$ELF_FILE" | grep -F "pathrs_")"

[ -n "$SYMBOL_OBJDUMP" ] || bail "$ELF_FILE does not contain any pathrs_* symbols"
echo "$(wc -l <<<"$SYMBOL_OBJDUMP") pathrs symbols present in $ELF_FILE"

for node in "${EXPECTED_VERSION_NODES[@]}"; do
awk -v NODE="$node" '
BEGIN { num_symbols = 0 }
$(NF-1) == NODE {
symbols[$NF]++
num_symbols++
}
END {
print NODE, "symbols (" num_symbols, "total):"
if (num_symbols) {
for (sym in symbols) {
print " " sym
}
}
}
' <<<"$SYMBOL_OBJDUMP"
done

unversioned="$(awk '!($(NF-1) ~ /^LIBPATHRS_/)' <<<"$SYMBOL_OBJDUMP")"
[ -z "$unversioned" ] || {
echo "UNVERSIONED SYMBOLS ($(wc -l <<<"$unversioned") total):"
echo "$unversioned"
bail "$ELF_FILE contains unversioned symbols"
}

echo "++ ALL SYMBOLS ARE VERSIONED! ++"
84 changes: 40 additions & 44 deletions src/capi/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ use libc::{c_char, c_int, size_t};

/// Generate `.symver` entries for a given function.
///
/// On platforms without stabilised
/// On platforms without stabilised `global_asm!` implementations, this macro is
/// a no-op but will eventually work once they are supported.
///
/// ```ignore
/// # use pathrs::capi::utils::symver;
Expand All @@ -71,7 +72,7 @@ use libc::{c_char, c_int, size_t};
/// ```
macro_rules! symver {
() => {};
($(#[$meta:meta])* fn $implsym:ident <- ($symname:ident, version = $version:literal); $($tail:tt)*) => {
(@with-meta $(#[$meta:meta])* $($block:tt)+) => {
// Some architectures still have unstable ASM, which stops us from
// injecting the ".symver" section. You can see the list in
// LoweringContext::lower_inline_asm (compiler/rustc_asm_lowering).
Expand All @@ -82,57 +83,52 @@ macro_rules! symver {
target_arch = "x86_64",
target_arch = "riscv32",
target_arch = "riscv64",
//target_arch = "loongarch32", // MSRV(1.91?)
// These are only supported after our MSRV and have corresponding
// #[rustversion::since(1.XY)] tags below.
target_arch = "loongarch64",
target_arch = "arm64ec",
target_arch = "s390x",
target_arch = "loongarch32",
// TODO: Once stabilised, add these arches:
//target_arch = "powerpc",
//target_arch = "powerpc64",
//target_arch = "sparc64",
))]
#[::rustversion::attr(since(1.72), cfg(target_arch = "loongarch64"))]
#[::rustversion::attr(since(1.84), cfg(target_arch = "arm64ec"))]
#[::rustversion::attr(since(1.84), cfg(target_arch = "s390x"))]
// .symver $implsym, $symname@$version
#[cfg_attr(target_arch = "loongarch64", ::rustversion::since(1.72))]
#[cfg_attr(target_arch = "arm64ec", ::rustversion::since(1.84))]
#[cfg_attr(target_arch = "s390x", ::rustversion::since(1.84))]
#[cfg_attr(target_arch = "loongarch32", ::rustversion::since(1.91))]
$(#[$meta])*
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@",
$version,
)}
$($block)*
};
($(#[$meta:meta])* fn $implsym:ident <- ($symname:ident, version = $version:literal); $($tail:tt)*) => {
$crate::capi::utils::symver! {
@with-meta $(#[$meta])*
// .symver $implsym, $symname@$version
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@",
$version,
)}
}
$crate::capi::utils::symver! { $($tail)* }
};
($(#[$meta:meta])* fn $implsym:ident <- ($symname:ident, version = $version:literal, default); $($tail:tt)*) => {
// Some architectures still have unstable ASM, which stops us from
// injecting the ".symver" section. You can see the list in
// LoweringContext::lower_inline_asm (compiler/rustc_asm_lowering).
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "riscv32",
target_arch = "riscv64",
// TODO: Once stabilised, add these arches:
//target_arch = "loongarch32", // MSRV(1.91?)
//target_arch = "powerpc",
//target_arch = "powerpc64",
//target_arch = "sparc64",
))]
#[::rustversion::attr(since(1.72), cfg(target_arch = "loongarch64"))]
#[::rustversion::attr(since(1.84), cfg(target_arch = "arm64ec"))]
#[::rustversion::attr(since(1.84), cfg(target_arch = "s390x"))]
// .symver $implsym, $symname@@$version
$(#[$meta])*
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@@",
$version,
)}
$crate::capi::utils::symver! {
@with-meta $(#[$meta])*
// .symver $implsym, $symname@@$version
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@@",
$version,
)}
}
$crate::capi::utils::symver! { $($tail)* }
};
}
Expand Down
Loading