/*-------------------------------------------------------------------------
 *
 * miscinit.c
 *	  miscellanious initialization support stuff
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.38 2000/01/09 12:15:57 ishii Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <sys/param.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>

#include "postgres.h"
#include "catalog/catname.h"
#include "catalog/pg_shadow.h"
#include "miscadmin.h"
#include "utils/syscache.h"


/*
 * EnableAbortEnvVarName
 *		Enables system abort iff set to a non-empty string in environment.
 */
#define EnableAbortEnvVarName	"POSTGRESABORT"

extern char *getenv(const char *name);	/* XXX STDLIB */

/*	from globals.c */
extern char *UserName;

#ifdef CYR_RECODE
unsigned char RecodeForwTable[128];
unsigned char RecodeBackTable[128];

#endif


/*
 * Define USE_ENVIRONMENT to get PGDATA, etc. from environment variables.
 * This is the default on UNIX platforms.
 */
#define USE_ENVIRONMENT

/* ----------------------------------------------------------------
 *				some of the 19 ways to leave postgres
 * ----------------------------------------------------------------
 */

/*
 * ExitPostgres
 *		Exit POSTGRES with a status code.
 *
 * Note:
 *		This function never returns.
 *		...
 *
 * Side effects:
 *		...
 *
 * Exceptions:
 *		none
 */
void
ExitPostgres(ExitStatus status)
{
	proc_exit(status);
}

/*
 * AbortPostgres
 *		Abort POSTGRES dumping core.
 *
 * Note:
 *		This function never returns.
 *		...
 *
 * Side effects:
 *		Core is dumped iff EnableAbortEnvVarName is set to a non-empty string.
 *		...
 *
 * Exceptions:
 *		none
 */
#ifdef NOT_USED
void
AbortPostgres()
{
	char	   *abortValue = getenv(EnableAbortEnvVarName);

	if (PointerIsValid(abortValue) && abortValue[0] != '\0')
		abort();
	else
		proc_exit(FatalExitStatus);
}


/* ----------------
 *		StatusBackendExit
 * ----------------
 */
void
StatusBackendExit(int status)
{
	/* someday, do some real cleanup and then call the LISP exit */
	/* someday, call StatusPostmasterExit if running without postmaster */
	proc_exit(status);
}

/* ----------------
 *		StatusPostmasterExit
 * ----------------
 */
void
StatusPostmasterExit(int status)
{
	/* someday, do some real cleanup and then call the LISP exit */
	proc_exit(status);
}

#endif


/* ----------------------------------------------------------------
 *		processing mode support stuff (used to be in pmod.c)
 * ----------------------------------------------------------------
 */
static ProcessingMode Mode = InitProcessing;

/*
 * IsBootstrapProcessingMode
 *		True iff processing mode is BootstrapProcessing.
 */
bool
IsBootstrapProcessingMode()
{
	return (bool) (Mode == BootstrapProcessing);
}

/*
 * IsInitProcessingMode
 *		True iff processing mode is InitProcessing.
 */
bool
IsInitProcessingMode()
{
	return (bool) (Mode == InitProcessing);
}

/*
 * IsNormalProcessingMode
 *		True iff processing mode is NormalProcessing.
 */
bool
IsNormalProcessingMode()
{
	return (bool) (Mode == NormalProcessing);
}

/*
 * SetProcessingMode
 *		Sets mode of processing as specified.
 *
 * Exceptions:
 *		BadArg if called with invalid mode.
 *
 * Note:
 *		Mode is InitProcessing before the first time this is called.
 */
void
SetProcessingMode(ProcessingMode mode)
{
	AssertArg(mode == BootstrapProcessing || mode == InitProcessing || 
				mode == NormalProcessing);

	Mode = mode;
}

ProcessingMode
GetProcessingMode()
{
	return Mode;
}

/* ----------------------------------------------------------------
 *				database path / name support stuff
 * ----------------------------------------------------------------
 */

void
SetDatabasePath(char *path)
{
	/* use malloc since this is done before memory contexts are set up */
	if (DatabasePath)
		free(DatabasePath);
	DatabasePath = malloc(strlen(path) + 1);
	strcpy(DatabasePath, path);
}

void
SetDatabaseName(char *name)
{
	if (DatabaseName)
		free(DatabaseName);
	DatabaseName = malloc(strlen(name) + 1);
	strcpy(DatabaseName, name);
}

#ifndef MULTIBYTE
/* even if MULTIBYTE is not enabled, this function is neccesary
 * since pg_proc.h has an entry for it.
 */
const char *
getdatabaseencoding()
{
	elog(ERROR, "MultiByte strings (MB) must be enabled to use this function");
	return ("");
}

#endif

#ifdef CYR_RECODE
#define MAX_TOKEN	80

/* Some standard C libraries, including GNU, have an isblank() function.
   Others, including Solaris, do not.  So we have our own.
*/
static bool
isblank(const char c)
{
	return c == ' ' || c == 9 /* tab */ ;
}

