summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fsimpl/gofer/directory.go
blob: 6d4ebc2bf7f01232963615d3a678a2c2118aa404 (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
// Copyright 2019 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 (
	"sync"
	"sync/atomic"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/context"
	"gvisor.dev/gvisor/pkg/p9"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/syserror"
)

func (d *dentry) isDir() bool {
	return d.fileType() == linux.S_IFDIR
}

// Preconditions: d.dirMu must be locked. d.isDir(). fs.opts.interop !=
// InteropModeShared.
func (d *dentry) cacheNegativeChildLocked(name string) {
	if d.negativeChildren == nil {
		d.negativeChildren = make(map[string]struct{})
	}
	d.negativeChildren[name] = struct{}{}
}

type directoryFD struct {
	fileDescription
	vfs.DirectoryFileDescriptionDefaultImpl

	mu      sync.Mutex
	off     int64
	dirents []vfs.Dirent
}

// Release implements vfs.FileDescriptionImpl.Release.
func (fd *directoryFD) Release() {
}

// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
	fd.mu.Lock()
	defer fd.mu.Unlock()

	if fd.dirents == nil {
		ds, err := fd.dentry().getDirents(ctx)
		if err != nil {
			return err
		}
		fd.dirents = ds
	}

	for fd.off < int64(len(fd.dirents)) {
		if !cb.Handle(fd.dirents[fd.off]) {
			return nil
		}
		fd.off++
	}
	return nil
}

// Preconditions: d.isDir(). There exists at least one directoryFD representing d.
func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
	// 9P2000.L's readdir does not specify behavior in the presence of
	// concurrent mutation of an iterated directory, so implementations may
	// duplicate or omit entries in this case, which violates POSIX semantics.
	// Thus we read all directory entries while holding d.dirMu to exclude
	// directory mutations. (Note that it is impossible for the client to
	// exclude concurrent mutation from other remote filesystem users. Since
	// there is no way to detect if the server has incorrectly omitted
	// directory entries, we simply assume that the server is well-behaved
	// under InteropModeShared.) This is inconsistent with Linux (which appears
	// to assume that directory fids have the correct semantics, and translates
	// struct file_operations::readdir calls directly to readdir RPCs), but is
	// consistent with VFS1.
	//
	// NOTE(b/135560623): In particular, some gofer implementations may not
	// retain state between calls to Readdir, so may not provide a coherent
	// directory stream across in the presence of mutation.

	d.fs.renameMu.RLock()
	defer d.fs.renameMu.RUnlock()
	d.dirMu.Lock()
	defer d.dirMu.Unlock()
	if d.dirents != nil {
		return d.dirents, nil
	}

	// It's not clear if 9P2000.L's readdir is expected to return "." and "..",
	// so we generate them here.
	parent := d.vfsd.ParentOrSelf().Impl().(*dentry)
	dirents := []vfs.Dirent{
		{
			Name:    ".",
			Type:    linux.DT_DIR,
			Ino:     d.ino,
			NextOff: 1,
		},
		{
			Name:    "..",
			Type:    uint8(atomic.LoadUint32(&parent.mode) >> 12),
			Ino:     parent.ino,
			NextOff: 2,
		},
	}
	off := uint64(0)
	const count = 64 * 1024 // for consistency with the vfs1 client
	d.handleMu.RLock()
	defer d.handleMu.RUnlock()
	if !d.handleReadable {
		// This should not be possible because a readable handle should have
		// been opened when the calling directoryFD was opened.
		panic("gofer.dentry.getDirents called without a readable handle")
	}
	for {
		p9ds, err := d.handle.file.readdir(ctx, off, count)
		if err != nil {
			return nil, err
		}
		if len(p9ds) == 0 {
			// Cache dirents for future directoryFDs if permitted.
			if d.fs.opts.interop != InteropModeShared {
				d.dirents = dirents
			}
			return dirents, nil
		}
		for _, p9d := range p9ds {
			if p9d.Name == "." || p9d.Name == ".." {
				continue
			}
			dirent := vfs.Dirent{
				Name:    p9d.Name,
				Ino:     p9d.QID.Path,
				NextOff: int64(len(dirents) + 1),
			}
			// p9 does not expose 9P2000.U's DMDEVICE, DMNAMEDPIPE, or
			// DMSOCKET.
			switch p9d.Type {
			case p9.TypeSymlink:
				dirent.Type = linux.DT_LNK
			case p9.TypeDir:
				dirent.Type = linux.DT_DIR
			default:
				dirent.Type = linux.DT_REG
			}
			dirents = append(dirents, dirent)
		}
		off = p9ds[len(p9ds)-1].Offset
	}
}

// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
	fd.mu.Lock()
	defer fd.mu.Unlock()

	switch whence {
	case linux.SEEK_SET:
		if offset < 0 {
			return 0, syserror.EINVAL
		}
		if offset == 0 {
			// Ensure that the next call to fd.IterDirents() calls
			// fd.dentry().getDirents().
			fd.dirents = nil
		}
		fd.off = offset
		return fd.off, nil
	case linux.SEEK_CUR:
		offset += fd.off
		if offset < 0 {
			return 0, syserror.EINVAL
		}
		// Don't clear fd.dirents in this case, even if offset == 0.
		fd.off = offset
		return fd.off, nil
	default:
		return 0, syserror.EINVAL
	}
}