/*-------------------------------------------------------------------------
 *
 * path.c
 *	  portable path handling routines
 *
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $PostgreSQL: pgsql/src/port/path.c,v 1.25 2004/07/12 19:27:31 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "c.h"

#include <ctype.h>

#include "pg_config_paths.h"


#ifndef WIN32
#define	IS_DIR_SEP(ch)	((ch) == '/')
#else
#define	IS_DIR_SEP(ch)	((ch) == '/' || (ch) == '\\')
#endif

#ifndef WIN32
#define	IS_PATH_SEP(ch)	((ch) == ':')
#else
#define	IS_PATH_SEP(ch)	((ch) == ';')
#endif

const static char *relative_path(const char *bin_path, const char *other_path);
static void make_relative(const char *my_exec_path, const char *p, char *ret_path);
static void trim_directory(char *path);
static void trim_trailing_separator(char *path);

/* Move to last of consecutive separators or to null byte */
#define MOVE_TO_SEP_END(p) \
{ \
	while (IS_DIR_SEP((p)[0]) && (IS_DIR_SEP((p)[1]) || !(p)[1])) \
		(p)++; \
}

/*
 *	first_dir_separator
 */
char *
first_dir_separator(const char *filename)
{
	char	   *p;

	for (p = (char *)filename; *p; p++)
		if (IS_DIR_SEP(*p))
			return p;
	return NULL;
}

/*
 *	first_path_separator
 */
char *
first_path_separator(const char *filename)
{
	char	   *p;

	for (p = (char *)filename; *p; p++)
		if (IS_PATH_SEP(*p))
			return p;
	return NULL;
}

/*
 *	last_dir_separator
 */
char *
last_dir_separator(const char *filename)
{
	char	   *p, *ret = NULL;

	for (p = (char *)filename; *p; p++)
		if (IS_DIR_SEP(*p))
			ret = p;
	return ret;
}


/*
 * Make all paths look like Unix
 */
void
canonicalize_path(char *path)
{
#ifdef WIN32
	/*
	 * The Windows command processor will accept suitably quoted paths
	 * with forward slashes, but barfs badly with mixed forward and back
	 * slashes.
	 */
	char	   *p;

	for (p = path; *p; p++)
	{
		if (*p == '\\')
			*p = '/';
	}

	/*	In Win32, if you do:
	 *		prog.exe "a b" "\c\d\"
	 *	the system will pass \c\d" as argv[2].
	 */
	if (p > path && *(p-1) == '"')
		*(p-1) = '/';
#endif

	/*
	 *	Removing the trailing slash on a path means we never get
	 *	ugly double slashes.  Don't remove a leading slash, though.
	 *	Also, Win32 can't stat() a directory with a trailing slash.
	 */
	trim_trailing_separator(path);
}


/*
 * Extracts the actual name of the program as called.
 */
const char *
get_progname(const char *argv0)
{
	if (!last_dir_separator(argv0))
		return argv0;
	else
		return last_dir_separator(argv0) + 1;
}


/*
 *	get_share_path
 */
void
get_share_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, PGSHAREDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, PGSHAREDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	get_etc_path
 */
void
get_etc_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, SYSCONFDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, SYSCONFDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	get_include_path
 */
void
get_include_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, INCLUDEDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, INCLUDEDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	get_pkginclude_path
 */
void
get_pkginclude_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, PKGINCLUDEDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, PKGINCLUDEDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	get_pkglib_path
 *
 *	Return library path, either relative to /bin or hardcoded
 */
void
get_pkglib_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, PKGLIBDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, PKGLIBDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	get_locale_path
 *
 *	Return locale path, either relative to /bin or hardcoded
 */
void
get_locale_path(const char *my_exec_path, char *ret_path)
{
	const char *p;
	
	if ((p = relative_path(PGBINDIR, LOCALEDIR)))
		make_relative(my_exec_path, p, ret_path);
	else
		StrNCpy(ret_path, LOCALEDIR, MAXPGPATH);
	canonicalize_path(ret_path);
}



/*
 *	set_pglocale_pgservice
 *
 *	Set application-specific locale and service directory
 *
 *	This function takes an argv[0] rather than a full path.
 */
