postgresql/src/port/path.c

909 lines
20 KiB
C
Raw Normal View History

2003-11-12 00:52:45 +01:00
/*-------------------------------------------------------------------------
*
* path.c
* portable path handling routines
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
2003-11-12 00:52:45 +01:00
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/port/path.c
2003-11-12 00:52:45 +01:00
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include <ctype.h>
#include <sys/stat.h>
#ifdef WIN32
#ifdef _WIN32_IE
#undef _WIN32_IE
#endif
#define _WIN32_IE 0x0500
#ifdef near
#undef near
#endif
#define near
#include <shlobj.h>
#else
#include <unistd.h>
#endif
#include "pg_config_paths.h"
#ifndef WIN32
#define IS_PATH_VAR_SEP(ch) ((ch) == ':')
#else
#define IS_PATH_VAR_SEP(ch) ((ch) == ';')
#endif
static void make_relative_path(char *ret_path, const char *target_path,
2005-10-15 04:49:52 +02:00
const char *bin_path, const char *my_exec_path);
static void trim_directory(char *path);
static void trim_trailing_separator(char *path);
/*
* 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((unsigned char) path[0]) && path[1] == ':')
{
path += 2;
}
return (char *) path;
}
#else
#define skip_drive(path) (path)
#endif
/*
* has_drive_prefix
*
* Return true if the given pathname has a drive prefix.
*/
bool
has_drive_prefix(const char *path)
{
#ifdef WIN32
return skip_drive(path) != path;
#else
return false;
#endif
}
/*
* first_dir_separator
*
* Find the location of the first directory separator, return
* NULL if not found.
*/
char *
first_dir_separator(const char *filename)
{
const char *p;
for (p = skip_drive(filename); *p; p++)
if (IS_DIR_SEP(*p))
return (char *) p;
return NULL;
}
/*
* first_path_var_separator
*
* Find the location of the first path separator (i.e. ':' on
* Unix, ';' on Windows), return NULL if not found.
*/
2003-08-04 02:43:34 +02:00
char *
first_path_var_separator(const char *pathlist)
{
const char *p;
/* skip_drive is not needed */
for (p = pathlist; *p; p++)
if (IS_PATH_VAR_SEP(*p))
return (char *) p;
return NULL;
}
/*
* last_dir_separator
*
* Find the location of the last directory separator, return
* NULL if not found.
*/
2003-08-04 02:43:34 +02:00
char *
last_dir_separator(const char *filename)
{
const char *p,
2004-08-29 07:07:03 +02:00
*ret = NULL;
for (p = skip_drive(filename); *p; p++)
if (IS_DIR_SEP(*p))
ret = p;
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
* does not convert forward to backward slashes, but COPY does
* properly process quoted forward slashes in the second argument.
*
* COPY works with quoted forward slashes in the first argument
* only if the current directory is the same as the directory
* of the first argument.
*/
void
make_native_path(char *filename)
{
#ifdef WIN32
2004-08-29 07:07:03 +02:00
char *p;
for (p = filename; *p; p++)
if (*p == '/')
*p = '\\';
#endif
}
/*
* This function cleans up the paths for use with either cmd.exe or Msys
* on Windows. We need them to use filenames without spaces, for which a
* short filename is the safest equivalent, eg:
* C:/Progra~1/
*/
void
cleanup_path(char *path)
{
#ifdef WIN32
char *ptr;
/*
* GetShortPathName() will fail if the path does not exist, or short names
* are disabled on this file system. In both cases, we just return the
* original path. This is particularly useful for --sysconfdir, which
* might not exist.
*/
GetShortPathName(path, path, MAXPGPATH - 1);
/* Replace '\' with '/' */
for (ptr = path; *ptr; ptr++)
{
if (*ptr == '\\')
*ptr = '/';
}
#endif
}
/*
* join_path_components - join two path components, inserting a slash
*
* We omit the slash if either given component is empty.
*
* 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)
strlcpy(ret_path, head, MAXPGPATH);
2005-10-15 04:49:52 +02:00
/*
* Remove any leading "." in the tail component.
*
* Note: we used to try to remove ".." as well, but that's tricky to get
* right; now we just leave it to be done by canonicalize_path() later.
*/
while (tail[0] == '.' && IS_DIR_SEP(tail[1]))
tail += 2;
if (*tail)
{
/* only separate with slash if head wasn't empty */
snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path),
"%s%s",
(*(skip_drive(head)) != '\0') ? "/" : "",
tail);
}
}
/*
* Clean up path by:
* o make Win32 path use Unix slashes
* o remove trailing quote on Win32
* o remove trailing slash
* o remove duplicate adjacent separators
* o remove trailing '.'
* o process trailing '..' ourselves
*/
void
canonicalize_path(char *path)
{
2005-10-15 04:49:52 +02:00
char *p,
*to_p;
char *spath;
bool was_sep = false;
int pending_strips;
2004-08-29 07:07:03 +02:00
#ifdef WIN32
/*
2005-10-15 04:49:52 +02:00
* The Windows command processor will accept suitably quoted paths with
* forward slashes, but barfs badly with mixed forward and back slashes.
*/
for (p = path; *p; p++)
{
if (*p == '\\')
*p = '/';
}
2004-08-29 07:07:03 +02:00
/*
2005-10-15 04:49:52 +02:00
* In Win32, if you do: prog.exe "a b" "\c\d\" the system will pass \c\d"
* as argv[2], so trim off trailing quote.
*/
2004-08-29 07:07:03 +02:00
if (p > path && *(p - 1) == '"')
*(p - 1) = '/';
#endif
/*
2005-10-15 04:49:52 +02:00
* Removing the trailing slash on a path means we never get ugly double
* trailing slashes. Also, Win32 can't stat() a directory with a trailing
* slash. Don't remove a leading slash, though.
*/
trim_trailing_separator(path);
/*
2005-10-15 04:49:52 +02:00
* Remove duplicate adjacent separators
*/
p = path;
#ifdef WIN32
/* Don't remove leading double-slash on Win32 */
if (*p)
p++;
#endif
to_p = p;
for (; *p; p++, to_p++)
{
/* Handle many adjacent slashes, like "/a///b" */
while (*p == '/' && was_sep)
p++;
if (to_p != p)
*to_p = *p;
was_sep = (*p == '/');
}
*to_p = '\0';
/*
* Remove any trailing uses of "." and process ".." ourselves
*
* Note that "/../.." should reduce to just "/", while "../.." has to be
* kept as-is. In the latter case we put back mistakenly trimmed ".."
2005-10-15 04:49:52 +02:00
* components below. Also note that we want a Windows drive spec to be
* visible to trim_directory(), but it's not part of the logic that's
* looking at the name components; hence distinction between path and
* spath.
*/
spath = skip_drive(path);
pending_strips = 0;
for (;;)
{
int len = strlen(spath);
if (len >= 2 && strcmp(spath + len - 2, "/.") == 0)
trim_directory(path);
else if (strcmp(spath, ".") == 0)
{
/* Want to leave "." alone, but "./.." has to become ".." */
if (pending_strips > 0)
*spath = '\0';
break;
}
else if ((len >= 3 && strcmp(spath + len - 3, "/..") == 0) ||
strcmp(spath, "..") == 0)
{
trim_directory(path);
pending_strips++;
}
else if (pending_strips > 0 && *spath != '\0')
{
/* trim a regular directory name canceled by ".." */
trim_directory(path);
pending_strips--;
/* foo/.. should become ".", not empty */
if (*spath == '\0')
strcpy(spath, ".");
}
else
break;
}
if (pending_strips > 0)
{
/*
2005-10-15 04:49:52 +02:00
* We could only get here if path is now totally empty (other than a
* possible drive specifier on Windows). We have to put back one or
* more ".."'s that we took off.
*/
while (--pending_strips > 0)
strcat(path, "../");
strcat(path, "..");
}
}
/*
* Detect whether a path contains any parent-directory references ("..")
*
* The input *must* have been put through canonicalize_path previously.
*
* This is a bit tricky because we mustn't be fooled by "..a.." (legal)
* nor "C:.." (legal on Unix but not Windows).
*/
bool
path_contains_parent_reference(const char *path)
{
2005-10-15 04:49:52 +02:00
int path_len;
path = skip_drive(path); /* C: shouldn't affect our conclusion */
path_len = strlen(path);
/*
2005-10-15 04:49:52 +02:00
* ".." could be the whole path; otherwise, if it's present it must be at
* the beginning, in the middle, or at the end.
*/
if (strcmp(path, "..") == 0 ||
strncmp(path, "../", 3) == 0 ||
strstr(path, "/../") != NULL ||
(path_len >= 3 && strcmp(path + path_len - 3, "/..") == 0))
return true;
return false;
}
/*
* Detect whether a path is only in or below the current working directory.
* An absolute path that matches the current working directory should
* return false (we only want relative to the cwd). We don't allow
* "/../" even if that would keep us under the cwd (it is too hard to
* track that).
*/
bool
path_is_relative_and_below_cwd(const char *path)
{
if (is_absolute_path(path))
return false;
/* don't allow anything above the cwd */
else if (path_contains_parent_reference(path))
return false;
#ifdef WIN32
/*
2011-04-10 17:42:00 +02:00
* On Win32, a drive letter _not_ followed by a slash, e.g. 'E:abc', is
* relative to the cwd on that drive, or the drive's root directory if
* that drive has no cwd. Because the path itself cannot tell us which is
* the case, we have to assume the worst, i.e. that it is not below the
* cwd. We could use GetFullPathName() to find the full path but that
* could change if the current directory for the drive changes underneath
* us, so we just disallow it.
*/
else if (isalpha((unsigned char) path[0]) && path[1] == ':' &&
2011-04-10 17:42:00 +02:00
!IS_DIR_SEP(path[2]))
return false;
#endif
else
2011-04-10 17:42:00 +02:00
return true;
}
/*
* Detect whether path1 is a prefix of path2 (including equality).
*
* This is pretty trivial, but it seems better to export a function than
* to export IS_DIR_SEP.
*/
bool
path_is_prefix_of_path(const char *path1, const char *path2)
{
2005-10-15 04:49:52 +02:00
int path1_len = strlen(path1);
if (strncmp(path1, path2, path1_len) == 0 &&
(IS_DIR_SEP(path2[path1_len]) || path2[path1_len] == '\0'))
return true;
return false;
}
/*
2005-10-15 04:49:52 +02:00
* Extracts the actual name of the program as called -
* stripped of .exe suffix if any
*/
const char *
get_progname(const char *argv0)
{
2006-10-04 02:30:14 +02:00
const char *nodir_name;
char *progname;
nodir_name = last_dir_separator(argv0);
if (nodir_name)
nodir_name++;
else
nodir_name = skip_drive(argv0);
/*
2006-10-04 02:30:14 +02:00
* Make a copy in case argv[0] is modified by ps_status. Leaks memory, but
* called only once.
*/
progname = strdup(nodir_name);
if (progname == NULL)
{
fprintf(stderr, "%s: out of memory\n", nodir_name);
abort(); /* This could exit the postmaster */
}
#if defined(__CYGWIN__) || defined(WIN32)
/* strip ".exe" suffix, regardless of case */
if (strlen(progname) > sizeof(EXE) - 1 &&
pg_strcasecmp(progname + strlen(progname) - (sizeof(EXE) - 1), EXE) == 0)
progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
#endif
return progname;
}
/*
* dir_strcmp: strcmp except any two DIR_SEP characters are considered equal,
* and we honor filesystem case insensitivity if known
*/
static int
dir_strcmp(const char *s1, const char *s2)
{
while (*s1 && *s2)
{
if (
#ifndef WIN32
*s1 != *s2
#else
/* On windows, paths are case-insensitive */
pg_tolower((unsigned char) *s1) != pg_tolower((unsigned char) *s2)
#endif
&& !(IS_DIR_SEP(*s1) && IS_DIR_SEP(*s2)))
return (int) *s1 - (int) *s2;
s1++, s2++;
}
if (*s1)
return 1; /* s1 longer */
if (*s2)
return -1; /* s2 longer */
return 0;
}
/*
* make_relative_path - make a path relative to the actual binary location
*
* This function exists to support relocation of installation trees.
*
* ret_path is the output area (must be of size MAXPGPATH)
* target_path is the compiled-in path to the directory we want to find
* bin_path is the compiled-in path to the directory of executables
* my_exec_path is the actual location of my executable
*
* We determine the common prefix of target_path and bin_path, then compare
* the remainder of bin_path to the last directory component(s) of
* my_exec_path. If they match, build the result as the part of my_exec_path
* preceding the match, joined to the remainder of target_path. If no match,
* return target_path as-is.
2005-10-15 04:49:52 +02:00
*
* For example:
* target_path = '/usr/local/share/postgresql'
2005-10-15 04:49:52 +02:00
* bin_path = '/usr/local/bin'
* my_exec_path = '/opt/pgsql/bin/postmaster'
* Given these inputs, the common prefix is '/usr/local/', the tail of
* bin_path is 'bin' which does match the last directory component of
* my_exec_path, so we would return '/opt/pgsql/share/postgresql'
*/
static void
make_relative_path(char *ret_path, const char *target_path,
const char *bin_path, const char *my_exec_path)
{
int prefix_len;
int tail_start;
int tail_len;
int i;
/*
* Determine the common prefix --- note we require it to end on a
* directory separator, consider eg '/usr/lib' and '/usr/libexec'.
*/
prefix_len = 0;
for (i = 0; target_path[i] && bin_path[i]; i++)
{
if (IS_DIR_SEP(target_path[i]) && IS_DIR_SEP(bin_path[i]))
prefix_len = i + 1;
else if (target_path[i] != bin_path[i])
break;
}
if (prefix_len == 0)
goto no_match; /* no common prefix? */
tail_len = strlen(bin_path) - prefix_len;
/*
* Set up my_exec_path without the actual executable name, and
* canonicalize to simplify comparison to bin_path.
*/
strlcpy(ret_path, my_exec_path, MAXPGPATH);
trim_directory(ret_path); /* remove my executable name */
canonicalize_path(ret_path);
/*
* Tail match?
*/
tail_start = (int) strlen(ret_path) - tail_len;
if (tail_start > 0 &&
2006-10-04 02:30:14 +02:00
IS_DIR_SEP(ret_path[tail_start - 1]) &&
dir_strcmp(ret_path + tail_start, bin_path + prefix_len) == 0)
{
ret_path[tail_start] = '\0';
trim_trailing_separator(ret_path);
join_path_components(ret_path, ret_path, target_path + prefix_len);
canonicalize_path(ret_path);
return;
}
2004-08-29 07:07:03 +02:00
no_match:
strlcpy(ret_path, target_path, MAXPGPATH);
canonicalize_path(ret_path);
}
/*
* make_absolute_path
*
* If the given pathname isn't already absolute, make it so, interpreting
* it relative to the current working directory.
*
* Also canonicalizes the path. The result is always a malloc'd copy.
*
* In backend, failure cases result in ereport(ERROR); in frontend,
* we write a complaint on stderr and return NULL.
*
* Note: interpretation of relative-path arguments during postmaster startup
* should happen before doing ChangeToDataDir(), else the user will probably
* not like the results.
*/
char *
make_absolute_path(const char *path)
{
char *new;
/* Returning null for null input is convenient for some callers */
if (path == NULL)
return NULL;
if (!is_absolute_path(path))
{
char *buf;
size_t buflen;
buflen = MAXPGPATH;
for (;;)
{
buf = malloc(buflen);
if (!buf)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#else
fprintf(stderr, _("out of memory\n"));
return NULL;
#endif
}
if (getcwd(buf, buflen))
break;
else if (errno == ERANGE)
{
free(buf);
buflen *= 2;
continue;
}
else
{
int save_errno = errno;
free(buf);
errno = save_errno;
#ifndef FRONTEND
elog(ERROR, "could not get current working directory: %m");
#else
fprintf(stderr, _("could not get current working directory: %s\n"),
strerror(errno));
return NULL;
#endif
}
}
new = malloc(strlen(buf) + strlen(path) + 2);
if (!new)
{
free(buf);
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#else
fprintf(stderr, _("out of memory\n"));
return NULL;
#endif
}
sprintf(new, "%s/%s", buf, path);
free(buf);
}
else
{
new = strdup(path);
if (!new)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#else
fprintf(stderr, _("out of memory\n"));
return NULL;
#endif
}
}
/* Make sure punctuation is canonical, too */
canonicalize_path(new);
return new;
}
/*
* get_share_path
*/
void
get_share_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, PGSHAREDIR, PGBINDIR, my_exec_path);
}
/*
* get_etc_path
*/
void
get_etc_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path);
}
/*
* get_include_path
*/
void
get_include_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, INCLUDEDIR, PGBINDIR, my_exec_path);
}
/*
* get_pkginclude_path
*/
void
get_pkginclude_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, PKGINCLUDEDIR, PGBINDIR, my_exec_path);
}
/*
* get_includeserver_path
*/
void
get_includeserver_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path);
}
/*
* get_lib_path
*/
void
get_lib_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path);
}
/*
* get_pkglib_path
*/
void
get_pkglib_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, PKGLIBDIR, PGBINDIR, my_exec_path);
}
/*
* get_locale_path
*/
void
get_locale_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, LOCALEDIR, PGBINDIR, my_exec_path);
}
/*
* get_doc_path
*/
void
get_doc_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path);
}
/*
* get_html_path
*/
void
get_html_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path);
}
/*
* get_man_path
*/
void
get_man_path(const char *my_exec_path, char *ret_path)
{
make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path);
}
/*
* get_home_path
*
* On Unix, this actually returns the user's home directory. On Windows
* it returns the PostgreSQL-specific application data folder.
*/
bool
get_home_path(char *ret_path)
{
#ifndef WIN32
char pwdbuf[BUFSIZ];
struct passwd pwdstr;
struct passwd *pwd = NULL;
Fix libpq's behavior when /etc/passwd isn't readable. Some users run their applications in chroot environments that lack an /etc/passwd file. This means that the current UID's user name and home directory are not obtainable. libpq used to be all right with that, so long as the database role name to use was specified explicitly. But commit a4c8f14364c27508233f8a31ac4b10a4c90235a9 broke such cases by causing any failure of pg_fe_getauthname() to be treated as a hard error. In any case it did little to advance its nominal goal of causing errors in pg_fe_getauthname() to be reported better. So revert that and instead put some real error-reporting code in place. This requires changes to the APIs of pg_fe_getauthname() and pqGetpwuid(), since the latter had departed from the POSIX-specified API of getpwuid_r() in a way that made it impossible to distinguish actual lookup errors from "no such user". To allow such failures to be reported, while not failing if the caller supplies a role name, add a second call of pg_fe_getauthname() in connectOptions2(). This is a tad ugly, and could perhaps be avoided with some refactoring of PQsetdbLogin(), but I'll leave that idea for later. (Note that the complained-of misbehavior only occurs in PQsetdbLogin, not when using the PQconnect functions, because in the latter we will never bother to call pg_fe_getauthname() if the user gives a role name.) In passing also clean up the Windows-side usage of GetUserName(): the recommended buffer size is 257 bytes, the passed buffer length should be the buffer size not buffer size less 1, and any error is reported by GetLastError() not errno. Per report from Christoph Berg. Back-patch to 9.4 where the chroot failure case was introduced. The generally poor reporting of errors here is of very long standing, of course, but given the lack of field complaints about it we won't risk changing these APIs further back (even though they're theoretically internal to libpq).
2015-01-11 18:35:44 +01:00
(void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
if (pwd == NULL)
return false;
strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
return true;
#else
2007-11-15 22:14:46 +01:00
char *tmppath;
2007-11-15 22:14:46 +01:00
/*
* Note: We use getenv() here because the more modern SHGetFolderPath()
* would force the backend to link with shell32.lib, which eats valuable
* desktop heap. XXX This function is used only in psql, which already
* brings in shell32 via libpq. Moving this function to its own file
* would keep it out of the backend, freeing it from this concern.
2007-11-15 22:14:46 +01:00
*/
tmppath = getenv("APPDATA");
if (!tmppath)
return false;
snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
return true;
#endif
}
/*
* get_parent_directory
*
* Modify the given string in-place to name the parent directory of the
* named file.
*
* If the input is just a file name with no directory part, the result is
* an empty string, not ".". This is appropriate when the next step is
* join_path_components(), but might need special handling otherwise.
*
* Caution: this will not produce desirable results if the string ends
* with "..". For most callers this is not a problem since the string
* is already known to name a regular file. If in doubt, apply
* canonicalize_path() first.
*/
void
get_parent_directory(char *path)
{
trim_directory(path);
}
/*
* trim_directory
*
* 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)
{
2004-08-29 07:07:03 +02:00
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 = skip_drive(path);
p = path + strlen(path);
if (p > path)
for (p--; p > path && IS_DIR_SEP(*p); p--)
*p = '\0';
}