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
9 changes: 9 additions & 0 deletions .github/workflows/bindings-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ jobs:
- "1.18"
- "oldstable"
- "stable"
# CGo's handling of C constants differs between compilers, so make sure
# the bindings build with both of the mainstream C compilers.
cc:
- gcc
- clang
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand All @@ -92,7 +97,11 @@ jobs:
go-version: ${{ matrix.go-version }}
check-latest: true
# Run smoke-tests.
# NOTE: CC is only set here (and not for the whole job) so that we don't
# affect how libpathrs itself is built.
- run: make -C examples/go smoke-test
env:
CC: ${{ matrix.cc }}

go-complete:
needs:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
library installation were incorrectly called `--disable-dynamic` rather than
autoconf's `--disable-shared`. We have added new `--{enable,disable}-shared`
flags and the old ones are kept for compatibility.
- go-pathrs: Fix a build failure when building the Go bindings with Clang.
CGo's handling of the signedness of C constants is compiler-dependent, and
the internal sanity check of the `PATHRS_PROC_*` values only handled the way
GCC does it (which made Clang builds fail with "untyped int constant
... overflows"). (#401)

## [0.2.5] - 2026-06-17 ##

Expand Down
37 changes: 25 additions & 12 deletions go-pathrs/internal/libpathrs/libpathrs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,12 @@ func InRootHardlink(oldRootFd uintptr, oldPath string, newRootFd uintptr, newPat
// ProcBase is pathrs_proc_base_t (uint64_t).
type ProcBase C.pathrs_proc_base_t

// FIXME: We need to open-code the constants because CGo unfortunately will
// implicitly convert any non-literal constants (i.e. those resolved using gcc)
// to signed integers. See <https://github.com/golang/go/issues/39136> for some
// more information on the underlying issue (though.
// FIXME: We need to open-code the constants because CGo's handling of
// non-literal constants (i.e. those resolved using the C compiler) that don't
// fit in an int64 is compiler-dependent -- GCC gives us implicitly-converted
// (negative) signed integers while Clang gives us the actual unsigned values.
// See <https://github.com/golang/go/issues/39136> for some more information on
// the underlying issue.
const (
// ProcRoot is PATHRS_PROC_ROOT.
ProcRoot ProcBase = 0xFFFF_FFFE_7072_6F63 // C.PATHRS_PROC_ROOT
Expand All @@ -246,25 +248,36 @@ func assertEqual[T comparable](a, b T, msg string) {
}
}

// u64Mask is used to convert the C constants below into plain uint64 values,
// regardless of how CGo decided to interpret their signedness.
//
// As explained above, CGo gives us the PATHRS_PROC_* values as negative
// (signed) constants with GCC and as (unsigned) constants that overflow int64
// with Clang, so neither int64 nor uint64 can hold them with both compilers.
// However, Go constant expressions are arbitrary-precision and bitwise
// operations on negative constants operate on their infinite two's complement
// representation, so masking off the lower 64 bits yields the same uint64
// value no matter which compiler CGo used.
const u64Mask = 0xFFFF_FFFF_FFFF_FFFF

// Verify that the values above match the actual C values. Unfortunately, Go
// only allows us to forcefully cast int64 to uint64 if you use a temporary
// variable, which means we cannot do it in a const context and thus need to do
// it at runtime (even though it is a check that fundamentally could be done at
// has no way of doing compile-time assertions, so we need to do it at runtime
// (even though it is a check that fundamentally could be done at
// compile-time)...
func init() {
var (
actualProcRoot int64 = C.PATHRS_PROC_ROOT
actualProcSelf int64 = C.PATHRS_PROC_SELF
actualProcThreadSelf int64 = C.PATHRS_PROC_THREAD_SELF
actualProcRoot uint64 = C.PATHRS_PROC_ROOT & u64Mask
actualProcSelf uint64 = C.PATHRS_PROC_SELF & u64Mask
actualProcThreadSelf uint64 = C.PATHRS_PROC_THREAD_SELF & u64Mask
)

assertEqual(ProcRoot, ProcBase(actualProcRoot), "PATHRS_PROC_ROOT")
assertEqual(ProcSelf, ProcBase(actualProcSelf), "PATHRS_PROC_SELF")
assertEqual(ProcThreadSelf, ProcBase(actualProcThreadSelf), "PATHRS_PROC_THREAD_SELF")

var (
actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK
actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID
actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK & u64Mask
actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID & u64Mask
)

assertEqual(ProcBaseTypeMask, ProcBase(actualProcBaseTypeMask), "__PATHRS_PROC_TYPE_MASK")
Expand Down
Loading