1 /*
2  * @(#)path.c	1.1	10/21/84
3  *
4  * These routines implement a path mechanism for the SUN Gremlin
5  * picture editor.  It provides facilities whereby several directories
6  * may be searched for files and a defaulting mechanism for file name
7  * extensions.
8  *
9  * Mark Opperman (opcode@monet.BERKELEY)
10  *
11  */
12 
13 #include "gremlin.h"
14 #include <pwd.h>
15 #include <ctype.h>
16 
17 /* imports from main.c */
18 
19 extern char GLibrary[];
20 
21 /* imports from C */
22 
23 extern char *strcpy();
24 extern char *strncpy();
25 extern char *index();
26 extern char *sprintf();
27 extern char *malloc();
28 
29 /*
30  * The following string holds the current path, consisting of a bunch
31  * of directory names separated by spaces.
32  */
33 
34 #define PATHSIZE 400
35 
36 static char path[PATHSIZE] = ".";
37 
38 /*
39  * The following string pointers constitute a cache of recently
40  * referenced tilde names.
41  */
42 
43 #define NTILDE 10
44 
45 static char *tildename[NTILDE] = { NULL, NULL, NULL, NULL, NULL,
46 				   NULL, NULL, NULL, NULL, NULL };
47 static char *realname[NTILDE]  = { NULL, NULL, NULL, NULL, NULL,
48 				   NULL, NULL, NULL, NULL, NULL };
49 static int discard = 0;
50 
51 
52 /*
53  * This routine converts tilde notation into standard directory names.
54  *
55  * If the conversion was done successfully, then TRUE is returned.
56  * If a user name couldn't be found in the password file, then
57  * FALSE is returned.
58  *
59  * If the first character of the string indicated by psource is a
60  * tilde ("~") then the subsequent user name is converted to a login
61  * directory name and stored in the string indicated by pdest.  Then
62  * remaining characters in the file name at psource are copied to
63  * pdest and both pointers are updated.  Upon return, psource points
64  * to the terminating character in the source file name and pdest
65  * points to the next character after the last character in the file
66  * name at that location.  If a tilde cannot be converted because the
67  * user name cannot be found, psource is still advanced past the current
68  * entry, but pdest is unaffected.  At most size characters will be
69  * stored at pdest, and the size is decremented by the number of
70  * characters we actually stored.
71  *
72  * mro 7/28/84
73  * fixed NULL pointer bugs
74  */
75 PConvertTilde(psource, pdest, size)
76 char **psource;		/* Pointer to a pointer to the source string */
77 char **pdest;		/* Pointer to a pointer to the dest. string */
78 int *size;		/* Pointer to no. bytes available at pdest */
79 {
80     register char *ps, *pd;
81     struct passwd *passwd, *getpwnam();
82     char username[17], *string;
83     int i, length;
84 
85     ps = *psource;
86     string = NULL;
87 
88     if (*ps == '~') {
89 	/* Copy the user name into a string (at most 16 characters).  If we
90 	   don't have a cached entry for this name, then read the password
91 	   file entry for the user and grab out the login directory. */
92 
93 	pd = username;
94 	for (i=0; ; i++) {
95 	    *pd = *++ps;
96 	    if (isspace(*pd) || (*pd == '\0') || (*pd == '/') || (*pd == ':'))
97 		break;
98 	    if (i < 16)
99 		pd++;
100 	}
101 
102 	*pd = '\0';
103 	for (i=0;  i<NTILDE; i++) {
104 	    if (tildename[i] != NULL) {
105 		if (strcmp(tildename[i], username) == 0) {
106 		    string = realname[i];
107 		    break;
108 		}
109 	    }
110 	}
111 
112 	if (string == NULL) { 			/* didn't find cached entry */
113 	    passwd = getpwnam(username);	/* check password file */
114 	    if (passwd != NULL) {		/* got it! */
115 		string = passwd->pw_dir;
116 		if (++discard >= NTILDE)
117 		    discard = 0;
118 		if (tildename[discard] != NULL) {
119 		    free(tildename[discard]);
120 		    free(realname[discard]);
121 		}
122 		tildename[discard] = malloc((unsigned) strlen(username)+1);
123 		strcpy(tildename[discard], username);
124 		realname[discard] = malloc((unsigned) strlen(string)+1);
125 		strcpy(realname[discard], string);
126 	    }
127 	    else {				/* illegal user name */
128 		/* skip the source entry and return */
129 		while ((*ps != '\0') && !isspace(*ps) && (*ps != ':'))
130 		    ps++;
131 		*psource = ps;
132 		return(FALSE);
133 	    }
134 	}
135 
136 	length = strlen(string);
137 	if (length > *size)
138 	    length = *size;
139 	strncpy(*pdest, string, length);
140 	*size -= length;
141 	pd = *pdest+length;
142     }
143     else
144 	pd = *pdest;
145 
146     /* Copy the rest of the directory name from the source to the dest. */
147 
148     while ((*ps != '\0') && !isspace(*ps) && (*ps != ':'))
149 	if (*size > 0) {
150 	    *pd++ = *ps++;
151 	    (*size)--;
152 	}
153 	else
154 	    ps++;
155     *psource = ps;
156     *pdest = pd;
157     return(TRUE);
158 }
159 
160 
161 /*
162  * PSetPath sets up the current search path.
163  *
164  * (-1) is returned if one or more of the paths contained a tilde
165  * notation that couldn't be converted.  (-2) is returned if the
166  * path space was exhausted.  Otherwise, a value >= 0 is returned.
167  *
168  * The string is stored as the current path, and all entries with
169  * tilde notation are converted to non-tilde notation.  Tilde entries
170  * that cannot be converted are ignored.  Note:  only PATHSIZE total
171  * bytes of path are stored, after tilde conversion.  Excess bytes
172  * are truncated.
173  */
174 PSetPath(string)
175 char *string;	/* Pointer to a string that is to become the new file
176 		   search path.  Must consist of one or more directory
177 		   names separated by white space or colons.  Two adjacent
178 		   colons are the same as ":.:".  Tilde notation is ok.  */
179 {
180     int result, spaceleft;
181     char *p;
182 
183     result = 0;
184     spaceleft = PATHSIZE-1;
185     p = path;
186 
187     if (*string == '\0') {
188 	strcpy(path, ".");
189 	return(0);
190     }
191 
192     while (*string != '\0') {
193 	if (*string == ':')
194 	    if ((*++string == ':') && (spaceleft >= 2)) {
195 		*p++ = '.';
196 		*p++ = ' ';
197 		spaceleft -= 2;
198 	    }
199 	if (spaceleft <= 0)
200 	    break;
201 	while ((*string == ':') || isspace(*string))
202 	    string++;
203 	if (!PConvertTilde(&string, &p, &spaceleft))
204 	    result = -1;
205 	else if (spaceleft-- > 0)
206 	    *p++ = ' ';
207     }
208     *p = '\0';
209 
210     if (spaceleft < 2)
211 	return(-2);
212 
213     return(result);
214 }
215 
216 
217 /*
218  *  This routine merely returns a poiner to the current path.
219  */
220 char *
221 PGetPath()
222 {
223     return(path);
224 }
225 
226 
227 /*
228  *  This routine does a file lookup using the current path and
229  *  supplying a default extension.
230  *
231  *  Returns a pointer to a FILE, or NULL if the file couldn't be found.
232  *
233  *  If the first character of the
234  *  file name is "~" or "/" or if search is FALSE, then we try
235  *  to look up the file with the original name, doing tilde
236  *  expansion of course and returning that result.  If none of
237  *  these conditions is met, we go through the current path
238  *  trying to look up the file once for each path
239  *  entry by prepending the path entry to the original file name.
240  *  This concatenated name is stored in a static string and made
241  *  available to the caller through prealname if the open succeeds.
242  *  Note: the static string will be trashed on the next call to this
243  *  routine.  Also, note that no individual file name is allowed to
244  *  be more than NAMESIZE characters long.  Excess characters are lost.
245  */
246 FILE *
247 POpen(file, prealname, search)
248 char *file;			/* Name of the file to be opened. */
249 char **prealname;		/* Pointer to a location that will be filled
250 				   in with the address of the real name of
251 				   the file that was successfully opened.
252 				   If NULL, then nothing is stored.  */
253 int search;			/* If FALSE, then the search path business
254 				   doesn't happen.  A default extension
255 				   will still be added.  */
256 {
257 #define NAMESIZE 128
258     static char realname[NAMESIZE];
259     char extendedname[NAMESIZE], *p, *p2;
260     int length, spaceleft, size;
261     FILE *f;
262 
263     if ((file == NULL) || (file[0] == '\0'))
264 	return((FILE *) NULL);
265 
266     if (prealname != NULL)
267 	*prealname = realname;
268 
269     /* Now try the original name.  If it starts with a ~ or / look
270        it up directly.  */
271 
272     if (file[0] == '~') {
273 	p = realname;
274 	length = NAMESIZE-1;
275 	if (!PConvertTilde(&file, &p, &length))
276 	    return(NULL);
277 	*p = '\0';
278 	return(fopen(realname, "r"));
279     }
280 
281     if ((file[0] == '/') || (search == FALSE)) {
282 	strncpy(realname, file, NAMESIZE-1);
283 	realname[NAMESIZE-1] = '\0';
284 	return(fopen(realname, "r"));
285     }
286 
287     /* Now try going through the path. */
288 
289     p = path;
290     while (*p != '\0') {
291 	spaceleft = NAMESIZE-1;
292 	p2 = realname;
293 	while (isspace(*p))
294 	    p++;
295 	while ((*p != '\0') && !isspace(*p)) {
296 	    if (spaceleft-- > 0)
297 		*p2++ = *p++;
298 	    else
299 		p++;
300 	}
301 	if (spaceleft-- > 0)
302 	    *p2++ = '/';
303 	if (spaceleft > 0)
304 	    strncpy(p2, file, spaceleft);
305 	realname[NAMESIZE-1] = '\0';
306 	f = fopen(realname, "r");
307 	if (f != NULL) {
308 	    /* if file in current directory fix up prealname */
309 	    if ((strncmp(realname, "./", 2) == 0) && (prealname != NULL))
310 		    (*prealname) += 2;
311 	    return(f);
312 	}
313     }
314 
315     /* We've tried the path and that didn't work.  As a last shot,
316        try the library area.  Only use the library for reads.  */
317 
318     p = GLibrary;
319     p2 = realname;
320     size = NAMESIZE;
321     if (!PConvertTilde(&p, &p2, &size))
322 	return(FALSE);
323     (void) strncpy(p2, file, size);
324     realname[NAMESIZE-1] = '\0';
325     return(fopen(realname, "r"));
326 }
327