2 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 * Copyright (c) 2016 by Delphix. All rights reserved.
7 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
8 /* All Rights Reserved */
11 * Copyright (c) 1980 Regents of the University of California.
12 * All rights reserved. The Berkeley Software License Agreement
13 * specifies the terms and conditions for redistribution.
16 #pragma ident "%Z%%M% %I% %E% SMI"
20 #include "sh.tconst.h"
23 * C Shell - directory management
26 struct directory
*dfind(tchar
*);
27 tchar
*dfollow(tchar
*);
28 tchar
*dcanon(tchar
*, tchar
*);
29 void dtildepr(tchar
*, tchar
*);
30 void dfree(struct directory
*);
31 void dnewcwd(struct directory
*);
33 struct directory dhead
; /* "head" of loop */
34 int printd
; /* force name to be printed */
35 static tchar
*fakev
[] = { S_dirs
, NOSTR
};
38 * dinit - initialize current working directory
45 tchar path
[MAXPATHLEN
];
48 tprintf("TRACE- dinit()\n");
51 * If this is a login shell, we should have a home directory. But,
52 * if we got here via 'su - <user>' where the user has no directory
53 * in their passwd file, then su has passed HOME=<nothing>, so hp is
54 * non-null, but has zero length. Thus, we do not know the current
55 * working directory based on the home directory.
57 if (loginsh
&& hp
&& *hp
)
62 printf("Warning: cannot determine current directory\n");
66 dp
= (struct directory
*)xcalloc(sizeof (struct directory
), 1);
67 dp
->di_name
= savestr(cp
);
69 dhead
.di_next
= dhead
.di_prev
= dp
;
70 dp
->di_next
= dp
->di_prev
= &dhead
;
76 * dodirs - list all directories in directory loop
83 tchar
*hp
= value(S_home
);
86 tprintf("TRACE- dodirs()\n");
91 if (eq(*v
, S_MINl
/* "-l" */) && *++v
== NOSTR
)
94 error("Usage: dirs [ -l ]");
101 if (!lflag
&& hp
!= NOSTR
) {
102 dtildepr(hp
, dp
->di_name
);
104 printf("%t", dp
->di_name
);
106 } while ((dp
= dp
->di_prev
) != dcwd
);
111 dtildepr(tchar
*home
, tchar
*dir
)
115 tprintf("TRACE- dtildepr()\n");
117 if (!eq(home
, S_SLASH
/* "/" */) && prefix(home
, dir
))
118 printf("~%t", dir
+ strlen_(home
));
124 * dochngd - implement chdir command.
130 struct directory
*dp
;
133 tprintf("TRACE- dochngd()\n");
137 if ((cp
= value(S_home
)) == NOSTR
|| *cp
== 0)
138 bferr("No home directory");
140 bferr("Can't change to home directory");
142 } else if ((dp
= dfind(*v
)) != 0) {
144 if (chdir_(dp
->di_name
) < 0)
146 dcwd
->di_prev
->di_next
= dcwd
->di_next
;
147 dcwd
->di_next
->di_prev
= dcwd
->di_prev
;
151 dp
= (struct directory
*)xcalloc(sizeof (struct directory
), 1);
154 dp
->di_next
= dcwd
->di_next
;
155 dp
->di_prev
= dcwd
->di_prev
;
156 dp
->di_prev
->di_next
= dp
;
157 dp
->di_next
->di_prev
= dp
;
164 * dfollow - change to arg directory; fall back on cdpath if not valid
171 int cdhashval
, cdhashval1
;
173 int slash
; /* slashes in the argument */
175 tchar
*slashcp
; /* cp string prepended with a slash */
178 tprintf("TRACE- dfollow()\n");
185 * If the directory argument has a slash in it,
186 * for example, directory/directory, then can't
187 * find that in the cache table.
189 slash
= any('/', cp
);
192 * Try interpreting wrt successive components of cdpath.
193 * cdpath caching is turned off or directory argument
197 && !prefix(S_DOTSLA
/* "./" */, cp
)
198 && !prefix(S_DOTDOTSLA
/* "../" */, cp
)
199 && (c
= adrof(S_cdpath
))
200 && (!havhash2
|| slash
)) {
203 tchar buf
[MAXPATHLEN
];
205 for (cdp
= c
->vec
; *cdp
; cdp
++) {
206 for (dp
= buf
, p
= *cdp
; *dp
++ = *p
++; )
209 for (p
= cp
; *dp
++ = *p
++; )
211 if (chdir_(buf
) >= 0) {
220 /* cdpath caching turned on */
222 && !prefix(S_DOTSLA
/* "./" */, cp
)
223 && !prefix(S_DOTDOTSLA
/* "../" */, cp
)
224 && (c
= adrof(S_cdpath
))
225 && havhash2
&& !slash
) {
228 /* If no cdpath or no paths in cdpath, leave */
229 if (c
== 0 || c
->vec
[0] == 0)
234 slashcp
= strspl(S_SLASH
, cp
);
236 cdhashval
= hashname(cp
);
238 /* index points to next path component to test */
242 * Look at each path in cdpath until get a match.
243 * Only look at those path beginning with a slash
246 /* only check cache for absolute pathnames */
247 if (pv
[0][0] == '/') {
248 cdhashval1
= hash(cdhashval
, index
);
249 if (bit(xhash2
, cdhashval1
)) {
251 * concatenate found path with
254 fullpath
= strspl(*pv
, slashcp
);
255 if (chdir_(fullpath
) >= 0) {
258 cp
= savestr(fullpath
);
266 * relative pathnames are not cached, and must be
271 tchar buf
[MAXPATHLEN
];
273 for (dp
= buf
, p
= *pv
; *dp
++ = *p
++; )
276 for (p
= cp
; *dp
++ = *p
++; )
278 if (chdir_(buf
) >= 0) {
292 * Try dereferencing the variable named by the argument.
295 if ((dp
[0] == '/' || dp
[0] == '.') && chdir_(dp
) >= 0) {
301 xfree(cp
); /* XXX, use after free */
311 * All in the name of efficiency?
314 if ((cwdlen
= (strlen_(dcwd
->di_name
))) == 1) {
315 if (*dcwd
->di_name
== '/') /* root */
320 * if we are here, when the shell started
321 * it was unable to getwd(), lets try it again
323 tchar path
[MAXPATHLEN
];
327 error("cannot determine current directory");
330 xfree(dcwd
->di_name
);
331 dcwd
->di_name
= savestr(p
);
334 return dcanon(cp
, cp
);
341 * for (p = cp; *p++;)
343 * dp = (tchar *)xalloc((unsigned) (cwdlen + (p - cp) + 1)*sizeof (tchar))
346 dp
= (tchar
*)xalloc((unsigned)(cwdlen
+ len
+ 2) * sizeof (tchar
));
347 for (p
= dp
, q
= dcwd
->di_name
; *p
++ = *q
++; )
352 p
--; /* don't add a / after root */
353 for (q
= cp
; *p
++ = *q
++; )
360 return dcanon(cp
, dp
);
364 * dopushd - push new directory onto directory stack.
365 * with no arguments exchange top and second.
366 * with numeric argument (+n) bring it to top.
371 struct directory
*dp
;
374 tprintf("TRACE- dopushd()\n");
378 if ((dp
= dcwd
->di_prev
) == &dhead
)
381 bferr("No other directory");
382 if (chdir_(dp
->di_name
) < 0)
384 dp
->di_prev
->di_next
= dp
->di_next
;
385 dp
->di_next
->di_prev
= dp
->di_prev
;
386 dp
->di_next
= dcwd
->di_next
;
388 dcwd
->di_next
->di_prev
= dp
;
390 } else if (dp
= dfind(*v
)) {
391 if (chdir_(dp
->di_name
) < 0)
397 dp
= (struct directory
*)xcalloc(sizeof (struct directory
), 1);
401 dp
->di_next
= dcwd
->di_next
;
403 dp
->di_next
->di_prev
= dp
;
409 * dfind - find a directory if specified by numeric (+n) argument
414 struct directory
*dp
;
419 tprintf("TRACE- dfind()\n");
423 for (ep
= cp
; digit(*ep
); ep
++)
430 for (dp
= dcwd
; i
!= 0; i
--) {
431 if ((dp
= dp
->di_prev
) == &dhead
)
434 bferr("Directory stack not that deep");
440 * dopopd - pop a directory out of the directory stack
441 * with a numeric argument just discard it.
446 struct directory
*dp
, *p
;
449 tprintf("TRACE- dopopd()\n");
454 else if ((dp
= dfind(*v
)) == 0)
455 bferr("Invalid argument");
456 if (dp
->di_prev
== &dhead
&& dp
->di_next
== &dhead
)
457 bferr("Directory stack empty");
459 if ((p
= dp
->di_prev
) == &dhead
)
461 if (chdir_(p
->di_name
) < 0)
464 dp
->di_prev
->di_next
= dp
->di_next
;
465 dp
->di_next
->di_prev
= dp
->di_prev
;
474 * dfree - free the directory (or keep it if it still has ref count)
477 dfree(struct directory
*dp
)
481 tprintf("TRACE- dfree()\n");
483 if (dp
->di_count
!= 0)
484 dp
->di_next
= dp
->di_prev
= 0;
486 xfree(dp
->di_name
), xfree((tchar
*)dp
);
490 * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
491 * We are of course assuming that the file system is standardly
492 * constructed (always have ..'s, directories have links).
494 * If the hardpaths shell variable is set, resolve the
495 * resulting pathname to contain no symbolic link components.
498 dcanon(tchar
*cp
, tchar
*p
)
500 tchar
*sp
; /* rightmost component currently under
502 tchar
*p1
, /* general purpose */
504 bool slash
, dotdot
, hardpaths
;
507 tprintf("TRACE- dcannon()\n");
513 if (hardpaths
= (adrof(S_hardpaths
) != NULL
)) {
515 * Be paranoid: don't trust the initial prefix
516 * to be symlink-free.
522 * Loop invariant: cp points to the overall path start,
523 * p to its as yet uncanonicalized trailing suffix.
525 while (*p
) { /* for each component */
526 sp
= p
; /* save slash address */
528 while (*++p
== '/') /* flush extra slashes */
531 for (p1
= sp
, p2
= p
; *p1
++ = *p2
++; )
534 p
= sp
; /* save start of component */
537 while (*++p
) /* find next slash or end of path */
545 /* component is null */
546 if (--sp
== cp
) /* if path is one tchar (i.e. /) */
553 if (sp
[0] == '.' && sp
[1] == '\0') {
554 /* Squeeze out component consisting of "." */
556 for (p1
= sp
, p2
= p
+ 1; *p1
++ = *p2
++; )
559 } else if (--sp
!= cp
)
565 * At this point we have a path of the form "x/yz",
566 * where "x" is null or rooted at "/", "y" is a single
567 * component, and "z" is possibly null. The pointer cp
568 * points to the start of "x", sp to the start of "y",
569 * and p to the beginning of "z", which has been forced
573 * Process symbolic link component. Provided that either
574 * the hardpaths shell variable is set or "y" is really
575 * ".." we replace the symlink with its contents. The
576 * second condition for replacement is necessary to make
577 * the command "cd x/.." produce the same results as the
578 * sequence "cd x; cd ..".
580 * Note that the two conditions correspond to different
581 * potential symlinks. When hardpaths is set, we must
582 * check "x/y"; otherwise, when "y" is known to be "..",
585 dotdot
= sp
[0] == '.' && sp
[1] == '.' && sp
[2] == '\0';
586 if (hardpaths
|| dotdot
) {
587 tchar link
[MAXPATHLEN
];
592 * Isolate the end of the component that is to
593 * be checked for symlink-hood.
600 * See whether the component is really a symlink by
601 * trying to read it. If the read succeeds, it is.
603 if ((hardpaths
|| sp
> cp
) &&
604 (cc
= readlink_(cp
, link
, MAXPATHLEN
)) >= 0) {
606 * readlink_ put null, so we don't need this.
608 /* link[cc] = '\0'; */
615 * Point p at the start of the trailing
616 * path following the symlink component.
617 * It's already there is hardpaths is set.
620 /* Restore path as well. */
627 for (p1
= p
; *p1
++; )
632 * Relative path: replace the symlink
633 * component with its value. First,
634 * set sp to point to the slash at
635 * its beginning. If hardpaths is
636 * set, this is already the case.
644 * Terminate the leading part of the
645 * path, including trailing slash.
651 * New length is: "x/" + link + "z"
653 p1
= newcp
= (tchar
*)xalloc((unsigned)
654 ((sp
- cp
) + cc
+ (p1
- p
)) * sizeof (tchar
));
656 * Copy new path into newcp
658 for (p2
= cp
; *p1
++ = *p2
++; )
660 for (p1
--, p2
= link
; *p1
++ = *p2
++; )
662 for (p1
--, p2
= p
; *p1
++ = *p2
++; )
665 * Restart canonicalization at
668 p
= sp
- cp
- 1 + newcp
;
671 * New length is: link + "z"
673 p1
= newcp
= (tchar
*)xalloc((unsigned)
674 (cc
+ (p1
- p
))*sizeof (tchar
));
676 * Copy new path into newcp
678 for (p2
= link
; *p1
++ = *p2
++; )
680 for (p1
--, p2
= p
; *p1
++ = *p2
++; )
683 * Restart canonicalization at beginning
689 continue; /* canonicalize the link */
692 /* The component wasn't a symlink after all. */
702 for (p1
= sp
+ 1, p2
= p
+ 1; *p1
++ = *p2
++; )
719 * dnewcwd - make a new directory in the loop the current one
720 * and export its name to the PWD environment variable.
723 dnewcwd(struct directory
*dp
)
727 tprintf("TRACE- dnewcwd()\n");
732 * If we have a fast version of getwd available
733 * and hardpaths is set, it would be reasonable
734 * here to verify that dcwd->di_name really does
735 * name the current directory. Later...
740 set(S_cwd
, savestr(dcwd
->di_name
));
742 local_setenv(S_PWD
, dcwd
->di_name
);