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

package restorer

import (
	"os"
	"syscall"

	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/fs"
)

// OpenFile opens the file with create, truncate and write only options if
// createSize is specified greater than 0 i.e. if the file hasn't already
// been created. Otherwise it opens the file with only write only option.
func (fw *filesWriter) OpenFile(createSize int64, path string, fileInfo *fileInfo) (file *os.File, err error) {
	return fw.openFile(createSize, path, fileInfo)
}

// OpenFile opens the file with create, truncate and write only options if
// createSize is specified greater than 0 i.e. if the file hasn't already
// been created. Otherwise it opens the file with only write only option.
func (fw *filesWriter) openFile(createSize int64, path string, _ *fileInfo) (file *os.File, err error) {
	if createSize >= 0 {
		file, err = openFileWithCreate(path)
		if fs.IsAccessDenied(err) {
			// If file is readonly, clear the readonly flag by resetting the
			// permissions of the file and try again
			// as the metadata will be set again in the second pass and the
			// readonly flag will be applied again if needed.
			err = fs.ResetPermissions(path)
			if err != nil {
				return nil, err
			}
			file, err = openFileWithTruncWrite(path)
		}
	} else {
		flags := os.O_WRONLY
		file, err = os.OpenFile(path, flags, 0600)
	}
	return file, err
}

// openFileWithCreate opens the file with os.O_CREATE flag along with os.O_TRUNC and os.O_WRONLY.
func openFileWithCreate(path string) (file *os.File, err error) {
	flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
	return os.OpenFile(path, flags, 0600)
}

// openFileWithTruncWrite opens the file without os.O_CREATE flag along with os.O_TRUNC and os.O_WRONLY.
func openFileWithTruncWrite(path string) (file *os.File, err error) {
	flags := os.O_TRUNC | os.O_WRONLY
	return os.OpenFile(path, flags, 0600)
}

// CleanupPath performs clean up for the specified path.
func CleanupPath(_ string) {
	// no-op
}

func createOrOpenFile(path string, createSize int64, fileInfo *fileInfo, allowRecursiveDelete bool) (*os.File, error) {
	if createSize >= 0 {
		return createFile(path, createSize, fileInfo, allowRecursiveDelete)
	}
	return openFile(path)
}

func createFile(path string, createSize int64, fileInfo *fileInfo, allowRecursiveDelete bool) (*os.File, error) {
	f, err := fs.OpenFile(path, fs.O_CREATE|fs.O_WRONLY|fs.O_NOFOLLOW, 0600)
	if err != nil && fs.IsAccessDenied(err) {
		// If file is readonly, clear the readonly flag by resetting the
		// permissions of the file and try again
		// as the metadata will be set again in the second pass and the
		// readonly flag will be applied again if needed.
		if err = fs.ResetPermissions(path); err != nil {
			return nil, err
		}
		if f, err = fs.OpenFile(path, fs.O_WRONLY|fs.O_NOFOLLOW, 0600); err != nil {
			return nil, err
		}
	} else if err != nil && (errors.Is(err, syscall.ELOOP) || errors.Is(err, syscall.EISDIR)) {
		// symlink or directory, try to remove it later on
		f = nil
	} else if err != nil {
		return nil, err
	}
	return postCreateFile(f, path, createSize, allowRecursiveDelete, fileInfo.sparse)
}