static void
next_token(FILE *fp, char *buf, const int bufsz)
{
/*--------------------------------------------------------------------------
  Grab one token out of fp.  Tokens are strings of non-blank
  characters bounded by blank characters, beginning of line, and end
  of line.	Blank means space or tab.  Return the token as *buf.
  Leave file positioned to character immediately after the token or
  EOF, whichever comes first.  If no more tokens on line, return null
  string as *buf and position file to beginning of next line or EOF,
  whichever comes first.
--------------------------------------------------------------------------*/
	int			c;
	char	   *eb = buf + (bufsz - 1);

	/* Move over inital token-delimiting blanks */
	while (isblank(c = getc(fp)));

	if (c != '\n')
	{

		/*
		 * build a token in buf of next characters up to EOF, eol, or
		 * blank.
		 */
		while (c != EOF && c != '\n' && !isblank(c))
		{
			if (buf < eb)
				*buf++ = c;
			c = getc(fp);

			/*
			 * Put back the char right after the token (putting back EOF
			 * is ok)
			 */
		}
		ungetc(c, fp);
	}
	*buf = '\0';
}

static void
read_through_eol(FILE *file)
{
	int			c;

	do
		c = getc(file);
	while (c != '\n' && c != EOF);
}

void
SetCharSet()
{
	FILE	   *file;
	char	   *p,
				c,
				eof = false;
	char	   *map_file;
	char		buf[MAX_TOKEN];
	int			i;
	unsigned char FromChar,
				ToChar;

	for (i = 0; i < 128; i++)
	{
		RecodeForwTable[i] = i + 128;
		RecodeBackTable[i] = i + 128;
	}

	p = getenv("PG_RECODETABLE");
	if (p && *p != '\0')
	{
		map_file = (char *) malloc((strlen(DataDir) +
									strlen(p) + 2) * sizeof(char));
		sprintf(map_file, "%s/%s", DataDir, p);
#ifndef __CYGWIN32__
		file = AllocateFile(map_file, "r");
#else
		file = AllocateFile(map_file, "rb");
#endif
		if (file == NULL)
			return;
		eof = false;
		while (!eof)
		{
			c = getc(file);
			ungetc(c, file);
			if (c == EOF)
				eof = true;
			else
			{
				if (c == '#')
					read_through_eol(file);
				else
				{
					/* Read the FromChar */
					next_token(file, buf, sizeof(buf));
					if (buf[0] != '\0')
					{
						FromChar = strtoul(buf, 0, 0);
						/* Read the ToChar */
						next_token(file, buf, sizeof(buf));
						if (buf[0] != '\0')
						{
							ToChar = strtoul(buf, 0, 0);
							RecodeForwTable[FromChar - 128] = ToChar;
							RecodeBackTable[ToChar - 128] = FromChar;
						}
						read_through_eol(file);
					}
				}
			}
		}
		FreeFile(file);
		free(map_file);
	}
}

char *
convertstr(unsigned char *buff, int len, int dest)
{
	int			i;
	char	   *ch = buff;

	for (i = 0; i < len; i++, buff++)
	{
		if (*buff > 127)
			if (dest)
				*buff = RecodeForwTable[*buff - 128];
			else
				*buff = RecodeBackTable[*buff - 128];
	}
	return ch;
}

#endif

/* ----------------
 *		GetPgUserName and SetPgUserName
 *
 *		SetPgUserName must be called before InitPostgres, since the setuid()
 *		is done there.
 *
 *		Replace GetPgUserName() with a lower-case version
 *		to allow use in new case-insensitive SQL (referenced
 *		in pg_proc.h). Define GetPgUserName() as a macro - tgl 97/04/26
 * ----------------
 */
char *
getpgusername()
{
	return UserName;
}

void
SetPgUserName()
{
#ifndef NO_SECURITY
	char	   *p;
	struct passwd *pw;

	if (IsUnderPostmaster)
	{
		/* use the (possibly) authenticated name that's provided */
		if (!(p = getenv("PG_USER")))
			elog(FATAL, "SetPgUserName: PG_USER environment variable is unset");
	}
	else
	{
		/* setuid() has not yet been done, see above comment */
		if (!(pw = getpwuid(geteuid())))
			elog(FATAL, "SetPgUserName: no entry in host passwd file");
		p = pw->pw_name;
	}
	if (UserName)
		free(UserName);
	UserName = malloc(strlen(p) + 1);
	strcpy(UserName, p);
#endif	 /* NO_SECURITY */
}

/* ----------------------------------------------------------------
 *		GetUserId and SetUserId
 * ----------------------------------------------------------------
 */
static Oid	UserId = InvalidOid;

int
GetUserId()
{
	Assert(OidIsValid(UserId));
	return UserId;
}

