From 924c3075300cf17b314d17287070bdb87970bf70 Mon Sep 17 00:00:00 2001 From: "John R. Lenton" Date: Fri, 5 Oct 2018 12:38:37 +0100 Subject: [PATCH] osutil, interfaces/apparmor: add and use of osutil.UnlinkMany This adds `osutil.UnlinkMany`, which is mostly just a convenience function to remove multiple files form a single directory. It also uses it from interfaces/apparmor. --- interfaces/apparmor/apparmor.go | 9 +-- osutil/unlink.go | 59 +++++++++++++++++++ osutil/unlink_darwin.go | 29 +++++++++ osutil/unlink_linux.go | 27 +++++++++ osutil/unlink_test.go | 100 ++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 osutil/unlink.go create mode 100644 osutil/unlink_darwin.go create mode 100644 osutil/unlink_linux.go create mode 100644 osutil/unlink_test.go diff --git a/interfaces/apparmor/apparmor.go b/interfaces/apparmor/apparmor.go index 4ae09a6c616..64585c2091b 100644 --- a/interfaces/apparmor/apparmor.go +++ b/interfaces/apparmor/apparmor.go @@ -30,7 +30,6 @@ import ( "io" "os" "os/exec" - "path/filepath" "strings" "github.com/snapcore/snapd/osutil" @@ -88,12 +87,8 @@ func unloadProfiles(names []string, cacheDir string) error { if err != nil { return fmt.Errorf("cannot unload apparmor profile: %s\napparmor_parser output:\n%s", err, string(output)) } - for _, name := range names { - err = os.Remove(filepath.Join(cacheDir, name)) - // It is not an error if the cache file wasn't there to remove. - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("cannot remove apparmor profile cache: %s", err) - } + if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("cannot remove apparmor profile cache: %s", err) } return nil } diff --git a/osutil/unlink.go b/osutil/unlink.go new file mode 100644 index 00000000000..fb8a699b810 --- /dev/null +++ b/osutil/unlink.go @@ -0,0 +1,59 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package osutil + +import ( + "os" + "syscall" +) + +// UnlinkMany removes multiple files from a single directory. +// +// If dirname is not a directory, this will fail. +// +// This will abort at the first removal error (but ENOENT is ignored). +// +// Filenames must refer to files. They don't necessarily have to be +// relative paths to the given dirname, but if they aren't why are you +// using this function? +// +// Errors are *os.PathError, for convenience +func UnlinkMany(dirname string, filenames []string) error { + dirfd, err := syscall.Open(dirname, syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_DIRECTORY|0x200000, 0) + if err != nil { + return &os.PathError{ + Op: "open", + Path: dirname, + Err: err, + } + } + defer syscall.Close(dirfd) + + for _, filename := range filenames { + if err = sysUnlinkat(dirfd, filename); err != nil && err != syscall.ENOENT { + return &os.PathError{ + Op: "remove", + Path: filename, + Err: err, + } + } + } + return nil +} diff --git a/osutil/unlink_darwin.go b/osutil/unlink_darwin.go new file mode 100644 index 00000000000..cfa562c3488 --- /dev/null +++ b/osutil/unlink_darwin.go @@ -0,0 +1,29 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package osutil + +import ( + "golang.org/x/sys/unix" +) + +// sys/unix _does_ expose flags, so we need to wrap it +func sysUnlinkat(dirfd int, path string) error { + return unix.Unlinkat(dirfd, path, 0) +} diff --git a/osutil/unlink_linux.go b/osutil/unlink_linux.go new file mode 100644 index 00000000000..a02dcee2221 --- /dev/null +++ b/osutil/unlink_linux.go @@ -0,0 +1,27 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package osutil + +import ( + "syscall" +) + +// syscall.Unlinkat calls unlinkat(2) _without_ AT_REMOVEDIR, which is exactly what we want +var sysUnlinkat = syscall.Unlinkat diff --git a/osutil/unlink_test.go b/osutil/unlink_test.go new file mode 100644 index 00000000000..05016dcecd0 --- /dev/null +++ b/osutil/unlink_test.go @@ -0,0 +1,100 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package osutil_test + +import ( + "os" + "path/filepath" + "sort" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/osutil" +) + +type unlinkSuite struct { + d string +} + +var _ = check.Suite(&unlinkSuite{}) + +func (s *unlinkSuite) checkDirnames(c *check.C, names []string) { + dir, err := os.Open(s.d) + c.Assert(err, check.IsNil) + defer dir.Close() + found, err := dir.Readdirnames(-1) + c.Assert(err, check.IsNil) + sort.Strings(names) + sort.Strings(found) + c.Check(found, check.DeepEquals, names) +} + +func (s *unlinkSuite) SetUpTest(c *check.C) { + s.d = c.MkDir() + s.mkFixture(c) +} + +func (s *unlinkSuite) mkFixture(c *check.C) { + for _, fname := range []string{"foo", "bar", "baz", "quux"} { + f, err := os.Create(filepath.Join(s.d, fname)) + if err == nil { + f.Close() + } else if !os.IsExist(err) { + c.Fatal(err) + } + } + if err := os.Mkdir(filepath.Join(s.d, "dir"), 0700); err != nil && !os.IsExist(err) { + c.Fatal(err) + } +} + +func (s *unlinkSuite) TestUnlinkMany(c *check.C) { + c.Assert(osutil.UnlinkMany(s.d, []string{"bar", "does-not-exist", "baz"}), check.IsNil) + + s.checkDirnames(c, []string{"foo", "quux", "dir"}) +} + +func (s *unlinkSuite) TestUnlinkManyFails(c *check.C) { + type T struct { + dirname string + filenames []string + expected string + } + tests := []T{ + { + dirname: filepath.Join(s.d, "does-not-exist"), + filenames: []string{"bar", "baz"}, + expected: `open /tmp/.*/does-not-exist: no such file or directory`, + }, { + dirname: filepath.Join(s.d, "foo"), + filenames: []string{"bar", "baz"}, + expected: `open /tmp/.*/foo: not a directory`, + }, { + dirname: s.d, + filenames: []string{"bar", "dir", "baz"}, + expected: `remove dir: is a directory`, + }, + } + + for i, test := range tests { + c.Check(osutil.UnlinkMany(test.dirname, test.filenames), check.ErrorMatches, test.expected, check.Commentf("%d", i)) + s.mkFixture(c) + } +}