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