/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: _gettxt.c 1.4 90/10/23 $@(#)libgen:gettxt.c/1.02 1.0 AT&T-UP"

#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/filehdr.h>
#include <sys/aouthdr.h>
#include <sys/scnhdr.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <locale.h>

#define	P_locale	"/usr/lib/locale/"
#define L_locale	(sizeof(P_locale))
#define MAXDB	5	/* maximum number of data bases per program */
#define DEF_LOCALE	"/usr/lib/locale/C/LC_MESSAGES/"
#define MESSAGES 	"/LC_MESSAGES/"
#define DB_NAME_LEN	15
#define LC_NAMELEN	15	/* should be in locale.h */
#define NULL	0

extern	char	_cur_locale[][LC_NAMELEN];

extern  int	atoi();
extern	char	*_align_malloc();
extern	void	_align_free();
extern	int	_mmap();

extern 	int	errno;

static 	char	saved_locale[LC_NAMELEN] = "";
static  char	not_found[] = "Message not found!!\n";


static	struct	db_info {
	char	db_name[DB_NAME_LEN];
	int	addr;
} db_info[MAXDB];

static	int	db_count;   	/* number of currently accessible data bases */

static	int	msgattach();
static	void	msgdetach();

char *
_gettxt(msg_id, dflt_str)
char	*msg_id;
char	*dflt_str;
{
	char  name[DB_NAME_LEN];
	int   rec_num;
	char  pathname[MAXPATHLEN];
	int   i;
	int   detach = 0;
	int   addr, size;
	char  *tokp;
	int   name_len;

	int   saveerrno;

	/* parse msg_id */

	if (((tokp = strchr(msg_id, ':')) == NULL) || *(tokp+1) == '\0')
		return(not_found);
	if ((name_len = (tokp - msg_id)) >= DB_NAME_LEN)
		return(not_found);
	if (name_len) {
		(void)strncpy(name, msg_id, name_len);
		name[name_len] = '\0';
	}
	else
		return(not_found);
	while (*++tokp)
		if (!isdigit(*tokp))
			return(not_found);
	rec_num = atoi(msg_id + name_len + 1);

	/*
	 * Has locale been changed?  If not, see if the message
	 * database has already been attached.
	 */

	if (strcmp(_cur_locale[LC_MESSAGES], saved_locale) == 0) {
		for(i = 0; i < db_count; i++)
			if(strcmp(db_info[i].db_name, name) == 0)
				break;
	}

	/*
	 * Locale has changed; flush out all databases which had
	 * been attached under the previous locale.
	 */
	else {
		(void)strcpy(saved_locale, _cur_locale[LC_MESSAGES]);
		for(i = 0; i < db_count; i++) {
			saveerrno = errno;
			msgdetach(db_info[i].addr);
			errno = saveerrno;
			(void)strcpy(db_info[i].db_name, "");
			detach++;
		}
		db_count = 0;
	}

	/*
	 * Attach relevant message database.
	 */
	if (detach || i == db_count) {
		(void)strcpy(pathname, P_locale);
		(void)strcpy(&pathname[L_locale - 1], saved_locale);
		(void)strcat(pathname, MESSAGES);
		(void)strcat(pathname, name);
/*		if (db_count == MAXDB)
			return(not_found);		*/
		saveerrno = errno;
		if ((addr = msgattach(pathname)) == -1) {
			errno = saveerrno;
			(void)strcpy(pathname, DEF_LOCALE);
			(void)strcat(pathname, name);
			saveerrno = errno;
			if ((addr = msgattach(pathname)) == -1) { 
				errno = saveerrno;
				if (!dflt_str || *dflt_str == '\0')
					return(not_found);
				else
					return(dflt_str);
			}
			else {
				/* Change locale to C */
				/* detach libraries to maintain consistency */
				(void)strcpy(saved_locale, "C");
				for(i = 0; i < db_count; i++) {
					saveerrno = errno;
					msgdetach(db_info[i].addr);
					errno = saveerrno;
					(void)strcpy(db_info[i].db_name, "");
				}
				db_count = 0;
			}
		}
		if (db_count >= MAXDB) {		/*free in FIFO order*/
			msgdetach(db_info[0].addr);
			for (i = 0; i < MAXDB - 1; i++)
				db_info[i] = db_info[i+1];
			db_count--;
		}
		(void)strcpy(db_info[db_count].db_name, name);
		i = db_count;
		db_info[db_count++].addr = addr;
	}
	if (rec_num == 0 || rec_num > *(int *)(db_info[i].addr))
		return(not_found);
	return((char *)(*(int *)(db_info[i].addr + sizeof(int) * (rec_num)) + db_info[i].addr));
}


/*
 * msgattach - Make a message database available in the address space
 *
 * _align_malloc() is used to provide room for the database, then mmap()
 * makes the file accessable at that address.  The mapping is
 * arranged to minimize impact on commands which link with this routine.
 */

#define	COFF_FHDR(coff) \
	((struct filehdr *)coff)
#define	COFF_AHDR(coff) \
	((struct aouthdr *)((coff)+sizeof(struct filehdr)))
#define	COFF_SHDR0(coff) \
	((struct scnhdr *)((coff) + sizeof(struct filehdr) + \
				    sizeof(struct aouthdr)))

static int
msgattach(path)
char *path;
{
	int		addr;
	int		fd;
	struct	stat	st;
	struct	scnhdr	*shdr;
	int		sindex;
	int		txt_ptr;

	if ((fd = open(path, O_RDONLY)) < 0) {
		return(-1);
	}
	
	if (fstat(fd, &st) < 0) {
		close(fd);
		return(-1);
	}

	if ((addr = (int)_align_malloc(st.st_size, NBPG)) == NULL) {
		close(fd);		/* cannot malloc */
		return(-1);
	}

	/*
	 * Making it executable will mean that the page will remain in
	 * memory a while even after the application goes away.
	 * Making it "shared" means that other processes can use it.
	 * Should mean that the pages will be multiplexed amongst
	 * all processes which need the message database.
	 */

	if (_mmap(addr, st.st_size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0) < 0)
	{
		_align_free(addr);	/* cannot map */
		close(fd);
		return(-1);
	}

	if ( COFF_FHDR(addr)->f_magic != I386MAGIC ||	/* magic no. wrong */
	     COFF_AHDR(addr)->magic != 0443 ) {
		_mmap(addr, st.st_size, PROT_RDWR, MAP_ZEROFILL, 0, 0);
		_align_free(addr);
		close(fd);
		return(-1);
	}

	shdr = COFF_SHDR0(addr);		/* look for .text section */
	for (sindex = 0; sindex < COFF_FHDR(addr)->f_nscns; sindex++, shdr++ ) {
		if (!strcmp( shdr->s_name, ".text" ))
			break;
	}

	if (sindex == COFF_FHDR(addr)->f_nscns) {	/* no .text */
		_mmap(addr, st.st_size, PROT_RDWR, MAP_ZEROFILL, 0, 0);
		_align_free(addr);
		close(fd);
		return(-1);
	}

	txt_ptr = shdr->s_scnptr;		/* get .text section start */

	if (_mmap(addr, txt_ptr, PROT_RDWR, MAP_ZEROFILL, 0, 0) < 0)
	{					/* unmap COFF header */
		_mmap(addr, st.st_size, PROT_RDWR, MAP_ZEROFILL, 0, 0);
		_align_free(addr);
		close(fd);
		return(-1);
	}

	close(fd);			/* space should still be mapped */
	addr += txt_ptr;
	((int*)addr)[-1] = st.st_size - txt_ptr;	/* size for unmap */
	((int*)addr)[-2] = addr - txt_ptr;		/* addr for free */
	return(addr);
}

/*
 * msgdetach - remove association with a message database
 *
 * A command message database which was previously acquired via
 * msgattach() is tossed.  Rather than unmap the pages, they are
 * mapped zerofill, so that malloc() can reallocate them.
 */

static void
msgdetach(addr)
int addr;
{
	if (_mmap(addr, ((int*)addr)[-1], PROT_RDWR, MAP_ZEROFILL, 0, 0) < 0)
		return;

	_align_free(((int*)addr)[-2]);
	return;
}
