1 /**
2  * @file
3  * File management functions
4  *
5  * @authors
6  * Copyright (C) 2017 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page mutt_file File management functions
25  *
26  * Commonly used file/dir management routines.
27  */
28 
29 #include "config.h"
30 #include <ctype.h>
31 #include <dirent.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <libgen.h>
35 #include <limits.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <unistd.h>
43 #include <utime.h>
44 #include "config/lib.h"
45 #include "core/lib.h"
46 #include "file.h"
47 #include "buffer.h"
48 #include "date.h"
49 #include "logging.h"
50 #include "memory.h"
51 #include "message.h"
52 #include "path.h"
53 #include "string2.h"
54 #ifdef USE_FLOCK
55 #include <sys/file.h>
56 #endif
57 
58 /* these characters must be escaped in regular expressions */
59 static const char rx_special_chars[] = "^.[$()|*+?{\\";
60 
61 const char filename_safe_chars[] =
62     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
63 
64 #define MAX_LOCK_ATTEMPTS 5
65 
66 /* This is defined in POSIX:2008 which isn't a build requirement */
67 #ifndef O_NOFOLLOW
68 #define O_NOFOLLOW 0
69 #endif
70 
71 /**
72  * compare_stat - Compare the struct stat's of two files/dirs
73  * @param st_old struct stat of the first file/dir
74  * @param st_new struct stat of the second file/dir
75  * @retval true They match
76  *
77  * This compares the device id (st_dev), inode number (st_ino) and special id
78  * (st_rdev) of the files/dirs.
79  */
compare_stat(struct stat * st_old,struct stat * st_new)80 static bool compare_stat(struct stat *st_old, struct stat *st_new)
81 {
82   return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) &&
83          (st_old->st_rdev == st_new->st_rdev);
84 }
85 
86 /**
87  * mkwrapdir - Create a temporary directory next to a file name
88  * @param path    Existing filename
89  * @param newfile New filename
90  * @param newdir  New directory name
91  * @retval  0 Success
92  * @retval -1 Error
93  */
mkwrapdir(const char * path,struct Buffer * newfile,struct Buffer * newdir)94 static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
95 {
96   const char *basename = NULL;
97   int rc = 0;
98 
99   struct Buffer parent = mutt_buffer_make(PATH_MAX);
100   mutt_buffer_strcpy(&parent, NONULL(path));
101 
102   char *p = strrchr(parent.data, '/');
103   if (p)
104   {
105     *p = '\0';
106     basename = p + 1;
107   }
108   else
109   {
110     mutt_buffer_strcpy(&parent, ".");
111     basename = path;
112   }
113 
114   mutt_buffer_printf(newdir, "%s/%s", mutt_buffer_string(&parent), ".muttXXXXXX");
115   if (!mkdtemp(newdir->data))
116   {
117     mutt_debug(LL_DEBUG1, "mkdtemp() failed\n");
118     rc = -1;
119     goto cleanup;
120   }
121 
122   mutt_buffer_printf(newfile, "%s/%s", newdir->data, NONULL(basename));
123 
124 cleanup:
125   mutt_buffer_dealloc(&parent);
126   return rc;
127 }
128 
129 /**
130  * put_file_in_place - Move a file into place
131  * @param path      Destination path
132  * @param safe_file Current filename
133  * @param safe_dir  Current directory name
134  * @retval  0 Success
135  * @retval -1 Error, see errno
136  */
put_file_in_place(const char * path,const char * safe_file,const char * safe_dir)137 static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
138 {
139   int rc;
140 
141   rc = mutt_file_safe_rename(safe_file, path);
142   unlink(safe_file);
143   rmdir(safe_dir);
144   return rc;
145 }
146 
147 /**
148  * mutt_file_fclose - Close a FILE handle (and NULL the pointer)
149  * @param[out] fp FILE handle to close
150  * @retval 0   Success
151  * @retval EOF Error, see errno
152  */
mutt_file_fclose(FILE ** fp)153 int mutt_file_fclose(FILE **fp)
154 {
155   if (!fp || !*fp)
156     return 0;
157 
158   int rc = fclose(*fp);
159   *fp = NULL;
160   return rc;
161 }
162 
163 /**
164  * mutt_file_fsync_close - Flush the data, before closing a file (and NULL the pointer)
165  * @param[out] fp FILE handle to close
166  * @retval 0   Success
167  * @retval EOF Error, see errno
168  */
mutt_file_fsync_close(FILE ** fp)169 int mutt_file_fsync_close(FILE **fp)
170 {
171   if (!fp || !*fp)
172     return 0;
173 
174   int rc = 0;
175 
176   if (fflush(*fp) || fsync(fileno(*fp)))
177   {
178     int save_errno = errno;
179     rc = -1;
180     mutt_file_fclose(fp);
181     errno = save_errno;
182   }
183   else
184     rc = mutt_file_fclose(fp);
185 
186   return rc;
187 }
188 
189 /**
190  * mutt_file_unlink - Delete a file, carefully
191  * @param s Filename
192  *
193  * This won't follow symlinks.
194  */
mutt_file_unlink(const char * s)195 void mutt_file_unlink(const char *s)
196 {
197   if (!s)
198     return;
199 
200   struct stat st = { 0 };
201   /* Defend against symlink attacks */
202 
203   const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode);
204   if (!is_regular_file)
205     return;
206 
207   const int fd = open(s, O_RDWR | O_NOFOLLOW);
208   if (fd < 0)
209     return;
210 
211   struct stat st2 = { 0 };
212   if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) ||
213       (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
214   {
215     close(fd);
216     return;
217   }
218 
219   FILE *fp = fdopen(fd, "r+");
220   if (fp)
221   {
222     unlink(s);
223     char buf[2048] = { 0 };
224     while (st.st_size > 0)
225     {
226       fwrite(buf, 1, MIN(sizeof(buf), st.st_size), fp);
227       st.st_size -= MIN(sizeof(buf), st.st_size);
228     }
229     mutt_file_fclose(&fp);
230   }
231 }
232 
233 /**
234  * mutt_file_copy_bytes - Copy some content from one file to another
235  * @param fp_in  Source file
236  * @param fp_out Destination file
237  * @param size   Maximum number of bytes to copy
238  * @retval  0 Success
239  * @retval -1 Error, see errno
240  */
mutt_file_copy_bytes(FILE * fp_in,FILE * fp_out,size_t size)241 int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
242 {
243   if (!fp_in || !fp_out)
244     return -1;
245 
246   while (size > 0)
247   {
248     char buf[2048];
249     size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
250     chunk = fread(buf, 1, chunk, fp_in);
251     if (chunk < 1)
252       break;
253     if (fwrite(buf, 1, chunk, fp_out) != chunk)
254       return -1;
255 
256     size -= chunk;
257   }
258 
259   if (fflush(fp_out) != 0)
260     return -1;
261   return 0;
262 }
263 
264 /**
265  * mutt_file_copy_stream - Copy the contents of one file into another
266  * @param fp_in  Source file
267  * @param fp_out Destination file
268  * @retval  n Success, number of bytes copied
269  * @retval -1 Error, see errno
270  */
mutt_file_copy_stream(FILE * fp_in,FILE * fp_out)271 int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
272 {
273   if (!fp_in || !fp_out)
274     return -1;
275 
276   size_t total = 0;
277   size_t l;
278   char buf[1024];
279 
280   while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0)
281   {
282     if (fwrite(buf, 1, l, fp_out) != l)
283       return -1;
284     total += l;
285   }
286 
287   if (fflush(fp_out) != 0)
288     return -1;
289   return total;
290 }
291 
292 /**
293  * mutt_file_symlink - Create a symlink
294  * @param oldpath Existing pathname
295  * @param newpath New pathname
296  * @retval  0 Success
297  * @retval -1 Error, see errno
298  */
mutt_file_symlink(const char * oldpath,const char * newpath)299 int mutt_file_symlink(const char *oldpath, const char *newpath)
300 {
301   struct stat st_old = { 0 };
302   struct stat st_new = { 0 };
303 
304   if (!oldpath || !newpath)
305     return -1;
306 
307   if ((unlink(newpath) == -1) && (errno != ENOENT))
308     return -1;
309 
310   if (oldpath[0] == '/')
311   {
312     if (symlink(oldpath, newpath) == -1)
313       return -1;
314   }
315   else
316   {
317     struct Buffer abs_oldpath = mutt_buffer_make(PATH_MAX);
318 
319     if (!mutt_path_getcwd(&abs_oldpath))
320     {
321       mutt_buffer_dealloc(&abs_oldpath);
322       return -1;
323     }
324 
325     mutt_buffer_addch(&abs_oldpath, '/');
326     mutt_buffer_addstr(&abs_oldpath, oldpath);
327     if (symlink(mutt_buffer_string(&abs_oldpath), newpath) == -1)
328     {
329       mutt_buffer_dealloc(&abs_oldpath);
330       return -1;
331     }
332 
333     mutt_buffer_dealloc(&abs_oldpath);
334   }
335 
336   if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) ||
337       !compare_stat(&st_old, &st_new))
338   {
339     unlink(newpath);
340     return -1;
341   }
342 
343   return 0;
344 }
345 
346 /**
347  * mutt_file_safe_rename - NFS-safe renaming of files
348  * @param src    Original filename
349  * @param target New filename
350  * @retval  0 Success
351  * @retval -1 Error, see errno
352  *
353  * Warning: We don't check whether src and target are equal.
354  */
mutt_file_safe_rename(const char * src,const char * target)355 int mutt_file_safe_rename(const char *src, const char *target)
356 {
357   struct stat st_src = { 0 };
358   struct stat st_target = { 0 };
359   int link_errno;
360 
361   if (!src || !target)
362     return -1;
363 
364   if (link(src, target) != 0)
365   {
366     link_errno = errno;
367 
368     /* It is historically documented that link can return -1 if NFS
369      * dies after creating the link.  In that case, we are supposed
370      * to use stat to check if the link was created.
371      *
372      * Derek Martin notes that some implementations of link() follow a
373      * source symlink.  It might be more correct to use stat() on src.
374      * I am not doing so to minimize changes in behavior: the function
375      * used lstat() further below for 20 years without issue, and I
376      * believe was never intended to be used on a src symlink.  */
377     if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) &&
378         (compare_stat(&st_src, &st_target) == 0))
379     {
380       mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
381                  src, target, strerror(errno), errno);
382       goto success;
383     }
384 
385     errno = link_errno;
386 
387     /* Coda does not allow cross-directory links, but tells
388      * us it's a cross-filesystem linking attempt.
389      *
390      * However, the Coda rename call is allegedly safe to use.
391      *
392      * With other file systems, rename should just fail when
393      * the files reside on different file systems, so it's safe
394      * to try it here. */
395     mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
396                strerror(errno), errno);
397 
398     /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
399      * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear. */
400     if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
401 #ifdef ENOTSUP
402         || errno == ENOTSUP
403 #endif
404 #ifdef EOPNOTSUPP
405         || errno == EOPNOTSUPP
406 #endif
407     )
408     {
409       mutt_debug(LL_DEBUG1, "trying rename\n");
410       if (rename(src, target) == -1)
411       {
412         mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
413                    strerror(errno), errno);
414         return -1;
415       }
416       mutt_debug(LL_DEBUG1, "rename succeeded\n");
417 
418       return 0;
419     }
420 
421     return -1;
422   }
423 
424   /* Remove the compare_stat() check, because it causes problems with maildir
425    * on filesystems that don't properly support hard links, such as sshfs.  The
426    * filesystem creates the link, but the resulting file is given a different
427    * inode number by the sshfs layer.  This results in an infinite loop
428    * creating links.  */
429 #if 0
430   /* Stat both links and check if they are equal. */
431   if (lstat(src, &st_src) == -1)
432   {
433     mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
434     return -1;
435   }
436 
437   if (lstat(target, &st_target) == -1)
438   {
439     mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
440     return -1;
441   }
442 
443   /* pretend that the link failed because the target file did already exist. */
444 
445   if (!compare_stat(&st_src, &st_target))
446   {
447     mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
448     errno = EEXIST;
449     return -1;
450   }
451 #endif
452 
453 success:
454   /* Unlink the original link.
455    * Should we really ignore the return value here? XXX */
456   if (unlink(src) == -1)
457   {
458     mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
459   }
460 
461   return 0;
462 }
463 
464 /**
465  * mutt_file_rmtree - Recursively remove a directory
466  * @param path Directory to delete
467  * @retval  0 Success
468  * @retval -1 Error, see errno
469  */
mutt_file_rmtree(const char * path)470 int mutt_file_rmtree(const char *path)
471 {
472   if (!path)
473     return -1;
474 
475   struct dirent *de = NULL;
476   struct stat st = { 0 };
477   int rc = 0;
478 
479   DIR *dirp = opendir(path);
480   if (!dirp)
481   {
482     mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
483     return -1;
484   }
485 
486   /* We avoid using the buffer pool for this function, because it
487    * invokes recursively to an unknown depth. */
488   struct Buffer cur = mutt_buffer_make(PATH_MAX);
489 
490   while ((de = readdir(dirp)))
491   {
492     if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0))
493       continue;
494 
495     mutt_buffer_printf(&cur, "%s/%s", path, de->d_name);
496     /* XXX make nonrecursive version */
497 
498     if (stat(mutt_buffer_string(&cur), &st) == -1)
499     {
500       rc = 1;
501       continue;
502     }
503 
504     if (S_ISDIR(st.st_mode))
505       rc |= mutt_file_rmtree(mutt_buffer_string(&cur));
506     else
507       rc |= unlink(mutt_buffer_string(&cur));
508   }
509   closedir(dirp);
510 
511   rc |= rmdir(path);
512 
513   mutt_buffer_dealloc(&cur);
514   return rc;
515 }
516 
517 /**
518  * mutt_file_open - Open a file
519  * @param path  Pathname to open
520  * @param flags Flags, e.g. O_EXCL
521  * @retval >0 Success, file handle
522  * @retval -1 Error
523  */
mutt_file_open(const char * path,uint32_t flags)524 int mutt_file_open(const char *path, uint32_t flags)
525 {
526   if (!path)
527     return -1;
528 
529   int fd;
530   struct Buffer safe_file = mutt_buffer_make(0);
531   struct Buffer safe_dir = mutt_buffer_make(0);
532 
533   if (flags & O_EXCL)
534   {
535     mutt_buffer_alloc(&safe_file, PATH_MAX);
536     mutt_buffer_alloc(&safe_dir, PATH_MAX);
537 
538     if (mkwrapdir(path, &safe_file, &safe_dir) == -1)
539     {
540       fd = -1;
541       goto cleanup;
542     }
543 
544     fd = open(mutt_buffer_string(&safe_file), flags, 0600);
545     if (fd < 0)
546     {
547       rmdir(mutt_buffer_string(&safe_dir));
548       goto cleanup;
549     }
550 
551     /* NFS and I believe cygwin do not handle movement of open files well */
552     close(fd);
553     if (put_file_in_place(path, mutt_buffer_string(&safe_file),
554                           mutt_buffer_string(&safe_dir)) == -1)
555     {
556       fd = -1;
557       goto cleanup;
558     }
559   }
560 
561   fd = open(path, flags & ~O_EXCL, 0600);
562   if (fd < 0)
563     goto cleanup;
564 
565   /* make sure the file is not symlink */
566   struct stat st_old = { 0 };
567   struct stat st_new = { 0 };
568   if (((lstat(path, &st_old) < 0) || (fstat(fd, &st_new) < 0)) ||
569       !compare_stat(&st_old, &st_new))
570   {
571     close(fd);
572     fd = -1;
573     goto cleanup;
574   }
575 
576 cleanup:
577   mutt_buffer_dealloc(&safe_file);
578   mutt_buffer_dealloc(&safe_dir);
579 
580   return fd;
581 }
582 
583 /**
584  * mutt_file_fopen - Call fopen() safely
585  * @param path Filename
586  * @param mode Mode e.g. "r" readonly; "w" read-write
587  * @retval ptr  FILE handle
588  * @retval NULL Error, see errno
589  *
590  * When opening files for writing, make sure the file doesn't already exist to
591  * avoid race conditions.
592  */
mutt_file_fopen(const char * path,const char * mode)593 FILE *mutt_file_fopen(const char *path, const char *mode)
594 {
595   if (!path || !mode)
596     return NULL;
597 
598   if (mode[0] == 'w')
599   {
600     uint32_t flags = O_CREAT | O_EXCL | O_NOFOLLOW;
601 
602     if (mode[1] == '+')
603       flags |= O_RDWR;
604     else
605       flags |= O_WRONLY;
606 
607     int fd = mutt_file_open(path, flags);
608     if (fd < 0)
609       return NULL;
610 
611     return fdopen(fd, mode);
612   }
613   else
614     return fopen(path, mode);
615 }
616 
617 /**
618  * mutt_file_sanitize_filename - Replace unsafe characters in a filename
619  * @param path  Filename to make safe
620  * @param slash Replace '/' characters too
621  */
mutt_file_sanitize_filename(char * path,bool slash)622 void mutt_file_sanitize_filename(char *path, bool slash)
623 {
624   if (!path)
625     return;
626 
627   for (; *path; path++)
628   {
629     if ((slash && (*path == '/')) || !strchr(filename_safe_chars, *path))
630       *path = '_';
631   }
632 }
633 
634 /**
635  * mutt_file_sanitize_regex - Escape any regex-magic characters in a string
636  * @param dest Buffer for result
637  * @param src  String to transform
638  * @retval  0 Success
639  * @retval -1 Error
640  */
mutt_file_sanitize_regex(struct Buffer * dest,const char * src)641 int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
642 {
643   if (!dest || !src)
644     return -1;
645 
646   mutt_buffer_reset(dest);
647   while (*src != '\0')
648   {
649     if (strchr(rx_special_chars, *src))
650       mutt_buffer_addch(dest, '\\');
651     mutt_buffer_addch(dest, *src++);
652   }
653 
654   return 0;
655 }
656 
657 /**
658  * mutt_file_read_line - Read a line from a file
659  * @param[out] line     Buffer allocated on the head (optional)
660  * @param[in]  size     Length of buffer
661  * @param[in]  fp       File to read
662  * @param[out] line_num Current line number (optional)
663  * @param[in]  flags    Flags, e.g. #MUTT_RL_CONT
664  * @retval ptr          The allocated string
665  *
666  * Read a line from "fp" into the dynamically allocated "line", increasing
667  * "line" if necessary. The ending "\n" or "\r\n" is removed.  If a line ends
668  * with "\", this char and the linefeed is removed, and the next line is read
669  * too.
670  */
mutt_file_read_line(char * line,size_t * size,FILE * fp,int * line_num,ReadLineFlags flags)671 char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
672 {
673   if (!size || !fp)
674     return NULL;
675 
676   size_t offset = 0;
677   char *ch = NULL;
678 
679   if (!line)
680   {
681     *size = 256;
682     line = mutt_mem_malloc(*size);
683   }
684 
685   while (true)
686   {
687     if (!fgets(line + offset, *size - offset, fp))
688     {
689       FREE(&line);
690       return NULL;
691     }
692     ch = strchr(line + offset, '\n');
693     if (ch)
694     {
695       if (line_num)
696         (*line_num)++;
697       if (flags & MUTT_RL_EOL)
698         return line;
699       *ch = '\0';
700       if ((ch > line) && (*(ch - 1) == '\r'))
701         *--ch = '\0';
702       if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
703         return line;
704       offset = ch - line - 1;
705     }
706     else
707     {
708       int c;
709       c = getc(fp); /* This is kind of a hack. We want to know if the
710                         char at the current point in the input stream is EOF.
711                         feof() will only tell us if we've already hit EOF, not
712                         if the next character is EOF. So, we need to read in
713                         the next character and manually check if it is EOF. */
714       if (c == EOF)
715       {
716         /* The last line of fp isn't \n terminated */
717         if (line_num)
718           (*line_num)++;
719         return line;
720       }
721       else
722       {
723         ungetc(c, fp); /* undo our damage */
724         /* There wasn't room for the line -- increase "line" */
725         offset = *size - 1; /* overwrite the terminating 0 */
726         *size += 256;
727         mutt_mem_realloc(&line, *size);
728       }
729     }
730   }
731 }
732 
733 /**
734  * mutt_file_iter_line - Iterate over the lines from an open file pointer
735  * @param iter  State of iteration including ptr to line
736  * @param fp    File pointer to read from
737  * @param flags Same as mutt_file_read_line()
738  * @retval true Data read
739  * @retval false On eof
740  *
741  * This is a slightly cleaner interface for mutt_file_read_line() which avoids
742  * the eternal C loop initialization ugliness.  Use like this:
743  *
744  * ```
745  * struct MuttFileIter iter = { 0 };
746  * while (mutt_file_iter_line(&iter, fp, flags))
747  * {
748  *   do_stuff(iter.line, iter.line_num);
749  * }
750  * ```
751  */
mutt_file_iter_line(struct MuttFileIter * iter,FILE * fp,ReadLineFlags flags)752 bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
753 {
754   if (!iter)
755     return false;
756 
757   char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
758   if (!p)
759     return false;
760   iter->line = p;
761   return true;
762 }
763 
764 /**
765  * mutt_file_map_lines - Process lines of text read from a file pointer
766  * @param func      Callback function to call for each line, see mutt_file_map_t
767  * @param user_data Arbitrary data passed to "func"
768  * @param fp        File pointer to read from
769  * @param flags     Same as mutt_file_read_line()
770  * @retval true  All data mapped
771  * @retval false "func" returns false
772  */
mutt_file_map_lines(mutt_file_map_t func,void * user_data,FILE * fp,ReadLineFlags flags)773 bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
774 {
775   if (!func || !fp)
776     return false;
777 
778   struct MuttFileIter iter = { 0 };
779   while (mutt_file_iter_line(&iter, fp, flags))
780   {
781     if (!(*func)(iter.line, iter.line_num, user_data))
782     {
783       FREE(&iter.line);
784       return false;
785     }
786   }
787   return true;
788 }
789 
790 /**
791  * mutt_file_quote_filename - Quote a filename to survive the shell's quoting rules
792  * @param filename String to convert
793  * @param buf      Buffer for the result
794  * @param buflen   Length of buffer
795  * @retval num Bytes written to the buffer
796  *
797  * From the Unix programming FAQ by way of Liviu.
798  */
mutt_file_quote_filename(const char * filename,char * buf,size_t buflen)799 size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
800 {
801   if (!buf)
802     return 0;
803 
804   if (!filename)
805   {
806     *buf = '\0';
807     return 0;
808   }
809 
810   size_t j = 0;
811 
812   /* leave some space for the trailing characters. */
813   buflen -= 6;
814 
815   buf[j++] = '\'';
816 
817   for (size_t i = 0; (j < buflen) && filename[i]; i++)
818   {
819     if ((filename[i] == '\'') || (filename[i] == '`'))
820     {
821       buf[j++] = '\'';
822       buf[j++] = '\\';
823       buf[j++] = filename[i];
824       buf[j++] = '\'';
825     }
826     else
827       buf[j++] = filename[i];
828   }
829 
830   buf[j++] = '\'';
831   buf[j] = '\0';
832 
833   return j;
834 }
835 
836 /**
837  * mutt_buffer_quote_filename - Quote a filename to survive the shell's quoting rules
838  * @param buf       Buffer for the result
839  * @param filename  String to convert
840  * @param add_outer If true, add 'single quotes' around the result
841  */
mutt_buffer_quote_filename(struct Buffer * buf,const char * filename,bool add_outer)842 void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
843 {
844   if (!buf || !filename)
845     return;
846 
847   mutt_buffer_reset(buf);
848   if (add_outer)
849     mutt_buffer_addch(buf, '\'');
850 
851   for (; *filename != '\0'; filename++)
852   {
853     if ((*filename == '\'') || (*filename == '`'))
854     {
855       mutt_buffer_addch(buf, '\'');
856       mutt_buffer_addch(buf, '\\');
857       mutt_buffer_addch(buf, *filename);
858       mutt_buffer_addch(buf, '\'');
859     }
860     else
861       mutt_buffer_addch(buf, *filename);
862   }
863 
864   if (add_outer)
865     mutt_buffer_addch(buf, '\'');
866 }
867 
868 /**
869  * mutt_file_mkdir - Recursively create directories
870  * @param path Directories to create
871  * @param mode Permissions for final directory
872  * @retval    0  Success
873  * @retval   -1  Error (errno set)
874  *
875  * Create a directory, creating the parents if necessary. (like mkdir -p)
876  *
877  * @note The permissions are only set on the final directory.
878  *       The permissions of any parent directories are determined by the umask.
879  *       (This is how "mkdir -p" behaves)
880  */
mutt_file_mkdir(const char * path,mode_t mode)881 int mutt_file_mkdir(const char *path, mode_t mode)
882 {
883   if (!path || (*path == '\0'))
884   {
885     errno = EINVAL;
886     return -1;
887   }
888 
889   errno = 0;
890   char tmp_path[PATH_MAX];
891   const size_t len = strlen(path);
892 
893   if (len >= sizeof(tmp_path))
894   {
895     errno = ENAMETOOLONG;
896     return -1;
897   }
898 
899   struct stat st = { 0 };
900   if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
901     return 0;
902 
903   /* Create a mutable copy */
904   mutt_str_copy(tmp_path, path, sizeof(tmp_path));
905 
906   for (char *p = tmp_path + 1; *p; p++)
907   {
908     if (*p != '/')
909       continue;
910 
911     /* Temporarily truncate the path */
912     *p = '\0';
913 
914     if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
915       return -1;
916 
917     *p = '/';
918   }
919 
920   if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
921     return -1;
922 
923   return 0;
924 }
925 
926 /**
927  * mutt_file_mkstemp_full - Create temporary file safely
928  * @param file Source file of caller
929  * @param line Source line number of caller
930  * @param func Function name of caller
931  * @retval ptr  FILE handle
932  * @retval NULL Error, see errno
933  *
934  * Create and immediately unlink a temp file using mkstemp().
935  */
mutt_file_mkstemp_full(const char * file,int line,const char * func)936 FILE *mutt_file_mkstemp_full(const char *file, int line, const char *func)
937 {
938   char name[PATH_MAX];
939 
940   const char *const c_tmpdir = cs_subset_path(NeoMutt->sub, "tmpdir");
941   int n = snprintf(name, sizeof(name), "%s/neomutt-XXXXXX", NONULL(c_tmpdir));
942   if (n < 0)
943     return NULL;
944 
945   int fd = mkstemp(name);
946   if (fd == -1)
947     return NULL;
948 
949   FILE *fp = fdopen(fd, "w+");
950 
951   if ((unlink(name) != 0) && (errno != ENOENT))
952   {
953     mutt_file_fclose(&fp);
954     return NULL;
955   }
956 
957   MuttLogger(0, file, line, func, 1, "created temp file '%s'\n", name);
958   return fp;
959 }
960 
961 /**
962  * mutt_file_decrease_mtime - Decrease a file's modification time by 1 second
963  * @param fp Filename
964  * @param st struct stat for the file (optional)
965  * @retval num Updated Unix mtime
966  * @retval -1  Error, see errno
967  *
968  * If a file's mtime is NOW, then set it to 1 second in the past.
969  */
mutt_file_decrease_mtime(const char * fp,struct stat * st)970 time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
971 {
972   if (!fp)
973     return -1;
974 
975   struct utimbuf utim;
976   struct stat st2 = { 0 };
977   time_t mtime;
978 
979   if (!st)
980   {
981     if (stat(fp, &st2) == -1)
982       return -1;
983     st = &st2;
984   }
985 
986   mtime = st->st_mtime;
987   if (mtime == mutt_date_epoch())
988   {
989     mtime -= 1;
990     utim.actime = mtime;
991     utim.modtime = mtime;
992     int rc;
993     do
994     {
995       rc = utime(fp, &utim);
996     } while ((rc == -1) && (errno == EINTR));
997 
998     if (rc == -1)
999       return -1;
1000   }
1001 
1002   return mtime;
1003 }
1004 
1005 /**
1006  * mutt_file_set_mtime - Set the modification time of one file from another
1007  * @param from Filename whose mtime should be copied
1008  * @param to   Filename to update
1009  */
mutt_file_set_mtime(const char * from,const char * to)1010 void mutt_file_set_mtime(const char *from, const char *to)
1011 {
1012   if (!from || !to)
1013     return;
1014 
1015   struct utimbuf utim;
1016   struct stat st = { 0 };
1017 
1018   if (stat(from, &st) != -1)
1019   {
1020     utim.actime = st.st_mtime;
1021     utim.modtime = st.st_mtime;
1022     utime(to, &utim);
1023   }
1024 }
1025 
1026 /**
1027  * mutt_file_touch_atime - Set the access time to current time
1028  * @param fd File descriptor of the file to alter
1029  *
1030  * This is just as read() would do on !noatime.
1031  * Silently ignored if futimens() isn't supported.
1032  */
mutt_file_touch_atime(int fd)1033 void mutt_file_touch_atime(int fd)
1034 {
1035 #ifdef HAVE_FUTIMENS
1036   struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1037   futimens(fd, times);
1038 #endif
1039 }
1040 
1041 /**
1042  * mutt_file_chmod - Set permissions of a file
1043  * @param path Filename
1044  * @param mode the permissions to set
1045  * @retval num Same as chmod(2)
1046  *
1047  * This is essentially chmod(path, mode), see chmod(2).
1048  */
mutt_file_chmod(const char * path,mode_t mode)1049 int mutt_file_chmod(const char *path, mode_t mode)
1050 {
1051   if (!path)
1052     return -1;
1053 
1054   return chmod(path, mode);
1055 }
1056 
1057 /**
1058  * mutt_file_chmod_add - Add permissions to a file
1059  * @param path Filename
1060  * @param mode the permissions to add
1061  * @retval num Same as chmod(2)
1062  *
1063  * Adds the given permissions to the file. Permissions not mentioned in mode
1064  * will stay as they are. This function resembles the `chmod ugoa+rwxXst`
1065  * command family. Example:
1066  *
1067  *     mutt_file_chmod_add(path, S_IWUSR | S_IWGRP | S_IWOTH);
1068  *
1069  * will add write permissions to path but does not alter read and other
1070  * permissions.
1071  *
1072  * @sa mutt_file_chmod_add_stat()
1073  */
mutt_file_chmod_add(const char * path,mode_t mode)1074 int mutt_file_chmod_add(const char *path, mode_t mode)
1075 {
1076   return mutt_file_chmod_add_stat(path, mode, NULL);
1077 }
1078 
1079 /**
1080  * mutt_file_chmod_add_stat - Add permissions to a file
1081  * @param path Filename
1082  * @param mode the permissions to add
1083  * @param st   struct stat for the file (optional)
1084  * @retval num Same as chmod(2)
1085  *
1086  * Same as mutt_file_chmod_add() but saves a system call to stat() if a
1087  * non-NULL stat structure is given. Useful if the stat structure of the file
1088  * was retrieved before by the calling code. Example:
1089  *
1090  *     struct stat st;
1091  *     stat(path, &st);
1092  *     // ... do something else with st ...
1093  *     mutt_file_chmod_add_stat(path, S_IWUSR | S_IWGRP | S_IWOTH, st);
1094  *
1095  * @sa mutt_file_chmod_add()
1096  */
mutt_file_chmod_add_stat(const char * path,mode_t mode,struct stat * st)1097 int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1098 {
1099   if (!path)
1100     return -1;
1101 
1102   struct stat st2 = { 0 };
1103 
1104   if (!st)
1105   {
1106     if (stat(path, &st2) == -1)
1107       return -1;
1108     st = &st2;
1109   }
1110   return chmod(path, st->st_mode | mode);
1111 }
1112 
1113 /**
1114  * mutt_file_chmod_rm - Remove permissions from a file
1115  * @param path Filename
1116  * @param mode the permissions to remove
1117  * @retval num Same as chmod(2)
1118  *
1119  * Removes the given permissions from the file. Permissions not mentioned in
1120  * mode will stay as they are. This function resembles the `chmod ugoa-rwxXst`
1121  * command family. Example:
1122  *
1123  *     mutt_file_chmod_rm(path, S_IWUSR | S_IWGRP | S_IWOTH);
1124  *
1125  * will remove write permissions from path but does not alter read and other
1126  * permissions.
1127  *
1128  * @sa mutt_file_chmod_rm_stat()
1129  */
mutt_file_chmod_rm(const char * path,mode_t mode)1130 int mutt_file_chmod_rm(const char *path, mode_t mode)
1131 {
1132   return mutt_file_chmod_rm_stat(path, mode, NULL);
1133 }
1134 
1135 /**
1136  * mutt_file_chmod_rm_stat - Remove permissions from a file
1137  * @param path Filename
1138  * @param mode the permissions to remove
1139  * @param st   struct stat for the file (optional)
1140  * @retval num Same as chmod(2)
1141  *
1142  * Same as mutt_file_chmod_rm() but saves a system call to stat() if a non-NULL
1143  * stat structure is given. Useful if the stat structure of the file was
1144  * retrieved before by the calling code. Example:
1145  *
1146  *     struct stat st;
1147  *     stat(path, &st);
1148  *     // ... do something else with st ...
1149  *     mutt_file_chmod_rm_stat(path, S_IWUSR | S_IWGRP | S_IWOTH, st);
1150  *
1151  * @sa mutt_file_chmod_rm()
1152  */
mutt_file_chmod_rm_stat(const char * path,mode_t mode,struct stat * st)1153 int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1154 {
1155   if (!path)
1156     return -1;
1157 
1158   struct stat st2 = { 0 };
1159 
1160   if (!st)
1161   {
1162     if (stat(path, &st2) == -1)
1163       return -1;
1164     st = &st2;
1165   }
1166   return chmod(path, st->st_mode & ~mode);
1167 }
1168 
1169 #if defined(USE_FCNTL)
1170 /**
1171  * mutt_file_lock - (Try to) Lock a file using fcntl()
1172  * @param fd      File descriptor to file
1173  * @param excl    If true, try to lock exclusively
1174  * @param timeout If true, Retry #MAX_LOCK_ATTEMPTS times
1175  * @retval  0 Success
1176  * @retval -1 Failure
1177  *
1178  * Use fcntl() to lock a file.
1179  *
1180  * Use mutt_file_unlock() to unlock the file.
1181  */
mutt_file_lock(int fd,bool excl,bool timeout)1182 int mutt_file_lock(int fd, bool excl, bool timeout)
1183 {
1184   struct stat st = { 0 }, prev_sb = { 0 };
1185   int count = 0;
1186   int attempt = 0;
1187 
1188   struct flock lck;
1189   memset(&lck, 0, sizeof(struct flock));
1190   lck.l_type = excl ? F_WRLCK : F_RDLCK;
1191   lck.l_whence = SEEK_SET;
1192 
1193   while (fcntl(fd, F_SETLK, &lck) == -1)
1194   {
1195     mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1196     if ((errno != EAGAIN) && (errno != EACCES))
1197     {
1198       mutt_perror("fcntl");
1199       return -1;
1200     }
1201 
1202     if (fstat(fd, &st) != 0)
1203       st.st_size = 0;
1204 
1205     if (count == 0)
1206       prev_sb = st;
1207 
1208     /* only unlock file if it is unchanged */
1209     if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1210     {
1211       if (timeout)
1212         mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1213       return -1;
1214     }
1215 
1216     prev_sb = st;
1217 
1218     mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1219     sleep(1);
1220   }
1221 
1222   return 0;
1223 }
1224 
1225 /**
1226  * mutt_file_unlock - Unlock a file previously locked by mutt_file_lock()
1227  * @param fd File descriptor to file
1228  * @retval 0 Always
1229  */
mutt_file_unlock(int fd)1230 int mutt_file_unlock(int fd)
1231 {
1232   struct flock unlockit;
1233 
1234   memset(&unlockit, 0, sizeof(struct flock));
1235   unlockit.l_type = F_UNLCK;
1236   unlockit.l_whence = SEEK_SET;
1237   (void) fcntl(fd, F_SETLK, &unlockit);
1238 
1239   return 0;
1240 }
1241 #elif defined(USE_FLOCK)
1242 /**
1243  * mutt_file_lock - (Try to) Lock a file using flock()
1244  * @param fd      File descriptor to file
1245  * @param excl    If true, try to lock exclusively
1246  * @param timeout If true, Retry #MAX_LOCK_ATTEMPTS times
1247  * @retval  0 Success
1248  * @retval -1 Failure
1249  *
1250  * Use flock() to lock a file.
1251  *
1252  * Use mutt_file_unlock() to unlock the file.
1253  */
mutt_file_lock(int fd,bool excl,bool timeout)1254 int mutt_file_lock(int fd, bool excl, bool timeout)
1255 {
1256   struct stat st = { 0 }, prev_sb = { 0 };
1257   int rc = 0;
1258   int count = 0;
1259   int attempt = 0;
1260 
1261   while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1262   {
1263     if (errno != EWOULDBLOCK)
1264     {
1265       mutt_perror("flock");
1266       rc = -1;
1267       break;
1268     }
1269 
1270     if (fstat(fd, &st) != 0)
1271       st.st_size = 0;
1272 
1273     if (count == 0)
1274       prev_sb = st;
1275 
1276     /* only unlock file if it is unchanged */
1277     if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1278     {
1279       if (timeout)
1280         mutt_error(_("Timeout exceeded while attempting flock lock"));
1281       rc = -1;
1282       break;
1283     }
1284 
1285     prev_sb = st;
1286 
1287     mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1288     sleep(1);
1289   }
1290 
1291   /* release any other locks obtained in this routine */
1292   if (rc != 0)
1293   {
1294     flock(fd, LOCK_UN);
1295   }
1296 
1297   return rc;
1298 }
1299 
1300 /**
1301  * mutt_file_unlock - Unlock a file previously locked by mutt_file_lock()
1302  * @param fd File descriptor to file
1303  * @retval 0 Always
1304  */
mutt_file_unlock(int fd)1305 int mutt_file_unlock(int fd)
1306 {
1307   flock(fd, LOCK_UN);
1308   return 0;
1309 }
1310 #else
1311 #error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1312 #endif
1313 
1314 /**
1315  * mutt_file_unlink_empty - Delete a file if it's empty
1316  * @param path File to delete
1317  */
mutt_file_unlink_empty(const char * path)1318 void mutt_file_unlink_empty(const char *path)
1319 {
1320   if (!path)
1321     return;
1322 
1323   struct stat st = { 0 };
1324 
1325   int fd = open(path, O_RDWR);
1326   if (fd == -1)
1327     return;
1328 
1329   if (mutt_file_lock(fd, true, true) == -1)
1330   {
1331     close(fd);
1332     return;
1333   }
1334 
1335   if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1336     unlink(path);
1337 
1338   mutt_file_unlock(fd);
1339   close(fd);
1340 }
1341 
1342 /**
1343  * mutt_file_rename - Rename a file
1344  * @param oldfile Old filename
1345  * @param newfile New filename
1346  * @retval 0 Success
1347  * @retval 1 Old file doesn't exist
1348  * @retval 2 New file already exists
1349  * @retval 3 Some other error
1350  *
1351  * @note on access(2) use No dangling symlink problems here due to
1352  * mutt_file_fopen().
1353  */
mutt_file_rename(const char * oldfile,const char * newfile)1354 int mutt_file_rename(const char *oldfile, const char *newfile)
1355 {
1356   if (!oldfile || !newfile)
1357     return -1;
1358   if (access(oldfile, F_OK) != 0)
1359     return 1;
1360   if (access(newfile, F_OK) == 0)
1361     return 2;
1362 
1363   FILE *fp_old = fopen(oldfile, "r");
1364   if (!fp_old)
1365     return 3;
1366   FILE *fp_new = mutt_file_fopen(newfile, "w");
1367   if (!fp_new)
1368   {
1369     mutt_file_fclose(&fp_old);
1370     return 3;
1371   }
1372   mutt_file_copy_stream(fp_old, fp_new);
1373   mutt_file_fclose(&fp_new);
1374   mutt_file_fclose(&fp_old);
1375   mutt_file_unlink(oldfile);
1376   return 0;
1377 }
1378 
1379 /**
1380  * mutt_file_read_keyword - Read a keyword from a file
1381  * @param file   File to read
1382  * @param buf    Buffer to store the keyword
1383  * @param buflen Length of the buf
1384  * @retval ptr Start of the keyword
1385  *
1386  * Read one line from the start of a file.
1387  * Skip any leading whitespace and extract the first token.
1388  */
mutt_file_read_keyword(const char * file,char * buf,size_t buflen)1389 char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1390 {
1391   FILE *fp = mutt_file_fopen(file, "r");
1392   if (!fp)
1393     return NULL;
1394 
1395   buf = fgets(buf, buflen, fp);
1396   mutt_file_fclose(&fp);
1397 
1398   if (!buf)
1399     return NULL;
1400 
1401   SKIPWS(buf);
1402   char *start = buf;
1403 
1404   while ((*buf != '\0') && !isspace(*buf))
1405     buf++;
1406 
1407   *buf = '\0';
1408 
1409   return start;
1410 }
1411 
1412 /**
1413  * mutt_file_check_empty - Is the mailbox empty
1414  * @param path Path to mailbox
1415  * @retval  1 Mailbox is empty
1416  * @retval  0 Mailbox is not empty
1417  * @retval -1 Error
1418  */
mutt_file_check_empty(const char * path)1419 int mutt_file_check_empty(const char *path)
1420 {
1421   if (!path)
1422     return -1;
1423 
1424   struct stat st = { 0 };
1425   if (stat(path, &st) == -1)
1426     return -1;
1427 
1428   return st.st_size == 0;
1429 }
1430 
1431 /**
1432  * mutt_buffer_file_expand_fmt_quote - Replace `%s` in a string with a filename
1433  * @param dest    Buffer for the result
1434  * @param fmt     printf-like format string
1435  * @param src     Filename to substitute
1436  *
1437  * This function also quotes the file to prevent shell problems.
1438  */
mutt_buffer_file_expand_fmt_quote(struct Buffer * dest,const char * fmt,const char * src)1439 void mutt_buffer_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1440 {
1441   struct Buffer tmp = mutt_buffer_make(PATH_MAX);
1442 
1443   mutt_buffer_quote_filename(&tmp, src, true);
1444   mutt_file_expand_fmt(dest, fmt, mutt_buffer_string(&tmp));
1445   mutt_buffer_dealloc(&tmp);
1446 }
1447 
1448 /**
1449  * mutt_file_expand_fmt - Replace `%s` in a string with a filename
1450  * @param dest    Buffer for the result
1451  * @param fmt     printf-like format string
1452  * @param src     Filename to substitute
1453  */
mutt_file_expand_fmt(struct Buffer * dest,const char * fmt,const char * src)1454 void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1455 {
1456   if (!dest || !fmt || !src)
1457     return;
1458 
1459   const char *p = NULL;
1460   bool found = false;
1461 
1462   mutt_buffer_reset(dest);
1463 
1464   for (p = fmt; *p; p++)
1465   {
1466     if (*p == '%')
1467     {
1468       switch (p[1])
1469       {
1470         case '%':
1471           mutt_buffer_addch(dest, *p++);
1472           break;
1473         case 's':
1474           found = true;
1475           mutt_buffer_addstr(dest, src);
1476           p++;
1477           break;
1478         default:
1479           mutt_buffer_addch(dest, *p);
1480           break;
1481       }
1482     }
1483     else
1484     {
1485       mutt_buffer_addch(dest, *p);
1486     }
1487   }
1488 
1489   if (!found)
1490   {
1491     mutt_buffer_addch(dest, ' ');
1492     mutt_buffer_addstr(dest, src);
1493   }
1494 }
1495 
1496 /**
1497  * mutt_file_get_size - Get the size of a file
1498  * @param path File to measure
1499  * @retval num Size in bytes
1500  * @retval 0   Error
1501  */
mutt_file_get_size(const char * path)1502 long mutt_file_get_size(const char *path)
1503 {
1504   if (!path)
1505     return 0;
1506 
1507   struct stat st = { 0 };
1508   if (stat(path, &st) != 0)
1509     return 0;
1510 
1511   return st.st_size;
1512 }
1513 
1514 /**
1515  * mutt_file_get_size_fp - Get the size of a file
1516  * @param fp FILE* to measure
1517  * @retval num Size in bytes
1518  * @retval 0   Error
1519  */
mutt_file_get_size_fp(FILE * fp)1520 long mutt_file_get_size_fp(FILE *fp)
1521 {
1522   if (!fp)
1523     return 0;
1524 
1525   struct stat st = { 0 };
1526   if (fstat(fileno(fp), &st) != 0)
1527     return 0;
1528 
1529   return st.st_size;
1530 }
1531 
1532 /**
1533  * mutt_file_timespec_compare - Compare to time values
1534  * @param a First time value
1535  * @param b Second time value
1536  * @retval -1 a precedes b
1537  * @retval  0 a and b are identical
1538  * @retval  1 b precedes a
1539  */
mutt_file_timespec_compare(struct timespec * a,struct timespec * b)1540 int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
1541 {
1542   if (!a || !b)
1543     return 0;
1544   if (a->tv_sec < b->tv_sec)
1545     return -1;
1546   if (a->tv_sec > b->tv_sec)
1547     return 1;
1548 
1549   if (a->tv_nsec < b->tv_nsec)
1550     return -1;
1551   if (a->tv_nsec > b->tv_nsec)
1552     return 1;
1553   return 0;
1554 }
1555 
1556 /**
1557  * mutt_file_get_stat_timespec - Read the stat() time into a time value
1558  * @param dest Time value to populate
1559  * @param st   stat info
1560  * @param type Type of stat info to read, e.g. #MUTT_STAT_ATIME
1561  */
mutt_file_get_stat_timespec(struct timespec * dest,struct stat * st,enum MuttStatType type)1562 void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1563 {
1564   if (!dest || !st)
1565     return;
1566 
1567   dest->tv_sec = 0;
1568   dest->tv_nsec = 0;
1569 
1570   switch (type)
1571   {
1572     case MUTT_STAT_ATIME:
1573       dest->tv_sec = st->st_atime;
1574 #ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1575       dest->tv_nsec = st->st_atim.tv_nsec;
1576 #endif
1577       break;
1578     case MUTT_STAT_MTIME:
1579       dest->tv_sec = st->st_mtime;
1580 #ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1581       dest->tv_nsec = st->st_mtim.tv_nsec;
1582 #endif
1583       break;
1584     case MUTT_STAT_CTIME:
1585       dest->tv_sec = st->st_ctime;
1586 #ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1587       dest->tv_nsec = st->st_ctim.tv_nsec;
1588 #endif
1589       break;
1590   }
1591 }
1592 
1593 /**
1594  * mutt_file_stat_timespec_compare - Compare stat info with a time value
1595  * @param st   stat info
1596  * @param type Type of stat info, e.g. #MUTT_STAT_ATIME
1597  * @param b    Time value
1598  * @retval -1 a precedes b
1599  * @retval  0 a and b are identical
1600  * @retval  1 b precedes a
1601  */
mutt_file_stat_timespec_compare(struct stat * st,enum MuttStatType type,struct timespec * b)1602 int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1603                                     struct timespec *b)
1604 {
1605   if (!st || !b)
1606     return 0;
1607 
1608   struct timespec a = { 0 };
1609 
1610   mutt_file_get_stat_timespec(&a, st, type);
1611   return mutt_file_timespec_compare(&a, b);
1612 }
1613 
1614 /**
1615  * mutt_file_stat_compare - Compare two stat infos
1616  * @param st1      First stat info
1617  * @param st1_type Type of first stat info, e.g. #MUTT_STAT_ATIME
1618  * @param st2      Second stat info
1619  * @param st2_type Type of second stat info, e.g. #MUTT_STAT_ATIME
1620  * @retval -1 a precedes b
1621  * @retval  0 a and b are identical
1622  * @retval  1 b precedes a
1623  */
mutt_file_stat_compare(struct stat * st1,enum MuttStatType st1_type,struct stat * st2,enum MuttStatType st2_type)1624 int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1625                            struct stat *st2, enum MuttStatType st2_type)
1626 {
1627   if (!st1 || !st2)
1628     return 0;
1629 
1630   struct timespec a = { 0 };
1631   struct timespec b = { 0 };
1632 
1633   mutt_file_get_stat_timespec(&a, st1, st1_type);
1634   mutt_file_get_stat_timespec(&b, st2, st2_type);
1635   return mutt_file_timespec_compare(&a, &b);
1636 }
1637 
1638 /**
1639  * mutt_file_resolve_symlink - Resolve a symlink in place
1640  * @param buf Input/output path
1641  */
mutt_file_resolve_symlink(struct Buffer * buf)1642 void mutt_file_resolve_symlink(struct Buffer *buf)
1643 {
1644   struct stat st = { 0 };
1645   int rc = lstat(mutt_buffer_string(buf), &st);
1646   if ((rc != -1) && S_ISLNK(st.st_mode))
1647   {
1648     char path[PATH_MAX];
1649     if (realpath(mutt_buffer_string(buf), path))
1650     {
1651       mutt_buffer_strcpy(buf, path);
1652     }
1653   }
1654 }
1655