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