github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/inode_overlay_test.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 fs_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/SagerNet/gvisor/pkg/context"
    21  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    22  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/fs/fsutil"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/fs/ramfs"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/contexttest"
    26  )
    27  
    28  func TestLookup(t *testing.T) {
    29  	ctx := contexttest.Context(t)
    30  	for _, test := range []struct {
    31  		// Test description.
    32  		desc string
    33  
    34  		// Lookup parameters.
    35  		dir  *fs.Inode
    36  		name string
    37  
    38  		// Want from lookup.
    39  		found    bool
    40  		hasUpper bool
    41  		hasLower bool
    42  	}{
    43  		{
    44  			desc: "no upper, lower has name",
    45  			dir: fs.NewTestOverlayDir(ctx,
    46  				nil, /* upper */
    47  				newTestRamfsDir(ctx, []dirContent{
    48  					{
    49  						name: "a",
    50  						dir:  false,
    51  					},
    52  				}, nil), /* lower */
    53  				false /* revalidate */),
    54  			name:     "a",
    55  			found:    true,
    56  			hasUpper: false,
    57  			hasLower: true,
    58  		},
    59  		{
    60  			desc: "no lower, upper has name",
    61  			dir: fs.NewTestOverlayDir(ctx,
    62  				newTestRamfsDir(ctx, []dirContent{
    63  					{
    64  						name: "a",
    65  						dir:  false,
    66  					},
    67  				}, nil), /* upper */
    68  				nil, /* lower */
    69  				false /* revalidate */),
    70  			name:     "a",
    71  			found:    true,
    72  			hasUpper: true,
    73  			hasLower: false,
    74  		},
    75  		{
    76  			desc: "upper and lower, only lower has name",
    77  			dir: fs.NewTestOverlayDir(ctx,
    78  				newTestRamfsDir(ctx, []dirContent{
    79  					{
    80  						name: "b",
    81  						dir:  false,
    82  					},
    83  				}, nil), /* upper */
    84  				newTestRamfsDir(ctx, []dirContent{
    85  					{
    86  						name: "a",
    87  						dir:  false,
    88  					},
    89  				}, nil), /* lower */
    90  				false /* revalidate */),
    91  			name:     "a",
    92  			found:    true,
    93  			hasUpper: false,
    94  			hasLower: true,
    95  		},
    96  		{
    97  			desc: "upper and lower, only upper has name",
    98  			dir: fs.NewTestOverlayDir(ctx,
    99  				newTestRamfsDir(ctx, []dirContent{
   100  					{
   101  						name: "a",
   102  						dir:  false,
   103  					},
   104  				}, nil), /* upper */
   105  				newTestRamfsDir(ctx, []dirContent{
   106  					{
   107  						name: "b",
   108  						dir:  false,
   109  					},
   110  				}, nil), /* lower */
   111  				false /* revalidate */),
   112  			name:     "a",
   113  			found:    true,
   114  			hasUpper: true,
   115  			hasLower: false,
   116  		},
   117  		{
   118  			desc: "upper and lower, both have file",
   119  			dir: fs.NewTestOverlayDir(ctx,
   120  				newTestRamfsDir(ctx, []dirContent{
   121  					{
   122  						name: "a",
   123  						dir:  false,
   124  					},
   125  				}, nil), /* upper */
   126  				newTestRamfsDir(ctx, []dirContent{
   127  					{
   128  						name: "a",
   129  						dir:  false,
   130  					},
   131  				}, nil), /* lower */
   132  				false /* revalidate */),
   133  			name:     "a",
   134  			found:    true,
   135  			hasUpper: true,
   136  			hasLower: false,
   137  		},
   138  		{
   139  			desc: "upper and lower, both have directory",
   140  			dir: fs.NewTestOverlayDir(ctx,
   141  				newTestRamfsDir(ctx, []dirContent{
   142  					{
   143  						name: "a",
   144  						dir:  true,
   145  					},
   146  				}, nil), /* upper */
   147  				newTestRamfsDir(ctx, []dirContent{
   148  					{
   149  						name: "a",
   150  						dir:  true,
   151  					},
   152  				}, nil), /* lower */
   153  				false /* revalidate */),
   154  			name:     "a",
   155  			found:    true,
   156  			hasUpper: true,
   157  			hasLower: true,
   158  		},
   159  		{
   160  			desc: "upper and lower, upper negative masks lower file",
   161  			dir: fs.NewTestOverlayDir(ctx,
   162  				newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */
   163  				newTestRamfsDir(ctx, []dirContent{
   164  					{
   165  						name: "a",
   166  						dir:  false,
   167  					},
   168  				}, nil), /* lower */
   169  				false /* revalidate */),
   170  			name:     "a",
   171  			found:    false,
   172  			hasUpper: false,
   173  			hasLower: false,
   174  		},
   175  		{
   176  			desc: "upper and lower, upper negative does not mask lower file",
   177  			dir: fs.NewTestOverlayDir(ctx,
   178  				newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */
   179  				newTestRamfsDir(ctx, []dirContent{
   180  					{
   181  						name: "a",
   182  						dir:  false,
   183  					},
   184  				}, nil), /* lower */
   185  				false /* revalidate */),
   186  			name:     "a",
   187  			found:    true,
   188  			hasUpper: false,
   189  			hasLower: true,
   190  		},
   191  	} {
   192  		t.Run(test.desc, func(t *testing.T) {
   193  			dirent, err := test.dir.Lookup(ctx, test.name)
   194  			if test.found && (linuxerr.Equals(linuxerr.ENOENT, err) || dirent.IsNegative()) {
   195  				t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err)
   196  			}
   197  			if !test.found {
   198  				if !linuxerr.Equals(linuxerr.ENOENT, err) && !dirent.IsNegative() {
   199  					t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err)
   200  				}
   201  				// Nothing more to check.
   202  				return
   203  			}
   204  			if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper {
   205  				t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper)
   206  			}
   207  			if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower {
   208  				t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower)
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func TestLookupRevalidation(t *testing.T) {
   215  	// File name used in the tests.
   216  	fileName := "foofile"
   217  	ctx := contexttest.Context(t)
   218  	for _, tc := range []struct {
   219  		// Test description.
   220  		desc string
   221  
   222  		// Upper and lower fs for the overlay.
   223  		upper *fs.Inode
   224  		lower *fs.Inode
   225  
   226  		// Whether the upper requires revalidation.
   227  		revalidate bool
   228  
   229  		// Whether we should get the same dirent on second lookup.
   230  		wantSame bool
   231  	}{
   232  		{
   233  			desc:       "file from upper with no revalidation",
   234  			upper:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   235  			lower:      newTestRamfsDir(ctx, nil, nil),
   236  			revalidate: false,
   237  			wantSame:   true,
   238  		},
   239  		{
   240  			desc:       "file from upper with revalidation",
   241  			upper:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   242  			lower:      newTestRamfsDir(ctx, nil, nil),
   243  			revalidate: true,
   244  			wantSame:   false,
   245  		},
   246  		{
   247  			desc:       "file from lower with no revalidation",
   248  			upper:      newTestRamfsDir(ctx, nil, nil),
   249  			lower:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   250  			revalidate: false,
   251  			wantSame:   true,
   252  		},
   253  		{
   254  			desc:       "file from lower with revalidation",
   255  			upper:      newTestRamfsDir(ctx, nil, nil),
   256  			lower:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   257  			revalidate: true,
   258  			// The file does not exist in the upper, so we do not
   259  			// need to revalidate it.
   260  			wantSame: true,
   261  		},
   262  		{
   263  			desc:       "file from upper and lower with no revalidation",
   264  			upper:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   265  			lower:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   266  			revalidate: false,
   267  			wantSame:   true,
   268  		},
   269  		{
   270  			desc:       "file from upper and lower with revalidation",
   271  			upper:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   272  			lower:      newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil),
   273  			revalidate: true,
   274  			wantSame:   false,
   275  		},
   276  	} {
   277  		t.Run(tc.desc, func(t *testing.T) {
   278  			root := fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root")
   279  			ctx = &rootContext{
   280  				Context: ctx,
   281  				root:    root,
   282  			}
   283  			overlay := fs.NewDirent(ctx, fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay")
   284  			// Lookup the file twice through the overlay.
   285  			first, err := overlay.Walk(ctx, root, fileName)
   286  			if err != nil {
   287  				t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
   288  			}
   289  			second, err := overlay.Walk(ctx, root, fileName)
   290  			if err != nil {
   291  				t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err)
   292  			}
   293  
   294  			if tc.wantSame && first != second {
   295  				t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second)
   296  			} else if !tc.wantSame && first == second {
   297  				t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first)
   298  			}
   299  		})
   300  	}
   301  }
   302  
   303  func TestCacheFlush(t *testing.T) {
   304  	ctx := contexttest.Context(t)
   305  
   306  	// Upper and lower each have a file.
   307  	upperFileName := "file-from-upper"
   308  	lowerFileName := "file-from-lower"
   309  	upper := newTestRamfsDir(ctx, []dirContent{{name: upperFileName}}, nil)
   310  	lower := newTestRamfsDir(ctx, []dirContent{{name: lowerFileName}}, nil)
   311  
   312  	overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */)
   313  
   314  	mns, err := fs.NewMountNamespace(ctx, overlay)
   315  	if err != nil {
   316  		t.Fatalf("NewMountNamespace failed: %v", err)
   317  	}
   318  	root := mns.Root()
   319  	defer root.DecRef(ctx)
   320  
   321  	ctx = &rootContext{
   322  		Context: ctx,
   323  		root:    root,
   324  	}
   325  
   326  	for _, fileName := range []string{upperFileName, lowerFileName} {
   327  		// Walk to the file.
   328  		maxTraversals := uint(0)
   329  		dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals)
   330  		if err != nil {
   331  			t.Fatalf("FindInode(%q) failed: %v", fileName, err)
   332  		}
   333  
   334  		// Get a file from the dirent.
   335  		file, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true})
   336  		if err != nil {
   337  			t.Fatalf("GetFile() failed: %v", err)
   338  		}
   339  
   340  		// The dirent should have 3 refs, one from us, one from the
   341  		// file, and one from the dirent cache.
   342  		// dirent cache.
   343  		if got, want := dirent.ReadRefs(), 3; int(got) != want {
   344  			t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
   345  		}
   346  
   347  		// Drop the file reference.
   348  		file.DecRef(ctx)
   349  
   350  		// Dirent should have 2 refs left.
   351  		if got, want := dirent.ReadRefs(), 2; int(got) != want {
   352  			t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
   353  		}
   354  
   355  		// Flush the dirent cache.
   356  		mns.FlushMountSourceRefs()
   357  
   358  		// Dirent should have 1 ref left from the dirent cache.
   359  		if got, want := dirent.ReadRefs(), 1; int(got) != want {
   360  			t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
   361  		}
   362  
   363  		// Drop our ref.
   364  		dirent.DecRef(ctx)
   365  
   366  		// We should be back to zero refs.
   367  		if got, want := dirent.ReadRefs(), 0; int(got) != want {
   368  			t.Errorf("dirent.ReadRefs() got %d want %d", got, want)
   369  		}
   370  	}
   371  
   372  }
   373  
   374  type dir struct {
   375  	fs.InodeOperations
   376  
   377  	// List of negative child names.
   378  	negative []string
   379  
   380  	// ReaddirCalled records whether Readdir was called on a file
   381  	// corresponding to this inode.
   382  	ReaddirCalled bool
   383  }
   384  
   385  // GetXattr implements InodeOperations.GetXattr.
   386  func (d *dir) GetXattr(_ context.Context, _ *fs.Inode, name string, _ uint64) (string, error) {
   387  	for _, n := range d.negative {
   388  		if name == fs.XattrOverlayWhiteout(n) {
   389  			return "y", nil
   390  		}
   391  	}
   392  	return "", linuxerr.ENOATTR
   393  }
   394  
   395  // GetFile implements InodeOperations.GetFile.
   396  func (d *dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
   397  	file, err := d.InodeOperations.GetFile(ctx, dirent, flags)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  	defer file.DecRef(ctx)
   402  	// Wrap the file's FileOperations in a dirFile.
   403  	fops := &dirFile{
   404  		FileOperations: file.FileOperations,
   405  		inode:          d,
   406  	}
   407  	return fs.NewFile(ctx, dirent, flags, fops), nil
   408  }
   409  
   410  type dirContent struct {
   411  	name string
   412  	dir  bool
   413  }
   414  
   415  type dirFile struct {
   416  	fs.FileOperations
   417  	inode *dir
   418  }
   419  
   420  type inode struct {
   421  	fsutil.InodeGenericChecker       `state:"nosave"`
   422  	fsutil.InodeNoExtendedAttributes `state:"nosave"`
   423  	fsutil.InodeNoopRelease          `state:"nosave"`
   424  	fsutil.InodeNoopWriteOut         `state:"nosave"`
   425  	fsutil.InodeNotAllocatable       `state:"nosave"`
   426  	fsutil.InodeNotDirectory         `state:"nosave"`
   427  	fsutil.InodeNotMappable          `state:"nosave"`
   428  	fsutil.InodeNotSocket            `state:"nosave"`
   429  	fsutil.InodeNotSymlink           `state:"nosave"`
   430  	fsutil.InodeNotTruncatable       `state:"nosave"`
   431  	fsutil.InodeNotVirtual           `state:"nosave"`
   432  
   433  	fsutil.InodeSimpleAttributes
   434  	fsutil.InodeStaticFileGetter
   435  }
   436  
   437  // Readdir implements fs.FileOperations.Readdir. It sets the ReaddirCalled
   438  // field on the inode.
   439  func (f *dirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) {
   440  	f.inode.ReaddirCalled = true
   441  	return f.FileOperations.Readdir(ctx, file, ser)
   442  }
   443  
   444  func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
   445  	inode := fs.NewInode(ctx, &inode{
   446  		InodeStaticFileGetter: fsutil.InodeStaticFileGetter{
   447  			Contents: []byte("foobar"),
   448  		},
   449  	}, msrc, fs.StableAttr{Type: fs.RegularFile})
   450  	return inode
   451  }
   452  
   453  func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode {
   454  	msrc := fs.NewPseudoMountSource(ctx)
   455  	contents := make(map[string]*fs.Inode)
   456  	for _, c := range contains {
   457  		if c.dir {
   458  			contents[c.name] = newTestRamfsDir(ctx, nil, nil)
   459  		} else {
   460  			contents[c.name] = newTestRamfsInode(ctx, msrc)
   461  		}
   462  	}
   463  	dops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermissions{
   464  		User: fs.PermMask{Read: true, Execute: true},
   465  	})
   466  	return fs.NewInode(ctx, &dir{
   467  		InodeOperations: dops,
   468  		negative:        negative,
   469  	}, msrc, fs.StableAttr{Type: fs.Directory})
   470  }