summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/fs/proc/seqfile/seqfile.go
blob: 16fc6789eec1c0a81391cba339884e9bf9423e03 (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
// Copyright 2018 Google LLC
//
// 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 seqfile

import (
	"io"
	"sync"

	"gvisor.googlesource.com/gvisor/pkg/abi/linux"
	"gvisor.googlesource.com/gvisor/pkg/sentry/context"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil"
	"gvisor.googlesource.com/gvisor/pkg/sentry/fs/proc/device"
	ktime "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/time"
	"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
	"gvisor.googlesource.com/gvisor/pkg/syserror"
	"gvisor.googlesource.com/gvisor/pkg/waiter"
)

// SeqHandle is a helper handle to seek in the file.
type SeqHandle interface{}

// SeqData holds the data for one unit in the file.
//
// +stateify savable
type SeqData struct {
	// The data to be returned to the user.
	Buf []byte

	// A seek handle used to find the next valid unit in ReadSeqFiledata.
	Handle SeqHandle
}

// SeqSource is a data source for a SeqFile file.
type SeqSource interface {
	// NeedsUpdate returns true if the consumer of SeqData should call
	// ReadSeqFileData again. Generation is the generation returned by
	// ReadSeqFile or 0.
	NeedsUpdate(generation int64) bool

	// Returns a slice of SeqData ordered by unit and the current
	// generation. The first entry in the slice is greater than the handle.
	// If handle is nil then all known records are returned. Generation
	// must always be greater than 0.
	ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64)
}

// SeqGenerationCounter is a counter to keep track if the SeqSource should be
// updated. SeqGenerationCounter is not thread-safe and should be protected
// with a mutex.
type SeqGenerationCounter struct {
	// The generation that the SeqData is at.
	generation int64
}

// SetGeneration sets the generation to the new value, be careful to not set it
// to a value less than current.
func (s *SeqGenerationCounter) SetGeneration(generation int64) {
	s.generation = generation
}

// Update increments the current generation.
func (s *SeqGenerationCounter) Update() {
	s.generation++
}

// Generation returns the current generation counter.
func (s *SeqGenerationCounter) Generation() int64 {
	return s.generation
}

// IsCurrent returns whether the given generation is current or not.
func (s *SeqGenerationCounter) IsCurrent(generation int64) bool {
	return s.Generation() == generation
}

// SeqFile is used to provide dynamic files that can be ordered by record.
//
// +stateify savable
type SeqFile struct {
	fsutil.InodeGenericChecker `state:"nosave"`
	fsutil.InodeNoopRelease    `state:"nosave"`
	fsutil.InodeNoopWriteOut   `state:"nosave"`
	fsutil.InodeNotDirectory   `state:"nosave"`
	fsutil.InodeNotMappable    `state:"nosave"`
	fsutil.InodeNotSocket      `state:"nosave"`
	fsutil.InodeNotSymlink     `state:"nosave"`
	fsutil.InodeNotTruncatable `state:"nosave"`
	fsutil.InodeVirtual        `state:"nosave"`

	fsutil.InodeSimpleExtendedAttributes
	fsutil.InodeSimpleAttributes

	// mu protects the fields below.
	mu sync.Mutex `state:"nosave"`

	SeqSource

	source     []SeqData
	generation int64
	lastRead   int64
}

var _ fs.InodeOperations = (*SeqFile)(nil)

// NewSeqFile returns a seqfile suitable for use by external consumers.
func NewSeqFile(ctx context.Context, source SeqSource) *SeqFile {
	return &SeqFile{
		InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC),
		SeqSource:             source,
	}
}

// NewSeqFileInode returns an Inode with SeqFile InodeOperations.
func NewSeqFileInode(ctx context.Context, source SeqSource, msrc *fs.MountSource) *fs.Inode {
	iops := NewSeqFile(ctx, source)
	sattr := fs.StableAttr{
		DeviceID:  device.ProcDevice.DeviceID(),
		InodeID:   device.ProcDevice.NextIno(),
		BlockSize: usermem.PageSize,
		Type:      fs.SpecialFile,
	}
	return fs.NewInode(iops, msrc, sattr)
}

// UnstableAttr returns unstable attributes of the SeqFile.
func (s *SeqFile) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) {
	uattr, err := s.InodeSimpleAttributes.UnstableAttr(ctx, inode)
	if err != nil {
		return fs.UnstableAttr{}, err
	}
	uattr.ModificationTime = ktime.NowFromContext(ctx)
	return uattr, nil
}

