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