/src/postgres/src/port/path.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #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 | 0 | #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 |
94 | | has_drive_prefix(const char *path) |
95 | 0 | { |
96 | | #ifdef WIN32 |
97 | | return skip_drive(path) != path; |
98 | | #else |
99 | 0 | return false; |
100 | 0 | #endif |
101 | 0 | } |
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 * |
110 | | first_dir_separator(const char *filename) |
111 | 0 | { |
112 | 0 | const char *p; |
113 | |
|
114 | 0 | for (p = skip_drive(filename); *p; p++) |
115 | 0 | if (IS_DIR_SEP(*p)) |
116 | 0 | return unconstify(char *, p); |
117 | 0 | return NULL; |
118 | 0 | } |
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 * |
127 | | first_path_var_separator(const char *pathlist) |
128 | 0 | { |
129 | 0 | const char *p; |
130 | | |
131 | | /* skip_drive is not needed */ |
132 | 0 | for (p = pathlist; *p; p++) |
133 | 0 | if (IS_PATH_VAR_SEP(*p)) |
134 | 0 | return unconstify(char *, p); |
135 | 0 | return NULL; |
136 | 0 | } |
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 * |
145 | | last_dir_separator(const char *filename) |
146 | 0 | { |
147 | 0 | const char *p, |
148 | 0 | *ret = NULL; |
149 | |
|
150 | 0 | for (p = skip_drive(filename); *p; p++) |
151 | 0 | if (IS_DIR_SEP(*p)) |
152 | 0 | ret = p; |
153 | 0 | return unconstify(char *, ret); |
154 | 0 | } |
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 |
236 | | make_native_path(char *filename) |
237 | 0 | { |
238 | | #ifdef WIN32 |
239 | | char *p; |
240 | | |
241 | | for (p = filename; *p; p++) |
242 | | if (*p == '/') |
243 | | *p = '\\'; |
244 | | #endif |
245 | 0 | } |
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 |
258 | | cleanup_path(char *path) |
259 | 0 | { |
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 | 0 | } |
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 |
286 | | join_path_components(char *ret_path, |
287 | | const char *head, const char *tail) |
288 | 0 | { |
289 | 0 | if (ret_path != head) |
290 | 0 | 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 | |
|
297 | 0 | if (*tail) |
298 | 0 | { |
299 | | /* only separate with slash if head wasn't empty */ |
300 | 0 | snprintf(ret_path + strlen(ret_path), MAXPGPATH - strlen(ret_path), |
301 | 0 | "%s%s", |
302 | 0 | (*(skip_drive(head)) != '\0') ? "/" : "", |
303 | 0 | tail); |
304 | 0 | } |
305 | 0 | } |
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 |
337 | | canonicalize_path(char *path) |
338 | 0 | { |
339 | | /* All server-safe encodings are alike here, so just use PG_SQL_ASCII */ |
340 | 0 | canonicalize_path_enc(path, PG_SQL_ASCII); |
341 | 0 | } |
342 | | |
343 | | void |
344 | | canonicalize_path_enc(char *path, int encoding) |
345 | 0 | { |
346 | 0 | char *p, |
347 | 0 | *to_p; |
348 | 0 | char *spath; |
349 | 0 | char *parsed; |
350 | 0 | char *unparse; |
351 | 0 | bool was_sep = false; |
352 | 0 | canonicalize_state state; |
353 | 0 | 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 | | */ |
378 | 0 | trim_trailing_separator(path); |
379 | | |
380 | | /* |
381 | | * Remove duplicate adjacent separators |
382 | | */ |
383 | 0 | p = path; |
384 | | #ifdef WIN32 |
385 | | /* Don't remove leading double-slash on Win32 */ |
386 | | if (*p) |
387 | | p++; |
388 | | #endif |
389 | 0 | to_p = p; |
390 | 0 | for (; *p; p++, to_p++) |
391 | 0 | { |
392 | | /* Handle many adjacent slashes, like "/a///b" */ |
393 | 0 | while (*p == '/' && was_sep) |
394 | 0 | p++; |
395 | 0 | if (to_p != p) |
396 | 0 | *to_p = *p; |
397 | 0 | was_sep = (*p == '/'); |
398 | 0 | } |
399 | 0 | *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 | | */ |
413 | 0 | spath = skip_drive(path); |
414 | 0 | if (*spath == '\0') |
415 | 0 | return; /* empty path is returned as-is */ |
416 | | |
417 | 0 | if (*spath == '/') |
418 | 0 | { |
419 | 0 | state = ABSOLUTE_PATH_INIT; |
420 | | /* Skip the leading slash for absolute path */ |
421 | 0 | parsed = unparse = (spath + 1); |
422 | 0 | } |
423 | 0 | else |
424 | 0 | { |
425 | 0 | state = RELATIVE_PATH_INIT; |
426 | 0 | parsed = unparse = spath; |
427 | 0 | } |
428 | |
|
429 | 0 | while (*unparse != '\0') |
430 | 0 | { |
431 | 0 | char *unparse_next; |
432 | 0 | bool is_double_dot; |
433 | | |
434 | | /* Split off this dir name, and set unparse_next to the next one */ |
435 | 0 | unparse_next = unparse; |
436 | 0 | while (*unparse_next && *unparse_next != '/') |
437 | 0 | unparse_next++; |
438 | 0 | if (*unparse_next != '\0') |
439 | 0 | *unparse_next++ = '\0'; |
440 | | |
441 | | /* Identify type of this dir name */ |
442 | 0 | if (strcmp(unparse, ".") == 0) |
443 | 0 | { |
444 | | /* We can ignore "." components in all cases */ |
445 | 0 | unparse = unparse_next; |
446 | 0 | continue; |
447 | 0 | } |
448 | | |
449 | 0 | if (strcmp(unparse, "..") == 0) |
450 | 0 | is_double_dot = true; |
451 | 0 | else |
452 | 0 | { |
453 | | /* adjacent separators were eliminated above */ |
454 | 0 | Assert(*unparse != '\0'); |
455 | 0 | is_double_dot = false; |
456 | 0 | } |
457 | |
|
458 | 0 | switch (state) |
459 | 0 | { |
460 | 0 | case ABSOLUTE_PATH_INIT: |
461 | | /* We can ignore ".." immediately after / */ |
462 | 0 | if (!is_double_dot) |
463 | 0 | { |
464 | | /* Append first dir name (we already have leading slash) */ |
465 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
466 | 0 | state = ABSOLUTE_WITH_N_DEPTH; |
467 | 0 | pathdepth++; |
468 | 0 | } |
469 | 0 | break; |
470 | 0 | case ABSOLUTE_WITH_N_DEPTH: |
471 | 0 | if (is_double_dot) |
472 | 0 | { |
473 | | /* Remove last parsed dir */ |
474 | | /* (trim_directory won't remove the leading slash) */ |
475 | 0 | *parsed = '\0'; |
476 | 0 | parsed = trim_directory(path); |
477 | 0 | if (--pathdepth == 0) |
478 | 0 | state = ABSOLUTE_PATH_INIT; |
479 | 0 | } |
480 | 0 | else |
481 | 0 | { |
482 | | /* Append normal dir */ |
483 | 0 | *parsed++ = '/'; |
484 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
485 | 0 | pathdepth++; |
486 | 0 | } |
487 | 0 | break; |
488 | 0 | case RELATIVE_PATH_INIT: |
489 | 0 | if (is_double_dot) |
490 | 0 | { |
491 | | /* Append irreducible double-dot (..) */ |
492 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
493 | 0 | state = RELATIVE_WITH_PARENT_REF; |
494 | 0 | } |
495 | 0 | else |
496 | 0 | { |
497 | | /* Append normal dir */ |
498 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
499 | 0 | state = RELATIVE_WITH_N_DEPTH; |
500 | 0 | pathdepth++; |
501 | 0 | } |
502 | 0 | break; |
503 | 0 | case RELATIVE_WITH_N_DEPTH: |
504 | 0 | if (is_double_dot) |
505 | 0 | { |
506 | | /* Remove last parsed dir */ |
507 | 0 | *parsed = '\0'; |
508 | 0 | parsed = trim_directory(path); |
509 | 0 | if (--pathdepth == 0) |
510 | 0 | { |
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 | 0 | if (parsed == spath) |
518 | 0 | state = RELATIVE_PATH_INIT; |
519 | 0 | else |
520 | 0 | state = RELATIVE_WITH_PARENT_REF; |
521 | 0 | } |
522 | 0 | } |
523 | 0 | else |
524 | 0 | { |
525 | | /* Append normal dir */ |
526 | 0 | *parsed++ = '/'; |
527 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
528 | 0 | pathdepth++; |
529 | 0 | } |
530 | 0 | break; |
531 | 0 | case RELATIVE_WITH_PARENT_REF: |
532 | 0 | if (is_double_dot) |
533 | 0 | { |
534 | | /* Append next irreducible double-dot (..) */ |
535 | 0 | *parsed++ = '/'; |
536 | 0 | parsed = append_subdir_to_path(parsed, unparse); |
537 | 0 | } |
538 | 0 | else |
539 | 0 | { |
540 | | /* Append normal dir */ |
541 | 0 | *parsed++ = '/'; |
542 | 0 | 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 | 0 | state = RELATIVE_WITH_N_DEPTH; |
550 | 0 | pathdepth = 1; |
551 | 0 | } |
552 | 0 | break; |
553 | 0 | } |
554 | | |
555 | 0 | unparse = unparse_next; |
556 | 0 | } |
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 | 0 | if (parsed == spath) |
565 | 0 | *parsed++ = '.'; |
566 | | |
567 | | /* And finally, ensure the output path is nul-terminated. */ |
568 | 0 | *parsed = '\0'; |
569 | 0 | } |
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 |
577 | | path_contains_parent_reference(const char *path) |
578 | 0 | { |
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 | | */ |
585 | 0 | path = skip_drive(path); /* C: shouldn't affect our conclusion */ |
586 | |
|
587 | 0 | if (path[0] == '.' && |
588 | 0 | path[1] == '.' && |
589 | 0 | (path[2] == '\0' || path[2] == '/')) |
590 | 0 | return true; |
591 | | |
592 | 0 | return false; |
593 | 0 | } |
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 |
604 | | path_is_relative_and_below_cwd(const char *path) |
605 | 0 | { |
606 | 0 | if (is_absolute_path(path)) |
607 | 0 | return false; |
608 | | /* don't allow anything above the cwd */ |
609 | 0 | else if (path_contains_parent_reference(path)) |
610 | 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 | 0 | else |
627 | 0 | return true; |
628 | 0 | } |
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 |
637 | | path_is_prefix_of_path(const char *path1, const char *path2) |
638 | 0 | { |
639 | 0 | int path1_len = strlen(path1); |
640 | |
|
641 | 0 | if (strncmp(path1, path2, path1_len) == 0 && |
642 | 0 | (IS_DIR_SEP(path2[path1_len]) || path2[path1_len] == '\0')) |
643 | 0 | return true; |
644 | 0 | return false; |
645 | 0 | } |
646 | | |
647 | | /* |
648 | | * Extracts the actual name of the program as called - |
649 | | * stripped of .exe suffix if any |
650 | | */ |
651 | | const char * |
652 | | get_progname(const char *argv0) |
653 | 0 | { |
654 | 0 | const char *nodir_name; |
655 | 0 | char *progname; |
656 | |
|
657 | 0 | nodir_name = last_dir_separator(argv0); |
658 | 0 | if (nodir_name) |
659 | 0 | nodir_name++; |
660 | 0 | else |
661 | 0 | 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 | | */ |
667 | 0 | progname = strdup(nodir_name); |
668 | 0 | if (progname == NULL) |
669 | 0 | { |
670 | 0 | fprintf(stderr, "%s: out of memory\n", nodir_name); |
671 | 0 | abort(); /* This could exit the postmaster */ |
672 | 0 | } |
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 | | |
681 | 0 | return progname; |
682 | 0 | } |
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 |
690 | | dir_strcmp(const char *s1, const char *s2) |
691 | 0 | { |
692 | 0 | while (*s1 && *s2) |
693 | 0 | { |
694 | 0 | if ( |
695 | 0 | #ifndef WIN32 |
696 | 0 | *s1 != *s2 |
697 | | #else |
698 | | /* On windows, paths are case-insensitive */ |
699 | | pg_tolower((unsigned char) *s1) != pg_tolower((unsigned char) *s2) |
700 | | #endif |
701 | 0 | && !(IS_DIR_SEP(*s1) && IS_DIR_SEP(*s2))) |
702 | 0 | return (int) *s1 - (int) *s2; |
703 | 0 | s1++, s2++; |
704 | 0 | } |
705 | 0 | if (*s1) |
706 | 0 | return 1; /* s1 longer */ |
707 | 0 | if (*s2) |
708 | 0 | return -1; /* s2 longer */ |
709 | 0 | return 0; |
710 | 0 | } |
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 |
738 | | make_relative_path(char *ret_path, const char *target_path, |
739 | | const char *bin_path, const char *my_exec_path) |
740 | 0 | { |
741 | 0 | int prefix_len; |
742 | 0 | int tail_start; |
743 | 0 | int tail_len; |
744 | 0 | 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 | | */ |
750 | 0 | prefix_len = 0; |
751 | 0 | for (i = 0; target_path[i] && bin_path[i]; i++) |
752 | 0 | { |
753 | 0 | if (IS_DIR_SEP(target_path[i]) && IS_DIR_SEP(bin_path[i])) |
754 | 0 | prefix_len = i + 1; |
755 | 0 | else if (target_path[i] != bin_path[i]) |
756 | 0 | break; |
757 | 0 | } |
758 | 0 | if (prefix_len == 0) |
759 | 0 | goto no_match; /* no common prefix? */ |
760 | 0 | 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 | | */ |
766 | 0 | strlcpy(ret_path, my_exec_path, MAXPGPATH); |
767 | 0 | trim_directory(ret_path); /* remove my executable name */ |
768 | 0 | canonicalize_path(ret_path); |
769 | | |
770 | | /* |
771 | | * Tail match? |
772 | | */ |
773 | 0 | tail_start = (int) strlen(ret_path) - tail_len; |
774 | 0 | if (tail_start > 0 && |
775 | 0 | IS_DIR_SEP(ret_path[tail_start - 1]) && |
776 | 0 | dir_strcmp(ret_path + tail_start, bin_path + prefix_len) == 0) |
777 | 0 | { |
778 | 0 | ret_path[tail_start] = '\0'; |
779 | 0 | trim_trailing_separator(ret_path); |
780 | 0 | join_path_components(ret_path, ret_path, target_path + prefix_len); |
781 | 0 | canonicalize_path(ret_path); |
782 | 0 | return; |
783 | 0 | } |
784 | | |
785 | 0 | no_match: |
786 | 0 | strlcpy(ret_path, target_path, MAXPGPATH); |
787 | 0 | canonicalize_path(ret_path); |
788 | 0 | } |
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 * |
807 | | make_absolute_path(const char *path) |
808 | 0 | { |
809 | 0 | char *new; |
810 | | |
811 | | /* Returning null for null input is convenient for some callers */ |
812 | 0 | if (path == NULL) |
813 | 0 | return NULL; |
814 | | |
815 | 0 | if (!is_absolute_path(path)) |
816 | 0 | { |
817 | 0 | char *buf; |
818 | 0 | size_t buflen; |
819 | |
|
820 | 0 | buflen = MAXPGPATH; |
821 | 0 | for (;;) |
822 | 0 | { |
823 | 0 | buf = malloc(buflen); |
824 | 0 | if (!buf) |
825 | 0 | { |
826 | 0 | #ifndef FRONTEND |
827 | 0 | ereport(ERROR, |
828 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
829 | 0 | errmsg("out of memory"))); |
830 | | #else |
831 | | fprintf(stderr, _("out of memory\n")); |
832 | | return NULL; |
833 | | #endif |
834 | 0 | } |
835 | | |
836 | 0 | if (getcwd(buf, buflen)) |
837 | 0 | break; |
838 | 0 | else if (errno == ERANGE) |
839 | 0 | { |
840 | 0 | free(buf); |
841 | 0 | buflen *= 2; |
842 | 0 | continue; |
843 | 0 | } |
844 | 0 | else |
845 | 0 | { |
846 | 0 | int save_errno = errno; |
847 | |
|
848 | 0 | free(buf); |
849 | 0 | errno = save_errno; |
850 | 0 | #ifndef FRONTEND |
851 | 0 | elog(ERROR, "could not get current working directory: %m"); |
852 | | #else |
853 | | fprintf(stderr, _("could not get current working directory: %m\n")); |
854 | | return NULL; |
855 | | #endif |
856 | 0 | } |
857 | 0 | } |
858 | | |
859 | 0 | new = malloc(strlen(buf) + strlen(path) + 2); |
860 | 0 | if (!new) |
861 | 0 | { |
862 | 0 | free(buf); |
863 | 0 | #ifndef FRONTEND |
864 | 0 | ereport(ERROR, |
865 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
866 | 0 | errmsg("out of memory"))); |
867 | | #else |
868 | | fprintf(stderr, _("out of memory\n")); |
869 | | return NULL; |
870 | | #endif |
871 | 0 | } |
872 | 0 | sprintf(new, "%s/%s", buf, path); |
873 | 0 | free(buf); |
874 | 0 | } |
875 | 0 | else |
876 | 0 | { |
877 | 0 | new = strdup(path); |
878 | 0 | if (!new) |
879 | 0 | { |
880 | 0 | #ifndef FRONTEND |
881 | 0 | ereport(ERROR, |
882 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
883 | 0 | errmsg("out of memory"))); |
884 | | #else |
885 | | fprintf(stderr, _("out of memory\n")); |
886 | | return NULL; |
887 | | #endif |
888 | 0 | } |
889 | 0 | } |
890 | | |
891 | | /* Make sure punctuation is canonical, too */ |
892 | 0 | canonicalize_path(new); |
893 | |
|
894 | 0 | return new; |
895 | 0 | } |
896 | | |
897 | | |
898 | | /* |
899 | | * get_share_path |
900 | | */ |
901 | | void |
902 | | get_share_path(const char *my_exec_path, char *ret_path) |
903 | 0 | { |
904 | 0 | make_relative_path(ret_path, PGSHAREDIR, PGBINDIR, my_exec_path); |
905 | 0 | } |
906 | | |
907 | | /* |
908 | | * get_etc_path |
909 | | */ |
910 | | void |
911 | | get_etc_path(const char *my_exec_path, char *ret_path) |
912 | 0 | { |
913 | 0 | make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path); |
914 | 0 | } |
915 | | |
916 | | /* |
917 | | * get_include_path |
918 | | */ |
919 | | void |
920 | | get_include_path(const char *my_exec_path, char *ret_path) |
921 | 0 | { |
922 | 0 | make_relative_path(ret_path, INCLUDEDIR, PGBINDIR, my_exec_path); |
923 | 0 | } |
924 | | |
925 | | /* |
926 | | * get_pkginclude_path |
927 | | */ |
928 | | void |
929 | | get_pkginclude_path(const char *my_exec_path, char *ret_path) |
930 | 0 | { |
931 | 0 | make_relative_path(ret_path, PKGINCLUDEDIR, PGBINDIR, my_exec_path); |
932 | 0 | } |
933 | | |
934 | | /* |
935 | | * get_includeserver_path |
936 | | */ |
937 | | void |
938 | | get_includeserver_path(const char *my_exec_path, char *ret_path) |
939 | 0 | { |
940 | 0 | make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path); |
941 | 0 | } |
942 | | |
943 | | /* |
944 | | * get_lib_path |
945 | | */ |
946 | | void |
947 | | get_lib_path(const char *my_exec_path, char *ret_path) |
948 | 0 | { |
949 | 0 | make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path); |
950 | 0 | } |
951 | | |
952 | | /* |
953 | | * get_pkglib_path |
954 | | */ |
955 | | void |
956 | | get_pkglib_path(const char *my_exec_path, char *ret_path) |
957 | 0 | { |
958 | 0 | make_relative_path(ret_path, PKGLIBDIR, PGBINDIR, my_exec_path); |
959 | 0 | } |
960 | | |
961 | | /* |
962 | | * get_locale_path |
963 | | */ |
964 | | void |
965 | | get_locale_path(const char *my_exec_path, char *ret_path) |
966 | 0 | { |
967 | 0 | make_relative_path(ret_path, LOCALEDIR, PGBINDIR, my_exec_path); |
968 | 0 | } |
969 | | |
970 | | /* |
971 | | * get_doc_path |
972 | | */ |
973 | | void |
974 | | get_doc_path(const char *my_exec_path, char *ret_path) |
975 | 0 | { |
976 | 0 | make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path); |
977 | 0 | } |
978 | | |
979 | | /* |
980 | | * get_html_path |
981 | | */ |
982 | | void |
983 | | get_html_path(const char *my_exec_path, char *ret_path) |
984 | 0 | { |
985 | 0 | make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path); |
986 | 0 | } |
987 | | |
988 | | /* |
989 | | * get_man_path |
990 | | */ |
991 | | void |
992 | | get_man_path(const char *my_exec_path, char *ret_path) |
993 | 0 | { |
994 | 0 | make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path); |
995 | 0 | } |
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 |
1005 | | get_home_path(char *ret_path) |
1006 | 0 | { |
1007 | 0 | #ifndef WIN32 |
1008 | | /* |
1009 | | * We first consult $HOME. If that's unset, try to get the info from |
1010 | | * <pwd.h>. |
1011 | | */ |
1012 | 0 | const char *home; |
1013 | |
|
1014 | 0 | home = getenv("HOME"); |
1015 | 0 | if (home && home[0]) |
1016 | 0 | { |
1017 | 0 | strlcpy(ret_path, home, MAXPGPATH); |
1018 | 0 | return true; |
1019 | 0 | } |
1020 | 0 | else |
1021 | 0 | { |
1022 | 0 | struct passwd pwbuf; |
1023 | 0 | struct passwd *pw; |
1024 | 0 | char buf[1024]; |
1025 | 0 | 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 | 0 | } |
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 | 0 | } |
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 |
1068 | | get_parent_directory(char *path) |
1069 | 0 | { |
1070 | 0 | trim_directory(path); |
1071 | 0 | } |
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 * |
1085 | | trim_directory(char *path) |
1086 | 0 | { |
1087 | 0 | char *p; |
1088 | |
|
1089 | 0 | path = skip_drive(path); |
1090 | |
|
1091 | 0 | if (path[0] == '\0') |
1092 | 0 | return path; |
1093 | | |
1094 | | /* back up over trailing slash(es) */ |
1095 | 0 | for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--) |
1096 | 0 | ; |
1097 | | /* back up over directory name */ |
1098 | 0 | for (; !IS_DIR_SEP(*p) && p > path; p--) |
1099 | 0 | ; |
1100 | | /* if multiple slashes before directory name, remove 'em all */ |
1101 | 0 | for (; p > path && IS_DIR_SEP(*(p - 1)); p--) |
1102 | 0 | ; |
1103 | | /* don't erase a leading slash */ |
1104 | 0 | if (p == path && IS_DIR_SEP(*p)) |
1105 | 0 | p++; |
1106 | 0 | *p = '\0'; |
1107 | 0 | return p; |
1108 | 0 | } |
1109 | | |
1110 | | |
1111 | | /* |
1112 | | * trim_trailing_separator |
1113 | | * |
1114 | | * trim off trailing slashes, but not a leading slash |
1115 | | */ |
1116 | | static void |
1117 | | trim_trailing_separator(char *path) |
1118 | 0 | { |
1119 | 0 | char *p; |
1120 | |
|
1121 | 0 | path = skip_drive(path); |
1122 | 0 | p = path + strlen(path); |
1123 | 0 | if (p > path) |
1124 | 0 | for (p--; p > path && IS_DIR_SEP(*p); p--) |
1125 | 0 | *p = '\0'; |
1126 | 0 | } |
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 * |
1139 | | append_subdir_to_path(char *path, char *subdir) |
1140 | 0 | { |
1141 | 0 | size_t len = strlen(subdir); |
1142 | | |
1143 | | /* No need to copy data if path and subdir are the same. */ |
1144 | 0 | if (path != subdir) |
1145 | 0 | memmove(path, subdir, len); |
1146 | |
|
1147 | 0 | return path + len; |
1148 | 0 | } |