/*
 * Copyright (c) 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: chkpidfile.c,v 1.5 2007/06/07 04:41:32 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/stat.h"
#include "sm/fcntl.h"
#include "sm/signal.h"
#include "sm/misc.h"

#include <stdio.h>

/*
**  SM_CHK_PIDFILE -- check whether pid_file exists and is "valid"
**
**	Parameters:
**		pidfile -- pathname for pid file
**		pfd -- (pointer to) fd for pid file (output, can be NULL)
**		errtxt -- buffer for error text (output)
**		errlen -- length of errtext
**
**	Returns:
**		usual sm_error code
**
**	Usage: sm_chk_pidfile(pid_file, &pid_fd, errtxt, sizeof(errtxt));
**	on exit:
**	if (pid_fd >= 0)
**	{
**		close(pid_fd);
**		pid_fd = -1;
**		(void) unlink(pid_file);
**	}
*/

sm_ret_T
sm_chk_pidfile(const char *pidfile, int *pfd, char *errtxt, size_t errlen)
{
	int r, save_errno;
	pid_t pid;
	int fd;
	size_t len;
	ssize_t nbytes;
	struct stat stb;
	char buf[32];

	SM_REQUIRE(pidfile != NULL);
	SM_REQUIRE(errtxt != NULL);
	pid = getpid();
	fd = -1;
	r = stat(pidfile, &stb);
	if (r == 0)
	{
		long pidold;

		/* read/write is necessary for lockf() */
		fd = open(pidfile, O_RDWR, 0644);
		if (fd == -1)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, status=pid_file exists but not readable; not starting as other process might be running"
				, pidfile);
			return sm_err_temp(EEXIST);
		}
		nbytes = read(fd, buf, sizeof(buf));
		if (nbytes == -1 || nbytes <= 1)
		{
			save_errno = (nbytes == -1) ? errno : EINVAL;
			snprintf(errtxt, errlen,
				"pid_file=%s, status=cannot read content of pid_file; not starting as other process might be running"
				, pidfile);
			goto error;
		}
		r = sscanf(buf, "%ld\n", &pidold);
		if (r <= 0)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, status=cannot read content of pid_file; not starting as other process might be running"
				, pidfile);
			save_errno = EEXIST;
			goto error;
		}
#if HAVE_LOCKF
		len = (size_t) nbytes;
		r = lseek(fd, 0, SEEK_SET);
		r = lockf(fd, F_TEST, len);
		if (r == -1)
		{
			save_errno = errno;
			snprintf(errtxt, errlen, "pid_file=%s, pid=%ld, status=locked; not starting as other process might be running"
				, pidfile, pidold);
			goto error;
		}
#endif /* HAVE_LOCKF */
		close(fd);
		fd = -1;

		r = kill(pidold, 0);
		save_errno = errno;
		if (r == 0)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, pid=%ld, status=process with pid in pid_file exists; not starting as other process might be running"
				, pidfile, pidold);
			return sm_err_temp(EEXIST);
		}
		if (r == -1 && save_errno == EPERM)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, pid=%ld, status=process with pid in pid_file may exist but permission denied; not starting as other process might be running"
				, pidfile, pidold);
			return sm_err_temp(EEXIST);
		}
		if (r == -1 && save_errno != ESRCH)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, pid=%ld, kill=%d, status=process with pid in pid_file may exist; not starting as other process might be running"
				, pidfile, pidold, save_errno);
			return sm_err_temp(EEXIST);
		}

		/* pid from pid_file doesn't exist anymore */
		(void) unlink(pidfile);

		snprintf(errtxt, errlen,
			"pid_file=%s, pid=%ld, status=starting as process with that pid does not exist; pid_file removed"
			, pidfile, pidold);
	}

	fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
	if (fd >= 0)
	{
		snprintf(buf, sizeof(buf), "%ld\n", (long) pid);
		len = strlen(buf);

		/*
		**  There's a race condition here as the file is locked
		**  after it has been created. However, O_EXLOCK doesn't
		**  have the right semantics (remove lock on exit) and
		**  SunOS 5 says it shouldn't be used.
		**  Try to minimize the race condition by locking first
		**  and then writing the pid into the file, see read()
		**  above.
		*/

		if (pfd != NULL)
		{
#if HAVE_LOCKF
			r = lockf(fd, F_LOCK, len);
			if (r != 0)
			{
				save_errno = errno;
				snprintf(errtxt, errlen,
					"pid_file=%s, lock=%s"
					, pidfile, strerror(save_errno));
				goto error;
			}
#endif /* HAVE_LOCKF */
			r = fcntl(fd, F_GETFD, 0);
			if (r == -1)
			{
				save_errno = errno;
				snprintf(errtxt, errlen,
					"pid_file=%s, fcntl=%s"
					, pidfile, strerror(save_errno));
				goto error;
			}
			(void) fcntl(fd, F_SETFD, r|FD_CLOEXEC);
			*pfd = fd;
		}

		nbytes = write(fd, buf, len);
		save_errno = errno;
		if (nbytes != len)
		{
			snprintf(errtxt, errlen,
				"pid_file=%s, status=cannot write, error=%s"
				, pidfile, strerror(save_errno));
			goto error;
		}

		if (pfd == NULL)
		{
			close(fd);
			fd = -1;
		}
	}
	else
	{
		save_errno = errno;
		snprintf(errtxt, errlen,
			"pid_file=%s, status=cannot open for write, error=%s"
			, pidfile, strerror(save_errno));
		goto error;
	}
	return 0;

  error:
	if (fd >= 0)
		close(fd);
	return sm_err_temp((save_errno == 0) ? EIO : save_errno);
}


syntax highlighted by Code2HTML, v. 0.9.1