Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oci/cas/dir: Only Clean .umoci-* directories #198

Merged
merged 1 commit into from
Oct 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
responsibility of the caller (which was quite difficult if you were
unprivileged). This is a breaking change, but is in the error path so it's
not critical. openSUSE/umoci#174 openSUSE/umoci#187
- `umoci gc` now will no longer remove unknown files and directories that
aren't `flock(2)`ed, thus ensuring that any possible OCI image-spec
extensions or other users of an image being operated on will no longer
break. openSUSE/umoci#198

### Added
- `umoci repack` now supports `--refresh-bundle` which will update the
Expand Down
63 changes: 30 additions & 33 deletions oci/cas/dir/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"path/filepath"

"github.com/apex/log"
"github.com/openSUSE/umoci/oci/cas"
"github.com/opencontainers/go-digest"
imeta "github.com/opencontainers/image-spec/specs-go"
Expand Down Expand Up @@ -77,7 +78,7 @@ type dirEngine struct {

func (e *dirEngine) ensureTempDir() error {
if e.temp == "" {
tempDir, err := ioutil.TempDir(e.path, "tmp-")
tempDir, err := ioutil.TempDir(e.path, ".umoci-")
if err != nil {
return errors.Wrap(err, "create tempdir")
}
Expand Down Expand Up @@ -297,46 +298,42 @@ func (e *dirEngine) ListBlobs(ctx context.Context) ([]digest.Digest, error) {
// (this includes temporary files and directories not reachable from the CAS
// interface). This MUST NOT remove any blobs or references in the store.
func (e *dirEngine) Clean(ctx context.Context) error {
// Effectively we are going to remove every directory except the standard
// directories, unless they have a lock already.
fh, err := os.Open(e.path)
// Remove every .umoci directory that isn't flocked.
matches, err := filepath.Glob(filepath.Join(e.path, ".umoci-*"))
if err != nil {
return errors.Wrap(err, "open imagedir")
return errors.Wrap(err, "glob .umoci-*")
}
defer fh.Close()

children, err := fh.Readdir(-1)
if err != nil {
return errors.Wrap(err, "readdir imagedir")
for _, path := range matches {
err = e.cleanPath(ctx, path)
if err != nil && err != filepath.SkipDir {
return err
}
}

for _, child := range children {
// Skip any children that are expected to exist.
switch child.Name() {
case blobDirectory, indexFile, layoutFile:
continue
}
return nil
}

// Try to get a lock on the directory.
path := filepath.Join(e.path, child.Name())
cfh, err := os.Open(path)
if err != nil {
// Ignore errors because it might've been deleted underneath us.
continue
}
defer cfh.Close()
func (e *dirEngine) cleanPath(ctx context.Context, path string) error {
cfh, err := os.Open(path)
if err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "open for locking")
}
defer cfh.Close()

if err := unix.Flock(int(cfh.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
// If we fail to get a flock(2) then it's probably already locked,
// so we shouldn't touch it.
continue
}
defer unix.Flock(int(cfh.Fd()), unix.LOCK_UN)
if err := unix.Flock(int(cfh.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
// If we fail to get a flock(2) then it's probably already locked,
// so we shouldn't touch it.
return filepath.SkipDir
}
defer unix.Flock(int(cfh.Fd()), unix.LOCK_UN)

if err := os.RemoveAll(path); err != nil {
return errors.Wrap(err, "remove garbage path")
}
if err := os.RemoveAll(path); os.IsNotExist(err) {
return nil // somebody else beat us to it
} else if err != nil {
log.Warnf("failed to remove %s: %v", path, err)
return filepath.SkipDir
}
log.Debugf("cleaned %s", path)

return nil
}
Expand Down
32 changes: 22 additions & 10 deletions oci/cas/dir/dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,13 @@ func TestEngineGCLocking(t *testing.T) {
t.Errorf("engine doesn't have a tempdir after putting a blob!")
}

// Create tempdir to make sure things work.
removedDir, err := ioutil.TempDir(image, "testdir")
// Create umoci and other directories and files to make sure things work.
umociTestDir, err := ioutil.TempDir(image, ".umoci-dead-")
if err != nil {
t.Fatal(err)
}

otherTestDir, err := ioutil.TempDir(image, "other-")
if err != nil {
t.Fatal(err)
}
Expand All @@ -253,15 +258,22 @@ func TestEngineGCLocking(t *testing.T) {
t.Fatalf("unexpected error while GCing image: %+v", err)
}

// Make sure that engine.temp is still around.
if _, err := os.Lstat(engine.(*dirEngine).temp); err != nil {
t.Errorf("expected active direngine.temp to still exist after GC: %+v", err)
for _, path := range []string{
engine.(*dirEngine).temp,
otherTestDir,
} {
if _, err := os.Lstat(path); err != nil {
t.Errorf("expected %s to still exist after GC: %+v", path, err)
}
}

// Make sure that removedDir is still around
if _, err := os.Lstat(removedDir); err == nil {
t.Errorf("expected inactive temporary dir to not exist after GC")
} else if !os.IsNotExist(errors.Cause(err)) {
t.Errorf("expected IsNotExist for temporary dir after GC: %+v", err)
for _, path := range []string{
umociTestDir,
} {
if _, err := os.Lstat(path); err == nil {
t.Errorf("expected %s to not exist after GC", path)
} else if !os.IsNotExist(errors.Cause(err)) {
t.Errorf("expected IsNotExist for %s after GC: %+v", path, err)
}
}
}