package sysfs

import (
	
	
	
	

	experimentalsys 
	
	
)

func ( bool,  fs.File) (fsapi.File, error) {
	// Return constant stat, which has fake times, but keep the underlying
	// file mode. Fake times are needed to pass wasi-testsuite.
	// https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19
	var  fs.FileMode
	if ,  := .Stat();  != nil {
		return nil, 
	} else {
		 = .Mode()
	}
	var  experimentalsys.Oflag
	if  {
		 = experimentalsys.O_RDONLY
	} else {
		 = experimentalsys.O_WRONLY
	}
	var  fsapi.File
	if ,  := .(*os.File);  {
		// This is ok because functions that need path aren't used by stdioFile
		 = newOsFile("", , 0, )
	} else {
		 = &fsFile{file: }
	}
	return &stdioFile{File: , st: sys.Stat_t{Mode: , Nlink: 1}}, nil
}

func ( string,  experimentalsys.Oflag,  fs.FileMode) (*os.File, experimentalsys.Errno) {
	return openFile(, , )
}

func ( string,  experimentalsys.Oflag,  fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
	,  := OpenFile(, , )
	if  != 0 {
		return nil, 
	}
	return newOsFile(, , , ), 0
}

func ( fs.FS,  string,  experimentalsys.Oflag,  fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
	if &experimentalsys.O_DIRECTORY != 0 && &(experimentalsys.O_WRONLY|experimentalsys.O_RDWR) != 0 {
		return nil, experimentalsys.EISDIR // invalid to open a directory writeable
	}
	,  := .Open()
	if  := experimentalsys.UnwrapOSError();  != 0 {
		return nil, 
	}
	// Don't return an os.File because the path is not absolute. osFile needs
	// the path to be real and certain FS.File impls are subrooted.
	return &fsFile{fs: , name: , file: }, 0
}

type stdioFile struct {
	fsapi.File
	st sys.Stat_t
}

// SetAppend implements File.SetAppend
func ( *stdioFile) (bool) experimentalsys.Errno {
	// Ignore for stdio.
	return 0
}

// IsAppend implements File.SetAppend
func ( *stdioFile) () bool {
	return true
}

// Stat implements File.Stat
func ( *stdioFile) () (sys.Stat_t, experimentalsys.Errno) {
	return .st, 0
}

// Close implements File.Close
func ( *stdioFile) () experimentalsys.Errno {
	return 0
}

// fsFile is used for wrapped fs.File, like os.Stdin or any fs.File
// implementation. Notably, this does not have access to the full file path.
// so certain operations can't be supported, such as inode lookups on Windows.
type fsFile struct {
	experimentalsys.UnimplementedFile

	// fs is the file-system that opened the file, or nil when wrapped for
	// pre-opens like stdio.
	fs fs.FS

	// name is what was used in fs for Open, so it may not be the actual path.
	name string

	// file is always set, possibly an os.File like os.Stdin.
	file fs.File

	// reopenDir is true if reopen should be called before Readdir. This flag
	// is deferred until Readdir to prevent redundant rewinds. This could
	// happen if Seek(0) was called twice, or if in Windows, Seek(0) was called
	// before Readdir.
	reopenDir bool

	// closed is true when closed was called. This ensures proper sys.EBADF
	closed bool

	// cachedStat includes fields that won't change while a file is open.
	cachedSt *cachedStat
}

type cachedStat struct {
	// dev is the same as sys.Stat_t Dev.
	dev uint64

	// dev is the same as sys.Stat_t Ino.
	ino sys.Inode

	// isDir is sys.Stat_t Mode masked with fs.ModeDir
	isDir bool
}

// cachedStat returns the cacheable parts of sys.Stat_t or an error if they
// couldn't be retrieved.
func ( *fsFile) () ( uint64,  sys.Inode,  bool,  experimentalsys.Errno) {
	if .cachedSt == nil {
		if _,  = .Stat();  != 0 {
			return
		}
	}
	return .cachedSt.dev, .cachedSt.ino, .cachedSt.isDir, 0
}

