Skip to content

Commit c2f49c7

Browse files
ayushr2gvisor-bot
authored andcommitted
gofer: Add support for restoring deleted directories.
Fixes #11425 PiperOrigin-RevId: 740865278
1 parent b5f9bed commit c2f49c7

File tree

7 files changed

+138
-44
lines changed

7 files changed

+138
-44
lines changed

pkg/sentry/fsimpl/gofer/dentry_impl.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,12 @@ func (d *dentry) link(ctx context.Context, target *dentry, name string) (*dentry
369369
}
370370

371371
// Precondition: !d.isSynthetic().
372-
func (d *dentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) {
372+
func (d *dentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, error) {
373373
switch dt := d.impl.(type) {
374374
case *lisafsDentry:
375-
return dt.mkdir(ctx, name, mode, uid, gid)
375+
return dt.mkdir(ctx, name, mode, uid, gid, createDentry)
376376
case *directfsDentry:
377-
return dt.mkdir(name, mode, uid, gid)
377+
return dt.mkdir(name, mode, uid, gid, createDentry)
378378
default:
379379
panic("unknown dentry implementation")
380380
}

pkg/sentry/fsimpl/gofer/directfs_dentry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,11 +565,11 @@ func (d *directfsDentry) link(target *directfsDentry, name string) (*dentry, err
565565
return d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */, true /* createDentry */)
566566
}
567567

568-
func (d *directfsDentry) mkdir(name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) {
568+
func (d *directfsDentry) mkdir(name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, error) {
569569
if err := unix.Mkdirat(d.controlFD, name, uint32(mode)); err != nil {
570570
return nil, err
571571
}
572-
return d.getCreatedChild(name, uid, gid, true /* isDir */, true /* createDentry */)
572+
return d.getCreatedChild(name, uid, gid, true /* isDir */, createDentry)
573573
}
574574

575575
func (d *directfsDentry) symlink(name, target string, creds *auth.Credentials) (*dentry, error) {

pkg/sentry/fsimpl/gofer/filesystem.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ func (fs *filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
845845
mode |= linux.S_ISGID
846846
}
847847

848-
child, err := parent.mkdir(ctx, name, mode, creds.EffectiveKUID, kgid)
848+
child, err := parent.mkdir(ctx, name, mode, creds.EffectiveKUID, kgid, true /* createDentry */)
849849
if err == nil {
850850
if fs.opts.interop != InteropModeShared {
851851
parent.incLinks()

pkg/sentry/fsimpl/gofer/gofer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,9 @@ type dentry struct {
982982
// tracks dirty segments in cache. dirty is protected by dataMu.
983983
dirty fsutil.DirtySet
984984

985-
// If this dentry represents a deleted regular file, deletedDataSR is used to
986-
// store file data for save/restore.
987-
deletedDataSR []byte
985+
// If this dentry represents a deleted regular file, savedDeletedData is used
986+
// to store file data for save/restore.
987+
savedDeletedData []byte
988988

989989
// pf implements memmap.File for mappings of hostFD.
990990
pf dentryPlatformFile

pkg/sentry/fsimpl/gofer/lisafs_dentry.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,14 @@ func (d *lisafsDentry) link(ctx context.Context, target *lisafsDentry, name stri
432432
return d.newChildDentry(ctx, &linkInode, name)
433433
}
434434

435-
func (d *lisafsDentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) {
435+
func (d *lisafsDentry) mkdir(ctx context.Context, name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, error) {
436436
childDirInode, err := d.controlFD.MkdirAt(ctx, name, mode, lisafs.UID(uid), lisafs.GID(gid))
437437
if err != nil {
438438
return nil, err
439439
}
440+
if !createDentry {
441+
return nil, nil
442+
}
440443
return d.newChildDentry(ctx, &childDirInode, name)
441444
}
442445

@@ -458,13 +461,13 @@ func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32
458461
fdLisa: d.fs.client.NewFD(openFD),
459462
fd: int32(hostFD),
460463
}
461-
var child *dentry
462-
if createDentry {
463-
child, err = d.fs.newLisafsDentry(ctx, &ino)
464-
if err != nil {
465-
h.close(ctx)
466-
return nil, noHandle, err
467-
}
464+
if !createDentry {
465+
return nil, h, nil
466+
}
467+
child, err := d.fs.newLisafsDentry(ctx, &ino)
468+
if err != nil {
469+
h.close(ctx)
470+
return nil, noHandle, err
468471
}
469472
return child, h, nil
470473
}

pkg/sentry/fsimpl/gofer/save_restore.go

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,21 @@ func (fd *specialFileFD) savePipeData(ctx context.Context) error {
112112
}
113113

114114
func (d *dentry) prepareSaveDead(ctx context.Context) error {
115-
if !d.isRegularFile() {
116-
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: only regular deleted dentries can be saved, got %s", genericDebugPathname(d.fs, d), linux.FileMode(d.mode.Load()))
115+
if !d.isRegularFile() && !d.isDir() {
116+
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: only deleted dentries for regular files and directories can be saved, got %s", genericDebugPathname(d.fs, d), linux.FileMode(d.mode.Load()))
117117
}
118118
if !d.isDeleted() {
119119
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: invalidated dentries can't be saved", genericDebugPathname(d.fs, d))
120120
}
121-
if !d.cachedMetadataAuthoritative() {
122-
if err := d.updateMetadata(ctx); err != nil {
121+
if d.isRegularFile() {
122+
if !d.cachedMetadataAuthoritative() {
123+
// Get updated metadata for d in case we need to perform metadata
124+
// validation during restore.
125+
if err := d.updateMetadata(ctx); err != nil {
126+
return err
127+
}
128+
}
129+
if err := d.prepareSaveDeletedRegularFile(ctx); err != nil {
123130
return err
124131
}
125132
}
@@ -129,6 +136,18 @@ func (d *dentry) prepareSaveDead(ctx context.Context) error {
129136
write: d.isWriteHandleOk(),
130137
}
131138
}
139+
if d.fs.savedDeletedOpenDentries == nil {
140+
d.fs.savedDeletedOpenDentries = make(map[*dentry]struct{})
141+
}
142+
d.fs.savedDeletedOpenDentries[d] = struct{}{}
143+
return nil
144+
}
145+
146+
// Preconditions:
147+
// - d.isRegularFile()
148+
// - d.isDeleted()
149+
func (d *dentry) prepareSaveDeletedRegularFile(ctx context.Context) error {
150+
// Fetch an appropriate handle to read the deleted file.
132151
d.handleMu.RLock()
133152
defer d.handleMu.RUnlock()
134153
var h handle
@@ -142,12 +161,13 @@ func (d *dentry) prepareSaveDead(ctx context.Context) error {
142161
}
143162
defer h.close(ctx)
144163
}
164+
// Read the file data and store it in d.savedDeletedData.
145165
d.dataMu.RLock()
146166
defer d.dataMu.RUnlock()
147-
d.deletedDataSR = make([]byte, d.size.Load())
167+
d.savedDeletedData = make([]byte, d.size.Load())
148168
done := uint64(0)
149-
for done < uint64(len(d.deletedDataSR)) {
150-
n, err := h.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR[done:])), done)
169+
for done < uint64(len(d.savedDeletedData)) {
170+
n, err := h.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.savedDeletedData[done:])), done)
151171
done += n
152172
if err != nil {
153173
if err == io.EOF {
@@ -156,14 +176,9 @@ func (d *dentry) prepareSaveDead(ctx context.Context) error {
156176
return fmt.Errorf("failed to read deleted file %q: %w", genericDebugPathname(d.fs, d), err)
157177
}
158178
}
159-
if done < uint64(len(d.deletedDataSR)) {
160-
return fmt.Errorf("failed to read all of deleted file %q: read %d bytes, expected %d", genericDebugPathname(d.fs, d), done, len(d.deletedDataSR))
161-
}
162-
d.deletedDataSR = d.deletedDataSR[:done]
163-
if d.fs.savedDeletedOpenDentries == nil {
164-
d.fs.savedDeletedOpenDentries = make(map[*dentry]struct{})
179+
if done < uint64(len(d.savedDeletedData)) {
180+
return fmt.Errorf("failed to read all of deleted file %q: read %d bytes, expected %d", genericDebugPathname(d.fs, d), done, len(d.savedDeletedData))
165181
}
166-
d.fs.savedDeletedOpenDentries[d] = struct{}{}
167182
return nil
168183
}
169184

@@ -200,15 +215,17 @@ func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
200215

201216
// beforeSave is invoked by stateify.
202217
func (d *dentry) beforeSave() {
203-
if d.vfsd.IsDead() && d.deletedDataSR == nil {
204-
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: deletedDataSR is nil for dead dentry (deleted=%t, synthetic=%t)", genericDebugPathname(d.fs, d), d.isDeleted(), d.isSynthetic()))
218+
if d.vfsd.IsDead() {
219+
if _, ok := d.fs.savedDeletedOpenDentries[d]; !ok {
220+
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: dead dentry is not saved in fs.savedDeletedOpenDentries (deleted=%t, synthetic=%t)", genericDebugPathname(d.fs, d), d.isDeleted(), d.isSynthetic()))
221+
}
205222
}
206223
}
207224

208225
// BeforeResume implements vfs.FilesystemImplSaveRestoreExtension.BeforeResume.
209226
func (fs *filesystem) BeforeResume(ctx context.Context) {
210227
for d := range fs.savedDeletedOpenDentries {
211-
d.deletedDataSR = nil
228+
d.savedDeletedData = nil
212229
}
213230
fs.savedDeletedOpenDentries = nil
214231
fs.savedDentryRW = nil
@@ -310,11 +327,29 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
310327
}
311328

312329
// Restore deleted files which are still accessible via open application FDs.
330+
dirsToDelete := make(map[*dentry]struct{})
313331
for d := range fs.savedDeletedOpenDentries {
314-
if err := d.restoreDead(ctx, &opts); err != nil {
332+
if err := d.restoreDeleted(ctx, &opts, dirsToDelete); err != nil {
315333
return err
316334
}
317335
}
336+
for len(dirsToDelete) > 0 {
337+
// In case of nested deleted directories, only leaf directories can be
338+
// deleted. Then repeat as parent directories become leaves.
339+
leafDirectories := make(map[*dentry]struct{})
340+
for d := range dirsToDelete {
341+
leafDirectories[d] = struct{}{}
342+
}
343+
for d := range dirsToDelete {
344+
delete(leafDirectories, d.parent.Load())
345+
}
346+
for leafD := range leafDirectories {
347+
if err := leafD.parent.Load().unlink(ctx, leafD.name, linux.AT_REMOVEDIR); err != nil {
348+
return fmt.Errorf("failed to clean up recreated deleted directory %q: %v", genericDebugPathname(fs, leafD), err)
349+
}
350+
delete(dirsToDelete, leafD)
351+
}
352+
}
318353

319354
// Discard state only required during restore.
320355
fs.savedDeletedOpenDentries = nil
@@ -344,10 +379,51 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
344379
return nil
345380
}
346381

347-
// restoreDead restores a deleted regular file.
382+
// restoreDeleted restores a deleted dentry for a directory or regular file.
348383
//
349-
// Preconditions: d.deletedDataSR != nil.
350-
func (d *dentry) restoreDead(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
384+
// Preconditions:
385+
// - d.isRegularFile() || d.isDir()
386+
// - d.savedDeletedData != nil iff d.isRegularFile()
387+
func (d *dentry) restoreDeleted(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
388+
parent := d.parent.Load()
389+
if _, ok := d.fs.savedDeletedOpenDentries[parent]; ok {
390+
// Recursively restore the parent first if the parent is also deleted.
391+
if err := parent.restoreDeleted(ctx, opts, dirsToDelete); err != nil {
392+
return err
393+
}
394+
}
395+
switch {
396+
case d.isRegularFile():
397+
return d.restoreDeletedRegularFile(ctx, opts)
398+
case d.isDir():
399+
return d.restoreDeletedDirectory(ctx, opts, dirsToDelete)
400+
default:
401+
return fmt.Errorf("gofer.dentry(%q).restoreDeleted: invalid file type %s", genericDebugPathname(d.fs, d), linux.FileMode(d.mode.Load()))
402+
}
403+
}
404+
405+
func (d *dentry) restoreDeletedDirectory(ctx context.Context, opts *vfs.CompleteRestoreOptions, dirsToDelete map[*dentry]struct{}) error {
406+
// Recreate the directory on the host filesystem. This will be deleted later.
407+
parent := d.parent.Load()
408+
_, err := parent.mkdir(ctx, d.name, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
409+
if err != nil {
410+
return fmt.Errorf("failed to re-create deleted directory %q: %w", genericDebugPathname(d.fs, d), err)
411+
}
412+
// Restore the directory.
413+
if err := d.restoreFile(ctx, opts); err != nil {
414+
if err := parent.unlink(ctx, d.name, linux.AT_REMOVEDIR); err != nil {
415+
log.Warningf("failed to clean up recreated deleted directory %q: %v", genericDebugPathname(d.fs, d), err)
416+
}
417+
return fmt.Errorf("failed to restore deleted directory: %w", err)
418+
}
419+
// We will delete the directory later. We need to keep it around in case any
420+
// of its children need to be restored after this.
421+
dirsToDelete[d] = struct{}{}
422+
delete(d.fs.savedDeletedOpenDentries, d)
423+
return nil
424+
}
425+
426+
func (d *dentry) restoreDeletedRegularFile(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
351427
// Recreate the file on the host filesystem (this is temporary).
352428
parent := d.parent.Load()
353429
_, 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 */)
@@ -363,26 +439,27 @@ func (d *dentry) restoreDead(ctx context.Context, opts *vfs.CompleteRestoreOptio
363439
})
364440
defer unlinkCU.Clean()
365441
// Write the file data to the recreated file.
366-
n, err := h.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR)), 0)
442+
n, err := h.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.savedDeletedData)), 0)
367443
if err != nil {
368444
return fmt.Errorf("failed to write deleted file %q: %w", genericDebugPathname(d.fs, d), err)
369445
}
370-
if n != uint64(len(d.deletedDataSR)) {
371-
return fmt.Errorf("failed to write all of deleted file %q: wrote %d bytes, expected %d", genericDebugPathname(d.fs, d), n, len(d.deletedDataSR))
446+
if n != uint64(len(d.savedDeletedData)) {
447+
return fmt.Errorf("failed to write all of deleted file %q: wrote %d bytes, expected %d", genericDebugPathname(d.fs, d), n, len(d.savedDeletedData))
372448
}
373-
d.deletedDataSR = nil
449+
d.savedDeletedData = nil
374450
// Restore the file. Note that timestamps may not match since we re-created
375451
// the file on the host.
376452
recreateOpts := *opts
377453
recreateOpts.ValidateFileModificationTimestamps = false
378454
if err := d.restoreFile(ctx, &recreateOpts); err != nil {
379-
return err
455+
return fmt.Errorf("failed to restore deleted regular file: %w", err)
380456
}
381457
// Finally, unlink the recreated file.
382458
unlinkCU.Release()
383459
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
384460
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
385461
}
462+
delete(d.fs.savedDeletedOpenDentries, d)
386463
return nil
387464
}
388465

