46
46
47
47
static void make_relative_path (char * ret_path , const char * target_path ,
48
48
const char * bin_path , const char * my_exec_path );
49
- static void trim_directory (char * path );
49
+ static char * trim_directory (char * path );
50
50
static void trim_trailing_separator (char * path );
51
+ static char * append_subdir_to_path (char * path , char * subdir );
51
52
52
53
53
54
/*
@@ -222,13 +223,9 @@ join_path_components(char *ret_path,
222
223
strlcpy (ret_path , head , MAXPGPATH );
223
224
224
225
/*
225
- * Remove any leading "." in the tail component.
226
- *
227
- * Note: we used to try to remove ".." as well, but that's tricky to get
228
- * right; now we just leave it to be done by canonicalize_path() later.
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.
229
228
*/
230
- while (tail [0 ] == '.' && IS_DIR_SEP (tail [1 ]))
231
- tail += 2 ;
232
229
233
230
if (* tail )
234
231
{
@@ -241,23 +238,39 @@ join_path_components(char *ret_path,
241
238
}
242
239
243
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
+
244
254
/*
245
255
* Clean up path by:
246
256
* o make Win32 path use Unix slashes
247
257
* o remove trailing quote on Win32
248
258
* o remove trailing slash
249
- * o remove duplicate adjacent separators
250
- * o remove trailing '.'
251
- * o process trailing '..' ourselves
259
+ * o remove duplicate ( adjacent) separators
260
+ * o remove '.' (unless path reduces to only '.')
261
+ * o process '..' ourselves, removing it if possible
252
262
*/
253
263
void
254
264
canonicalize_path (char * path )
255
265
{
256
266
char * p ,
257
267
* to_p ;
258
268
char * spath ;
269
+ char * parsed ;
270
+ char * unparse ;
259
271
bool was_sep = false;
260
- int pending_strips ;
272
+ canonicalize_state state ;
273
+ int pathdepth = 0 ; /* counts collected regular directory names */
261
274
262
275
#ifdef WIN32
263
276
@@ -308,60 +321,173 @@ canonicalize_path(char *path)
308
321
* to_p = '\0' ;
309
322
310
323
/*
311
- * Remove any trailing uses of "." and process ".." ourselves
324
+ * Remove any uses of "." and process ".." ourselves
312
325
*
313
326
* Note that "/../.." should reduce to just "/", while "../.." has to be
314
- * kept as-is. In the latter case we put back mistakenly trimmed ".."
315
- * components below. Also note that we want a Windows drive spec to be
316
- * visible to trim_directory(), but it's not part of the logic that's
317
- * looking at the name components; hence distinction between path and
318
- * spath.
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.
319
334
*/
320
335
spath = skip_drive (path );
321
- pending_strips = 0 ;
322
- for (;;)
336
+ if (* spath == '\0' )
337
+ return ; /* empty path is returned as-is */
338
+
339
+ if (* spath == '/' )
323
340
{
324
- int len = strlen (spath );
341
+ state = ABSOLUTE_PATH_INIT ;
342
+ /* Skip the leading slash for absolute path */
343
+ parsed = unparse = (spath + 1 );
344
+ }
345
+ else
346
+ {
347
+ state = RELATIVE_PATH_INIT ;
348
+ parsed = unparse = spath ;
349
+ }
325
350
326
- if (len >= 2 && strcmp (spath + len - 2 , "/." ) == 0 )
327
- trim_directory (path );
328
- else if (strcmp (spath , "." ) == 0 )
351
+ 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
+ unparse_next = unparse ;
358
+ while (* unparse_next && * unparse_next != '/' )
359
+ unparse_next ++ ;
360
+ if (* unparse_next != '\0' )
361
+ * unparse_next ++ = '\0' ;
362
+
363
+ /* Identify type of this dir name */
364
+ if (strcmp (unparse , "." ) == 0 )
329
365
{
330
- /* Want to leave "." alone, but "./.." has to become ".." */
331
- if (pending_strips > 0 )
332
- * spath = '\0' ;
333
- break ;
366
+ /* We can ignore "." components in all cases */
367
+ unparse = unparse_next ;
368
+ continue ;
334
369
}
335
- else if ((len >= 3 && strcmp (spath + len - 3 , "/.." ) == 0 ) ||
336
- strcmp (spath , ".." ) == 0 )
370
+
371
+ if (strcmp (unparse , ".." ) == 0 )
372
+ is_double_dot = true;
373
+ else
337
374
{
338
- trim_directory (path );
339
- pending_strips ++ ;
375
+ /* adjacent separators were eliminated above */
376
+ Assert (* unparse != '\0' );
377
+ is_double_dot = false;
340
378
}
341
- else if (pending_strips > 0 && * spath != '\0' )
379
+
380
+ switch (state )
342
381
{
343
- /* trim a regular directory name canceled by ".." */
344
- trim_directory (path );
345
- pending_strips -- ;
346
- /* foo/.. should become ".", not empty */
347
- if (* spath == '\0' )
348
- strcpy (spath , "." );
382
+ case ABSOLUTE_PATH_INIT :
383
+ /* We can ignore ".." immediately after / */
384
+ if (!is_double_dot )
385
+ {
386
+ /* Append first dir name (we already have leading slash) */
387
+ parsed = append_subdir_to_path (parsed , unparse );
388
+ state = ABSOLUTE_WITH_N_DEPTH ;
389
+ pathdepth ++ ;
390
+ }
391
+ break ;
392
+ case ABSOLUTE_WITH_N_DEPTH :
393
+ if (is_double_dot )
394
+ {
395
+ /* Remove last parsed dir */
396
+ /* (trim_directory won't remove the leading slash) */
397
+ * parsed = '\0' ;
398
+ parsed = trim_directory (path );
399
+ if (-- pathdepth == 0 )
400
+ state = ABSOLUTE_PATH_INIT ;
401
+ }
402
+ else
403
+ {
404
+ /* Append normal dir */
405
+ * parsed ++ = '/' ;
406
+ parsed = append_subdir_to_path (parsed , unparse );
407
+ pathdepth ++ ;
408
+ }
409
+ break ;
410
+ case RELATIVE_PATH_INIT :
411
+ if (is_double_dot )
412
+ {
413
+ /* Append irreducible double-dot (..) */
414
+ parsed = append_subdir_to_path (parsed , unparse );
415
+ state = RELATIVE_WITH_PARENT_REF ;
416
+ }
417
+ else
418
+ {
419
+ /* Append normal dir */
420
+ parsed = append_subdir_to_path (parsed , unparse );
421
+ state = RELATIVE_WITH_N_DEPTH ;
422
+ pathdepth ++ ;
423
+ }
424
+ break ;
425
+ case RELATIVE_WITH_N_DEPTH :
426
+ if (is_double_dot )
427
+ {
428
+ /* Remove last parsed dir */
429
+ * parsed = '\0' ;
430
+ parsed = trim_directory (path );
431
+ 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
+ if (parsed == spath )
440
+ state = RELATIVE_PATH_INIT ;
441
+ else
442
+ state = RELATIVE_WITH_PARENT_REF ;
443
+ }
444
+ }
445
+ else
446
+ {
447
+ /* Append normal dir */
448
+ * parsed ++ = '/' ;
449
+ parsed = append_subdir_to_path (parsed , unparse );
450
+ pathdepth ++ ;
451
+ }
452
+ break ;
453
+ case RELATIVE_WITH_PARENT_REF :
454
+ if (is_double_dot )
455
+ {
456
+ /* Append next irreducible double-dot (..) */
457
+ * parsed ++ = '/' ;
458
+ parsed = append_subdir_to_path (parsed , unparse );
459
+ }
460
+ else
461
+ {
462
+ /* Append normal dir */
463
+ * parsed ++ = '/' ;
464
+ 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
+ state = RELATIVE_WITH_N_DEPTH ;
472
+ pathdepth = 1 ;
473
+ }
474
+ break ;
349
475
}
350
- else
351
- break ;
352
- }
353
476
354
- if (pending_strips > 0 )
355
- {
356
- /*
357
- * We could only get here if path is now totally empty (other than a
358
- * possible drive specifier on Windows). We have to put back one or
359
- * more ".."'s that we took off.
360
- */
361
- while (-- pending_strips > 0 )
362
- strcat (path , "../" );
363
- strcat (path , ".." );
477
+ unparse = unparse_next ;
364
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
+ if (parsed == spath )
487
+ * parsed ++ = '.' ;
488
+
489
+ /* And finally, ensure the output path is nul-terminated. */
490
+ * parsed = '\0' ;
365
491
}
366
492
367
493
/*
@@ -865,16 +991,19 @@ get_parent_directory(char *path)
865
991
* Trim trailing directory from path, that is, remove any trailing slashes,
866
992
* the last pathname component, and the slash just ahead of it --- but never
867
993
* remove a leading slash.
994
+ *
995
+ * For the convenience of canonicalize_path, the path's new end location
996
+ * is returned.
868
997
*/
869
- static void
998
+ static char *
870
999
trim_directory (char * path )
871
1000
{
872
1001
char * p ;
873
1002
874
1003
path = skip_drive (path );
875
1004
876
1005
if (path [0 ] == '\0' )
877
- return ;
1006
+ return path ;
878
1007
879
1008
/* back up over trailing slash(es) */
880
1009
for (p = path + strlen (path ) - 1 ; IS_DIR_SEP (* p ) && p > path ; p -- )
@@ -889,6 +1018,7 @@ trim_directory(char *path)
889
1018
if (p == path && IS_DIR_SEP (* p ))
890
1019
p ++ ;
891
1020
* p = '\0' ;
1021
+ return p ;
892
1022
}
893
1023
894
1024
@@ -908,3 +1038,25 @@ trim_trailing_separator(char *path)
908
1038
for (p -- ; p > path && IS_DIR_SEP (* p ); p -- )
909
1039
* p = '\0' ;
910
1040
}
1041
+
1042
+ /*
1043
+ * append_subdir_to_path
1044
+ *
1045
+ * Append the currently-considered subdirectory name to the output
1046
+ * path in canonicalize_path. Return the new end location of the
1047
+ * output path.
1048
+ *
1049
+ * Since canonicalize_path updates the path in-place, we must use
1050
+ * memmove not memcpy, and we don't yet terminate the path with '\0'.
1051
+ */
1052
+ static char *
1053
+ append_subdir_to_path (char * path , char * subdir )
1054
+ {
1055
+ size_t len = strlen (subdir );
1056
+
1057
+ /* No need to copy data if path and subdir are the same. */
1058
+ if (path != subdir )
1059
+ memmove (path , subdir , len );
1060
+
1061
+ return path + len ;
1062
+ }
0 commit comments