Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * path.c
4 : : * portable path handling routines
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/port/path.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : :
16 : : #ifndef FRONTEND
17 : : #include "postgres.h"
18 : : #else
19 : : #include "postgres_fe.h"
20 : : #endif
21 : :
22 : : #include <ctype.h>
23 : : #include <sys/stat.h>
24 : : #ifdef WIN32
25 : : #ifdef _WIN32_IE
26 : : #undef _WIN32_IE
27 : : #endif
28 : : #define _WIN32_IE 0x0500
29 : : #ifdef near
30 : : #undef near
31 : : #endif
32 : : #define near
33 : : #include <shlobj.h>
34 : : #else
35 : : #include <pwd.h>
36 : : #include <unistd.h>
37 : : #endif
38 : :
39 : : #include "mb/pg_wchar.h"
40 : : #include "pg_config_paths.h"
41 : :
42 : :
43 : : #ifndef WIN32
44 : : #define IS_PATH_VAR_SEP(ch) ((ch) == ':')
45 : : #else
46 : : #define IS_PATH_VAR_SEP(ch) ((ch) == ';')
47 : : #endif
48 : :
49 : : #ifdef WIN32
50 : : static void debackslash_path(char *path, int encoding);
51 : : static int pg_sjis_mblen(const unsigned char *s);
52 : : #endif
53 : : static void make_relative_path(char *ret_path, const char *target_path,
54 : : const char *bin_path, const char *my_exec_path);
55 : : static char *trim_directory(char *path);
56 : : static void trim_trailing_separator(char *path);
57 : : static char *append_subdir_to_path(char *path, char *subdir);
58 : :
59 : :
60 : : /*
61 : : * skip_drive
62 : : *
63 : : * On Windows, a path may begin with "C:" or "//network/". Advance over
64 : : * this and point to the effective start of the path.
65 : : */
66 : : #ifdef WIN32
67 : :
68 : : static char *
69 : : skip_drive(const char *path)
70 : : {
71 : : if (IS_DIR_SEP(path[0]) && IS_DIR_SEP(path[1]))
72 : : {
73 : : path += 2;
74 : : while (*path && !IS_DIR_SEP(*path))
75 : : path++;
76 : : }
77 : : else if (isalpha((unsigned char) path[0]) && path[1] == ':')
78 : : {
79 : : path += 2;
80 : : }
81 : : return (char *) path;
82 : : }
83 : : #else
84 : :
85 : : #define skip_drive(path) (path)
86 : : #endif
87 : :
88 : : /*
89 : : * has_drive_prefix
90 : : *
91 : : * Return true if the given pathname has a drive prefix.
92 : : */
93 : : bool
5442 rhaas@postgresql.org 94 :UBC 0 : has_drive_prefix(const char *path)
95 : : {
96 : : #ifdef WIN32
97 : : return skip_drive(path) != path;
98 : : #else
3753 peter_e@gmx.net 99 : 0 : return false;
100 : : #endif
101 : : }
102 : :
103 : : /*
104 : : * first_dir_separator
105 : : *
106 : : * Find the location of the first directory separator, return
107 : : * NULL if not found.
108 : : */
109 : : char *
8024 bruce@momjian.us 110 :CBC 86648 : first_dir_separator(const char *filename)
111 : : {
112 : : const char *p;
113 : :
7875 tgl@sss.pgh.pa.us 114 [ + + ]: 740906 : for (p = skip_drive(filename); *p; p++)
8024 bruce@momjian.us 115 [ + + ]: 670675 : if (IS_DIR_SEP(*p))
2678 peter@eisentraut.org 116 : 16417 : return unconstify(char *, p);
8024 bruce@momjian.us 117 : 70231 : return NULL;
118 : : }
119 : :
120 : : /*
121 : : * first_path_var_separator
122 : : *
123 : : * Find the location of the first path separator (i.e. ':' on
124 : : * Unix, ';' on Windows), return NULL if not found.
125 : : */
126 : : char *
5596 127 : 28092 : first_path_var_separator(const char *pathlist)
128 : : {
129 : : const char *p;
130 : :
131 : : /* skip_drive is not needed */
7875 tgl@sss.pgh.pa.us 132 [ + + ]: 835884 : for (p = pathlist; *p; p++)
5596 bruce@momjian.us 133 [ + + ]: 813238 : if (IS_PATH_VAR_SEP(*p))
2678 peter@eisentraut.org 134 : 5446 : return unconstify(char *, p);
8048 bruce@momjian.us 135 : 22646 : return NULL;
136 : : }
137 : :
138 : : /*
139 : : * last_dir_separator
140 : : *
141 : : * Find the location of the last directory separator, return
142 : : * NULL if not found.
143 : : */
144 : : char *
8024 145 : 192425 : last_dir_separator(const char *filename)
146 : : {
147 : : const char *p,
7944 148 : 192425 : *ret = NULL;
149 : :
7875 tgl@sss.pgh.pa.us 150 [ + + ]: 5819658 : for (p = skip_drive(filename); *p; p++)
8024 bruce@momjian.us 151 [ + + ]: 5627233 : if (IS_DIR_SEP(*p))
8048 152 : 636137 : ret = p;
2678 peter@eisentraut.org 153 : 192425 : return unconstify(char *, ret);
154 : : }
155 : :
156 : :
157 : : #ifdef WIN32
158 : :
159 : : /*
160 : : * Convert '\' to '/' within the given path, assuming the path
161 : : * is in the specified encoding.
162 : : */
163 : : static void
164 : : debackslash_path(char *path, int encoding)
165 : : {
166 : : char *p;
167 : :
168 : : /*
169 : : * Of the supported encodings, only Shift-JIS has multibyte characters
170 : : * that can include a byte equal to '\' (0x5C). So rather than implement
171 : : * a fully encoding-aware conversion, we special-case SJIS. (Invoking the
172 : : * general encoding-aware logic in wchar.c is impractical here for
173 : : * assorted reasons.)
174 : : */
175 : : if (encoding == PG_SJIS)
176 : : {
177 : : for (p = path; *p; p += pg_sjis_mblen((const unsigned char *) p))
178 : : {
179 : : if (*p == '\\')
180 : : *p = '/';
181 : : }
182 : : }
183 : : else
184 : : {
185 : : for (p = path; *p; p++)
186 : : {
187 : : if (*p == '\\')
188 : : *p = '/';
189 : : }
190 : : }
191 : : }
192 : :
193 : : /*
194 : : * SJIS character length
195 : : *
196 : : * This must match the behavior of
197 : : * pg_encoding_mblen_bounded(PG_SJIS, s)
198 : : * In particular, unlike the version of pg_sjis_mblen in src/common/wchar.c,
199 : : * do not allow caller to accidentally step past end-of-string.
200 : : */
201 : : static int
202 : : pg_sjis_mblen(const unsigned char *s)
203 : : {
204 : : int len;
205 : :
206 : : if (*s >= 0xa1 && *s <= 0xdf)
207 : : len = 1; /* 1 byte kana? */
208 : : else if (IS_HIGHBIT_SET(*s) && s[1] != '\0')
209 : : len = 2; /* kanji? */
210 : : else
211 : : len = 1; /* should be ASCII */
212 : : return len;
213 : : }
214 : :
215 : : #endif /* WIN32 */
216 : :
217 : :
218 : : /*
219 : : * make_native_path - on WIN32, change '/' to '\' in the path
220 : : *
221 : : * This reverses the '\'-to-'/' transformation of debackslash_path.
222 : : * We need not worry about encodings here, since '/' does not appear
223 : : * as a byte of a multibyte character in any supported encoding.
224 : : *
225 : : * This is required because WIN32 COPY is an internal CMD.EXE
226 : : * command and doesn't process forward slashes in the same way
227 : : * as external commands. Quoting the first argument to COPY
228 : : * does not convert forward to backward slashes, but COPY does
229 : : * properly process quoted forward slashes in the second argument.
230 : : *
231 : : * COPY works with quoted forward slashes in the first argument
232 : : * only if the current directory is the same as the directory
233 : : * of the first argument.
234 : : */
235 : : void
7961 bruce@momjian.us 236 : 954 : make_native_path(char *filename)
237 : : {
238 : : #ifdef WIN32
239 : : char *p;
240 : :
241 : : for (p = filename; *p; p++)
242 : : if (*p == '/')
243 : : *p = '\\';
244 : : #endif
245 : 954 : }
246 : :
247 : :
248 : : /*
249 : : * This function cleans up the paths for use with either cmd.exe or Msys
250 : : * on Windows. We need them to use filenames without spaces, for which a
251 : : * short filename is the safest equivalent, eg:
252 : : * C:/Progra~1/
253 : : *
254 : : * Presently, this is only used on paths that we can assume are in a
255 : : * server-safe encoding, so there's no need for an encoding-aware variant.
256 : : */
257 : : void
3755 mail@joeconway.com 258 : 7241 : cleanup_path(char *path)
259 : : {
260 : : #ifdef WIN32
261 : : /*
262 : : * GetShortPathName() will fail if the path does not exist, or short names
263 : : * are disabled on this file system. In both cases, we just return the
264 : : * original path. This is particularly useful for --sysconfdir, which
265 : : * might not exist.
266 : : */
267 : : GetShortPathName(path, path, MAXPGPATH - 1);
268 : :
269 : : /* Replace '\' with '/' */
270 : : /* All server-safe encodings are alike here, so just use PG_SQL_ASCII */
271 : : debackslash_path(path, PG_SQL_ASCII);
272 : : #endif
273 : 7241 : }
274 : :
275 : :
276 : : /*
277 : : * join_path_components - join two path components, inserting a slash
278 : : *
279 : : * We omit the slash if either given component is empty.
280 : : *
281 : : * ret_path is the output area (must be of size MAXPGPATH)
282 : : *
283 : : * ret_path can be the same as head, but not the same as tail.
284 : : */
285 : : void
7875 tgl@sss.pgh.pa.us 286 : 59853 : join_path_components(char *ret_path,
287 : : const char *head, const char *tail)
288 : : {
289 [ + + ]: 59853 : if (ret_path != head)
7185 290 : 2233 : strlcpy(ret_path, head, MAXPGPATH);
291 : :
292 : : /*
293 : : * We used to try to simplify some cases involving "." and "..", but now
294 : : * we just leave that to be done by canonicalize_path() later.
295 : : */
296 : :
7875 297 [ + - ]: 59853 : if (*tail)
298 : : {
299 : : /* only separate with slash if head wasn't empty */
300 : 59853 : snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path),
301 : : "%s%s",
5077 302 [ + + ]: 59853 : (*(skip_drive(head)) != '\0') ? "/" : "",
303 : : tail);
304 : : }
7875 305 : 59853 : }
306 : :
307 : :
308 : : /* State-machine states for canonicalize_path */
309 : : typedef enum
310 : : {
311 : : ABSOLUTE_PATH_INIT, /* Just past the leading '/' (and Windows
312 : : * drive name if any) of an absolute path */
313 : : ABSOLUTE_WITH_N_DEPTH, /* We collected 'pathdepth' directories in an
314 : : * absolute path */
315 : : RELATIVE_PATH_INIT, /* At start of a relative path */
316 : : RELATIVE_WITH_N_DEPTH, /* We collected 'pathdepth' directories in a
317 : : * relative path */
318 : : RELATIVE_WITH_PARENT_REF, /* Relative path containing only double-dots */
319 : : } canonicalize_state;
320 : :
321 : : /*
322 : : * canonicalize_path()
323 : : *
324 : : * Clean up path by:
325 : : * o make Win32 path use Unix slashes
326 : : * o remove trailing quote on Win32
327 : : * o remove trailing slash
328 : : * o remove duplicate (adjacent) separators
329 : : * o remove '.' (unless path reduces to only '.')
330 : : * o process '..' ourselves, removing it if possible
331 : : * Modifies path in-place.
332 : : *
333 : : * This comes in two variants: encoding-aware and not. The non-aware version
334 : : * is only safe to use on strings that are in a server-safe encoding.
335 : : */
336 : : void
8117 bruce@momjian.us 337 : 566725 : canonicalize_path(char *path)
338 : : {
339 : : /* All server-safe encodings are alike here, so just use PG_SQL_ASCII */
486 tgl@sss.pgh.pa.us 340 : 566725 : canonicalize_path_enc(path, PG_SQL_ASCII);
341 : 566725 : }
342 : :
343 : : void
344 : 566794 : canonicalize_path_enc(char *path, int encoding)
345 : : {
346 : : char *p,
347 : : *to_p;
348 : : char *spath;
349 : : char *parsed;
350 : : char *unparse;
7874 bruce@momjian.us 351 : 566794 : bool was_sep = false;
352 : : canonicalize_state state;
1580 tgl@sss.pgh.pa.us 353 : 566794 : int pathdepth = 0; /* counts collected regular directory names */
354 : :
355 : : #ifdef WIN32
356 : :
357 : : /*
358 : : * The Windows command processor will accept suitably quoted paths with
359 : : * forward slashes, but barfs badly with mixed forward and back slashes.
360 : : * Hence, start by converting all back slashes to forward slashes.
361 : : */
362 : : debackslash_path(path, encoding);
363 : :
364 : : /*
365 : : * In Win32, if you do: prog.exe "a b" "\c\d\" the system will pass \c\d"
366 : : * as argv[2], so trim off trailing quote.
367 : : */
368 : : p = path + strlen(path);
369 : : if (p > path && *(p - 1) == '"')
370 : : *(p - 1) = '/';
371 : : #endif
372 : :
373 : : /*
374 : : * Removing the trailing slash on a path means we never get ugly double
375 : : * trailing slashes. Also, Win32 can't stat() a directory with a trailing
376 : : * slash. Don't remove a leading slash, though.
377 : : */
8048 bruce@momjian.us 378 : 566794 : trim_trailing_separator(path);
379 : :
380 : : /*
381 : : * Remove duplicate adjacent separators
382 : : */
7874 383 : 566794 : p = path;
384 : : #ifdef WIN32
385 : : /* Don't remove leading double-slash on Win32 */
386 : : if (*p)
387 : : p++;
388 : : #endif
389 : 566794 : to_p = p;
390 [ + + ]: 24231362 : for (; *p; p++, to_p++)
391 : : {
392 : : /* Handle many adjacent slashes, like "/a///b" */
393 [ + + + + ]: 23669981 : while (*p == '/' && was_sep)
394 : 5413 : p++;
395 [ + + ]: 23664568 : if (to_p != p)
396 : 141617 : *to_p = *p;
397 : 23664568 : was_sep = (*p == '/');
398 : : }
399 : 566794 : *to_p = '\0';
400 : :
401 : : /*
402 : : * Remove any uses of "." and process ".." ourselves
403 : : *
404 : : * Note that "/../.." should reduce to just "/", while "../.." has to be
405 : : * kept as-is. Also note that we want a Windows drive spec to be visible
406 : : * to trim_directory(), but it's not part of the logic that's looking at
407 : : * the name components; hence distinction between path and spath.
408 : : *
409 : : * This loop overwrites the path in-place. This is safe since we'll never
410 : : * make the path longer. "unparse" points to where we are reading the
411 : : * path, "parse" to where we are writing.
412 : : */
7596 tgl@sss.pgh.pa.us 413 : 566794 : spath = skip_drive(path);
1580 414 [ + + ]: 566794 : if (*spath == '\0')
415 : 47 : return; /* empty path is returned as-is */
416 : :
417 [ + + ]: 566747 : if (*spath == '/')
418 : : {
419 : 146840 : state = ABSOLUTE_PATH_INIT;
420 : : /* Skip the leading slash for absolute path */
421 : 146840 : parsed = unparse = (spath + 1);
422 : : }
423 : : else
424 : : {
425 : 419907 : state = RELATIVE_PATH_INIT;
426 : 419907 : parsed = unparse = spath;
427 : : }
428 : :
429 [ + + ]: 3484864 : while (*unparse != '\0')
430 : : {
431 : : char *unparse_next;
432 : : bool is_double_dot;
433 : :
434 : : /* Split off this dir name, and set unparse_next to the next one */
435 : 2918117 : unparse_next = unparse;
436 [ + + + + ]: 24084470 : while (*unparse_next && *unparse_next != '/')
437 : 21166353 : unparse_next++;
438 [ + + ]: 2918117 : if (*unparse_next != '\0')
439 : 2351375 : *unparse_next++ = '\0';
440 : :
441 : : /* Identify type of this dir name */
442 [ + + ]: 2918117 : if (strcmp(unparse, ".") == 0)
443 : : {
444 : : /* We can ignore "." components in all cases */
445 : 46462 : unparse = unparse_next;
446 : 46462 : continue;
447 : : }
448 : :
449 [ + + ]: 2871655 : if (strcmp(unparse, "..") == 0)
450 : 173 : is_double_dot = true;
451 : : else
452 : : {
453 : : /* adjacent separators were eliminated above */
454 [ - + ]: 2871482 : Assert(*unparse != '\0');
455 : 2871482 : is_double_dot = false;
456 : : }
457 : :
458 [ + + + + : 2871655 : switch (state)
+ - ]
459 : : {
460 : 146880 : case ABSOLUTE_PATH_INIT:
461 : : /* We can ignore ".." immediately after / */
462 [ + + ]: 146880 : if (!is_double_dot)
463 : : {
464 : : /* Append first dir name (we already have leading slash) */
465 : 146850 : parsed = append_subdir_to_path(parsed, unparse);
466 : 146850 : state = ABSOLUTE_WITH_N_DEPTH;
467 : 146850 : pathdepth++;
468 : : }
469 : 146880 : break;
470 : 1513746 : case ABSOLUTE_WITH_N_DEPTH:
471 [ + + ]: 1513746 : if (is_double_dot)
472 : : {
473 : : /* Remove last parsed dir */
474 : : /* (trim_directory won't remove the leading slash) */
475 : 63 : *parsed = '\0';
476 : 63 : parsed = trim_directory(path);
477 [ + + ]: 63 : if (--pathdepth == 0)
478 : 25 : state = ABSOLUTE_PATH_INIT;
479 : : }
480 : : else
481 : : {
482 : : /* Append normal dir */
483 : 1513683 : *parsed++ = '/';
484 : 1513683 : parsed = append_subdir_to_path(parsed, unparse);
485 : 1513683 : pathdepth++;
486 : : }
487 : 1513746 : break;
488 : 419864 : case RELATIVE_PATH_INIT:
489 [ + + ]: 419864 : if (is_double_dot)
490 : : {
491 : : /* Append irreducible double-dot (..) */
492 : 30 : parsed = append_subdir_to_path(parsed, unparse);
493 : 30 : state = RELATIVE_WITH_PARENT_REF;
494 : : }
495 : : else
496 : : {
497 : : /* Append normal dir */
498 : 419834 : parsed = append_subdir_to_path(parsed, unparse);
499 : 419834 : state = RELATIVE_WITH_N_DEPTH;
500 : 419834 : pathdepth++;
501 : : }
502 : 419864 : break;
503 : 791125 : case RELATIVE_WITH_N_DEPTH:
504 [ + + ]: 791125 : if (is_double_dot)
505 : : {
506 : : /* Remove last parsed dir */
507 : 45 : *parsed = '\0';
508 : 45 : parsed = trim_directory(path);
509 [ + + ]: 45 : if (--pathdepth == 0)
510 : : {
511 : : /*
512 : : * If the output path is now empty, we're back to the
513 : : * INIT state. However, we could have processed a
514 : : * path like "../dir/.." and now be down to "..", in
515 : : * which case enter the correct state for that.
516 : : */
517 [ + + ]: 35 : if (parsed == spath)
518 : 20 : state = RELATIVE_PATH_INIT;
519 : : else
520 : 15 : state = RELATIVE_WITH_PARENT_REF;
521 : : }
522 : : }
523 : : else
524 : : {
525 : : /* Append normal dir */
526 : 791080 : *parsed++ = '/';
527 : 791080 : parsed = append_subdir_to_path(parsed, unparse);
528 : 791080 : pathdepth++;
529 : : }
530 : 791125 : break;
531 : 40 : case RELATIVE_WITH_PARENT_REF:
532 [ + + ]: 40 : if (is_double_dot)
533 : : {
534 : : /* Append next irreducible double-dot (..) */
535 : 5 : *parsed++ = '/';
536 : 5 : parsed = append_subdir_to_path(parsed, unparse);
537 : : }
538 : : else
539 : : {
540 : : /* Append normal dir */
541 : 35 : *parsed++ = '/';
542 : 35 : parsed = append_subdir_to_path(parsed, unparse);
543 : :
544 : : /*
545 : : * We can now start counting normal dirs. But if later
546 : : * double-dots make us remove this dir again, we'd better
547 : : * revert to RELATIVE_WITH_PARENT_REF not INIT state.
548 : : */
549 : 35 : state = RELATIVE_WITH_N_DEPTH;
550 : 35 : pathdepth = 1;
551 : : }
552 : 40 : break;
553 : : }
554 : :
555 : 2871655 : unparse = unparse_next;
556 : : }
557 : :
558 : : /*
559 : : * If our output path is empty at this point, insert ".". We don't want
560 : : * to do this any earlier because it'd result in an extra dot in corner
561 : : * cases such as "../dir/..". Since we rejected the wholly-empty-path
562 : : * case above, there is certainly room.
563 : : */
564 [ + + ]: 566747 : if (parsed == spath)
565 : 63 : *parsed++ = '.';
566 : :
567 : : /* And finally, ensure the output path is nul-terminated. */
568 : 566747 : *parsed = '\0';
569 : : }
570 : :
571 : : /*
572 : : * Detect whether a path contains any parent-directory references ("..")
573 : : *
574 : : * The input *must* have been put through canonicalize_path previously.
575 : : */
576 : : bool
7596 577 : 386424 : path_contains_parent_reference(const char *path)
578 : : {
579 : : /*
580 : : * Once canonicalized, an absolute path cannot contain any ".." at all,
581 : : * while a relative path could contain ".."(s) only at the start. So it
582 : : * is sufficient to check the start of the path, after skipping any
583 : : * Windows drive/network specifier.
584 : : */
1580 585 : 386424 : path = skip_drive(path); /* C: shouldn't affect our conclusion */
586 : :
587 [ + + ]: 386424 : if (path[0] == '.' &&
588 [ - + ]: 26 : path[1] == '.' &&
1580 tgl@sss.pgh.pa.us 589 [ # # # # ]:UBC 0 : (path[2] == '\0' || path[2] == '/'))
7596 590 : 0 : return true;
591 : :
7596 tgl@sss.pgh.pa.us 592 :CBC 386424 : return false;
593 : : }
594 : :
595 : : /*
596 : : * Detect whether a path is only in or below the current working directory.
597 : : *
598 : : * The input *must* have been put through canonicalize_path previously.
599 : : *
600 : : * An absolute path that matches the current working directory should
601 : : * return false (we only want relative to the cwd).
602 : : */
603 : : bool
5586 bruce@momjian.us 604 : 386424 : path_is_relative_and_below_cwd(const char *path)
605 : : {
5585 606 [ - + ]: 386424 : if (is_absolute_path(path))
5586 bruce@momjian.us 607 :UBC 0 : return false;
608 : : /* don't allow anything above the cwd */
5586 bruce@momjian.us 609 [ - + ]:CBC 386424 : else if (path_contains_parent_reference(path))
5586 bruce@momjian.us 610 :UBC 0 : return false;
611 : : #ifdef WIN32
612 : :
613 : : /*
614 : : * On Win32, a drive letter _not_ followed by a slash, e.g. 'E:abc', is
615 : : * relative to the cwd on that drive, or the drive's root directory if
616 : : * that drive has no cwd. Because the path itself cannot tell us which is
617 : : * the case, we have to assume the worst, i.e. that it is not below the
618 : : * cwd. We could use GetFullPathName() to find the full path but that
619 : : * could change if the current directory for the drive changes underneath
620 : : * us, so we just disallow it.
621 : : */
622 : : else if (isalpha((unsigned char) path[0]) && path[1] == ':' &&
623 : : !IS_DIR_SEP(path[2]))
624 : : return false;
625 : : #endif
626 : : else
5529 bruce@momjian.us 627 :CBC 386424 : return true;
628 : : }
629 : :
630 : : /*
631 : : * Detect whether a path is safe for use during archive extraction.
632 : : *
633 : : * This applies canonicalize_path(), then it checks that the path does
634 : : * not contain any parent directory references.
635 : : */
636 : : bool
19 michael@paquier.xyz 637 : 379253 : path_is_safe_for_extraction(const char *path)
638 : : {
639 : : char buf[MAXPGPATH];
640 : :
641 : 379253 : strlcpy(buf, path, sizeof(buf));
642 : 379253 : canonicalize_path(buf);
643 : :
644 : 379253 : return path_is_relative_and_below_cwd(buf);
645 : : }
646 : :
647 : : /*
648 : : * Detect whether path1 is a prefix of path2 (including equality).
649 : : *
650 : : * This is pretty trivial, but it seems better to export a function than
651 : : * to export IS_DIR_SEP.
652 : : */
653 : : bool
7579 tgl@sss.pgh.pa.us 654 : 83 : path_is_prefix_of_path(const char *path1, const char *path2)
655 : : {
7532 bruce@momjian.us 656 : 83 : int path1_len = strlen(path1);
657 : :
7579 tgl@sss.pgh.pa.us 658 [ - + ]: 83 : if (strncmp(path1, path2, path1_len) == 0 &&
7579 tgl@sss.pgh.pa.us 659 [ # # # # ]:UBC 0 : (IS_DIR_SEP(path2[path1_len]) || path2[path1_len] == '\0'))
660 : 0 : return true;
7579 tgl@sss.pgh.pa.us 661 :CBC 83 : return false;
662 : : }
663 : :
664 : : /*
665 : : * Extracts the actual name of the program as called -
666 : : * stripped of .exe suffix if any
667 : : */
668 : : const char *
8053 bruce@momjian.us 669 : 34717 : get_progname(const char *argv0)
670 : : {
671 : : const char *nodir_name;
672 : : char *progname;
673 : :
7875 tgl@sss.pgh.pa.us 674 : 34717 : nodir_name = last_dir_separator(argv0);
675 [ + + ]: 34717 : if (nodir_name)
676 : 27103 : nodir_name++;
677 : : else
678 : 7614 : nodir_name = skip_drive(argv0);
679 : :
680 : : /*
681 : : * Make a copy in case argv[0] is modified by ps_status. Leaks memory, but
682 : : * called only once.
683 : : */
7423 bruce@momjian.us 684 : 34717 : progname = strdup(nodir_name);
685 [ - + ]: 34717 : if (progname == NULL)
686 : : {
7423 bruce@momjian.us 687 :UBC 0 : fprintf(stderr, "%s: out of memory\n", nodir_name);
5234 peter_e@gmx.net 688 : 0 : abort(); /* This could exit the postmaster */
689 : : }
690 : :
691 : : #if defined(__CYGWIN__) || defined(WIN32)
692 : : /* strip ".exe" suffix, regardless of case */
693 : : if (strlen(progname) > sizeof(EXE) - 1 &&
694 : : pg_strcasecmp(progname + strlen(progname) - (sizeof(EXE) - 1), EXE) == 0)
695 : : progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
696 : : #endif
697 : :
7423 bruce@momjian.us 698 :CBC 34717 : return progname;
699 : : }
700 : :
701 : :
702 : : /*
703 : : * dir_strcmp: strcmp except any two DIR_SEP characters are considered equal,
704 : : * and we honor filesystem case insensitivity if known
705 : : */
706 : : static int
7463 tgl@sss.pgh.pa.us 707 : 51923 : dir_strcmp(const char *s1, const char *s2)
708 : : {
709 [ + + + - ]: 207692 : while (*s1 && *s2)
710 : : {
6266 711 : 155769 : if (
712 : : #ifndef WIN32
713 [ - + ]: 155769 : *s1 != *s2
714 : : #else
715 : : /* On windows, paths are case-insensitive */
716 : : pg_tolower((unsigned char) *s1) != pg_tolower((unsigned char) *s2)
717 : : #endif
6266 tgl@sss.pgh.pa.us 718 [ # # # # ]:UBC 0 : && !(IS_DIR_SEP(*s1) && IS_DIR_SEP(*s2)))
7463 719 : 0 : return (int) *s1 - (int) *s2;
7463 tgl@sss.pgh.pa.us 720 :CBC 155769 : s1++, s2++;
721 : : }
722 [ - + ]: 51923 : if (*s1)
7463 tgl@sss.pgh.pa.us 723 :UBC 0 : return 1; /* s1 longer */
7463 tgl@sss.pgh.pa.us 724 [ - + ]:CBC 51923 : if (*s2)
7463 tgl@sss.pgh.pa.us 725 :UBC 0 : return -1; /* s2 longer */
7463 tgl@sss.pgh.pa.us 726 :CBC 51923 : return 0;
727 : : }
728 : :
729 : :
730 : : /*
731 : : * make_relative_path - make a path relative to the actual binary location
732 : : *
733 : : * This function exists to support relocation of installation trees.
734 : : *
735 : : * ret_path is the output area (must be of size MAXPGPATH)
736 : : * target_path is the compiled-in path to the directory we want to find
737 : : * bin_path is the compiled-in path to the directory of executables
738 : : * my_exec_path is the actual location of my executable
739 : : *
740 : : * We determine the common prefix of target_path and bin_path, then compare
741 : : * the remainder of bin_path to the last directory component(s) of
742 : : * my_exec_path. If they match, build the result as the part of my_exec_path
743 : : * preceding the match, joined to the remainder of target_path. If no match,
744 : : * return target_path as-is.
745 : : *
746 : : * For example:
747 : : * target_path = '/usr/local/share/postgresql'
748 : : * bin_path = '/usr/local/bin'
749 : : * my_exec_path = '/opt/pgsql/bin/postgres'
750 : : * Given these inputs, the common prefix is '/usr/local/', the tail of
751 : : * bin_path is 'bin' which does match the last directory component of
752 : : * my_exec_path, so we would return '/opt/pgsql/share/postgresql'
753 : : */
754 : : static void
7875 755 : 53079 : make_relative_path(char *ret_path, const char *target_path,
756 : : const char *bin_path, const char *my_exec_path)
757 : : {
758 : : int prefix_len;
759 : : int tail_start;
760 : : int tail_len;
761 : : int i;
762 : :
763 : : /*
764 : : * Determine the common prefix --- note we require it to end on a
765 : : * directory separator, consider eg '/usr/lib' and '/usr/libexec'.
766 : : */
7463 767 : 53079 : prefix_len = 0;
768 [ + - + - ]: 955422 : for (i = 0; target_path[i] && bin_path[i]; i++)
769 : : {
770 [ + + + - ]: 955422 : if (IS_DIR_SEP(target_path[i]) && IS_DIR_SEP(bin_path[i]))
771 : 212316 : prefix_len = i + 1;
772 [ + + ]: 743106 : else if (target_path[i] != bin_path[i])
773 : 53079 : break;
774 : : }
775 [ - + ]: 53079 : if (prefix_len == 0)
7463 tgl@sss.pgh.pa.us 776 :UBC 0 : goto no_match; /* no common prefix? */
7463 tgl@sss.pgh.pa.us 777 :CBC 53079 : tail_len = strlen(bin_path) - prefix_len;
778 : :
779 : : /*
780 : : * Set up my_exec_path without the actual executable name, and
781 : : * canonicalize to simplify comparison to bin_path.
782 : : */
7185 783 : 53079 : strlcpy(ret_path, my_exec_path, MAXPGPATH);
7875 784 : 53079 : trim_directory(ret_path); /* remove my executable name */
785 : 53079 : canonicalize_path(ret_path);
786 : :
787 : : /*
788 : : * Tail match?
789 : : */
7463 790 : 53079 : tail_start = (int) strlen(ret_path) - tail_len;
791 [ + - ]: 53079 : if (tail_start > 0 &&
7178 bruce@momjian.us 792 [ + + - + ]: 105002 : IS_DIR_SEP(ret_path[tail_start - 1]) &&
7463 tgl@sss.pgh.pa.us 793 : 51923 : dir_strcmp(ret_path + tail_start, bin_path + prefix_len) == 0)
794 : : {
795 : 51923 : ret_path[tail_start] = '\0';
796 : 51923 : trim_trailing_separator(ret_path);
797 : 51923 : join_path_components(ret_path, ret_path, target_path + prefix_len);
798 : 51923 : canonicalize_path(ret_path);
799 : 51923 : return;
800 : : }
801 : :
7875 802 : 1156 : no_match:
7185 803 : 1156 : strlcpy(ret_path, target_path, MAXPGPATH);
7993 bruce@momjian.us 804 : 1156 : canonicalize_path(ret_path);
805 : : }
806 : :
807 : :
808 : : /*
809 : : * make_absolute_path
810 : : *
811 : : * If the given pathname isn't already absolute, make it so, interpreting
812 : : * it relative to the current working directory.
813 : : *
814 : : * Also canonicalizes the path. The result is always a malloc'd copy.
815 : : *
816 : : * In backend, failure cases result in ereport(ERROR); in frontend,
817 : : * we write a complaint on stderr and return NULL.
818 : : *
819 : : * Note: interpretation of relative-path arguments during postmaster startup
820 : : * should happen before doing ChangeToDataDir(), else the user will probably
821 : : * not like the results.
822 : : */
823 : : char *
4439 tgl@sss.pgh.pa.us 824 : 3062 : make_absolute_path(const char *path)
825 : : {
826 : : char *new;
827 : :
828 : : /* Returning null for null input is convenient for some callers */
829 [ - + ]: 3062 : if (path == NULL)
4439 tgl@sss.pgh.pa.us 830 :UBC 0 : return NULL;
831 : :
4439 tgl@sss.pgh.pa.us 832 [ + + ]:CBC 3062 : if (!is_absolute_path(path))
833 : : {
834 : : char *buf;
835 : : size_t buflen;
836 : :
837 : 4 : buflen = MAXPGPATH;
838 : : for (;;)
839 : : {
4439 tgl@sss.pgh.pa.us 840 :UBC 0 : buf = malloc(buflen);
4439 tgl@sss.pgh.pa.us 841 [ - + ]:CBC 4 : if (!buf)
842 : : {
843 : : #ifndef FRONTEND
4439 tgl@sss.pgh.pa.us 844 [ # # ]:UBC 0 : ereport(ERROR,
845 : : (errcode(ERRCODE_OUT_OF_MEMORY),
846 : : errmsg("out of memory")));
847 : : #else
848 : 0 : fprintf(stderr, _("out of memory\n"));
849 : 0 : return NULL;
850 : : #endif
851 : : }
852 : :
4439 tgl@sss.pgh.pa.us 853 [ + - ]:CBC 4 : if (getcwd(buf, buflen))
854 : 4 : break;
4439 tgl@sss.pgh.pa.us 855 [ # # ]:UBC 0 : else if (errno == ERANGE)
856 : : {
857 : 0 : free(buf);
858 : 0 : buflen *= 2;
859 : 0 : continue;
860 : : }
861 : : else
862 : : {
863 : 0 : int save_errno = errno;
864 : :
865 : 0 : free(buf);
866 : 0 : errno = save_errno;
867 : : #ifndef FRONTEND
868 [ # # ]: 0 : elog(ERROR, "could not get current working directory: %m");
869 : : #else
809 michael@paquier.xyz 870 : 0 : fprintf(stderr, _("could not get current working directory: %m\n"));
4439 tgl@sss.pgh.pa.us 871 : 0 : return NULL;
872 : : #endif
873 : : }
874 : : }
875 : :
4439 tgl@sss.pgh.pa.us 876 :CBC 4 : new = malloc(strlen(buf) + strlen(path) + 2);
877 [ - + ]: 4 : if (!new)
878 : : {
4439 tgl@sss.pgh.pa.us 879 :UBC 0 : free(buf);
880 : : #ifndef FRONTEND
881 [ # # ]: 0 : ereport(ERROR,
882 : : (errcode(ERRCODE_OUT_OF_MEMORY),
883 : : errmsg("out of memory")));
884 : : #else
885 : 0 : fprintf(stderr, _("out of memory\n"));
886 : 0 : return NULL;
887 : : #endif
888 : : }
4439 tgl@sss.pgh.pa.us 889 :CBC 4 : sprintf(new, "%s/%s", buf, path);
890 : 4 : free(buf);
891 : : }
892 : : else
893 : : {
894 : 3058 : new = strdup(path);
895 [ - + ]: 3058 : if (!new)
896 : : {
897 : : #ifndef FRONTEND
4439 tgl@sss.pgh.pa.us 898 [ # # ]:UBC 0 : ereport(ERROR,
899 : : (errcode(ERRCODE_OUT_OF_MEMORY),
900 : : errmsg("out of memory")));
901 : : #else
902 : 0 : fprintf(stderr, _("out of memory\n"));
903 : 0 : return NULL;
904 : : #endif
905 : : }
906 : : }
907 : :
908 : : /* Make sure punctuation is canonical, too */
4439 tgl@sss.pgh.pa.us 909 :CBC 3062 : canonicalize_path(new);
910 : :
911 : 3062 : return new;
912 : : }
913 : :
914 : :
915 : : /*
916 : : * get_share_path
917 : : */
918 : : void
7875 919 : 11123 : get_share_path(const char *my_exec_path, char *ret_path)
920 : : {
921 : 11123 : make_relative_path(ret_path, PGSHAREDIR, PGBINDIR, my_exec_path);
922 : 11123 : }
923 : :
924 : : /*
925 : : * get_etc_path
926 : : */
927 : : void
8048 bruce@momjian.us 928 : 14156 : get_etc_path(const char *my_exec_path, char *ret_path)
929 : : {
7875 tgl@sss.pgh.pa.us 930 : 14156 : make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path);
8048 bruce@momjian.us 931 : 14156 : }
932 : :
933 : : /*
934 : : * get_include_path
935 : : */
936 : : void
937 : 628 : get_include_path(const char *my_exec_path, char *ret_path)
938 : : {
7875 tgl@sss.pgh.pa.us 939 : 628 : make_relative_path(ret_path, INCLUDEDIR, PGBINDIR, my_exec_path);
8048 bruce@momjian.us 940 : 628 : }
941 : :
942 : : /*
943 : : * get_pkginclude_path
944 : : */
945 : : void
946 : 568 : get_pkginclude_path(const char *my_exec_path, char *ret_path)
947 : : {
7875 tgl@sss.pgh.pa.us 948 : 568 : make_relative_path(ret_path, PKGINCLUDEDIR, PGBINDIR, my_exec_path);
8048 bruce@momjian.us 949 : 568 : }
950 : :
951 : : /*
952 : : * get_includeserver_path
953 : : */
954 : : void
7972 955 : 557 : get_includeserver_path(const char *my_exec_path, char *ret_path)
956 : : {
7875 tgl@sss.pgh.pa.us 957 : 557 : make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path);
7972 bruce@momjian.us 958 : 557 : }
959 : :
960 : : /*
961 : : * get_lib_path
962 : : */
963 : : void
964 : 557 : get_lib_path(const char *my_exec_path, char *ret_path)
965 : : {
7875 tgl@sss.pgh.pa.us 966 : 557 : make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path);
7972 bruce@momjian.us 967 : 557 : }
968 : :
969 : : /*
970 : : * get_pkglib_path
971 : : */
972 : : void
8048 973 : 2406 : get_pkglib_path(const char *my_exec_path, char *ret_path)
974 : : {
7875 tgl@sss.pgh.pa.us 975 : 2406 : make_relative_path(ret_path, PKGLIBDIR, PGBINDIR, my_exec_path);
8048 bruce@momjian.us 976 : 2406 : }
977 : :
978 : : /*
979 : : * get_locale_path
980 : : */
981 : : void
8040 982 : 21413 : get_locale_path(const char *my_exec_path, char *ret_path)
983 : : {
7875 tgl@sss.pgh.pa.us 984 : 21413 : make_relative_path(ret_path, LOCALEDIR, PGBINDIR, my_exec_path);
8040 bruce@momjian.us 985 : 21413 : }
986 : :
987 : : /*
988 : : * get_doc_path
989 : : */
990 : : void
7550 tgl@sss.pgh.pa.us 991 : 557 : get_doc_path(const char *my_exec_path, char *ret_path)
992 : : {
993 : 557 : make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path);
994 : 557 : }
995 : :
996 : : /*
997 : : * get_html_path
998 : : */
999 : : void
6676 peter_e@gmx.net 1000 : 557 : get_html_path(const char *my_exec_path, char *ret_path)
1001 : : {
1002 : 557 : make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path);
1003 : 557 : }
1004 : :
1005 : : /*
1006 : : * get_man_path
1007 : : */
1008 : : void
7550 tgl@sss.pgh.pa.us 1009 : 557 : get_man_path(const char *my_exec_path, char *ret_path)
1010 : : {
1011 : 557 : make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path);
1012 : 557 : }
1013 : :
1014 : :
1015 : : /*
1016 : : * get_home_path
1017 : : *
1018 : : * On Unix, this actually returns the user's home directory. On Windows
1019 : : * it returns the PostgreSQL-specific application data folder.
1020 : : */
1021 : : bool
7944 tgl@sss.pgh.pa.us 1022 :UBC 0 : get_home_path(char *ret_path)
1023 : : {
1024 : : #ifndef WIN32
1025 : : /*
1026 : : * We first consult $HOME. If that's unset, try to get the info from
1027 : : * <pwd.h>.
1028 : : */
1029 : : const char *home;
1030 : :
1602 1031 : 0 : home = getenv("HOME");
635 peter@eisentraut.org 1032 [ # # # # ]: 0 : if (home && home[0])
1033 : : {
1034 : 0 : strlcpy(ret_path, home, MAXPGPATH);
1035 : 0 : return true;
1036 : : }
1037 : : else
1038 : : {
1039 : : struct passwd pwbuf;
1040 : : struct passwd *pw;
1041 : : char buf[1024];
1042 : : int rc;
1043 : :
1044 : 0 : rc = getpwuid_r(geteuid(), &pwbuf, buf, sizeof buf, &pw);
1045 [ # # # # ]: 0 : if (rc != 0 || !pw)
1046 : 0 : return false;
1047 : 0 : strlcpy(ret_path, pw->pw_dir, MAXPGPATH);
1048 : 0 : return true;
1049 : : }
1050 : : #else
1051 : : char *tmppath;
1052 : :
1053 : : /*
1054 : : * Note: We use getenv() here because the more modern SHGetFolderPath()
1055 : : * would force the backend to link with shell32.lib, which eats valuable
1056 : : * desktop heap. XXX This function is used only in psql, which already
1057 : : * brings in shell32 via libpq. Moving this function to its own file
1058 : : * would keep it out of the backend, freeing it from this concern.
1059 : : */
1060 : : tmppath = getenv("APPDATA");
1061 : : if (!tmppath)
1062 : : return false;
1063 : : snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
1064 : : return true;
1065 : : #endif
1066 : : }
1067 : :
1068 : :
1069 : : /*
1070 : : * get_parent_directory
1071 : : *
1072 : : * Modify the given string in-place to name the parent directory of the
1073 : : * named file.
1074 : : *
1075 : : * If the input is just a file name with no directory part, the result is
1076 : : * an empty string, not ".". This is appropriate when the next step is
1077 : : * join_path_components(), but might need special handling otherwise.
1078 : : *
1079 : : * Caution: this will not produce desirable results if the string ends
1080 : : * with "..". For most callers this is not a problem since the string
1081 : : * is already known to name a regular file. If in doubt, apply
1082 : : * canonicalize_path() first.
1083 : : */
1084 : : void
7944 tgl@sss.pgh.pa.us 1085 :CBC 8834 : get_parent_directory(char *path)
1086 : : {
1087 : 8834 : trim_directory(path);
1088 : 8834 : }
1089 : :
1090 : :
1091 : : /*
1092 : : * trim_directory
1093 : : *
1094 : : * Trim trailing directory from path, that is, remove any trailing slashes,
1095 : : * the last pathname component, and the slash just ahead of it --- but never
1096 : : * remove a leading slash.
1097 : : *
1098 : : * For the convenience of canonicalize_path, the path's new end location
1099 : : * is returned.
1100 : : */
1101 : : static char *
8048 bruce@momjian.us 1102 : 62021 : trim_directory(char *path)
1103 : : {
1104 : : char *p;
1105 : :
7875 tgl@sss.pgh.pa.us 1106 : 62021 : path = skip_drive(path);
1107 : :
8048 bruce@momjian.us 1108 [ - + ]: 62021 : if (path[0] == '\0')
1580 tgl@sss.pgh.pa.us 1109 :UBC 0 : return path;
1110 : :
1111 : : /* back up over trailing slash(es) */
8024 bruce@momjian.us 1112 [ - + - - ]:CBC 62021 : for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
1113 : : ;
1114 : : /* back up over directory name */
1115 [ + + + + ]: 601785 : for (; !IS_DIR_SEP(*p) && p > path; p--)
1116 : : ;
1117 : : /* if multiple slashes before directory name, remove 'em all */
7875 tgl@sss.pgh.pa.us 1118 [ + + - + ]: 62021 : for (; p > path && IS_DIR_SEP(*(p - 1)); p--)
1119 : : ;
1120 : : /* don't erase a leading slash */
1121 [ + + + + ]: 62021 : if (p == path && IS_DIR_SEP(*p))
1122 : 25 : p++;
8048 bruce@momjian.us 1123 : 62021 : *p = '\0';
1580 tgl@sss.pgh.pa.us 1124 : 62021 : return p;
1125 : : }
1126 : :
1127 : :
1128 : : /*
1129 : : * trim_trailing_separator
1130 : : *
1131 : : * trim off trailing slashes, but not a leading slash
1132 : : */
1133 : : static void
8048 bruce@momjian.us 1134 : 618717 : trim_trailing_separator(char *path)
1135 : : {
1136 : : char *p;
1137 : :
7875 tgl@sss.pgh.pa.us 1138 : 618717 : path = skip_drive(path);
1139 : 618717 : p = path + strlen(path);
8048 bruce@momjian.us 1140 [ + + ]: 618717 : if (p > path)
7994 tgl@sss.pgh.pa.us 1141 [ + + + + ]: 679771 : for (p--; p > path && IS_DIR_SEP(*p); p--)
8048 bruce@momjian.us 1142 : 61101 : *p = '\0';
1143 : 618717 : }
1144 : :
1145 : : /*
1146 : : * append_subdir_to_path
1147 : : *
1148 : : * Append the currently-considered subdirectory name to the output
1149 : : * path in canonicalize_path. Return the new end location of the
1150 : : * output path.
1151 : : *
1152 : : * Since canonicalize_path updates the path in-place, we must use
1153 : : * memmove not memcpy, and we don't yet terminate the path with '\0'.
1154 : : */
1155 : : static char *
1580 tgl@sss.pgh.pa.us 1156 : 2871517 : append_subdir_to_path(char *path, char *subdir)
1157 : : {
1158 : 2871517 : size_t len = strlen(subdir);
1159 : :
1160 : : /* No need to copy data if path and subdir are the same. */
1161 [ + + ]: 2871517 : if (path != subdir)
1162 : 110598 : memmove(path, subdir, len);
1163 : :
1164 : 2871517 : return path + len;
1165 : : }
|