//go:build !windows
// +build !windows

package secure

import (
	"errors"
	"fmt"
	"os"
	"path"
	"syscall"
)

func isMorePermissive(currentMode, newMode os.FileMode) bool {
	currentGroup := currentMode & 070
	newGroup := newMode & 070
	currentAll := currentMode & 07
	newAll := newMode & 07

	return newGroup > currentGroup || newAll > currentAll
}

func checkPermPath(path string, perm os.FileMode) error {
	if !perm.IsDir() {
		perm = perm ^ os.ModeDir
	}

	dir, err := os.Stat(path)
	if err == nil {
		if dir.IsDir() {
			if isMorePermissive(dir.Mode(), perm) {
				return fmt.Errorf(
					"Path %s already exists with mode %o instead of the expected %o", path, dir.Mode(), perm)
			}
			return nil
		}
		return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
	}

	// Look for the parent directory in the path and then check the permissions in that
	// This is based on the logic from os.MkdirAll
	i := len(path)
	// This first loop skips over trailing path separators. Eg:
	// /some/path//////
	//          ^ i will end up here
	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
		i--
	}

	j := i
	// This loop starts from where i left off and finds the previous path separator
	// /some/path//////
	//      ^ j will end up here
	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
		j--
	}

	// if we are pointing at a path separator that is not the root separator, then check for the path before it
	// /some
	if j > 1 {
		return checkPermPath(path[:j-1], perm)
	}

	return nil
}

func checkPermFile(filePath string, perm os.FileMode) error {
	if f, err := os.Stat(filePath); !errors.Is(err, os.ErrNotExist) && f != nil && f.Mode() != perm {
		return fmt.Errorf(
			"File %s already exists with mode %o instead of the expected %o", filePath, f.Mode(), perm)
	}
	if err := checkPermPath(path.Dir(filePath), perm); err != nil {
		return err
	}

	return nil
}

func MkdirAll(path string, perm os.FileMode) error {
	if err := checkPermPath(path, perm); err != nil {
		return err
	}
	return os.MkdirAll(path, perm)
}

func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
	if err := checkPermFile(name, perm); err != nil {
		return nil, err
	}
	return os.OpenFile(name, flag, perm)
}