// Dev implements the same method as documented on sys.File
func ( *fsFile) () (uint64, experimentalsys.Errno) {
	, , ,  := .cachedStat()
	return , 
}

// Ino implements the same method as documented on sys.File
func ( *fsFile) () (sys.Inode, experimentalsys.Errno) {
	, , ,  := .cachedStat()
	return , 
}

// IsDir implements the same method as documented on sys.File
func ( *fsFile) () (bool, experimentalsys.Errno) {
	, , ,  := .cachedStat()
	return , 
}

// IsAppend implements the same method as documented on sys.File
func ( *fsFile) () bool {
	return false
}

// SetAppend implements the same method as documented on sys.File
func ( *fsFile) (bool) ( experimentalsys.Errno) {
	return fileError(, .closed, experimentalsys.ENOSYS)
}

// Stat implements the same method as documented on sys.File
func ( *fsFile) () (sys.Stat_t, experimentalsys.Errno) {
	if .closed {
		return sys.Stat_t{}, experimentalsys.EBADF
	}

	,  := statFile(.file)
	switch  {
	case 0:
		.cachedSt = &cachedStat{dev: .Dev, ino: .Ino, isDir: .Mode&fs.ModeDir == fs.ModeDir}
	case experimentalsys.EIO:
		 = experimentalsys.EBADF
	}
	return , 
}

// Read implements the same method as documented on sys.File
func ( *fsFile) ( []byte) ( int,  experimentalsys.Errno) {
	if ,  = read(.file, );  != 0 {
		// Defer validation overhead until we've already had an error.
		 = fileError(, .closed, )
	}
	return
}

// Pread implements the same method as documented on sys.File
func ( *fsFile) ( []byte,  int64) ( int,  experimentalsys.Errno) {
	if ,  := .file.(io.ReaderAt);  {
		if ,  = pread(, , );  != 0 {
			// Defer validation overhead until we've already had an error.
			 = fileError(, .closed, )
		}
		return
	}

	// See /RATIONALE.md "fd_pread: io.Seeker fallback when io.ReaderAt is not supported"
	if ,  := .file.(io.ReadSeeker);  {
		// Determine the current position in the file, as we need to revert it.
		,  := .Seek(0, io.SeekCurrent)
		if  != nil {
			return 0, fileError(, .closed, experimentalsys.UnwrapOSError())
		}

		// Put the read position back when complete.
		defer func() { _, _ = .Seek(, io.SeekStart) }()

		// If the current offset isn't in sync with this reader, move it.
		if  !=  {
			if _,  = .Seek(, io.SeekStart);  != nil {
				return 0, fileError(, .closed, experimentalsys.UnwrapOSError())
			}
		}

		,  = .Read()
		if  = experimentalsys.UnwrapOSError();  != 0 {
			// Defer validation overhead until we've already had an error.
			 = fileError(, .closed, )
		}
	} else {
		 = experimentalsys.ENOSYS // unsupported
	}
	return
}

// Seek implements the same method as documented on sys.File
func ( *fsFile) ( int64,  int) ( int64,  experimentalsys.Errno) {
	// If this is a directory, and we're attempting to seek to position zero,
	// we have to re-open the file to ensure the directory state is reset.
	var  bool
	if  == 0 &&  == io.SeekStart {
		if ,  = .IsDir();  == 0 &&  {
			.reopenDir = true
			return
		}
	}

	if ,  := .file.(io.Seeker);  {
		if ,  = seek(, , );  != 0 {
			// Defer validation overhead until we've already had an error.
			 = fileError(, .closed, )
		}
	} else {
		 = experimentalsys.ENOSYS // unsupported
	}
	return
}