// GetFile implements fs.InodeOperations.GetFile.
func (s *SeqFile) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
	return fs.NewFile(ctx, dirent, flags, &seqFileOperations{seqFile: s}), nil
}

// findIndexAndOffset finds the unit that corresponds to a certain offset.
// Returns the unit and the offset within the unit. If there are not enough
// units len(data) and leftover offset is returned.
func findIndexAndOffset(data []SeqData, offset int64) (int, int64) {
	for i, buf := range data {
		l := int64(len(buf.Buf))
		if offset < l {
			return i, offset
		}
		offset -= l
	}
	return len(data), offset
}

// updateSourceLocked requires that s.mu is held.
func (s *SeqFile) updateSourceLocked(ctx context.Context, record int) {
	var h SeqHandle
	if record == 0 {
		h = nil
	} else {
		h = s.source[record-1].Handle
	}
	// Save what we have previously read.
	s.source = s.source[:record]
	var newSource []SeqData
	newSource, s.generation = s.SeqSource.ReadSeqFileData(ctx, h)
	s.source = append(s.source, newSource...)
}

// seqFileOperations implements fs.FileOperations.
//
// +stateify savable
type seqFileOperations struct {
	waiter.AlwaysReady       `state:"nosave"`
	fsutil.FileGenericSeek   `state:"nosave"`
	fsutil.FileNoIoctl       `state:"nosave"`
	fsutil.FileNoMMap        `state:"nosave"`
	fsutil.FileNoopFlush     `state:"nosave"`
	fsutil.FileNoopFsync     `state:"nosave"`
	fsutil.FileNoopRelease   `state:"nosave"`
	fsutil.FileNotDirReaddir `state:"nosave"`

	seqFile *SeqFile
}

var _ fs.FileOperations = (*seqFileOperations)(nil)

// Write implements fs.FileOperations.Write.
func (*seqFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) {
	return 0, syserror.EACCES
}

// Read implements fs.FileOperations.Read.
func (sfo *seqFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
	sfo.seqFile.mu.Lock()
	defer sfo.seqFile.mu.Unlock()

	sfo.seqFile.NotifyAccess(ctx)
	defer func() { sfo.seqFile.lastRead = offset }()

	updated := false

	// Try to find where we should start reading this file.
	i, recordOffset := findIndexAndOffset(sfo.seqFile.source, offset)
	if i == len(sfo.seqFile.source) {
		// Ok, we're at EOF. Let's first check to see if there might be
		// more data available to us. If there is more data, add it to
		// the end and try reading again.
		if !sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) {
			return 0, io.EOF
		}
		oldLen := len(sfo.seqFile.source)
		sfo.seqFile.updateSourceLocked(ctx, len(sfo.seqFile.source))
		updated = true
		// We know that we had consumed everything up until this point
		// so we search in the new slice instead of starting over.
		i, recordOffset = findIndexAndOffset(sfo.seqFile.source[oldLen:], recordOffset)
		i += oldLen
		// i is at most the length of the slice which is
		// len(sfo.seqFile.source) - oldLen. So at most i will be equal to
		// len(sfo.seqFile.source).
		if i == len(sfo.seqFile.source) {
			return 0, io.EOF
		}
	}

	var done int64
	// We're reading parts of a record, finish reading the current object
	// before continuing on to the next. We don't refresh our data source
	// before this record is completed.
	if recordOffset != 0 {
		n, err := dst.CopyOut(ctx, sfo.seqFile.source[i].Buf[recordOffset:])
		done += int64(n)
		dst = dst.DropFirst(n)
		if dst.NumBytes() == 0 || err != nil {
			return done, err
		}
		i++
	}

	// Next/New unit, update the source file if necessary. Make an extra
	// check to see if we've seeked backwards and if so always update our
	// data source.
	if !updated && (sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) || sfo.seqFile.lastRead > offset) {
		sfo.seqFile.updateSourceLocked(ctx, i)
		// recordOffset is 0 here and we won't update records behind the
		// current one so recordOffset is still 0 even though source
		// just got updated. Just read the next record.
	}

	// Finish by reading all the available data.
	for _, buf := range sfo.seqFile.source[i:] {
		n, err := dst.CopyOut(ctx, buf.Buf)
		done += int64(n)
		dst = dst.DropFirst(n)
		if dst.NumBytes() == 0 || err != nil {
			return done, err
		}
	}

	// If the file shrank (entries not yet read were removed above)
	// while we tried to read we can end up with nothing read.
	if done == 0 && dst.NumBytes() != 0 {
		return 0, io.EOF
	}
	return done, nil
}