// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || wasip1

package os

import (
	
	
	
	
)

// root implementation for platforms with a function to open a file
// relative to a directory.
type root struct {
	name string

	// refs is incremented while an operation is using fd.
	// closed is set when Close is called.
	// fd is closed when closed is true and refs is 0.
	mu     sync.Mutex
	fd     sysfdType
	refs   int  // number of active operations
	closed bool // set when closed
}

func ( *root) () error {
	.mu.Lock()
	defer .mu.Unlock()
	if !.closed && .refs == 0 {
		syscall.Close(.fd)
	}
	.closed = true
	runtime.SetFinalizer(, nil) // no need for a finalizer any more
	return nil
}

func ( *root) () error {
	.mu.Lock()
	defer .mu.Unlock()
	if .closed {
		return ErrClosed
	}
	.refs++
	return nil
}

func ( *root) () {
	.mu.Lock()
	defer .mu.Unlock()
	if .refs <= 0 {
		panic("bad Root refcount")
	}
	.refs--
	if .closed && .refs == 0 {
		syscall.Close(.fd)
	}
}

func ( *root) () string {
	return .name
}

func rootMkdir( *Root,  string,  FileMode) error {
	,  := doInRoot(, , func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, mkdirat(, , )
	})
	if  != nil {
		return &PathError{Op: "mkdirat", Path: , Err: }
	}
	return 
}

func rootRemove( *Root,  string) error {
	,  := doInRoot(, , func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, removeat(, )
	})
	if  != nil {
		return &PathError{Op: "removeat", Path: , Err: }
	}
	return 
}

// doInRoot performs an operation on a path in a Root.
//
// It opens the directory containing the final element of the path,
// and calls f with the directory FD and name of the final element.
//
// If the path refers to a symlink which should be followed,
// then f must return errSymlink.
// doInRoot will follow the symlink and call f again.
func doInRoot[ any]( *Root,  string,  func( sysfdType,  string) (, error)) ( ,  error) {
	if  := .root.incref();  != nil {
		return , 
	}
	defer .root.decref()

	,  := splitPathInRoot(, nil, nil)
	if  != nil {
		return , 
	}

	 := .root.fd
	 := 
	defer func() {
		if  !=  {
			syscall.Close()
		}
	}()

	// When resolving .. path components, we restart path resolution from the root.
	// (We can't openat(dir, "..") to move up to the parent directory,
	// because dir may have moved since we opened it.)
	// To limit how many opens a malicious path can cause us to perform, we set
	// a limit on the total number of path steps and the total number of restarts
	// caused by .. components. If *both* limits are exceeded, we halt the operation.
	const  = 255
	const  = 8

	 := 0
	 := 0
	 := 0
	 := 0
	for {
		++
		if  >  &&  >  {
			return , syscall.ENAMETOOLONG
		}

		if [] == ".." {
			// Resolve one or more parent ("..") path components.
			//
			// Rewrite the original path,
			// removing the elements eliminated by ".." components,
			// and start over from the beginning.
			++
			 :=  + 1
			for  < len() && [] == ".." {
				++
			}
			 :=  - 
			if  >  {
				return , errPathEscapes
			}
			 = slices.Delete(, -, )
			 = 0
			if  !=  {
				syscall.Close()
			}
			 = 
			continue
		}

		if  == len()-1 {
			// This is the last path element.
			// Call f to decide what to do with it.
			// If f returns errSymlink, this element is a symlink
			// which should be followed.
			,  = (, [])
			if ,  := .(errSymlink); ! {
				return , 
			}
		} else {
			var  sysfdType
			,  = rootOpenDir(, [])
			if  == nil {
				if  !=  {
					syscall.Close()
				}
				 = 
			} else if ,  := .(errSymlink); ! {
				return , 
			}
		}

		if ,  := .(errSymlink);  {
			++
			if  > rootMaxSymlinks {
				return , syscall.ELOOP
			}
			,  := splitPathInRoot(string(), [:], [+1:])
			if  != nil {
				return , 
			}
			if len() <  || !slices.Equal([:], [:]) {
				// Some component in the path which we have already traversed
				// has changed. We need to restart parsing from the root.
				 = 0
				if  !=  {
					syscall.Close()
				}
				 = 
			}
			 = 
			continue
		}

		++
	}
}

// errSymlink reports that a file being operated on is actually a symlink,
// and the target of that symlink.
type errSymlink string

func (errSymlink) () string { panic("errSymlink is not user-visible") }