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()