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-2025, 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
5176 rhaas@postgresql.org 94 :UBC 0 : has_drive_prefix(const char *path)
95 : : {
96 : : #ifdef WIN32
97 : : return skip_drive(path) != path;
98 : : #else
3487 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 *
7758 bruce@momjian.us 110 :CBC 69933 : first_dir_separator(const char *filename)
111 : : {
112 : : const char *p;
113 : :
7609 tgl@sss.pgh.pa.us 114 [ + + ]: 579997 : for (p = skip_drive(filename); *p; p++)
7758 bruce@momjian.us 115 [ + + ]: 523070 : if (IS_DIR_SEP(*p))
2412 peter@eisentraut.org 116 : 13006 : return unconstify(char *, p);
7758 bruce@momjian.us 117 : 56927 : 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 *
5330 127 : 22753 : first_path_var_separator(const char *pathlist)
128 : : {
129 : : const char *p;
130 : :
131 : : /* skip_drive is not needed */
7609 tgl@sss.pgh.pa.us 132 [ + + ]: 669632 : for (p = pathlist; *p; p++)
5330 bruce@momjian.us 133 [ + + ]: 651230 : if (IS_PATH_VAR_SEP(*p))
2412 peter@eisentraut.org 134 : 4351 : return unconstify(char *, p);
7782 bruce@momjian.us 135 : 18402 : 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 *
7758 145 : 169533 : last_dir_separator(const char *filename)
146 : : {
147 : : const char *p,
7678 148 : 169533 : *ret = NULL;
149 : :
7609 tgl@sss.pgh.pa.us 150 [ + + ]: 5128388 : for (p = skip_drive(filename); *p; p++)
7758 bruce@momjian.us 151 [ + + ]: 4958855 : if (IS_DIR_SEP(*p))
7782 152 : 554249 : ret = p;
2412 peter@eisentraut.org 153 : 169533 : 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
7695 bruce@momjian.us 236 : 894 : 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 : 894 : }
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
3489 mail@joeconway.com 258 : 5941 : 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 : 5941 : }
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
7609 tgl@sss.pgh.pa.us 286 : 47373 : join_path_components(char *ret_path,
287 : : const char *head, const char *tail)
288 : : {
289 [ + + ]: 47373 : if (ret_path != head)
6919 290 : 1737 : 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 : :
7609 297 [ + - ]: 47373 : if (*tail)
298 : : {
299 : : /* only separate with slash if head wasn't empty */
300 : 47373 : snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path),
301 : : "%s%s",
4811 302 [ + + ]: 47373 : (*(skip_drive(head)) != '\0') ? "/" : "",
303 : : tail);
304 : : }
7609 305 : 47373 : }
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
7851 bruce@momjian.us 337 : 153316 : canonicalize_path(char *path)
338 : : {
339 : : /* All server-safe encodings are alike here, so just use PG_SQL_ASCII */
220 tgl@sss.pgh.pa.us 340 : 153316 : canonicalize_path_enc(path, PG_SQL_ASCII);
341 : 153316 : }
342 : :
343 : : void
344 : 153382 : canonicalize_path_enc(char *path, int encoding)
345 : : {
346 : : char *p,
347 : : *to_p;
348 : : char *spath;
349 : : char *parsed;
350 : : char *unparse;
7608 bruce@momjian.us 351 : 153382 : bool was_sep = false;
352 : : canonicalize_state state;
1314 tgl@sss.pgh.pa.us 353 : 153382 : 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 : : */
7782 bruce@momjian.us 378 : 153382 : trim_trailing_separator(path);
379 : :
380 : : /*
381 : : * Remove duplicate adjacent separators
382 : : */
7608 383 : 153382 : p = path;
384 : : #ifdef WIN32
385 : : /* Don't remove leading double-slash on Win32 */
386 : : if (*p)
387 : : p++;
388 : : #endif
389 : 153382 : to_p = p;
390 [ + + ]: 15224346 : for (; *p; p++, to_p++)
391 : : {
392 : : /* Handle many adjacent slashes, like "/a///b" */
393 [ + + + + ]: 15075307 : while (*p == '/' && was_sep)
394 : 4343 : p++;
395 [ + + ]: 15070964 : if (to_p != p)
396 : 113722 : *to_p = *p;
397 : 15070964 : was_sep = (*p == '/');
398 : : }
399 : 153382 : *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 : : */
7330 tgl@sss.pgh.pa.us 413 : 153382 : spath = skip_drive(path);
1314 414 [ + + ]: 153382 : if (*spath == '\0')
415 : 42 : return; /* empty path is returned as-is */
416 : :
417 [ + + ]: 153340 : if (*spath == '/')
418 : : {
419 : 117177 : state = ABSOLUTE_PATH_INIT;
420 : : /* Skip the leading slash for absolute path */
421 : 117177 : parsed = unparse = (spath + 1);
422 : : }
423 : : else
424 : : {
425 : 36163 : state = RELATIVE_PATH_INIT;
426 : 36163 : parsed = unparse = spath;
427 : : }
428 : :
429 [ + + ]: 1612702 : 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 : 1459362 : unparse_next = unparse;
436 [ + + + + ]: 15107124 : while (*unparse_next && *unparse_next != '/')
437 : 13647762 : unparse_next++;
438 [ + + ]: 1459362 : if (*unparse_next != '\0')
439 : 1306025 : *unparse_next++ = '\0';
440 : :
441 : : /* Identify type of this dir name */
442 [ + + ]: 1459362 : if (strcmp(unparse, ".") == 0)
443 : : {
444 : : /* We can ignore "." components in all cases */
445 : 32120 : unparse = unparse_next;
446 : 32120 : continue;
447 : : }
448 : :
449 [ + + ]: 1427242 : if (strcmp(unparse, "..") == 0)
450 : 113 : is_double_dot = true;
451 : : else
452 : : {
453 : : /* adjacent separators were eliminated above */
454 [ - + ]: 1427129 : Assert(*unparse != '\0');
455 : 1427129 : is_double_dot = false;
456 : : }
457 : :
458 [ + + + + : 1427242 : switch (state)
+ - ]
459 : : {
460 : 117201 : case ABSOLUTE_PATH_INIT:
461 : : /* We can ignore ".." immediately after / */
462 [ + + ]: 117201 : if (!is_double_dot)
463 : : {
464 : : /* Append first dir name (we already have leading slash) */
465 : 117183 : parsed = append_subdir_to_path(parsed, unparse);
466 : 117183 : state = ABSOLUTE_WITH_N_DEPTH;
467 : 117183 : pathdepth++;
468 : : }
469 : 117201 : break;
470 : 1207510 : case ABSOLUTE_WITH_N_DEPTH:
471 [ + + ]: 1207510 : if (is_double_dot)
472 : : {
473 : : /* Remove last parsed dir */
474 : : /* (trim_directory won't remove the leading slash) */
475 : 47 : *parsed = '\0';
476 : 47 : parsed = trim_directory(path);
477 [ + + ]: 47 : if (--pathdepth == 0)
478 : 15 : state = ABSOLUTE_PATH_INIT;
479 : : }
480 : : else
481 : : {
482 : : /* Append normal dir */
483 : 1207463 : *parsed++ = '/';
484 : 1207463 : parsed = append_subdir_to_path(parsed, unparse);
485 : 1207463 : pathdepth++;
486 : : }
487 : 1207510 : break;
488 : 36145 : case RELATIVE_PATH_INIT:
489 [ + + ]: 36145 : if (is_double_dot)
490 : : {
491 : : /* Append irreducible double-dot (..) */
492 : 18 : parsed = append_subdir_to_path(parsed, unparse);
493 : 18 : state = RELATIVE_WITH_PARENT_REF;
494 : : }
495 : : else
496 : : {
497 : : /* Append normal dir */
498 : 36127 : parsed = append_subdir_to_path(parsed, unparse);
499 : 36127 : state = RELATIVE_WITH_N_DEPTH;
500 : 36127 : pathdepth++;
501 : : }
502 : 36145 : break;
503 : 66362 : case RELATIVE_WITH_N_DEPTH:
504 [ + + ]: 66362 : if (is_double_dot)
505 : : {
506 : : /* Remove last parsed dir */
507 : 27 : *parsed = '\0';
508 : 27 : parsed = trim_directory(path);
509 [ + + ]: 27 : 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 [ + + ]: 21 : if (parsed == spath)
518 : 12 : state = RELATIVE_PATH_INIT;
519 : : else
520 : 9 : state = RELATIVE_WITH_PARENT_REF;
521 : : }
522 : : }
523 : : else
524 : : {
525 : : /* Append normal dir */
526 : 66335 : *parsed++ = '/';
527 : 66335 : parsed = append_subdir_to_path(parsed, unparse);
528 : 66335 : pathdepth++;
529 : : }
530 : 66362 : break;
531 : 24 : case RELATIVE_WITH_PARENT_REF:
532 [ + + ]: 24 : if (is_double_dot)
533 : : {
534 : : /* Append next irreducible double-dot (..) */
535 : 3 : *parsed++ = '/';
536 : 3 : parsed = append_subdir_to_path(parsed, unparse);
537 : : }
538 : : else
539 : : {
540 : : /* Append normal dir */
541 : 21 : *parsed++ = '/';
542 : 21 : 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 : 21 : state = RELATIVE_WITH_N_DEPTH;
550 : 21 : pathdepth = 1;
551 : : }
552 : 24 : break;
553 : : }
554 : :
555 : 1427242 : 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 [ + + ]: 153340 : if (parsed == spath)
565 : 30 : *parsed++ = '.';
566 : :
567 : : /* And finally, ensure the output path is nul-terminated. */
568 : 153340 : *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
7330 577 : 6900 : 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 : : */
1314 585 : 6900 : path = skip_drive(path); /* C: shouldn't affect our conclusion */
586 : :
587 [ + + ]: 6900 : if (path[0] == '.' &&
588 [ - + ]: 4 : path[1] == '.' &&
1314 tgl@sss.pgh.pa.us 589 [ # # # # ]:UBC 0 : (path[2] == '\0' || path[2] == '/'))
7330 590 : 0 : return true;
591 : :
7330 tgl@sss.pgh.pa.us 592 :CBC 6900 : 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
5320 bruce@momjian.us 604 : 6900 : path_is_relative_and_below_cwd(const char *path)
605 : : {
5319 606 [ - + ]: 6900 : if (is_absolute_path(path))
5320 bruce@momjian.us 607 :UBC 0 : return false;
608 : : /* don't allow anything above the cwd */
5320 bruce@momjian.us 609 [ - + ]:CBC 6900 : else if (path_contains_parent_reference(path))
5320 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
5263 bruce@momjian.us 627 :CBC 6900 : return true;
628 : : }
629 : :
630 : : /*
631 : : * Detect whether path1 is a prefix of path2 (including equality).
632 : : *
633 : : * This is pretty trivial, but it seems better to export a function than
634 : : * to export IS_DIR_SEP.
635 : : */
636 : : bool
7313 tgl@sss.pgh.pa.us 637 : 72 : path_is_prefix_of_path(const char *path1, const char *path2)
638 : : {
7266 bruce@momjian.us 639 : 72 : int path1_len = strlen(path1);
640 : :
7313 tgl@sss.pgh.pa.us 641 [ - + ]: 72 : if (strncmp(path1, path2, path1_len) == 0 &&
7313 tgl@sss.pgh.pa.us 642 [ # # # # ]:UBC 0 : (IS_DIR_SEP(path2[path1_len]) || path2[path1_len] == '\0'))
643 : 0 : return true;
7313 tgl@sss.pgh.pa.us 644 :CBC 72 : return false;
645 : : }
646 : :
647 : : /*
648 : : * Extracts the actual name of the program as called -
649 : : * stripped of .exe suffix if any
650 : : */
651 : : const char *
7787 bruce@momjian.us 652 : 29455 : get_progname(const char *argv0)
653 : : {
654 : : const char *nodir_name;
655 : : char *progname;
656 : :
7609 tgl@sss.pgh.pa.us 657 : 29455 : nodir_name = last_dir_separator(argv0);
658 [ + + ]: 29455 : if (nodir_name)
659 : 23406 : nodir_name++;
660 : : else
661 : 6049 : nodir_name = skip_drive(argv0);
662 : :
663 : : /*
664 : : * Make a copy in case argv[0] is modified by ps_status. Leaks memory, but
665 : : * called only once.
666 : : */
7157 bruce@momjian.us 667 : 29455 : progname = strdup(nodir_name);
668 [ - + ]: 29455 : if (progname == NULL)
669 : : {
7157 bruce@momjian.us 670 :UBC 0 : fprintf(stderr, "%s: out of memory\n", nodir_name);
4968 peter_e@gmx.net 671 : 0 : abort(); /* This could exit the postmaster */
672 : : }
673 : :
674 : : #if defined(__CYGWIN__) || defined(WIN32)
675 : : /* strip ".exe" suffix, regardless of case */
676 : : if (strlen(progname) > sizeof(EXE) - 1 &&
677 : : pg_strcasecmp(progname + strlen(progname) - (sizeof(EXE) - 1), EXE) == 0)
678 : : progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
679 : : #endif
680 : :
7157 bruce@momjian.us 681 :CBC 29455 : return progname;
682 : : }
683 : :
684 : :
685 : : /*
686 : : * dir_strcmp: strcmp except any two DIR_SEP characters are considered equal,
687 : : * and we honor filesystem case insensitivity if known
688 : : */
689 : : static int
7197 tgl@sss.pgh.pa.us 690 : 41164 : dir_strcmp(const char *s1, const char *s2)
691 : : {
692 [ + + + - ]: 164656 : while (*s1 && *s2)
693 : : {
6000 694 : 123492 : if (
695 : : #ifndef WIN32
696 [ - + ]: 123492 : *s1 != *s2
697 : : #else
698 : : /* On windows, paths are case-insensitive */
699 : : pg_tolower((unsigned char) *s1) != pg_tolower((unsigned char) *s2)
700 : : #endif
6000 tgl@sss.pgh.pa.us 701 [ # # # # ]:UBC 0 : && !(IS_DIR_SEP(*s1) && IS_DIR_SEP(*s2)))
7197 702 : 0 : return (int) *s1 - (int) *s2;
7197 tgl@sss.pgh.pa.us 703 :CBC 123492 : s1++, s2++;
704 : : }
705 [ - + ]: 41164 : if (*s1)
7197 tgl@sss.pgh.pa.us 706 :UBC 0 : return 1; /* s1 longer */
7197 tgl@sss.pgh.pa.us 707 [ - + ]:CBC 41164 : if (*s2)
7197 tgl@sss.pgh.pa.us 708 :UBC 0 : return -1; /* s2 longer */
7197 tgl@sss.pgh.pa.us 709 :CBC 41164 : return 0;
710 : : }
711 : :
712 : :
713 : : /*
714 : : * make_relative_path - make a path relative to the actual binary location
715 : : *
716 : : * This function exists to support relocation of installation trees.
717 : : *
718 : : * ret_path is the output area (must be of size MAXPGPATH)
719 : : * target_path is the compiled-in path to the directory we want to find
720 : : * bin_path is the compiled-in path to the directory of executables
721 : : * my_exec_path is the actual location of my executable
722 : : *
723 : : * We determine the common prefix of target_path and bin_path, then compare
724 : : * the remainder of bin_path to the last directory component(s) of
725 : : * my_exec_path. If they match, build the result as the part of my_exec_path
726 : : * preceding the match, joined to the remainder of target_path. If no match,
727 : : * return target_path as-is.
728 : : *
729 : : * For example:
730 : : * target_path = '/usr/local/share/postgresql'
731 : : * bin_path = '/usr/local/bin'
732 : : * my_exec_path = '/opt/pgsql/bin/postgres'
733 : : * Given these inputs, the common prefix is '/usr/local/', the tail of
734 : : * bin_path is 'bin' which does match the last directory component of
735 : : * my_exec_path, so we would return '/opt/pgsql/share/postgresql'
736 : : */
737 : : static void
7609 738 : 42142 : make_relative_path(char *ret_path, const char *target_path,
739 : : const char *bin_path, const char *my_exec_path)
740 : : {
741 : : int prefix_len;
742 : : int tail_start;
743 : : int tail_len;
744 : : int i;
745 : :
746 : : /*
747 : : * Determine the common prefix --- note we require it to end on a
748 : : * directory separator, consider eg '/usr/lib' and '/usr/libexec'.
749 : : */
7197 750 : 42142 : prefix_len = 0;
751 [ + - + - ]: 758556 : for (i = 0; target_path[i] && bin_path[i]; i++)
752 : : {
753 [ + + + - ]: 758556 : if (IS_DIR_SEP(target_path[i]) && IS_DIR_SEP(bin_path[i]))
754 : 168568 : prefix_len = i + 1;
755 [ + + ]: 589988 : else if (target_path[i] != bin_path[i])
756 : 42142 : break;
757 : : }
758 [ - + ]: 42142 : if (prefix_len == 0)
7197 tgl@sss.pgh.pa.us 759 :UBC 0 : goto no_match; /* no common prefix? */
7197 tgl@sss.pgh.pa.us 760 :CBC 42142 : tail_len = strlen(bin_path) - prefix_len;
761 : :
762 : : /*
763 : : * Set up my_exec_path without the actual executable name, and
764 : : * canonicalize to simplify comparison to bin_path.
765 : : */
6919 766 : 42142 : strlcpy(ret_path, my_exec_path, MAXPGPATH);
7609 767 : 42142 : trim_directory(ret_path); /* remove my executable name */
768 : 42142 : canonicalize_path(ret_path);
769 : :
770 : : /*
771 : : * Tail match?
772 : : */
7197 773 : 42142 : tail_start = (int) strlen(ret_path) - tail_len;
774 [ + - ]: 42142 : if (tail_start > 0 &&
6912 bruce@momjian.us 775 [ + + - + ]: 83306 : IS_DIR_SEP(ret_path[tail_start - 1]) &&
7197 tgl@sss.pgh.pa.us 776 : 41164 : dir_strcmp(ret_path + tail_start, bin_path + prefix_len) == 0)
777 : : {
778 : 41164 : ret_path[tail_start] = '\0';
779 : 41164 : trim_trailing_separator(ret_path);
780 : 41164 : join_path_components(ret_path, ret_path, target_path + prefix_len);
781 : 41164 : canonicalize_path(ret_path);
782 : 41164 : return;
783 : : }
784 : :
7609 785 : 978 : no_match:
6919 786 : 978 : strlcpy(ret_path, target_path, MAXPGPATH);
7727 bruce@momjian.us 787 : 978 : canonicalize_path(ret_path);
788 : : }
789 : :
790 : :
791 : : /*
792 : : * make_absolute_path
793 : : *
794 : : * If the given pathname isn't already absolute, make it so, interpreting
795 : : * it relative to the current working directory.
796 : : *
797 : : * Also canonicalizes the path. The result is always a malloc'd copy.
798 : : *
799 : : * In backend, failure cases result in ereport(ERROR); in frontend,
800 : : * we write a complaint on stderr and return NULL.
801 : : *
802 : : * Note: interpretation of relative-path arguments during postmaster startup
803 : : * should happen before doing ChangeToDataDir(), else the user will probably
804 : : * not like the results.
805 : : */
806 : : char *
4173 tgl@sss.pgh.pa.us 807 : 2542 : make_absolute_path(const char *path)
808 : : {
809 : : char *new;
810 : :
811 : : /* Returning null for null input is convenient for some callers */
812 [ - + ]: 2542 : if (path == NULL)
4173 tgl@sss.pgh.pa.us 813 :UBC 0 : return NULL;
814 : :
4173 tgl@sss.pgh.pa.us 815 [ + + ]:CBC 2542 : if (!is_absolute_path(path))
816 : : {
817 : : char *buf;
818 : : size_t buflen;
819 : :
820 : 3 : buflen = MAXPGPATH;
821 : : for (;;)
822 : : {
4173 tgl@sss.pgh.pa.us 823 :UBC 0 : buf = malloc(buflen);
4173 tgl@sss.pgh.pa.us 824 [ - + ]:CBC 3 : if (!buf)
825 : : {
826 : : #ifndef FRONTEND
4173 tgl@sss.pgh.pa.us 827 [ # # ]:UBC 0 : ereport(ERROR,
828 : : (errcode(ERRCODE_OUT_OF_MEMORY),
829 : : errmsg("out of memory")));
830 : : #else
831 : 0 : fprintf(stderr, _("out of memory\n"));
832 : 0 : return NULL;
833 : : #endif
834 : : }
835 : :
4173 tgl@sss.pgh.pa.us 836 [ + - ]:CBC 3 : if (getcwd(buf, buflen))
837 : 3 : break;
4173 tgl@sss.pgh.pa.us 838 [ # # ]:UBC 0 : else if (errno == ERANGE)
839 : : {
840 : 0 : free(buf);
841 : 0 : buflen *= 2;
842 : 0 : continue;
843 : : }
844 : : else
845 : : {
846 : 0 : int save_errno = errno;
847 : :
848 : 0 : free(buf);
849 : 0 : errno = save_errno;
850 : : #ifndef FRONTEND
851 [ # # ]: 0 : elog(ERROR, "could not get current working directory: %m");
852 : : #else
543 michael@paquier.xyz 853 : 0 : fprintf(stderr, _("could not get current working directory: %m\n"));
4173 tgl@sss.pgh.pa.us 854 : 0 : return NULL;
855 : : #endif
856 : : }
857 : : }
858 : :
4173 tgl@sss.pgh.pa.us 859 :CBC 3 : new = malloc(strlen(buf) + strlen(path) + 2);
860 [ - + ]: 3 : if (!new)
861 : : {
4173 tgl@sss.pgh.pa.us 862 :UBC 0 : free(buf);
863 : : #ifndef FRONTEND
864 [ # # ]: 0 : ereport(ERROR,
865 : : (errcode(ERRCODE_OUT_OF_MEMORY),
866 : : errmsg("out of memory")));
867 : : #else
868 : 0 : fprintf(stderr, _("out of memory\n"));
869 : 0 : return NULL;
870 : : #endif
871 : : }
4173 tgl@sss.pgh.pa.us 872 :CBC 3 : sprintf(new, "%s/%s", buf, path);
873 : 3 : free(buf);
874 : : }
875 : : else
876 : : {
877 : 2539 : new = strdup(path);
878 [ - + ]: 2539 : if (!new)
879 : : {
880 : : #ifndef FRONTEND
4173 tgl@sss.pgh.pa.us 881 [ # # ]:UBC 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 : : }
889 : : }
890 : :
891 : : /* Make sure punctuation is canonical, too */
4173 tgl@sss.pgh.pa.us 892 :CBC 2542 : canonicalize_path(new);
893 : :
894 : 2542 : return new;
895 : : }
896 : :
897 : :
898 : : /*
899 : : * get_share_path
900 : : */
901 : : void
7609 902 : 8414 : get_share_path(const char *my_exec_path, char *ret_path)
903 : : {
904 : 8414 : make_relative_path(ret_path, PGSHAREDIR, PGBINDIR, my_exec_path);
905 : 8414 : }
906 : :
907 : : /*
908 : : * get_etc_path
909 : : */
910 : : void
7782 bruce@momjian.us 911 : 11367 : get_etc_path(const char *my_exec_path, char *ret_path)
912 : : {
7609 tgl@sss.pgh.pa.us 913 : 11367 : make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path);
7782 bruce@momjian.us 914 : 11367 : }
915 : :
916 : : /*
917 : : * get_include_path
918 : : */
919 : : void
920 : 526 : get_include_path(const char *my_exec_path, char *ret_path)
921 : : {
7609 tgl@sss.pgh.pa.us 922 : 526 : make_relative_path(ret_path, INCLUDEDIR, PGBINDIR, my_exec_path);
7782 bruce@momjian.us 923 : 526 : }
924 : :
925 : : /*
926 : : * get_pkginclude_path
927 : : */
928 : : void
929 : 468 : get_pkginclude_path(const char *my_exec_path, char *ret_path)
930 : : {
7609 tgl@sss.pgh.pa.us 931 : 468 : make_relative_path(ret_path, PKGINCLUDEDIR, PGBINDIR, my_exec_path);
7782 bruce@momjian.us 932 : 468 : }
933 : :
934 : : /*
935 : : * get_includeserver_path
936 : : */
937 : : void
7706 938 : 457 : get_includeserver_path(const char *my_exec_path, char *ret_path)
939 : : {
7609 tgl@sss.pgh.pa.us 940 : 457 : make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path);
7706 bruce@momjian.us 941 : 457 : }
942 : :
943 : : /*
944 : : * get_lib_path
945 : : */
946 : : void
947 : 457 : get_lib_path(const char *my_exec_path, char *ret_path)
948 : : {
7609 tgl@sss.pgh.pa.us 949 : 457 : make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path);
7706 bruce@momjian.us 950 : 457 : }
951 : :
952 : : /*
953 : : * get_pkglib_path
954 : : */
955 : : void
7782 956 : 1981 : get_pkglib_path(const char *my_exec_path, char *ret_path)
957 : : {
7609 tgl@sss.pgh.pa.us 958 : 1981 : make_relative_path(ret_path, PKGLIBDIR, PGBINDIR, my_exec_path);
7782 bruce@momjian.us 959 : 1981 : }
960 : :
961 : : /*
962 : : * get_locale_path
963 : : */
964 : : void
7774 965 : 17101 : get_locale_path(const char *my_exec_path, char *ret_path)
966 : : {
7609 tgl@sss.pgh.pa.us 967 : 17101 : make_relative_path(ret_path, LOCALEDIR, PGBINDIR, my_exec_path);
7774 bruce@momjian.us 968 : 17101 : }
969 : :
970 : : /*
971 : : * get_doc_path
972 : : */
973 : : void
7284 tgl@sss.pgh.pa.us 974 : 457 : get_doc_path(const char *my_exec_path, char *ret_path)
975 : : {
976 : 457 : make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path);
977 : 457 : }
978 : :
979 : : /*
980 : : * get_html_path
981 : : */
982 : : void
6410 peter_e@gmx.net 983 : 457 : get_html_path(const char *my_exec_path, char *ret_path)
984 : : {
985 : 457 : make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path);
986 : 457 : }
987 : :
988 : : /*
989 : : * get_man_path
990 : : */
991 : : void
7284 tgl@sss.pgh.pa.us 992 : 457 : get_man_path(const char *my_exec_path, char *ret_path)
993 : : {
994 : 457 : make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path);
995 : 457 : }
996 : :
997 : :
998 : : /*
999 : : * get_home_path
1000 : : *
1001 : : * On Unix, this actually returns the user's home directory. On Windows
1002 : : * it returns the PostgreSQL-specific application data folder.
1003 : : */
1004 : : bool
7678 tgl@sss.pgh.pa.us 1005 :UBC 0 : get_home_path(char *ret_path)
1006 : : {
1007 : : #ifndef WIN32
1008 : : /*
1009 : : * We first consult $HOME. If that's unset, try to get the info from
1010 : : * <pwd.h>.
1011 : : */
1012 : : const char *home;
1013 : :
1336 1014 : 0 : home = getenv("HOME");
369 peter@eisentraut.org 1015 [ # # # # ]: 0 : if (home && home[0])
1016 : : {
1017 : 0 : strlcpy(ret_path, home, MAXPGPATH);
1018 : 0 : return true;
1019 : : }
1020 : : else
1021 : : {
1022 : : struct passwd pwbuf;
1023 : : struct passwd *pw;
1024 : : char buf[1024];
1025 : : int rc;
1026 : :
1027 : 0 : rc = getpwuid_r(geteuid(), &pwbuf, buf, sizeof buf, &pw);
1028 [ # # # # ]: 0 : if (rc != 0 || !pw)
1029 : 0 : return false;
1030 : 0 : strlcpy(ret_path, pw->pw_dir, MAXPGPATH);
1031 : 0 : return true;
1032 : : }
1033 : : #else
1034 : : char *tmppath;
1035 : :
1036 : : /*
1037 : : * Note: We use getenv() here because the more modern SHGetFolderPath()
1038 : : * would force the backend to link with shell32.lib, which eats valuable
1039 : : * desktop heap. XXX This function is used only in psql, which already
1040 : : * brings in shell32 via libpq. Moving this function to its own file
1041 : : * would keep it out of the backend, freeing it from this concern.
1042 : : */
1043 : : tmppath = getenv("APPDATA");
1044 : : if (!tmppath)
1045 : : return false;
1046 : : snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
1047 : : return true;
1048 : : #endif
1049 : : }
1050 : :
1051 : :
1052 : : /*
1053 : : * get_parent_directory
1054 : : *
1055 : : * Modify the given string in-place to name the parent directory of the
1056 : : * named file.
1057 : : *
1058 : : * If the input is just a file name with no directory part, the result is
1059 : : * an empty string, not ".". This is appropriate when the next step is
1060 : : * join_path_components(), but might need special handling otherwise.
1061 : : *
1062 : : * Caution: this will not produce desirable results if the string ends
1063 : : * with "..". For most callers this is not a problem since the string
1064 : : * is already known to name a regular file. If in doubt, apply
1065 : : * canonicalize_path() first.
1066 : : */
1067 : : void
7678 tgl@sss.pgh.pa.us 1068 :CBC 7598 : get_parent_directory(char *path)
1069 : : {
1070 : 7598 : trim_directory(path);
1071 : 7598 : }
1072 : :
1073 : :
1074 : : /*
1075 : : * trim_directory
1076 : : *
1077 : : * Trim trailing directory from path, that is, remove any trailing slashes,
1078 : : * the last pathname component, and the slash just ahead of it --- but never
1079 : : * remove a leading slash.
1080 : : *
1081 : : * For the convenience of canonicalize_path, the path's new end location
1082 : : * is returned.
1083 : : */
1084 : : static char *
7782 bruce@momjian.us 1085 : 49814 : trim_directory(char *path)
1086 : : {
1087 : : char *p;
1088 : :
7609 tgl@sss.pgh.pa.us 1089 : 49814 : path = skip_drive(path);
1090 : :
7782 bruce@momjian.us 1091 [ - + ]: 49814 : if (path[0] == '\0')
1314 tgl@sss.pgh.pa.us 1092 :UBC 0 : return path;
1093 : :
1094 : : /* back up over trailing slash(es) */
7758 bruce@momjian.us 1095 [ - + - - ]:CBC 49814 : for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
1096 : : ;
1097 : : /* back up over directory name */
1098 [ + + + + ]: 493173 : for (; !IS_DIR_SEP(*p) && p > path; p--)
1099 : : ;
1100 : : /* if multiple slashes before directory name, remove 'em all */
7609 tgl@sss.pgh.pa.us 1101 [ + + - + ]: 49814 : for (; p > path && IS_DIR_SEP(*(p - 1)); p--)
1102 : : ;
1103 : : /* don't erase a leading slash */
1104 [ + + + + ]: 49814 : if (p == path && IS_DIR_SEP(*p))
1105 : 15 : p++;
7782 bruce@momjian.us 1106 : 49814 : *p = '\0';
1314 tgl@sss.pgh.pa.us 1107 : 49814 : return p;
1108 : : }
1109 : :
1110 : :
1111 : : /*
1112 : : * trim_trailing_separator
1113 : : *
1114 : : * trim off trailing slashes, but not a leading slash
1115 : : */
1116 : : static void
7782 bruce@momjian.us 1117 : 194546 : trim_trailing_separator(char *path)
1118 : : {
1119 : : char *p;
1120 : :
7609 tgl@sss.pgh.pa.us 1121 : 194546 : path = skip_drive(path);
1122 : 194546 : p = path + strlen(path);
7782 bruce@momjian.us 1123 [ + + ]: 194546 : if (p > path)
7728 tgl@sss.pgh.pa.us 1124 [ + + + + ]: 235687 : for (p--; p > path && IS_DIR_SEP(*p); p--)
7782 bruce@momjian.us 1125 : 41183 : *p = '\0';
1126 : 194546 : }
1127 : :
1128 : : /*
1129 : : * append_subdir_to_path
1130 : : *
1131 : : * Append the currently-considered subdirectory name to the output
1132 : : * path in canonicalize_path. Return the new end location of the
1133 : : * output path.
1134 : : *
1135 : : * Since canonicalize_path updates the path in-place, we must use
1136 : : * memmove not memcpy, and we don't yet terminate the path with '\0'.
1137 : : */
1138 : : static char *
1314 tgl@sss.pgh.pa.us 1139 : 1427150 : append_subdir_to_path(char *path, char *subdir)
1140 : : {
1141 : 1427150 : size_t len = strlen(subdir);
1142 : :
1143 : : /* No need to copy data if path and subdir are the same. */
1144 [ + + ]: 1427150 : if (path != subdir)
1145 : 73914 : memmove(path, subdir, len);
1146 : :
1147 : 1427150 : return path + len;
1148 : : }
|