summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/filesystems.go
blob: c5b51620af5d74e050f88417a57f814fc7e1dc55 (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
// 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 fs

import (
	"fmt"
	"sort"
	"strings"

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

// FilesystemFlags matches include/linux/fs.h:file_system_type.fs_flags.
type FilesystemFlags int

const (
	// FilesystemRequiresDev indicates that the file system requires a device name
	// on mount. It is used to construct the output of /proc/filesystems.
	FilesystemRequiresDev FilesystemFlags = 1

	// Currently other flags are not used, but can be pulled in from
	// include/linux/fs.h:file_system_type as needed.
)

// Filesystem is a mountable file system.
type Filesystem interface {
	// Name is the unique identifier of the file system. It corresponds to the
	// filesystemtype argument of sys_mount and will appear in the output of
	// /proc/filesystems.
	Name() string

	// Flags indicate common properties of the file system.
	Flags() FilesystemFlags

	// Mount generates a mountable Inode backed by device and configured
	// using file system independent flags and file system dependent
	// data options.
	//
	// Mount may return arbitrary errors. They do not need syserr translations.
	Mount(ctx context.Context, device string, flags MountSourceFlags, data string, dataObj interface{}) (*Inode, error)

	// AllowUserMount determines whether mount(2) is allowed to mount a
	// file system of this type.
	AllowUserMount() bool

	// AllowUserList determines whether this filesystem is listed in
	// /proc/filesystems
	AllowUserList() bool
}

// filesystems is the global set of registered file systems. It does not need
// to be saved. Packages registering and unregistering file systems must do so
// before calling save/restore methods.
var filesystems = struct {
	// mu protects registered below.
	mu sync.Mutex

	// registered is a set of registered Filesystems.
	registered map[string]Filesystem
}{
	registered: make(map[string]Filesystem),
}

// RegisterFilesystem registers a new file system that is visible to mount and
// the /proc/filesystems list. Packages implementing Filesystem should call
// RegisterFilesystem in init().
func RegisterFilesystem(f Filesystem) {
	filesystems.mu.Lock()
	defer filesystems.mu.Unlock()

	if _, ok := filesystems.registered[f.Name()]; ok {
		panic(fmt.Sprintf("filesystem already registered at %q", f.Name()))
	}
	filesystems.registered[f.Name()] = f
}

// UnregisterFilesystem removes a file system from the global set. To keep the
// file system set compatible with save/restore, UnregisterFilesystem must be
// called before save/restore methods.
//
// For instance, packages may unregister their file system after it is mounted.
// This makes sense for pseudo file systems that should not be visible or
// mountable. See whitelistfs in fs/host/fs.go for one example.
func UnregisterFilesystem(name string) {
	filesystems.mu.Lock()
	defer filesystems.mu.Unlock()

	delete(filesystems.registered, name)
}

// FindFilesystem returns a Filesystem registered at name or (nil, false) if name
// is not a file system type that can be found in /proc/filesystems.
func FindFilesystem(name string) (Filesystem, bool) {
	filesystems.mu.Lock()
	defer filesystems.mu.Unlock()

	f, ok := filesystems.registered[name]
	return f, ok
}

// GetFilesystems returns the set of registered filesystems in a consistent order.
func GetFilesystems() []Filesystem {
	filesystems.mu.Lock()
	defer filesystems.mu.Unlock()

	var ss []Filesystem
	for _, s := range filesystems.registered {
		ss = append(ss, s)
	}
	sort.Slice(ss, func(i, j int) bool { return ss[i].Name() < ss[j].Name() })
	return ss
}

// MountSourceFlags represents all mount option flags as a struct.
//
// +stateify savable
type MountSourceFlags struct {
	// ReadOnly corresponds to mount(2)'s "MS_RDONLY" and indicates that
	// the filesystem should be mounted read-only.
	ReadOnly bool

	// NoAtime corresponds to mount(2)'s "MS_NOATIME" and indicates that
	// the filesystem should not update access time in-place.
	NoAtime bool

	// ForcePageCache causes all filesystem I/O operations to use the page
	// cache, even when the platform supports direct mapped I/O. This
	// doesn't correspond to any Linux mount options.
	ForcePageCache bool

	// NoExec corresponds to mount(2)'s "MS_NOEXEC" and indicates that
	// binaries from this file system can't be executed.
	NoExec bool
}

// GenericMountSourceOptions splits a string containing comma separated tokens of the
// format 'key=value' or 'key' into a map of keys and values. For example:
//
// data = "key0=value0,key1,key2=value2" -> map{'key0':'value0','key1':'','key2':'value2'}
//
// If data contains duplicate keys, then the last token wins.
func GenericMountSourceOptions(data string) map[string]string {
	options := make(map[string]string)
	if len(data) == 0 {
		// Don't return a nil map, callers might not be expecting that.
		return options
	}

	// Parse options and skip empty ones.
	for _, opt := range strings.Split(data, ",") {
		if len(opt) > 0 {
			res := strings.SplitN(opt, "=", 2)
			if len(res) == 2 {
				options[res[0]] = res[1]
			} else {
				options[opt] = ""
			}
		}
	}
	return options
}