void
set_pglocale_pgservice(const char *argv0, const char *app)
{
	char path[MAXPGPATH];
	char my_exec_path[MAXPGPATH];
	char env_path[MAXPGPATH + sizeof("PGLOCALEDIR=")]; /* longer than PGSYSCONFDIR */

	/* don't set LC_ALL in the backend */
	if (strcmp(app, "postgres") != 0)
		setlocale(LC_ALL, "");

	if (find_my_exec(argv0, my_exec_path) < 0)
		return;
		
#ifdef ENABLE_NLS
	get_locale_path(my_exec_path, path);
	bindtextdomain(app, path);
	textdomain(app);

	if (getenv("PGLOCALEDIR") == NULL)
	{
		/* set for libpq to use */
		snprintf(env_path, sizeof(env_path), "PGLOCALEDIR=%s", path);
		canonicalize_path(env_path);
		putenv(strdup(env_path));
	}
#endif

	if (getenv("PGSYSCONFDIR") == NULL)
	{
		get_etc_path(my_exec_path, path);
	
		/* set for libpq to use */
		snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
		canonicalize_path(env_path);
		putenv(strdup(env_path));
	}
}


/*
 *	make_relative - adjust path to be relative to bin/
 */
static void
make_relative(const char *my_exec_path, const char *p, char *ret_path)
{
	char path[MAXPGPATH];

	StrNCpy(path, my_exec_path, MAXPGPATH);
	trim_directory(path);
	trim_directory(path);
	snprintf(ret_path, MAXPGPATH, "%s/%s", path, p);
}


/*
 *	relative_path
 *
 *	Do the supplied paths differ only in their last component?
 */
static const char *
relative_path(const char *bin_path, const char *other_path)
{
	const char *other_sep = other_path;
	
#ifdef WIN32
	/* Driver letters match? */
	if (isalpha(*bin_path) && bin_path[1] == ':' &&
		(!isalpha(*other_path) || !other_path[1] == ':'))
		return NULL;
	if ((!isalpha(*bin_path) || !bin_path[1] == ':') &&
		(isalpha(*other_path) && other_path[1] == ':'))
		return NULL;
	if (isalpha(*bin_path) && bin_path[1] == ':' &&
		isalpha(*other_path) && other_path[1] == ':')
	{
		if (toupper(*bin_path) != toupper(*other_path))
			return NULL;
		bin_path += 2;
		other_path += 2;
		other_sep = other_path + 1;		/* past separator */
	}
#endif

	while (1)
	{
		/* Move past adjacent slashes like //, and trailing ones */
		MOVE_TO_SEP_END(bin_path);
		MOVE_TO_SEP_END(other_path);

		/* One of the paths is done? */
		if (!*bin_path || !*other_path)
			break;

		/* Win32 filesystem is case insensitive */
		if ((!IS_DIR_SEP(*bin_path) || !IS_DIR_SEP(*other_path)) &&
#ifndef WIN32
			*bin_path != *other_path
#else
			toupper((unsigned char) *bin_path) != toupper((unsigned char)*other_path)
#endif
			)
			break;

		if (IS_DIR_SEP(*other_path))
			other_sep = other_path + 1;		/* past separator */
			
		bin_path++;
		other_path++;
	}

	/* identical? */
	if (!*bin_path && !*other_path)
		return NULL;

	/* advance past directory name */	
	while (!IS_DIR_SEP(*bin_path) && *bin_path)
		bin_path++;

	MOVE_TO_SEP_END(bin_path);

	/* Is bin done? */
	if (!*bin_path)
		return other_path;
	else
		return NULL;
}


/*
 *	trim_directory
 *
 *	Trim trailing directory from path
 */
static void
trim_directory(char *path)
{
	char *p;
	
	if (path[0] == '\0')
		return;

	for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
		;
	for (; !IS_DIR_SEP(*p) && p > path; p--)
		;
	*p = '\0';
}



/*
 *	trim_trailing_separator
 */
static void
trim_trailing_separator(char *path)
{
	char *p = path + strlen(path);

#ifdef WIN32
	/*
	 *	Skip over network and drive specifiers for win32.
	 *	Set 'path' to point to the last character to keep.
	 */
    if (strlen(path) >= 2)
    {
        if (IS_DIR_SEP(path[0]) && IS_DIR_SEP(path[1]))
        {
        	path += 2;
			while (*path && !IS_DIR_SEP(*path))
				path++;
		}
        else if (isalpha(path[0]) && path[1] == ':')
        {
            path++;
	        if (IS_DIR_SEP(path[1]))
    	    	path++;
    	}
    }
#endif

	/* trim off trailing slashes */
	if (p > path)
		for (p--; p > path && IS_DIR_SEP(*p); p--)
			*p = '\0';
}
