NeoMutt  2022-04-29-81-g9c5a59
Teaching an old dog new tricks
DOXYGEN
file.c
Go to the documentation of this file.
1 
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 <unistd.h>
42 #include <utime.h>
43 #include "config/lib.h"
44 #include "core/lib.h"
45 #include "file.h"
46 #include "buffer.h"
47 #include "date.h"
48 #include "logging.h"
49 #include "memory.h"
50 #include "message.h"
51 #include "path.h"
52 #include "pool.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[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
62 
63 #define MAX_LOCK_ATTEMPTS 5
64 
65 /* This is defined in POSIX:2008 which isn't a build requirement */
66 #ifndef O_NOFOLLOW
67 #define O_NOFOLLOW 0
68 #endif
69 
79 static bool compare_stat(struct stat *st_old, struct stat *st_new)
80 {
81  return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) &&
82  (st_old->st_rdev == st_new->st_rdev);
83 }
84 
93 static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
94 {
95  const char *basename = NULL;
96  int rc = 0;
97 
98  struct Buffer parent = mutt_buffer_make(PATH_MAX);
99  mutt_buffer_strcpy(&parent, NONULL(path));
100 
101  char *p = strrchr(parent.data, '/');
102  if (p)
103  {
104  *p = '\0';
105  basename = p + 1;
106  }
107  else
108  {
109  mutt_buffer_strcpy(&parent, ".");
110  basename = path;
111  }
112 
113  mutt_buffer_printf(newdir, "%s/%s", mutt_buffer_string(&parent), ".muttXXXXXX");
114  if (!mkdtemp(newdir->data))
115  {
116  mutt_debug(LL_DEBUG1, "mkdtemp() failed\n");
117  rc = -1;
118  goto cleanup;
119  }
120 
121  mutt_buffer_printf(newfile, "%s/%s", newdir->data, NONULL(basename));
122 
123 cleanup:
124  mutt_buffer_dealloc(&parent);
125  return rc;
126 }
127 
136 static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
137 {
138  int rc;
139 
140  rc = mutt_file_safe_rename(safe_file, path);
141  unlink(safe_file);
142  rmdir(safe_dir);
143  return rc;
144 }
145 
152 int mutt_file_fclose(FILE **fp)
153 {
154  if (!fp || !*fp)
155  return 0;
156 
157  int rc = fclose(*fp);
158  *fp = NULL;
159  return rc;
160 }
161 
168 int mutt_file_fsync_close(FILE **fp)
169 {
170  if (!fp || !*fp)
171  return 0;
172 
173  int rc = 0;
174 
175  if (fflush(*fp) || fsync(fileno(*fp)))
176  {
177  int save_errno = errno;
178  rc = -1;
179  mutt_file_fclose(fp);
180  errno = save_errno;
181  }
182  else
183  rc = mutt_file_fclose(fp);
184 
185  return rc;
186 }
187 
194 void mutt_file_unlink(const char *s)
195 {
196  if (!s)
197  return;
198 
199  struct stat st = { 0 };
200  /* Defend against symlink attacks */
201 
202  const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode);
203  if (!is_regular_file)
204  return;
205 
206  const int fd = open(s, O_RDWR | O_NOFOLLOW);
207  if (fd < 0)
208  return;
209 
210  struct stat st2 = { 0 };
211  if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) ||
212  (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
213  {
214  close(fd);
215  return;
216  }
217 
218  unlink(s);
219  close(fd);
220 }
221 
230 int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
231 {
232  if (!fp_in || !fp_out)
233  return -1;
234 
235  while (size > 0)
236  {
237  char buf[2048];
238  size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
239  chunk = fread(buf, 1, chunk, fp_in);
240  if (chunk < 1)
241  break;
242  if (fwrite(buf, 1, chunk, fp_out) != chunk)
243  return -1;
244 
245  size -= chunk;
246  }
247 
248  if (fflush(fp_out) != 0)
249  return -1;
250  return 0;
251 }
252 
260 int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
261 {
262  if (!fp_in || !fp_out)
263  return -1;
264 
265  size_t total = 0;
266  size_t l;
267  char buf[1024];
268 
269  while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0)
270  {
271  if (fwrite(buf, 1, l, fp_out) != l)
272  return -1;
273  total += l;
274  }
275 
276  if (fflush(fp_out) != 0)
277  return -1;
278  return total;
279 }
280 
288 int mutt_file_symlink(const char *oldpath, const char *newpath)
289 {
290  struct stat st_old = { 0 };
291  struct stat st_new = { 0 };
292 
293  if (!oldpath || !newpath)
294  return -1;
295 
296  if ((unlink(newpath) == -1) && (errno != ENOENT))
297  return -1;
298 
299  if (oldpath[0] == '/')
300  {
301  if (symlink(oldpath, newpath) == -1)
302  return -1;
303  }
304  else
305  {
306  struct Buffer abs_oldpath = mutt_buffer_make(PATH_MAX);
307 
308  if (!mutt_path_getcwd(&abs_oldpath))
309  {
310  mutt_buffer_dealloc(&abs_oldpath);
311  return -1;
312  }
313 
314  mutt_buffer_addch(&abs_oldpath, '/');
315  mutt_buffer_addstr(&abs_oldpath, oldpath);
316  if (symlink(mutt_buffer_string(&abs_oldpath), newpath) == -1)
317  {
318  mutt_buffer_dealloc(&abs_oldpath);
319  return -1;
320  }
321 
322  mutt_buffer_dealloc(&abs_oldpath);
323  }
324 
325  if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) ||
326  !compare_stat(&st_old, &st_new))
327  {
328  unlink(newpath);
329  return -1;
330  }
331 
332  return 0;
333 }
334 
344 int mutt_file_safe_rename(const char *src, const char *target)
345 {
346  struct stat st_src = { 0 };
347  struct stat st_target = { 0 };
348  int link_errno;
349 
350  if (!src || !target)
351  return -1;
352 
353  if (link(src, target) != 0)
354  {
355  link_errno = errno;
356 
357  /* It is historically documented that link can return -1 if NFS
358  * dies after creating the link. In that case, we are supposed
359  * to use stat to check if the link was created.
360  *
361  * Derek Martin notes that some implementations of link() follow a
362  * source symlink. It might be more correct to use stat() on src.
363  * I am not doing so to minimize changes in behavior: the function
364  * used lstat() further below for 20 years without issue, and I
365  * believe was never intended to be used on a src symlink. */
366  if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) &&
367  (compare_stat(&st_src, &st_target) == 0))
368  {
369  mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
370  src, target, strerror(errno), errno);
371  goto success;
372  }
373 
374  errno = link_errno;
375 
376  /* Coda does not allow cross-directory links, but tells
377  * us it's a cross-filesystem linking attempt.
378  *
379  * However, the Coda rename call is allegedly safe to use.
380  *
381  * With other file systems, rename should just fail when
382  * the files reside on different file systems, so it's safe
383  * to try it here. */
384  mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
385  strerror(errno), errno);
386 
387  /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
388  * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */
389  if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
390 #ifdef ENOTSUP
391  || errno == ENOTSUP
392 #endif
393 #ifdef EOPNOTSUPP
394  || errno == EOPNOTSUPP
395 #endif
396  )
397  {
398  mutt_debug(LL_DEBUG1, "trying rename\n");
399  if (rename(src, target) == -1)
400  {
401  mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
402  strerror(errno), errno);
403  return -1;
404  }
405  mutt_debug(LL_DEBUG1, "rename succeeded\n");
406 
407  return 0;
408  }
409 
410  return -1;
411  }
412 
413  /* Remove the compare_stat() check, because it causes problems with maildir
414  * on filesystems that don't properly support hard links, such as sshfs. The
415  * filesystem creates the link, but the resulting file is given a different
416  * inode number by the sshfs layer. This results in an infinite loop
417  * creating links. */
418 #if 0
419  /* Stat both links and check if they are equal. */
420  if (lstat(src, &st_src) == -1)
421  {
422  mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
423  return -1;
424  }
425 
426  if (lstat(target, &st_target) == -1)
427  {
428  mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
429  return -1;
430  }
431 
432  /* pretend that the link failed because the target file did already exist. */
433 
434  if (!compare_stat(&st_src, &st_target))
435  {
436  mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
437  errno = EEXIST;
438  return -1;
439  }
440 #endif
441 
442 success:
443  /* Unlink the original link.
444  * Should we really ignore the return value here? XXX */
445  if (unlink(src) == -1)
446  {
447  mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
448  }
449 
450  return 0;
451 }
452 
459 int mutt_file_rmtree(const char *path)
460 {
461  if (!path)
462  return -1;
463 
464  struct dirent *de = NULL;
465  struct stat st = { 0 };
466  int rc = 0;
467 
468  DIR *dirp = opendir(path);
469  if (!dirp)
470  {
471  mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
472  return -1;
473  }
474 
475  /* We avoid using the buffer pool for this function, because it
476  * invokes recursively to an unknown depth. */
477  struct Buffer cur = mutt_buffer_make(PATH_MAX);
478 
479  while ((de = readdir(dirp)))
480  {
481  if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0))
482  continue;
483 
484  mutt_buffer_printf(&cur, "%s/%s", path, de->d_name);
485  /* XXX make nonrecursive version */
486 
487  if (stat(mutt_buffer_string(&cur), &st) == -1)
488  {
489  rc = 1;
490  continue;
491  }
492 
493  if (S_ISDIR(st.st_mode))
495  else
496  rc |= unlink(mutt_buffer_string(&cur));
497  }
498  closedir(dirp);
499 
500  rc |= rmdir(path);
501 
502  mutt_buffer_dealloc(&cur);
503  return rc;
504 }
505 
519 const char *mutt_file_rotate(const char *path, int count)
520 {
521  if (!path)
522  return NULL;
523 
524  struct Buffer *old_file = mutt_buffer_pool_get();
525  struct Buffer *new_file = mutt_buffer_pool_get();
526 
527  /* rotate the old debug logs */
528  for (count -= 2; count >= 0; count--)
529  {
530  mutt_buffer_printf(old_file, "%s%d", path, count);
531  mutt_buffer_printf(new_file, "%s%d", path, count + 1);
532  (void) rename(mutt_buffer_string(old_file), mutt_buffer_string(new_file));
533  }
534 
535  path = mutt_buffer_strdup(old_file);
536  mutt_buffer_pool_release(&old_file);
537  mutt_buffer_pool_release(&new_file);
538 
539  return path;
540 }
541 
549 int mutt_file_open(const char *path, uint32_t flags)
550 {
551  if (!path)
552  return -1;
553 
554  int fd;
555  struct Buffer safe_file = mutt_buffer_make(0);
556  struct Buffer safe_dir = mutt_buffer_make(0);
557 
558  if (flags & O_EXCL)
559  {
560  mutt_buffer_alloc(&safe_file, PATH_MAX);
561  mutt_buffer_alloc(&safe_dir, PATH_MAX);
562 
563  if (mkwrapdir(path, &safe_file, &safe_dir) == -1)
564  {
565  fd = -1;
566  goto cleanup;
567  }
568 
569  fd = open(mutt_buffer_string(&safe_file), flags, 0600);
570  if (fd < 0)
571  {
572  rmdir(mutt_buffer_string(&safe_dir));
573  goto cleanup;
574  }
575 
576  /* NFS and I believe cygwin do not handle movement of open files well */
577  close(fd);
578  if (put_file_in_place(path, mutt_buffer_string(&safe_file),
579  mutt_buffer_string(&safe_dir)) == -1)
580  {
581  fd = -1;
582  goto cleanup;
583  }
584  }
585 
586  fd = open(path, flags & ~O_EXCL, 0600);
587  if (fd < 0)
588  goto cleanup;
589 
590  /* make sure the file is not symlink */
591  struct stat st_old = { 0 };
592  struct stat st_new = { 0 };
593  if (((lstat(path, &st_old) < 0) || (fstat(fd, &st_new) < 0)) ||
594  !compare_stat(&st_old, &st_new))
595  {
596  close(fd);
597  fd = -1;
598  goto cleanup;
599  }
600 
601 cleanup:
602  mutt_buffer_dealloc(&safe_file);
603  mutt_buffer_dealloc(&safe_dir);
604 
605  return fd;
606 }
607 
618 FILE *mutt_file_fopen(const char *path, const char *mode)
619 {
620  if (!path || !mode)
621  return NULL;
622 
623  if (mode[0] == 'w')
624  {
625  uint32_t flags = O_CREAT | O_EXCL | O_NOFOLLOW;
626 
627  if (mode[1] == '+')
628  flags |= O_RDWR;
629  else
630  flags |= O_WRONLY;
631 
632  int fd = mutt_file_open(path, flags);
633  if (fd < 0)
634  return NULL;
635 
636  return fdopen(fd, mode);
637  }
638  else
639  return fopen(path, mode);
640 }
641 
647 void mutt_file_sanitize_filename(char *path, bool slash)
648 {
649  if (!path)
650  return;
651 
652  for (; *path; path++)
653  {
654  if ((slash && (*path == '/')) || !strchr(filename_safe_chars, *path))
655  *path = '_';
656  }
657 }
658 
666 int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
667 {
668  if (!dest || !src)
669  return -1;
670 
671  mutt_buffer_reset(dest);
672  while (*src != '\0')
673  {
674  if (strchr(rx_special_chars, *src))
675  mutt_buffer_addch(dest, '\\');
676  mutt_buffer_addch(dest, *src++);
677  }
678 
679  return 0;
680 }
681 
690 bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
691 {
692  if (!fp)
693  {
694  return false;
695  }
696 
697  if (fseeko(fp, offset, whence) != 0)
698  {
699  mutt_perror(_("Failed to seek file: %s"), strerror(errno));
700  return false;
701  }
702 
703  return true;
704 }
705 
720 char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
721 {
722  if (!size || !fp)
723  return NULL;
724 
725  size_t offset = 0;
726  char *ch = NULL;
727 
728  if (!line)
729  {
730  *size = 256;
731  line = mutt_mem_malloc(*size);
732  }
733 
734  while (true)
735  {
736  if (!fgets(line + offset, *size - offset, fp))
737  {
738  FREE(&line);
739  return NULL;
740  }
741  ch = strchr(line + offset, '\n');
742  if (ch)
743  {
744  if (line_num)
745  (*line_num)++;
746  if (flags & MUTT_RL_EOL)
747  return line;
748  *ch = '\0';
749  if ((ch > line) && (*(ch - 1) == '\r'))
750  *--ch = '\0';
751  if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
752  return line;
753  offset = ch - line - 1;
754  }
755  else
756  {
757  int c;
758  c = getc(fp); /* This is kind of a hack. We want to know if the
759  char at the current point in the input stream is EOF.
760  feof() will only tell us if we've already hit EOF, not
761  if the next character is EOF. So, we need to read in
762  the next character and manually check if it is EOF. */
763  if (c == EOF)
764  {
765  /* The last line of fp isn't \n terminated */
766  if (line_num)
767  (*line_num)++;
768  return line;
769  }
770  else
771  {
772  ungetc(c, fp); /* undo our damage */
773  /* There wasn't room for the line -- increase "line" */
774  offset = *size - 1; /* overwrite the terminating 0 */
775  *size += 256;
776  mutt_mem_realloc(&line, *size);
777  }
778  }
779  }
780 }
781 
801 bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
802 {
803  if (!iter)
804  return false;
805 
806  char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
807  if (!p)
808  return false;
809  iter->line = p;
810  return true;
811 }
812 
822 bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
823 {
824  if (!func || !fp)
825  return false;
826 
827  struct MuttFileIter iter = { 0 };
828  while (mutt_file_iter_line(&iter, fp, flags))
829  {
830  if (!(*func)(iter.line, iter.line_num, user_data))
831  {
832  FREE(&iter.line);
833  return false;
834  }
835  }
836  return true;
837 }
838 
848 size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
849 {
850  if (!buf)
851  return 0;
852 
853  if (!filename)
854  {
855  *buf = '\0';
856  return 0;
857  }
858 
859  size_t j = 0;
860 
861  /* leave some space for the trailing characters. */
862  buflen -= 6;
863 
864  buf[j++] = '\'';
865 
866  for (size_t i = 0; (j < buflen) && filename[i]; i++)
867  {
868  if ((filename[i] == '\'') || (filename[i] == '`'))
869  {
870  buf[j++] = '\'';
871  buf[j++] = '\\';
872  buf[j++] = filename[i];
873  buf[j++] = '\'';
874  }
875  else
876  buf[j++] = filename[i];
877  }
878 
879  buf[j++] = '\'';
880  buf[j] = '\0';
881 
882  return j;
883 }
884 
891 void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
892 {
893  if (!buf || !filename)
894  return;
895 
896  mutt_buffer_reset(buf);
897  if (add_outer)
898  mutt_buffer_addch(buf, '\'');
899 
900  for (; *filename != '\0'; filename++)
901  {
902  if ((*filename == '\'') || (*filename == '`'))
903  {
904  mutt_buffer_addch(buf, '\'');
905  mutt_buffer_addch(buf, '\\');
906  mutt_buffer_addch(buf, *filename);
907  mutt_buffer_addch(buf, '\'');
908  }
909  else
910  mutt_buffer_addch(buf, *filename);
911  }
912 
913  if (add_outer)
914  mutt_buffer_addch(buf, '\'');
915 }
916 
930 int mutt_file_mkdir(const char *path, mode_t mode)
931 {
932  if (!path || (*path == '\0'))
933  {
934  errno = EINVAL;
935  return -1;
936  }
937 
938  errno = 0;
939  char tmp_path[PATH_MAX];
940  const size_t len = strlen(path);
941 
942  if (len >= sizeof(tmp_path))
943  {
944  errno = ENAMETOOLONG;
945  return -1;
946  }
947 
948  struct stat st = { 0 };
949  if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
950  return 0;
951 
952  /* Create a mutable copy */
953  mutt_str_copy(tmp_path, path, sizeof(tmp_path));
954 
955  for (char *p = tmp_path + 1; *p; p++)
956  {
957  if (*p != '/')
958  continue;
959 
960  /* Temporarily truncate the path */
961  *p = '\0';
962 
963  if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
964  return -1;
965 
966  *p = '/';
967  }
968 
969  if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
970  return -1;
971 
972  return 0;
973 }
974 
985 FILE *mutt_file_mkstemp_full(const char *file, int line, const char *func)
986 {
987  char name[PATH_MAX];
988 
989  const char *const c_tmpdir = cs_subset_path(NeoMutt->sub, "tmpdir");
990  int n = snprintf(name, sizeof(name), "%s/neomutt-XXXXXX", NONULL(c_tmpdir));
991  if (n < 0)
992  return NULL;
993 
994  int fd = mkstemp(name);
995  if (fd == -1)
996  return NULL;
997 
998  FILE *fp = fdopen(fd, "w+");
999 
1000  if ((unlink(name) != 0) && (errno != ENOENT))
1001  {
1002  mutt_file_fclose(&fp);
1003  return NULL;
1004  }
1005 
1006  MuttLogger(0, file, line, func, 1, "created temp file '%s'\n", name);
1007  return fp;
1008 }
1009 
1019 time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
1020 {
1021  if (!fp)
1022  return -1;
1023 
1024  struct utimbuf utim;
1025  struct stat st2 = { 0 };
1026  time_t mtime;
1027 
1028  if (!st)
1029  {
1030  if (stat(fp, &st2) == -1)
1031  return -1;
1032  st = &st2;
1033  }
1034 
1035  mtime = st->st_mtime;
1036  if (mtime == mutt_date_epoch())
1037  {
1038  mtime -= 1;
1039  utim.actime = mtime;
1040  utim.modtime = mtime;
1041  int rc;
1042  do
1043  {
1044  rc = utime(fp, &utim);
1045  } while ((rc == -1) && (errno == EINTR));
1046 
1047  if (rc == -1)
1048  return -1;
1049  }
1050 
1051  return mtime;
1052 }
1053 
1059 void mutt_file_set_mtime(const char *from, const char *to)
1060 {
1061  if (!from || !to)
1062  return;
1063 
1064  struct utimbuf utim;
1065  struct stat st = { 0 };
1066 
1067  if (stat(from, &st) != -1)
1068  {
1069  utim.actime = st.st_mtime;
1070  utim.modtime = st.st_mtime;
1071  utime(to, &utim);
1072  }
1073 }
1074 
1083 {
1084 #ifdef HAVE_FUTIMENS
1085  struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1086  futimens(fd, times);
1087 #endif
1088 }
1089 
1098 int mutt_file_chmod(const char *path, mode_t mode)
1099 {
1100  if (!path)
1101  return -1;
1102 
1103  return chmod(path, mode);
1104 }
1105 
1123 int mutt_file_chmod_add(const char *path, mode_t mode)
1124 {
1125  return mutt_file_chmod_add_stat(path, mode, NULL);
1126 }
1127 
1146 int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1147 {
1148  if (!path)
1149  return -1;
1150 
1151  struct stat st2 = { 0 };
1152 
1153  if (!st)
1154  {
1155  if (stat(path, &st2) == -1)
1156  return -1;
1157  st = &st2;
1158  }
1159  return chmod(path, st->st_mode | mode);
1160 }
1161 
1179 int mutt_file_chmod_rm(const char *path, mode_t mode)
1180 {
1181  return mutt_file_chmod_rm_stat(path, mode, NULL);
1182 }
1183 
1202 int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1203 {
1204  if (!path)
1205  return -1;
1206 
1207  struct stat st2 = { 0 };
1208 
1209  if (!st)
1210  {
1211  if (stat(path, &st2) == -1)
1212  return -1;
1213  st = &st2;
1214  }
1215  return chmod(path, st->st_mode & ~mode);
1216 }
1217 
1218 #if defined(USE_FCNTL)
1231 int mutt_file_lock(int fd, bool excl, bool timeout)
1232 {
1233  struct stat st = { 0 }, prev_sb = { 0 };
1234  int count = 0;
1235  int attempt = 0;
1236 
1237  struct flock lck;
1238  memset(&lck, 0, sizeof(struct flock));
1239  lck.l_type = excl ? F_WRLCK : F_RDLCK;
1240  lck.l_whence = SEEK_SET;
1241 
1242  while (fcntl(fd, F_SETLK, &lck) == -1)
1243  {
1244  mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1245  if ((errno != EAGAIN) && (errno != EACCES))
1246  {
1247  mutt_perror("fcntl");
1248  return -1;
1249  }
1250 
1251  if (fstat(fd, &st) != 0)
1252  st.st_size = 0;
1253 
1254  if (count == 0)
1255  prev_sb = st;
1256 
1257  /* only unlock file if it is unchanged */
1258  if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1259  {
1260  if (timeout)
1261  mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1262  return -1;
1263  }
1264 
1265  prev_sb = st;
1266 
1267  mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1268  sleep(1);
1269  }
1270 
1271  return 0;
1272 }
1273 
1279 int mutt_file_unlock(int fd)
1280 {
1281  struct flock unlockit;
1282 
1283  memset(&unlockit, 0, sizeof(struct flock));
1284  unlockit.l_type = F_UNLCK;
1285  unlockit.l_whence = SEEK_SET;
1286  (void) fcntl(fd, F_SETLK, &unlockit);
1287 
1288  return 0;
1289 }
1290 #elif defined(USE_FLOCK)
1303 int mutt_file_lock(int fd, bool excl, bool timeout)
1304 {
1305  struct stat st = { 0 }, prev_sb = { 0 };
1306  int rc = 0;
1307  int count = 0;
1308  int attempt = 0;
1309 
1310  while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1311  {
1312  if (errno != EWOULDBLOCK)
1313  {
1314  mutt_perror("flock");
1315  rc = -1;
1316  break;
1317  }
1318 
1319  if (fstat(fd, &st) != 0)
1320  st.st_size = 0;
1321 
1322  if (count == 0)
1323  prev_sb = st;
1324 
1325  /* only unlock file if it is unchanged */
1326  if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1327  {
1328  if (timeout)
1329  mutt_error(_("Timeout exceeded while attempting flock lock"));
1330  rc = -1;
1331  break;
1332  }
1333 
1334  prev_sb = st;
1335 
1336  mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1337  sleep(1);
1338  }
1339 
1340  /* release any other locks obtained in this routine */
1341  if (rc != 0)
1342  {
1343  flock(fd, LOCK_UN);
1344  }
1345 
1346  return rc;
1347 }
1348 
1354 int mutt_file_unlock(int fd)
1355 {
1356  flock(fd, LOCK_UN);
1357  return 0;
1358 }
1359 #else
1360 #error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1361 #endif
1362 
1367 void mutt_file_unlink_empty(const char *path)
1368 {
1369  if (!path)
1370  return;
1371 
1372  struct stat st = { 0 };
1373 
1374  int fd = open(path, O_RDWR);
1375  if (fd == -1)
1376  return;
1377 
1378  if (mutt_file_lock(fd, true, true) == -1)
1379  {
1380  close(fd);
1381  return;
1382  }
1383 
1384  if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1385  unlink(path);
1386 
1387  mutt_file_unlock(fd);
1388  close(fd);
1389 }
1390 
1403 int mutt_file_rename(const char *oldfile, const char *newfile)
1404 {
1405  if (!oldfile || !newfile)
1406  return -1;
1407  if (access(oldfile, F_OK) != 0)
1408  return 1;
1409  if (access(newfile, F_OK) == 0)
1410  return 2;
1411 
1412  FILE *fp_old = fopen(oldfile, "r");
1413  if (!fp_old)
1414  return 3;
1415  FILE *fp_new = mutt_file_fopen(newfile, "w");
1416  if (!fp_new)
1417  {
1418  mutt_file_fclose(&fp_old);
1419  return 3;
1420  }
1421  mutt_file_copy_stream(fp_old, fp_new);
1422  mutt_file_fclose(&fp_new);
1423  mutt_file_fclose(&fp_old);
1424  mutt_file_unlink(oldfile);
1425  return 0;
1426 }
1427 
1438 char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1439 {
1440  FILE *fp = mutt_file_fopen(file, "r");
1441  if (!fp)
1442  return NULL;
1443 
1444  buf = fgets(buf, buflen, fp);
1445  mutt_file_fclose(&fp);
1446 
1447  if (!buf)
1448  return NULL;
1449 
1450  SKIPWS(buf);
1451  char *start = buf;
1452 
1453  while ((*buf != '\0') && !isspace(*buf))
1454  buf++;
1455 
1456  *buf = '\0';
1457 
1458  return start;
1459 }
1460 
1468 int mutt_file_check_empty(const char *path)
1469 {
1470  if (!path)
1471  return -1;
1472 
1473  struct stat st = { 0 };
1474  if (stat(path, &st) == -1)
1475  return -1;
1476 
1477  return st.st_size == 0;
1478 }
1479 
1488 void mutt_buffer_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1489 {
1490  struct Buffer tmp = mutt_buffer_make(PATH_MAX);
1491 
1492  mutt_buffer_quote_filename(&tmp, src, true);
1493  mutt_file_expand_fmt(dest, fmt, mutt_buffer_string(&tmp));
1494  mutt_buffer_dealloc(&tmp);
1495 }
1496 
1503 void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1504 {
1505  if (!dest || !fmt || !src)
1506  return;
1507 
1508  const char *p = NULL;
1509  bool found = false;
1510 
1511  mutt_buffer_reset(dest);
1512 
1513  for (p = fmt; *p; p++)
1514  {
1515  if (*p == '%')
1516  {
1517  switch (p[1])
1518  {
1519  case '%':
1520  mutt_buffer_addch(dest, *p++);
1521  break;
1522  case 's':
1523  found = true;
1524  mutt_buffer_addstr(dest, src);
1525  p++;
1526  break;
1527  default:
1528  mutt_buffer_addch(dest, *p);
1529  break;
1530  }
1531  }
1532  else
1533  {
1534  mutt_buffer_addch(dest, *p);
1535  }
1536  }
1537 
1538  if (!found)
1539  {
1540  mutt_buffer_addch(dest, ' ');
1541  mutt_buffer_addstr(dest, src);
1542  }
1543 }
1544 
1551 long mutt_file_get_size(const char *path)
1552 {
1553  if (!path)
1554  return 0;
1555 
1556  struct stat st = { 0 };
1557  if (stat(path, &st) != 0)
1558  return 0;
1559 
1560  return st.st_size;
1561 }
1562 
1570 {
1571  if (!fp)
1572  return 0;
1573 
1574  struct stat st = { 0 };
1575  if (fstat(fileno(fp), &st) != 0)
1576  return 0;
1577 
1578  return st.st_size;
1579 }
1580 
1590 {
1591  if (!a || !b)
1592  return 0;
1593  if (a->tv_sec < b->tv_sec)
1594  return -1;
1595  if (a->tv_sec > b->tv_sec)
1596  return 1;
1597 
1598  if (a->tv_nsec < b->tv_nsec)
1599  return -1;
1600  if (a->tv_nsec > b->tv_nsec)
1601  return 1;
1602  return 0;
1603 }
1604 
1611 void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1612 {
1613  if (!dest || !st)
1614  return;
1615 
1616  dest->tv_sec = 0;
1617  dest->tv_nsec = 0;
1618 
1619  switch (type)
1620  {
1621  case MUTT_STAT_ATIME:
1622  dest->tv_sec = st->st_atime;
1623 #ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1624  dest->tv_nsec = st->st_atim.tv_nsec;
1625 #endif
1626  break;
1627  case MUTT_STAT_MTIME:
1628  dest->tv_sec = st->st_mtime;
1629 #ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1630  dest->tv_nsec = st->st_mtim.tv_nsec;
1631 #endif
1632  break;
1633  case MUTT_STAT_CTIME:
1634  dest->tv_sec = st->st_ctime;
1635 #ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1636  dest->tv_nsec = st->st_ctim.tv_nsec;
1637 #endif
1638  break;
1639  }
1640 }
1641 
1651 int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1652  struct timespec *b)
1653 {
1654  if (!st || !b)
1655  return 0;
1656 
1657  struct timespec a = { 0 };
1658 
1659  mutt_file_get_stat_timespec(&a, st, type);
1660  return mutt_file_timespec_compare(&a, b);
1661 }
1662 
1673 int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1674  struct stat *st2, enum MuttStatType st2_type)
1675 {
1676  if (!st1 || !st2)
1677  return 0;
1678 
1679  struct timespec a = { 0 };
1680  struct timespec b = { 0 };
1681 
1682  mutt_file_get_stat_timespec(&a, st1, st1_type);
1683  mutt_file_get_stat_timespec(&b, st2, st2_type);
1684  return mutt_file_timespec_compare(&a, &b);
1685 }
1686 
1692 {
1693  struct stat st = { 0 };
1694  int rc = lstat(mutt_buffer_string(buf), &st);
1695  if ((rc != -1) && S_ISLNK(st.st_mode))
1696  {
1697  char path[PATH_MAX];
1698  if (realpath(mutt_buffer_string(buf), path))
1699  {
1700  mutt_buffer_strcpy(buf, path);
1701  }
1702  }
1703 }
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:61
void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:265
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:432
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
General purpose object for storing and parsing strings.
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
Convenience wrapper for the config headers.
Convenience wrapper for the core headers.
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:427
Time and date handling routines.
void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
Read the stat() time into a time value.
Definition: file.c:1611
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:260
int mutt_file_open(const char *path, uint32_t flags)
Open a file.
Definition: file.c:549
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:344
void mutt_file_unlink_empty(const char *path)
Delete a file if it's empty.
Definition: file.c:1367
int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type, struct stat *st2, enum MuttStatType st2_type)
Compare two stat infos.
Definition: file.c:1673
int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
Copy some content from one file to another.
Definition: file.c:230
static const char rx_special_chars[]
Definition: file.c:59
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
#define MAX_LOCK_ATTEMPTS
Definition: file.c:63
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition: file.c:1082
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:666
const char * mutt_file_rotate(const char *path, int count)
Rotate a set of numbered files.
Definition: file.c:519
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition: file.c:720
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1468
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:930
const char filename_safe_chars[]
Definition: file.c:61
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition: file.c:1231
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition: file.c:1569
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:647
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1589
void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
Quote a filename to survive the shell's quoting rules.
Definition: file.c:891
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1279
int mutt_file_chmod_rm(const char *path, mode_t mode)
Remove permissions from a file.
Definition: file.c:1179
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition: file.c:1019
bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
Process lines of text read from a file pointer.
Definition: file.c:822
size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
Quote a filename to survive the shell's quoting rules.
Definition: file.c:848
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:690
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition: file.c:1551
int mutt_file_chmod(const char *path, mode_t mode)
Set permissions of a file.
Definition: file.c:1098
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1403
#define O_NOFOLLOW
Definition: file.c:67
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
Iterate over the lines from an open file pointer.
Definition: file.c:801
static bool compare_stat(struct stat *st_old, struct stat *st_new)
Compare the struct stat's of two files/dirs.
Definition: file.c:79
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1438
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:288
FILE * mutt_file_mkstemp_full(const char *file, int line, const char *func)
Create temporary file safely.
Definition: file.c:985
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition: file.c:1146
static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
Move a file into place.
Definition: file.c:136
void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
Replace s in a string with a filename.
Definition: file.c:1503
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1691
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1059
static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
Create a temporary directory next to a file name.
Definition: file.c:93
int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type, struct timespec *b)
Compare stat info with a time value.
Definition: file.c:1651
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1123
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:194
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition: file.c:168
void mutt_buffer_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
Replace s in a string with a filename.
Definition: file.c:1488
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition: file.c:459
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1202
File management functions.
#define MUTT_RL_CONT
-continuation
Definition: file.h:39
bool(* mutt_file_map_t)(char *line, int line_num, void *user_data)
Definition: file.h:88
#define MUTT_RL_EOL
don't strip \n / \r\n
Definition: file.h:40
MuttStatType
Flags for mutt_file_get_stat_timespec.
Definition: file.h:61
@ MUTT_STAT_CTIME
File/dir's ctime - creation time.
Definition: file.h:64
@ MUTT_STAT_ATIME
File/dir's atime - last accessed time.
Definition: file.h:62
@ MUTT_STAT_MTIME
File/dir's mtime - last modified time.
Definition: file.h:63
uint8_t ReadLineFlags
Flags for mutt_file_read_line(), e.g. MUTT_RL_CONT.
Definition: file.h:37
log_dispatcher_t MuttLogger
The log dispatcher -.
Definition: logging.c:52
#define mutt_error(...)
Definition: logging.h:87
#define mutt_message(...)
Definition: logging.h:86
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
#define mutt_perror(...)
Definition: logging.h:88
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
Logging Dispatcher.
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Memory management wrappers.
#define FREE(x)
Definition: memory.h:40
Message logging.
#define _(a)
Definition: message.h:28
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:561
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:629
#define PATH_MAX
Definition: mutt.h:40
Path manipulation functions.
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
A global pool of Buffers.
String manipulation functions.
#define NONULL(x)
Definition: string2.h:37
#define SKIPWS(ch)
Definition: string2.h:46
String manipulation buffer.
Definition: buffer.h:34
char * data
Pointer to data.
Definition: buffer.h:35
State record for mutt_file_iter_line()
Definition: file.h:71
char * line
the line data
Definition: file.h:72
int line_num
line number
Definition: file.h:74
size_t size
allocated size of line data
Definition: file.h:73
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Time value with nanosecond precision.
Definition: file.h:49
long tv_nsec
Number of nanosecond, on top.
Definition: file.h:51
time_t tv_sec
Number of seconds since the epoch.
Definition: file.h:50