Coverage Report

Created: 2025-06-13 06:06

/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
}