From f064558a3c969cc4989e0a2038a7758a512e21fa Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:41:43 +0000 Subject: [PATCH] fix: retain GPU library paths when symlinks resolve outside mountpoint When the host uses /usr/lib64 (common on RHEL/Amazon Linux) and it is mounted into the outer container at /var/coder/usr/lib, symlinks inside the directory may use absolute paths referencing the original host path (e.g. /usr/lib64/libnvidia-ml.so.545.23.08). The recursiveSymlinks function would discard the entire symlink chain (including the original file within the mountpoint) when it encountered a target outside the mountpoint, returning nil. This changes the behavior to return all paths collected so far within the mountpoint instead of discarding them. The GPU libraries are still valid bind mount sources at their paths within the mountpoint. Fixes #164 --- xunix/gpu.go | 7 +++-- xunix/gpu_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/xunix/gpu.go b/xunix/gpu.go index a9129d5..60090f6 100644 --- a/xunix/gpu.go +++ b/xunix/gpu.go @@ -156,7 +156,10 @@ func usrLibGPUs(ctx context.Context, log slog.Logger, usrLibDir string) ([]mount // recursiveSymlinks returns all of the paths in the chain of symlinks starting // at `path`. If `path` isn't a symlink, only `path` is returned. If at any -// point the symlink chain goes outside of `mountpoint` then nil is returned. +// point the symlink chain goes outside of `mountpoint`, the paths collected so +// far (within the mountpoint) are returned. This handles cases where host +// libraries (e.g. from /usr/lib64) contain symlinks with absolute targets +// referencing the original host path rather than the mounted path. // Despite its name it's interestingly enough not implemented recursively. func recursiveSymlinks(afs FS, mountpoint string, path string) ([]string, error) { if !strings.HasSuffix(mountpoint, "/") { @@ -166,7 +169,7 @@ func recursiveSymlinks(afs FS, mountpoint string, path string) ([]string, error) paths := []string{} for { if !strings.HasPrefix(path, mountpoint) { - return nil, nil + break } stat, err := afs.LStat(path) diff --git a/xunix/gpu_test.go b/xunix/gpu_test.go index 4324fcf..adb8606 100644 --- a/xunix/gpu_test.go +++ b/xunix/gpu_test.go @@ -132,6 +132,72 @@ func TestGPUs(t *testing.T) { }) } +func TestGPUs_UsrLib64Symlinks(t *testing.T) { + t.Parallel() + + // This test simulates the scenario where the host has /usr/lib64 mounted + // into the outer container at /var/coder/usr/lib. On RHEL/Amazon Linux + // systems, symlinks inside /usr/lib64 may use absolute paths referencing + // /usr/lib64/... which don't match the mounted path. Previously, + // recursiveSymlinks would discard these files entirely. + var ( + ctx = context.Background() + afs = xunix.GetFS(ctx) + mounter = &mount.FakeMounter{} + log = slogtest.Make(t, nil) + tmpDir = t.TempDir() + ) + + ctx = xunix.WithFS(ctx, afs) + ctx = xunix.WithMounter(ctx, mounter) + + // Create the real library file. + realLib := filepath.Join(tmpDir, "libnvidia-ml.so.545.23.08") + _, err := os.Create(realLib) + require.NoError(t, err) + + // Create a symlink with an absolute path pointing OUTSIDE the mountpoint. + // This simulates what happens when /usr/lib64 contains: + // libnvidia-ml.so.1 -> /usr/lib64/libnvidia-ml.so.545.23.08 + // but is mounted at /var/coder/usr/lib. + symlink := filepath.Join(tmpDir, "libnvidia-ml.so.1") + err = os.Symlink("/usr/lib64/libnvidia-ml.so.545.23.08", symlink) + require.NoError(t, err) + + // Create another symlink in the chain. + symlink2 := filepath.Join(tmpDir, "libnvidia-ml.so") + err = os.Symlink("libnvidia-ml.so.1", symlink2) + require.NoError(t, err) + + // Create a non-GPU library to verify it's excluded. + _, err = os.Create(filepath.Join(tmpDir, "libcurl.so")) + require.NoError(t, err) + + devices, binds, err := xunix.GPUs(ctx, log, tmpDir) + require.NoError(t, err) + require.Empty(t, devices) + + // All three nvidia library paths should be detected as bind mounts. + require.Contains(t, binds, mount.MountPoint{ + Path: realLib, + Opts: []string{"ro"}, + }) + require.Contains(t, binds, mount.MountPoint{ + Path: symlink, + Opts: []string{"ro"}, + }) + require.Contains(t, binds, mount.MountPoint{ + Path: symlink2, + Opts: []string{"ro"}, + }) + + // Non-GPU library should not be included. + require.NotContains(t, binds, mount.MountPoint{ + Path: filepath.Join(tmpDir, "libcurl.so"), + Opts: []string{"ro"}, + }) +} + func Test_SameDirSymlinks(t *testing.T) { t.Parallel()