// Readdir implements the same method as documented on sys.File
//
// Notably, this uses readdirFile or fs.ReadDirFile if available. This does not
// return inodes on windows.
func ( *fsFile) ( int) ( []experimentalsys.Dirent,  experimentalsys.Errno) {
	// Windows lets you Readdir after close, FS.File also may not implement
	// close in a meaningful way. read our closed field to return consistent
	// results.
	if .closed {
		 = experimentalsys.EBADF
		return
	}

	if .reopenDir { // re-open the directory if needed.
		.reopenDir = false
		if  = adjustReaddirErr(, .closed, .rewindDir());  != 0 {
			return
		}
	}

	if ,  := .file.(readdirFile);  {
		// We can't use f.name here because it is the path up to the sys.FS,
		// not necessarily the real path. For this reason, Windows may not be
		// able to populate inodes. However, Darwin and Linux will.
		if ,  = readdir(, "", );  != 0 {
			 = adjustReaddirErr(, .closed, )
		}
		return
	}

	// Try with FS.ReadDirFile which is available on api.FS implementations
	// like embed:FS.
	if ,  := .file.(fs.ReadDirFile);  {
		,  := .ReadDir()
		if  = adjustReaddirErr(, .closed, );  != 0 {
			return
		}
		 = make([]experimentalsys.Dirent, 0, len())
		for ,  := range  {
			// By default, we don't attempt to read inode data
			 = append(, experimentalsys.Dirent{Name: .Name(), Type: .Type()})
		}
	} else {
		 = experimentalsys.EBADF // not a directory
	}
	return
}

// Write implements the same method as documented on sys.File.
func ( *fsFile) ( []byte) ( int,  experimentalsys.Errno) {
	if ,  := .file.(io.Writer);  {
		if ,  = write(, );  != 0 {
			// Defer validation overhead until we've already had an error.
			 = fileError(, .closed, )
		}
	} else {
		 = experimentalsys.ENOSYS // unsupported
	}
	return
}

// Pwrite implements the same method as documented on sys.File.
func ( *fsFile) ( []byte,  int64) ( int,  experimentalsys.Errno) {
	if ,  := .file.(io.WriterAt);  {
		if ,  = pwrite(, , );  != 0 {
			// Defer validation overhead until we've already had an error.
			 = fileError(, .closed, )
		}
	} else {
		 = experimentalsys.ENOSYS // unsupported
	}
	return
}

// Close implements the same method as documented on sys.File.
func ( *fsFile) () experimentalsys.Errno {
	if .closed {
		return 0
	}
	.closed = true
	return .close()
}

func ( *fsFile) () experimentalsys.Errno {
	return experimentalsys.UnwrapOSError(.file.Close())
}

// IsNonblock implements the same method as documented on fsapi.File
func ( *fsFile) () bool {
	return false
}

// SetNonblock implements the same method as documented on fsapi.File
func ( *fsFile) (bool) experimentalsys.Errno {
	return experimentalsys.ENOSYS
}

// Poll implements the same method as documented on fsapi.File
func ( *fsFile) (fsapi.Pflag, int32) ( bool,  experimentalsys.Errno) {
	return false, experimentalsys.ENOSYS
}

// dirError is used for commands that work against a directory, but not a file.
func dirError( experimentalsys.File,  bool,  experimentalsys.Errno) experimentalsys.Errno {
	if  := validate(, , false, true);  != 0 {
		return 
	}
	return 
}

// fileError is used for commands that work against a file, but not a directory.
func fileError( experimentalsys.File,  bool,  experimentalsys.Errno) experimentalsys.Errno {
	if  := validate(, , true, false);  != 0 {
		return 
	}
	return 
}

// validate is used to making syscalls which will fail.
func validate( experimentalsys.File, , ,  bool) experimentalsys.Errno {
	if  {
		return experimentalsys.EBADF
	}

	,  := .IsDir()
	if  != 0 {
		return 
	}

	if  &&  {
		return experimentalsys.EISDIR
	} else if  && ! {
		return experimentalsys.ENOTDIR
	}
	return 0
}

func read( io.Reader,  []byte) ( int,  experimentalsys.Errno) {
	if len() == 0 {
		return 0, 0 // less overhead on zero-length reads.
	}

	,  := .Read()
	return , experimentalsys.UnwrapOSError()
}

