Source file
src/os/root_openat.go
1
2
3
4
5
6
7 package os
8
9 import (
10 "io/fs"
11 "runtime"
12 "slices"
13 "sync"
14 "syscall"
15 "time"
16 )
17
18
19
20 type root struct {
21 name string
22
23
24
25
26 mu sync.Mutex
27 fd sysfdType
28 refs int
29 closed bool
30 }
31
32 func (r *root) Close() error {
33 r.mu.Lock()
34 defer r.mu.Unlock()
35 if !r.closed && r.refs == 0 {
36 syscall.Close(r.fd)
37 }
38 r.closed = true
39 runtime.SetFinalizer(r, nil)
40 return nil
41 }
42
43 func (r *root) incref() error {
44 r.mu.Lock()
45 defer r.mu.Unlock()
46 if r.closed {
47 return ErrClosed
48 }
49 r.refs++
50 return nil
51 }
52
53 func (r *root) decref() {
54 r.mu.Lock()
55 defer r.mu.Unlock()
56 if r.refs <= 0 {
57 panic("bad Root refcount")
58 }
59 r.refs--
60 if r.closed && r.refs == 0 {
61 syscall.Close(r.fd)
62 }
63 }
64
65 func (r *root) Name() string {
66 return r.name
67 }
68
69 func rootChmod(r *Root, name string, mode FileMode) error {
70 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
71 return struct{}{}, chmodat(parent, name, mode)
72 })
73 if err != nil {
74 return &PathError{Op: "chmodat", Path: name, Err: err}
75 }
76 return nil
77 }
78
79 func rootChown(r *Root, name string, uid, gid int) error {
80 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
81 return struct{}{}, chownat(parent, name, uid, gid)
82 })
83 if err != nil {
84 return &PathError{Op: "chownat", Path: name, Err: err}
85 }
86 return nil
87 }
88
89 func rootLchown(r *Root, name string, uid, gid int) error {
90 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
91 return struct{}{}, lchownat(parent, name, uid, gid)
92 })
93 if err != nil {
94 return &PathError{Op: "lchownat", Path: name, Err: err}
95 }
96 return nil
97 }
98
99 func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
100 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
101 return struct{}{}, chtimesat(parent, name, atime, mtime)
102 })
103 if err != nil {
104 return &PathError{Op: "chtimesat", Path: name, Err: err}
105 }
106 return nil
107 }
108
109 func rootMkdir(r *Root, name string, perm FileMode) error {
110 flags := uint(doInRootCreatingDirectory)
111 switch runtime.GOOS {
112 case "linux", "windows":
113
114
115
116
117 flags = doInRootNoHandleTerminalSlash
118 }
119 _, err := doInRoot(r, name, flags, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
120 return struct{}{}, mkdirat(parent, name, perm)
121 })
122 if err != nil {
123 return &PathError{Op: "mkdirat", Path: name, Err: err}
124 }
125 return nil
126 }
127
128 func rootMkdirAll(r *Root, fullname string, perm FileMode) error {
129
130
131
132
133
134 openDirFunc := func(parent sysfdType, name string) (sysfdType, error) {
135 for try := range 2 {
136 fd, err := rootOpenDir(parent, name)
137 switch err.(type) {
138 case nil, errSymlink:
139 return fd, err
140 }
141 if try > 0 || !IsNotExist(err) {
142 return 0, &PathError{Op: "openat", Err: err}
143 }
144
145
146 if err := mkdirat(parent, name, perm); err != nil && err != syscall.EEXIST {
147 return 0, &PathError{Op: "mkdirat", Err: err}
148 }
149 }
150 panic("unreachable")
151 }
152
153 openLastComponentFunc := func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
154 err := mkdirat(parent, name, perm)
155 if err == syscall.EEXIST {
156 mode, e := modeAt(parent, name)
157 if e == nil {
158 if mode.IsDir() {
159
160 err = nil
161 } else if mode&ModeSymlink != 0 {
162
163
164
165
166
167 fi, e := r.Stat(fullname)
168 if e == nil && fi.Mode().IsDir() {
169 err = nil
170 }
171 }
172 }
173 }
174 switch err.(type) {
175 case nil, errSymlink:
176 return struct{}{}, err
177 }
178 return struct{}{}, &PathError{Op: "mkdirat", Err: err}
179 }
180 _, err := doInRoot(r, fullname, 0, openDirFunc, openLastComponentFunc)
181 if err != nil {
182 if _, ok := err.(*PathError); !ok {
183 err = &PathError{Op: "mkdirat", Path: fullname, Err: err}
184 }
185 }
186 return err
187 }
188
189 func rootReadlink(r *Root, name string) (string, error) {
190 target, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (string, error) {
191 return readlinkat(parent, name)
192 })
193 if err != nil {
194 return "", &PathError{Op: "readlinkat", Path: name, Err: err}
195 }
196 return target, nil
197 }
198
199 func rootRemove(r *Root, name string) error {
200 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
201 return struct{}{}, removeat(parent, name)
202 })
203 if err != nil {
204 return &PathError{Op: "removeat", Path: name, Err: err}
205 }
206 return nil
207 }
208
209 func rootRemoveAll(r *Root, name string) error {
210
211
212 for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
213 name = name[:len(name)-1]
214 }
215 if endsWithDot(name) {
216
217 return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
218 }
219 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
220 return struct{}{}, removeAllFrom(parent, name)
221 })
222 if IsNotExist(err) {
223 return nil
224 }
225 if err != nil {
226 return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
227 }
228 return err
229 }
230
231 func rootRename(r *Root, oldname, newname string) error {
232 _, err := doInRoot(r, oldname, 0, nil, func(oldparent sysfdType, oldname string, oldEndsInSlash bool) (struct{}, error) {
233 flags := uint(doInRootCreatingDirectory)
234 if runtime.GOOS == "windows" {
235 flags = doInRootNoHandleTerminalSlash
236 }
237 _, err := doInRoot(r, newname, flags, nil, func(newparent sysfdType, newname string, newEndsInSlash bool) (struct{}, error) {
238 if runtime.GOOS != "windows" && newEndsInSlash {
239 oldMode, err := modeAt(oldparent, oldname)
240 if err != nil {
241 return struct{}{}, err
242 }
243 if oldMode.Type() != fs.ModeDir {
244 return struct{}{}, syscall.ENOTDIR
245 }
246 }
247
248 fi, err := lstatat(newparent, newname)
249 if err == nil && fi.IsDir() {
250 if ofi, err := lstatat(oldparent, oldname); err != nil {
251 return struct{}{}, err
252 } else if newname == oldname || !SameFile(fi, ofi) {
253 return struct{}{}, syscall.EEXIST
254 }
255 }
256 return struct{}{}, renameat(oldparent, oldname, newparent, newname)
257 })
258 return struct{}{}, err
259 })
260 if err != nil {
261 return &LinkError{"renameat", oldname, newname, err}
262 }
263 return err
264 }
265
266 func rootLink(r *Root, oldname, newname string) error {
267 _, err := doInRoot(r, oldname, 0, nil, func(oldparent sysfdType, oldname string, oldEndsInSlash bool) (struct{}, error) {
268 flags := uint(0)
269 if runtime.GOOS == "windows" {
270
271 flags = doInRootNoHandleTerminalSlash
272 }
273 _, err := doInRoot(r, newname, flags, nil, func(newparent sysfdType, newname string, newEndsInSlash bool) (struct{}, error) {
274 return struct{}{}, linkat(oldparent, oldname, newparent, newname)
275 })
276 return struct{}{}, err
277 })
278 if err != nil {
279 return &LinkError{"linkat", oldname, newname, err}
280 }
281 return err
282 }
283
284
285 const (
286
287
288 doInRootNoHandleTerminalSlash = 1 << iota
289
290
291
292 doInRootCreatingDirectory
293
294
295
296
297
298
299
300
301 doInRootAlwaysResolveTerminalSlash
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 func doInRoot[T any](r *Root, name string, flags uint, openDirFunc func(parent sysfdType, name string) (sysfdType, error), f func(parent sysfdType, name string, endsInSlash bool) (T, error)) (ret T, err error) {
330 if err := r.root.incref(); err != nil {
331 return ret, err
332 }
333 defer r.root.decref()
334
335 parts, endsInSlash, err := splitPathInRoot(name, nil, nil)
336 if err != nil {
337 return ret, err
338 }
339 if openDirFunc == nil {
340 openDirFunc = rootOpenDir
341 }
342
343 rootfd := r.root.fd
344 dirfd := rootfd
345 defer func() {
346 if dirfd != rootfd {
347 syscall.Close(dirfd)
348 }
349 }()
350
351
352
353
354
355
356
357 const maxSteps = 255
358 const maxRestarts = 8
359
360 i := 0
361 steps := 0
362 restarts := 0
363 symlinks := 0
364 Loop:
365 for {
366 steps++
367 if steps > maxSteps && restarts > maxRestarts {
368 return ret, syscall.ENAMETOOLONG
369 }
370
371 if parts[i] == ".." {
372
373
374
375
376
377 restarts++
378 end := i + 1
379 for end < len(parts) && parts[end] == ".." {
380 end++
381 }
382 count := end - i
383 if count > i {
384 return ret, errPathEscapes
385 }
386 parts = slices.Delete(parts, i-count, end)
387 if len(parts) == 0 {
388 parts = []string{"."}
389 }
390 i = 0
391 if dirfd != rootfd {
392 syscall.Close(dirfd)
393 }
394 dirfd = rootfd
395 continue
396 }
397
398 if i == len(parts)-1 {
399 err = nil
400 if endsInSlash && flags&doInRootNoHandleTerminalSlash == 0 {
401 var fi FileInfo
402 fi, err = lstatat(dirfd, parts[i])
403 switch {
404 case IsNotExist(err) && flags&doInRootCreatingDirectory != 0:
405
406
407
408 err = nil
409 case err != nil:
410 return
411 case fi.Mode().Type() == fs.ModeDir:
412 case fi.Mode().Type() == fs.ModeSymlink:
413 if runtime.GOOS != "windows" || flags&doInRootAlwaysResolveTerminalSlash != 0 {
414 err = checkSymlink(dirfd, parts[i], syscall.ENOTDIR)
415 } else {
416 if !isDirectoryLink(fi) {
417 err = syscall.ENOTDIR
418 }
419 }
420 default:
421 err = syscall.ENOTDIR
422 return
423 }
424 }
425
426
427
428
429
430
431 if err == nil {
432 ret, err = f(dirfd, parts[i], endsInSlash)
433 if err == nil {
434 return
435 }
436 }
437 } else {
438 var fd sysfdType
439 fd, err = openDirFunc(dirfd, parts[i])
440 if err == nil {
441 if dirfd != rootfd {
442 syscall.Close(dirfd)
443 }
444 dirfd = fd
445 }
446 }
447
448 switch e := err.(type) {
449 case nil:
450 case errSymlink:
451 symlinks++
452 if symlinks > rootMaxSymlinks {
453 return ret, syscall.ELOOP
454 }
455 lastPart := i == len(parts)-1
456 newparts, newEndsInSlash, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
457 if err != nil {
458 return ret, err
459 }
460 if lastPart && newEndsInSlash {
461
462
463 endsInSlash = true
464 }
465 if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
466
467
468 i = 0
469 if dirfd != rootfd {
470 syscall.Close(dirfd)
471 }
472 dirfd = rootfd
473 }
474 parts = newparts
475 continue Loop
476 case *PathError:
477
478 e.Path = parts[0]
479 for _, part := range parts[1 : i+1] {
480 e.Path += string(PathSeparator) + part
481 }
482 return ret, e
483 default:
484 return ret, err
485 }
486
487 i++
488 }
489 }
490
491 func modeAt(parent sysfdType, name string) (FileMode, error) {
492 fi, err := lstatat(parent, name)
493 if err != nil {
494 return 0, err
495 }
496 return fi.Mode(), nil
497 }
498
499
500
501 type errSymlink string
502
503 func (errSymlink) Error() string { panic("errSymlink is not user-visible") }
504
View as plain text