Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
difs,interfaces/mount: add support for locking namespaces #3311
Merged
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,75 @@ | ||
| +// -*- 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 mount | ||
| + | ||
| +import ( | ||
| + "fmt" | ||
| + "os" | ||
| + "path/filepath" | ||
| + "syscall" | ||
| + | ||
| + "github.com/snapcore/snapd/dirs" | ||
| +) | ||
| + | ||
| +// lockFileName returns the name of the lock file for the given snap. | ||
| +func lockFileName(snapName string) string { | ||
| + return filepath.Join(dirs.SnapRunLockDir, fmt.Sprintf("%s.lock", snapName)) | ||
| +} | ||
| + | ||
| +// NSLock describes a lock on a mount namespace of a particular snap. | ||
| +type NSLock struct { | ||
| + file *os.File | ||
| + fname string | ||
| +} | ||
| + | ||
| +// OpenLock creates and opens a lock file associated with a particular snap. | ||
| +func OpenLock(snapName string) (*NSLock, error) { | ||
| + if err := os.MkdirAll(dirs.SnapRunLockDir, 0700); err != nil { | ||
| + return nil, fmt.Errorf("cannot create lock directory: %s", err) | ||
| + } | ||
| + fname := lockFileName(snapName) | ||
| + mode := syscall.O_RDWR | syscall.O_CREAT | syscall.O_NOFOLLOW | syscall.O_CLOEXEC | ||
| + file, err := os.OpenFile(fname, mode, os.FileMode(0600)) | ||
| + if err != nil { | ||
| + return nil, err | ||
| + } | ||
| + l := &NSLock{fname: fname, file: file} | ||
| + return l, nil | ||
| +} | ||
| + | ||
| +// Path returns the path of the lock file. | ||
| +func (l *NSLock) Path() string { | ||
| + return l.fname | ||
| +} | ||
| + | ||
| +// Close closes the lock, unlocking it automatically if needed. | ||
| +func (l *NSLock) Close() error { | ||
| + return l.file.Close() | ||
zyga
Contributor
|
||
| +} | ||
| + | ||
| +// Lock acquires an exclusive lock on the mount namespace. | ||
| +func (l *NSLock) Lock() error { | ||
| + return syscall.Flock(int(l.file.Fd()), syscall.LOCK_EX) | ||
|
|
||
| +} | ||
| + | ||
| +// Unlock releases an acquired lock. | ||
| +func (l *NSLock) Unlock() error { | ||
| + return syscall.Flock(int(l.file.Fd()), syscall.LOCK_UN) | ||
|
|
||
| +} | ||
| @@ -0,0 +1,111 @@ | ||
| +// -*- 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 mount_test | ||
| + | ||
| +import ( | ||
| + "os" | ||
| + "os/exec" | ||
| + | ||
| + . "gopkg.in/check.v1" | ||
| + | ||
| + "github.com/snapcore/snapd/dirs" | ||
| + "github.com/snapcore/snapd/interfaces/mount" | ||
| +) | ||
| + | ||
| +type lockSuite struct{} | ||
| + | ||
| +var _ = Suite(&lockSuite{}) | ||
| + | ||
| +func (s *lockSuite) SetUpTest(c *C) { | ||
| + dirs.SetRootDir(c.MkDir()) | ||
| +} | ||
| + | ||
| +func (s *lockSuite) TearDownTest(c *C) { | ||
| + dirs.SetRootDir("") | ||
| +} | ||
| + | ||
| +// Test that opening and closing a lock works as expected. | ||
| +func (s *lockSuite) TestOpenLock(c *C) { | ||
| + lock, err := mount.OpenLock("name") | ||
| + c.Assert(err, IsNil) | ||
| + defer lock.Close() | ||
| + | ||
| + _, err = os.Stat(lock.Path()) | ||
| + c.Assert(err, IsNil) | ||
| +} | ||
| + | ||
| +// Test that Lock and Unlock work as expected. | ||
| +func (s *lockSuite) TestLockUnlockWorks(c *C) { | ||
| + lock, err := mount.OpenLock("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 locking a locked lock does nothing. | ||
| +func (s *lockSuite) TestLockLocked(c *C) { | ||
| + lock, err := mount.OpenLock("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 *lockSuite) TestUnlockUnlocked(c *C) { | ||
| + lock, err := mount.OpenLock("name") | ||
| + c.Assert(err, IsNil) | ||
| + defer lock.Close() | ||
| + | ||
| + c.Assert(lock.Unlock(), IsNil) | ||
| +} | ||
| + | ||
| +// Test that locking or unlocking a closed lock fails. | ||
| +func (s *lockSuite) TestUsingClosedLock(c *C) { | ||
| + lock, err := mount.OpenLock("name") | ||
| + c.Assert(err, IsNil) | ||
| + lock.Close() | ||
| + | ||
| + c.Assert(lock.Lock(), ErrorMatches, "bad file descriptor") | ||
| + c.Assert(lock.Unlock(), ErrorMatches, "bad file descriptor") | ||
| +} |
Worth to check if the file is really open and return an error if it is not? If l.file is nil then this will result in a SEGV.