Skip to content

Commit 8d342f5

Browse files
ayushr2gvisor-bot
authored andcommitted
gofer: Support restore of deleted directories whose original path is occupied.
It is possible that the application can open an FD to a directory, delete the directory, create another directory at that path and then checkpoint the container. In such a scenario, our gofer restore logic will fail with EEXIST because a directory already exists on the host at that location when we try to recreate it. Use a temporary filename to recreate the directory in such a scenario. This directory is deleted again anyways. This is similar to e3c4c4c ("gofer: Support restore of deleted files whose original path is occupied.") In addition to this, this change also makes the following bug fixes: - Adds a random suffix to the temporary filename used to recreate the file/dir. This helps if multiple (different) deleted open directories exist at the same position. - Change dentry.name for the duration of the restoreDeleted*() function. Note that dentry.restoreFile() use dentry.name to re-open handles and control FD. Hence setting the correct dentry.name is crucial for the handles to point to the correct file. - Added a syscall test, which when run in save/restore mode, tests these code paths for handling open deleted files/directories. Updates #11425 PiperOrigin-RevId: 775406486
1 parent 3b6e02d commit 8d342f5

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

pkg/sentry/fsimpl/gofer/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ go_library(
9393
"//pkg/marshal",
9494
"//pkg/marshal/primitive",
9595
"//pkg/metric",
96+
"//pkg/rand",
9697
"//pkg/refs",
9798
"//pkg/safemem",
9899
"//pkg/sentry/fsimpl/host",

pkg/sentry/fsimpl/gofer/save_restore.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package gofer
1616

1717
import (
1818
goContext "context"
19+
"encoding/hex"
1920
"fmt"
2021
"io"
2122

@@ -27,6 +28,7 @@ import (
2728
"gvisor.dev/gvisor/pkg/fdnotifier"
2829
"gvisor.dev/gvisor/pkg/hostarch"
2930
"gvisor.dev/gvisor/pkg/log"
31+
"gvisor.dev/gvisor/pkg/rand"
3032
"gvisor.dev/gvisor/pkg/refs"
3133
"gvisor.dev/gvisor/pkg/safemem"
3234
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -327,7 +329,7 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
327329
}
328330

329331
// Restore deleted files which are still accessible via open application FDs.
330-
dirsToDelete := make(map[*dentry]struct{})
332+
dirsToDelete := make(map[*dentry]string)
331333
for d := range fs.savedDeletedOpenDentries {
332334
if err := d.restoreDeleted(ctx, &opts, dirsToDelete); err != nil {
333335
return err
@@ -344,7 +346,10 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
344346
delete(leafDirectories, d.parent.Load())
345347
}
346348
for leafD := range leafDirectories {
347-
if err := leafD.parent.Load().unlink(ctx, leafD.name, linux.AT_REMOVEDIR); err != nil {
349+
// Note that we use the name specified in dirsToDelete map, which is the
350+
// name used to create the temporary directory. This name may differ from
351+
// leafD.name if another non-deleted directory already exists there.
352+
if err := leafD.parent.Load().unlink(ctx, dirsToDelete[leafD], linux.AT_REMOVEDIR); err != nil {
348353
return fmt.Errorf("failed to clean up recreated deleted directory %q: %v", genericDebugPathname(fs, leafD), err)
349354
}
350355
delete(dirsToDelete, leafD)
@@ -384,7 +389,7 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
384389
// Preconditions:
385390
// - d.isRegularFile() || d.isDir()
386391
// - d.savedDeletedData != nil iff d.isRegularFile()
387-
func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
392+
func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]string) error {
388393
parent := d.parent.Load()
389394
if _, ok := d.fs.savedDeletedOpenDentries[parent]; ok {
390395
// Recursively restore the parent first if the parent is also deleted.
@@ -402,10 +407,26 @@ func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOp
402407
}
403408
}
404409

405-
func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
410+
func randomNameForDeleted(name string) string {
411+
var randBuf [8]byte
412+
rand.Read(randBuf[:])
413+
return fmt.Sprintf("%s.tmp-gvisor-restore-%s", name, hex.EncodeToString(randBuf[:]))
414+
}
415+
416+
func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]string) error {
406417
// Recreate the directory on the host filesystem. This will be deleted later.
407418
parent := d.parent.Load()
408419
_, err := parent.mkdir(ctx, d.name, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
420+
if linuxerr.Equals(linuxerr.EEXIST, err) {
421+
// Change d.name for the remainder of this function.
422+
origName := d.name
423+
d.name = randomNameForDeleted(d.name)
424+
defer func() {
425+
d.name = origName
426+
}()
427+
log.Warningf("Deleted directory %q was replaced with a new directory at the same path, using new name %q", genericDebugPathname(d.fs, d), d.name)
428+
_, err = parent.mkdir(ctx, d.name, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
429+
}
409430
if err != nil {
410431
return fmt.Errorf("failed to re-create deleted directory %q: %w", genericDebugPathname(d.fs, d), err)
411432
}
@@ -418,28 +439,32 @@ func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.Complete
418439
}
419440
// We will delete the directory later. We need to keep it around in case any
420441
// of its children need to be restored after this.
421-
dirsToDelete[d] = struct{}{}
442+
dirsToDelete[d] = d.name
422443
delete(d.fs.savedDeletedOpenDentries, d)
423444
return nil
424445
}
425446

426447
func (d *dentry) restoreDeletedRegularFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
427448
// Recreate the file on the host filesystem (this is temporary).
428449
parent := d.parent.Load()
429-
name := d.name
430-
_, h, err := parent.openCreate(ctx, name, linux.O_WRONLY, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
450+
_, h, err := parent.openCreate(ctx, d.name, linux.O_WRONLY, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
431451
if linuxerr.Equals(linuxerr.EEXIST, err) {
432-
name = fmt.Sprintf("%s.tmp-gvisor-restore", name)
433-
log.Warningf("Deleted file %q was replaced with a new file at the same path, using new name %q", genericDebugPathname(d.fs, d), name)
434-
_, h, err = parent.openCreate(ctx, name, linux.O_WRONLY, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
452+
// Change d.name for the remainder of this function.
453+
origName := d.name
454+
d.name = randomNameForDeleted(d.name)
455+
defer func() {
456+
d.name = origName
457+
}()
458+
log.Warningf("Deleted file %q was replaced with a new file at the same path, using new name %q", genericDebugPathname(d.fs, d), d.name)
459+
_, h, err = parent.openCreate(ctx, d.name, linux.O_WRONLY, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
435460
}
436461
if err != nil {
437462
return fmt.Errorf("failed to re-create deleted file %q: %w", genericDebugPathname(d.fs, d), err)
438463
}
439464
defer h.close(ctx)
440465
// In case of errors, clean up the recreated file.
441466
unlinkCU := cleanup.Make(func() {
442-
if err := parent.unlink(ctx, name, 0 /* flags */); err != nil {
467+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
443468
log.Warningf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
444469
}
445470
})
@@ -462,7 +487,7 @@ func (d *dentry) restoreDeletedRegularFile(ctx context.Context, opts *vfs.Comple
462487
}
463488
// Finally, unlink the recreated file.
464489
unlinkCU.Release()
465-
if err := parent.unlink(ctx, name, 0 /* flags */); err != nil {
490+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
466491
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
467492
}
468493
delete(d.fs.savedDeletedOpenDentries, d)

test/syscalls/linux/unlink.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,62 @@ TEST(UnlinkTest, UnlinkAtEmptyPath) {
239239
SyscallFailsWithErrno(ENOENT));
240240
}
241241

242+
// The primary purpose of this test is to verify that save/restore works for
243+
// open file descriptors to deleted files and directories.
244+
TEST(UnlinkTest, UnlinkWithOpenFDs) {
245+
// TODO: b/400287667 - Enable save/restore for local gofer.
246+
DisableSave ds;
247+
if (IsRunningOnRunsc()) {
248+
ds.reset();
249+
}
250+
251+
// Create some nested directories.
252+
TempPath foo = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
253+
TempPath bar = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(foo.path()));
254+
TempPath baz = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(bar.path()));
255+
256+
// Create a file and directory in the inner most directory.
257+
const std::string file_path = JoinPath(baz.path(), "file");
258+
FileDescriptor file_fd =
259+
ASSERT_NO_ERRNO_AND_VALUE(Open(file_path, O_RDWR | O_CREAT, 0666));
260+
const char kHello[] = "hello";
261+
ASSERT_THAT(write(file_fd.get(), kHello, sizeof(kHello)),
262+
SyscallSucceedsWithValue(sizeof(kHello)));
263+
264+
const std::string dir_path = JoinPath(baz.path(), "dir");
265+
ASSERT_THAT(mkdir(dir_path.c_str(), 0777), SyscallSucceeds());
266+
FileDescriptor dir_fd =
267+
ASSERT_NO_ERRNO_AND_VALUE(Open(dir_path, O_RDONLY | O_DIRECTORY));
268+
269+
// Unlink "file" and "dir".
270+
EXPECT_THAT(unlink(file_path.c_str()), SyscallSucceeds());
271+
EXPECT_THAT(rmdir(dir_path.c_str()), SyscallSucceeds());
272+
273+
// Recreate files in the same position. S/R should be able to handle this.
274+
FileDescriptor new_file_fd =
275+
ASSERT_NO_ERRNO_AND_VALUE(Open(file_path, O_RDWR | O_CREAT, 0666));
276+
const char kWorld[] = "world";
277+
ASSERT_THAT(write(new_file_fd.get(), kWorld, sizeof(kWorld)),
278+
SyscallSucceedsWithValue(sizeof(kWorld)));
279+
new_file_fd.reset();
280+
ASSERT_THAT(mkdir(dir_path.c_str(), 0777), SyscallSucceeds());
281+
282+
// Unlink "file" and "dir" again.
283+
EXPECT_THAT(unlink(file_path.c_str()), SyscallSucceeds());
284+
EXPECT_THAT(rmdir(dir_path.c_str()), SyscallSucceeds());
285+
286+
// Delete the remaining directories.
287+
EXPECT_THAT(rmdir(baz.path().c_str()), SyscallSucceeds());
288+
EXPECT_THAT(rmdir(bar.path().c_str()), SyscallSucceeds());
289+
EXPECT_THAT(rmdir(foo.path().c_str()), SyscallSucceeds());
290+
291+
// Verify that the original file contents were preserved across unlink/rmdir.
292+
char buf[sizeof(kHello)] = {};
293+
ASSERT_THAT(pread(file_fd.get(), buf, sizeof(buf), 0),
294+
SyscallSucceedsWithValue(sizeof(buf)));
295+
EXPECT_STREQ(buf, kHello);
296+
}
297+
242298
} // namespace
243299

244300
} // namespace testing

0 commit comments

Comments
 (0)