xref: /openbsd-src/usr.bin/mg/fileio.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: fileio.c,v 1.19 2001/07/09 07:04:49 deraadt Exp $	*/
2 
3 /*
4  *	POSIX fileio.c
5  */
6 #include	"def.h"
7 
8 static FILE	*ffp;
9 
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/dir.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 
17 /*
18  * Open a file for reading.
19  */
20 int
21 ffropen(fn, bp)
22 	char	*fn;
23 	BUFFER	*bp;
24 {
25 	struct stat	statbuf;
26 
27 	if ((ffp = fopen(fn, "r")) == NULL)
28 		return (FIOFNF);
29 	if (bp && fstat(fileno(ffp), &statbuf) == 0) {
30 		/* set highorder bit to make sure this isn't all zero */
31 		bp->b_fi.fi_mode = statbuf.st_mode | 0x8000;
32 		bp->b_fi.fi_uid = statbuf.st_uid;
33 		bp->b_fi.fi_gid = statbuf.st_gid;
34 	}
35 	return (FIOSUC);
36 }
37 
38 /*
39  * Open a file for writing.
40  * Return TRUE if all is well, and
41  * FALSE on error (cannot create).
42  */
43 int
44 ffwopen(fn, bp)
45 	char   *fn;
46 	BUFFER *bp;
47 {
48 
49 	if ((ffp = fopen(fn, "w")) == NULL) {
50 		ewprintf("Cannot open file for writing");
51 		return (FIOERR);
52 	}
53 
54 	/*
55 	 * If we have file information, use it.  We don't bother to check for
56 	 * errors, because there's no a lot we can do about it.  Certainly
57 	 * trying to change ownership will fail if we aren' root.  That's
58 	 * probably OK.  If we don't have info, no need to get it, since any
59 	 * future writes will do the same thing.
60 	 */
61 	if (bp && bp->b_fi.fi_mode) {
62 		chmod(fn, bp->b_fi.fi_mode & 07777);
63 		chown(fn, bp->b_fi.fi_uid, bp->b_fi.fi_gid);
64 	}
65 	return (FIOSUC);
66 }
67 
68 /*
69  * Close a file.
70  * XXX - Should look at the status.
71  */
72 /* ARGSUSED */
73 int
74 ffclose(bp)
75 	BUFFER *bp;
76 {
77 
78 	(void) fclose(ffp);
79 	return (FIOSUC);
80 }
81 
82 /*
83  * Write a buffer to the already
84  * opened file. bp points to the
85  * buffer. Return the status.
86  * Check only at the newline and
87  * end of buffer.
88  */
89 int
90 ffputbuf(bp)
91 	BUFFER *bp;
92 {
93 	char   *cp;
94 	char   *cpend;
95 	LINE   *lp;
96 	LINE   *lpend;
97 
98 	lpend = bp->b_linep;
99 	lp = lforw(lpend);
100 	do {
101 		cp = &ltext(lp)[0];		/* begining of line	 */
102 		cpend = &cp[llength(lp)];	/* end of line		 */
103 		while (cp != cpend) {
104 			putc(*cp, ffp);
105 			cp++;			/* putc may evaluate arguments
106 						   more than once */
107 		}
108 		lp = lforw(lp);
109 		if (lp == lpend)
110 			break;			/* no implied \n on last line */
111 		putc('\n', ffp);
112 	} while (!ferror(ffp));
113 	if (ferror(ffp)) {
114 		ewprintf("Write I/O error");
115 		return FIOERR;
116 	}
117 	return (FIOSUC);
118 }
119 
120 /*
121  * Read a line from a file, and store the bytes
122  * in the supplied buffer. Stop on end of file or end of
123  * line.  When FIOEOF is returned, there is a valid line
124  * of data without the normally implied \n.
125  */
126 int
127 ffgetline(buf, nbuf, nbytes)
128 	char	*buf;
129 	int	nbuf;
130 	int	*nbytes;
131 {
132 	int	c, i;
133 
134 	i = 0;
135 	while ((c = getc(ffp)) != EOF && c != '\n') {
136 		buf[i++] = c;
137 		if (i >= nbuf)
138 			return FIOLONG;
139 	}
140 	if (c == EOF && ferror(ffp) != FALSE) {
141 		ewprintf("File read error");
142 		return FIOERR;
143 	}
144 	*nbytes = i;
145 	return c == EOF ? FIOEOF : FIOSUC;
146 }
147 
148 #ifndef NO_BACKUP
149 /*
150  * Make a backup copy of "fname".  On Unix the backup has the same
151  * name as the original file, with a "~" on the end; this seems to
152  * be newest of the new-speak. The error handling is all in "file.c".
153  * We do a copy instead of a rename since otherwise another process
154  * with an open fd will get the backup, not the new file.  This is
155  * a problem when using mg with things like crontab and vipw.
156  */
157 int
158 fbackupfile(fn)
159 	char  *fn;
160 {
161 	struct stat	sb;
162 	int		from, to, serrno;
163 	size_t		nread;
164 	size_t		len;
165 	char		buf[BUFSIZ];
166 	char		*nname;
167 
168 	len = strlen(fn);
169 	if ((nname = malloc(len + 1 + 1)) == NULL) {
170 		ewprintf("Can't get %d bytes", len + 1 + 1);
171 		return (ABORT);
172 	}
173 	(void) strcpy(nname, fn);
174 	(void) strcpy(nname + len, "~");
175 
176 	if (stat(fn, &sb) == -1) {
177 		ewprintf("Can't stat %s", fn);
178 		return (FALSE);
179 	}
180 
181 	if ((from = open(fn, O_RDONLY)) == -1)
182 		return (FALSE);
183 	to = open(nname, O_WRONLY|O_CREAT|O_TRUNC, (sb.st_mode & 0777));
184 	if (to == -1) {
185 		serrno = errno;
186 		close(from);
187 		errno = serrno;
188 		return (FALSE);
189 	}
190 	while ((nread = read(from, buf, sizeof(buf))) > 0) {
191 		if (write(to, buf, nread) != nread) {
192 		    nread = -1;
193 		    break;
194 		}
195 	}
196 	serrno = errno;
197 	close(from);
198 	close(to);
199 	if (nread == -1)
200 		unlink(nname);
201 	free(nname);
202 	errno = serrno;
203 	return (nread == -1 ? FALSE : TRUE);
204 }
205 #endif
206 
207 /*
208  * The string "fn" is a file name.
209  * Perform any required appending of directory name or case adjustments.
210  * If NO_DIR is not defined, the same file should be refered to even if the
211  * working directory changes.
212  */
213 #ifdef SYMBLINK
214 #include <sys/types.h>
215 #include <sys/stat.h>
216 #ifndef MAXLINK
217 #define MAXLINK 8		/* maximum symbolic links to follow */
218 #endif
219 #endif
220 #include <pwd.h>
221 #ifndef NO_DIR
222 extern char	*wdir;
223 #endif
224 
225 char *
226 adjustname(fn)
227 	char	*fn;
228 {
229 	char		*cp;
230 	static char	fnb[NFILEN];
231 	struct passwd	*pwent;
232 #ifdef	SYMBLINK
233 	struct stat	statbuf;
234 	int		i, j;
235 	char		linkbuf[NFILEN];
236 #endif
237 
238 	switch (*fn) {
239 	case '/':
240 		cp = fnb;
241 		*cp++ = *fn++;
242 		break;
243 	case '~':
244 		fn++;
245 		cp = getenv("HOME");
246 		if (cp != NULL && *cp != '\0' && (*fn == '/' || *fn == '\0')) {
247 			cp = fnb + strlcpy(fnb, cp, sizeof(fnb));
248 			if (*fn)
249 				fn++;
250 			break;
251 		} else {
252 			cp = fnb;
253 			while (*fn && *fn != '/')
254 				*cp++ = *fn++;
255 			*cp = '\0';
256 			if ((pwent = getpwnam(fnb)) != NULL) {
257 				cp = fnb + strlcpy(fnb, pwent->pw_dir, sizeof(fnb));
258 				break;
259 			} else {
260 				fn -= strlen(fnb) + 1;
261 				/* can't find ~user, continue to default case */
262 			}
263 		}
264 	default:
265 #ifndef	NODIR
266 		cp = fnb + strlcpy(fnb, wdir, sizeof(fnb));
267 		break;
268 #else
269 		return fn;	/* punt */
270 #endif
271 	}
272 	if (cp != fnb && cp[-1] != '/')
273 		*cp++ = '/';
274 	while (*fn) {
275 		switch (*fn) {
276 		case '.':
277 			switch (fn[1]) {
278 			case '\0':
279 				*--cp = '\0';
280 				return fnb;
281 			case '/':
282 				fn += 2;
283 				continue;
284 			case '.':
285 				if (fn[2] != '/' && fn[2] != '\0')
286 					break;
287 #ifdef SYMBLINK
288 				cp[-1] = '\0';
289 				for (j = MAXLINK; j-- &&
290 				     lstat(fnb, &statbuf) != -1 &&
291 				     (statbuf.st_mode & S_IFMT) == S_IFLNK &&
292 				     (i = readlink(fnb, linkbuf, sizeof linkbuf))
293 				     != -1;) {
294 					if (linkbuf[0] != '/') {
295 						--cp;
296 						while (cp > fnb && *--cp != '/') {
297 						}
298 						++cp;
299 						(void) strncpy(cp, linkbuf, i);
300 						cp += i;
301 					} else {
302 						(void) strncpy(fnb, linkbuf, i);
303 						cp = fnb + i;
304 					}
305 					if (cp[-1] != '/')
306 						*cp++ = '\0';
307 					else
308 						cp[-1] = '\0';
309 				}
310 				cp[-1] = '/';
311 #endif
312 				--cp;
313 				while (cp > fnb && *--cp != '/') {
314 				}
315 				++cp;
316 				if (fn[2] == '\0') {
317 					*--cp = '\0';
318 					return fnb;
319 				}
320 				fn += 3;
321 				continue;
322 			default:
323 				break;
324 			}
325 			break;
326 		case '/':
327 			fn++;
328 			continue;
329 		default:
330 			break;
331 		}
332 		while (*fn && (*cp++ = *fn++) != '/') {
333 		}
334 	}
335 	if (cp[-1] == '/')
336 		--cp;
337 	*cp = '\0';
338 	return fnb;
339 }
340 
341 #ifndef NO_STARTUP
342 /*
343  * Find a startup file for the user and return its name. As a service
344  * to other pieces of code that may want to find a startup file (like
345  * the terminal driver in particular), accepts a suffix to be appended
346  * to the startup file name.
347  */
348 char *
349 startupfile(suffix)
350 	char	*suffix;
351 {
352 	static char	file[NFILEN];
353 	char		*home;
354 
355 	if ((home = getenv("HOME")) == NULL || *home == '\0')
356 		goto nohome;
357 
358 	if (suffix == NULL) {
359 		if (snprintf(file, sizeof(file), "%s/.mg", home)
360 			    >= sizeof(file))
361 			return NULL;
362 	} else {
363 		if (snprintf(file, sizeof(file), "%s/.mg-%s", home, suffix)
364 			    >= sizeof(file))
365 			return NULL;
366 	}
367 
368 	if (access(file, R_OK) == 0)
369 		return file;
370 nohome:
371 #ifdef STARTUPFILE
372 	if (suffix == NULL)
373 		if (snprintf(file, sizeof(file), "%s", STARTUPFILE)
374 			    >= sizeof(file))
375 			return NULL;
376 	} else {
377 		if (snprintf(file, sizeof(file), "%s%s", STARTUPFILE, suffix)
378 			    >= sizeof(file))
379 			return NULL;
380 	}
381 
382 	if (access(file, R_OK) == 0)
383 		return file;
384 #endif
385 	return NULL;
386 }
387 #endif
388 
389 #ifndef NO_DIRED
390 #include <sys/wait.h>
391 #include "kbd.h"
392 
393 int
394 copy(frname, toname)
395 	char	*frname;
396 	char	*toname;
397 {
398 	pid_t	pid;
399 	int	status;
400 
401 	if ((pid = vfork())) {
402 		if (pid == -1)
403 			return -1;
404 		execl("/bin/cp", "cp", frname, toname, (char *)NULL);
405 		_exit(1);	/* shouldn't happen */
406 	}
407 	waitpid(pid, &status, 0);
408 	return (WIFEXITED(status) && WEXITSTATUS(status) == 0);
409 }
410 
411 BUFFER *
412 dired_(dirname)
413 	char	*dirname;
414 {
415 	BUFFER	*bp;
416 	FILE	*dirpipe;
417 	char	line[256];
418 	int	len;
419 
420 	if ((dirname = adjustname(dirname)) == NULL) {
421 		ewprintf("Bad directory name");
422 		return NULL;
423 	}
424 	/* this should not be done, instead adjustname() should get a flag */
425 	len = strlen(dirname);
426 	if (dirname[len - 1] != '/') {
427 		dirname[len++] = '/';
428 		dirname[len] = '\0';
429 	}
430 	if ((bp = findbuffer(dirname)) == NULL) {
431 		ewprintf("Could not create buffer");
432 		return NULL;
433 	}
434 	if (bclear(bp) != TRUE)
435 		return FALSE;
436 	if (snprintf(line, sizeof(line), "ls -al %s", dirname) >= sizeof(line)){
437 		ewprintf("Path too long");
438 		return NULL;
439 	}
440 	if ((dirpipe = popen(line, "r")) == NULL) {
441 		ewprintf("Problem opening pipe to ls");
442 		return NULL;
443 	}
444 	line[0] = line[1] = ' ';
445 	while (fgets(&line[2], sizeof(line) - 2, dirpipe) != NULL) {
446 		line[strlen(line) - 1] = '\0';	/* remove ^J	 */
447 		(void) addline(bp, line);
448 	}
449 	if (pclose(dirpipe) == -1) {
450 		ewprintf("Problem closing pipe to ls");
451 		return NULL;
452 	}
453 	bp->b_dotp = lforw(bp->b_linep);	/* go to first line */
454 	(void) strncpy(bp->b_fname, dirname, NFILEN);
455 	if ((bp->b_modes[0] = name_mode("dired")) == NULL) {
456 		bp->b_modes[0] = name_mode("fundamental");
457 		ewprintf("Could not find mode dired");
458 		return NULL;
459 	}
460 	bp->b_nmodes = 0;
461 	return bp;
462 }
463 
464 int
465 d_makename(lp, fn)
466 	LINE  *lp;
467 	char  *fn;
468 {
469 	char  *cp;
470 
471 	if (llength(lp) <= 56)
472 		return ABORT;
473 	(void) strcpy(fn, curbp->b_fname);
474 	cp = fn + strlen(fn);
475 	bcopy(&lp->l_text[56], cp, llength(lp) - 56);
476 	cp[llength(lp) - 56] = '\0';
477 	return lgetc(lp, 2) == 'd';
478 }
479 #endif				/* NO_DIRED */
480 
481 struct filelist {
482 	LIST	fl_l;
483 	char	fl_name[NFILEN + 2];
484 };
485 
486 /*
487  * return list of file names that match the name in buf.
488  */
489 
490 LIST *
491 make_file_list(buf)
492 	char	*buf;
493 {
494 	char		*dir, *file, *cp;
495 	int		len, preflen;
496 	DIR		*dirp;
497 	struct dirent	*dent;
498 	LIST		*last;
499 	struct filelist *current;
500 	char		prefixx[NFILEN + 1];
501 
502 	/*
503 	 * We need three different strings: dir - the name of the directory
504 	 * containing what the user typed. Must be a real unix file name,
505 	 * e.g. no ~user, etc..  Must not end in /. prefix - the portion of
506 	 * what the user typed that is before the names we are going to find
507 	 * in the directory.  Must have a trailing / if the user typed it.
508 	 * names from the directory. we open dir, and return prefix
509 	 * concatenated with names.
510 	 */
511 
512 	/* first we get a directory name we can look up */
513 	/*
514 	 * Names ending in . are potentially odd, because adjustname will
515 	 * treat foo/.. as a reference to another directory, whereas we are
516 	 * interested in names starting with ..
517 	 */
518 	len = strlen(buf);
519 	if (buf[len - 1] == '.') {
520 		buf[len - 1] = 'x';
521 		dir = adjustname(buf);
522 		buf[len - 1] = '.';
523 	} else
524 		dir = adjustname(buf);
525 	/*
526 	 * If the user typed a trailing / or the empty string
527 	 * he wants us to use his file spec as a directory name.
528 	 */
529 	if (buf[0] && buf[strlen(buf) - 1] != '/') {
530 		file = strrchr(dir, '/');
531 		if (file) {
532 			*file = 0;
533 			if (*dir == 0)
534 				dir = "/";
535 		} else {
536 			return (NULL);
537 		}
538 	}
539 	/* Now we get the prefix of the name the user typed. */
540 	strcpy(prefixx, buf);
541 	cp = strrchr(prefixx, '/');
542 	if (cp == NULL)
543 		prefixx[0] = 0;
544 	else
545 		cp[1] = 0;
546 
547 	preflen = strlen(prefixx);
548 	/* cp is the tail of buf that really needs to be compared */
549 	cp = buf + preflen;
550 	len = strlen(cp);
551 
552 	/*
553 	 * Now make sure that file names will fit in the buffers allocated.
554 	 * SV files are fairly short.  For BSD, something more general would
555 	 * be required.
556 	 */
557 	if ((preflen + MAXNAMLEN) > NFILEN)
558 		return (NULL);
559 
560 	/* loop over the specified directory, making up the list of files */
561 
562 	/*
563 	 * Note that it is worth our time to filter out names that don't
564 	 * match, even though our caller is going to do so again, and to
565 	 * avoid doing the stat if completion is being done, because stat'ing
566 	 * every file in the directory is relatively expensive.
567 	 */
568 
569 	dirp = opendir(dir);
570 	if (dirp == NULL)
571 		return (NULL);
572 	last = NULL;
573 
574 	while ((dent = readdir(dirp)) != NULL) {
575 		int isdir;
576 
577 		if (dent->d_namlen < len || memcmp(cp, dent->d_name, len) != 0)
578 			continue;
579 
580 		isdir = 0;
581 		if (dent->d_type == DT_DIR) {
582 			isdir = 1;
583 		} else if (dent->d_type == DT_LNK ||
584 			    dent->d_type == DT_UNKNOWN) {
585 			struct stat	statbuf;
586 			char		statname[NFILEN + 2];
587 
588 			statbuf.st_mode = 0;
589 			if (snprintf(statname, sizeof(statname), "%s/%s",
590 			    dir, dent->d_name) > sizeof(statname) - 1) {
591 				continue;
592 			}
593 			if (stat(statname, &statbuf) < 0)
594 				continue;
595 			if (statbuf.st_mode & S_IFDIR)
596 				isdir = 1;
597 		}
598 
599 		current = malloc(sizeof(struct filelist));
600 		if (current == NULL)
601 			break;
602 
603 		if (snprintf(current->fl_name, sizeof(current->fl_name),
604 		    "%s%s%s", prefixx, dent->d_name, isdir ? "/" : "")
605 		    >= sizeof(current->fl_name)) {
606 			free(current);
607 			continue;
608 		}
609 		current->fl_l.l_next = last;
610 		current->fl_l.l_name = current->fl_name;
611 		last = (LIST *) current;
612 	}
613 	closedir(dirp);
614 
615 	return (last);
616 }
617