diff --git a/pkg/lockfile/lockfile_unix.go b/pkg/lockfile/lockfile_unix.go index f0e46b6b6..b224e7b5c 100644 --- a/pkg/lockfile/lockfile_unix.go +++ b/pkg/lockfile/lockfile_unix.go @@ -5,6 +5,7 @@ package lockfile import ( "fmt" "os" + "path/filepath" "sync" "time" @@ -33,11 +34,30 @@ type lockfile struct { // descriptor. Note that the path is opened read-only when ro is set. If ro // is unset, openLock will open the path read-write and create the file if // necessary. -func openLock(path string, ro bool) (int, error) { +func openLock(path string, ro bool) (fd int, err error) { if ro { - return unix.Open(path, os.O_RDONLY|unix.O_CLOEXEC, 0) + fd, err = unix.Open(path, os.O_RDONLY|unix.O_CLOEXEC, 0) + } else { + fd, err = unix.Open(path, + os.O_RDWR|unix.O_CLOEXEC|os.O_CREATE, + unix.S_IRUSR|unix.S_IWUSR|unix.S_IRGRP|unix.S_IROTH, + ) } - return unix.Open(path, os.O_RDWR|unix.O_CLOEXEC|os.O_CREATE, unix.S_IRUSR|unix.S_IWUSR|unix.S_IRGRP|unix.S_IROTH) + + if err == nil { + return + } + + // the directory of the lockfile seems to be removed, try to create it + if os.IsNotExist(err) { + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return fd, errors.Wrap(err, "creating locker directory") + } + + return openLock(path, ro) + } + + return } // createLockerForPath returns a Locker object, possibly (depending on the platform) diff --git a/pkg/lockfile/lockfile_unix_test.go b/pkg/lockfile/lockfile_unix_test.go new file mode 100644 index 000000000..3354662ea --- /dev/null +++ b/pkg/lockfile/lockfile_unix_test.go @@ -0,0 +1,65 @@ +// +build linux solaris darwin freebsd + +package lockfile + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOpenLock(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + prepare func() (path string, readOnly bool) + }{ + { + name: "file exists (read/write)", + prepare: func() (string, bool) { + tempFile, err := ioutil.TempFile("", "lock-") + require.NoError(t, err) + return tempFile.Name(), false + }, + }, + { + name: "file exists readonly (readonly)", + prepare: func() (string, bool) { + tempFile, err := ioutil.TempFile("", "lock-") + require.NoError(t, err) + return tempFile.Name(), true + }, + }, + { + name: "base dir exists (read/write)", + prepare: func() (string, bool) { + tempDir := os.TempDir() + require.DirExists(t, tempDir) + return filepath.Join(tempDir, "test-1.lock"), false + }, + }, + { + name: "base dir not exists (read/write)", + prepare: func() (string, bool) { + tempDir, err := ioutil.TempDir("", "lock-") + require.NoError(t, err) + return filepath.Join(tempDir, "subdir", "test-1.lock"), false + }, + }, + } { + path, readOnly := tc.prepare() + + _, err := openLock(path, readOnly) + + require.NoError(t, err, tc.name) + + _, err = openLock(path, readOnly) + require.NoError(t, err) + + require.Nil(t, os.RemoveAll(path)) + } +}