Skip to content

Commit b3e7be1

Browse files
ayushr2gvisor-bot
authored andcommitted
Add S/R support for open FDs to deleted files.
Fixes #11425 PiperOrigin-RevId: 728549889
1 parent 84670a4 commit b3e7be1

File tree

6 files changed

+141
-25
lines changed

6 files changed

+141
-25
lines changed

pkg/sentry/fsimpl/gofer/dentry_impl.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,12 @@ func (d *dentry) symlink(ctx context.Context, name, target string, creds *auth.C
393393
}
394394

395395
// Precondition: !d.isSynthetic().
396-
func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
396+
func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
397397
switch dt := d.impl.(type) {
398398
case *lisafsDentry:
399-
return dt.openCreate(ctx, name, accessFlags, mode, uid, gid)
399+
return dt.openCreate(ctx, name, accessFlags, mode, uid, gid, createDentry)
400400
case *directfsDentry:
401-
return dt.openCreate(name, accessFlags, mode, uid, gid)
401+
return dt.openCreate(name, accessFlags, mode, uid, gid, createDentry)
402402
default:
403403
panic("unknown dentry implementation")
404404
}

pkg/sentry/fsimpl/gofer/directfs_dentry.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ func (d *directfsDentry) getXattr(ctx context.Context, name string, size uint64)
453453

454454
// getCreatedChild opens the newly created child, sets its uid/gid, constructs
455455
// a disconnected dentry and returns it.
456-
func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KGID, isDir bool) (*dentry, error) {
456+
func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KGID, isDir bool, createDentry bool) (*dentry, error) {
457457
unlinkFlags := 0
458458
extraOpenFlags := 0
459459
if isDir {
@@ -481,12 +481,15 @@ func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KG
481481
return nil, err
482482
}
483483

484-
child, err := d.fs.newDirectfsDentry(childFD)
485-
if err != nil {
486-
// Ownership of childFD was passed to newDirectDentry(), so no need to
487-
// clean that up.
488-
deleteChild()
489-
return nil, err
484+
var child *dentry
485+
if createDentry {
486+
child, err = d.fs.newDirectfsDentry(childFD)
487+
if err != nil {
488+
// Ownership of childFD was passed to newDirectDentry(), so no need to
489+
// clean that up.
490+
deleteChild()
491+
return nil, err
492+
}
490493
}
491494
return child, nil
492495
}
@@ -506,7 +509,7 @@ func (d *directfsDentry) mknod(ctx context.Context, name string, creds *auth.Cre
506509
if err := unix.Mknodat(d.controlFD, name, uint32(opts.Mode), 0); err != nil {
507510
return nil, err
508511
}
509-
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */)
512+
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */, true /* createDentry */)
510513
}
511514

512515
// Precondition: opts.Endpoint != nil and is transport.HostBoundEndpoint type.
@@ -531,7 +534,7 @@ func (d *directfsDentry) bindAt(ctx context.Context, name string, creds *auth.Cr
531534
return nil, err
532535
}
533536
// Socket already has the right UID/GID set, so use uid = gid = -1.
534-
child, err := d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */)
537+
child, err := d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */, true /* createDentry */)
535538
if err != nil {
536539
hbep.ResetBoundSocketFD(ctx)
537540
return nil, err
@@ -559,31 +562,31 @@ func (d *directfsDentry) link(target *directfsDentry, name string) (*dentry, err
559562
// link. The original file already has the right owner.
560563
// TODO(gvisor.dev/issue/6739): Hard linked dentries should share the same
561564
// inode fields.
562-
return d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */)
565+
return d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */, true /* createDentry */)
563566
}
564567

565568
func (d *directfsDentry) mkdir(name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) {
566569
if err := unix.Mkdirat(d.controlFD, name, uint32(mode)); err != nil {
567570
return nil, err
568571
}
569-
return d.getCreatedChild(name, uid, gid, true /* isDir */)
572+
return d.getCreatedChild(name, uid, gid, true /* isDir */, true /* createDentry */)
570573
}
571574

572575
func (d *directfsDentry) symlink(name, target string, creds *auth.Credentials) (*dentry, error) {
573576
if err := unix.Symlinkat(target, d.controlFD, name); err != nil {
574577
return nil, err
575578
}
576-
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */)
579+
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */, true /* createDentry */)
577580
}
578581

