From 4867afef7a6493161e666df55d6b53f102ea5780 Mon Sep 17 00:00:00 2001 From: Tom Lane 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 e6bde3de6e..c5dfee9977 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 /* 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 8d32754c8e..839bc73f00 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 5d94227106..1c630043ab 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); + char *p; -#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 - - /* 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';