test/syscalls/linux/unlink.cc

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,27 @@ TEST(UnlinkTest, OpenFile) {
168168
if (IsRunningOnRunsc()) {
169169
ds.reset();
170170
}
171-
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
171+
TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
172172
int fd;
173173
EXPECT_THAT(fd = open(file.path().c_str(), O_RDWR, 0666), SyscallSucceeds());
174174
EXPECT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
175175
EXPECT_THAT(close(fd), SyscallSucceeds());
176176
}
177177

178+
TEST(RmdirTest, OpenDirectory) {
179+
// TODO(b/400287667): Enable save/restore for local gofer.
180+
DisableSave ds;
181+
if (IsRunningOnRunsc()) {
182+
ds.reset();
183+
}
184+
TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
185+
int fd;
186+
EXPECT_THAT(fd = open(dir.path().c_str(), O_RDONLY | O_DIRECTORY),
187+
SyscallSucceeds());
188+
EXPECT_THAT(rmdir(dir.path().c_str()), SyscallSucceeds());
189+
EXPECT_THAT(close(fd), SyscallSucceeds());
190+
}
191+
178192
TEST(UnlinkTest, CannotRemoveDots) {
179193
auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
180194
const std::string self = JoinPath(file.path(), ".");

0 commit comments

Comments
 (0)