From 4867afef7a6493161e666df55d6b53f102ea5780 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 6 Nov 2004 01:16:22 +0000
Subject: [PATCH] Code cleanup in path.c and exec.c.  Handle Windows drive and
 network specs everywhere not just some places, get rid of . and .. when
 joining path sections together.  This should eliminate most of the ugly paths
 like /foo/bar/./baz that we've been generating.

---
 src/include/port.h |  38 ++++-----
 src/port/exec.c    | 118 ++++++++--------------------
 src/port/path.c    | 186 +++++++++++++++++++++++++++++++--------------
 3 files changed, 176 insertions(+), 166 deletions(-)

diff --git a/src/include/port.h b/src/include/port.h
index e6bde3de6e0..c5dfee99779 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * port.h
- *	  Header for /port compatibility functions.
+ *	  Header for src/port/ compatibility functions.
  *
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/port.h,v 1.64 2004/10/11 22:50:33 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/port.h,v 1.65 2004/11/06 01:16:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,25 +20,15 @@
 #include <ctype.h>
 
 /* non-blocking */
-bool		set_noblock(int sock);
+extern bool set_noblock(int sock);
 
 /* Portable path handling for Unix/Win32 */
 
-/* Find the location of the first directory separator, return
- * NULL if not found.
- */
 extern char *first_dir_separator(const char *filename);
-
-/* Find the location of the last directory separator, return
- * NULL if not found.
- */
 extern char *last_dir_separator(const char *filename);
-
-/* Find the location of the first path separator (i.e. ':' on
- * Unix, ';' on Windows), return NULL if not found.
- */
-extern char *first_path_separator(const char *filename);
-
+extern char *first_path_separator(const char *pathlist);
+extern void join_path_components(char *ret_path,
+								 const char *head, const char *tail);
 extern void canonicalize_path(char *path);
 extern void make_native_path(char *path);
 extern const char *get_progname(const char *argv0);
@@ -123,11 +113,6 @@ extern unsigned char pg_tolower(unsigned char ch);
 /* Portable prompt handling */
 extern char *simple_prompt(const char *prompt, int maxlen, bool echo);
 
-#if defined(bsdi) || defined(netbsd)
-extern int	fseeko(FILE *stream, off_t offset, int whence);
-extern off_t ftello(FILE *stream);
-#endif
-
 /*
  *	WIN32 doesn't allow descriptors returned by pipe() to be used in select(),
  *	so for that platform we use socket() instead of pipe().
@@ -185,7 +170,7 @@ extern int	pgsymlink(const char *oldpath, const char *newpath);
 #define symlink(oldpath, newpath)	pgsymlink(oldpath, newpath)
 #endif
 
-#endif
+#endif /* defined(WIN32) || defined(__CYGWIN__) */
 
 extern bool rmtree(char *path, bool rmtopdir);
 
@@ -212,14 +197,14 @@ extern void srand48(long seed);
 /* Last parameter not used */
 extern int	gettimeofday(struct timeval * tp, struct timezone * tzp);
 
-#else
+#else /* !WIN32 */
 
 /*
  *	Win32 requires a special close for sockets and pipes, while on Unix
  *	close() does them all.
  */
 #define closesocket close
-#endif
+#endif /* WIN32 */
 
 /*
  * Default "extern" declarations or macro substitutes for library routines.
@@ -229,6 +214,11 @@ extern int	gettimeofday(struct timeval * tp, struct timezone * tzp);
 extern char *crypt(const char *key, const char *setting);
 #endif
 
+#if defined(bsdi) || defined(netbsd)
+extern int	fseeko(FILE *stream, off_t offset, int whence);
+extern off_t ftello(FILE *stream);
+#endif
+
 #ifndef HAVE_FSEEKO
 #define fseeko(a, b, c) fseek((a), (b), (c))
 #define ftello(a) ftell((a))
diff --git a/src/port/exec.c b/src/port/exec.c
index 8d32754c8e7..839bc73f005 100644
--- a/src/port/exec.c
+++ b/src/port/exec.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/port/exec.c,v 1.30 2004/10/18 19:08:58 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/port/exec.c,v 1.31 2004/11/06 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,15 +42,12 @@
 
 #ifndef FRONTEND
 /* We use only 3-parameter elog calls in this file, for simplicity */
-#define log_error(str, param)	elog(LOG, (str), (param))
+#define log_error(str, param)	elog(LOG, str, param)
 #else
-#define log_error(str, param)	fprintf(stderr, (str), (param))
+#define log_error(str, param)	(fprintf(stderr, str, param), fputc('\n', stderr))
 #endif
 
 