func pread( io.ReaderAt,  []byte,  int64) ( int,  experimentalsys.Errno) {
	if len() == 0 {
		return 0, 0 // less overhead on zero-length reads.
	}

	,  := .ReadAt(, )
	return , experimentalsys.UnwrapOSError()
}

func seek( io.Seeker,  int64,  int) (int64, experimentalsys.Errno) {
	if uint() > io.SeekEnd {
		return 0, experimentalsys.EINVAL // negative or exceeds the largest valid whence
	}

	,  := .Seek(, )
	return , experimentalsys.UnwrapOSError()
}

func ( *fsFile) () experimentalsys.Errno {
	// Reopen the directory to rewind it.
	,  := .fs.Open(.name)
	if  != nil {
		return experimentalsys.UnwrapOSError()
	}
	,  := .Stat()
	if  != nil {
		return experimentalsys.UnwrapOSError()
	}
	// Can't check if it's still the same file,
	// but is it still a directory, at least?
	if !.IsDir() {
		return experimentalsys.ENOTDIR
	}
	// Only update f on success.
	_ = .file.Close()
	.file = 
	return 0
}

// readdirFile allows masking the `Readdir` function on os.File.
type readdirFile interface {
	Readdir(n int) ([]fs.FileInfo, error)
}

// readdir uses readdirFile.Readdir, special casing windows when path !="".
func readdir( readdirFile,  string,  int) ( []experimentalsys.Dirent,  experimentalsys.Errno) {
	,  := .Readdir()
	if  = experimentalsys.UnwrapOSError();  != 0 {
		return
	}

	 = make([]experimentalsys.Dirent, 0, len())

	// linux/darwin won't have to fan out to lstat, but windows will.
	var  sys.Inode
	for  := range  {
		 := []
		// inoFromFileInfo is more efficient than sys.NewStat_t, as it gets the
		// inode without allocating an instance and filling other fields.
		if ,  = inoFromFileInfo(, );  != 0 {
			return
		}
		 = append(, experimentalsys.Dirent{Name: .Name(), Ino: , Type: .Mode().Type()})
	}
	return
}

func write( io.Writer,  []byte) ( int,  experimentalsys.Errno) {
	if len() == 0 {
		return 0, 0 // less overhead on zero-length writes.
	}

	,  := .Write()
	return , experimentalsys.UnwrapOSError()
}

func pwrite( io.WriterAt,  []byte,  int64) ( int,  experimentalsys.Errno) {
	if len() == 0 {
		return 0, 0 // less overhead on zero-length writes.
	}

	,  := .WriteAt(, )
	return , experimentalsys.UnwrapOSError()
}

func chtimes( string, ,  int64) ( experimentalsys.Errno) { //nolint:unused
	// When both inputs are omitted, there is nothing to change.
	if  == experimentalsys.UTIME_OMIT &&  == experimentalsys.UTIME_OMIT {
		return
	}

	// UTIME_OMIT is expensive until progress is made in Go, as it requires a
	// stat to read-back the value to re-apply.
	// - https://github.com/golang/go/issues/32558.
	// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
	var  sys.Stat_t
	if  == experimentalsys.UTIME_OMIT ||  == experimentalsys.UTIME_OMIT {
		if ,  = stat();  != 0 {
			return
		}
	}

	var ,  time.Time
	if  == experimentalsys.UTIME_OMIT {
		 = epochNanosToTime(.Atim)
		 = epochNanosToTime()
	} else if  == experimentalsys.UTIME_OMIT {
		 = epochNanosToTime()
		 = epochNanosToTime(.Mtim)
	} else {
		 = epochNanosToTime()
		 = epochNanosToTime()
	}
	return experimentalsys.UnwrapOSError(os.Chtimes(, , ))
}

func epochNanosToTime( int64) time.Time { //nolint:unused
	 :=  / 1e9
	 :=  % 1e9
	return time.Unix(, )
}