1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2017 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package osutil_test
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/osutil"
)
type flockSuite struct{}
var _ = Suite(&flockSuite{})
// Test that an existing lock file can be opened.
func (s *flockSuite) TestOpenExistingLockForReading(c *C) {
fname := filepath.Join(c.MkDir(), "name")
lock, err := osutil.OpenExistingLockForReading(fname)
c.Assert(err, ErrorMatches, ".* no such file or directory")
c.Assert(lock, IsNil)
lock, err = osutil.NewFileLockWithMode(fname, 0644)
c.Assert(err, IsNil)
lock.Close()
// Having created the lock above, we can now open it correctly.
lock, err = osutil.OpenExistingLockForReading(fname)
c.Assert(err, IsNil)
defer lock.Close()
// The lock file is read-only though.
file := lock.File()
defer file.Close()
n, err := file.Write([]byte{1, 2, 3})
// write(2) returns EBADF if the file descriptor is read only.
c.Assert(err, ErrorMatches, ".* bad file descriptor")
c.Assert(n, Equals, 0)
}
// Test that opening and closing a lock works as expected, and that the mode is right.
func (s *flockSuite) TestNewFileLockWithMode(c *C) {
lock, err := osutil.NewFileLockWithMode(filepath.Join(c.MkDir(), "name"), 0644)
c.Assert(err, IsNil)
defer lock.Close()
fi, err := os.Stat(lock.Path())
c.Assert(err, IsNil)
c.Assert(fi.Mode().Perm(), Equals, os.FileMode(0644))
}
// Test that opening and closing a lock works as expected.
func (s *flockSuite) TestNewFileLock(c *C) {
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
defer lock.Close()
fi, err := os.Stat(lock.Path())
c.Assert(err, IsNil)
c.Assert(fi.Mode().Perm(), Equals, os.FileMode(0600))
}
// Test that opening and closing a lock works as expected, and that the mode is right.
func (s *flockSuite) TestNewFileLockWithFile(c *C) {
myfile, err := os.OpenFile(filepath.Join(c.MkDir(), "name"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
c.Assert(err, IsNil)
lock := osutil.NewFileLockWithFile(myfile)
defer lock.Close()
fi, err := os.Stat(lock.Path())
c.Assert(err, IsNil)
c.Assert(fi.Mode().Perm(), Equals, os.FileMode(0600))
}
// Test that we can access the underlying open file.
func (s *flockSuite) TestFile(c *C) {
fname := filepath.Join(c.MkDir(), "name")
lock, err := osutil.NewFileLock(fname)
c.Assert(err, IsNil)
defer lock.Close()
f := lock.File()
c.Assert(f, NotNil)
c.Check(f.Name(), Equals, fname)
}
func flockSupportsConflictExitCodeSwitch(c *C) bool {
output, err := exec.Command("flock", "--help").CombinedOutput()
c.Assert(err, IsNil)
return bytes.Contains(output, []byte("--conflict-exit-code"))
}
// Test that Lock and Unlock work as expected.
func (s *flockSuite) TestLockUnlockWorks(c *C) {
if !flockSupportsConflictExitCodeSwitch(c) {
c.Skip("flock too old for this test")
}
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
defer lock.Close()
// Run a flock command in another process, it should succeed because it can
// lock the lock as we didn't do it yet.
cmd := exec.Command("flock", "--exclusive", "--nonblock", lock.Path(), "true")
c.Assert(cmd.Run(), IsNil)
// Lock the lock.
c.Assert(lock.Lock(), IsNil)
// Run a flock command in another process, it should fail with the distinct
// error code because we hold the lock already and we asked it not to block.
cmd = exec.Command("flock", "--exclusive", "--nonblock",
"--conflict-exit-code", "2", lock.Path(), "true")
c.Assert(cmd.Run(), ErrorMatches, "exit status 2")
// Unlock the lock.
c.Assert(lock.Unlock(), IsNil)
// Run a flock command in another process, it should succeed because it can
// grab the lock again now.
cmd = exec.Command("flock", "--exclusive", "--nonblock", lock.Path(), "true")
c.Assert(cmd.Run(), IsNil)
}
// Test that ReadLock and Unlock work as expected.
func (s *flockSuite) TestReadLockUnlockWorks(c *C) {
if !flockSupportsConflictExitCodeSwitch(c) {
c.Skip("flock too old for this test")
}
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
defer lock.Close()
// Run a flock command in another process, it should succeed because it can
// lock the lock as we didn't do it yet.
cmd := exec.Command("flock", "--exclusive", "--nonblock", lock.Path(), "true")
c.Assert(cmd.Run(), IsNil)
// Grab a shared lock.
c.Assert(lock.ReadLock(), IsNil)
// Run a flock command in another process, it should fail with the distinct
// error code because we hold a shared lock already and we asked it not to block.
cmd = exec.Command("flock", "--exclusive", "--nonblock",
"--conflict-exit-code", "2", lock.Path(), "true")
c.Assert(cmd.Run(), ErrorMatches, "exit status 2")
// Run a flock command in another process, it should succeed because we
// hold a shared lock and those do not prevent others from acquiring a
// shared lock.
cmd = exec.Command("flock", "--shared", "--nonblock",
"--conflict-exit-code", "2", lock.Path(), "true")
c.Assert(cmd.Run(), IsNil)
// Unlock the lock.
c.Assert(lock.Unlock(), IsNil)
// Run a flock command in another process, it should succeed because it can
// grab the lock again now.
cmd = exec.Command("flock", "--exclusive", "--nonblock", lock.Path(), "true")
c.Assert(cmd.Run(), IsNil)
}
// Test that locking a locked lock does nothing.
func (s *flockSuite) TestLockLocked(c *C) {
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
defer lock.Close()
// NOTE: technically this replaces the lock type but we only use LOCK_EX.
c.Assert(lock.Lock(), IsNil)
c.Assert(lock.Lock(), IsNil)
}
// Test that unlocking an unlocked lock does nothing.
func (s *flockSuite) TestUnlockUnlocked(c *C) {
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
defer lock.Close()
c.Assert(lock.Unlock(), IsNil)
}
// Test that locking or unlocking a closed lock fails.
func (s *flockSuite) TestUsingClosedLock(c *C) {
lock, err := osutil.NewFileLock(filepath.Join(c.MkDir(), "name"))
c.Assert(err, IsNil)
lock.Close()
c.Assert(lock.Lock(), ErrorMatches, "bad file descriptor")
c.Assert(lock.Unlock(), ErrorMatches, "bad file descriptor")
}
// Test that non-blocking locking reports error on pre-acquired lock.
func (s *flockSuite) TestLockUnlockNonblockingWorks(c *C) {
// Use the "flock" command to grab a lock for 9999 seconds in another process.
lockPath := filepath.Join(c.MkDir(), "lock")
sleeperKillerPath := filepath.Join(c.MkDir(), "pid")
// we can't use --no-fork because we still support 14.04
cmd := exec.Command("flock", "--exclusive", lockPath, "-c", fmt.Sprintf(`echo "kill $$" > %s && exec sleep 30`, sleeperKillerPath))
// flock uses the env variable 'SHELL' to run the passed in command. a non-posix
// shell will not understand $$. we can force flock to use its default by unsetting
// the variable
cmd.Env = append(cmd.Env, "SHELL=")
c.Assert(cmd.Start(), IsNil)
defer func() { exec.Command("/bin/sh", sleeperKillerPath).Run() }()
// Give flock some chance to create the lock file.
for i := 0; i < 10; i++ {
if osutil.FileExists(lockPath) {
break
}
time.Sleep(time.Millisecond * 300)
}
// Try to acquire the same lock file and see that it is busy.
lock, err := osutil.NewFileLock(lockPath)
c.Assert(err, IsNil)
c.Assert(lock, NotNil)
defer lock.Close()
c.Assert(lock.TryLock(), Equals, osutil.ErrAlreadyLocked)
}
|