void
SetUserId()
{
	HeapTuple	userTup;
	char	   *userName;

	Assert(!OidIsValid(UserId));/* only once */

	/*
	 * Don't do scans if we're bootstrapping, none of the system catalogs
	 * exist yet, and they should be owned by postgres anyway.
	 */
	if (IsBootstrapProcessingMode())
	{
		UserId = geteuid();
		return;
	}

	userName = GetPgUserName();
	userTup = SearchSysCacheTuple(SHADOWNAME,
								  PointerGetDatum(userName),
								  0, 0, 0);
	if (!HeapTupleIsValid(userTup))
		elog(FATAL, "SetUserId: user '%s' is not in '%s'",
			 userName,
			 ShadowRelationName);
	UserId = (Oid) ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
}

/*-------------------------------------------------------------------------
 *
 * posmaster pid file stuffs. $DATADIR/postmaster.pid is created when:
 *
 *	(1) postmaster starts. In this case pid > 0.
 *	(2) postgres starts in standalone mode. In this case
 *	    pid < 0
 *
 * to gain an interlock.
 * 
 *	SetPidFname(datadir)
 *		Remember the the pid file name. This is neccesary
 *		UnlinkPidFile() is called from proc_exit().
 *
 *	GetPidFname(datadir)
 *		Get the pid file name. SetPidFname() should be called
 *		before GetPidFname() gets called.
 *
 *	UnlinkPidFile()
 *		This is called from proc_exit() and unlink the pid file.
 *
 *	SetPidFile(pid_t pid)
 *		Create the pid file. On failure, it checks if the process
 *		actually exists or not. SetPidFname() should be called
 *		in prior to calling SetPidFile().
 *
 *-------------------------------------------------------------------------
 */

/*
 * Path to pid file. proc_exit() remember it to unlink the file.
 */
static char PidFile[MAXPGPATH];

/*
 * Remove the pid file. This function is called from proc_exit.
 */
void UnlinkPidFile(void)
{
	unlink(PidFile);
}

/*
 * Set path to the pid file
 */
void SetPidFname(char * datadir)
{
	snprintf(PidFile, sizeof(PidFile), "%s/%s", datadir, PIDFNAME);
}

/*
 * Get path to the pid file
 */
char *GetPidFname(void)
{
	return(PidFile);
}

/*
 * Create the pid file
 */
int SetPidFile(pid_t pid)
{
	int fd;
	char *pidfile;
	char pidstr[32];
	int len;
	pid_t post_pid;
	int is_postgres = 0;

	/*
	 * Creating pid file
	 */
	pidfile = GetPidFname();
	fd = open(pidfile, O_RDWR | O_CREAT | O_EXCL, 0600);
	if (fd < 0) {
		/*
		 * Couldn't create the pid file. Probably
		 * it already exists. Read the file to see if the process
		 * actually exists
		 */
		fd = open(pidfile, O_RDONLY, 0600);
		if (fd < 0) {
			fprintf(stderr, "Can't open pid file: %s\n", pidfile);
			fprintf(stderr, "Please check the permission and try again.\n");
			return(-1);
		}
		if ((len = read(fd, pidstr, sizeof(pidstr)-1)) < 0) {
			fprintf(stderr, "Can't read pid file: %s\n", pidfile);
			fprintf(stderr, "Please check the permission and try again.\n");
			close(fd);
			return(-1);
		}
		close(fd);

		/*
		 * Check to see if the process actually exists
		 */
		pidstr[len] = '\0';
		post_pid = (pid_t)atoi(pidstr);

		/* if pid < 0, the pid is for postgres, not postmatser */
		if (post_pid < 0) {
			is_postgres++;
			post_pid = -post_pid;
		}

		if (post_pid == 0 || (post_pid > 0 && kill(post_pid, 0) < 0)) {
			/*
			 * No, the process did not exist. Unlink
			 * the file and try to create it
			 */
			if (unlink(pidfile) < 0) {
				fprintf(stderr, "Can't remove pid file: %s\n", pidfile);
				fprintf(stderr, "The file seems accidently left, but I couldn't remove it.\n");
				fprintf(stderr, "Please remove the file by hand and try again.\n");
				return(-1);
			}
			fd = open(pidfile, O_RDWR | O_CREAT | O_EXCL, 0600);
			if (fd < 0) {
				fprintf(stderr, "Can't create pid file: %s\n", pidfile);
				fprintf(stderr, "Please check the permission and try again.\n");
				return(-1);
			}
		} else {
			/*
			 * Another postmaster is running
			 */
			fprintf(stderr, "Can't create pid file: %s\n", pidfile);
			if (is_postgres) {
			  fprintf(stderr, "Is another postgres (pid: %d) running?\n", post_pid);
			}
			else
			{
			  fprintf(stderr, "Is another postmaster (pid: %s) running?\n", pidstr);
			}
			return(-1);
		}
	}

	sprintf(pidstr, "%d", pid);
	if (write(fd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
		fprintf(stderr,"Write to pid file failed\n");
		fprintf(stderr, "Please check the permission and try again.\n");
		close(fd);
		unlink(pidfile);
		return(-1);
	}
	close(fd);

	return(0);
}
