From 9f4cccdf08e8006cd9c39222c1935ffe890a7b72 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Tue, 23 Jul 2024 12:14:01 +0200 Subject: [PATCH] loopback: fix race condition opening loopback device the loopback device file could be already used/removed by another process. Since the process is inherently racy, just grab the next available index and try again until it succeeds. Closes: https://github.com/containers/storage/issues/2038 Signed-off-by: Giuseppe Scrivano --- pkg/loopback/attach_loopback.go | 7 +++++ pkg/loopback/attach_test.go | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 pkg/loopback/attach_test.go diff --git a/pkg/loopback/attach_loopback.go b/pkg/loopback/attach_loopback.go index bfdafcdf78..92f2f2186a 100644 --- a/pkg/loopback/attach_loopback.go +++ b/pkg/loopback/attach_loopback.go @@ -58,6 +58,13 @@ func openNextAvailableLoopback(index int, sparseName string, sparseFile *os.File loopFile, err = os.OpenFile(target, os.O_RDWR, 0o644) if err != nil { if errors.Is(err, fs.ErrNotExist) { + // Another process could have taken the loopback device in the meantime. So repeat + // the process with the next loopback device. + index, err = getNextFreeLoopbackIndex() + if err != nil { + logrus.Debugf("Error retrieving the next available loopback: %s", err) + return nil, err + } continue } logrus.Errorf("Opening loopback device: %s", err) diff --git a/pkg/loopback/attach_test.go b/pkg/loopback/attach_test.go new file mode 100644 index 0000000000..8ae2f1143d --- /dev/null +++ b/pkg/loopback/attach_test.go @@ -0,0 +1,49 @@ +//go:build linux && cgo +// +build linux,cgo + +package loopback + +import ( + "os" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + maxDevicesPerGoroutine = 1000 + maxGoroutines = 10 +) + +func TestAttachLoopbackDeviceRace(t *testing.T) { + createLoopbackDevice := func() { + // Create a file to use as a backing file + f, err := os.CreateTemp(t.TempDir(), "loopback-test") + require.NoError(t, err) + defer f.Close() + + defer os.Remove(f.Name()) + + lp, err := AttachLoopDevice(f.Name()) + assert.NoError(t, err) + assert.NotNil(t, lp, "loopback device file should not be nil") + if lp != nil { + lp.Close() + } + } + + wg := sync.WaitGroup{} + + for i := 0; i < maxGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < maxDevicesPerGoroutine; i++ { + createLoopbackDevice() + } + }() + } + wg.Wait() +}