Add code to find_my_exec() to resolve a symbolic link down to the
authorTom Lane
Sat, 6 Nov 2004 23:06:29 +0000 (23:06 +0000)
committerTom Lane
Sat, 6 Nov 2004 23:06:29 +0000 (23:06 +0000)
actual executable location.  This allows people to continue to use
setups where, eg, postmaster is symlinked from a convenient place.
Per gripe from Josh Berkus.

configure
configure.in
src/include/pg_config.h.in
src/port/exec.c

index 7e0a563444df053f902c68961593c3ff4a9f8a56..b53dc7723083b9db02aff424b558dc0d5e592a3f 100755 (executable)
--- a/configure
+++ b/configure
@@ -11295,7 +11295,8 @@ test $ac_cv_func_memcmp_working = no && LIBOBJS="$LIBOBJS memcmp.$ac_objext"
 
 
 
-for ac_func in cbrt dlopen fcvt fdatasync getpeereid memmove poll pstat setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs
+
+for ac_func in cbrt dlopen fcvt fdatasync getpeereid memmove poll pstat readlink setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs
 do
 as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
 echo "$as_me:$LINENO: checking for $ac_func" >&5
index 14af5ae0279a2d878c6882b5cb7a3494571ae152..6eb4e120b027e2f605df6d65f933f0b755d7437b 100644 (file)
@@ -1,5 +1,5 @@
 dnl Process this file with autoconf to produce a configure script.
-dnl $PostgreSQL: pgsql/configure.in,v 1.384 2004/11/02 05:44:45 momjian Exp $
+dnl $PostgreSQL: pgsql/configure.in,v 1.385 2004/11/06 23:06:15 tgl Exp $
 dnl
 dnl Developers, please strive to achieve this order:
 dnl
@@ -826,7 +826,7 @@ PGAC_FUNC_GETTIMEOFDAY_1ARG
 # SunOS doesn't handle negative byte comparisons properly with +/- return
 AC_FUNC_MEMCMP
 
-AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid memmove poll pstat setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs])
+AC_CHECK_FUNCS([cbrt dlopen fcvt fdatasync getpeereid memmove poll pstat readlink setproctitle setsid sigprocmask symlink sysconf towlower utime utimes waitpid wcstombs])
 
 AC_CHECK_DECLS(fdatasync, [], [], [#include ])
 
index 600f4229f0a1c1dd239586db9217a9d10ae41265..279b6b539c8879c84d2c94755d53016b4541ae60 100644 (file)
 /* Define to 1 if you have the  header file. */
 #undef HAVE_READLINE_READLINE_H
 
+/* Define to 1 if you have the `readlink' function. */
+#undef HAVE_READLINK
+
 /* Define to 1 if you have the `replace_history_entry' function. */
 #undef HAVE_REPLACE_HISTORY_ENTRY
 
index 839bc73f00514859f02f13dcc9725a0e839fc64f..28b25c4b7f99ac99ec5a3b1066f601ed6c573bce 100644 (file)
@@ -1,13 +1,15 @@
 /*-------------------------------------------------------------------------
  *
  * exec.c
+ *     Functions for finding and validating executable files
+ *
  *
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/port/exec.c,v 1.31 2004/11/06 01:16:22 tgl Exp $
+ *   $PostgreSQL: pgsql/src/port/exec.c,v 1.32 2004/11/06 23:06:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #define log_error(str, param)  (fprintf(stderr, str, param), fputc('\n', stderr))
 #endif
 
+#ifdef WIN32_CLIENT_ONLY
+#define getcwd(cwd,len)  GetCurrentDirectory(len, cwd)
+#endif
+
+static int validate_exec(const char *path);
+static int resolve_symlinks(char *path);
+static char *pipe_read_line(char *cmd, char *line, int maxsize);
+
 
 /*
  * validate_exec -- validate "path" as an executable file
@@ -154,13 +164,20 @@ validate_exec(const char *path)
 #endif
 }
 
+
 /*
  * find_my_exec -- find an absolute path to a valid executable
  *
+ * argv0 is the name passed on the command line
+ * retpath is the output area (must be of size MAXPGPATH)
+ * Returns 0 if OK, -1 if error.
+ *
  * The reason we have to work so hard to find an absolute path is that
  * on some platforms we can't do dynamic loading unless we know the
  * executable's location.  Also, we need a full path not a relative
- * path because we will later change working directory.
+ * path because we will later change working directory.  Finally, we want
+ * a true path not a symlink location, so that we can locate other files
+ * that are part of our installation relative to the executable.
  *
  * This function is not thread-safe because it calls validate_exec(),
  * which calls getgrgid(). This function should be used only in
@@ -173,13 +190,12 @@ find_my_exec(const char *argv0, char *retpath)
                test_path[MAXPGPATH];
    char       *path;
 
-#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
+   {
+       log_error(_("could not identify current directory: %s"),
+                 strerror(errno));
+       return -1;
+   }
 
    /*
     * If argv0 contains a separator, then PATH wasn't used.
@@ -193,7 +209,7 @@ find_my_exec(const char *argv0, char *retpath)
        canonicalize_path(retpath);
 
        if (validate_exec(retpath) == 0)
-           return 0;
+           return resolve_symlinks(retpath);
 
        log_error("invalid binary \"%s\"", retpath);
        return -1;
@@ -203,7 +219,7 @@ find_my_exec(const char *argv0, char *retpath)
    /* Win32 checks the current directory first for names without slashes */
    join_path_components(retpath, cwd, argv0);
    if (validate_exec(retpath) == 0)
-       return 0;
+       return resolve_symlinks(retpath);
 #endif
 
    /*
@@ -240,7 +256,7 @@ find_my_exec(const char *argv0, char *retpath)
            switch (validate_exec(retpath))
            {
                case 0:         /* found ok */
-                   return 0;
+                   return resolve_symlinks(retpath);
                case -1:        /* wasn't even a candidate, keep looking */
                    break;
                case -2:        /* found but disqualified */
@@ -254,6 +270,141 @@ find_my_exec(const char *argv0, char *retpath)
    return -1;
 }
 
+
+/*
+ * resolve_symlinks - resolve symlinks to the underlying file
+ *
+ * If path does not point to a symlink, leave it alone.  If it does,
+ * replace it by the absolute path to the referenced file.
+ *
+ * Returns 0 if OK, -1 if error.
+ *
+ * Note: we are not particularly tense about producing nice error messages
+ * because we are not really expecting error here; we just determined that
+ * the symlink does point to a valid executable.
+ */
+static int
+resolve_symlinks(char *path)
+{
+#ifdef HAVE_READLINK
+   struct stat buf;
+   char        orig_wd[MAXPGPATH],
+               link_buf[MAXPGPATH];
+   char       *fname;
+
+   /* Quick out if it's not a symlink */
+   if (lstat(path, &buf) < 0 ||
+       (buf.st_mode & S_IFMT) != S_IFLNK)
+       return 0;
+
+   /*
+    * To resolve a symlink properly, we have to chdir into its directory
+    * and then chdir to where the symlink points; otherwise we may fail to
+    * resolve relative links correctly (consider cases involving mount
+    * points, for example).  After following the final symlink, we use
+    * getcwd() to figure out where the heck we're at.
+    */
+   if (!getcwd(orig_wd, MAXPGPATH))
+   {
+       log_error(_("could not identify current directory: %s"),
+                 strerror(errno));
+       return -1;
+   }
+
+   for (;;)
+   {
+       char   *lsep;
+       int     rllen;
+
+       lsep = last_dir_separator(path);
+       if (lsep)
+       {
+           *lsep = '\0';
+           if (chdir(path) == -1)
+           {
+               log_error(_("could not change directory to \"%s\""), path);
+               return -1;
+           }
+           fname = lsep + 1;
+       }
+       else
+           fname = path;
+
+       if (lstat(fname, &buf) < 0 ||
+           (buf.st_mode & S_IFMT) != S_IFLNK)
+           break;
+
+       rllen = readlink(fname, link_buf, sizeof(link_buf));
+       if (rllen < 0 || rllen >= sizeof(link_buf))
+       {
+           log_error(_("could not read symbolic link \"%s\""), fname);
+           return -1;
+       }
+       link_buf[rllen] = '\0';
+       strcpy(path, link_buf);
+   }
+
+   /* must copy final component out of 'path' temporarily */
+   strcpy(link_buf, fname);
+
+   if (!getcwd(path, MAXPGPATH))
+   {
+       log_error(_("could not identify current directory: %s"),
+                 strerror(errno));
+       return -1;
+   }
+   join_path_components(path, path, link_buf);
+   canonicalize_path(path);
+
+   if (chdir(orig_wd) == -1)
+   {
+       log_error(_("could not change directory to \"%s\""), orig_wd);
+       return -1;
+   }
+
+#endif /* HAVE_READLINK */
+
+   return 0;
+}
+
+
+/*
+ * 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,
+               const char *versionstr, char *retpath)
+{
+   char        cmd[MAXPGPATH];
+   char        line[100];
+
+   if (find_my_exec(argv0, retpath) < 0)
+       return -1;
+
+   /* Trim off program name and keep just directory */
+   *last_dir_separator(retpath) = '\0';
+   canonicalize_path(retpath);
+
+   /* Now append the other program's name */
+   snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
+            "/%s%s", target, EXE);
+
+   if (validate_exec(retpath) != 0)
+       return -1;
+
+   snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);
+
+   if (!pipe_read_line(cmd, line, sizeof(line)))
+       return -1;
+
+   if (strcmp(line, versionstr) != 0)
+       return -2;
+
+   return 0;
+}
+
+
 /*
  * The runtime library's popen() on win32 does not work when being
  * called from a service when running on windows <= 2000, because
@@ -262,7 +413,6 @@ find_my_exec(const char *argv0, char *retpath)
  * Executing a command in a pipe and reading the first line from it
  * is all we need.
  */
