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 } else if e == nil {
171 err = syscall.ENOTDIR
172 } else if !IsNotExist(e) {
173
174
175 err = e
176 }
177 }
178 }
179 }
180 switch err.(type) {
181 case nil, errSymlink:
182 return struct{}{}, err
183 }
184 return struct{}{}, &PathError{Op: "mkdirat", Err: err}
185 }
186 flags := uint(doInRootCreatingDirectory)
187 switch runtime.GOOS {
188 case "linux", "windows":
189 flags |= doInRootNoHandleTerminalSlash
190 }
191 _, err := doInRoot(r, fullname, flags, openDirFunc, openLastComponentFunc)
192 if err != nil {
193 if _, ok := err.(*PathError); !ok {
194 err = &PathError{Op: "mkdirat", Path: fullname, Err: err}
195 }
196 }
197 return err
198 }
199
200 func rootReadlink(r *Root, name string) (string, error) {
201 target, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (string, error) {
202 return readlinkat(parent, name)
203 })
204 if err != nil {
205 return "", &PathError{Op: "readlinkat", Path: name, Err: err}
206 }
207 return target, nil
208 }
209
210 func rootRemove(r *Root, name string) error {
211 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
212 return struct{}{}, removeat(parent, name)
213 })
214 if err != nil {
215 return &PathError{Op: "removeat", Path: name, Err: err}
216 }
217 return nil
218 }
219
220 func rootRemoveAll(r *Root, name string) error {
221
222
223 for len(name) > 0 && IsPathSeparator(name[len(name)-1]) {
224 name = name[:len(name)-1]
225 }
226 if endsWithDot(name) {
227
228 return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
229 }
230 _, err := doInRoot(r, name, 0, nil, func(parent sysfdType, name string, endsInSlash bool) (struct{}, error) {
231 return struct{}{}, removeAllFrom(parent, name)
232 })
233 if IsNotExist(err) {
234 return nil
235 }
236 if err != nil {
237 return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
238 }
239 return err
240 }
241
242 func rootRename(r *Root, oldname, newname string) error {
243 _, err := doInRoot(r, oldname, 0, nil, func(oldparent sysfdType, oldname string, oldEndsInSlash bool) (struct{}, error) {
244 flags := uint(doInRootCreatingDirectory)
245 if runtime.GOOS == "windows" {
246 flags |= doInRootNoHandleTerminalSlash
247 }
248 _, err := doInRoot(r, newname, flags, nil, func(newparent sysfdType, newname string, newEndsInSlash bool) (struct{}, error) {
249 if runtime.GOOS != "windows" && newEndsInSlash {
250 oldMode, err := modeAt(oldparent, oldname)
251 if err != nil {
252 return struct{}{}, err
253 }
254 if oldMode.Type() != fs.ModeDir {
255 return struct{}{}, syscall.ENOTDIR
256 }
257 }
258
259 fi, err := lstatat(newparent, newname)
260 if err == nil && fi.IsDir() {
261 if ofi, err := lstatat(oldparent, oldname); err != nil {
262 return struct{}{}, err
263 } else if newname == oldname || !SameFile(fi, ofi) {
264 return struct{}{}, syscall.EEXIST
265 }
266 }
267 return struct{}{}, renameat(oldparent, oldname, newparent, newname)
268 })
269 return struct{}{}, err
270 })
271 if err != nil {
272 return &LinkError{"renameat", oldname, newname, err}
273 }
274 return err
275 }
276
277 func rootLink(r *Root, oldname, newname string) error {
278 _, err := doInRoot(r, oldname, 0, nil, func(oldparent sysfdType, oldname string, oldEndsInSlash bool) (struct{}, error) {
279 flags := uint(0)
280 if runtime.GOOS == "windows" {
281
282 flags |= doInRootNoHandleTerminalSlash
283 }
284 _, err := doInRoot(r, newname, flags, nil, func(newparent sysfdType, newname string, newEndsInSlash bool) (struct{}, error) {
285 return struct{}{}, linkat(oldparent, oldname, newparent, newname)
286 })
287 return struct{}{}, err
288 })
289 if err != nil {
290 return &LinkError{"linkat", oldname, newname, err}
291 }
292 return err
293 }
294
295
296 const (
297
298
299 doInRootNoHandleTerminalSlash = 1 << iota
300
301
302
303 doInRootCreatingDirectory
304
305
306
307
308
309
310
311
312 doInRootAlwaysResolveTerminalSlash
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 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) {
341 if err := r.root.incref(); err != nil {
342 return ret, err
343 }
344 defer r.root.decref()
345
346 parts, endsInSlash, err := splitPathInRoot(name, nil, nil)
347 if err != nil {
348 return ret, err
349 }
350 if openDirFunc == nil {
351 openDirFunc = rootOpenDir
352 }
353
354 rootfd := r.root.fd
355 dirfd := rootfd
356 defer func() {
357 if dirfd != rootfd {
358 syscall.Close(dirfd)
359 }
360 }()
361
362
363
364
365
366
367
368 const maxSteps = 255
369 const maxRestarts = 8
370
371 i := 0
372 steps := 0
373 restarts := 0
374 symlinks := 0
375 Loop:
376 for {
377 steps++
378 if steps > maxSteps && restarts > maxRestarts {
379 return ret, syscall.ENAMETOOLONG
380 }
381
382 if parts[i] == ".." {
383
384
385
386
387
388 restarts++
389 end := i + 1
390 for end < len(parts) && parts[end] == ".." {
391 end++
392 }
393 count := end - i
394 if count > i {
395 return ret, errPathEscapes
396 }
397 parts = slices.Delete(parts, i-count, end)
398 if len(parts) == 0 {
399 parts = []string{"."}
400 }
401 i = 0
402 if dirfd != rootfd {
403 syscall.Close(dirfd)
404 }
405 dirfd = rootfd
406 continue
407 }
408
409 if i == len(parts)-1 {
410 err = nil
411 if endsInSlash && flags&doInRootNoHandleTerminalSlash == 0 {
412 var fi FileInfo
413 fi, err = lstatat(dirfd, parts[i])
414 switch {
415 case IsNotExist(err) && flags&doInRootCreatingDirectory != 0:
416
417
418
419 err = nil
420 case err != nil:
421 return
422 case fi.Mode().Type() == fs.ModeDir:
423 case fi.Mode().Type() == fs.ModeSymlink:
424 if runtime.GOOS != "windows" || flags&doInRootAlwaysResolveTerminalSlash != 0 {
425 err = checkSymlink(dirfd, parts[i], syscall.ENOTDIR)
426 } else {
427 if !isDirectoryLink(fi) {
428 err = syscall.ENOTDIR
429 }
430 }
431 default:
432 err = syscall.ENOTDIR
433 return
434 }
435 }
436
437
438
439
440
441
442 if err == nil {
443 ret, err = f(dirfd, parts[i], endsInSlash)
444 if err == nil {
445 return
446 }
447 }
448 } else {
449 var fd sysfdType
450 fd, err = openDirFunc(dirfd, parts[i])
451 if err == nil {
452 if dirfd != rootfd {
453 syscall.Close(dirfd)
454 }
455 dirfd = fd
456 }
457 }
458
459 switch e := err.(type) {
460 case nil:
461 case errSymlink:
462 symlinks++
463 if symlinks > rootMaxSymlinks {
464 return ret, syscall.ELOOP
465 }
466 lastPart := i == len(parts)-1
467 newparts, newEndsInSlash, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
468 if err != nil {
469 return ret, err
470 }
471 if lastPart && newEndsInSlash {
472
473
474 endsInSlash = true
475 }
476 if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
477
478
479 i = 0
480 if dirfd != rootfd {
481 syscall.Close(dirfd)
482 }
483 dirfd = rootfd
484 }
485 parts = newparts
486 continue Loop
487 case *PathError:
488
489 e.Path = parts[0]
490 for _, part := range parts[1 : i+1] {
491 e.Path += string(PathSeparator) + part
492 }
493 return ret, e
494 default:
495 return ret, err
496 }
497
498 i++
499 }
500 }
501
502 func modeAt(parent sysfdType, name string) (FileMode, error) {
503 fi, err := lstatat(parent, name)
504 if err != nil {
505 return 0, err
506 }
507 return fi.Mode(), nil
508 }
509
510
511
512 type errSymlink string
513
514 func (errSymlink) Error() string { panic("errSymlink is not user-visible") }
515
View as plain text