@@ -548,88 +548,87 @@ void xstat(char *path, struct stat *st)
548
548
}
549
549
550
550
// Canonicalize path, even to file with one or more missing components at end.
551
- // Returns allocated string for pathname or NULL if doesn't exist
552
- // exact = 1 file must exist, 0 dir must exist, -1 show theoretical location,
553
- // -2 don't resolve last file
554
- char * xabspath (char * path , int exact )
551
+ // Returns allocated string for pathname or NULL if doesn't exist. Flags are:
552
+ // ABS_PATH:path to last component must exist ABS_FILE: whole path must exist
553
+ // ABS_KEEP:keep symlinks in path ABS_LAST: keep symlink at end of path
554
+ char * xabspath (char * path , int flags )
555
555
{
556
- struct string_list * todo , * done = 0 ;
557
- int try = 9999 , dirfd = open ( "/" , O_PATH ) , missing = 0 ;
558
- char * ret ;
556
+ struct string_list * todo , * done = 0 , * new , * * tail ;
557
+ int fd , track , len , try = 9999 , dirfd = -1 , missing = 0 ;
558
+ char * str ;
559
559
560
- // If this isn't an absolute path, start with cwd.
561
- if (* path != '/' ) {
562
- char * temp = xgetcwd ();
560
+ // If the last file must exist, path to it must exist.
561
+ if (flags & ABS_FILE ) flags |= ABS_PATH ;
562
+ // If we don't resolve path's symlinks, don't resolve last symlink.
563
+ if (flags & ABS_KEEP ) flags |= ABS_LAST ;
563
564
564
- splitpath (path , splitpath (temp , & todo ));
565
- free (temp );
565
+ // If this isn't an absolute path, start with cwd or $PWD.
566
+ if (* path != '/' ) {
567
+ if ((flags & ABS_KEEP ) && (str = getenv ("PWD" )))
568
+ splitpath (path , splitpath (str , & todo ));
569
+ else {
570
+ splitpath (path , splitpath (str = xgetcwd (), & todo ));
571
+ free (str );
572
+ }
566
573
} else splitpath (path , & todo );
567
574
568
575
// Iterate through path components in todo, prepend processed ones to done.
569
576
while (todo ) {
570
- struct string_list * new = llist_pop (& todo ), * * tail ;
571
- ssize_t len ;
572
-
573
- // Eventually break out of endless loops
577
+ // break out of endless symlink loops
574
578
if (!try -- ) {
575
579
errno = ELOOP ;
576
580
goto error ;
577
581
}
578
582
579
- // Removable path componenents.
580
- if (!strcmp (new -> str , "." ) || !strcmp (new -> str , ".." )) {
581
- int x = new -> str [1 ];
582
-
583
+ // Remove . or .. component, tracking dirfd back up tree as necessary
584
+ str = (new = llist_pop (& todo ))-> str ;
585
+ // track dirfd if this component must exist or we're resolving symlinks
586
+ track = ((flags >>!todo ) & (ABS_PATH |ABS_KEEP )) ^ ABS_KEEP ;
587
+ if (!done && track ) dirfd = open ("/" , O_PATH );
588
+ if (* str == '.' && !str [1 + ((fd = str [1 ])== '.' )]) {
583
589
free (new );
584
- if (!x ) continue ;
585
- if (done ) free (llist_pop (& done ));
586
- len = 0 ;
587
-
588
- if (missing ) missing -- ;
589
- else {
590
- if (-1 == (x = openat (dirfd , ".." , O_PATH ))) goto error ;
591
- close (dirfd );
592
- dirfd = x ;
590
+ if (fd ) {
591
+ if (done ) free (llist_pop (& done ));
592
+ if (missing ) missing -- ;
593
+ else if (track ) {
594
+ if (-1 == (fd = openat (dirfd , ".." , O_PATH ))) goto error ;
595
+ close (dirfd );
596
+ dirfd = fd ;
597
+ }
593
598
}
594
599
continue ;
595
600
}
596
601
597
602
// Is this a symlink?
598
- if (exact == -2 && !todo ) len = 0 ;
603
+ if (flags & ( ABS_KEEP << !todo )) errno = len = 0 ;
599
604
else len = readlinkat (dirfd , new -> str , libbuf , sizeof (libbuf ));
600
605
if (len > 4095 ) goto error ;
601
606
602
607
// Not a symlink: add to linked list, move dirfd, fail if error
603
608
if (len < 1 ) {
604
- int fd ;
605
-
606
609
new -> next = done ;
607
610
done = new ;
608
- if (errno == EINVAL && !todo ) break ;
609
- if (errno == ENOENT && exact < 0 ) {
610
- missing ++ ;
611
- continue ;
611
+ if (errno == ENOENT && !(flags & (ABS_PATH <<!todo ))) missing ++ ;
612
+ else if (errno != EINVAL && (flags & (ABS_PATH <<!todo ))) goto error ;
613
+ else if (track ) {
614
+ if (-1 == (fd = openat (dirfd , new -> str , O_PATH ))) goto error ;
615
+ close (dirfd );
616
+ dirfd = fd ;
612
617
}
613
- if (errno != EINVAL && (exact || todo )) goto error ;
614
-
615
- fd = openat (dirfd , new -> str , O_PATH );
616
- if (fd == -1 && (exact || todo || errno != ENOENT )) goto error ;
617
- close (dirfd );
618
- dirfd = fd ;
619
618
continue ;
620
619
}
621
620
622
621
// If this symlink is to an absolute path, discard existing resolved path
623
622
libbuf [len ] = 0 ;
624
623
if (* libbuf == '/' ) {
625
624
llist_traverse (done , free );
626
- done = 0 ;
625
+ done = 0 ;
627
626
close (dirfd );
628
- dirfd = open ( "/" , O_PATH ) ;
627
+ dirfd = -1 ;
629
628
}
630
629
free (new );
631
630
632
- // prepend components of new path. Note symlink to "/" will leave new NULL
631
+ // prepend components of new path. Note symlink to "/" will leave new = NULL
633
632
tail = splitpath (libbuf , & new );
634
633
635
634
// symlink to "/" will return null and leave tail alone
@@ -638,11 +637,10 @@ char *xabspath(char *path, int exact)
638
637
todo = new ;
639
638
}
640
639
}
641
- close (dirfd );
642
-
643
- // At this point done has the path, in reverse order. Reverse list while
644
- // calculating buffer length.
640
+ xclose (dirfd );
645
641
642
+ // At this point done has the path, in reverse order. Reverse list
643
+ // (into todo) while calculating buffer length.
646
644
try = 2 ;
647
645
while (done ) {
648
646
struct string_list * temp = llist_pop (& done );
@@ -654,20 +652,18 @@ char *xabspath(char *path, int exact)
654
652
}
655
653
656
654
// Assemble return buffer
657
-
658
- ret = xmalloc (try );
659
- * ret = '/' ;
660
- ret [try = 1 ] = 0 ;
655
+ * (str = xmalloc (try )) = '/' ;
656
+ str [try = 1 ] = 0 ;
661
657
while (todo ) {
662
- if (try > 1 ) ret [try ++ ] = '/' ;
663
- try = stpcpy (ret + try , todo -> str ) - ret ;
658
+ if (try > 1 ) str [try ++ ] = '/' ;
659
+ try = stpcpy (str + try , todo -> str ) - str ;
664
660
free (llist_pop (& todo ));
665
661
}
666
662
667
- return ret ;
663
+ return str ;
668
664
669
665
error :
670
- close (dirfd );
666
+ xclose (dirfd );
671
667
llist_traverse (todo , free );
672
668
llist_traverse (done , free );
673
669
0 commit comments