/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Ohio Trollius
 *	Copyright 1997 The Ohio State University
 *	NJN
 *
 *	$Id: registry.c,v 1.1 1999/07/28 00:31:53 jsquyres Exp $
 *
 *	Function:	- object registry
 *			- mechanism for registering semi-persistent
 *			  objects for cleanup
 *			- objects are registered with their type
 *				m	SYSV shared memory segment
 *				s	SYSV semaphore set
 *				f	temporary file
 *
 */

#include <lam_config.h>

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>

#include <terror.h>
#include <typical.h>

#ifndef MAXPATHLEN
#define MAXPATHLEN	1024
#endif

/*
 * public functions
 */
#if __STDC__
void			lam_register_objects(int, ...);
#else
void			lam_register_objects();
#endif
void			lam_deregister_object();
void			lam_cleanup_objects();

/*
 * local constants
 */
#define	FORMBUFLEN	(MAXPATHLEN + 16)	/* size of formatting buffer */

/*
 * external functions
 */
extern char		*killname();

/*
 * private functions
 */
static char		*lam_reg_filename();
static char		*lam_reg_tmpname();
static char		*lam_reg_lockname();
static void		clean_warning();
static void		del_warning();
static int		filelock();

/*
 * private variables
 */
static FILE		*f = 0;			/* registry file */
static FILE		*tmpf = 0;		/* temporary file */
static char		*reg_fname = 0;		/* registry filename */
static char		*reg_tmpname = 0;	/* temporary filename */
static char		*reg_lockname = 0;	/* registry lock filename */
static int		lockfd;			/* lock file descriptor */

#if LAM_HAVE_UNION_SEMUN
static	union semun	semctl_arg;
#else
static union {
	int		val;
	struct semid_ds	*buf;
	unsigned short	*array;
} semctl_arg;
#endif


/*
 *	lam_register_objects
 *
 *	Function:	- add objects to registry
 *	Accepts:	- number of objects
 *			- variable length list of (int, char *) pairs
 *			  <obj type> <obj name>, <obj type> <obj name>, ...
 *			  type is 'm' => shared memory, 's' => semaphore set
 *			  	  'f' => file 
 */
#if __STDC__
void
lam_register_objects(int nobjs, ...)
#else
void
lam_register_objects(nobjs, va_alist)

int			nobjs;
va_dcl
#endif

{
	char		buf[FORMBUFLEN];	/* formatting buffer */
	char		type;			/* object type */
	char		*obj;			/* object name */
	int		fd;			/* registry file descriptor */
	int		i;
	va_list		arglist;

#if __STDC__
	va_start(arglist, nobjs);
#else
	va_start(arglist);
#endif

	if (reg_fname == 0) {
		reg_fname = lam_reg_filename();
		if (reg_fname == 0) return;

		reg_tmpname = lam_reg_tmpname();
		if (reg_tmpname == 0) return;

		reg_lockname = lam_reg_lockname();
		if (reg_lockname == 0) return;
	}
/*
 * Open lock file atomically.
 */
	lockfd = open(reg_lockname, O_RDWR | O_CREAT | O_EXCL, 0600);

	if (lockfd == -1 && errno == EEXIST) {
		lockfd = open(reg_lockname, O_RDWR, 0600);
	}

	if (lockfd == -1) return;

	if (filelock(lockfd, F_SETLKW, F_WRLCK,
			(off_t) 0, SEEK_SET, (off_t) 0) == -1) {
	    close(lockfd);
	    return;
	}
/*
 * Open the registry file and add the objects.
 */
	fd = open(reg_fname, O_WRONLY | O_APPEND | O_CREAT, 0600);

	if (fd < 0) {
		close(lockfd);
		return;
	}

	for (i = 0; i < nobjs; i++) {
		type = (char) va_arg(arglist, int);
		obj = va_arg(arglist, char *);

		switch (type) {
		case 'm':
		case 's':
		case 'f':
			sprintf(buf, "%c %s\n", type, obj);
			write(fd, buf, strlen(buf));
			break;
		default:
			close(fd);
			close(lockfd);
			va_end(arglist);
			return;
		}
	}

	close(fd);
	close(lockfd);
	va_end(arglist);
}


