// 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.

package os

import (
	
	
	
	
	
	
	
)

// OpenInRoot opens the file name in the directory dir.
// It is equivalent to OpenRoot(dir) followed by opening the file in the root.
//
// OpenInRoot returns an error if any component of the name
// references a location outside of dir.
//
// See [Root] for details and limitations.
func (,  string) (*File, error) {
	,  := OpenRoot()
	if  != nil {
		return nil, 
	}
	defer .Close()
	return .Open()
}

// Root may be used to only access files within a single directory tree.
//
// Methods on Root can only access files and directories beneath a root directory.
// If any component of a file name passed to a method of Root references a location
// outside the root, the method returns an error.
// File names may reference the directory itself (.).
//
// Methods on Root will follow symbolic links, but symbolic links may not
// reference a location outside the root.
// Symbolic links must not be absolute.
//
// Methods on Root do not prohibit traversal of filesystem boundaries,
// Linux bind mounts, /proc special files, or access to Unix device files.
//
// Methods on Root are safe to be used from multiple goroutines simultaneously.
//
// On most platforms, creating a Root opens a file descriptor or handle referencing
// the directory. If the directory is moved, methods on Root reference the original
// directory in its new location.
//
// Root's behavior differs on some platforms:
//
//   - When GOOS=windows, file names may not reference Windows reserved device names
//     such as NUL and COM1.
//   - When GOOS=js, Root is vulnerable to TOCTOU (time-of-check-time-of-use)
//     attacks in symlink validation, and cannot ensure that operations will not
//     escape the root.
//   - When GOOS=plan9 or GOOS=js, Root does not track directories across renames.
//     On these platforms, a Root references a directory name, not a file descriptor.
type Root struct {
	root root
}

const (
	// Maximum number of symbolic links we will follow when resolving a file in a root.
	// 8 is __POSIX_SYMLOOP_MAX (the minimum allowed value for SYMLOOP_MAX),
	// and a common limit.
	rootMaxSymlinks = 8
)

// OpenRoot opens the named directory.
// If there is an error, it will be of type *PathError.
func ( string) (*Root, error) {
	testlog.Open()
	return openRootNolog()
}

// Name returns the name of the directory presented to OpenRoot.
//
// It is safe to call Name after [Close].
func ( *Root) () string {
	return .root.Name()
}

// Close closes the Root.
// After Close is called, methods on Root return errors.
func ( *Root) () error {
	return .root.Close()
}

// Open opens the named file in the root for reading.
// See [Open] for more details.
func ( *Root) ( string) (*File, error) {
	return .OpenFile(, O_RDONLY, 0)
}