-static void win32_make_absolute(char *path);
-
-
 /*
  * validate_exec -- validate "path" as an executable file
  *
@@ -165,7 +162,7 @@ validate_exec(const char *path)
  * executable's location.  Also, we need a full path not a relative
  * path because we will later change working directory.
  *
- * This function is not thread-safe because of it calls validate_exec(),
+ * This function is not thread-safe because it calls validate_exec(),
  * which calls getgrgid().	This function should be used only in
  * non-threaded binaries, not in library routines.
  */
@@ -178,61 +175,40 @@ find_my_exec(const char *argv0, char *retpath)
 
 #ifndef WIN32_CLIENT_ONLY
 	if (!getcwd(cwd, MAXPGPATH))
+		strcpy(cwd, ".");		/* cheesy, but better than nothing */
 #else
 	if (!GetCurrentDirectory(MAXPGPATH, cwd))
+		strcpy(cwd, ".");		/* cheesy, but better than nothing */
 #endif
-		cwd[0] = '\0';
 
 	/*
-	 * First try: use the binary that's located in the same directory if
-	 * it was invoked with an explicit path. Presumably the user used an
-	 * explicit path because it wasn't in PATH, and we don't want to use
-	 * incompatible executables.
-	 *
-	 * For the binary: First try: if we're given some kind of path, use it
-	 * (making sure that a relative path is made absolute before returning
-	 * it).
+	 * If argv0 contains a separator, then PATH wasn't used.
 	 */
-	/* Does argv0 have a separator? */
-	if ((path = last_dir_separator(argv0)))
+	if (first_dir_separator(argv0) != NULL)
 	{
-		if (*++path == '\0')
-		{
-			log_error("argv[0] ends with a path separator \"%s\"", argv0);
-			return -1;
-		}
-
 		if (is_absolute_path(argv0))
 			StrNCpy(retpath, argv0, MAXPGPATH);
 		else
-			snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
-
+			join_path_components(retpath, cwd, argv0);
 		canonicalize_path(retpath);
+
 		if (validate_exec(retpath) == 0)
-		{
-			win32_make_absolute(retpath);
 			return 0;
-		}
-		else
-		{
-			log_error("invalid binary \"%s\"", retpath);
-			return -1;
-		}
+
+		log_error("invalid binary \"%s\"", retpath);
+		return -1;
 	}
 
 #ifdef WIN32
 	/* Win32 checks the current directory first for names without slashes */
-	if (validate_exec(argv0) == 0)
-	{
-		snprintf(retpath, MAXPGPATH, "%s/%s", cwd, argv0);
-		win32_make_absolute(retpath);
+	join_path_components(retpath, cwd, argv0);
+	if (validate_exec(retpath) == 0)
 		return 0;
-	}
 #endif
 
 	/*
-	 * Second try: since no explicit path was supplied, the user must have
-	 * been relying on PATH.  We'll use the same PATH.
+	 * Since no explicit path was supplied, the user must have
+	 * been relying on PATH.  We'll search the same PATH.
 	 */
 	if ((path = getenv("PATH")) && *path)
 	{
@@ -253,40 +229,33 @@ find_my_exec(const char *argv0, char *retpath)
 			StrNCpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
 
 			if (is_absolute_path(test_path))
-				snprintf(retpath, MAXPGPATH, "%s/%s", test_path, argv0);
+				join_path_components(retpath, test_path, argv0);
 			else
-				snprintf(retpath, MAXPGPATH, "%s/%s/%s", cwd, test_path, argv0);
-
+			{
+				join_path_components(retpath, cwd, test_path);
+				join_path_components(retpath, retpath, argv0);
+			}
 			canonicalize_path(retpath);
+
 			switch (validate_exec(retpath))
 			{
-				case 0: /* found ok */
-					win32_make_absolute(retpath);
+				case 0:			/* found ok */
 					return 0;
 				case -1:		/* wasn't even a candidate, keep looking */
-					continue;
+					break;
 				case -2:		/* found but disqualified */
 					log_error("could not read binary \"%s\"", retpath);
-					continue;
+					break;
 			}
 		} while (*endp);
 	}
 
 	log_error("could not find a \"%s\" to execute", argv0);
 	return -1;
