Skip to content

Commit 36e4dc6

Browse files
committed
Ensure bundle removal is atomic
This makes bundle removal atomic by first renaming the bundle and working directories to a hidden path before removing the underlying directories. Closes containerd#2567 Closes containerd#2327 Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
1 parent 9b366b2 commit 36e4dc6

File tree

4 files changed

+36
-6
lines changed

4 files changed

+36
-6
lines changed

runtime/v1/linux/bundle.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package linux
2020

2121
import (
2222
"context"
23+
"fmt"
2324
"io/ioutil"
2425
"os"
2526
"path/filepath"
@@ -114,12 +115,12 @@ func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientO
114115

115116
// Delete deletes the bundle from disk
116117
func (b *bundle) Delete() error {
117-
err := os.RemoveAll(b.path)
118+
err := atomicDelete(b.path)
118119
if err == nil {
119-
return os.RemoveAll(b.workDir)
120+
return atomicDelete(b.workDir)
120121
}
121122
// error removing the bundle path; still attempt removing work dir
122-
err2 := os.RemoveAll(b.workDir)
123+
err2 := atomicDelete(b.workDir)
123124
if err2 == nil {
124125
return err
125126
}
@@ -152,3 +153,13 @@ func (b *bundle) shimConfig(namespace string, c *Config, runcOptions *runctypes.
152153
SystemdCgroup: systemdCgroup,
153154
}
154155
}
156+
157+
// atomicDelete renames the path to a hidden file before removal
158+
func atomicDelete(path string) error {
159+
// create a hidden dir for an atomic removal
160+
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
161+
if err := os.Rename(path, atomicPath); err != nil {
162+
return err
163+
}
164+
return os.RemoveAll(atomicPath)
165+
}

runtime/v1/linux/runtime.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ func (r *Runtime) restoreTasks(ctx context.Context) ([]*Task, error) {
290290
continue
291291
}
292292
name := namespace.Name()
293+
// skip hidden directories
294+
if len(name) > 0 && name[0] == '.' {
295+
continue
296+
}
293297
log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace")
294298
tasks, err := r.loadTasks(ctx, name)
295299
if err != nil {

runtime/v2/bundle.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package v2
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"io/ioutil"
2223
"os"
2324
"path/filepath"
@@ -114,20 +115,30 @@ type Bundle struct {
114115
// Delete a bundle atomically
115116
func (b *Bundle) Delete() error {
116117
work, werr := os.Readlink(filepath.Join(b.Path, "work"))
117-
err := os.RemoveAll(b.Path)
118+
err := atomicDelete(b.Path)
118119
if err == nil {
119120
if werr == nil {
120-
return os.RemoveAll(work)
121+
return atomicDelete(work)
121122
}
122123
return nil
123124
}
124125
// error removing the bundle path; still attempt removing work dir
125126
var err2 error
126127
if werr == nil {
127-
err2 = os.RemoveAll(work)
128+
err2 = atomicDelete(work)
128129
if err2 == nil {
129130
return err
130131
}
131132
}
132133
return errors.Wrapf(err, "failed to remove both bundle and workdir locations: %v", err2)
133134
}
135+
136+
// atomicDelete renames the path to a hidden file before removal
137+
func atomicDelete(path string) error {
138+
// create a hidden dir for an atomic removal
139+
atomicPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
140+
if err := os.Rename(path, atomicPath); err != nil {
141+
return err
142+
}
143+
return os.RemoveAll(atomicPath)
144+
}

runtime/v2/manager.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ func (m *TaskManager) loadExistingTasks(ctx context.Context) error {
145145
continue
146146
}
147147
ns := nsd.Name()
148+
// skip hidden directories
149+
if len(ns) > 0 && ns[0] == '.' {
150+
continue
151+
}
148152
log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace")
149153
if err := m.loadTasks(namespaces.WithNamespace(ctx, ns)); err != nil {
150154
log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace")

0 commit comments

Comments
 (0)