File: sys_getdents.go

package info (click to toggle)
golang-gvisor-gvisor 0.0~20240729.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,276 kB
  • sloc: asm: 3,361; ansic: 1,197; cpp: 348; makefile: 92; python: 89; sh: 83
file content (196 lines) | stat: -rw-r--r-- 6,523 bytes parent folder | download | duplicates (3)
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
// Copyright 2020 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 linux

import (
	"fmt"

	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/hostarch"
	"gvisor.dev/gvisor/pkg/sentry/arch"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	"gvisor.dev/gvisor/pkg/sentry/vfs"
	"gvisor.dev/gvisor/pkg/sync"
	"gvisor.dev/gvisor/pkg/usermem"
)

// Getdents implements Linux syscall getdents(2).
func Getdents(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	return getdents(t, args, false /* isGetdents64 */)
}

// Getdents64 implements Linux syscall getdents64(2).
func Getdents64(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
	return getdents(t, args, true /* isGetdents64 */)
}

// DirentStructBytesWithoutName is enough to fit (struct linux_dirent) and
// (struct linux_dirent64) without accounting for the name parameter.
const DirentStructBytesWithoutName = 8 + 8 + 2 + 1 + 1

func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (uintptr, *kernel.SyscallControl, error) {
	fd := args[0].Int()
	addr := args[1].Pointer()
	size := args[2].Int()

	if size < DirentStructBytesWithoutName {
		return 0, nil, linuxerr.EINVAL
	}

	file := t.GetFile(fd)
	if file == nil {
		return 0, nil, linuxerr.EBADF
	}
	defer file.DecRef(t)

	// We want to be sure of the allowed buffer size before calling IterDirents,
	// because this function depends on IterDirents saving state of which dirent
	// was the last one that was successfully operated on.
	allowedSize, err := t.MemoryManager().EnsurePMAsExist(t, addr, int64(size), usermem.IOOpts{
		AddressSpaceActive: true,
	})
	if allowedSize == 0 {
		return 0, nil, err
	}

	cb := getGetdentsCallback(t, int(allowedSize), int(size), isGetdents64)
	err = file.IterDirents(t, cb)
	n, _ := t.CopyOutBytes(addr, cb.buf[:cb.copied])

	putGetdentsCallback(cb)

	// Only report an error in case we didn't copy anything.
	// If we did manage to give _something_ to the caller then the correct
	// behaviour is to return success.
	if n == 0 {
		return 0, nil, err
	}

	return uintptr(n), nil, nil
}

type getdentsCallback struct {
	t                *kernel.Task
	buf              []byte
	copied           int
	userReportedSize int
	isGetdents64     bool
}

var getdentsCallbackPool = sync.Pool{
	New: func() any {
		return &getdentsCallback{}
	},
}

func getGetdentsCallback(t *kernel.Task, size int, userReportedSize int, isGetdents64 bool) *getdentsCallback {
	cb := getdentsCallbackPool.Get().(*getdentsCallback)
	buf := cb.buf
	if cap(buf) < size {
		buf = make([]byte, size)
	} else {
		buf = buf[:size]
	}

	*cb = getdentsCallback{
		t:                t,
		buf:              buf,
		copied:           0,
		userReportedSize: userReportedSize,
		isGetdents64:     isGetdents64,
	}
	return cb
}

func putGetdentsCallback(cb *getdentsCallback) {
	cb.t = nil
	cb.buf = cb.buf[:0]
	getdentsCallbackPool.Put(cb)
}

// Handle implements vfs.IterDirentsCallback.Handle.
func (cb *getdentsCallback) Handle(dirent vfs.Dirent) error {
	remaining := len(cb.buf) - cb.copied
	if cb.isGetdents64 {
		// struct linux_dirent64 {
		//     ino64_t        d_ino;    /* 64-bit inode number */
		//     off64_t        d_off;    /* 64-bit offset to next structure */
		//     unsigned short d_reclen; /* Size of this dirent */
		//     unsigned char  d_type;   /* File type */
		//     char           d_name[]; /* Filename (null-terminated) */
		// };
		size := DirentStructBytesWithoutName + len(dirent.Name)
		size = (size + 7) &^ 7 // round up to multiple of 8
		if size > remaining {
			// This is only needed to imitate Linux, since it writes out to the user
			// as it's iterating over dirs. We don't do that because we can't take
			// the mm.mappingMu while holding the filesystem mutex.
			if cb.copied == 0 && cb.userReportedSize >= size {
				return linuxerr.EFAULT
			}
			return linuxerr.EINVAL
		}
		buf := cb.buf[cb.copied : cb.copied+size]
		hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino)
		hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff))
		hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size))
		buf[18] = dirent.Type
		copy(buf[19:], dirent.Name)
		// Zero out all remaining bytes in buf, including the NUL terminator
		// after dirent.Name.
		bufTail := buf[19+len(dirent.Name):]
		clear(bufTail)
		cb.copied += size
	} else {
		// struct linux_dirent {
		//     unsigned long  d_ino;     /* Inode number */
		//     unsigned long  d_off;     /* Offset to next linux_dirent */
		//     unsigned short d_reclen;  /* Length of this linux_dirent */
		//     char           d_name[];  /* Filename (null-terminated) */
		//                       /* length is actually (d_reclen - 2 -
		//                          offsetof(struct linux_dirent, d_name)) */
		//     /*
		//     char           pad;       // Zero padding byte
		//     char           d_type;    // File type (only since Linux
		//                               // 2.6.4); offset is (d_reclen - 1)
		//     */
		// };
		if cb.t.Arch().Width() != 8 {
			panic(fmt.Sprintf("unsupported sizeof(unsigned long): %d", cb.t.Arch().Width()))
		}
		size := DirentStructBytesWithoutName + len(dirent.Name)
		size = (size + 7) &^ 7 // round up to multiple of sizeof(long)
		if size > remaining {
			if cb.copied == 0 && cb.userReportedSize >= size {
				return linuxerr.EFAULT
			}
			return linuxerr.EINVAL
		}
		buf := cb.buf[cb.copied : cb.copied+size]
		hostarch.ByteOrder.PutUint64(buf[0:8], dirent.Ino)
		hostarch.ByteOrder.PutUint64(buf[8:16], uint64(dirent.NextOff))
		hostarch.ByteOrder.PutUint16(buf[16:18], uint16(size))
		copy(buf[18:], dirent.Name)
		// Zero out all remaining bytes in buf, including the NUL terminator
		// after dirent.Name and the zero padding byte between the name and
		// dirent type.
		bufTail := buf[18+len(dirent.Name) : size-1]
		clear(bufTail)
		buf[size-1] = dirent.Type
		cb.copied += size
	}

	return nil
}