287 lines
7.4 KiB
C++
287 lines
7.4 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* path.c
|
|
* portable path handling routines
|
|
*
|
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
* Portions Copyright (c) 2010-2012 Postgres-XC Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL$
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "gtm/gtm_c.h"
|
|
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gtm/path.h>
|
|
|
|
#define IS_DIR_SEP_GTM(ch) ((ch) == '/' || (ch) == '\\')
|
|
|
|
#define skip_drive(path) (path)
|
|
|
|
static void trim_directory(char* path);
|
|
static void trim_trailing_separator(char* path);
|
|
|
|
/*
|
|
* Clean up path by:
|
|
* o remove trailing slash
|
|
* o remove duplicate adjacent separators
|
|
* o remove trailing '.'
|
|
* o process trailing '..' ourselves
|
|
*/
|
|
void canonicalize_path(char* path)
|
|
{
|
|
char *p, *to_p;
|
|
char* spath;
|
|
bool was_sep = false;
|
|
int pending_strips;
|
|
errno_t rc;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* Remove duplicate adjacent separators
|
|
*/
|
|
p = path;
|
|
|
|
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 ".."
|
|
* 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 cancelled by ".." */
|
|
trim_directory(path);
|
|
pending_strips--;
|
|
/* foo/.. should become ".", not empty */
|
|
if (*spath == '\0') {
|
|
rc = strcpy_s(spath, 2, ".");
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pending_strips > 0) {
|
|
/*
|
|
* 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) {
|
|
rc = strcat_s(path, MAXPGPATH, "../");
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
rc = strcat_s(path, MAXPGPATH, "..");
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get_parent_directory
|
|
*
|
|
* Modify the given string in-place to name the parent directory of the
|
|
* named file.
|
|
*/
|
|
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)
|
|
{
|
|
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_GTM(*p) && p > path; p--)
|
|
;
|
|
/* back up over directory name */
|
|
for (; !IS_DIR_SEP_GTM(*p) && p > path; p--)
|
|
;
|
|
/* if multiple slashes before directory name, remove 'em all */
|
|
for (; p > path && IS_DIR_SEP_GTM(*(p - 1)); p--)
|
|
;
|
|
/* don't erase a leading slash */
|
|
if (p == path && IS_DIR_SEP_GTM(*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_GTM(*p); p--)
|
|
*p = '\0';
|
|
}
|
|
|
|
/*
|
|
* If the given pathname isn't already absolute, make it so, interpreting
|
|
* it relative to the current working directory.
|
|
*
|
|
* Also canonicalize the path. The result is always a malloc'd copy.
|
|
*
|
|
*/
|
|
char* make_absolute_path(const char* path)
|
|
{
|
|
char* new;
|
|
int rc;
|
|
|
|
/* 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)
|
|
return NULL;
|
|
|
|
if (getcwd(buf, buflen))
|
|
break;
|
|
else if (errno == ERANGE) {
|
|
free(buf);
|
|
buflen *= 2;
|
|
continue;
|
|
} else {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
size_t newlen;
|
|
newlen = strlen(buf) + strlen(path) + 2;
|
|
new = malloc(newlen);
|
|
if (!new) {
|
|
return NULL;
|
|
}
|
|
rc = sprintf_s(new, newlen, "%s/%s", buf, path);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
free(buf);
|
|
} else {
|
|
new = strdup(path);
|
|
if (!new) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Make sure punctuation is canonical, too */
|
|
canonicalize_path(new);
|
|
|
|
return new;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
int rc;
|
|
|
|
if (ret_path != head) {
|
|
strlcpy(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) {
|
|
size_t len;
|
|
len = MAXPGPATH - strlen(ret_path);
|
|
rc = snprintf_s(ret_path + strlen(ret_path), len, len - 1, "/%s", tail);
|
|
securec_check_ss(rc, "\0", "\0");
|
|
}
|
|
}
|