/*
 * Copyright (c) 2001, 2002, 2003, 2004, 2005  Netli, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: ncnf_app_int.c,v 1.1 2005/05/26 12:08:19 vlm Exp $
 */
#include "headers.h"
#include "ncnf_app.h"
#include "ncnf_app_int.h"
#include "ncnf_int.h"

/* pidfile manual handler */
static void (*pf_handler)(int pfd, const char *filename);

void
__na_pidfile_manual_handler(
        void (*onUnload)(int pfd, const char *filename)) {
	assert(!pf_handler);
	pf_handler = onUnload;
}

static int
__na_trylock(int fd, int timeout) {
	int ret;
	int warned = 0;

	struct flock flk;

	flk.l_start = 0;	/* lock from start */
	flk.l_len = 0;	/* till the end */
	flk.l_type = F_WRLCK;
	flk.l_whence = 0;

	do {

		ret = fcntl(fd, F_SETLK, &flk);
		if(ret == 0)
			return 0;
		assert(ret == -1);
		if(errno != EAGAIN)
			return -1;

		if(((warned++) % 30) == 0) {
			_ncnf_debug_print(0,
			"Waiting %d for the previous process to exit...",
			timeout
			);
		}
		sleep(1);
	} while(--timeout);

	errno = ETIMEDOUT;
	return -1;
}

int
__na_pidfile_update(ncnf_obj *process, pid_t update_pid) {
        ncnf_obj *iter;
        ncnf_obj *pfo;  /* pidfile object */

        if(process == NULL || strcmp(ncnf_obj_type(process), "process")) {
                errno = EINVAL;
                return -1;
        }

        iter = ncnf_get_obj(process, "pidfile", NULL, NCNF_ITER_ATTRIBUTES);
        while((pfo = ncnf_iter_next(iter))) {
		struct ncnf_obj_s *pfo_int = pfo;
		int pfd;

		if(pfo_int->notify != __na_pidfile_notificator)
			/* Incorrectly initialized object, or not ours */
			continue;
		pfd = ((int)pfo_int->notify_key) - 1;
		if(pfd <= 0)
			/* Not bound to specific FD */
			continue;

		__na_write_pid_file(pfd, update_pid);
        }

        ncnf_destroy(iter);

        return 0;
}

int
__na_write_pid_file(int pf, pid_t opt_pid) {
	char _buf[32];
	int ret, len;
	struct flock flk;

	assert(pf != -1);

	/*
	 * Lock it again.
	 */
	flk.l_start = 0;	/* lock from start */
	flk.l_len = 0;		/* till the end */
	flk.l_type = F_WRLCK;
	flk.l_whence = 0;
	(void)fcntl(pf, F_SETLK, &flk);

	if(opt_pid) {
		len = snprintf(_buf, sizeof(_buf), "%lu\n",
			(unsigned long)opt_pid);
		assert(len < sizeof(_buf));
		if(len >= sizeof(_buf))
			return -1;

		if(lseek(pf, 0, SEEK_SET) != 0) {
			errno = EIO;
			return -1;
		}

		ftruncate(pf, 0);

		do {
			ret = write(pf, _buf, len);
		} while(ret == -1 && errno == EINTR);
		if(ret != len) {
			if(ret > 0) {
				/*
				 * Do not allow others to use partial file.
				 */
				ftruncate(pf, 0);
			}
			errno = EIO;
			return -1;
		}

	} else {
		ftruncate(pf, 0);
	}

	fsync(pf);

	return 0;
}

