Skip to content

Commit

Permalink
loopback: fix race condition opening loopback device
Browse files Browse the repository at this point in the history
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: #2038

Signed-off-by: Giuseppe Scrivano <[email protected]>
  • Loading branch information
giuseppe committed Jul 23, 2024
1 parent 7bd2cce commit 9f4cccd
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pkg/loopback/attach_loopback.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
49 changes: 49 additions & 0 deletions pkg/loopback/attach_test.go
Original file line number Diff line number Diff line change
@@ -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()
}

0 comments on commit 9f4cccd

Please sign in to comment.