/* * 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 * * If target_path matches bin_path up to the last directory component of * bin_path, then we build the result as my_exec_path (less the executable * name and last directory) joined to the non-matching part of target_path. * Otherwise, we return target_path as-is. * * For example: * target_path = '/usr/local/share/postgresql' * bin_path = '/usr/local/bin' * my_exec_path = '/opt/pgsql/bin/postmaster' * Given these inputs 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) { const char *bin_end; int prefix_len; bin_end = last_dir_separator(bin_path); if (!bin_end) goto no_match; prefix_len = bin_end - bin_path + 1; if (strncmp(target_path, bin_path, prefix_len) != 0) goto no_match; StrNCpy(ret_path, my_exec_path, MAXPGPATH); trim_directory(ret_path); /* remove my executable name */ trim_directory(ret_path); /* remove last directory component (/bin) */ join_path_components(ret_path, ret_path, target_path + prefix_len); canonicalize_path(ret_path); return; no_match: StrNCpy(ret_path, target_path, MAXPGPATH); canonicalize_path(ret_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; bool was_sep = false; /* * Removing the trailing slash on a path means we never get ugly * double trailing slashes. */ 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 */ for (;;) { int len = strlen(path); if (len > 2 && strcmp(path + len - 2, "/.") == 0) trim_directory(path); else if (len > 3 && strcmp(path + len - 3, "/..") == 0) { trim_directory(path); trim_directory(path); /* remove directory above */ } else break; } }
/* * 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) 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) snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path), "/%s", tail); }
/* * 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. * * For example: * target_path = '/usr/local/share/postgresql' * 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 && 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; } no_match: strlcpy(ret_path, target_path, MAXPGPATH); canonicalize_path(ret_path); }
/* * 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); }
/* * 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) { char *p, *to_p; char *spath; bool was_sep = false; int pending_strips; #ifdef WIN32 /* * 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 = '/'; } /* * 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. */ if (p > path && *(p - 1) == '"') *(p - 1) = '/'; #endif /* * 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; #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 ".." * 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) { /* * 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, ".."); } }
/* * 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) { char *p, *to_p; bool was_sep = false; #ifdef WIN32 /* * 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 = '/'; } /* * 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. */ if (p > path && *(p - 1) == '"') *(p - 1) = '/'; #endif /* * 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; #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 */ for (;;) { int len = strlen(path); if (len > 2 && strcmp(path + len - 2, "/.") == 0) trim_directory(path); else if (len > 3 && strcmp(path + len - 3, "/..") == 0) { trim_directory(path); trim_directory(path); /* remove directory above */ } else break; } }