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
7 changes: 5 additions & 2 deletions xunix/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "/") {
Expand All @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions xunix/gpu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Loading