github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/gofer/inode_state.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gofer
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/SagerNet/gvisor/pkg/context"
    24  	"github.com/SagerNet/gvisor/pkg/p9"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/device"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    27  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/time"
    28  )
    29  
    30  // Some fs implementations may not support atime, ctime, or mtime in getattr.
    31  // The unstable() logic would try to use clock time for them. However, we do not
    32  // want to use such time during S/R as that would cause restore timestamp
    33  // checking failure. Hence a dummy stable-time clock is needed.
    34  //
    35  // Note that application-visible UnstableAttrs either come from CachingInodeOps
    36  // (in which case they are saved), or they are requested from the gofer on each
    37  // stat (for non-caching), so the dummy time only affects the modification
    38  // timestamp check.
    39  type dummyClock struct {
    40  	time.Clock
    41  }
    42  
    43  // Now returns a stable dummy time.
    44  func (d *dummyClock) Now() time.Time {
    45  	return time.Time{}
    46  }
    47  
    48  type dummyClockContext struct {
    49  	context.Context
    50  }
    51  
    52  // Value implements context.Context
    53  func (d *dummyClockContext) Value(key interface{}) interface{} {
    54  	switch key {
    55  	case time.CtxRealtimeClock:
    56  		return &dummyClock{}
    57  	default:
    58  		return d.Context.Value(key)
    59  	}
    60  }
    61  
    62  // beforeSave is invoked by stateify.
    63  func (i *inodeFileState) beforeSave() {
    64  	if _, ok := i.s.inodeMappings[i.sattr.InodeID]; !ok {
    65  		panic(fmt.Sprintf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings)))
    66  	}
    67  	if i.sattr.Type == fs.RegularFile {
    68  		uattr, err := i.unstableAttr(&dummyClockContext{context.Background()})
    69  		if err != nil {
    70  			panic(&fs.ErrSaveRejection{
    71  				Err: fmt.Errorf("failed to get unstable atttribute of %s: %w", i.s.inodeMappings[i.sattr.InodeID], err),
    72  			})
    73  		}
    74  		i.savedUAttr = &uattr
    75  	}
    76  }
    77  
    78  // saveLoading is invoked by stateify.
    79  func (i *inodeFileState) saveLoading() struct{} {
    80  	return struct{}{}
    81  }
    82  
    83  // splitAbsolutePath splits the path on slashes ignoring the leading slash.
    84  func splitAbsolutePath(path string) []string {
    85  	if len(path) == 0 {
    86  		panic("There is no path!")
    87  	}
    88  	if path != filepath.Clean(path) {
    89  		panic(fmt.Sprintf("path %q is not clean", path))
    90  	}
    91  	// This case is to return {} rather than {""}
    92  	if path == "/" {
    93  		return []string{}
    94  	}
    95  	if path[0] != '/' {
    96  		panic(fmt.Sprintf("path %q is not absolute", path))
    97  	}
    98  
    99  	s := strings.Split(path, "/")
   100  
   101  	// Since p is absolute, the first component of s
   102  	// is an empty string. We must remove that.
   103  	return s[1:]
   104  }
   105  
   106  // loadLoading is invoked by stateify.
   107  func (i *inodeFileState) loadLoading(_ struct{}) {
   108  	i.loading.Lock()
   109  }
   110  
   111  // afterLoad is invoked by stateify.
   112  // +checklocks:i.loading
   113  func (i *inodeFileState) afterLoad() {
   114  	load := func() (err error) {
   115  		// Manually restore the p9.File.
   116  		name, ok := i.s.inodeMappings[i.sattr.InodeID]
   117  		if !ok {
   118  			// This should be impossible, see assertion in
   119  			// beforeSave.
   120  			return fmt.Errorf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings))
   121  		}
   122  		ctx := &dummyClockContext{context.Background()}
   123  
   124  		_, i.file, err = i.s.attach.walk(ctx, splitAbsolutePath(name))
   125  		if err != nil {
   126  			return fs.ErrCorruption{fmt.Errorf("failed to walk to %q: %v", name, err)}
   127  		}
   128  
   129  		// Remap the saved inode number into the gofer device using the
   130  		// actual device and actual inode that exists in our new
   131  		// environment.
   132  		qid, mask, attrs, err := i.file.getAttr(ctx, p9.AttrMaskAll())
   133  		if err != nil {
   134  			return fs.ErrCorruption{fmt.Errorf("failed to get file attributes of %s: %v", name, err)}
   135  		}
   136  		if !mask.RDev {
   137  			return fs.ErrCorruption{fmt.Errorf("file %s lacks device", name)}
   138  		}
   139  		i.key = device.MultiDeviceKey{
   140  			Device:          attrs.RDev,
   141  			SecondaryDevice: i.s.connID,
   142  			Inode:           qid.Path,
   143  		}
   144  		if !goferDevice.Load(i.key, i.sattr.InodeID) {
   145  			return fs.ErrCorruption{fmt.Errorf("gofer device %s -> %d conflict in gofer device mappings: %s", i.key, i.sattr.InodeID, goferDevice)}
   146  		}
   147  
   148  		if i.sattr.Type == fs.RegularFile {
   149  			env, ok := fs.CurrentRestoreEnvironment()
   150  			if !ok {
   151  				return errors.New("missing restore environment")
   152  			}
   153  			uattr := unstable(ctx, mask, attrs, i.s.mounter, i.s.client)
   154  			if env.ValidateFileSize && uattr.Size != i.savedUAttr.Size {
   155  				return fs.ErrCorruption{fmt.Errorf("file size has changed for %s: previously %d, now %d", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.Size)}
   156  			}
   157  			if env.ValidateFileTimestamp && uattr.ModificationTime != i.savedUAttr.ModificationTime {
   158  				return fs.ErrCorruption{fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.ModificationTime)}
   159  			}
   160  			i.savedUAttr = nil
   161  		}
   162  
   163  		// See comment on i.loading(). This only unlocks on the
   164  		// non-error path.
   165  		i.loading.Unlock() // +checklocksforce: per comment.
   166  		return nil
   167  	}
   168  
   169  	fs.Async(fs.CatchError(load))
   170  }