Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / nvi / ex / ex_cd.c
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *      Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9
10 #include "config.h"
11
12 #ifndef lint
13 static const char sccsid[] = "@(#)ex_cd.c       10.10 (Berkeley) 8/12/96";
14 #endif /* not lint */
15
16 #include <sys/param.h>
17 #include <sys/queue.h>
18
19 #include <bitstring.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <pwd.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "../common/common.h"
29
30 /*
31  * ex_cd -- :cd[!] [directory]
32  *      Change directories.
33  *
34  * PUBLIC: int ex_cd __P((SCR *, EXCMD *));
35  */
36 int
37 ex_cd(sp, cmdp)
38         SCR *sp;
39         EXCMD *cmdp;
40 {
41         struct passwd *pw;
42         ARGS *ap;
43         CHAR_T savech;
44         char *dir, *p, *t;      /* XXX: END OF THE STACK, DON'T TRUST GETCWD. */
45         char buf[MAXPATHLEN * 2];
46
47         /*
48          * !!!
49          * Historic practice is that the cd isn't attempted if the file has
50          * been modified, unless its name begins with a leading '/' or the
51          * force flag is set.
52          */
53         if (F_ISSET(sp->ep, F_MODIFIED) &&
54             !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') {
55                 msgq(sp, M_ERR,
56     "120|File modified since last complete write; write or use ! to override");
57                 return (1);
58         }
59
60         switch (cmdp->argc) {
61         case 0:
62                 /* If no argument, change to the user's home directory. */
63                 if ((dir = getenv("HOME")) == NULL) {
64                         if ((pw = getpwuid(getuid())) == NULL ||
65                             pw->pw_dir == NULL || pw->pw_dir[0] == '\0') {
66                                 msgq(sp, M_ERR,
67                            "121|Unable to find home directory location");
68                                 return (1);
69                         }
70                         dir = pw->pw_dir;
71                 }
72                 break;
73         case 1:
74                 dir = cmdp->argv[0]->bp;
75                 break;
76         default:
77                 abort();
78         }
79
80         /*
81          * Try the current directory first.  If this succeeds, don't display
82          * a message, vi didn't historically, and it should be obvious to the
83          * user where they are.
84          */
85         if (!chdir(dir))
86                 return (0);
87
88         /*
89          * If moving to the user's home directory, or, the path begins with
90          * "/", "./" or "../", it's the only place we try.
91          */
92         if (cmdp->argc == 0 ||
93             (ap = cmdp->argv[0])->bp[0] == '/' ||
94             ap->len == 1 && ap->bp[0] == '.' ||
95             ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' &&
96             (ap->bp[2] == '/' || ap->bp[2] == '\0'))
97                 goto err;
98
99         /* Try the O_CDPATH option values. */
100         for (p = t = O_STR(sp, O_CDPATH);; ++p)
101                 if (*p == '\0' || *p == ':') {
102                         /*
103                          * Empty strings specify ".".  The only way to get an
104                          * empty string is a leading colon, colons in a row,
105                          * or a trailing colon.  Or, to put it the other way,
106                          * if the length is 1 or less, then we're dealing with
107                          * ":XXX", "XXX::XXXX" , "XXX:", or "".  Since we've
108                          * already tried dot, we ignore tham all.
109                          */
110                         if (t < p - 1) {
111                                 savech = *p;
112                                 *p = '\0';
113                                 (void)snprintf(buf,
114                                     sizeof(buf), "%s/%s", t, dir);
115                                 *p = savech;
116                                 if (!chdir(buf)) {
117                                         if (getcwd(buf, sizeof(buf)) != NULL)
118                 msgq_str(sp, M_INFO, buf, "122|New current directory: %s");
119                                         return (0);
120                                 }
121                         }
122                         t = p + 1;
123                         if (*p == '\0')
124                                 break;
125                 }
126
127 err:    msgq_str(sp, M_SYSERR, dir, "%s");
128         return (1);
129 }