]> pd.if.org Git - pd_readline/blob - mg/fileio.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / fileio.c
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 }