/*
 * Copyright 2004-2019 the Pacemaker project contributors
 *
 * The version control history for this file may have further details.
 *
 * This source code is licensed under the GNU Lesser General Public License
 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
 */

#include <crm_internal.h>

#ifndef _GNU_SOURCE
#  define _GNU_SOURCE
#endif

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#include <crm/crm.h>

int
crm_pid_active(long pid, const char *daemon)
{
    static int last_asked_pid = 0;  /* log spam prevention */
#if SUPPORT_PROCFS
    static int have_proc_pid = 0;
#else
    static int have_proc_pid = -1;
#endif
    int rc = 0;

    if (have_proc_pid == 0) {
        /* evaluation of /proc/PID/exe applicability via self-introspection */
        char proc_path[PATH_MAX], exe_path[PATH_MAX];
        snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe",
                 (long unsigned int) getpid());
        have_proc_pid = 1;
        if (readlink(proc_path, exe_path, sizeof(exe_path) - 1) < 0) {
            have_proc_pid = -1;
        }
    }

    if (pid <= 0) {
        return -1;

    } else if ((rc = kill(pid, 0)) < 0 && errno == ESRCH) {
        return 0;  /* no such PID detected */

    } else if (rc < 0 && (daemon == NULL || have_proc_pid == -1)) {
        if (last_asked_pid != pid) {
            crm_info("Cannot examine PID %ld: %s", pid, strerror(errno));
            last_asked_pid = pid;
        }
        return -2;  /* errno != ESRCH */

    } else if (rc == 0 && (daemon == NULL || have_proc_pid == -1)) {
        return 1;  /* kill as the only indicator, cannot double check */

    } else if (daemon != NULL) {
        /* make sure PID hasn't been reused by another process
           XXX: might still be just a zombie, which could confuse decisions */
        bool checked_through_kill = (rc == 0);
        char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX];
        snprintf(proc_path, sizeof(proc_path), "/proc/%ld/exe", pid);

        rc = readlink(proc_path, exe_path, sizeof(exe_path) - 1);
        if ((rc < 0) && (errno == EACCES)) {
            if (last_asked_pid != pid) {
                crm_info("Could not read from %s: %s", proc_path,
                         strerror(errno));
                last_asked_pid = pid;
            }
            return checked_through_kill ? 1 : -2;
        } else if (rc < 0) {
            if (last_asked_pid != pid) {
                crm_err("Could not read from %s: %s (%d)", proc_path,
                        strerror(errno), errno);
                last_asked_pid = pid;
            }
            return 0;  /* most likely errno == ENOENT */
        }
        exe_path[rc] = '\0';

        if (daemon[0] != '/') {
            rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s",
                          daemon);
        } else {
            rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon);
        }

        if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) {
            return 1;
        }
    }

    return 0;
}

#define	LOCKSTRLEN	11

long
crm_read_pidfile(const char *filename)
{
    int fd;
    struct stat sbuf;
    long pid = -ENOENT;
    char buf[LOCKSTRLEN + 1];

    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        goto bail;
    }

    if ((fstat(fd, &sbuf) >= 0) && (sbuf.st_size < LOCKSTRLEN)) {
        sleep(2);           /* if someone was about to create one,
                             * give'm a sec to do so
                             */
    }

    if (read(fd, buf, sizeof(buf)) < 1) {
        goto bail;
    }

    if (sscanf(buf, "%ld", &pid) > 0) {
        if (pid <= 0) {
            pid = -ESRCH;
        } else {
            crm_trace("Got pid %lu from %s\n", pid, filename);
        }
    }

  bail:
    if (fd >= 0) {
        close(fd);
    }
    return pid;
}

long
crm_pidfile_inuse(const char *filename, long mypid, const char *daemon)
{
    long pid = crm_read_pidfile(filename);

    if (pid < 2) {
        // Invalid pid
        pid = -ENOENT;
        unlink(filename);

    } else if (mypid && (pid == mypid)) {
        // In use by us
        pid = pcmk_ok;

    } else if (crm_pid_active(pid, daemon) == FALSE) {
        // Contains a stale value
        unlink(filename);
        pid = -ENOENT;

    } else if (mypid && (pid != mypid)) {
        // Locked by existing process
        pid = -EEXIST;
    }

    return pid;
}

int
crm_lock_pidfile(const char *filename, const char *name)
{
    long mypid = 0;
    int fd = 0;
    int rc = 0;
    char buf[LOCKSTRLEN + 2];

    mypid = (unsigned long) getpid();

    rc = crm_pidfile_inuse(filename, 0, name);
    if (rc == -ENOENT) {
        // Exists, but the process is not active

    } else if (rc != pcmk_ok) {
        // Locked by existing process
        return rc;
    }

    fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644);
    if (fd < 0) {
        return -errno;
    }

    snprintf(buf, sizeof(buf), "%*ld\n", LOCKSTRLEN - 1, mypid);
    rc = write(fd, buf, LOCKSTRLEN);
    close(fd);

    if (rc != LOCKSTRLEN) {
        crm_perror(LOG_ERR, "Incomplete write to %s", filename);
        return -errno;
    }

    return crm_pidfile_inuse(filename, mypid, name);
}
