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

package os

import (
	
	
	
	
)

type sysfdType = int

// openRootNolog is OpenRoot.
func openRootNolog( string) (*Root, error) {
	var  int
	 := ignoringEINTR(func() error {
		var  error
		, _,  = open(, syscall.O_CLOEXEC, 0)
		return 
	})
	if  != nil {
		return nil, &PathError{Op: "open", Path: , Err: }
	}
	return newRoot(, )
}

// newRoot returns a new Root.
// If fd is not a directory, it closes it and returns an error.
func newRoot( int,  string) (*Root, error) {
	var  fileStat
	 := ignoringEINTR(func() error {
		return syscall.Fstat(, &.sys)
	})
	fillFileStatFromSys(&, )
	if  == nil && !.IsDir() {
		syscall.Close()
		return nil, &PathError{Op: "open", Path: , Err: errors.New("not a directory")}
	}

	// There's a race here with fork/exec, which we are
	// content to live with. See ../syscall/exec_unix.go.
	if !supportsCloseOnExec {
		syscall.CloseOnExec()
	}

	 := &Root{root{
		fd:   ,
		name: ,
	}}
	runtime.SetFinalizer(&.root, (*root).Close)
	return , nil
}

// openRootInRoot is Root.OpenRoot.
func openRootInRoot( *Root,  string) (*Root, error) {
	,  := doInRoot(, , func( int,  string) ( int,  error) {
		ignoringEINTR(func() error {
			,  = unix.Openat(, , syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
			if isNoFollowErr() {
				 = checkSymlink(, , )
			}
			return 
		})
		return , 
	})
	if  != nil {
		return nil, &PathError{Op: "openat", Path: , Err: }
	}
	return newRoot(, )
}

// rootOpenFileNolog is Root.OpenFile.
func rootOpenFileNolog( *Root,  string,  int,  FileMode) (*File, error) {
	,  := doInRoot(, , func( int,  string) ( int,  error) {
		ignoringEINTR(func() error {
			,  = unix.Openat(, , syscall.O_NOFOLLOW|syscall.O_CLOEXEC|, uint32())
			if isNoFollowErr() ||  == syscall.ENOTDIR {
				 = checkSymlink(, , )
			}
			return 
		})
		return , 
	})
	if  != nil {
		return nil, &PathError{Op: "openat", Path: , Err: }
	}
	 := newFile(, joinPath(.Name(), ), kindOpenFile, unix.HasNonblockFlag())
	return , nil
}

func rootOpenDir( int,  string) (int, error) {
	var (
		  int
		 error
	)
	ignoringEINTR(func() error {
		,  = unix.Openat(, , syscall.O_NOFOLLOW|syscall.O_CLOEXEC|syscall.O_DIRECTORY, 0)
		if isNoFollowErr() ||  == syscall.ENOTDIR {
			 = checkSymlink(, , )
		} else if  == syscall.ENOTSUP ||  == syscall.EOPNOTSUPP {
			// ENOTSUP and EOPNOTSUPP are often, but not always, the same errno.
			// Translate both to ENOTDIR, since this indicates a non-terminal
			// path component was not a directory.
			 = syscall.ENOTDIR
		}
		return 
	})
	return , 
}

func rootStat( *Root,  string,  bool) (FileInfo, error) {
	,  := doInRoot(, , func( sysfdType,  string) (FileInfo, error) {
		var  fileStat
		if  := unix.Fstatat(, , &.sys, unix.AT_SYMLINK_NOFOLLOW);  != nil {
			return nil, 
		}
		fillFileStatFromSys(&, )
		if ! && .Mode()&ModeSymlink != 0 {
			return nil, checkSymlink(, , syscall.ELOOP)
		}
		return &, nil
	})
	if  != nil {
		return nil, &PathError{Op: "statat", Path: , Err: }
	}
	return , nil
}

func mkdirat( int,  string,  FileMode) error {
	return ignoringEINTR(func() error {
		return unix.Mkdirat(, , syscallMode())
	})
}

func removeat( int,  string) error {
	// The system call interface forces us to know whether
	// we are removing a file or directory. Try both.
	 := ignoringEINTR(func() error {
		return unix.Unlinkat(, , 0)
	})
	if  == nil {
		return nil
	}
	 := ignoringEINTR(func() error {
		return unix.Unlinkat(, , unix.AT_REMOVEDIR)
	})
	if  == nil {
		return nil
	}
	// Both failed. See comment in Remove for how we decide which error to return.
	if  != syscall.ENOTDIR {
		return 
	}
	return 
}

// checkSymlink resolves the symlink name in parent,
// and returns errSymlink with the link contents.
//
// If name is not a symlink, return origError.
func checkSymlink( int,  string,  error) error {
	,  := readlinkat(, )
	if  != nil {
		return 
	}
	return errSymlink()
}

func readlinkat( int,  string) (string, error) {
	for  := 128; ;  *= 2 {
		 := make([]byte, )
		var (
			 int
			 error
		)
		ignoringEINTR(func() error {
			,  = unix.Readlinkat(, , )
			return 
		})
		if  == syscall.ERANGE {
			continue
		}
		if  != nil {
			return "", 
		}
		if  < 0 {
			 = 0
		}
		if  <  {
			return string([0:]), nil
		}
	}
}