int
__na_make_pid_file(char *pidfile) {
	struct stat sb;
	struct flock flk;
	char buf[32] = { '\0' };
	int pf;
	int ret;
	int open_flags = O_RDWR | O_CREAT;

	if(!pidfile) {
		errno = EINVAL;
		return -1;
	}

	if(*pidfile == '\0') {
		errno = 0;
		return -1;
	}

	ret = stat(pidfile, &sb);
	if(ret == -1) {
		if(errno != ENOENT)
			return -1;

		open_flags |= O_EXCL;
	} else {
		if((sb.st_mode & S_IFMT) != S_IFREG) {
			_ncnf_debug_print(1, "%s: Inappropriate file type",
				pidfile);
			errno = EPERM;
			return -1;
		}
	}

	/*
	 * Open the PID file
	 */

	pf = open(pidfile, open_flags, 0644);
	if(pf == -1) {
		_ncnf_debug_print(1,
			"Can't open PID file %s: %s",
				pidfile, strerror(errno));
		return -1;
	}

	/*
	 * Set close-on-exec flag.
	 */

	ret = fcntl(pf, F_GETFD, 0);
	if(ret == -1) {
		close(pf);
		_ncnf_debug_print(1,
			"Can't get flags for %s: %s",
				pidfile, strerror(errno));
		return -1;
	}
	if(fcntl(pf, F_SETFD, ret | FD_CLOEXEC) == -1) {
		close(pf);
		_ncnf_debug_print(1,
			"Can't set close-on-exec flag for %s: %s",
				pidfile, strerror(errno));
		return -1;
	}

	/*
	 * Lock the file.
	 */

	flk.l_start = 0;	/* lock from start */
	flk.l_len = 0;	/* till the end */
	flk.l_type = F_WRLCK;
	flk.l_whence = 0;

	do {
		ret = fcntl(pf, F_SETLK, &flk);
	} while(ret == -1 && errno == EINTR);
	if(ret != -1) {
		if(__na_write_pid_file(pf, getpid())) {
			_ncnf_debug_print(1,
				"Can't write PID file %s", pidfile);
			return -1;
		}

		/* Don't close the file descriptor in order to save lock. */

		return pf;
	}

	if(errno != EACCES && errno != EAGAIN) {
		/*
		 * Unknown problem during locking.
		 */
		_ncnf_debug_print(1,
			"Can't lock PID file %s: %s",
				pidfile, strerror(errno));
		close(pf);
		errno = EPERM;
		return -1;
	}

	/*
	 * Try to read the contents of the file.
	 */
	do {
		ret = read(pf, buf, sizeof(buf));
	} while(ret == -1 && errno == EINTR);
	if(ret == -1 || (ret > 0 && buf[ret -1] != '\n')) {
		/*
		 * Something wrong here: error or partial contents.
		 */
		_ncnf_debug_print(1,
			"Can't start: another instance running");
	} else if(ret >= 0) {
		/*
		 * Somebody locked the file and wrote a getpid() in it.
		 */
		buf[ret - 1] = '\0';
		ret = fcntl(pf, F_GETLK, &flk);
		if(ret == -1) {
			_ncnf_debug_print(1,
			"Can't start: Problem getting pid file "
			"lock information, data=[%s]", buf);
		} else {
			long pif;	/* pid in file */
			pif = strtol(buf, NULL, 10);
			if(flk.l_type == F_UNLCK) {
				/* Suddenly, not locked anymore! */
				_ncnf_debug_print(1,
				"Can't start: another instance "
				"almost running (\"%s\")", buf);
			} else if(*buf == 0
				/* Tested new behavior and old one */
				|| strcmp(buf, "finishing") == 0) {

				/*
				 * Wait until the previous process finishes.
				 */
				ret = __na_trylock(pf, 5 * 60);
				if(ret == -1) {
					_ncnf_debug_print(1,
					"Can't start: another instance "
					"still running: %s",
					strerror(errno));
				} else if(__na_write_pid_file(pf, getpid())) {
					_ncnf_debug_print(1,
					"Can't write PID file %s",
					pidfile);
				} else {
					return pf;
				}
			} else {
				if(pif != flk.l_pid) {
					_ncnf_debug_print(1,
					"Can't start: another instance running,"
					" pid in file=%s, lock pid=%lu",
					buf, (unsigned long)flk.l_pid);
				} else {
					_ncnf_debug_print(1,
					"Can't start: another instance running,"
					" pid=%lu",
					(unsigned long)flk.l_pid);
				}
			}
		}
	}

	close(pf);
	errno = EPERM;
	return -1;
}