// Create creates or truncates the named file in the root.
// See [Create] for more details.
func ( *Root) ( string) (*File, error) {
	return .OpenFile(, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

// OpenFile opens the named file in the root.
// See [OpenFile] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
// OpenFile returns an error.
func ( *Root) ( string,  int,  FileMode) (*File, error) {
	if &0o777 !=  {
		return nil, &PathError{Op: "openat", Path: , Err: errors.New("unsupported file mode")}
	}
	.logOpen()
	,  := rootOpenFileNolog(, , , )
	if  != nil {
		return nil, 
	}
	.appendMode = &O_APPEND != 0
	return , nil
}

// OpenRoot opens the named directory in the root.
// If there is an error, it will be of type *PathError.
func ( *Root) ( string) (*Root, error) {
	.logOpen()
	return openRootInRoot(, )
}

// Mkdir creates a new directory in the root
// with the specified name and permission bits (before umask).
// See [Mkdir] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
// OpenFile returns an error.
func ( *Root) ( string,  FileMode) error {
	if &0o777 !=  {
		return &PathError{Op: "mkdirat", Path: , Err: errors.New("unsupported file mode")}
	}
	return rootMkdir(, , )
}

// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func ( *Root) ( string) error {
	return rootRemove(, )
}

// Stat returns a [FileInfo] describing the named file in the root.
// See [Stat] for more details.
func ( *Root) ( string) (FileInfo, error) {
	.logStat()
	return rootStat(, , false)
}

// Lstat returns a [FileInfo] describing the named file in the root.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link.
// See [Lstat] for more details.
func ( *Root) ( string) (FileInfo, error) {
	.logStat()
	return rootStat(, , true)
}

func ( *Root) ( string) {
	if  := testlog.Logger();  != nil {
		// This won't be right if r's name has changed since it was opened,
		// but it's the best we can do.
		.Open(joinPath(.Name(), ))
	}
}

func ( *Root) ( string) {
	if  := testlog.Logger();  != nil {
		// This won't be right if r's name has changed since it was opened,
		// but it's the best we can do.
		.Stat(joinPath(.Name(), ))
	}
}

// splitPathInRoot splits a path into components
// and joins it with the given prefix and suffix.
//
// The path is relative to a Root, and must not be
// absolute, volume-relative, or "".
//
// "." components are removed, except in the last component.
//
// Path separators following the last component are preserved.
func splitPathInRoot( string, ,  []string) ( []string,  error) {
	if len() == 0 {
		return nil, errors.New("empty path")
	}
	if IsPathSeparator([0]) {
		return nil, errPathEscapes
	}

	if runtime.GOOS == "windows" {
		// Windows cleans paths before opening them.
		,  = rootCleanPath(, , )
		if  != nil {
			return nil, 
		}
		 = nil
		 = nil
	}

	 := append([]string{}, ...)
	,  := 0, 1
	for {
		if  < len() && !IsPathSeparator([]) {
			// Keep looking for the end of this component.
			++
			continue
		}
		 = append(, [:])
		// Advance to the next component, or end of the path.
		for  < len() && IsPathSeparator([]) {
			++
		}
		if  == len() {
			// If this is the last path component,
			// preserve any trailing path separators.
			[len()-1] = [:]
			break
		}
		if [len()-1] == "." {
			// Remove "." components, except at the end.
			 = [:len()-1]
		}
		 = 
	}
	if len() > 0 && len() > 0 && [len()-1] == "." {
		// Remove a trailing "." component if we're joining to a suffix.
		 = [:len()-1]
	}
	 = append(, ...)
	return , nil
}

// FS returns a file system (an fs.FS) for the tree of files in the root.
//
// The result implements [io/fs.StatFS], [io/fs.ReadFileFS] and
// [io/fs.ReadDirFS].
func ( *Root) () fs.FS {
	return (*rootFS)()
}

type rootFS Root

func ( *rootFS) ( string) (fs.File, error) {
	 := (*Root)()
	if !isValidRootFSPath() {
		return nil, &PathError{Op: "open", Path: , Err: ErrInvalid}
	}
	,  := .Open()
	if  != nil {
		return nil, 
	}
	return , nil
}

func ( *rootFS) ( string) ([]DirEntry, error) {
	 := (*Root)()
	if !isValidRootFSPath() {
		return nil, &PathError{Op: "readdir", Path: , Err: ErrInvalid}
	}

	// This isn't efficient: We just open a regular file and ReadDir it.
	// Ideally, we would skip creating a *File entirely and operate directly
	// on the file descriptor, but that will require some extensive reworking
	// of directory reading in general.
	//
	// This suffices for the moment.
	,  := .Open()
	if  != nil {
		return nil, 
	}
	defer .Close()
	,  := .ReadDir(-1)
	slices.SortFunc(, func(,  DirEntry) int {
		return bytealg.CompareString(.Name(), .Name())
	})
	return , 
}

func ( *rootFS) ( string) ([]byte, error) {
	 := (*Root)()
	if !isValidRootFSPath() {
		return nil, &PathError{Op: "readfile", Path: , Err: ErrInvalid}
	}
	,  := .Open()
	if  != nil {
		return nil, 
	}
	defer .Close()
	return readFileContents()
}

func ( *rootFS) ( string) (FileInfo, error) {
	 := (*Root)()
	if !isValidRootFSPath() {
		return nil, &PathError{Op: "stat", Path: , Err: ErrInvalid}
	}
	return .Stat()
}

// isValidRootFSPath reprots whether name is a valid filename to pass a Root.FS method.
func isValidRootFSPath( string) bool {
	if !fs.ValidPath() {
		return false
	}
	if runtime.GOOS == "windows" {
		// fs.FS paths are /-separated.
		// On Windows, reject the path if it contains any \ separators.
		// Other forms of invalid path (for example, "NUL") are handled by
		// Root's usual file lookup mechanisms.
		if stringslite.IndexByte(, '\\') >= 0 {
			return false
		}
	}
	return true
}