-
-#if NOT_USED
-	/*
-	 * Win32 has a native way to find the executable name, but the above
-	 * method works too.
-	 */
-	if (GetModuleFileName(NULL, retpath, MAXPGPATH) == 0)
-		log_error("GetModuleFileName failed (%i)", (int) GetLastError());
-#endif
 }
 
 /*
- * The runtime librarys popen() on win32 does not work when being
+ * The runtime library's popen() on win32 does not work when being
  * called from a service when running on windows <= 2000, because
  * there is no stdin/stdout/stderr.
  *
@@ -427,10 +396,9 @@ pipe_read_line(char *cmd, char *line, int maxsize)
 }
 
 
-
 /*
- * Find our binary directory, then make sure the "target" executable
- * is the proper version.
+ * Find another program in our binary's directory,
+ * then make sure it is the proper version.
  */
 int
 find_other_exec(const char *argv0, const char *target,
@@ -487,41 +455,19 @@ pclose_check(FILE *stream)
 	}
 	else if (WIFEXITED(exitstatus))
 	{
-		log_error(_("child process exited with exit code %d\n"),
+		log_error(_("child process exited with exit code %d"),
 				  WEXITSTATUS(exitstatus));
 	}
 	else if (WIFSIGNALED(exitstatus))
 	{
-		log_error(_("child process was terminated by signal %d\n"),
+		log_error(_("child process was terminated by signal %d"),
 				  WTERMSIG(exitstatus));
 	}
 	else
 	{
-		log_error(_("child process exited with unrecognized status %d\n"),
+		log_error(_("child process exited with unrecognized status %d"),
 				  exitstatus);
 	}
 
 	return -1;
 }
-
-
-/*
- * Windows doesn't like relative paths to executables (other things work fine)
- * so we call its builtin function to expand them. Elsewhere this is a NOOP
- */
-static void
-win32_make_absolute(char *path)
-{
-#ifdef WIN32
-	char		abspath[MAXPGPATH];
-
-	if (_fullpath(abspath, path, MAXPGPATH) == NULL)
-	{
-		log_error("Win32 path expansion failed: %s", strerror(errno));
-		StrNCpy(abspath, path, MAXPGPATH);
-	}
-	canonicalize_path(abspath);
-
-	StrNCpy(path, abspath, MAXPGPATH);
-#endif
-}
diff --git a/src/port/path.c b/src/port/path.c
index 5d94227106f..1c630043ab6 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/port/path.c,v 1.41 2004/11/02 03:09:06 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/port/path.c,v 1.42 2004/11/06 01:16:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,53 +44,95 @@ static void trim_trailing_separator(char *path);
 		(p)++; \
 }
 
+/*
+ * skip_drive
+ *
+ * On Windows, a path may begin with "C:" or "//network/".  Advance over
+ * this and point to the effective start of the path.
+ */
+#ifdef WIN32
+
+static char *
+skip_drive(const char *path)
+{
+	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 += 2;
+	}
+	return (char *) path;
+}
+
+#else
+
+#define skip_drive(path)	(path)
+
+#endif
+
 /*
  *	first_dir_separator
+ *
+ * Find the location of the first directory separator, return
+ * NULL if not found.
  */
 char *
 first_dir_separator(const char *filename)
 {
-	char	   *p;
+	const char *p;
 
-	for (p = (char *) filename; *p; p++)
+	for (p = skip_drive(filename); *p; p++)
 		if (IS_DIR_SEP(*p))
-			return p;
+			return (char *) p;
 	return NULL;
 }
 
 /*
  *	first_path_separator
+ *
+ * Find the location of the first path separator (i.e. ':' on
+ * Unix, ';' on Windows), return NULL if not found.
  */
 char *
-first_path_separator(const char *filename)
+first_path_separator(const char *pathlist)
 {
-	char	   *p;
+	const char *p;
 
-	for (p = (char *) filename; *p; p++)
+	/* skip_drive is not needed */
+	for (p = pathlist; *p; p++)
 		if (IS_PATH_SEP(*p))
-			return p;
+			return (char *) p;
 	return NULL;
 }
 
 /*
  *	last_dir_separator
+ *
+ * Find the location of the last directory separator, return
+ * NULL if not found.
  */
 char *
 last_dir_separator(const char *filename)
 {
-	char	   *p,
+	const char *p,
 			   *ret = NULL;
 
-	for (p = (char *) filename; *p; p++)
+	for (p = skip_drive(filename); *p; p++)
 		if (IS_DIR_SEP(*p))
 			ret = p;
-	return ret;
+	return (char *) ret;
 }
 
 
 /*
  *	make_native_path - on WIN32, change / to \ in the path
  *
+ *	This effectively undoes canonicalize_path.
+ *
  *	This is required because WIN32 COPY is an internal CMD.EXE
  *	command and doesn't process forward slashes in the same way
  *	as external commands.  Quoting the first argument to COPY
@@ -114,11 +156,59 @@ make_native_path(char *filename)
 }
 
 
+/*
+ * join_path_components - join two path components, inserting a slash
+ *
+ * ret_path is the output area (must be of size MAXPGPATH)
+ *
+ * ret_path can be the same as head, but not the same as tail.
+ */
+void
+join_path_components(char *ret_path,
+					 const char *head, const char *tail)
+{
+	if (ret_path != head)
+		StrNCpy(ret_path, head, MAXPGPATH);
+	/*
+	 * Remove any leading "." and ".." in the tail component,
+	 * adjusting head as needed.
+	 */
+	for (;;)
+	{
+		if (tail[0] == '.' && IS_DIR_SEP(tail[1]))
+		{
+			tail += 2;
+		}
+		else if (tail[0] == '.' && tail[1] == '\0')
+		{
+			tail += 1;
+			break;
+		}
+		else if (tail[0] == '.' && tail[1] == '.' && IS_DIR_SEP(tail[2]))
+		{
+			trim_directory(ret_path);
+			tail += 3;
+		}
+		else if (tail[0] == '.' && tail[1] == '.' && tail[2] == '\0')
+		{
+			trim_directory(ret_path);
+			tail += 2;
+			break;
+		}
+		else
+			break;
+	}
+	if (*tail)
+		snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path),
+				 "/%s", tail);
+}
+
+
 /*
  *	Clean up path by:
  *		o  make Win32 path use Unix slashes
- *		o  remove trailling quote on Win32
- *		o  remove trailling slash
+ *		o  remove trailing quote on Win32
+ *		o  remove trailing slash
  *		o  remove trailing '.'
  *		o  process trailing '..' ourselves
  */