/*
 *	lam_deregister_object
 *
 *	Function:	- deregister object from registry file
 *			- does not delete the object, this should be done
 *			  before calling this function
 *	Accepts:	- type of the object to deregister
 *			- name of the object to deregister
 */
void
lam_deregister_object(deltype, delobj)

int			deltype;
char			*delobj;

{
    struct stat		fstatus;		/* registry file status */
    int			fd;			/* registry file descriptor */
    char		obj[MAXPATHLEN];	/* object name */
    char		type;			/* object type */

    if (reg_fname == 0) {
/*
 * Haven't previously opened the registry file.
 */
	reg_fname = lam_reg_filename();
	if (reg_fname == 0) return;

	reg_tmpname = lam_reg_tmpname();
	if (reg_tmpname == 0) return;

	reg_lockname = lam_reg_lockname();
	if (reg_lockname == 0) return;
    }
/*
 * Open and lock the registry lock file.
 */
    lockfd = open(reg_lockname, O_RDWR, 0600);
    if (lockfd < 0) {
	del_warning("lam_deregister_object (open)");
	return;
    }

    if (filelock(lockfd, F_SETLKW, F_WRLCK,
		(off_t) 0, SEEK_SET, (off_t) 0) == -1) {
	del_warning("lam_deregister_object (filelock)");
	return;
    }
/*
 * Open the registry and temporary files.
 */
    if ((f = fopen(reg_fname, "r")) == 0) {
	del_warning("lam_deregister_object (fopen)");
	return;
    }

    fd = fileno(f);

    if (fstat(fd, &fstatus) == -1) {
	del_warning("lam_deregister_object (fstat)");
	return;
    }

    if (fstatus.st_size == 0) {
	fclose(f);
	f = 0;
	close(lockfd);
	return;
    }

    if ((tmpf = fopen(reg_tmpname, "w")) == 0) {
	del_warning("lam_deregister_object (fopen)");
	return;
    }

    while (fscanf(f, "%c %s\n", &type, obj) == 2) {
	if (deltype != type || strcmp(obj, delobj) != 0) {
	    fprintf(tmpf, "%c %s\n", type, obj);
	}
    }
/*
 * Replace registry file with temporary file.
 */
    fclose(tmpf);
    tmpf = 0;
    fclose(f);
    f = 0;

    if (rename(reg_tmpname, reg_fname)) {
	del_warning("lam_deregister_object (rename)");
	return;
    }

    close(lockfd);
}


/*
 *	lam_cleanup_objects
 *
 *	Function:	- clean all registered objects
 *	Returns:	- 0 or LAMERROR
 */
void
lam_cleanup_objects()

{
    int			sysvid;			/* SYSV IPC shared id */
    char		type;			/* shared structure type */
    char		obj[MAXPATHLEN];	/* object name */

    lockfd = -1;

    if (reg_fname == 0) {
	reg_fname = lam_reg_filename();
	if (reg_fname == 0) {
	    clean_warning("lam_cleanup_objects (lam_reg_filename)");
	    return;
	}

	reg_lockname = lam_reg_lockname();
	if (reg_lockname == 0) {
	    clean_warning("lam_cleanup_objects (lam_reg_lockname)");
	    return;
	}
    }
/*
 * Open lock file atomically.
 */
    lockfd = open(reg_lockname, O_RDWR | O_CREAT | O_EXCL, 0600);

    if (lockfd == -1 && errno == EEXIST) {
	lockfd = open(reg_lockname, O_RDWR, 0600);
    }

    if (lockfd == -1) {
	clean_warning("lam_cleanup_objects (open)");
	return;
    }

    if (filelock(lockfd, F_SETLKW, F_WRLCK,
		(off_t) 0, SEEK_SET, (off_t) 0) == -1) {
	clean_warning("lam_cleanup_objects (filelock)");
	return;
    }
/*
 * Open the registry file.
 */
    if ((f = fopen(reg_fname, "r")) == 0) {
	if (errno != ENOENT) {
	    clean_warning("lam_cleanup_objects (fopen)");
	}
	close(lockfd);
	return;
    }

    while (fscanf(f, "%c %s\n", &type, obj) == 2) {
/*
 * Don't check for errors here as structures may not exist.
 */
	switch (type) {
	case 'm':
	    sysvid = atoi(obj);
	    shmctl(sysvid, IPC_RMID, (struct shmid_ds *) 0);
	    break;
	case 's':
	    sysvid = atoi(obj);
	    semctl(sysvid, 0, IPC_RMID, semctl_arg);
	    break;
	case 'f':
	    unlink(obj);
	    break;
	default:
	    break;
	}
    }

    fclose(f);
    f = 0;

    unlink(reg_fname);
    close(lockfd);
    lockfd = -1;

    if (reg_fname) {
	free(reg_fname);
	reg_fname = 0;
    }
    if (reg_lockname) {
	free(reg_lockname);
	reg_lockname = 0;
    }
    if (reg_tmpname) {
	free(reg_tmpname);
	reg_tmpname = 0;
    }
}


