xref: /openbsd-src/usr.bin/mg/fileio.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: fileio.c,v 1.97 2014/03/20 07:47:29 lum Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *	POSIX fileio.c
7  */
8 #include "def.h"
9 
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13 #include <sys/resource.h>
14 #include <sys/wait.h>
15 
16 #include <fcntl.h>
17 #include <limits.h>
18 #include <dirent.h>
19 #include <pwd.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include "kbd.h"
24 #include "pathnames.h"
25 
26 static char *bkuplocation(const char *);
27 static int   bkupleavetmp(const char *);
28 
29 static char *bkupdir;
30 static int   leavetmp = 0;	/* 1 = leave any '~' files in tmp dir */
31 
32 /*
33  * Open a file for reading.
34  */
35 int
36 ffropen(FILE ** ffp, const char *fn, struct buffer *bp)
37 {
38 	if ((*ffp = fopen(fn, "r")) == NULL) {
39 		if (errno == ENOENT)
40 			return (FIOFNF);
41 		return (FIOERR);
42 	}
43 
44 	/* If 'fn' is a directory open it with dired. */
45 	if (fisdir(fn) == TRUE)
46 		return (FIODIR);
47 
48 	ffstat(*ffp, bp);
49 
50 	return (FIOSUC);
51 }
52 
53 /*
54  * Update stat/dirty info
55  */
56 void
57 ffstat(FILE *ffp, struct buffer *bp)
58 {
59 	struct stat	sb;
60 
61 	if (bp && fstat(fileno(ffp), &sb) == 0) {
62 		/* set highorder bit to make sure this isn't all zero */
63 		bp->b_fi.fi_mode = sb.st_mode | 0x8000;
64 		bp->b_fi.fi_uid = sb.st_uid;
65 		bp->b_fi.fi_gid = sb.st_gid;
66 		bp->b_fi.fi_mtime = sb.st_mtimespec;
67 		/* Clear the ignore flag */
68 		bp->b_flag &= ~(BFIGNDIRTY | BFDIRTY);
69 	}
70 }
71 
72 /*
73  * Update the status/dirty info. If there is an error,
74  * there's not a lot we can do.
75  */
76 int
77 fupdstat(struct buffer *bp)
78 {
79 	FILE *ffp;
80 
81 	if ((ffp = fopen(bp->b_fname, "r")) == NULL) {
82 		if (errno == ENOENT)
83 			return (FIOFNF);
84 		return (FIOERR);
85 	}
86 	ffstat(ffp, bp);
87 	(void)ffclose(ffp, bp);
88 	return (FIOSUC);
89 }
90 
91 /*
92  * Open a file for writing.
93  */
94 int
95 ffwopen(FILE ** ffp, const char *fn, struct buffer *bp)
96 {
97 	int	fd;
98 	mode_t	fmode = DEFFILEMODE;
99 
100 	if (bp && bp->b_fi.fi_mode)
101 		fmode = bp->b_fi.fi_mode & 07777;
102 
103 	fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, fmode);
104 	if (fd == -1) {
105 		ffp = NULL;
106 		dobeep();
107 		ewprintf("Cannot open file for writing : %s", strerror(errno));
108 		return (FIOERR);
109 	}
110 
111 	if ((*ffp = fdopen(fd, "w")) == NULL) {
112 		dobeep();
113 		ewprintf("Cannot open file for writing : %s", strerror(errno));
114 		close(fd);
115 		return (FIOERR);
116 	}
117 
118 	/*
119 	 * If we have file information, use it.  We don't bother to check for
120 	 * errors, because there's no a lot we can do about it.  Certainly
121 	 * trying to change ownership will fail if we aren't root.  That's
122 	 * probably OK.  If we don't have info, no need to get it, since any
123 	 * future writes will do the same thing.
124 	 */
125 	if (bp && bp->b_fi.fi_mode) {
126 		fchmod(fd, bp->b_fi.fi_mode & 07777);
127 		fchown(fd, bp->b_fi.fi_uid, bp->b_fi.fi_gid);
128 	}
129 	return (FIOSUC);
130 }
131 
132 /*
133  * Close a file.
134  */
135 /* ARGSUSED */
136 int
137 ffclose(FILE *ffp, struct buffer *bp)
138 {
139 	if (fclose(ffp) == 0)
140 		return (FIOSUC);
141 	return (FIOERR);
142 }
143 
144 /*
145  * Write a buffer to the already opened file. bp points to the
146  * buffer. Return the status.
147  */
148 int
149 ffputbuf(FILE *ffp, struct buffer *bp)
150 {
151 	struct line   *lp, *lpend;
152 
153 	lpend = bp->b_headp;
154 	for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) {
155 		if (fwrite(ltext(lp), 1, llength(lp), ffp) != llength(lp)) {
156 			dobeep();
157 			ewprintf("Write I/O error");
158 			return (FIOERR);
159 		}
160 		if (lforw(lp) != lpend)		/* no implied \n on last line */
161 			putc('\n', ffp);
162 	}
163 	/*
164 	 * XXX should be variable controlled (once we have variables)
165 	 */
166 	if (llength(lback(lpend)) != 0) {
167 		if (eyorn("No newline at end of file, add one") == TRUE) {
168 			lnewline_at(lback(lpend), llength(lback(lpend)));
169 			putc('\n', ffp);
170 		}
171 	}
172 	return (FIOSUC);
173 }
174 
175 /*
176  * Read a line from a file, and store the bytes
177  * in the supplied buffer. Stop on end of file or end of
178  * line.  When FIOEOF is returned, there is a valid line
179  * of data without the normally implied \n.
180  * If the line length exceeds nbuf, FIOLONG is returned.
181  */
182 int
183 ffgetline(FILE *ffp, char *buf, int nbuf, int *nbytes)
184 {
185 	int	c, i;
186 
187 	i = 0;
188 	while ((c = getc(ffp)) != EOF && c != '\n') {
189 		buf[i++] = c;
190 		if (i >= nbuf)
191 			return (FIOLONG);
192 	}
193 	if (c == EOF && ferror(ffp) != FALSE) {
194 		dobeep();
195 		ewprintf("File read error");
196 		return (FIOERR);
197 	}
198 	*nbytes = i;
199 	return (c == EOF ? FIOEOF : FIOSUC);
200 }
201 
202 /*
203  * Make a backup copy of "fname".  On Unix the backup has the same
204  * name as the original file, with a "~" on the end; this seems to
205  * be newest of the new-speak. The error handling is all in "file.c".
206  * We do a copy instead of a rename since otherwise another process
207  * with an open fd will get the backup, not the new file.  This is
208  * a problem when using mg with things like crontab and vipw.
209  */
210 int
211 fbackupfile(const char *fn)
212 {
213 	struct stat	 sb;
214 	int		 from, to, serrno;
215 	ssize_t		 nread;
216 	char		 buf[BUFSIZ];
217 	char		*nname, *tname, *bkpth;
218 
219 	if (stat(fn, &sb) == -1) {
220 		dobeep();
221 		ewprintf("Can't stat %s : %s", fn, strerror(errno));
222 		return (FALSE);
223 	}
224 
225 	if ((bkpth = bkuplocation(fn)) == NULL)
226 		return (FALSE);
227 
228 	if (asprintf(&nname, "%s~", bkpth) == -1) {
229 		dobeep();
230 		ewprintf("Can't allocate backup file name : %s", strerror(errno));
231 		free(bkpth);
232 		return (ABORT);
233 	}
234 	if (asprintf(&tname, "%s.XXXXXXXXXX", bkpth) == -1) {
235 		dobeep();
236 		ewprintf("Can't allocate temp file name : %s", strerror(errno));
237 		free(bkpth);
238 		free(nname);
239 		return (ABORT);
240 	}
241 	free(bkpth);
242 
243 	if ((from = open(fn, O_RDONLY)) == -1) {
244 		free(nname);
245 		free(tname);
246 		return (FALSE);
247 	}
248 	to = mkstemp(tname);
249 	if (to == -1) {
250 		serrno = errno;
251 		close(from);
252 		free(nname);
253 		free(tname);
254 		errno = serrno;
255 		return (FALSE);
256 	}
257 	while ((nread = read(from, buf, sizeof(buf))) > 0) {
258 		if (write(to, buf, (size_t)nread) != nread) {
259 			nread = -1;
260 			break;
261 		}
262 	}
263 	serrno = errno;
264 	(void) fchmod(to, (sb.st_mode & 0777));
265 	close(from);
266 	close(to);
267 	if (nread == -1) {
268 		if (unlink(tname) == -1)
269 			ewprintf("Can't unlink temp : %s", strerror(errno));
270 	} else {
271 		if (rename(tname, nname) == -1) {
272 			ewprintf("Can't rename temp : %s", strerror(errno));
273 			(void) unlink(tname);
274 			nread = -1;
275 		}
276 	}
277 	free(nname);
278 	free(tname);
279 	errno = serrno;
280 
281 	return (nread == -1 ? FALSE : TRUE);
282 }
283 
284 /*
285  * Convert "fn" to a canonicalized absolute filename, replacing
286  * a leading ~/ with the user's home dir, following symlinks, and
287  * remove all occurrences of /./ and /../
288  */
289 char *
290 adjustname(const char *fn, int slashslash)
291 {
292 	static char	 fnb[MAXPATHLEN];
293 	const char	*cp, *ep = NULL;
294 	char		*path;
295 
296 	if (slashslash == TRUE) {
297 		cp = fn + strlen(fn) - 1;
298 		for (; cp >= fn; cp--) {
299 			if (ep && (*cp == '/')) {
300 				fn = ep;
301 				break;
302 			}
303 			if (*cp == '/' || *cp == '~')
304 				ep = cp;
305 			else
306 				ep = NULL;
307 		}
308 	}
309 	if ((path = expandtilde(fn)) == NULL)
310 		return (NULL);
311 
312 	if (realpath(path, fnb) == NULL)
313 		(void)strlcpy(fnb, path, sizeof(fnb));
314 
315 	free(path);
316 	return (fnb);
317 }
318 
319 /*
320  * Find a startup file for the user and return its name. As a service
321  * to other pieces of code that may want to find a startup file (like
322  * the terminal driver in particular), accepts a suffix to be appended
323  * to the startup file name.
324  */
325 char *
326 startupfile(char *suffix)
327 {
328 	static char	 file[NFILEN];
329 	char		*home;
330 	int		 ret;
331 
332 	if ((home = getenv("HOME")) == NULL || *home == '\0')
333 		goto nohome;
334 
335 	if (suffix == NULL) {
336 		ret = snprintf(file, sizeof(file), _PATH_MG_STARTUP, home);
337 		if (ret < 0 || ret >= sizeof(file))
338 			return (NULL);
339 	} else {
340 		ret = snprintf(file, sizeof(file), _PATH_MG_TERM, home, suffix);
341 		if (ret < 0 || ret >= sizeof(file))
342 			return (NULL);
343 	}
344 
345 	if (access(file, R_OK) == 0)
346 		return (file);
347 nohome:
348 #ifdef STARTUPFILE
349 	if (suffix == NULL) {
350 		ret = snprintf(file, sizeof(file), "%s", STARTUPFILE);
351 		if (ret < 0 || ret >= sizeof(file))
352 			return (NULL);
353 	} else {
354 		ret = snprintf(file, sizeof(file), "%s%s", STARTUPFILE,
355 		    suffix);
356 		if (ret < 0 || ret >= sizeof(file))
357 			return (NULL);
358 	}
359 
360 	if (access(file, R_OK) == 0)
361 		return (file);
362 #endif /* STARTUPFILE */
363 	return (NULL);
364 }
365 
366 int
367 copy(char *frname, char *toname)
368 {
369 	int	ifd, ofd;
370 	char	buf[BUFSIZ];
371 	mode_t	fmode = DEFFILEMODE;	/* XXX?? */
372 	struct	stat orig;
373 	ssize_t	sr;
374 
375 	if ((ifd = open(frname, O_RDONLY)) == -1)
376 		return (FALSE);
377 	if (fstat(ifd, &orig) == -1) {
378 		dobeep();
379 		ewprintf("fstat: %s", strerror(errno));
380 		close(ifd);
381 		return (FALSE);
382 	}
383 
384 	if ((ofd = open(toname, O_WRONLY|O_CREAT|O_TRUNC, fmode)) == -1) {
385 		close(ifd);
386 		return (FALSE);
387 	}
388 	while ((sr = read(ifd, buf, sizeof(buf))) > 0) {
389 		if (write(ofd, buf, (size_t)sr) != sr) {
390 			ewprintf("write error : %s", strerror(errno));
391 			break;
392 		}
393 	}
394 	if (fchmod(ofd, orig.st_mode) == -1)
395 		ewprintf("Cannot set original mode : %s", strerror(errno));
396 
397 	if (sr == -1) {
398 		ewprintf("Read error : %s", strerror(errno));
399 		close(ifd);
400 		close(ofd);
401 		return (FALSE);
402 	}
403 	/*
404 	 * It is "normal" for this to fail since we can't guarantee that
405 	 * we will be running as root.
406 	 */
407 	if (fchown(ofd, orig.st_uid, orig.st_gid) && errno != EPERM)
408 		ewprintf("Cannot set owner : %s", strerror(errno));
409 
410 	(void) close(ifd);
411 	(void) close(ofd);
412 
413 	return (TRUE);
414 }
415 
416 /*
417  * return list of file names that match the name in buf.
418  */
419 struct list *
420 make_file_list(char *buf)
421 {
422 	char		*dir, *file, *cp;
423 	size_t		 len, preflen;
424 	int		 ret;
425 	DIR		*dirp;
426 	struct dirent	*dent;
427 	struct list	*last, *current;
428 	char		 fl_name[NFILEN + 2];
429 	char		 prefixx[NFILEN + 1];
430 
431 	/*
432 	 * We need three different strings:
433 
434 	 * dir - the name of the directory containing what the user typed.
435 	 *  Must be a real unix file name, e.g. no ~user, etc..
436 	 *  Must not end in /.
437 	 * prefix - the portion of what the user typed that is before the
438 	 *  names we are going to find in the directory.  Must have a
439 	 * trailing / if the user typed it.
440 	 * names from the directory - We open dir, and return prefix
441 	 * concatenated with names.
442 	 */
443 
444 	/* first we get a directory name we can look up */
445 	/*
446 	 * Names ending in . are potentially odd, because adjustname will
447 	 * treat foo/bar/.. as a foo/, whereas we are
448 	 * interested in names starting with ..
449 	 */
450 	len = strlen(buf);
451 	if (len && buf[len - 1] == '.') {
452 		buf[len - 1] = 'x';
453 		dir = adjustname(buf, TRUE);
454 		buf[len - 1] = '.';
455 	} else
456 		dir = adjustname(buf, TRUE);
457 	if (dir == NULL)
458 		return (NULL);
459 	/*
460 	 * If the user typed a trailing / or the empty string
461 	 * he wants us to use his file spec as a directory name.
462 	 */
463 	if (len && buf[len - 1] != '/') {
464 		file = strrchr(dir, '/');
465 		if (file) {
466 			*file = '\0';
467 			if (*dir == '\0')
468 				dir = "/";
469 		} else
470 			return (NULL);
471 	}
472 	/* Now we get the prefix of the name the user typed. */
473 	if (strlcpy(prefixx, buf, sizeof(prefixx)) >= sizeof(prefixx))
474 		return (NULL);
475 	cp = strrchr(prefixx, '/');
476 	if (cp == NULL)
477 		prefixx[0] = '\0';
478 	else
479 		cp[1] = '\0';
480 
481 	preflen = strlen(prefixx);
482 	/* cp is the tail of buf that really needs to be compared. */
483 	cp = buf + preflen;
484 	len = strlen(cp);
485 
486 	/*
487 	 * Now make sure that file names will fit in the buffers allocated.
488 	 * SV files are fairly short.  For BSD, something more general would
489 	 * be required.
490 	 */
491 	if (preflen > NFILEN - MAXNAMLEN)
492 		return (NULL);
493 
494 	/* loop over the specified directory, making up the list of files */
495 
496 	/*
497 	 * Note that it is worth our time to filter out names that don't
498 	 * match, even though our caller is going to do so again, and to
499 	 * avoid doing the stat if completion is being done, because stat'ing
500 	 * every file in the directory is relatively expensive.
501 	 */
502 
503 	dirp = opendir(dir);
504 	if (dirp == NULL)
505 		return (NULL);
506 	last = NULL;
507 
508 	while ((dent = readdir(dirp)) != NULL) {
509 		int isdir;
510 		if (strncmp(cp, dent->d_name, len) != 0)
511 			continue;
512 		isdir = 0;
513 		if (dent->d_type == DT_DIR) {
514 			isdir = 1;
515 		} else if (dent->d_type == DT_LNK ||
516 			    dent->d_type == DT_UNKNOWN) {
517 			struct stat	statbuf;
518 			char		statname[NFILEN + 2];
519 
520 			statbuf.st_mode = 0;
521 			ret = snprintf(statname, sizeof(statname), "%s/%s",
522 			    dir, dent->d_name);
523 			if (ret < 0 || ret > sizeof(statname) - 1)
524 				continue;
525 			if (stat(statname, &statbuf) < 0)
526 				continue;
527 			if (S_ISDIR(statbuf.st_mode))
528 				isdir = 1;
529 		}
530 
531 		if ((current = malloc(sizeof(struct list))) == NULL) {
532 			free_file_list(last);
533 			closedir(dirp);
534 			return (NULL);
535 		}
536 		ret = snprintf(fl_name, sizeof(fl_name),
537 		    "%s%s%s", prefixx, dent->d_name, isdir ? "/" : "");
538 		if (ret < 0 || ret >= sizeof(fl_name)) {
539 			free(current);
540 			continue;
541 		}
542 		current->l_next = last;
543 		current->l_name = strdup(fl_name);
544 		last = current;
545 	}
546 	closedir(dirp);
547 
548 	return (last);
549 }
550 
551 /*
552  * Test if a supplied filename refers to a directory
553  * Returns ABORT on error, TRUE if directory. FALSE otherwise
554  */
555 int
556 fisdir(const char *fname)
557 {
558 	struct stat	statbuf;
559 
560 	if (stat(fname, &statbuf) != 0)
561 		return (ABORT);
562 
563 	if (S_ISDIR(statbuf.st_mode))
564 		return (TRUE);
565 
566 	return (FALSE);
567 }
568 
569 /*
570  * Check the mtime of the supplied filename.
571  * Return TRUE if last mtime matches, FALSE if not,
572  * If the stat fails, return TRUE and try the save anyway
573  */
574 int
575 fchecktime(struct buffer *bp)
576 {
577 	struct stat sb;
578 
579 	if (stat(bp->b_fname, &sb) == -1)
580 		return (TRUE);
581 
582 	if (bp->b_fi.fi_mtime.tv_sec != sb.st_mtimespec.tv_sec ||
583 	    bp->b_fi.fi_mtime.tv_nsec != sb.st_mtimespec.tv_nsec)
584 		return (FALSE);
585 
586 	return (TRUE);
587 
588 }
589 
590 /*
591  * Location of backup file. This function creates the correct path.
592  */
593 static char *
594 bkuplocation(const char *fn)
595 {
596 	struct stat sb;
597 	char *ret;
598 
599 	if (bkupdir != NULL && (stat(bkupdir, &sb) == 0) &&
600 	    S_ISDIR(sb.st_mode) && !bkupleavetmp(fn)) {
601 		char fname[NFILEN];
602 		const char *c;
603 		int i = 0, len;
604 
605 		c = fn;
606 		len = strlen(bkupdir);
607 
608 		while (*c != '\0') {
609 			/* Make sure we don't go over combined:
610 		 	* strlen(bkupdir + '/' + fname + '\0')
611 		 	*/
612 			if (i >= NFILEN - len - 1)
613 				return (NULL);
614 			if (*c == '/') {
615 				fname[i] = '!';
616 			} else if (*c == '!') {
617 				if (i >= NFILEN - len - 2)
618 					return (NULL);
619 				fname[i++] = '!';
620 				fname[i] = '!';
621 			} else
622 				fname[i] = *c;
623 			i++;
624 			c++;
625 		}
626 		fname[i] = '\0';
627 		if (asprintf(&ret, "%s/%s", bkupdir, fname) == -1)
628 			return (NULL);
629 
630 	} else if ((ret = strndup(fn, NFILEN)) == NULL)
631 		return (NULL);
632 
633 	return (ret);
634 }
635 
636 int
637 backuptohomedir(int f, int n)
638 {
639 	const char	*c = _PATH_MG_DIR;
640 	char		*p;
641 
642 	if (bkupdir == NULL) {
643 		p = adjustname(c, TRUE);
644 		bkupdir = strndup(p, NFILEN);
645 		if (bkupdir == NULL)
646 			return(FALSE);
647 
648 		if (mkdir(bkupdir, 0700) == -1 && errno != EEXIST) {
649 			free(bkupdir);
650 			bkupdir = NULL;
651 		}
652 	} else {
653 		free(bkupdir);
654 		bkupdir = NULL;
655 	}
656 
657 	return (TRUE);
658 }
659 
660 /*
661  * For applications that use mg as the editor and have a desire to keep
662  * '~' files in the TMPDIR, toggle the location: /tmp | ~/.mg.d
663  */
664 int
665 toggleleavetmp(int f, int n)
666 {
667 	leavetmp = !leavetmp;
668 
669 	return (TRUE);
670 }
671 
672 /*
673  * Returns TRUE if fn is located in the temp directory and we want to save
674  * those backups there.
675  */
676 int
677 bkupleavetmp(const char *fn)
678 {
679 	char	*tmpdir, *tmp = NULL;
680 
681 	if (!leavetmp)
682 		return(FALSE);
683 
684 	if((tmpdir = getenv("TMPDIR")) != NULL && *tmpdir != '\0') {
685 		tmp = strstr(fn, tmpdir);
686 		if (tmp == fn)
687 			return (TRUE);
688 
689 		return (FALSE);
690 	}
691 
692 	tmp = strstr(fn, "/tmp");
693 	if (tmp == fn)
694 		return (TRUE);
695 
696 	return (FALSE);
697 }
698 
699 /*
700  * Expand file names beginning with '~' if appropriate:
701  *   1, if ./~fn exists, continue without expanding tilde.
702  *   2, else, if username 'fn' exists, expand tilde with home directory path.
703  *   3, otherwise, continue and create new buffer called ~fn.
704  */
705 char *
706 expandtilde(const char *fn)
707 {
708 	struct passwd	*pw;
709 	struct stat	 statbuf;
710 	const char	*cp;
711 	char		 user[LOGIN_NAME_MAX], path[NFILEN];
712 	char		*un, *ret;
713 	size_t		 ulen, plen;
714 
715 	path[0] = '\0';
716 
717 	if (fn[0] != '~' || stat(fn, &statbuf) == 0) {
718 		if ((ret = strndup(fn, NFILEN)) == NULL)
719 			return (NULL);
720 		return(ret);
721 	}
722 	cp = strchr(fn, '/');
723 	if (cp == NULL)
724 		cp = fn + strlen(fn); /* point to the NUL byte */
725 	ulen = cp - &fn[1];
726 	if (ulen >= sizeof(user)) {
727 		if ((ret = strndup(fn, NFILEN)) == NULL)
728 			return (NULL);
729 		return(ret);
730 	}
731 	if (ulen == 0) { /* ~/ or ~ */
732 		if ((un = getlogin()) != NULL)
733 			(void)strlcpy(user, un, sizeof(user));
734 		else
735 			user[0] = '\0';
736 	} else { /* ~user/ or ~user */
737 		memcpy(user, &fn[1], ulen);
738 		user[ulen] = '\0';
739 	}
740 	pw = getpwnam(user);
741 	if (pw != NULL) {
742 		plen = strlcpy(path, pw->pw_dir, sizeof(path));
743 		if (plen == 0 || path[plen - 1] != '/') {
744 			if (strlcat(path, "/", sizeof(path)) >= sizeof(path)) {
745 				dobeep();
746 				ewprintf("Path too long");
747 				return (NULL);
748 			}
749 		}
750 		fn = cp;
751 		if (*fn == '/')
752 			fn++;
753 	}
754 	if (strlcat(path, fn, sizeof(path)) >= sizeof(path)) {
755 		dobeep();
756 		ewprintf("Path too long");
757 		return (NULL);
758 	}
759 	if ((ret = strndup(path, NFILEN)) == NULL)
760 		return (NULL);
761 
762 	return (ret);
763 }
764