void
__na_default_pidfile_open_failed_callback(char *filename, int is_firsttime) {
	_ncnf_debug_print(is_firsttime?1:0, "Pidfile %s open failed: %s",
		filename,
		strerror(errno));
	if(is_firsttime)
		exit(EX_TEMPFAIL);
}


int
__na_pidfile_notificator(ncnf_obj *obj, enum ncnf_notify_event event,
		void *key) {
	static int firsttime = 1;
	int old_pidfd = ((int)key) - 1;
	int pf;
	char *filename;

	switch(event) {
	case NCNF_NOTIF_ATTACH:
		return 0;
	case NCNF_NOTIF_DETACH:
		if(old_pidfd != -1)
			__na_write_pid_file(old_pidfd, getpid());
		/* Refusing to detach myself */
		return -1;
	case NCNF_UDATA_DETACH:
	case NCNF_UDATA_ATTACH:
		return 0;
	case NCNF_OBJ_ADD:

		if(pf_handler) {
			/* Manual override requested. */
			break;
		}

		filename = ncnf_obj_name(obj);

		/*
		 * Append sysid if filename is a directory name.
		 */
		if(filename && filename[0]
			&& filename[strlen(filename) - 1] == '/') {
			bstr_t sysid = NCNF_APP_construct_id(ncnf_obj_parent(obj));
			if(sysid) {
				filename = alloca(
					strlen(ncnf_obj_name(obj))
					+ bstr_len(sysid)
					+ sizeof(".pid"));
				assert(filename);
				strcpy(filename, ncnf_obj_name(obj));
				strcat(filename, sysid);
				strcat(filename, ".pid");
				bstr_free(sysid);
			}
		}

		pf = __na_make_pid_file(filename);
		if(pf == -1 && errno != 0) {
			if(NCNF_APP_pidfile_open_failed_callback)
			NCNF_APP_pidfile_open_failed_callback(filename,
				firsttime);
			return -1;
		}
		ncnf_notificator_attach(obj,
			__na_pidfile_notificator,
			(void *)(pf + 1));
		break;
	case NCNF_OBJ_DESTROY:
		if(old_pidfd != -1) {
			if(pf_handler) {
				pf_handler(old_pidfd, ncnf_obj_name(obj));
			} else {
				__na_write_pid_file(old_pidfd, 0);
				close(old_pidfd);
			}
		}
		break;
	default:
		/* Do nothing */
		break;
	}

	if(strcmp(ncnf_obj_type(obj), "process") == 0)
		firsttime = 0;

	return 0;
}


/* Current value of "reload-ncnf-validator" setting */
char *_param_reload_ncnf_validator;
int _param_reload_ncnf_validator_ncql = 1;

/*
 * React on change in "reload-ncnf-validator" setting.
 */
int
__na_reload_ncnf_validator_notificator(ncnf_obj *obj, enum ncnf_notify_event event, void *key) {
	switch(event) {
	case NCNF_OBJ_ADD:
		ncnf_notificator_attach(obj,
			__na_reload_ncnf_validator_notificator,
			NULL);
		/* Fall through */
	case NCNF_OBJ_CHANGE:
		_param_reload_ncnf_validator = ncnf_obj_name(obj);
		break;
	case NCNF_OBJ_DESTROY:
		_param_reload_ncnf_validator = 0;
		break;
	default:
		break;
	}

	return 0;
}


/*
 * React on change in "reload-ncnf-validator-ncql" setting.
 */
int
__na_reload_ncnf_validator_ncql_notificator(ncnf_obj *obj, enum ncnf_notify_event event, void *key) {
	switch(event) {
	case NCNF_OBJ_ADD:
		ncnf_notificator_attach(obj,
			__na_reload_ncnf_validator_ncql_notificator,
			NULL);
		/* Fall through */
	case NCNF_OBJ_CHANGE:
		ncnf_get_attr_int(ncnf_obj_parent(obj),
			"reload-ncnf-validator-ncql",
			&_param_reload_ncnf_validator_ncql);
		break;
	case NCNF_OBJ_DESTROY:
		_param_reload_ncnf_validator_ncql = 1;
		break;
	default:
		break;
	}

	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1