summaryrefslogtreecommitdiffhomepage
path: root/pkg/p9/file.go
blob: 8d6af2d6bdeed28e4402767a2a1e58b907029c5f (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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// 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 p9

import (
	"errors"

	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/pkg/fd"
)

// Attacher is provided by the server.
type Attacher interface {
	// Attach returns a new File.
	//
	// The client-side attach will be translate to a series of walks from
	// the file returned by this Attach call.
	Attach() (File, error)
}

// File is a set of operations corresponding to a single node.
//
// Note that on the server side, the server logic places constraints on
// concurrent operations to make things easier. This may reduce the need for
// complex, error-prone locking and logic in the backend. These are documented
// for each method.
//
// There are three different types of guarantees provided:
//
// none: There is no concurrency guarantee. The method may be invoked
// concurrently with any other method on any other file.
//
// read: The method is guaranteed to be exclusive of any write or global
// operation that is mutating the state of the directory tree starting at this
// node. For example, this means creating new files, symlinks, directories or
// renaming a directory entry (or renaming in to this target), but the method
// may be called concurrently with other read methods.
//
// write: The method is guaranteed to be exclusive of any read, write or global
// operation that is mutating the state of the directory tree starting at this
// node, as described in read above. There may however, be other write
// operations executing concurrently on other components in the directory tree.
//
// global: The method is guaranteed to be exclusive of any read, write or
// global operation.
type File interface {
	// Walk walks to the path components given in names.
	//
	// Walk returns QIDs in the same order that the names were passed in.
	//
	// An empty list of arguments should return a copy of the current file.
	//
	// On the server, Walk has a read concurrency guarantee.
	Walk(names []string) ([]QID, File, error)

	// WalkGetAttr walks to the next file and returns its maximal set of
	// attributes.
	//
	// Server-side p9.Files may return unix.ENOSYS to indicate that Walk
	// and GetAttr should be used separately to satisfy this request.
	//
	// On the server, WalkGetAttr has a read concurrency guarantee.
	WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error)

	// MultiGetAttr batches up multiple calls to GetAttr(). names is a list of
	// path components similar to Walk(). If the first component name is empty,
	// the current file is stat'd and included in the results. If the walk reaches
	// a file that doesn't exist or not a directory, MultiGetAttr returns the
	// partial result with no error.
	//
	// On the server, MultiGetAttr has a read concurrency guarantee.
	MultiGetAttr(names []string) ([]FullStat, error)

	// StatFS returns information about the file system associated with
	// this file.
	//
	// On the server, StatFS has no concurrency guarantee.
	StatFS() (FSStat, error)

	// GetAttr returns attributes of this node.
	//
	// On the server, GetAttr has a read concurrency guarantee.
	GetAttr(req AttrMask) (QID, AttrMask, Attr, error)

	// SetAttr sets attributes on this node.
	//
	// On the server, SetAttr has a write concurrency guarantee.
	SetAttr(valid SetAttrMask, attr SetAttr) error

	// GetXattr returns extended attributes of this node.
	//
	// Size indicates the size of the buffer that has been allocated to hold the
	// attribute value. If the value is larger than size, implementations may
	// return ERANGE to indicate that the buffer is too small, but they are also
	// free to ignore the hint entirely (i.e. the value returned may be larger
	// than size). All size checking is done independently at the syscall layer.
	//
	// On the server, GetXattr has a read concurrency guarantee.
	GetXattr(name string, size uint64) (string, error)

	// SetXattr sets extended attributes on this node.
	//
	// On the server, SetXattr has a write concurrency guarantee.
	SetXattr(name, value string, flags uint32) error

	// ListXattr lists the names of the extended attributes on this node.
	//
	// Size indicates the size of the buffer that has been allocated to hold the
	// attribute list. If the list would be larger than size, implementations may
	// return ERANGE to indicate that the buffer is too small, but they are also
	// free to ignore the hint entirely (i.e. the value returned may be larger
	// than size). All size checking is done independently at the syscall layer.
	//
	// On the server, ListXattr has a read concurrency guarantee.
	ListXattr(size uint64) (map[string]struct{}, error)

	// RemoveXattr removes extended attributes on this node.
	//
	// On the server, RemoveXattr has a write concurrency guarantee.
	RemoveXattr(name string) error

	// Allocate allows the caller to directly manipulate the allocated disk space
	// for the file. See fallocate(2) for more details.
	Allocate(mode AllocateMode, offset, length uint64) error

	// Close is called when all references are dropped on the server side,
	// and Close should be called by the client to drop all references.
	//
	// For server-side implementations of Close, the error is ignored.
	//
	// Close must be called even when Open has not been called.
	//
	// On the server, Close has no concurrency guarantee.
	Close() error

	// SetAttrClose is the equivalent of calling SetAttr() followed by Close().
	// This can be used to set file times before closing the file in a single
	// operation.
	//
	// On the server, SetAttr has a write concurrency guarantee.
	// On the server, Close has no concurrency guarantee.
	SetAttrClose(valid SetAttrMask, attr SetAttr) error

	// Open must be called prior to using Read, Write or Readdir. Once Open
	// is called, some operations, such as Walk, will no longer work.
	//
	// On the client, Open should be called only once. The fd return is
	// optional, and may be nil.
	//
	// On the server, Open has a read concurrency guarantee. If an *fd.FD
	// is provided, ownership now belongs to the caller. Open is guaranteed
	// to be called only once.
	//
	// N.B. The server must resolve any lazy paths when open is called.
	// After this point, read and write may be called on files with no
	// deletion check, so resolving in the data path is not viable.
	Open(flags OpenFlags) (*fd.FD, QID, uint32, error)

	// Read reads from this file. Open must be called first.
	//
	// This may return io.EOF in addition to unix.Errno values.
	//
	// On the server, ReadAt has a read concurrency guarantee. See Open for
	// additional requirements regarding lazy path resolution.
	ReadAt(p []byte, offset uint64) (int, error)

	// Write writes to this file. Open must be called first.
	//
	// This may return io.EOF in addition to unix.Errno values.
	//
	// On the server, WriteAt has a read concurrency guarantee. See Open
	// for additional requirements regarding lazy path resolution.
	WriteAt(p []byte, offset uint64) (int, error)

	// FSync syncs this node. Open must be called first.
	//
	// On the server, FSync has a read concurrency guarantee.
	FSync() error

	// Create creates a new regular file and opens it according to the
	// flags given. This file is already Open.
	//
	// N.B. On the client, the returned file is a reference to the current
	// file, which now represents the created file. This is not the case on
	// the server. These semantics are very subtle and can easily lead to
	// bugs, but are a consequence of the 9P create operation.
	//
	// See p9.File.Open for a description of *fd.FD.
	//
	// On the server, Create has a write concurrency guarantee.
	Create(name string, flags OpenFlags, permissions FileMode, uid UID, gid GID) (*fd.FD, File, QID, uint32, error)

	// Mkdir creates a subdirectory.
	//
	// On the server, Mkdir has a write concurrency guarantee.
	Mkdir(name string, permissions FileMode, uid UID, gid GID) (QID, error)

	// Symlink makes a new symbolic link.
	//
	// On the server, Symlink has a write concurrency guarantee.
	Symlink(oldName string, newName string, uid UID, gid GID) (QID, error)

	// Link makes a new hard link.
	//
	// On the server, Link has a write concurrency guarantee.
	Link(target File, newName string) error

	// Mknod makes a new device node.
	//
	// On the server, Mknod has a write concurrency guarantee.
	Mknod(name string, mode FileMode, major uint32, minor uint32, uid UID, gid GID) (QID, error)

	// Rename renames the file.
	//
	// Rename will never be called on the server, and RenameAt will always
	// be used instead.
	Rename(newDir File, newName string) error

	// RenameAt renames a given file to a new name in a potentially new
	// directory.
	//
	// oldName must be a name relative to this file, which must be a
	// directory. newName is a name relative to newDir.
	//
	// On the server, RenameAt has a global concurrency guarantee.
	RenameAt(oldName string, newDir File, newName string) error

	// UnlinkAt the given named file.
	//
	// name must be a file relative to this directory.
	//
	// Flags are implementation-specific (e.g. O_DIRECTORY), but are
	// generally Linux unlinkat(2) flags.
	//
	// On the server, UnlinkAt has a write concurrency guarantee.
	UnlinkAt(name string, flags uint32) error

	// Readdir reads directory entries.
	//
	// This may return io.EOF in addition to unix.Errno values.
	//
	// On the server, Readdir has a read concurrency guarantee.
	Readdir(offset uint64, count uint32) ([]Dirent, error)

	// Readlink reads the link target.
	//
	// On the server, Readlink has a read concurrency guarantee.
	Readlink() (string, error)

	// Flush is called prior to Close.
	//
	// Whereas Close drops all references to the file, Flush cleans up the
	// file state. Behavior is implementation-specific.
	//
	// Flush is not related to flush(9p). Flush is an extension to 9P2000.L,
	// see version.go.
	//
	// On the server, Flush has a read concurrency guarantee.
	Flush() error

	// Connect establishes a new host-socket backed connection with a
	// socket. A File does not need to be opened before it can be connected
	// and it can be connected to multiple times resulting in a unique
	// *fd.FD each time. In addition, the lifetime of the *fd.FD is
	// independent from the lifetime of the p9.File and must be managed by
	// the caller.
	//
	// The returned FD must be non-blocking.
	//
	// Flags indicates the requested type of socket.
	//
	// On the server, Connect has a read concurrency guarantee.
	Connect(flags ConnectFlags) (*fd.FD, error)

	// Renamed is called when this node is renamed.
	//
	// This may not fail. The file will hold a reference to its parent
	// within the p9 package, and is therefore safe to use for the lifetime
	// of this File (until Close is called).
	//
	// This method should not be called by clients, who should use the
	// relevant Rename methods. (Although the method will be a no-op.)
	//
	// On the server, Renamed has a global concurrency guarantee.
	Renamed(newDir File, newName string)
}