-
 static char *
 pipe_read_line(char *cmd, char *line, int maxsize)
 {
@@ -286,8 +436,9 @@ pipe_read_line(char *cmd, char *line, int maxsize)
        return NULL;
 
    return line;
-#else
-   /* Win32 */
+
+#else /* WIN32 */
+
    SECURITY_ATTRIBUTES sattr;
    HANDLE      childstdoutrd,
                childstdoutwr,
@@ -392,44 +543,7 @@ pipe_read_line(char *cmd, char *line, int maxsize)
    CloseHandle(childstdoutrddup);
 
    return retval;
-#endif
-}
-
-
-/*
- * 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,
-               const char *versionstr, char *retpath)
-{
-   char        cmd[MAXPGPATH];
-   char        line[100];
-
-   if (find_my_exec(argv0, retpath) < 0)
-       return -1;
-
-   /* Trim off program name and keep just directory */
-   *last_dir_separator(retpath) = '\0';
-   canonicalize_path(retpath);
-
-   /* Now append the other program's name */
-   snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
-            "/%s%s", target, EXE);
-
-   if (validate_exec(retpath))
-       return -1;
-
-   snprintf(cmd, sizeof(cmd), "\"%s\" -V 2>%s", retpath, DEVNULL);
-
-   if (!pipe_read_line(cmd, line, sizeof(line)))
-       return -1;
-
-   if (strcmp(line, versionstr) != 0)
-       return -2;
-
-   return 0;
+#endif /* WIN32 */
 }
 
 
@@ -454,20 +568,14 @@ pclose_check(FILE *stream)
        perror("pclose failed");
    }
    else if (WIFEXITED(exitstatus))
-   {
        log_error(_("child process exited with exit code %d"),
                  WEXITSTATUS(exitstatus));
-   }
    else if (WIFSIGNALED(exitstatus))
-   {
        log_error(_("child process was terminated by signal %d"),
                  WTERMSIG(exitstatus));
-   }
    else
-   {
        log_error(_("child process exited with unrecognized status %d"),
                  exitstatus);
-   }
 
    return -1;
 }