579-
func (d *directfsDentry) openCreate(name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
582+
func (d *directfsDentry) openCreate(name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
580583
createFlags := unix.O_CREAT | unix.O_EXCL | int(accessFlags) | hostOpenFlags
581584
childHandleFD, err := unix.Openat(d.controlFD, name, createFlags, uint32(mode&^linux.FileTypeMask))
582585
if err != nil {
583586
return nil, noHandle, err
584587
}
585588

586-
child, err := d.getCreatedChild(name, uid, gid, false /* isDir */)
589+
child, err := d.getCreatedChild(name, uid, gid, false /* isDir */, createDentry)
587590
if err != nil {
588591
_ = unix.Close(childHandleFD)
589592
return nil, noHandle, err

pkg/sentry/fsimpl/gofer/filesystem.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,7 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
12711271
kgid = auth.KGID(d.gid.Load())
12721272
}
12731273

1274-
child, h, err := d.openCreate(ctx, name, opts.Flags&linux.O_ACCMODE, opts.Mode, creds.EffectiveKUID, kgid)
1274+
child, h, err := d.openCreate(ctx, name, opts.Flags&linux.O_ACCMODE, opts.Mode, creds.EffectiveKUID, kgid, true /* createDentry */)
12751275
if err != nil {
12761276
return nil, err
12771277
}

pkg/sentry/fsimpl/gofer/gofer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,11 @@ type dentry struct {
978978
// tracks dirty segments in cache. dirty is protected by dataMu.
979979
dirty fsutil.DirtySet
980980

981+
// If this dentry represents a deleted regular file, deletedDataSR is used to
982+
// store file data for save/restore.
983+
// TODO(ayushranjan): Figure out how to clear this field on resume.
984+
deletedDataSR []byte
985+
981986
// pf implements memmap.File for mappings of hostFD.
982987
pf dentryPlatformFile
983988

pkg/sentry/fsimpl/gofer/lisafs_dentry.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ func (d *lisafsDentry) symlink(ctx context.Context, name, target string, creds *
448448
return d.newChildDentry(ctx, &symlinkInode, name)
449449
}
450450

451-
func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
451+
func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
452452
ino, openFD, hostFD, err := d.controlFD.OpenCreateAt(ctx, name, flags, mode, lisafs.UID(uid), lisafs.GID(gid))
453453
if err != nil {
454454
return nil, noHandle, err
@@ -458,10 +458,13 @@ func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32
458458
fdLisa: d.fs.client.NewFD(openFD),
459459
fd: int32(hostFD),
460460
}
461-
child, err := d.fs.newLisafsDentry(ctx, &ino)
462-
if err != nil {
463-
h.close(ctx)
464-
return nil, noHandle, err
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+
}
465468
}
466469
return child, h, nil
467470
}

pkg/sentry/fsimpl/gofer/save_restore.go

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import (
2525
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2626
"gvisor.dev/gvisor/pkg/fdnotifier"
2727
"gvisor.dev/gvisor/pkg/hostarch"
28+
"gvisor.dev/gvisor/pkg/log"
2829
"gvisor.dev/gvisor/pkg/refs"
2930
"gvisor.dev/gvisor/pkg/safemem"
31+
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
3032
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
3133
"gvisor.dev/gvisor/pkg/sentry/vfs"
3234
)
@@ -48,6 +50,7 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
4850
fs.renameMu.Lock()
4951
fs.evictAllCachedDentriesLocked(ctx)
5052
fs.renameMu.Unlock()
53+
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
5154

5255
// Buffer pipe data so that it's available for reading after restore. (This
5356
// is a legacy VFS1 feature.)
@@ -60,14 +63,23 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
6063
}
6164
}
6265
}
66+
// Save file data for deleted regular files which are still accessible via
67+
// open application FDs.
68+
for sd := fs.syncableDentries.Front(); sd != nil; sd = sd.Next() {
69+
if sd.d.vfsd.IsDead() {
70+
if err := sd.d.prepareSaveDead(ctx); err != nil {
71+
fs.syncMu.Unlock()
72+
return err
73+
}
74+
}
75+
}
6376
fs.syncMu.Unlock()
6477

6578
// Flush local state to the remote filesystem.
6679
if err := fs.Sync(ctx); err != nil {
6780
return err
6881
}
6982

70-
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
7183
return fs.root.prepareSaveRecursive(ctx)
7284
}
7385

@@ -96,6 +108,55 @@ func (fd *specialFileFD) savePipeData(ctx context.Context) error {
96108
return nil
97109
}
98110

