3131// dentry.handleMu
3232// dentry.dataMu
3333// filesystem.ancestryMu
34+ // filesystem.devMu
3435// filesystem.inoMu
3536// filesystem.inodeMu
3637// specialFileFD.mu
@@ -91,6 +92,7 @@ const (
9192 moptDisableFileHandleSharing = "disable_file_handle_sharing"
9293 moptDisableFifoOpen = "disable_fifo_open"
9394 moptCharDevicePolicy = "char_device_policy"
95+ moptVirtualInodeNumbers = "virtual_inode_numbers"
9496
9597 // Directfs options.
9698 moptDirectfs = "directfs"
@@ -211,7 +213,9 @@ type filesystem struct {
211213 // clock is a realtime clock used to set timestamps in file operations.
212214 clock ktime.Clock
213215
214- // devMinor is the filesystem's minor device number. devMinor is immutable.
216+ // devMinor is the filesystem's minor device number. It is used for
217+ // synthetic files, and for all files if opts.virtualInodeNumbers.
218+ // devMinor is immutable.
215219 devMinor uint32
216220
217221 // root is the root dentry. root is immutable.
@@ -241,9 +245,18 @@ type filesystem struct {
241245 syncableDentries dentryList
242246 specialFileFDs specialFDList
243247
248+ // devMinorByHostDev maps host device numbers, as encoded by unix.Mkdev,
249+ // to minor device numbers assigned to files on those devices. It is only
250+ // used if !opts.virtualInodeNumbers. Host device numbers may change
251+ // across checkpoint/restore, so devMinorByHostDev is rebuilt on restore.
252+ // devMinorByHostDev is protected by devMu.
253+ devMu sync.RWMutex `state:"nosave"`
254+ devMinorByHostDev map [uint64 ]uint32
255+
244256 // inoByKey maps previously-observed device ID and host inode numbers to
245- // internal inode numbers assigned to those files. inoByKey is not preserved
246- // across checkpoint/restore because inode numbers may be reused between
257+ // internal inode numbers assigned to those files. It is only used if
258+ // opts.virtualInodeNumbers. inoByKey is not preserved across
259+ // checkpoint/restore because inode numbers may be reused between
247260 // different gofer processes, so inode numbers may be repeated for different
248261 // files across checkpoint/restore. inoByKey is protected by inoMu.
249262 inoMu sync.Mutex `state:"nosave"`
@@ -348,6 +361,12 @@ type filesystemOptions struct {
348361 // via the gofer, or a combination of both.
349362 charDevicePolicy charDevicePolicy
350363
364+ // If virtualInodeNumbers is true, files are assigned sequential inode
365+ // numbers, recorded in filesystem.inoByKey, and the filesystem's device
366+ // number. Otherwise files report their host inode numbers, and their host
367+ // device numbers are mapped to sandbox-private device numbers.
368+ virtualInodeNumbers bool
369+
351370 // directfs holds options for directfs mode.
352371 directfs directfsOpts
353372}
@@ -605,6 +624,10 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
605624 delete (mopts , moptDirectfs )
606625 fsopts .directfs .enabled = true
607626 }
627+ if _ , ok := mopts [moptVirtualInodeNumbers ]; ok {
628+ delete (mopts , moptVirtualInodeNumbers )
629+ fsopts .virtualInodeNumbers = true
630+ }
608631 // fsopts.regularFilesUseSpecialFileFD can only be enabled by specifying
609632 // "cache=none".
610633
@@ -638,13 +661,14 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
638661 return nil , nil , err
639662 }
640663 fs := & filesystem {
641- mf : mf ,
642- opts : fsopts ,
643- iopts : iopts ,
644- clock : ktime .RealtimeClockFromContext (ctx ),
645- devMinor : devMinor ,
646- inoByKey : make (map [inoKey ]uint64 ),
647- inodeByKey : make (map [inoKey ]* inode ),
664+ mf : mf ,
665+ opts : fsopts ,
666+ iopts : iopts ,
667+ clock : ktime .RealtimeClockFromContext (ctx ),
668+ devMinor : devMinor ,
669+ inoByKey : make (map [inoKey ]uint64 ),
670+ inodeByKey : make (map [inoKey ]* inode ),
671+ devMinorByHostDev : make (map [uint64 ]uint32 ),
648672 }
649673
650674 // Did the user configure a global dentry cache?
@@ -828,7 +852,11 @@ func (fs *filesystem) Release(ctx context.Context) {
828852 }
829853 }
830854
831- fs .vfsfs .VirtualFilesystem ().PutAnonBlockDevMinor (fs .devMinor )
855+ vfsObj := fs .vfsfs .VirtualFilesystem ()
856+ vfsObj .PutAnonBlockDevMinor (fs .devMinor )
857+ for _ , devMinor := range fs .devMinorByHostDev {
858+ vfsObj .PutAnonBlockDevMinor (devMinor )
859+ }
832860}
833861
834862// releaseExtraRefsRecursiveLocked traverses the tree with root d and
@@ -914,7 +942,8 @@ type inode struct {
914942 // - Lock metadataMu and use atomic operations to update because we might
915943 // have atomic readers that don't hold the lock.
916944 metadataMu sync.Mutex `state:"nosave"`
917- ino uint64 // immutable
945+ ino uint64 // immutable, except during restore
946+ devMinor uint32 // immutable, except during restore
918947 mode atomicbitops.Uint32 // type is immutable, perms are mutable
919948 uid atomicbitops.Uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
920949 gid atomicbitops.Uint32 // auth.KGID, but ...
@@ -1251,7 +1280,12 @@ type dentryListElem struct {
12511280 dentryEntry
12521281}
12531282
1283+ // inoFromKey returns the inode number of the file identified by key. With
1284+ // virtual inode numbers, key must include the host device numbers.
12541285func (fs * filesystem ) inoFromKey (key inoKey ) uint64 {
1286+ if ! fs .opts .virtualInodeNumbers {
1287+ return key .ino
1288+ }
12551289 fs .inoMu .Lock ()
12561290 defer fs .inoMu .Unlock ()
12571291
@@ -1263,6 +1297,111 @@ func (fs *filesystem) inoFromKey(key inoKey) uint64 {
12631297 return ino
12641298}
12651299
1300+ // devMinorFromKey returns the minor device number of the file identified by
1301+ // key.
1302+ func (fs * filesystem ) devMinorFromKey (key inoKey ) (uint32 , error ) {
1303+ if fs .opts .virtualInodeNumbers {
1304+ return fs .devMinor , nil
1305+ }
1306+ dev := unix .Mkdev (key .devMajor , key .devMinor )
1307+ fs .devMu .RLock ()
1308+ devMinor , ok := fs .devMinorByHostDev [dev ]
1309+ fs .devMu .RUnlock ()
1310+ if ok {
1311+ return devMinor , nil
1312+ }
1313+ fs .devMu .Lock ()
1314+ defer fs .devMu .Unlock ()
1315+ if devMinor , ok := fs .devMinorByHostDev [dev ]; ok {
1316+ return devMinor , nil
1317+ }
1318+ return fs .newDevMinorLocked (dev )
1319+ }
1320+
1321+ // Preconditions: fs.devMu is locked.
1322+ func (fs * filesystem ) newDevMinorLocked (dev uint64 ) (uint32 , error ) {
1323+ devMinor , err := fs .vfsfs .VirtualFilesystem ().GetAnonBlockDevMinor ()
1324+ if err != nil {
1325+ return 0 , err
1326+ }
1327+ fs .devMinorByHostDev [dev ] = devMinor
1328+ return devMinor , nil
1329+ }
1330+
1331+ func hasDevMinor (devMinorByHostDev map [uint64 ]uint32 , devMinor uint32 ) bool {
1332+ for _ , m := range devMinorByHostDev {
1333+ if m == devMinor {
1334+ return true
1335+ }
1336+ }
1337+ return false
1338+ }
1339+
1340+ // restoreDevMinor returns the minor device number for the host device of key,
1341+ // which identifies a restored inode that had minor device number saved at
1342+ // checkpoint. saved is reused unless another host device has already been
1343+ // mapped to it, so that device numbers are stable across checkpoint/restore
1344+ // when the host filesystem is unchanged.
1345+ func (fs * filesystem ) restoreDevMinor (key inoKey , saved uint32 ) (uint32 , error ) {
1346+ dev := unix .Mkdev (key .devMajor , key .devMinor )
1347+ fs .devMu .Lock ()
1348+ defer fs .devMu .Unlock ()
1349+ if devMinor , ok := fs .devMinorByHostDev [dev ]; ok {
1350+ return devMinor , nil
1351+ }
1352+ if hasDevMinor (fs .devMinorByHostDev , saved ) {
1353+ return fs .newDevMinorLocked (dev )
1354+ }
1355+ fs .devMinorByHostDev [dev ] = saved
1356+ return saved , nil
1357+ }
1358+
1359+ // releaseDevMinors releases the minor device numbers in prevDevMinors, the
1360+ // devMinorByHostDev saved at checkpoint, that restore did not reuse.
1361+ func (fs * filesystem ) releaseDevMinors (prevDevMinors map [uint64 ]uint32 ) {
1362+ vfsObj := fs .vfsfs .VirtualFilesystem ()
1363+ fs .devMu .Lock ()
1364+ defer fs .devMu .Unlock ()
1365+ for _ , devMinor := range prevDevMinors {
1366+ if ! hasDevMinor (fs .devMinorByHostDev , devMinor ) {
1367+ vfsObj .PutAnonBlockDevMinor (devMinor )
1368+ }
1369+ }
1370+ }
1371+
1372+ // restoreInoKey records the host identity of a restored inode, and updates
1373+ // the inode and device numbers derived from it. Host inode and device
1374+ // numbers are not preserved across checkpoint/restore, so:
1375+ //
1376+ // - We must assume that the host filesystem did not change in a way that
1377+ // would invalidate dentries, since we can't revalidate dentries by
1378+ // checking inoKey.
1379+ //
1380+ // - With virtual inode numbers, the new inoKey is associated with the
1381+ // existing i.ino. Otherwise, i.ino follows the host inode number.
1382+ func (i * inode ) restoreInoKey (key inoKey ) error {
1383+ i .inoKey = key
1384+ if i .fs .opts .virtualInodeNumbers {
1385+ i .fs .inoMu .Lock ()
1386+ i .fs .inoByKey [key ] = i .ino
1387+ i .fs .inoMu .Unlock ()
1388+ } else {
1389+ i .ino = key .ino
1390+ devMinor , err := i .fs .restoreDevMinor (key , i .devMinor )
1391+ if err != nil {
1392+ return err
1393+ }
1394+ i .devMinor = devMinor
1395+ }
1396+ // inodeByKey contains non-directory inodes only.
1397+ if i .fileType () != linux .S_IFDIR {
1398+ i .fs .inodeMu .Lock ()
1399+ i .fs .inodeByKey [key ] = i
1400+ i .fs .inodeMu .Unlock ()
1401+ }
1402+ return nil
1403+ }
1404+
12661405func (fs * filesystem ) nextIno () uint64 {
12671406 return fs .lastIno .Add (1 )
12681407}
@@ -1346,7 +1485,7 @@ func (d *dentry) statTo(stat *linux.Statx) {
13461485 stat .Ctime = linux .NsecToStatxTimestamp (d .inode .ctime .Load ())
13471486 stat .Mtime = linux .NsecToStatxTimestamp (d .inode .mtime .Load ())
13481487 stat .DevMajor = linux .UNNAMED_MAJOR
1349- stat .DevMinor = d .inode .fs . devMinor
1488+ stat .DevMinor = d .inode .devMinor
13501489 stat .RdevMajor = d .inode .rdevMajor
13511490 stat .RdevMinor = d .inode .rdevMinor
13521491}
0 commit comments