*-------------------------------------------------------------------------
*/
+/*
+ * On macOS, "man realpath" avers:
+ * Defining _DARWIN_C_SOURCE or _DARWIN_BETTER_REALPATH before including
+ * stdlib.h will cause the provided implementation of realpath() to use
+ * F_GETPATH from fcntl(2) to discover the path.
+ * This should be harmless everywhere else.
+ */
+#define _DARWIN_BETTER_REALPATH
+
#ifndef FRONTEND
#include "postgres.h"
#else
(fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
#endif
-#ifdef _MSC_VER
-#define getcwd(cwd,len) GetCurrentDirectory(len, cwd)
-#endif
-
-static int resolve_symlinks(char *path);
+static int normalize_exec_path(char *path);
+static char *pg_realpath(const char *fname);
#ifdef WIN32
static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
char path_exe[MAXPGPATH + sizeof(".exe") - 1];
/* Win32 requires a .exe suffix for stat() */
- if (strlen(path) >= strlen(".exe") &&
+ if (strlen(path) < strlen(".exe") ||
pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
{
strlcpy(path_exe, path, sizeof(path_exe) - 4);
/*
- * find_my_exec -- find an absolute path to a valid executable
+ * find_my_exec -- find an absolute path to this program's executable
*
* argv0 is the name passed on the command line
* retpath is the output area (must be of size MAXPGPATH)
*
* 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. Finally, we want
+ * executable's location. Also, we need an absolute path not a relative
+ * path because we may 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.
*/
int
find_my_exec(const char *argv0, char *retpath)
{
- char cwd[MAXPGPATH],
- test_path[MAXPGPATH];
char *path;
- if (!getcwd(cwd, MAXPGPATH))
- {
- log_error(errcode_for_file_access(),
- _("could not identify current directory: %m"));
- return -1;
- }
-
/*
* If argv0 contains a separator, then PATH wasn't used.
*/
- if (first_dir_separator(argv0) != NULL)
+ strlcpy(retpath, argv0, MAXPGPATH);
+ if (first_dir_separator(retpath) != NULL)
{
- if (is_absolute_path(argv0))
- strlcpy(retpath, argv0, MAXPGPATH);
- else
- join_path_components(retpath, cwd, argv0);
- canonicalize_path(retpath);
-
if (validate_exec(retpath) == 0)
- return resolve_symlinks(retpath);
+ return normalize_exec_path(retpath);
log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
_("invalid binary \"%s\": %m"), retpath);
#ifdef WIN32
/* Win32 checks the current directory first for names without slashes */
- join_path_components(retpath, cwd, argv0);
if (validate_exec(retpath) == 0)
- return resolve_symlinks(retpath);
+ return normalize_exec_path(retpath);
#endif
/*
if (!endp)
endp = startp + strlen(startp); /* point to end */
- strlcpy(test_path, startp, Min(endp - startp + 1, MAXPGPATH));
+ strlcpy(retpath, startp, Min(endp - startp + 1, MAXPGPATH));
- if (is_absolute_path(test_path))
- join_path_components(retpath, test_path, argv0);
- else
- {
- join_path_components(retpath, cwd, test_path);
- join_path_components(retpath, retpath, argv0);
- }
+ join_path_components(retpath, retpath, argv0);
canonicalize_path(retpath);
switch (validate_exec(retpath))
{
case 0: /* found ok */
- return resolve_symlinks(retpath);
+ return normalize_exec_path(retpath);
case -1: /* wasn't even a candidate, keep looking */
break;
case -2: /* found but disqualified */
/*
- * resolve_symlinks - resolve symlinks to the underlying file
+ * normalize_exec_path - resolve symlinks and convert to absolute path
*
- * Replace "path" by the absolute path to the referenced file.
+ * Given a path that refers to an executable, chase through any symlinks
+ * to find the real file location; then convert that to an absolute path.
*
+ * On success, replaces the contents of "path" with the absolute path.
+ * ("path" is assumed to be of size MAXPGPATH.)
* 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.
- *
- * Here we test HAVE_READLINK, which excludes Windows. There's no point in
- * using our junction point-based replacement code for this, because that only
- * works for directories.
*/
static int
-resolve_symlinks(char *path)
+normalize_exec_path(char *path)
{
-#ifdef HAVE_READLINK
- struct stat buf;
- char orig_wd[MAXPGPATH],
- link_buf[MAXPGPATH];
- char *fname;
-
/*
- * 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.
- *
- * One might think we could skip all this if path doesn't point to a
- * symlink to start with, but that's wrong. We also want to get rid of
- * any directory symlinks that are present in the given path. We expect
- * getcwd() to give us an accurate, symlink-free path.
+ * We used to do a lot of work ourselves here, but now we just let
+ * realpath(3) do all the heavy lifting.
*/
- if (!getcwd(orig_wd, MAXPGPATH))
+ char *abspath = pg_realpath(path);
+
+ if (abspath == NULL)
{
log_error(errcode_for_file_access(),
- _("could not identify current directory: %m"));
+ _("could not resolve path \"%s\" to absolute form: %m"),
+ path);
return -1;
}
+ strlcpy(path, abspath, MAXPGPATH);
+ free(abspath);
- for (;;)
- {
- char *lsep;
- int rllen;
-
- lsep = last_dir_separator(path);
- if (lsep)
- {
- *lsep = '\0';
- if (chdir(path) == -1)
- {
- log_error(errcode_for_file_access(),
- _("could not change directory to \"%s\": %m"), path);
- return -1;
- }
- fname = lsep + 1;
- }
- else
- fname = path;
+#ifdef WIN32
+ /* On Windows, be sure to convert '\' to '/' */
+ canonicalize_path(path);
+#endif
- if (lstat(fname, &buf) < 0 ||
- !S_ISLNK(buf.st_mode))
- break;
+ return 0;
+}
- errno = 0;
- rllen = readlink(fname, link_buf, sizeof(link_buf));
- if (rllen < 0 || rllen >= sizeof(link_buf))
- {
- log_error(errcode_for_file_access(),
- _("could not read symbolic link \"%s\": %m"), fname);
- return -1;
- }
- link_buf[rllen] = '\0';
- strcpy(path, link_buf);
- }
- /* must copy final component out of 'path' temporarily */
- strlcpy(link_buf, fname, sizeof(link_buf));
+/*
+ * pg_realpath() - realpath(3) with POSIX.1-2008 semantics
+ *
+ * This is equivalent to realpath(fname, NULL), in that it returns a
+ * malloc'd buffer containing the absolute path equivalent to fname.
+ * On error, returns NULL with errno set.
+ *
+ * On Windows, what you get is spelled per platform conventions,
+ * so you probably want to apply canonicalize_path() to the result.
+ *
+ * For now, this is needed only here so mark it static. If you choose to
+ * move it into its own file, move the _DARWIN_BETTER_REALPATH #define too!
+ */
+static char *
+pg_realpath(const char *fname)
+{
+ char *path;
- if (!getcwd(path, MAXPGPATH))
+#ifndef WIN32
+ path = realpath(fname, NULL);
+ if (path == NULL && errno == EINVAL)
{
- log_error(errcode_for_file_access(),
- _("could not identify current directory: %m"));
- return -1;
- }
- join_path_components(path, path, link_buf);
- canonicalize_path(path);
+ /*
+ * Cope with old-POSIX systems that require a user-provided buffer.
+ * Assume MAXPGPATH is enough room on all such systems.
+ */
+ char *buf = malloc(MAXPGPATH);
- if (chdir(orig_wd) == -1)
- {
- log_error(errcode_for_file_access(),
- _("could not change directory to \"%s\": %m"), orig_wd);
- return -1;
+ if (buf == NULL)
+ return NULL; /* assume errno is set */
+ path = realpath(fname, buf);
+ if (path == NULL) /* don't leak memory */
+ {
+ int save_errno = errno;
+
+ free(buf);
+ errno = save_errno;
+ }
}
-#endif /* HAVE_READLINK */
+#else /* WIN32 */
- return 0;
+ /*
+ * Microsoft is resolutely non-POSIX, but _fullpath() does the same thing.
+ * The documentation claims it reports errors by setting errno, which is a
+ * bit surprising for Microsoft, but we'll believe that until it's proven
+ * wrong. Clear errno first, though, so we can at least tell if a failure
+ * occurs and doesn't set it.
+ */
+ errno = 0;
+ path = _fullpath(NULL, fname, 0);
+#endif
+
+ return path;
}