111+
func (d *dentry) prepareSaveDead(ctx context.Context) error {
112+
if !d.isRegularFile() {
113+
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()))
114+
}
115+
if !d.isDeleted() {
116+
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: invalidated dentries can't be saved", genericDebugPathname(d.fs, d))
117+
}
118+
if !d.cachedMetadataAuthoritative() {
119+
if err := d.updateMetadata(ctx); err != nil {
120+
return err
121+
}
122+
}
123+
if d.isReadHandleOk() || d.isWriteHandleOk() {
124+
d.fs.savedDentryRW[d] = savedDentryRW{
125+
read: d.isReadHandleOk(),
126+
write: d.isWriteHandleOk(),
127+
}
128+
}
129+
d.handleMu.RLock()
130+
defer d.handleMu.RUnlock()
131+
var h handle
132+
if d.isReadHandleOk() {
133+
h = d.readHandle()
134+
} else {
135+
var err error
136+
h, err = d.openHandle(ctx, true /* read */, false /* write */, false /* trunc */)
137+
if err != nil {
138+
return fmt.Errorf("failed to open read handle for deleted file %q: %w", genericDebugPathname(d.fs, d), err)
139+
}
140+
defer h.close(ctx)
141+
}
142+
d.dataMu.RLock()
143+
defer d.dataMu.RUnlock()
144+
d.deletedDataSR = make([]byte, d.size.Load())
145+
done := uint64(0)
146+
for done < uint64(len(d.deletedDataSR)) {
147+
n, err := h.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR[done:])), done)
148+
done += n
149+
if err != nil {
150+
if err == io.EOF {
151+
break
152+
}
153+
return fmt.Errorf("failed to read deleted file %q: %w", genericDebugPathname(d.fs, d), err)
154+
}
155+
}
156+
d.deletedDataSR = d.deletedDataSR[:done]
157+
return nil
158+
}
159+
99160
func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
100161
if d.isRegularFile() && !d.cachedMetadataAuthoritative() {
101162
// Get updated metadata for d in case we need to perform metadata
@@ -129,7 +190,7 @@ func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
129190

130191
// beforeSave is invoked by stateify.
131192
func (d *dentry) beforeSave() {
132-
if d.vfsd.IsDead() {
193+
if d.vfsd.IsDead() && d.deletedDataSR == nil {
133194
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: deleted and invalidated dentries can't be restored", genericDebugPathname(d.fs, d)))
134195
}
135196
}
@@ -204,6 +265,7 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
204265
return err
205266
}
206267

268+
fs.syncMu.Lock()
207269
// Re-open handles for specialFileFDs. Unlike the initial open
208270
// (dentry.openSpecialFile()), pipes are always opened without blocking;
209271
// non-readable pipe FDs are opened last to ensure that they don't get
@@ -216,18 +278,30 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
216278
continue
217279
}
218280
if err := fd.completeRestore(ctx); err != nil {
281+
fs.syncMu.Unlock()
219282
return err
220283
}
221284
}
222285
if haveWriteOnlyPipes {
223286
for fd := fs.specialFileFDs.Front(); fd != nil; fd = fd.Next() {
224287
if fd.dentry().fileType() == linux.S_IFIFO && !fd.vfsfd.IsReadable() {
225288
if err := fd.completeRestore(ctx); err != nil {
289+
fs.syncMu.Unlock()
226290
return err
227291
}
228292
}
229293
}
230294
}
295+
// Restore deleted files which are still accessible via open application FDs.
296+
for sd := fs.syncableDentries.Front(); sd != nil; sd = sd.Next() {
297+
if sd.d.deletedDataSR != nil {
298+
if err := sd.d.restoreDead(ctx, &opts); err != nil {
299+
fs.syncMu.Unlock()
300+
return err
301+
}
302+
}
303+
}
304+
fs.syncMu.Unlock()
231305

232306
// Discard state only required during restore.
233307
fs.savedDentryRW = nil
@@ -256,6 +330,37 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
256330
return nil
257331
}
258332

333+
// Preconditions: d.deletedDataSR != nil.
334+
func (d *dentry) restoreDead(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
335+
parent := d.parent.Load()
336+
// This is a deleted regular file. Recreate it on the host filesystem
337+
// temporarily, fill it with data and then proceed with the restore.
338+
_, 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 */)
339+
if err != nil {
340+
return fmt.Errorf("failed to re-create deleted file %q: %w", genericDebugPathname(d.fs, d), err)
341+
}
342+
defer h.close(ctx)
343+
n, err := h.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR)), 0)
344+
if err != nil {
345+
return fmt.Errorf("failed to write deleted file %q: %w", genericDebugPathname(d.fs, d), err)
346+
}
347+
if n != uint64(len(d.deletedDataSR)) {
348+
return fmt.Errorf("failed to write all of deleted file %q: wrote %d bytes, expected %d", genericDebugPathname(d.fs, d), n, len(d.deletedDataSR))
349+
}
350+
d.deletedDataSR = nil
351+
if err := d.restoreFile(ctx, opts); err != nil {
352+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
353+
// Log warning, give preference to the restore error.
354+
log.Warningf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
355+
}
356+
return err
357+
}
358+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
359+
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
360+
}
361+
return nil
362+
}
363+
259364
func (fd *specialFileFD) completeRestore(ctx context.Context) error {
260365
d := fd.dentry()
261366
h, err := d.openHandle(ctx, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)

0 commit comments

Comments
 (0)