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-2024, 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 <unistd.h>
36 : : #endif
37 : :
38 : : #include "pg_config_paths.h"
39 : :
40 : :
41 : : #ifndef WIN32
42 : : #define IS_PATH_VAR_SEP(ch) ((ch) == ':')
43 : : #else
44 : : #define IS_PATH_VAR_SEP(ch) ((ch) == ';')
45 : : #endif
46 : :
47 : : static void make_relative_path(char *ret_path, const char *target_path,
48 : : const char *bin_path, const char *my_exec_path);
49 : : static char *trim_directory(char *path);
50 : : static void trim_trailing_separator(char *path);
51 : : static char *append_subdir_to_path(char *path, char *subdir);
52 : :
53 : :
54 : : /*
55 : : * skip_drive
56 : : *
57 : : * On Windows, a path may begin with "C:" or "//network/". Advance over
58 : : * this and point to the effective start of the path.
59 : : */
60 : : #ifdef WIN32
61 : :
62 : : static char *
63 : : skip_drive(const char *path)
64 : : {
65 : : if (IS_DIR_SEP(path[0]) && IS_DIR_SEP(path[1]))
66 : : {
67 : : path += 2;
68 : : while (*path && !IS_DIR_SEP(*path))
69 : : path++;
70 : : }
71 : : else if (isalpha((unsigned char) path[0]) && path[1] == ':')
72 : : {
73 : : path += 2;
74 : : }
75 : : return (char *) path;
76 : : }
77 : : #else
78 : :
79 : : #define skip_drive(path) (path)
80 : : #endif
81 : :
82 : : /*
83 : : * has_drive_prefix
84 : : *
85 : : * Return true if the given pathname has a drive prefix.
86 : : */
87 : : bool
4666 rhaas@postgresql.org 88 :UBC 0 : has_drive_prefix(const char *path)
89 : : {
90 : : #ifdef WIN32
91 : : return skip_drive(path) != path;
92 : : #else
2977 peter_e@gmx.net 93 : 0 : return false;
94 : : #endif
95 : : }
96 : :
97 : : /*
98 : : * first_dir_separator
99 : : *
100 : : * Find the location of the first directory separator, return
101 : : * NULL if not found.
102 : : */
103 : : char *
7248 bruce@momjian.us 104 :CBC 44853 : first_dir_separator(const char *filename)
105 : : {
106 : : const char *p;
107 : :
7099 tgl@sss.pgh.pa.us 108 [ + + ]: 320560 : for (p = skip_drive(filename); *p; p++)
7248 bruce@momjian.us 109 [ + + ]: 304794 : if (IS_DIR_SEP(*p))
1902 peter@eisentraut.org 110 : 29087 : return unconstify(char *, p);
7248 bruce@momjian.us 111 : 15766 : return NULL;
112 : : }
113 : :
114 : : /*
115 : : * first_path_var_separator
116 : : *
117 : : * Find the location of the first path separator (i.e. ':' on
118 : : * Unix, ';' on Windows), return NULL if not found.
119 : : */
120 : : char *
4820 121 : 8541 : first_path_var_separator(const char *pathlist)
122 : : {
123 : : const char *p;
124 : :
125 : : /* skip_drive is not needed */
7099 tgl@sss.pgh.pa.us 126 [ + + ]: 480510 : for (p = pathlist; *p; p++)
4820 bruce@momjian.us 127 [ + + ]: 476010 : if (IS_PATH_VAR_SEP(*p))
1902 peter@eisentraut.org 128 : 4041 : return unconstify(char *, p);
7272 bruce@momjian.us 129 : 4500 : return NULL;
130 : : }
131 : :
132 : : /*
133 : : * last_dir_separator
134 : : *
135 : : * Find the location of the last directory separator, return
136 : : * NULL if not found.
137 : : */
138 : : char *
7248 139 : 156503 : last_dir_separator(const char *filename)
140 : : {
141 : : const char *p,
7168 142 : 156503 : *ret = NULL;
143 : :
7099 tgl@sss.pgh.pa.us 144 [ + + ]: 4246767 : for (p = skip_drive(filename); *p; p++)
7248 bruce@momjian.us 145 [ + + ]: 4090264 : if (IS_DIR_SEP(*p))
7272 146 : 514919 : ret = p;
1902 peter@eisentraut.org 147 : 156503 : return unconstify(char *, ret);
148 : : }
149 : :
150 : :
151 : : /*
152 : : * make_native_path - on WIN32, change / to \ in the path
153 : : *
154 : : * This effectively undoes canonicalize_path.
155 : : *
156 : : * This is required because WIN32 COPY is an internal CMD.EXE
157 : : * command and doesn't process forward slashes in the same way
158 : : * as external commands. Quoting the first argument to COPY
159 : : * does not convert forward to backward slashes, but COPY does
160 : : * properly process quoted forward slashes in the second argument.
161 : : *
162 : : * COPY works with quoted forward slashes in the first argument
163 : : * only if the current directory is the same as the directory
164 : : * of the first argument.
165 : : */
166 : : void
7185 bruce@momjian.us 167 : 454 : make_native_path(char *filename)
168 : : {
169 : : #ifdef WIN32
170 : : char *p;
171 : :
172 : : for (p = filename; *p; p++)
173 : : if (*p == '/')
174 : : *p = '\\';
175 : : #endif
176 : 454 : }
177 : :
178 : :
179 : : /*
180 : : * This function cleans up the paths for use with either cmd.exe or Msys
181 : : * on Windows. We need them to use filenames without spaces, for which a
182 : : * short filename is the safest equivalent, eg:
183 : : * C:/Progra~1/
184 : : */
185 : : void
2979 mail@joeconway.com 186 : 5200 : cleanup_path(char *path)
187 : : {
188 : : #ifdef WIN32
189 : : char *ptr;
190 : :
191 : : /*
192 : : * GetShortPathName() will fail if the path does not exist, or short names
193 : : * are disabled on this file system. In both cases, we just return the
194 : : * original path. This is particularly useful for --sysconfdir, which
195 : : * might not exist.
196 : : */
197 : : GetShortPathName(path, path, MAXPGPATH - 1);
198 : :
199 : : /* Replace '\' with '/' */
200 : : for (ptr = path; *ptr; ptr++)
201 : : {
202 : : if (*ptr == '\\')
203 : : *ptr = '/';
204 : : }
205 : : #endif
206 : 5200 : }
207 : :
208 : :
209 : : /*
210 : : * join_path_components - join two path components, inserting a slash
211 : : *
212 : : * We omit the slash if either given component is empty.
213 : : *
214 : : * ret_path is the output area (must be of size MAXPGPATH)
215 : : *
216 : : * ret_path can be the same as head, but not the same as tail.
217 : : */
218 : : void
7099 tgl@sss.pgh.pa.us 219 : 48000 : join_path_components(char *ret_path,
220 : : const char *head, const char *tail)
221 : : {
222 [ + + ]: 48000 : if (ret_path != head)
6409 223 : 1721 : strlcpy(ret_path, head, MAXPGPATH);
224 : :
225 : : /*
226 : : * We used to try to simplify some cases involving "." and "..", but now
227 : : * we just leave that to be done by canonicalize_path() later.
228 : : */
229 : :
7099 230 [ + - ]: 48000 : if (*tail)
231 : : {
232 : : /* only separate with slash if head wasn't empty */
233 : 30369 : snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path),
234 : : "%s%s",
4301 235 [ + + ]: 48000 : (*(skip_drive(head)) != '\0') ? "/" : "",
236 : : tail);
237 : : }
7099 238 : 48000 : }
239 : :
240 : :
241 : : /* State-machine states for canonicalize_path */
242 : : typedef enum
243 : : {
244 : : ABSOLUTE_PATH_INIT, /* Just past the leading '/' (and Windows
245 : : * drive name if any) of an absolute path */
246 : : ABSOLUTE_WITH_N_DEPTH, /* We collected 'pathdepth' directories in an
247 : : * absolute path */
248 : : RELATIVE_PATH_INIT, /* At start of a relative path */
249 : : RELATIVE_WITH_N_DEPTH, /* We collected 'pathdepth' directories in a
250 : : * relative path */
251 : : RELATIVE_WITH_PARENT_REF, /* Relative path containing only double-dots */
252 : : } canonicalize_state;
253 : :
254 : : /*
255 : : * Clean up path by:
256 : : * o make Win32 path use Unix slashes
257 : : * o remove trailing quote on Win32
258 : : * o remove trailing slash
259 : : * o remove duplicate (adjacent) separators
260 : : * o remove '.' (unless path reduces to only '.')
261 : : * o process '..' ourselves, removing it if possible
262 : : */
263 : : void
7341 bruce@momjian.us 264 : 114285 : canonicalize_path(char *path)
265 : : {
266 : : char *p,
267 : : *to_p;
268 : : char *spath;
269 : : char *parsed;
270 : : char *unparse;
7098 271 : 114285 : bool was_sep = false;
272 : : canonicalize_state state;
804 tgl@sss.pgh.pa.us 273 : 114285 : int pathdepth = 0; /* counts collected regular directory names */
274 : :
275 : : #ifdef WIN32
276 : :
277 : : /*
278 : : * The Windows command processor will accept suitably quoted paths with
279 : : * forward slashes, but barfs badly with mixed forward and back slashes.
280 : : */
281 : : for (p = path; *p; p++)
282 : : {
283 : : if (*p == '\\')
284 : : *p = '/';
285 : : }
286 : :
287 : : /*
288 : : * In Win32, if you do: prog.exe "a b" "\c\d\" the system will pass \c\d"
289 : : * as argv[2], so trim off trailing quote.
290 : : */
291 : : if (p > path && *(p - 1) == '"')
292 : : *(p - 1) = '/';
293 : : #endif
294 : :
295 : : /*
296 : : * Removing the trailing slash on a path means we never get ugly double
297 : : * trailing slashes. Also, Win32 can't stat() a directory with a trailing
298 : : * slash. Don't remove a leading slash, though.
299 : : */
7272 bruce@momjian.us 300 : 114285 : trim_trailing_separator(path);
301 : :
302 : : /*
303 : : * Remove duplicate adjacent separators
304 : : */
7098 305 : 114285 : p = path;
306 : : #ifdef WIN32
307 : : /* Don't remove leading double-slash on Win32 */
308 : : if (*p)
309 : : p++;
310 : : #endif
311 : 114285 : to_p = p;
312 [ + + ]: 11701201 : for (; *p; p++, to_p++)
313 : : {
314 : : /* Handle many adjacent slashes, like "/a///b" */
315 [ + + + + ]: 11590957 : while (*p == '/' && was_sep)
316 : 4041 : p++;
317 [ + + ]: 11586916 : if (to_p != p)
318 : 230951 : *to_p = *p;
319 : 11586916 : was_sep = (*p == '/');
320 : : }
321 : 114285 : *to_p = '\0';
322 : :
323 : : /*
324 : : * Remove any uses of "." and process ".." ourselves
325 : : *
326 : : * Note that "/../.." should reduce to just "/", while "../.." has to be
327 : : * kept as-is. Also note that we want a Windows drive spec to be visible
328 : : * to trim_directory(), but it's not part of the logic that's looking at
329 : : * the name components; hence distinction between path and spath.
330 : : *
331 : : * This loop overwrites the path in-place. This is safe since we'll never
332 : : * make the path longer. "unparse" points to where we are reading the
333 : : * path, "parse" to where we are writing.
334 : : */
6820 tgl@sss.pgh.pa.us 335 : 114285 : spath = skip_drive(path);
804 336 [ + + ]: 114285 : if (*spath == '\0')
337 : 33 : return; /* empty path is returned as-is */
338 : :
339 [ + + ]: 114252 : if (*spath == '/')
340 : : {
341 : 103548 : state = ABSOLUTE_PATH_INIT;
342 : : /* Skip the leading slash for absolute path */
343 : 103548 : parsed = unparse = (spath + 1);
344 : : }
345 : : else
346 : : {
347 : 10704 : state = RELATIVE_PATH_INIT;
348 : 10704 : parsed = unparse = spath;
349 : : }
350 : :
351 [ + + ]: 1389492 : while (*unparse != '\0')
352 : : {
353 : : char *unparse_next;
354 : : bool is_double_dot;
355 : :
356 : : /* Split off this dir name, and set unparse_next to the next one */
357 : 1275240 : unparse_next = unparse;
358 [ + + + + ]: 11597617 : while (*unparse_next && *unparse_next != '/')
359 : 10322377 : unparse_next++;
360 [ + + ]: 1275240 : if (*unparse_next != '\0')
361 : 1160991 : *unparse_next++ = '\0';
362 : :
363 : : /* Identify type of this dir name */
364 [ + + ]: 1275240 : if (strcmp(unparse, ".") == 0)
365 : : {
366 : : /* We can ignore "." components in all cases */
367 : 74 : unparse = unparse_next;
368 : 74 : continue;
369 : : }
370 : :
371 [ + + ]: 1275166 : if (strcmp(unparse, "..") == 0)
372 : 113 : is_double_dot = true;
373 : : else
374 : : {
375 : : /* adjacent separators were eliminated above */
376 [ - + ]: 1275053 : Assert(*unparse != '\0');
377 : 1275053 : is_double_dot = false;
378 : : }
379 : :
380 [ + + + + : 1275166 : switch (state)
+ - ]
381 : : {
382 : 103572 : case ABSOLUTE_PATH_INIT:
383 : : /* We can ignore ".." immediately after / */
384 [ + + ]: 103572 : if (!is_double_dot)
385 : : {
386 : : /* Append first dir name (we already have leading slash) */
387 : 103554 : parsed = append_subdir_to_path(parsed, unparse);
388 : 103554 : state = ABSOLUTE_WITH_N_DEPTH;
389 : 103554 : pathdepth++;
390 : : }
391 : 103572 : break;
392 : 1143165 : case ABSOLUTE_WITH_N_DEPTH:
393 [ + + ]: 1143165 : if (is_double_dot)
394 : : {
395 : : /* Remove last parsed dir */
396 : : /* (trim_directory won't remove the leading slash) */
397 : 47 : *parsed = '\0';
398 : 47 : parsed = trim_directory(path);
399 [ + + ]: 47 : if (--pathdepth == 0)
400 : 15 : state = ABSOLUTE_PATH_INIT;
401 : : }
402 : : else
403 : : {
404 : : /* Append normal dir */
405 : 1143118 : *parsed++ = '/';
406 : 1143118 : parsed = append_subdir_to_path(parsed, unparse);
407 : 1143118 : pathdepth++;
408 : : }
409 : 1143165 : break;
410 : 10686 : case RELATIVE_PATH_INIT:
411 [ + + ]: 10686 : if (is_double_dot)
412 : : {
413 : : /* Append irreducible double-dot (..) */
414 : 18 : parsed = append_subdir_to_path(parsed, unparse);
415 : 18 : state = RELATIVE_WITH_PARENT_REF;
416 : : }
417 : : else
418 : : {
419 : : /* Append normal dir */
420 : 10668 : parsed = append_subdir_to_path(parsed, unparse);
421 : 10668 : state = RELATIVE_WITH_N_DEPTH;
422 : 10668 : pathdepth++;
423 : : }
424 : 10686 : break;
425 : 17719 : case RELATIVE_WITH_N_DEPTH:
426 [ + + ]: 17719 : if (is_double_dot)
427 : : {
428 : : /* Remove last parsed dir */
429 : 27 : *parsed = '\0';
430 : 27 : parsed = trim_directory(path);
431 [ + + ]: 27 : if (--pathdepth == 0)
432 : : {
433 : : /*
434 : : * If the output path is now empty, we're back to the
435 : : * INIT state. However, we could have processed a
436 : : * path like "../dir/.." and now be down to "..", in
437 : : * which case enter the correct state for that.
438 : : */
439 [ + + ]: 21 : if (parsed == spath)
440 : 12 : state = RELATIVE_PATH_INIT;
441 : : else
442 : 9 : state = RELATIVE_WITH_PARENT_REF;
443 : : }
444 : : }
445 : : else
446 : : {
447 : : /* Append normal dir */
448 : 17692 : *parsed++ = '/';
449 : 17692 : parsed = append_subdir_to_path(parsed, unparse);
450 : 17692 : pathdepth++;
451 : : }
452 : 17719 : break;
453 : 24 : case RELATIVE_WITH_PARENT_REF:
454 [ + + ]: 24 : if (is_double_dot)
455 : : {
456 : : /* Append next irreducible double-dot (..) */
457 : 3 : *parsed++ = '/';
458 : 3 : parsed = append_subdir_to_path(parsed, unparse);
459 : : }
460 : : else
461 : : {
462 : : /* Append normal dir */
463 : 21 : *parsed++ = '/';
464 : 21 : parsed = append_subdir_to_path(parsed, unparse);
465 : :
466 : : /*
467 : : * We can now start counting normal dirs. But if later
468 : : * double-dots make us remove this dir again, we'd better
469 : : * revert to RELATIVE_WITH_PARENT_REF not INIT state.
470 : : */
471 : 21 : state = RELATIVE_WITH_N_DEPTH;
472 : 21 : pathdepth = 1;
473 : : }
474 : 24 : break;
475 : : }
476 : :
477 : 1275166 : unparse = unparse_next;
478 : : }
479 : :
480 : : /*
481 : : * If our output path is empty at this point, insert ".". We don't want
482 : : * to do this any earlier because it'd result in an extra dot in corner
483 : : * cases such as "../dir/..". Since we rejected the wholly-empty-path
484 : : * case above, there is certainly room.
485 : : */
486 [ + + ]: 114252 : if (parsed == spath)
487 : 30 : *parsed++ = '.';
488 : :
489 : : /* And finally, ensure the output path is nul-terminated. */
490 : 114252 : *parsed = '\0';
491 : : }
492 : :
493 : : /*
494 : : * Detect whether a path contains any parent-directory references ("..")
495 : : *
496 : : * The input *must* have been put through canonicalize_path previously.
497 : : */
498 : : bool
6820 499 : 6886 : path_contains_parent_reference(const char *path)
500 : : {
501 : : /*
502 : : * Once canonicalized, an absolute path cannot contain any ".." at all,
503 : : * while a relative path could contain ".."(s) only at the start. So it
504 : : * is sufficient to check the start of the path, after skipping any
505 : : * Windows drive/network specifier.
506 : : */
804 507 : 6886 : path = skip_drive(path); /* C: shouldn't affect our conclusion */
508 : :
509 [ + + ]: 6886 : if (path[0] == '.' &&
510 [ - + ]: 4 : path[1] == '.' &&
804 tgl@sss.pgh.pa.us 511 [ # # # # ]:LBC (1) : (path[2] == '\0' || path[2] == '/'))
6820 512 : (1) : return true;
513 : :
6820 tgl@sss.pgh.pa.us 514 :CBC 6886 : return false;
515 : : }
516 : :
517 : : /*
518 : : * Detect whether a path is only in or below the current working directory.
519 : : *
520 : : * The input *must* have been put through canonicalize_path previously.
521 : : *
522 : : * An absolute path that matches the current working directory should
523 : : * return false (we only want relative to the cwd).
524 : : */
525 : : bool
4810 bruce@momjian.us 526 : 6886 : path_is_relative_and_below_cwd(const char *path)
527 : : {
4809 528 [ - + ]: 6886 : if (is_absolute_path(path))
4810 bruce@momjian.us 529 :UBC 0 : return false;
530 : : /* don't allow anything above the cwd */
4810 bruce@momjian.us 531 [ - + ]:CBC 6886 : else if (path_contains_parent_reference(path))
4810 bruce@momjian.us 532 :LBC (1) : return false;
533 : : #ifdef WIN32
534 : :
535 : : /*
536 : : * On Win32, a drive letter _not_ followed by a slash, e.g. 'E:abc', is
537 : : * relative to the cwd on that drive, or the drive's root directory if
538 : : * that drive has no cwd. Because the path itself cannot tell us which is
539 : : * the case, we have to assume the worst, i.e. that it is not below the
540 : : * cwd. We could use GetFullPathName() to find the full path but that
541 : : * could change if the current directory for the drive changes underneath
542 : : * us, so we just disallow it.
543 : : */
544 : : else if (isalpha((unsigned char) path[0]) && path[1] == ':' &&
545 : : !IS_DIR_SEP(path[2]))
546 : : return false;
547 : : #endif
548 : : else
4753 bruce@momjian.us 549 :CBC 6886 : return true;
550 : : }
551 : :
552 : : /*
553 : : * Detect whether path1 is a prefix of path2 (including equality).
554 : : *
555 : : * This is pretty trivial, but it seems better to export a function than
556 : : * to export IS_DIR_SEP.
557 : : */
558 : : bool
6803 tgl@sss.pgh.pa.us 559 : 53 : path_is_prefix_of_path(const char *path1, const char *path2)
560 : : {
6756 bruce@momjian.us 561 : 53 : int path1_len = strlen(path1);
562 : :
6803 tgl@sss.pgh.pa.us 563 [ - + ]: 53 : if (strncmp(path1, path2, path1_len) == 0 &&
6803 tgl@sss.pgh.pa.us 564 [ # # # # ]:LBC (1) : (IS_DIR_SEP(path2[path1_len]) || path2[path1_len] == '\0'))
565 : (1) : return true;
6803 tgl@sss.pgh.pa.us 566 :CBC 53 : return false;
567 : : }
568 : :
569 : : /*
570 : : * Extracts the actual name of the program as called -
571 : : * stripped of .exe suffix if any
572 : : */
573 : : const char *
7277 bruce@momjian.us 574 : 25587 : get_progname(const char *argv0)
575 : : {
576 : : const char *nodir_name;
577 : : char *progname;
578 : :
7099 tgl@sss.pgh.pa.us 579 : 25587 : nodir_name = last_dir_separator(argv0);
580 [ + + ]: 25587 : if (nodir_name)
581 : 19905 : nodir_name++;
582 : : else
583 : 5682 : nodir_name = skip_drive(argv0);
584 : :
585 : : /*
586 : : * Make a copy in case argv[0] is modified by ps_status. Leaks memory, but
587 : : * called only once.
588 : : */
6647 bruce@momjian.us 589 : 25587 : progname = strdup(nodir_name);
590 [ - + ]: 25587 : if (progname == NULL)
591 : : {
6647 bruce@momjian.us 592 :UBC 0 : fprintf(stderr, "%s: out of memory\n", nodir_name);
4458 peter_e@gmx.net 593 : 0 : abort(); /* This could exit the postmaster */
594 : : }
595 : :
596 : : #if defined(__CYGWIN__) || defined(WIN32)
597 : : /* strip ".exe" suffix, regardless of case */
598 : : if (strlen(progname) > sizeof(EXE) - 1 &&
599 : : pg_strcasecmp(progname + strlen(progname) - (sizeof(EXE) - 1), EXE) == 0)
600 : : progname[strlen(progname) - (sizeof(EXE) - 1)] = '\0';
601 : : #endif
602 : :
6647 bruce@momjian.us 603 :CBC 25587 : return progname;
604 : : }
605 : :
606 : :
607 : : /*
608 : : * dir_strcmp: strcmp except any two DIR_SEP characters are considered equal,
609 : : * and we honor filesystem case insensitivity if known
610 : : */
611 : : static int
6687 tgl@sss.pgh.pa.us 612 : 42124 : dir_strcmp(const char *s1, const char *s2)
613 : : {
614 [ + + + - ]: 168496 : while (*s1 && *s2)
615 : : {
5490 616 : 126372 : if (
617 : : #ifndef WIN32
618 [ - + ]: 126372 : *s1 != *s2
619 : : #else
620 : : /* On windows, paths are case-insensitive */
621 : : pg_tolower((unsigned char) *s1) != pg_tolower((unsigned char) *s2)
622 : : #endif
5490 tgl@sss.pgh.pa.us 623 [ # # # # ]:UBC 0 : && !(IS_DIR_SEP(*s1) && IS_DIR_SEP(*s2)))
6687 624 : 0 : return (int) *s1 - (int) *s2;
6687 tgl@sss.pgh.pa.us 625 :CBC 126372 : s1++, s2++;
626 : : }
627 [ - + ]: 42124 : if (*s1)
6687 tgl@sss.pgh.pa.us 628 :UBC 0 : return 1; /* s1 longer */
6687 tgl@sss.pgh.pa.us 629 [ - + ]:CBC 42124 : if (*s2)
6687 tgl@sss.pgh.pa.us 630 :UBC 0 : return -1; /* s2 longer */
6687 tgl@sss.pgh.pa.us 631 :CBC 42124 : return 0;
632 : : }
633 : :
634 : :
635 : : /*
636 : : * make_relative_path - make a path relative to the actual binary location
637 : : *
638 : : * This function exists to support relocation of installation trees.
639 : : *
640 : : * ret_path is the output area (must be of size MAXPGPATH)
641 : : * target_path is the compiled-in path to the directory we want to find
642 : : * bin_path is the compiled-in path to the directory of executables
643 : : * my_exec_path is the actual location of my executable
644 : : *
645 : : * We determine the common prefix of target_path and bin_path, then compare
646 : : * the remainder of bin_path to the last directory component(s) of
647 : : * my_exec_path. If they match, build the result as the part of my_exec_path
648 : : * preceding the match, joined to the remainder of target_path. If no match,
649 : : * return target_path as-is.
650 : : *
651 : : * For example:
652 : : * target_path = '/usr/local/share/postgresql'
653 : : * bin_path = '/usr/local/bin'
654 : : * my_exec_path = '/opt/pgsql/bin/postgres'
655 : : * Given these inputs, the common prefix is '/usr/local/', the tail of
656 : : * bin_path is 'bin' which does match the last directory component of
657 : : * my_exec_path, so we would return '/opt/pgsql/share/postgresql'
658 : : */
659 : : static void
7099 660 : 43036 : make_relative_path(char *ret_path, const char *target_path,
661 : : const char *bin_path, const char *my_exec_path)
662 : : {
663 : : int prefix_len;
664 : : int tail_start;
665 : : int tail_len;
666 : : int i;
667 : :
668 : : /*
669 : : * Determine the common prefix --- note we require it to end on a
670 : : * directory separator, consider eg '/usr/lib' and '/usr/libexec'.
671 : : */
6687 672 : 43036 : prefix_len = 0;
673 [ + - + - ]: 2108764 : for (i = 0; target_path[i] && bin_path[i]; i++)
674 : : {
675 [ + + + - ]: 2108764 : if (IS_DIR_SEP(target_path[i]) && IS_DIR_SEP(bin_path[i]))
676 : 258216 : prefix_len = i + 1;
677 [ + + ]: 1850548 : else if (target_path[i] != bin_path[i])
678 : 43036 : break;
679 : : }
680 [ - + ]: 43036 : if (prefix_len == 0)
6687 tgl@sss.pgh.pa.us 681 :UBC 0 : goto no_match; /* no common prefix? */
6687 tgl@sss.pgh.pa.us 682 :CBC 43036 : tail_len = strlen(bin_path) - prefix_len;
683 : :
684 : : /*
685 : : * Set up my_exec_path without the actual executable name, and
686 : : * canonicalize to simplify comparison to bin_path.
687 : : */
6409 688 : 43036 : strlcpy(ret_path, my_exec_path, MAXPGPATH);
7099 689 : 43036 : trim_directory(ret_path); /* remove my executable name */
690 : 43036 : canonicalize_path(ret_path);
691 : :
692 : : /*
693 : : * Tail match?
694 : : */
6687 695 : 43036 : tail_start = (int) strlen(ret_path) - tail_len;
696 [ + - ]: 43036 : if (tail_start > 0 &&
6402 bruce@momjian.us 697 [ + + - + ]: 85160 : IS_DIR_SEP(ret_path[tail_start - 1]) &&
6687 tgl@sss.pgh.pa.us 698 : 42124 : dir_strcmp(ret_path + tail_start, bin_path + prefix_len) == 0)
699 : : {
700 : 42124 : ret_path[tail_start] = '\0';
701 : 42124 : trim_trailing_separator(ret_path);
702 : 42124 : join_path_components(ret_path, ret_path, target_path + prefix_len);
703 : 42124 : canonicalize_path(ret_path);
704 : 42124 : return;
705 : : }
706 : :
7099 tgl@sss.pgh.pa.us 707 :UBC 0 : no_match:
6409 tgl@sss.pgh.pa.us 708 :CBC 912 : strlcpy(ret_path, target_path, MAXPGPATH);
7217 bruce@momjian.us 709 : 912 : canonicalize_path(ret_path);
710 : : }
711 : :
712 : :
713 : : /*
714 : : * make_absolute_path
715 : : *
716 : : * If the given pathname isn't already absolute, make it so, interpreting
717 : : * it relative to the current working directory.
718 : : *
719 : : * Also canonicalizes the path. The result is always a malloc'd copy.
720 : : *
721 : : * In backend, failure cases result in ereport(ERROR); in frontend,
722 : : * we write a complaint on stderr and return NULL.
723 : : *
724 : : * Note: interpretation of relative-path arguments during postmaster startup
725 : : * should happen before doing ChangeToDataDir(), else the user will probably
726 : : * not like the results.
727 : : */
728 : : char *
3663 tgl@sss.pgh.pa.us 729 : 2253 : make_absolute_path(const char *path)
730 : : {
731 : : char *new;
732 : :
733 : : /* Returning null for null input is convenient for some callers */
734 [ - + ]: 2253 : if (path == NULL)
3663 tgl@sss.pgh.pa.us 735 :UBC 0 : return NULL;
736 : :
3663 tgl@sss.pgh.pa.us 737 [ + + ]:CBC 2253 : if (!is_absolute_path(path))
738 : : {
739 : : char *buf;
740 : : size_t buflen;
741 : :
742 : 3 : buflen = MAXPGPATH;
743 : : for (;;)
744 : : {
3663 tgl@sss.pgh.pa.us 745 :UBC 0 : buf = malloc(buflen);
3663 tgl@sss.pgh.pa.us 746 [ - + ]:CBC 3 : if (!buf)
747 : : {
748 : : #ifndef FRONTEND
3663 tgl@sss.pgh.pa.us 749 [ # # ]:UBC 0 : ereport(ERROR,
750 : : (errcode(ERRCODE_OUT_OF_MEMORY),
751 : : errmsg("out of memory")));
752 : : #else
753 : 0 : fprintf(stderr, _("out of memory\n"));
754 : 0 : return NULL;
755 : : #endif
756 : : }
757 : :
3663 tgl@sss.pgh.pa.us 758 [ + - ]:CBC 3 : if (getcwd(buf, buflen))
759 : 3 : break;
3663 tgl@sss.pgh.pa.us 760 [ # # ]:UBC 0 : else if (errno == ERANGE)
761 : : {
762 : 0 : free(buf);
763 : 0 : buflen *= 2;
764 : 0 : continue;
765 : : }
766 : : else
767 : : {
768 : 0 : int save_errno = errno;
769 : :
770 : 0 : free(buf);
771 : 0 : errno = save_errno;
772 : : #ifndef FRONTEND
773 [ # # ]: 0 : elog(ERROR, "could not get current working directory: %m");
774 : : #else
33 michael@paquier.xyz 775 :UNC 0 : fprintf(stderr, _("could not get current working directory: %m\n"));
3663 tgl@sss.pgh.pa.us 776 :UBC 0 : return NULL;
777 : : #endif
778 : : }
779 : : }
780 : :
3663 tgl@sss.pgh.pa.us 781 :CBC 3 : new = malloc(strlen(buf) + strlen(path) + 2);
782 [ - + ]: 3 : if (!new)
783 : : {
3663 tgl@sss.pgh.pa.us 784 :UBC 0 : free(buf);
785 : : #ifndef FRONTEND
786 [ # # ]: 0 : ereport(ERROR,
787 : : (errcode(ERRCODE_OUT_OF_MEMORY),
788 : : errmsg("out of memory")));
789 : : #else
790 : 0 : fprintf(stderr, _("out of memory\n"));
791 : 0 : return NULL;
792 : : #endif
793 : : }
3663 tgl@sss.pgh.pa.us 794 :CBC 3 : sprintf(new, "%s/%s", buf, path);
795 : 3 : free(buf);
796 : : }
797 : : else
798 : : {
799 : 2250 : new = strdup(path);
800 [ - + ]: 2250 : if (!new)
801 : : {
802 : : #ifndef FRONTEND
3663 tgl@sss.pgh.pa.us 803 [ # # ]:UBC 0 : ereport(ERROR,
804 : : (errcode(ERRCODE_OUT_OF_MEMORY),
805 : : errmsg("out of memory")));
806 : : #else
807 : 0 : fprintf(stderr, _("out of memory\n"));
808 : 0 : return NULL;
809 : : #endif
810 : : }
811 : : }
812 : :
813 : : /* Make sure punctuation is canonical, too */
3663 tgl@sss.pgh.pa.us 814 :CBC 2253 : canonicalize_path(new);
815 : :
816 : 2253 : return new;
817 : : }
818 : :
819 : :
820 : : /*
821 : : * get_share_path
822 : : */
823 : : void
7099 824 : 11581 : get_share_path(const char *my_exec_path, char *ret_path)
825 : : {
826 : 11581 : make_relative_path(ret_path, PGSHAREDIR, PGBINDIR, my_exec_path);
827 : 11581 : }
828 : :
829 : : /*
830 : : * get_etc_path
831 : : */
832 : : void
7272 bruce@momjian.us 833 : 10810 : get_etc_path(const char *my_exec_path, char *ret_path)
834 : : {
7099 tgl@sss.pgh.pa.us 835 : 10810 : make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path);
7272 bruce@momjian.us 836 : 10810 : }
837 : :
838 : : /*
839 : : * get_include_path
840 : : */
841 : : void
842 : 466 : get_include_path(const char *my_exec_path, char *ret_path)
843 : : {
7099 tgl@sss.pgh.pa.us 844 : 466 : make_relative_path(ret_path, INCLUDEDIR, PGBINDIR, my_exec_path);
7272 bruce@momjian.us 845 : 466 : }
846 : :
847 : : /*
848 : : * get_pkginclude_path
849 : : */
850 : : void
851 : 410 : get_pkginclude_path(const char *my_exec_path, char *ret_path)
852 : : {
7099 tgl@sss.pgh.pa.us 853 : 410 : make_relative_path(ret_path, PKGINCLUDEDIR, PGBINDIR, my_exec_path);
7272 bruce@momjian.us 854 : 410 : }
855 : :
856 : : /*
857 : : * get_includeserver_path
858 : : */
859 : : void
7196 860 : 400 : get_includeserver_path(const char *my_exec_path, char *ret_path)
861 : : {
7099 tgl@sss.pgh.pa.us 862 : 400 : make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path);
7196 bruce@momjian.us 863 : 400 : }
864 : :
865 : : /*
866 : : * get_lib_path
867 : : */
868 : : void
869 : 400 : get_lib_path(const char *my_exec_path, char *ret_path)
870 : : {
7099 tgl@sss.pgh.pa.us 871 : 400 : make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path);
7196 bruce@momjian.us 872 : 400 : }
873 : :
874 : : /*
875 : : * get_pkglib_path
876 : : */
877 : : void
7272 878 : 1728 : get_pkglib_path(const char *my_exec_path, char *ret_path)
879 : : {
7099 tgl@sss.pgh.pa.us 880 : 1728 : make_relative_path(ret_path, PKGLIBDIR, PGBINDIR, my_exec_path);
7272 bruce@momjian.us 881 : 1728 : }
882 : :
883 : : /*
884 : : * get_locale_path
885 : : */
886 : : void
7264 887 : 16041 : get_locale_path(const char *my_exec_path, char *ret_path)
888 : : {
7099 tgl@sss.pgh.pa.us 889 : 16041 : make_relative_path(ret_path, LOCALEDIR, PGBINDIR, my_exec_path);
7264 bruce@momjian.us 890 : 16041 : }
891 : :
892 : : /*
893 : : * get_doc_path
894 : : */
895 : : void
6774 tgl@sss.pgh.pa.us 896 : 400 : get_doc_path(const char *my_exec_path, char *ret_path)
897 : : {
898 : 400 : make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path);
899 : 400 : }
900 : :
901 : : /*
902 : : * get_html_path
903 : : */
904 : : void
5900 peter_e@gmx.net 905 : 400 : get_html_path(const char *my_exec_path, char *ret_path)
906 : : {
907 : 400 : make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path);
908 : 400 : }
909 : :
910 : : /*
911 : : * get_man_path
912 : : */
913 : : void
6774 tgl@sss.pgh.pa.us 914 : 400 : get_man_path(const char *my_exec_path, char *ret_path)
915 : : {
916 : 400 : make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path);
917 : 400 : }
918 : :
919 : :
920 : : /*
921 : : * get_home_path
922 : : *
923 : : * On Unix, this actually returns the user's home directory. On Windows
924 : : * it returns the PostgreSQL-specific application data folder.
925 : : */
926 : : bool
7168 tgl@sss.pgh.pa.us 927 :UBC 0 : get_home_path(char *ret_path)
928 : : {
929 : : #ifndef WIN32
930 : : /*
931 : : * We first consult $HOME. If that's unset, try to get the info from
932 : : * <pwd.h>.
933 : : */
934 : : const char *home;
935 : :
826 936 : 0 : home = getenv("HOME");
937 [ # # # # ]: 0 : if (home == NULL || home[0] == '\0')
824 938 : 0 : return pg_get_user_home_dir(geteuid(), ret_path, MAXPGPATH);
826 939 : 0 : strlcpy(ret_path, home, MAXPGPATH);
7038 940 : 0 : return true;
941 : : #else
942 : : char *tmppath;
943 : :
944 : : /*
945 : : * Note: We use getenv() here because the more modern SHGetFolderPath()
946 : : * would force the backend to link with shell32.lib, which eats valuable
947 : : * desktop heap. XXX This function is used only in psql, which already
948 : : * brings in shell32 via libpq. Moving this function to its own file
949 : : * would keep it out of the backend, freeing it from this concern.
950 : : */
951 : : tmppath = getenv("APPDATA");
952 : : if (!tmppath)
953 : : return false;
954 : : snprintf(ret_path, MAXPGPATH, "%s/postgresql", tmppath);
955 : : return true;
956 : : #endif
957 : : }
958 : :
959 : :
960 : : /*
961 : : * get_parent_directory
962 : : *
963 : : * Modify the given string in-place to name the parent directory of the
964 : : * named file.
965 : : *
966 : : * If the input is just a file name with no directory part, the result is
967 : : * an empty string, not ".". This is appropriate when the next step is
968 : : * join_path_components(), but might need special handling otherwise.
969 : : *
970 : : * Caution: this will not produce desirable results if the string ends
971 : : * with "..". For most callers this is not a problem since the string
972 : : * is already known to name a regular file. If in doubt, apply
973 : : * canonicalize_path() first.
974 : : */
975 : : void
7168 tgl@sss.pgh.pa.us 976 :CBC 3593 : get_parent_directory(char *path)
977 : : {
978 : 3593 : trim_directory(path);
979 : 3593 : }
980 : :
981 : :
982 : : /*
983 : : * trim_directory
984 : : *
985 : : * Trim trailing directory from path, that is, remove any trailing slashes,
986 : : * the last pathname component, and the slash just ahead of it --- but never
987 : : * remove a leading slash.
988 : : *
989 : : * For the convenience of canonicalize_path, the path's new end location
990 : : * is returned.
991 : : */
992 : : static char *
7272 bruce@momjian.us 993 : 46703 : trim_directory(char *path)
994 : : {
995 : : char *p;
996 : :
7099 tgl@sss.pgh.pa.us 997 : 46703 : path = skip_drive(path);
998 : :
7272 bruce@momjian.us 999 [ - + ]: 46703 : if (path[0] == '\0')
804 tgl@sss.pgh.pa.us 1000 :UBC 0 : return path;
1001 : :
1002 : : /* back up over trailing slash(es) */
7248 bruce@momjian.us 1003 [ - + - - ]:CBC 46703 : for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
1004 : : ;
1005 : : /* back up over directory name */
1006 [ + + + + ]: 413140 : for (; !IS_DIR_SEP(*p) && p > path; p--)
1007 : : ;
1008 : : /* if multiple slashes before directory name, remove 'em all */
7099 tgl@sss.pgh.pa.us 1009 [ + + - + ]: 46703 : for (; p > path && IS_DIR_SEP(*(p - 1)); p--)
1010 : : ;
1011 : : /* don't erase a leading slash */
1012 [ + + + + ]: 46703 : if (p == path && IS_DIR_SEP(*p))
1013 : 15 : p++;
7272 bruce@momjian.us 1014 : 46703 : *p = '\0';
804 tgl@sss.pgh.pa.us 1015 : 46703 : return p;
1016 : : }
1017 : :
1018 : :
1019 : : /*
1020 : : * trim_trailing_separator
1021 : : *
1022 : : * trim off trailing slashes, but not a leading slash
1023 : : */
1024 : : static void
7272 bruce@momjian.us 1025 : 156409 : trim_trailing_separator(char *path)
1026 : : {
1027 : : char *p;
1028 : :
7099 tgl@sss.pgh.pa.us 1029 : 156409 : path = skip_drive(path);
1030 : 156409 : p = path + strlen(path);
7272 bruce@momjian.us 1031 [ + + ]: 156409 : if (p > path)
7218 tgl@sss.pgh.pa.us 1032 [ + + + + ]: 198519 : for (p--; p > path && IS_DIR_SEP(*p); p--)
7272 bruce@momjian.us 1033 : 42143 : *p = '\0';
1034 : 156409 : }
1035 : :
1036 : : /*
1037 : : * append_subdir_to_path
1038 : : *
1039 : : * Append the currently-considered subdirectory name to the output
1040 : : * path in canonicalize_path. Return the new end location of the
1041 : : * output path.
1042 : : *
1043 : : * Since canonicalize_path updates the path in-place, we must use
1044 : : * memmove not memcpy, and we don't yet terminate the path with '\0'.
1045 : : */
1046 : : static char *
804 tgl@sss.pgh.pa.us 1047 : 1275074 : append_subdir_to_path(char *path, char *subdir)
1048 : : {
1049 : 1275074 : size_t len = strlen(subdir);
1050 : :
1051 : : /* No need to copy data if path and subdir are the same. */
1052 [ + + ]: 1275074 : if (path != subdir)
1053 : 119 : memmove(path, subdir, len);
1054 : :
1055 : 1275074 : return path + len;
1056 : : }
|