@@ -165,13 +255,11 @@ canonicalize_path(char *path)
 		if (len >= 2 && strcmp(path + len - 2, "/.") == 0)
 		{
 			trim_directory(path);
-			trim_trailing_separator(path);
 		}
 		else if (len >= 3 && strcmp(path + len - 3, "/..") == 0)
 		{
 			trim_directory(path);
 			trim_directory(path);	/* remove directory above */
-			trim_trailing_separator(path);
 		}
 		else
 			break;
@@ -188,10 +276,11 @@ get_progname(const char *argv0)
 {
 	const char *nodir_name;
 
-	if (!last_dir_separator(argv0))
-		nodir_name = argv0;
+	nodir_name = last_dir_separator(argv0);
+	if (nodir_name)
+		nodir_name++;
 	else
-		nodir_name = last_dir_separator(argv0) + 1;
+		nodir_name = skip_drive(argv0);
 
 #if defined(__CYGWIN__) || defined(WIN32)
 	/* strip .exe suffix, regardless of case */
@@ -231,7 +320,6 @@ get_share_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_etc_path
  */
@@ -248,7 +336,6 @@ get_etc_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_include_path
  */
@@ -265,7 +352,6 @@ get_include_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_pkginclude_path
  */
@@ -282,7 +368,6 @@ get_pkginclude_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_includeserver_path
  */
@@ -299,7 +384,6 @@ get_includeserver_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_lib_path
  */
@@ -316,7 +400,6 @@ get_lib_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_pkglib_path
  */
@@ -333,7 +416,6 @@ get_pkglib_path(const char *my_exec_path, char *ret_path)
 }
 
 
-
 /*
  *	get_locale_path
  *
@@ -382,7 +464,6 @@ void
 get_parent_directory(char *path)
 {
 	trim_directory(path);
-	trim_trailing_separator(path);
 }
 
 
@@ -436,6 +517,8 @@ set_pglocale_pgservice(const char *argv0, const char *app)
 
 /*
  *	make_relative - adjust path to be relative to bin/
+ *
+ * ret_path is the output area (must be of size MAXPGPATH)
  */
 static void
 make_relative(const char *my_exec_path, const char *p, char *ret_path)
@@ -443,9 +526,9 @@ 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);
+	trim_directory(path);		/* remove my executable name */
+	trim_directory(path);		/* remove last directory component (/bin) */
+	join_path_components(ret_path, path, p);
 }
 
 
@@ -520,57 +603,48 @@ relative_path(const char *bin_path, const char *other_path)
 /*
  *	trim_directory
  *
- *	Trim trailing directory from path
+ *	Trim trailing directory from path, that is, remove any trailing slashes,
+ *	the last pathname component, and the slash just ahead of it --- but never
+ *	remove a leading slash.
  */
 static void
 trim_directory(char *path)
 {
 	char	   *p;
 
+	path = skip_drive(path);
+
 	if (path[0] == '\0')
 		return;
 
+	/* back up over trailing slash(es) */
 	for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
 		;
+	/* back up over directory name */
 	for (; !IS_DIR_SEP(*p) && p > path; p--)
 		;
+	/* if multiple slashes before directory name, remove 'em all */
+	for (; p > path && IS_DIR_SEP(*(p - 1)); p--)
+		;
+	/* don't erase a leading slash */
+	if (p == path && IS_DIR_SEP(*p))
+		p++;
 	*p = '\0';
 }
 
 
-
 /*
  *	trim_trailing_separator
+ *
+ * trim off trailing slashes, but not a leading slash
  */
 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 we must 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
+	char	   *p;
 
-	/* trim off trailing slashes */
+	path = skip_drive(path);
+	p = path + strlen(path);
 	if (p > path)
 		for (p--; p > path && IS_DIR_SEP(*p); p--)
 			*p = '\0';
-- 
GitLab