Skip to content

gofer: Support restore of deleted directories whose original path is occupied. #11843

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions pkg/sentry/fsimpl/gofer/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ go_library(
"//pkg/marshal",
"//pkg/marshal/primitive",
"//pkg/metric",
"//pkg/rand",
"//pkg/refs",
"//pkg/safemem",
"//pkg/sentry/fsimpl/host",
Expand Down
49 changes: 37 additions & 12 deletions pkg/sentry/fsimpl/gofer/save_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package gofer

import (
goContext "context"
"encoding/hex"
"fmt"
"io"

Expand All @@ -27,6 +28,7 @@ import (
"gvisor.dev/gvisor/pkg/fdnotifier"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/rand"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
Expand Down Expand Up @@ -327,7 +329,7 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
}

// Restore deleted files which are still accessible via open application FDs.
dirsToDelete := make(map[*dentry]struct{})
dirsToDelete := make(map[*dentry]string)
for d := range fs.savedDeletedOpenDentries {
if err := d.restoreDeleted(ctx, &opts, dirsToDelete); err != nil {
return err
Expand All @@ -344,7 +346,10 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
delete(leafDirectories, d.parent.Load())
}
for leafD := range leafDirectories {
if err := leafD.parent.Load().unlink(ctx, leafD.name, linux.AT_REMOVEDIR); err != nil {
// Note that we use the name specified in dirsToDelete map, which is the
// name used to create the temporary directory. This name may differ from
// leafD.name if another non-deleted directory already exists there.
if err := leafD.parent.Load().unlink(ctx, dirsToDelete[leafD], linux.AT_REMOVEDIR); err != nil {
return fmt.Errorf("failed to clean up recreated deleted directory %q: %v", genericDebugPathname(fs, leafD), err)
}
delete(dirsToDelete, leafD)
Expand Down Expand Up @@ -384,7 +389,7 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
// Preconditions:
// - d.isRegularFile() || d.isDir()
// - d.savedDeletedData != nil iff d.isRegularFile()
func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]string) error {
parent := d.parent.Load()
if _, ok := d.fs.savedDeletedOpenDentries[parent]; ok {
// Recursively restore the parent first if the parent is also deleted.
Expand All @@ -402,10 +407,26 @@ func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOp
}
}

func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
func randomNameForDeleted(name string) string {
var randBuf [8]byte
rand.Read(randBuf[:])
return fmt.Sprintf("%s.tmp-gvisor-restore-%s", name, hex.EncodeToString(randBuf[:]))
}

func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]string) error {
// Recreate the directory on the host filesystem. This will be deleted later.
parent := d.parent.Load()
_, err := parent.mkdir(ctx, d.name, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
if linuxerr.Equals(linuxerr.EEXIST, err) {
// Change d.name for the remainder of this function.
origName := d.name
d.name = randomNameForDeleted(d.name)
defer func() {
d.name = origName
}()
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)
_, err = parent.mkdir(ctx, d.name, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
}
if err != nil {
return fmt.Errorf("failed to re-create deleted directory %q: %w", genericDebugPathname(d.fs, d), err)
}
Expand All @@ -418,28 +439,32 @@ func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.Complete
}
// We will delete the directory later. We need to keep it around in case any
// of its children need to be restored after this.
dirsToDelete[d] = struct{}{}
dirsToDelete[d] = d.name
delete(d.fs.savedDeletedOpenDentries, d)
return nil
}

func (d *dentry) restoreDeletedRegularFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
// Recreate the file on the host filesystem (this is temporary).
parent := d.parent.Load()
name := d.name
_, 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 */)
_, 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 */)
if linuxerr.Equals(linuxerr.EEXIST, err) {
name = fmt.Sprintf("%s.tmp-gvisor-restore", name)
log.Warningf("Deleted file %q was replaced with a new file at the same path, using new name %q", genericDebugPathname(d.fs, d), name)
_, 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 */)
// Change d.name for the remainder of this function.
origName := d.name
d.name = randomNameForDeleted(d.name)
defer func() {
d.name = origName
}()
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)
_, 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 */)
}
if err != nil {
return fmt.Errorf("failed to re-create deleted file %q: %w", genericDebugPathname(d.fs, d), err)
}
defer h.close(ctx)
// In case of errors, clean up the recreated file.
unlinkCU := cleanup.Make(func() {
if err := parent.unlink(ctx, name, 0 /* flags */); err != nil {
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
log.Warningf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
}
})
Expand All @@ -462,7 +487,7 @@ func (d *dentry) restoreDeletedRegularFile(ctx context.Context, opts *vfs.Comple
}
// Finally, unlink the recreated file.
unlinkCU.Release()
if err := parent.unlink(ctx, name, 0 /* flags */); err != nil {
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
}
delete(d.fs.savedDeletedOpenDentries, d)
Expand Down
54 changes: 54 additions & 0 deletions test/syscalls/linux/unlink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,60 @@ TEST(UnlinkTest, UnlinkAtEmptyPath) {
SyscallFailsWithErrno(ENOENT));
}

TEST(UnlinkTest, UnlinkWithOpenFDs) {
// TODO(b/400287667): Enable save/restore for local gofer.
DisableSave ds;
if (IsRunningOnRunsc()) {
ds.reset();
}

// Create some nested directories.
auto foo = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto bar = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(foo.path()));
auto baz = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(bar.path()));

// Create a file and directory in the inner most directory.
const std::string file_path = JoinPath(baz.path(), "file");
auto file_fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(file_path, O_RDWR | O_CREAT, 0666));
const char kHello[] = "hello";
ASSERT_THAT(write(file_fd.get(), kHello, sizeof(kHello)),
SyscallSucceedsWithValue(sizeof(kHello)));

const std::string dir_path = JoinPath(baz.path(), "dir");
ASSERT_THAT(mkdir(dir_path.c_str(), 0777), SyscallSucceeds());
auto dir_fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(dir_path, O_RDONLY | O_DIRECTORY));

// Unlink "file" and "dir".
EXPECT_THAT(unlink(file_path.c_str()), SyscallSucceeds());
EXPECT_THAT(rmdir(dir_path.c_str()), SyscallSucceeds());

// Recreate files in the same position. S/R should be able to handle this.
auto new_file_fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(file_path, O_RDWR | O_CREAT, 0666));
const char kWorld[] = "world";
ASSERT_THAT(write(new_file_fd.get(), kWorld, sizeof(kWorld)),
SyscallSucceedsWithValue(sizeof(kWorld)));
new_file_fd.reset();
ASSERT_THAT(mkdir(dir_path.c_str(), 0777), SyscallSucceeds());

// Unlink "file" and "dir" again.
EXPECT_THAT(unlink(file_path.c_str()), SyscallSucceeds());
EXPECT_THAT(rmdir(dir_path.c_str()), SyscallSucceeds());

// Delete the remaining directories.
EXPECT_THAT(rmdir(baz.path().c_str()), SyscallSucceeds());
EXPECT_THAT(rmdir(bar.path().c_str()), SyscallSucceeds());
EXPECT_THAT(rmdir(foo.path().c_str()), SyscallSucceeds());

// Verify that the original file contents were preserved across unlink/rmdir.
char buf[sizeof(kHello)] = {};
ASSERT_THAT(pread(file_fd.get(), buf, sizeof(buf), 0),
SyscallSucceedsWithValue(sizeof(buf)));
EXPECT_STREQ(buf, kHello);
}

} // namespace

} // namespace testing
Expand Down