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

package os

import (
	
	
	
	
	
)

// sysfdType is the native type of a file handle
// (int on Unix, syscall.Handle on Windows),
// permitting helper functions to be written portably.
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: ,
	}}
	.root.cleanup = runtime.AddCleanup(, func( *root) { .Close() }, .root)
	return , nil
}

// openRootInRoot is Root.OpenRoot.
func openRootInRoot( *Root,  string) (*Root, error) {
	,  := doInRoot(, , nil, 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(, , nil, func( int,  string) ( int,  error) {
		ignoringEINTR(func() error {
			,  = unix.Openat(, , syscall.O_NOFOLLOW|syscall.O_CLOEXEC|, uint32())
			if  != nil {
				// Never follow symlinks when O_CREATE|O_EXCL, no matter
				// what error the OS returns.
				 := &(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL)
				if ! && (isNoFollowErr() ||  == syscall.ENOTDIR) {
					 = checkSymlink(, , )
				}
				// AIX returns ELOOP instead of EEXIST for a dangling symlink.
				// Convert this to EEXIST so it matches ErrExists.
				if  &&  == syscall.ELOOP {
					 = syscall.EEXIST
				}
			}
			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(, , nil, 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 rootSymlink( *Root, ,  string) error {
	,  := doInRoot(, , nil, func( sysfdType,  string) (struct{}, error) {
		return struct{}{}, symlinkat(, , )
	})
	if  != nil {
		return &LinkError{"symlinkat", , , }
	}
	return nil
}

// On systems which use fchmodat, fchownat, etc., we have a race condition:
// When "name" is a symlink, Root.Chmod("name") should act on the target of that link.
// However, fchmodat doesn't allow us to chmod a file only if it is not a symlink;
// the AT_SYMLINK_NOFOLLOW parameter causes the operation to act on the symlink itself.
//
// We do the best we can by first checking to see if the target of the operation is a symlink,
// and only attempting the fchmodat if it is not. If the target is replaced between the check
// and the fchmodat, we will chmod the symlink rather than following it.
//
// This race condition is unfortunate, but does not permit escaping a root:
// We may act on the wrong file, but that file will be contained within the root.
func afterResolvingSymlink( int,  string,  func() error) error {
	if  := checkSymlink(, , nil);  != nil {
		return 
	}
	return ()
}

func chmodat( int,  string,  FileMode) error {
	return afterResolvingSymlink(, , func() error {
		return ignoringEINTR(func() error {
			return unix.Fchmodat(, , syscallMode(), unix.AT_SYMLINK_NOFOLLOW)
		})
	})
}

func chownat( int,  string, ,  int) error {
	return afterResolvingSymlink(, , func() error {
		return ignoringEINTR(func() error {
			return unix.Fchownat(, , , , unix.AT_SYMLINK_NOFOLLOW)
		})
	})
}

func lchownat( int,  string, ,  int) error {
	return ignoringEINTR(func() error {
		return unix.Fchownat(, , , , unix.AT_SYMLINK_NOFOLLOW)
	})
}

func chtimesat( int,  string,  time.Time,  time.Time) error {
	return afterResolvingSymlink(, , func() error {
		return ignoringEINTR(func() error {
			 := chtimesUtimes(, )
			return unix.Utimensat(, , &, unix.AT_SYMLINK_NOFOLLOW)
		})
	})
}

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 
}

func removefileat( int,  string) error {
	return ignoringEINTR(func() error {
		return unix.Unlinkat(, , 0)
	})
}

func removedirat( int,  string) error {
	return ignoringEINTR(func() error {
		return unix.Unlinkat(, , unix.AT_REMOVEDIR)
	})
}

func renameat( int,  string,  int,  string) error {
	return unix.Renameat(, , , )
}

func linkat( int,  string,  int,  string) error {
	return unix.Linkat(, , , , 0)
}

func symlinkat( string,  int,  string) error {
	return unix.Symlinkat(, , )
}

func modeAt( int,  string) (FileMode, error) {
	var  fileStat
	if  := unix.Fstatat(, , &.sys, unix.AT_SYMLINK_NOFOLLOW);  != nil {
		return 0, 
	}
	fillFileStatFromSys(&, )
	return .mode, nil
}

// 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 "", 
		}
		 = max(, 0)
		if  <  {
			return string([0:]), nil
		}
	}
}