1 /*
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.
5 */
6
7 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
8 /* All Rights Reserved */
9
10 /*
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.
14 */
15
16 #include "sh.h"
17 #include "sh.dir.h"
18 #include "sh.tconst.h"
19
20 /*
21 * C Shell - directory management
22 */
23
24 struct directory *dfind(tchar *);
25 tchar *dfollow(tchar *);
26 tchar *dcanon(tchar *, tchar *);
27 void dtildepr(tchar *, tchar *);
28 void dfree(struct directory *);
29 void dnewcwd(struct directory *);
30
31 int didchdir;
32 bool loginsh;
33 bool havhash2;
34 struct varent shvhed;
35 struct directory *dcwd;
36 struct directory dhead; /* "head" of loop */
37 int printd; /* force name to be printed */
38 static tchar *fakev[] = { S_dirs, NOSTR };
39 char xhash2[HSHSIZ / 8];
40
41 /*
42 * dinit - initialize current working directory
43 */
44 void
dinit(tchar * hp)45 dinit(tchar *hp)
46 {
47 tchar *cp;
48 struct directory *dp;
49 tchar path[MAXPATHLEN];
50
51 #ifdef TRACE
52 tprintf("TRACE- dinit()\n");
53 #endif
54 /*
55 * If this is a login shell, we should have a home directory. But,
56 * if we got here via 'su - <user>' where the user has no directory
57 * in their passwd file, then su has passed HOME=<nothing>, so hp is
58 * non-null, but has zero length. Thus, we do not know the current
59 * working directory based on the home directory.
60 */
61 if (loginsh && hp && *hp)
62 cp = hp;
63 else {
64 cp = getwd_(path);
65 if (cp == NULL) {
66 printf("Warning: cannot determine current directory\n");
67 cp = S_DOT;
68 }
69 }
70 dp = (struct directory *)xcalloc(sizeof (struct directory), 1);
71 dp->di_name = savestr(cp);
72 dp->di_count = 0;
73 dhead.di_next = dhead.di_prev = dp;
74 dp->di_next = dp->di_prev = &dhead;
75 printd = 0;
76 dnewcwd(dp);
77 }
78
79 /*
80 * dodirs - list all directories in directory loop
81 */
82 void
dodirs(tchar ** v)83 dodirs(tchar **v)
84 {
85 struct directory *dp;
86 bool lflag;
87 tchar *hp = value(S_home);
88
89 #ifdef TRACE
90 tprintf("TRACE- dodirs()\n");
91 #endif
92 if (*hp == '\0')
93 hp = NOSTR;
94 if (*++v != NOSTR)
95 if (eq(*v, S_MINl /* "-l" */) && *++v == NOSTR)
96 lflag = 1;
97 else
98 error("Usage: dirs [ -l ]");
99 else
100 lflag = 0;
101 dp = dcwd;
102 do {
103 if (dp == &dhead)
104 continue;
105 if (!lflag && hp != NOSTR) {
106 dtildepr(hp, dp->di_name);
107 } else
108 printf("%t", dp->di_name);
109 printf(" ");
110 } while ((dp = dp->di_prev) != dcwd);
111 printf("\n");
112 }
113
114 void
dtildepr(tchar * home,tchar * dir)115 dtildepr(tchar *home, tchar *dir)
116 {
117
118 #ifdef TRACE
119 tprintf("TRACE- dtildepr()\n");
120 #endif
121 if (!eq(home, S_SLASH /* "/" */) && prefix(home, dir))
122 printf("~%t", dir + strlen_(home));
123 else
124 printf("%t", dir);
125 }
126
127 /*
128 * dochngd - implement chdir command.
129 */
130 void
dochngd(tchar ** v)131 dochngd(tchar **v)
132 {
133 tchar *cp;
134 struct directory *dp;
135
136 #ifdef TRACE
137 tprintf("TRACE- dochngd()\n");
138 #endif
139 printd = 0;
140 if (*++v == NOSTR) {
141 if ((cp = value(S_home)) == NOSTR || *cp == 0)
142 bferr("No home directory");
143 if (chdir_(cp) < 0)
144 bferr("Can't change to home directory");
145 cp = savestr(cp);
146 } else if ((dp = dfind(*v)) != 0) {
147 printd = 1;
148 if (chdir_(dp->di_name) < 0)
149 Perror(dp->di_name);
150 dcwd->di_prev->di_next = dcwd->di_next;
151 dcwd->di_next->di_prev = dcwd->di_prev;
152 goto flushcwd;
153 } else
154 cp = dfollow(*v);
155 dp = (struct directory *)xcalloc(sizeof (struct directory), 1);
156 dp->di_name = cp;
157 dp->di_count = 0;
158 dp->di_next = dcwd->di_next;
159 dp->di_prev = dcwd->di_prev;
160 dp->di_prev->di_next = dp;
161 dp->di_next->di_prev = dp;
162 flushcwd:
163 dfree(dcwd);
164 dnewcwd(dp);
165 }
166
167 /*
168 * dfollow - change to arg directory; fall back on cdpath if not valid
169 */
170 tchar *
dfollow(tchar * cp)171 dfollow(tchar *cp)
172 {
173 tchar *dp;
174 struct varent *c;
175 int cdhashval, cdhashval1;
176 int index;
177 int slash; /* slashes in the argument */
178 tchar *fullpath;
179 tchar *slashcp; /* cp string prepended with a slash */
180
181 #ifdef TRACE
182 tprintf("TRACE- dfollow()\n");
183 #endif
184 cp = globone(cp);
185 if (chdir_(cp) >= 0)
186 goto gotcha;
187
188 /*
189 * If the directory argument has a slash in it,
190 * for example, directory/directory, then can't
191 * find that in the cache table.
192 */
193 slash = any('/', cp);
194
195 /*
196 * Try interpreting wrt successive components of cdpath.
197 * cdpath caching is turned off or directory argument
198 * has a slash in it.
199 */
200 if (cp[0] != '/'
201 && !prefix(S_DOTSLA /* "./" */, cp)
202 && !prefix(S_DOTDOTSLA /* "../" */, cp)
203 && (c = adrof(S_cdpath))
204 && (!havhash2 || slash)) {
205 tchar **cdp;
206 tchar *p;
207 tchar buf[MAXPATHLEN];
208
209 for (cdp = c->vec; *cdp; cdp++) {
210 for (dp = buf, p = *cdp; *dp++ = *p++; )
211 ;
212 dp[-1] = '/';
213 for (p = cp; *dp++ = *p++; )
214 ;
215 if (chdir_(buf) >= 0) {
216 printd = 1;
217 xfree(cp);
218 cp = savestr(buf);
219 goto gotcha;
220 }
221 }
222 }
223
224 /* cdpath caching turned on */
225 if (cp[0] != '/'
226 && !prefix(S_DOTSLA /* "./" */, cp)
227 && !prefix(S_DOTDOTSLA /* "../" */, cp)
228 && (c = adrof(S_cdpath))
229 && havhash2 && !slash) {
230 tchar **pv;
231
232 /* If no cdpath or no paths in cdpath, leave */
233 if (c == 0 || c->vec[0] == 0)
234 pv = justabs;
235 else
236 pv = c->vec;
237
238 slashcp = strspl(S_SLASH, cp);
239
240 cdhashval = hashname(cp);
241
242 /* index points to next path component to test */
243 index = 0;
244
245 /*
246 * Look at each path in cdpath until get a match.
247 * Only look at those path beginning with a slash
248 */
249 do {
250 /* only check cache for absolute pathnames */
251 if (pv[0][0] == '/') {
252 cdhashval1 = hash(cdhashval, index);
253 if (bit(xhash2, cdhashval1)) {
254 /*
255 * concatenate found path with
256 * arg directory
257 */
258 fullpath = strspl(*pv, slashcp);
259 if (chdir_(fullpath) >= 0) {
260 printd = 1;
261 xfree(cp);
262 cp = savestr(fullpath);
263 xfree(slashcp);
264 xfree(fullpath);
265 goto gotcha;
266 }
267 }
268 }
269 /*
270 * relative pathnames are not cached, and must be
271 * checked manually
272 */
273 else {
274 tchar *p;
275 tchar buf[MAXPATHLEN];
276
277 for (dp = buf, p = *pv; *dp++ = *p++; )
278 ;
279 dp[-1] = '/';
280 for (p = cp; *dp++ = *p++; )
281 ;
282 if (chdir_(buf) >= 0) {
283 printd = 1;
284 xfree(cp);
285 cp = savestr(buf);
286 xfree(slashcp);
287 goto gotcha;
288 }
289 }
290 pv++;
291 index++;
292 } while (*pv);
293 }
294
295 /*
296 * Try dereferencing the variable named by the argument.
297 */
298 dp = value(cp);
299 if ((dp[0] == '/' || dp[0] == '.') && chdir_(dp) >= 0) {
300 xfree(cp);
301 cp = savestr(dp);
302 printd = 1;
303 goto gotcha;
304 }
305 xfree(cp); /* XXX, use after free */
306 Perror(cp);
307
308 gotcha:
309 if (*cp != '/') {
310 tchar *p, *q;
311 int cwdlen;
312 int len;
313
314 /*
315 * All in the name of efficiency?
316 */
317
318 if ((cwdlen = (strlen_(dcwd->di_name))) == 1) {
319 if (*dcwd->di_name == '/') /* root */
320 cwdlen = 0;
321 else
322 {
323 /*
324 * if we are here, when the shell started
325 * it was unable to getwd(), lets try it again
326 */
327 tchar path[MAXPATHLEN];
328
329 p = getwd_(path);
330 if (p == NULL)
331 error("cannot determine current directory");
332 else
333 {
334 xfree(dcwd->di_name);
335 dcwd->di_name = savestr(p);
336 xfree(cp);
337 cp = savestr(p);
338 return dcanon(cp, cp);
339 }
340
341 }
342 }
343 /*
344 *
345 * for (p = cp; *p++;)
346 * ;
347 * dp = (tchar *)xalloc((unsigned) (cwdlen + (p - cp) + 1)*sizeof (tchar))
348 */
349 len = strlen_(cp);
350 dp = (tchar *)xalloc((unsigned)(cwdlen + len + 2) * sizeof (tchar));
351 for (p = dp, q = dcwd->di_name; *p++ = *q++; )
352 ;
353 if (cwdlen)
354 p[-1] = '/';
355 else
356 p--; /* don't add a / after root */
357 for (q = cp; *p++ = *q++; )
358 ;
359 xfree(cp);
360 cp = dp;
361 dp += cwdlen;
362 } else
363 dp = cp;
364 return dcanon(cp, dp);
365 }
366
367 /*
368 * dopushd - push new directory onto directory stack.
369 * with no arguments exchange top and second.
370 * with numeric argument (+n) bring it to top.
371 */
372 void
dopushd(tchar ** v)373 dopushd(tchar **v)
374 {
375 struct directory *dp;
376
377 #ifdef TRACE
378 tprintf("TRACE- dopushd()\n");
379 #endif
380 printd = 1;
381 if (*++v == NOSTR) {
382 if ((dp = dcwd->di_prev) == &dhead)
383 dp = dhead.di_prev;
384 if (dp == dcwd)
385 bferr("No other directory");
386 if (chdir_(dp->di_name) < 0)
387 Perror(dp->di_name);
388 dp->di_prev->di_next = dp->di_next;
389 dp->di_next->di_prev = dp->di_prev;
390 dp->di_next = dcwd->di_next;
391 dp->di_prev = dcwd;
392 dcwd->di_next->di_prev = dp;
393 dcwd->di_next = dp;
394 } else if (dp = dfind(*v)) {
395 if (chdir_(dp->di_name) < 0)
396 Perror(dp->di_name);
397 } else {
398 tchar *cp;
399
400 cp = dfollow(*v);
401 dp = (struct directory *)xcalloc(sizeof (struct directory), 1);
402 dp->di_name = cp;
403 dp->di_count = 0;
404 dp->di_prev = dcwd;
405 dp->di_next = dcwd->di_next;
406 dcwd->di_next = dp;
407 dp->di_next->di_prev = dp;
408 }
409 dnewcwd(dp);
410 }
411
412 /*
413 * dfind - find a directory if specified by numeric (+n) argument
414 */
415 struct directory *
dfind(tchar * cp)416 dfind(tchar *cp)
417 {
418 struct directory *dp;
419 int i;
420 tchar *ep;
421
422 #ifdef TRACE
423 tprintf("TRACE- dfind()\n");
424 #endif
425 if (*cp++ != '+')
426 return (0);
427 for (ep = cp; digit(*ep); ep++)
428 continue;
429 if (*ep)
430 return (0);
431 i = getn(cp);
432 if (i <= 0)
433 return (0);
434 for (dp = dcwd; i != 0; i--) {
435 if ((dp = dp->di_prev) == &dhead)
436 dp = dp->di_prev;
437 if (dp == dcwd)
438 bferr("Directory stack not that deep");
439 }
440 return (dp);
441 }
442
443 /*
444 * dopopd - pop a directory out of the directory stack
445 * with a numeric argument just discard it.
446 */
447 void
dopopd(tchar ** v)448 dopopd(tchar **v)
449 {
450 struct directory *dp, *p;
451
452 #ifdef TRACE
453 tprintf("TRACE- dopopd()\n");
454 #endif
455 printd = 1;
456 if (*++v == NOSTR)
457 dp = dcwd;
458 else if ((dp = dfind(*v)) == 0)
459 bferr("Invalid argument");
460 if (dp->di_prev == &dhead && dp->di_next == &dhead)
461 bferr("Directory stack empty");
462 if (dp == dcwd) {
463 if ((p = dp->di_prev) == &dhead)
464 p = dhead.di_prev;
465 if (chdir_(p->di_name) < 0)
466 Perror(p->di_name);
467 }
468 dp->di_prev->di_next = dp->di_next;
469 dp->di_next->di_prev = dp->di_prev;
470 if (dp == dcwd)
471 dnewcwd(p);
472 else
473 dodirs(fakev);
474 dfree(dp);
475 }
476
477 /*
478 * dfree - free the directory (or keep it if it still has ref count)
479 */
480 void
dfree(struct directory * dp)481 dfree(struct directory *dp)
482 {
483
484 #ifdef TRACE
485 tprintf("TRACE- dfree()\n");
486 #endif
487 if (dp->di_count != 0)
488 dp->di_next = dp->di_prev = 0;
489 else
490 xfree(dp->di_name), xfree((tchar *)dp);
491 }
492
493 /*
494 * dcanon - canonicalize the pathname, removing excess ./ and ../ etc.
495 * We are of course assuming that the file system is standardly
496 * constructed (always have ..'s, directories have links).
497 *
498 * If the hardpaths shell variable is set, resolve the
499 * resulting pathname to contain no symbolic link components.
500 */
501 tchar *
dcanon(tchar * cp,tchar * p)502 dcanon(tchar *cp, tchar *p)
503 {
504 tchar *sp; /* rightmost component currently under
505 consideration */
506 tchar *p1, /* general purpose */
507 *p2;
508 bool slash, dotdot, hardpaths;
509
510 #ifdef TRACE
511 tprintf("TRACE- dcannon()\n");
512 #endif
513
514 if (*cp != '/')
515 abort();
516
517 if (hardpaths = (adrof(S_hardpaths) != NULL)) {
518 /*
519 * Be paranoid: don't trust the initial prefix
520 * to be symlink-free.
521 */
522 p = cp;
523 }
524
525 /*
526 * Loop invariant: cp points to the overall path start,
527 * p to its as yet uncanonicalized trailing suffix.
528 */
529 while (*p) { /* for each component */
530 sp = p; /* save slash address */
531
532 while (*++p == '/') /* flush extra slashes */
533 ;
534 if (p != ++sp)
535 for (p1 = sp, p2 = p; *p1++ = *p2++; )
536 ;
537
538 p = sp; /* save start of component */
539 slash = 0;
540 if (*p)
541 while (*++p) /* find next slash or end of path */
542 if (*p == '/') {
543 slash = 1;
544 *p = '\0';
545 break;
546 }
547
548 if (*sp == '\0') {
549 /* component is null */
550 if (--sp == cp) /* if path is one tchar (i.e. /) */
551 break;
552 else
553 *sp = '\0';
554 continue;
555 }
556
557 if (sp[0] == '.' && sp[1] == '\0') {
558 /* Squeeze out component consisting of "." */
559 if (slash) {
560 for (p1 = sp, p2 = p + 1; *p1++ = *p2++; )
561 ;
562 p = --sp;
563 } else if (--sp != cp)
564 *sp = '\0';
565 continue;
566 }
567
568 /*
569 * At this point we have a path of the form "x/yz",
570 * where "x" is null or rooted at "/", "y" is a single
571 * component, and "z" is possibly null. The pointer cp
572 * points to the start of "x", sp to the start of "y",
573 * and p to the beginning of "z", which has been forced
574 * to a null.
575 */
576 /*
577 * Process symbolic link component. Provided that either
578 * the hardpaths shell variable is set or "y" is really
579 * ".." we replace the symlink with its contents. The
580 * second condition for replacement is necessary to make
581 * the command "cd x/.." produce the same results as the
582 * sequence "cd x; cd ..".
583 *
584 * Note that the two conditions correspond to different
585 * potential symlinks. When hardpaths is set, we must
586 * check "x/y"; otherwise, when "y" is known to be "..",
587 * we check "x".
588 */
589 dotdot = sp[0] == '.' && sp[1] == '.' && sp[2] == '\0';
590 if (hardpaths || dotdot) {
591 tchar link[MAXPATHLEN];
592 int cc;
593 tchar *newcp;
594
595 /*
596 * Isolate the end of the component that is to
597 * be checked for symlink-hood.
598 */
599 sp--;
600 if (! hardpaths)
601 *sp = '\0';
602
603 /*
604 * See whether the component is really a symlink by
605 * trying to read it. If the read succeeds, it is.
606 */
607 if ((hardpaths || sp > cp) &&
608 (cc = readlink_(cp, link, MAXPATHLEN)) >= 0) {
609 /*
610 * readlink_ put null, so we don't need this.
611 */
612 /* link[cc] = '\0'; */
613
614 /* Restore path. */
615 if (slash)
616 *p = '/';
617
618 /*
619 * Point p at the start of the trailing
620 * path following the symlink component.
621 * It's already there is hardpaths is set.
622 */
623 if (! hardpaths) {
624 /* Restore path as well. */
625 *(p = sp) = '/';
626 }
627
628 /*
629 * Find length of p.
630 */
631 for (p1 = p; *p1++; )
632 ;
633
634 if (*link != '/') {
635 /*
636 * Relative path: replace the symlink
637 * component with its value. First,
638 * set sp to point to the slash at
639 * its beginning. If hardpaths is
640 * set, this is already the case.
641 */
642 if (! hardpaths) {
643 while (*--sp != '/')
644 ;
645 }
646
647 /*
648 * Terminate the leading part of the
649 * path, including trailing slash.
650 */
651 sp++;
652 *sp = '\0';
653
654 /*
655 * New length is: "x/" + link + "z"
656 */
657 p1 = newcp = (tchar *)xalloc((unsigned)
658 ((sp - cp) + cc + (p1 - p)) * sizeof (tchar));
659 /*
660 * Copy new path into newcp
661 */
662 for (p2 = cp; *p1++ = *p2++; )
663 ;
664 for (p1--, p2 = link; *p1++ = *p2++; )
665 ;
666 for (p1--, p2 = p; *p1++ = *p2++; )
667 ;
668 /*
669 * Restart canonicalization at
670 * expanded "/y".
671 */
672 p = sp - cp - 1 + newcp;
673 } else {
674 /*
675 * New length is: link + "z"
676 */
677 p1 = newcp = (tchar *)xalloc((unsigned)
678 (cc + (p1 - p))*sizeof (tchar));
679 /*
680 * Copy new path into newcp
681 */
682 for (p2 = link; *p1++ = *p2++; )
683 ;
684 for (p1--, p2 = p; *p1++ = *p2++; )
685 ;
686 /*
687 * Restart canonicalization at beginning
688 */
689 p = newcp;
690 }
691 xfree(cp);
692 cp = newcp;
693 continue; /* canonicalize the link */
694 }
695
696 /* The component wasn't a symlink after all. */
697 if (! hardpaths)
698 *sp = '/';
699 }
700
701 if (dotdot) {
702 if (sp != cp)
703 while (*--sp != '/')
704 ;
705 if (slash) {
706 for (p1 = sp + 1, p2 = p + 1; *p1++ = *p2++; )
707 ;
708 p = sp;
709 } else if (cp == sp)
710 *++sp = '\0';
711 else
712 *sp = '\0';
713 continue;
714 }
715
716 if (slash)
717 *p = '/';
718 }
719 return cp;
720 }
721
722 /*
723 * dnewcwd - make a new directory in the loop the current one
724 * and export its name to the PWD environment variable.
725 */
726 void
dnewcwd(struct directory * dp)727 dnewcwd(struct directory *dp)
728 {
729
730 #ifdef TRACE
731 tprintf("TRACE- dnewcwd()\n");
732 #endif
733 dcwd = dp;
734 #ifdef notdef
735 /*
736 * If we have a fast version of getwd available
737 * and hardpaths is set, it would be reasonable
738 * here to verify that dcwd->di_name really does
739 * name the current directory. Later...
740 */
741 #endif /* notdef */
742
743 didchdir = 1;
744 set(S_cwd, savestr(dcwd->di_name));
745 didchdir = 0;
746 local_setenv(S_PWD, dcwd->di_name);
747 if (printd)
748 dodirs(fakev);
749 }
750