/*
 *	lam_reg_filename
 *
 *	Function:	- forms shared structure registry filename
 *			- allocates space
 *	Returns:	- registry filename
 */
static char *
lam_reg_filename()

{
    	char		*fname;
	
	fname = killname();
	if (fname == 0) return(0);

	strcat(fname, "-reg");
	return(fname);
}

/*
 *	lam_reg_filename
 *
 *	Function:	- forms temporary registry filename
 *			- allocates space
 *	Returns:	- temporary registry filename
 */
static char *
lam_reg_tmpname()

{
    	char		*fname;

	fname = killname();
	if (fname == 0) return(0);

	strcat(fname, "-tmp");
	return(fname);
}


/*
 *	lam_reg_lockname
 *
 *	Function:	- forms registry lock filename
 *			- allocates space
 *	Returns:	- registry lock filename
 */
static char *
lam_reg_lockname()

{
    	char		*fname;

	fname = killname();
	if (fname == 0) return(0);

	strcat(fname, "-lock");
	return(fname);
}


/*
 *	clean_warning
 *
 *	Function:	- print warning of error while cleaning and
 *			  close work files
 *			- errno contains the error code
 *	Accepts:	- message
 */
static void
clean_warning(msg)

char			*msg;

{
	char		errmsg[256];		/* error message */

	lam_errorstr(errmsg, sizeof(errmsg));
	fprintf(stderr,
		"%s: Warning: %s: deleting shared memory structures in \"%s\"",
		msg, reg_fname, errmsg);

	fprintf(stderr, "\tManual cleanup may be necessary. Try ipcrm.\n");

	if (f) {
		fclose(f);
		f = 0;
	}
	if (tmpf) {
		fclose(tmpf);
		tmpf = 0;
		unlink(reg_tmpname);
	}
	if (lockfd >= 0) {
		close(lockfd);
		lockfd = -1;
	}
}


/*
 *	del_warning
 *
 *	Function:	- print warning of error while deleting an id from
 *			  the registry file and then close work files
 *			- errno contains the error code
 *	Accepts:	- message
 */
static void
del_warning(msg)

char			*msg;

{
    char		errmsg[128];		/* error message */

    lam_errorstr(errmsg, sizeof(errmsg));
    if (errno) {
	fprintf(stderr,
	    "%s: Warning: %s: deleting shared memory id's from registry.\n",
	    msg, errmsg);
    } else {
	fprintf(stderr, "%s\n", msg);
    }

    if (f) {
	fclose(f);
	f = 0;
    }
    if (tmpf) {
	fclose(tmpf);
	tmpf = 0;
	unlink(reg_tmpname);
    }
    if (lockfd >= 0) {
	close(lockfd);
	lockfd = -1;
    }
}


/*
 *	filelock
 *
 *	Function:	- lock file via fcntl
 *	Accepts:	- file descriptor to lock
 *			- fcntl locking cmd
 *			- type of lock
 *			- relative offset of lock
 *			- starting offset of lock
 *			- length of lock
 *	Returns:	- fcntl return value
 */
static int
filelock(fd, cmd, type, offset, whence, len)

int			fd;
int			cmd;
int			type;
off_t			offset;
int			whence;
off_t			len;

{
	struct flock	lock;

	lock.l_type = type;
	lock.l_start = offset;
	lock.l_whence = whence;
	lock.l_len = len;

	return(fcntl(fd, cmd, &lock));
}
