// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

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

package cache

import (
	"context"
	stderrors "errors"
	"os"

	"github.com/hashicorp/boundary/internal/errors"
	"github.com/sevlyar/go-daemon"
)

func writePidFile(ctx context.Context, pidFile string) (pidCleanup, error) {
	const op = "cache.writePidFile"

	// Determine if we should clean up the file after we are done or if
	// it should stick around in the case of lock aquision error since this
	// file didn't create it.
	var pidExists bool
	_, err := os.Stat(pidFile)
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return noopPidCleanup, errors.Wrap(ctx, err, op)
	}
	if err == nil {
		pidExists = true
	}

	f, err := os.OpenFile(pidFile, os.O_CREATE|os.O_WRONLY, 0o600)
	if err != nil {
		return noopPidCleanup, errors.Wrap(ctx, err, op, errors.WithMsg("opening file"))
	}
	closeAndDeleteFileFn := func() error {
		err := f.Close()
		if !pidExists {
			err = stderrors.Join(err, os.Remove(pidFile))
		}
		return err
	}
	l := daemon.NewLockFile(f)
	if err := l.Lock(); err != nil {
		return closeAndDeleteFileFn, errors.Wrap(ctx, err, op)
	}
	// Now that we have acquired the lock and verified we own the pid file
	// we can remove it always when cleaning up.
	unlockAndCleanFn := func() error {
		return l.Remove()
	}
	if err := l.WritePid(); err != nil {
		return unlockAndCleanFn, errors.Wrap(ctx, err, op)
	}
	return unlockAndCleanFn, nil
}

func pidFileInUse(ctx context.Context, pidFile string) (*os.Process, error) {
	const op = "cache.pidFileInUse"
	if pidFile == "" {
		return nil, errors.New(ctx, errors.InvalidParameter, op, "pid filename is empty")
	}
	proc, err := (&daemon.Context{PidFileName: pidFile}).Search()
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	return proc, nil
}
