// SPDX-License-Identifier: Apache-2.0
/*
 * umoci: Umoci Modifies Open Containers' Images
 * Copyright (C) 2016-2025 SUSE LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fseval

import (
	"io"
	"os"
	"path/filepath"
	"time"

	"github.com/vbatts/go-mtree"
	"golang.org/x/sys/unix"

	"github.com/opencontainers/umoci/pkg/unpriv"
)

// Rootless is an FsEval implementation that uses "umoci/pkg/unpriv".*
// functions in order to provide the ability for unprivileged users (those
// without CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH) to evaluate parts of a
// filesystem that they own. Note that by necessity this requires modifying the
// filesystem (and thus will not work on read-only filesystems).
var Rootless FsEval = unprivFsEval(0)

// unprivFsEval is a hack to be able to make RootlessFsEval a const.
type unprivFsEval int

// Open is equivalent to unpriv.Open.
func (fs unprivFsEval) Open(path string) (*os.File, error) {
	return unpriv.Open(path)
}

// Create is equivalent to unpriv.Create.
func (fs unprivFsEval) Create(path string) (*os.File, error) {
	return unpriv.Create(path)
}

// Readdir is equivalent to unpriv.Readdir.
func (fs unprivFsEval) Readdir(path string) ([]os.FileInfo, error) {
	return unpriv.Readdir(path)
}

// Lstat is equivalent to unpriv.Lstat.
func (fs unprivFsEval) Lstat(path string) (os.FileInfo, error) {
	return unpriv.Lstat(path)
}

func (fs unprivFsEval) Lstatx(path string) (unix.Stat_t, error) {
	return unpriv.Lstatx(path)
}

// Readlink is equivalent to unpriv.Readlink.
func (fs unprivFsEval) Readlink(path string) (string, error) {
	return unpriv.Readlink(path)
}

// Symlink is equivalent to unpriv.Symlink.
func (fs unprivFsEval) Symlink(target, linkname string) error {
	return unpriv.Symlink(target, linkname)
}

// Link is equivalent to unpriv.Link.
func (fs unprivFsEval) Link(target, linkname string) error {
	return unpriv.Link(target, linkname)
}

// Chmod is equivalent to unpriv.Chmod.
func (fs unprivFsEval) Chmod(path string, mode os.FileMode) error {
	return unpriv.Chmod(path, mode)
}

// Lutimes is equivalent to unpriv.Lutimes.
func (fs unprivFsEval) Lutimes(path string, atime, mtime time.Time) error {
	return unpriv.Lutimes(path, atime, mtime)
}

// RemoveAll is equivalent to unpriv.RemoveAll.
func (fs unprivFsEval) RemoveAll(path string) error {
	return unpriv.RemoveAll(path)
}

// Mknod is equivalent to unpriv.Mknod.
func (fs unprivFsEval) Mknod(path string, mode os.FileMode, dev uint64) error {
	return unpriv.Mknod(path, mode, dev)
}

// MkdirAll is equivalent to unpriv.MkdirAll.
func (fs unprivFsEval) MkdirAll(path string, perm os.FileMode) error {
	return unpriv.MkdirAll(path, perm)
}

// Llistxattr is equivalent to unpriv.Llistxattr.
func (fs unprivFsEval) Llistxattr(path string) ([]string, error) {
	return unpriv.Llistxattr(path)
}

// Lremovexattr is equivalent to unpriv.Lremovexattr.
func (fs unprivFsEval) Lremovexattr(path, name string) error {
	return unpriv.Lremovexattr(path, name)
}

// Lsetxattr is equivalent to unpriv.Lsetxattr.
func (fs unprivFsEval) Lsetxattr(path, name string, value []byte, flags int) error {
	return unpriv.Lsetxattr(path, name, value, flags)
}

// Lgetxattr is equivalent to unpriv.Lgetxattr.
func (fs unprivFsEval) Lgetxattr(path string, name string) ([]byte, error) {
	return unpriv.Lgetxattr(path, name)
}

// Lclearxattrs is equivalent to unpriv.Lclearxattrs.
func (fs unprivFsEval) Lclearxattrs(path string, skipFn func(xattrName string) bool) error {
	return unpriv.Lclearxattrs(path, skipFn)
}

// KeywordFunc returns a wrapper around the given mtree.KeywordFunc.
func (fs unprivFsEval) KeywordFunc(fn mtree.KeywordFunc) mtree.KeywordFunc {
	return func(path string, info os.FileInfo, r io.Reader) ([]mtree.KeyVal, error) {
		var kv []mtree.KeyVal
		err := unpriv.Wrap(path, func(path string) error {
			var err error
			kv, err = fn(path, info, r)
			return err
		})
		return kv, err
	}
}

// Walk is equivalent to filepath.Walk.
func (fs unprivFsEval) Walk(root string, fn filepath.WalkFunc) error {
	return unpriv.Walk(root, fn)
}
