summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/gofer/cache_policy.go
blob: 07a564e929825eb7ea8e9c47bde363581bd8e50a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gofer

import (
	"fmt"

	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/sentry/fs"
)

// cachePolicy is a 9p cache policy. It has methods that determine what to
// cache (if anything) for a given inode.
type cachePolicy int

const (
	// Cache nothing.
	cacheNone cachePolicy = iota

	// Use virtual file system cache for everything.
	cacheAll

	// Use virtual file system cache for everything, but send writes to the
	// fs agent immediately.
	cacheAllWritethrough

	// Use the (host) page cache for reads/writes, but don't cache anything
	// else. This allows the sandbox filesystem to stay in sync with any
	// changes to the remote filesystem.
	//
	// This policy should *only* be used with remote filesystems that
	// donate their host FDs to the sandbox and thus use the host page
	// cache, otherwise the dirent state will be inconsistent.
	cacheRemoteRevalidating
)

// String returns the string name of the cache policy.
func (cp cachePolicy) String() string {
	switch cp {
	case cacheNone:
		return "cacheNone"
	case cacheAll:
		return "cacheAll"
	case cacheAllWritethrough:
		return "cacheAllWritethrough"
	case cacheRemoteRevalidating:
		return "cacheRemoteRevalidating"
	default:
		return "unknown"
	}
}

func parseCachePolicy(policy string) (cachePolicy, error) {
	switch policy {
	case "fscache":
		return cacheAll, nil
	case "none":
		return cacheNone, nil
	case "fscache_writethrough":
		return cacheAllWritethrough, nil
	case "remote_revalidating":
		return cacheRemoteRevalidating, nil
	}
	return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy)
}

// cacheUAtters determines whether unstable attributes should be cached for the
// given inode.
func (cp cachePolicy) cacheUAttrs(inode *fs.Inode) bool {
	if !fs.IsFile(inode.StableAttr) && !fs.IsDir(inode.StableAttr) {
		return false
	}
	return cp == cacheAll || cp == cacheAllWritethrough
}

// cacheReaddir determines whether readdir results should be cached.
func (cp cachePolicy) cacheReaddir() bool {
	return cp == cacheAll || cp == cacheAllWritethrough
}

// useCachingInodeOps determines whether the page cache should be used for the
// given inode. If the remote filesystem donates host FDs to the sentry, then
// the host kernel's page cache will be used, otherwise we will use a
// sentry-internal page cache.
func (cp cachePolicy) useCachingInodeOps(inode *fs.Inode) bool {
	// Do cached IO for regular files only. Some "character devices" expect
	// no caching.
	if !fs.IsFile(inode.StableAttr) {
		return false
	}
	return cp == cacheAll || cp == cacheAllWritethrough
}

// writeThough indicates whether writes to the file should be synced to the
// gofer immediately.
func (cp cachePolicy) writeThrough(inode *fs.Inode) bool {
	return cp == cacheNone || cp == cacheAllWritethrough
}

// revalidate revalidates the child Inode if the cache policy allows it.
//
// Depending on the cache policy, revalidate will walk from the parent to the
// child inode, and if any unstable attributes have changed, will update the
// cached attributes on the child inode. If the walk fails, or the returned
// inode id is different from the one being revalidated, then the entire Dirent
// must be reloaded.
func (cp cachePolicy) revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool {
	if cp == cacheAll || cp == cacheAllWritethrough {
		return false
	}

	if cp == cacheNone {
		return true
	}

	childIops, ok := child.InodeOperations.(*inodeOperations)
	if !ok {
		if _, ok := child.InodeOperations.(*fifo); ok {
			return false
		}
		panic(fmt.Sprintf("revalidating inode operations of unknown type %T", child.InodeOperations))
	}
	parentIops, ok := parent.InodeOperations.(*inodeOperations)
	if !ok {
		panic(fmt.Sprintf("revalidating inode operations with parent of unknown type %T", parent.InodeOperations))
	}

	// Walk from parent to child again.
	//
	// TODO(b/112031682): If we have a directory FD in the parent
	// inodeOperations, then we can use fstatat(2) to get the inode
	// attributes instead of making this RPC.
	qids, f, mask, attr, err := parentIops.fileState.file.walkGetAttr(ctx, []string{name})
	if err != nil {
		// Can't look up the name. Trigger reload.
		return true
	}
	f.close(ctx)

	// If the Path has changed, then we are not looking at the file file.
	// We must reload.
	if qids[0].Path != childIops.fileState.key.Inode {
		return true
	}

	// If we are not caching unstable attrs, then there is nothing to
	// update on this inode.
	if !cp.cacheUAttrs(child) {
		return false
	}

	// Update the inode's cached unstable attrs.
	s := childIops.session()
	childIops.cachingInodeOps.UpdateUnstable(unstable(ctx, mask, attr, s.mounter, s.client))

	return false
}

// keep indicates that dirents should be kept pinned in the dirent tree even if
// there are no application references on the file.
func (cp cachePolicy) keep(d *fs.Dirent) bool {
	if cp == cacheNone {
		return false
	}
	sattr := d.Inode.StableAttr
	// NOTE(b/31979197): Only cache files, directories, and symlinks.
	return fs.IsFile(sattr) || fs.IsDir(sattr) || fs.IsSymlink(sattr)
}

// cacheNegativeDirents indicates that negative dirents should be held in the
// dirent tree.
func (cp cachePolicy) cacheNegativeDirents() bool {
	return cp == cacheAll || cp == cacheAllWritethrough
}