// DefaultWalkGetAttr implements File.WalkGetAttr to return ENOSYS for server-side Files.
type DefaultWalkGetAttr struct{}

// WalkGetAttr implements File.WalkGetAttr.
func (DefaultWalkGetAttr) WalkGetAttr([]string) ([]QID, File, AttrMask, Attr, error) {
	return nil, nil, AttrMask{}, Attr{}, unix.ENOSYS
}

// DisallowClientCalls panics if a client-only function is called.
type DisallowClientCalls struct{}

// SetAttrClose implements File.SetAttrClose.
func (DisallowClientCalls) SetAttrClose(SetAttrMask, SetAttr) error {
	panic("SetAttrClose should not be called on the server")
}

// DisallowServerCalls panics if a server-only function is called.
type DisallowServerCalls struct{}

// Renamed implements File.Renamed.
func (*DisallowServerCalls) Renamed(File, string) {
	panic("Renamed should not be called on the client")
}

// DefaultMultiGetAttr implements File.MultiGetAttr() on top of File.
func DefaultMultiGetAttr(start File, names []string) ([]FullStat, error) {
	stats := make([]FullStat, 0, len(names))
	parent := start
	closeParent := func() {
		if parent != start {
			_ = parent.Close()
		}
	}
	defer closeParent()
	mask := AttrMaskAll()
	for i, name := range names {
		if len(name) == 0 && i == 0 {
			qid, valid, attr, err := parent.GetAttr(mask)
			if err != nil {
				return nil, err
			}
			stats = append(stats, FullStat{
				QID:   qid,
				Valid: valid,
				Attr:  attr,
			})
			continue
		}
		qids, child, valid, attr, err := parent.WalkGetAttr([]string{name})
		if err != nil {
			if errors.Is(err, unix.ENOENT) {
				return stats, nil
			}
			return nil, err
		}
		closeParent()
		parent = child
		stats = append(stats, FullStat{
			QID:   qids[0],
			Valid: valid,
			Attr:  attr,
		})
		if attr.Mode.FileType() != ModeDirectory {
			// Doesn't need to continue if entry is not a dir. Including symlinks
			// that cannot be followed.
			break
		}
	}
	return stats, nil
}