// 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 unix || 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
	cleanup runtime.Cleanup // cleanup closes the file when no longer referenced
}

func ( *root) () error {
	.mu.Lock()
	defer .mu.Unlock()
	if !.closed && .refs == 0 {
		syscall.Close(.fd)
	}
	.closed = true
	// There is no need for a cleanup at this point. Root must be alive at the point
	// where cleanup.stop is called.
	.cleanup.Stop()
	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 rootChmod( *Root,  string,  FileMode) error {
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, chmodat(, , )
	})
	if  != nil {
		return &PathError{Op: "chmodat", Path: , Err: }
	}
	return nil
}

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

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

func rootChtimes( *Root,  string,  time.Time,  time.Time) error {
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, chtimesat(, , , )
	})
	if  != nil {
		return &PathError{Op: "chtimesat", Path: , Err: }
	}
	return 
}

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

func rootMkdirAll( *Root,  string,  FileMode) error {
	// doInRoot opens each path element in turn.
	//
	// openDirFunc opens all but the last path component.
	// The usual default openDirFunc just opens directories with O_DIRECTORY.
	// We replace it here with one that creates missing directories along the way.
	 := func( sysfdType,  string) (sysfdType, error) {
		for  := range 2 {
			,  := rootOpenDir(, )
			switch .(type) {
			case nil, errSymlink:
				return , 
			}
			if  > 0 || !IsNotExist() {
				return 0, &PathError{Op: "openat", Err: }
			}
			if  := mkdirat(, , );  != nil {
				return 0, &PathError{Op: "mkdirat", Err: }
			}
		}
		panic("unreachable")
	}
	// openLastComponentFunc opens the last path component.
	 := func( sysfdType,  string) (struct{}, error) {
		 := mkdirat(, , )
		if  == syscall.EEXIST {
			,  := modeAt(, )
			if  == nil {
				if .IsDir() {
					// The target of MkdirAll is an existing directory.
					 = nil
				} else if &ModeSymlink != 0 {
					// The target of MkdirAll is a symlink.
					// For consistency with os.MkdirAll,
					// succeed if the link resolves to a directory.
					// We don't return errSymlink here, because we don't
					// want to create the link target if it doesn't exist.
					,  := .Stat()
					if  == nil && .Mode().IsDir() {
						 = nil
					}
				}
			}
		}
		switch .(type) {
		case nil, errSymlink:
			return struct{}{}, 
		}
		return struct{}{}, &PathError{Op: "mkdirat", Err: }
	}
	,  := doInRoot(, , , )
	if  != nil {
		if ,  := .(*PathError); ! {
			 = &PathError{Op: "mkdirat", Path: , Err: }
		}
	}
	return 
}

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

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

func rootRemoveAll( *Root,  string) error {
	// Consistency with os.RemoveAll: Strip trailing /s from the name,
	// so RemoveAll("not_a_directory/") succeeds.
	for len() > 0 && IsPathSeparator([len()-1]) {
		 = [:len()-1]
	}
	if endsWithDot() {
		// Consistency with os.RemoveAll: Return EINVAL when trying to remove .
		return &PathError{Op: "RemoveAll", Path: , Err: syscall.EINVAL}
	}
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, removeAllFrom(, )
	})
	if IsNotExist() {
		return nil
	}
	if  != nil {
		return &PathError{Op: "RemoveAll", Path: , Err: underlyingError()}
	}
	return 
}

func rootRename( *Root, ,  string) error {
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
			return struct{}{}, renameat(, , , )
		})
		return struct{}{}, 
	})
	if  != nil {
		return &LinkError{"renameat", , , }
	}
	return 
}

func rootLink( *Root, ,  string) error {
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
			return struct{}{}, linkat(, , , )
		})
		return struct{}{}, 
	})
	if  != nil {
		return &LinkError{"linkat", , , }
	}
	return 
}

// doInRoot performs an operation on a path in a Root.
//
// It calls f with the FD or handle for the directory containing the last
// path element, and the name of the last path element.
//
// For example, given the path a/b/c it calls f with the FD for a/b and the name "c".
//
// If openDirFunc is non-nil, it is called to open intermediate path elements.
// For example, given the path a/b/c openDirFunc will be called to open a and a/b in turn.
//
// f or openDirFunc may return errSymlink to indicate that the path element is a symlink
// which should be followed. Note that this can result in f being called multiple times
// with different names. For example, give the path "link" which is a symlink to "target",
// f is called with the path "link", returns errSymlink("target"), and is called again with
// the path "target".
//
// If f or openDirFunc return a *PathError, doInRoot will set PathError.Path to the
// full path which caused the error.
func doInRoot[ any]( *Root,  string,  func( sysfdType,  string) (sysfdType, error),  func( sysfdType,  string) (, error)) ( ,  error) {
	if  := .root.incref();  != nil {
		return , 
	}
	defer .root.decref()

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

	 := .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(, -, )
			if len() == 0 {
				 = []string{"."}
			}
			 = 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.
			// suffixSep contains any trailing separator characters
			// which we rejoin to the final part at this time.
			,  = (, []+)
			if  == nil {
				return
			}
		} else {
			var  sysfdType
			,  = (, [])
			if  == nil {
				if  !=  {
					syscall.Close()
				}
				 = 
			}
		}

		switch e := .(type) {
		case nil:
		case errSymlink:
			++
			if  > rootMaxSymlinks {
				return , syscall.ELOOP
			}
			, ,  := splitPathInRoot(string(), [:], [+1:])
			if  != nil {
				return , 
			}
			if  == len()-1 {
				// suffixSep contains any trailing path separator characters
				// in the link target.
				// If we are replacing the remainder of the path, retain these.
				// If we're replacing some intermediate component of the path,
				// ignore them, since intermediate components must always be
				// directories.
				 = 
			}
			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 
		case *PathError:
			// This is strings.Join(parts[:i+1], PathSeparator).
			.Path = [0]
			for ,  := range [1 : +1] {
				.Path += string(PathSeparator) + 
			}
			return , 
		default:
			return , 
		}

		++
	}
}

// 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") }