1414 *-------------------------------------------------------------------------
1515 */
1616
17+ /*
18+ * On macOS, "man realpath" avers:
19+ * Defining _DARWIN_C_SOURCE or _DARWIN_BETTER_REALPATH before including
20+ * stdlib.h will cause the provided implementation of realpath() to use
21+ * F_GETPATH from fcntl(2) to discover the path.
22+ * This should be harmless everywhere else.
23+ */
24+ #define _DARWIN_BETTER_REALPATH
25+
1726#ifndef FRONTEND
1827#include "postgres.h"
1928#else
@@ -58,11 +67,8 @@ extern int _CRT_glob = 0; /* 0 turns off globbing; 1 turns it on */
5867 (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
5968#endif
6069
61- #ifdef _MSC_VER
62- #define getcwd (cwd ,len ) GetCurrentDirectory(len, cwd)
63- #endif
64-
65- static int resolve_symlinks (char * path );
70+ static int normalize_exec_path (char * path );
71+ static char * pg_realpath (const char * fname );
6672
6773#ifdef WIN32
6874static BOOL GetTokenUser (HANDLE hToken , PTOKEN_USER * ppTokenUser );
@@ -87,7 +93,7 @@ validate_exec(const char *path)
8793 char path_exe [MAXPGPATH + sizeof (".exe" ) - 1 ];
8894
8995 /* Win32 requires a .exe suffix for stat() */
90- if (strlen (path ) >= strlen (".exe" ) &&
96+ if (strlen (path ) < strlen (".exe" ) ||
9197 pg_strcasecmp (path + strlen (path ) - strlen (".exe" ), ".exe" ) != 0 )
9298 {
9399 strlcpy (path_exe , path , sizeof (path_exe ) - 4 );
@@ -135,46 +141,32 @@ validate_exec(const char *path)
135141
136142
137143/*
138- * find_my_exec -- find an absolute path to a valid executable
144+ * find_my_exec -- find an absolute path to this program's executable
139145 *
140146 * argv0 is the name passed on the command line
141147 * retpath is the output area (must be of size MAXPGPATH)
142148 * Returns 0 if OK, -1 if error.
143149 *
144150 * The reason we have to work so hard to find an absolute path is that
145151 * on some platforms we can't do dynamic loading unless we know the
146- * executable's location. Also, we need a full path not a relative
147- * path because we will later change working directory. Finally, we want
152+ * executable's location. Also, we need an absolute path not a relative
153+ * path because we may later change working directory. Finally, we want
148154 * a true path not a symlink location, so that we can locate other files
149155 * that are part of our installation relative to the executable.
150156 */
151157int
152158find_my_exec (const char * argv0 , char * retpath )
153159{
154- char cwd [MAXPGPATH ],
155- test_path [MAXPGPATH ];
156160 char * path ;
157161
158- if (!getcwd (cwd , MAXPGPATH ))
159- {
160- log_error (errcode_for_file_access (),
161- _ ("could not identify current directory: %m" ));
162- return -1 ;
163- }
164-
165162 /*
166163 * If argv0 contains a separator, then PATH wasn't used.
167164 */
168- if (first_dir_separator (argv0 ) != NULL )
165+ strlcpy (retpath , argv0 , MAXPGPATH );
166+ if (first_dir_separator (retpath ) != NULL )
169167 {
170- if (is_absolute_path (argv0 ))
171- strlcpy (retpath , argv0 , MAXPGPATH );
172- else
173- join_path_components (retpath , cwd , argv0 );
174- canonicalize_path (retpath );
175-
176168 if (validate_exec (retpath ) == 0 )
177- return resolve_symlinks (retpath );
169+ return normalize_exec_path (retpath );
178170
179171 log_error (errcode (ERRCODE_WRONG_OBJECT_TYPE ),
180172 _ ("invalid binary \"%s\": %m" ), retpath );
@@ -183,9 +175,8 @@ find_my_exec(const char *argv0, char *retpath)
183175
184176#ifdef WIN32
185177 /* Win32 checks the current directory first for names without slashes */
186- join_path_components (retpath , cwd , argv0 );
187178 if (validate_exec (retpath ) == 0 )
188- return resolve_symlinks (retpath );
179+ return normalize_exec_path (retpath );
189180#endif
190181
191182 /*
@@ -208,21 +199,15 @@ find_my_exec(const char *argv0, char *retpath)
208199 if (!endp )
209200 endp = startp + strlen (startp ); /* point to end */
210201
211- strlcpy (test_path , startp , Min (endp - startp + 1 , MAXPGPATH ));
202+ strlcpy (retpath , startp , Min (endp - startp + 1 , MAXPGPATH ));
212203
213- if (is_absolute_path (test_path ))
214- join_path_components (retpath , test_path , argv0 );
215- else
216- {
217- join_path_components (retpath , cwd , test_path );
218- join_path_components (retpath , retpath , argv0 );
219- }
204+ join_path_components (retpath , retpath , argv0 );
220205 canonicalize_path (retpath );
221206
222207 switch (validate_exec (retpath ))
223208 {
224209 case 0 : /* found ok */
225- return resolve_symlinks (retpath );
210+ return normalize_exec_path (retpath );
226211 case -1 : /* wasn't even a candidate, keep looking */
227212 break ;
228213 case -2 : /* found but disqualified */
@@ -241,105 +226,96 @@ find_my_exec(const char *argv0, char *retpath)
241226
242227
243228/*
244- * resolve_symlinks - resolve symlinks to the underlying file
229+ * normalize_exec_path - resolve symlinks and convert to absolute path
245230 *
246- * Replace "path" by the absolute path to the referenced file.
231+ * Given a path that refers to an executable, chase through any symlinks
232+ * to find the real file location; then convert that to an absolute path.
247233 *
234+ * On success, replaces the contents of "path" with the absolute path.
235+ * ("path" is assumed to be of size MAXPGPATH.)
248236 * Returns 0 if OK, -1 if error.
249- *
250- * Note: we are not particularly tense about producing nice error messages
251- * because we are not really expecting error here; we just determined that
252- * the symlink does point to a valid executable.
253- *
254- * Here we test HAVE_READLINK, which excludes Windows. There's no point in
255- * using our junction point-based replacement code for this, because that only
256- * works for directories.
257237 */
258238static int
259- resolve_symlinks (char * path )
239+ normalize_exec_path (char * path )
260240{
261- #ifdef HAVE_READLINK
262- struct stat buf ;
263- char orig_wd [MAXPGPATH ],
264- link_buf [MAXPGPATH ];
265- char * fname ;
266-
267241 /*
268- * To resolve a symlink properly, we have to chdir into its directory and
269- * then chdir to where the symlink points; otherwise we may fail to
270- * resolve relative links correctly (consider cases involving mount
271- * points, for example). After following the final symlink, we use
272- * getcwd() to figure out where the heck we're at.
273- *
274- * One might think we could skip all this if path doesn't point to a
275- * symlink to start with, but that's wrong. We also want to get rid of
276- * any directory symlinks that are present in the given path. We expect
277- * getcwd() to give us an accurate, symlink-free path.
242+ * We used to do a lot of work ourselves here, but now we just let
243+ * realpath(3) do all the heavy lifting.
278244 */
279- if (!getcwd (orig_wd , MAXPGPATH ))
245+ char * abspath = pg_realpath (path );
246+
247+ if (abspath == NULL )
280248 {
281249 log_error (errcode_for_file_access (),
282- _ ("could not identify current directory: %m" ));
250+ _ ("could not resolve path \"%s\" to absolute form: %m" ),
251+ path );
283252 return -1 ;
284253 }
254+ strlcpy (path , abspath , MAXPGPATH );
255+ free (abspath );
285256
286- for (;;)
287- {
288- char * lsep ;
289- int rllen ;
290-
291- lsep = last_dir_separator (path );
292- if (lsep )
293- {
294- * lsep = '\0' ;
295- if (chdir (path ) == -1 )
296- {
297- log_error (errcode_for_file_access (),
298- _ ("could not change directory to \"%s\": %m" ), path );
299- return -1 ;
300- }
301- fname = lsep + 1 ;
302- }
303- else
304- fname = path ;
257+ #ifdef WIN32
258+ /* On Windows, be sure to convert '\' to '/' */
259+ canonicalize_path (path );
260+ #endif
305261
306- if (lstat (fname , & buf ) < 0 ||
307- !S_ISLNK (buf .st_mode ))
308- break ;
262+ return 0 ;
263+ }
309264
310- errno = 0 ;
311- rllen = readlink (fname , link_buf , sizeof (link_buf ));
312- if (rllen < 0 || rllen >= sizeof (link_buf ))
313- {
314- log_error (errcode_for_file_access (),
315- _ ("could not read symbolic link \"%s\": %m" ), fname );
316- return -1 ;
317- }
318- link_buf [rllen ] = '\0' ;
319- strcpy (path , link_buf );
320- }
321265
322- /* must copy final component out of 'path' temporarily */
323- strlcpy (link_buf , fname , sizeof (link_buf ));
266+ /*
267+ * pg_realpath() - realpath(3) with POSIX.1-2008 semantics
268+ *
269+ * This is equivalent to realpath(fname, NULL), in that it returns a
270+ * malloc'd buffer containing the absolute path equivalent to fname.
271+ * On error, returns NULL with errno set.
272+ *
273+ * On Windows, what you get is spelled per platform conventions,
274+ * so you probably want to apply canonicalize_path() to the result.
275+ *
276+ * For now, this is needed only here so mark it static. If you choose to
277+ * move it into its own file, move the _DARWIN_BETTER_REALPATH #define too!
278+ */
279+ static char *
280+ pg_realpath (const char * fname )
281+ {
282+ char * path ;
324283
325- if (!getcwd (path , MAXPGPATH ))
284+ #ifndef WIN32
285+ path = realpath (fname , NULL );
286+ if (path == NULL && errno == EINVAL )
326287 {
327- log_error (errcode_for_file_access (),
328- _ ("could not identify current directory: %m" ));
329- return -1 ;
330- }
331- join_path_components (path , path , link_buf );
332- canonicalize_path (path );
288+ /*
289+ * Cope with old-POSIX systems that require a user-provided buffer.
290+ * Assume MAXPGPATH is enough room on all such systems.
291+ */
292+ char * buf = malloc (MAXPGPATH );
333293
334- if (chdir (orig_wd ) == -1 )
335- {
336- log_error (errcode_for_file_access (),
337- _ ("could not change directory to \"%s\": %m" ), orig_wd );
338- return -1 ;
294+ if (buf == NULL )
295+ return NULL ; /* assume errno is set */
296+ path = realpath (fname , buf );
297+ if (path == NULL ) /* don't leak memory */
298+ {
299+ int save_errno = errno ;
300+
301+ free (buf );
302+ errno = save_errno ;
303+ }
339304 }
340- #endif /* HAVE_READLINK */
305+ #else /* WIN32 */
341306
342- return 0 ;
307+ /*
308+ * Microsoft is resolutely non-POSIX, but _fullpath() does the same thing.
309+ * The documentation claims it reports errors by setting errno, which is a
310+ * bit surprising for Microsoft, but we'll believe that until it's proven
311+ * wrong. Clear errno first, though, so we can at least tell if a failure
312+ * occurs and doesn't set it.
313+ */
314+ errno = 0 ;
315+ path = _fullpath (NULL , fname , 0 );
316+ #endif
317+
318+ return path ;
343319}
344320
345321
0 commit comments