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

package unix

import (
	"syscall"
	"unsafe"
)

// The values of these constants are not part of the WASI API.
const (
	// UTIME_OMIT is the sentinel value to indicate that a time value should not
	// be changed. It is useful for example to indicate for example with UtimesNano
	// to avoid changing AccessTime or ModifiedTime.
	// Its value must match syscall/fs_wasip1.go
	UTIME_OMIT = -0x2

	AT_REMOVEDIR        = 0x200
	AT_SYMLINK_NOFOLLOW = 0x100
)

func Unlinkat(dirfd int, path string, flags int) error {
	if flags&AT_REMOVEDIR == 0 {
		return errnoErr(path_unlink_file(
			int32(dirfd),
			unsafe.StringData(path),
			size(len(path)),
		))
	} else {
		return errnoErr(path_remove_directory(
			int32(dirfd),
			unsafe.StringData(path),
			size(len(path)),
		))
	}
}

//go:wasmimport wasi_snapshot_preview1 path_unlink_file
//go:noescape
func path_unlink_file(fd int32, path *byte, pathLen size) syscall.Errno

//go:wasmimport wasi_snapshot_preview1 path_remove_directory
//go:noescape
func path_remove_directory(fd int32, path *byte, pathLen size) syscall.Errno

func Openat(dirfd int, path string, flags int, perm uint32) (int, error) {
	return syscall.Openat(dirfd, path, flags, perm)
}

func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error {
	var filestatFlags uint32
	if flags&AT_SYMLINK_NOFOLLOW == 0 {
		filestatFlags |= syscall.LOOKUP_SYMLINK_FOLLOW
	}
	return errnoErr(path_filestat_get(
		int32(dirfd),
		uint32(filestatFlags),
		unsafe.StringData(path),
		size(len(path)),
		unsafe.Pointer(stat),
	))
}

//go:wasmimport wasi_snapshot_preview1 path_filestat_get
//go:noescape
func path_filestat_get(fd int32, flags uint32, path *byte, pathLen size, buf unsafe.Pointer) syscall.Errno

func Readlinkat(dirfd int, path string, buf []byte) (int, error) {
	var nwritten size
	errno := path_readlink(
		int32(dirfd),
		unsafe.StringData(path),
		size(len(path)),
		&buf[0],
		size(len(buf)),
		&nwritten)
	return int(nwritten), errnoErr(errno)

}

type (
	size = uint32
)

//go:wasmimport wasi_snapshot_preview1 path_readlink
//go:noescape
func path_readlink(fd int32, path *byte, pathLen size, buf *byte, bufLen size, nwritten *size) syscall.Errno

func Mkdirat(dirfd int, path string, mode uint32) error {
	if path == "" {
		return syscall.EINVAL
	}
	return errnoErr(path_create_directory(
		int32(dirfd),
		unsafe.StringData(path),
		size(len(path)),
	))
}

//go:wasmimport wasi_snapshot_preview1 path_create_directory
//go:noescape
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno

func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
	// WASI preview 1 doesn't support changing file modes.
	return syscall.ENOSYS
}

func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
	// WASI preview 1 doesn't support changing file ownership.
	return syscall.ENOSYS
}

func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
	if oldpath == "" || newpath == "" {
		return syscall.EINVAL
	}
	return errnoErr(path_rename(
		int32(olddirfd),
		unsafe.StringData(oldpath),
		size(len(oldpath)),
		int32(newdirfd),
		unsafe.StringData(newpath),
		size(len(newpath)),
	))
}

//go:wasmimport wasi_snapshot_preview1 path_rename
//go:noescape
func path_rename(oldFd int32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno

func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) error {
	if oldpath == "" || newpath == "" {
		return syscall.EINVAL
	}
	return errnoErr(path_link(
		int32(olddirfd),
		0,
		unsafe.StringData(oldpath),
		size(len(oldpath)),
		int32(newdirfd),
		unsafe.StringData(newpath),
		size(len(newpath)),
	))
}

//go:wasmimport wasi_snapshot_preview1 path_link
//go:noescape
func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno

func Symlinkat(oldpath string, newdirfd int, newpath string) error {
	if oldpath == "" || newpath == "" {
		return syscall.EINVAL
	}
	return errnoErr(path_symlink(
		unsafe.StringData(oldpath),
		size(len(oldpath)),
		int32(newdirfd),
		unsafe.StringData(newpath),
		size(len(newpath)),
	))
}

//go:wasmimport wasi_snapshot_preview1 path_symlink
//go:noescape
func path_symlink(oldPath *byte, oldPathLen size, fd int32, newPath *byte, newPathLen size) syscall.Errno

func errnoErr(errno syscall.Errno) error {
	if errno == 0 {
		return nil
	}
	return errno
}
