NeoMutt  2020-09-25
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 <time.h>
42 #include <unistd.h>
43 #include <utime.h>
44 #include "file.h"
45 #include "buffer.h"
46 #include "date.h"
47 #include "logging.h"
48 #include "memory.h"
49 #include "message.h"
50 #include "path.h"
51 #include "string2.h"
52 #ifdef USE_FLOCK
53 #include <sys/file.h>
54 #endif
55 
56 char *C_Tmpdir;
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 
80 static bool compare_stat(struct stat *osb, struct stat *nsb)
81 {
82  return (osb->st_dev == nsb->st_dev) && (osb->st_ino == nsb->st_ino) &&
83  (osb->st_rdev == nsb->st_rdev);
84 }
85 
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_b2s(&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 
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 
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 
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 
195 void mutt_file_unlink(const char *s)
196 {
197  if (!s)
198  return;
199 
200  struct stat sb;
201  /* Defend against symlink attacks */
202 
203  const bool is_regular_file = (lstat(s, &sb) == 0) && S_ISREG(sb.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 sb2;
212  if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode) ||
213  (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.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 (sb.st_size > 0)
225  {
226  fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), fp);
227  sb.st_size -= MIN(sizeof(buf), sb.st_size);
228  }
229  mutt_file_fclose(&fp);
230  }
231 }
232 
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 
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 
299 int mutt_file_symlink(const char *oldpath, const char *newpath)
300 {
301  struct stat osb, nsb;
302 
303  if (!oldpath || !newpath)
304  return -1;
305 
306  if ((unlink(newpath) == -1) && (errno != ENOENT))
307  return -1;
308 
309  if (oldpath[0] == '/')
310  {
311  if (symlink(oldpath, newpath) == -1)
312  return -1;
313  }
314  else
315  {
316  struct Buffer abs_oldpath = mutt_buffer_make(PATH_MAX);
317 
318  if (!mutt_path_getcwd(&abs_oldpath))
319  {
320  mutt_buffer_dealloc(&abs_oldpath);
321  return -1;
322  }
323 
324  mutt_buffer_addch(&abs_oldpath, '/');
325  mutt_buffer_addstr(&abs_oldpath, oldpath);
326  if (symlink(mutt_b2s(&abs_oldpath), newpath) == -1)
327  {
328  mutt_buffer_dealloc(&abs_oldpath);
329  return -1;
330  }
331 
332  mutt_buffer_dealloc(&abs_oldpath);
333  }
334 
335  if ((stat(oldpath, &osb) == -1) || (stat(newpath, &nsb) == -1) ||
336  !compare_stat(&osb, &nsb))
337  {
338  unlink(newpath);
339  return -1;
340  }
341 
342  return 0;
343 }
344 
354 int mutt_file_safe_rename(const char *src, const char *target)
355 {
356  struct stat ssb, tsb;
357  int link_errno;
358 
359  if (!src || !target)
360  return -1;
361 
362  if (link(src, target) != 0)
363  {
364  link_errno = errno;
365 
366  /* It is historically documented that link can return -1 if NFS
367  * dies after creating the link. In that case, we are supposed
368  * to use stat to check if the link was created.
369  *
370  * Derek Martin notes that some implementations of link() follow a
371  * source symlink. It might be more correct to use stat() on src.
372  * I am not doing so to minimize changes in behavior: the function
373  * used lstat() further below for 20 years without issue, and I
374  * believe was never intended to be used on a src symlink. */
375  if ((lstat(src, &ssb) == 0) && (lstat(target, &tsb) == 0) &&
376  (compare_stat(&ssb, &tsb) == 0))
377  {
378  mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
379  src, target, strerror(errno), errno);
380  goto success;
381  }
382 
383  errno = link_errno;
384 
385  /* Coda does not allow cross-directory links, but tells
386  * us it's a cross-filesystem linking attempt.
387  *
388  * However, the Coda rename call is allegedly safe to use.
389  *
390  * With other file systems, rename should just fail when
391  * the files reside on different file systems, so it's safe
392  * to try it here. */
393  mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
394  strerror(errno), errno);
395 
396  /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
397  * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */
398  if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
399 #ifdef ENOTSUP
400  || errno == ENOTSUP
401 #endif
402 #ifdef EOPNOTSUPP
403  || errno == EOPNOTSUPP
404 #endif
405  )
406  {
407  mutt_debug(LL_DEBUG1, "trying rename\n");
408  if (rename(src, target) == -1)
409  {
410  mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
411  strerror(errno), errno);
412  return -1;
413  }
414  mutt_debug(LL_DEBUG1, "rename succeeded\n");
415 
416  return 0;
417  }
418 
419  return -1;
420  }
421 
422  /* Remove the compare_stat() check, because it causes problems with maildir
423  * on filesystems that don't properly support hard links, such as sshfs. The
424  * filesystem creates the link, but the resulting file is given a different
425  * inode number by the sshfs layer. This results in an infinite loop
426  * creating links. */
427 #if 0
428  /* Stat both links and check if they are equal. */
429  if (lstat(src, &ssb) == -1)
430  {
431  mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
432  return -1;
433  }
434 
435  if (lstat(target, &tsb) == -1)
436  {
437  mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
438  return -1;
439  }
440 
441  /* pretend that the link failed because the target file did already exist. */
442 
443  if (!compare_stat(&ssb, &tsb))
444  {
445  mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
446  errno = EEXIST;
447  return -1;
448  }
449 #endif
450 
451 success:
452  /* Unlink the original link.
453  * Should we really ignore the return value here? XXX */
454  if (unlink(src) == -1)
455  {
456  mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
457  }
458 
459  return 0;
460 }
461 
468 int mutt_file_rmtree(const char *path)
469 {
470  if (!path)
471  return -1;
472 
473  struct dirent *de = NULL;
474  struct stat statbuf;
475  int rc = 0;
476 
477  DIR *dirp = opendir(path);
478  if (!dirp)
479  {
480  mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
481  return -1;
482  }
483 
484  /* We avoid using the buffer pool for this function, because it
485  * invokes recursively to an unknown depth. */
486  struct Buffer cur = mutt_buffer_make(PATH_MAX);
487 
488  while ((de = readdir(dirp)))
489  {
490  if ((strcmp(".", de->d_name) == 0) || (strcmp("..", de->d_name) == 0))
491  continue;
492 
493  mutt_buffer_printf(&cur, "%s/%s", path, de->d_name);
494  /* XXX make nonrecursive version */
495 
496  if (stat(mutt_b2s(&cur), &statbuf) == -1)
497  {
498  rc = 1;
499  continue;
500  }
501 
502  if (S_ISDIR(statbuf.st_mode))
503  rc |= mutt_file_rmtree(mutt_b2s(&cur));
504  else
505  rc |= unlink(mutt_b2s(&cur));
506  }
507  closedir(dirp);
508 
509  rc |= rmdir(path);
510 
511  mutt_buffer_dealloc(&cur);
512  return rc;
513 }
514 
522 int mutt_file_open(const char *path, int flags)
523 {
524  if (!path)
525  return -1;
526 
527  struct stat osb, nsb;
528  int fd;
529  struct Buffer safe_file = mutt_buffer_make(0);
530  struct Buffer safe_dir = mutt_buffer_make(0);
531 
532  if (flags & O_EXCL)
533  {
534  mutt_buffer_alloc(&safe_file, PATH_MAX);
535  mutt_buffer_alloc(&safe_dir, PATH_MAX);
536 
537  if (mkwrapdir(path, &safe_file, &safe_dir) == -1)
538  {
539  fd = -1;
540  goto cleanup;
541  }
542 
543  fd = open(mutt_b2s(&safe_file), flags, 0600);
544  if (fd < 0)
545  {
546  rmdir(mutt_b2s(&safe_dir));
547  goto cleanup;
548  }
549 
550  /* NFS and I believe cygwin do not handle movement of open files well */
551  close(fd);
552  if (put_file_in_place(path, mutt_b2s(&safe_file), mutt_b2s(&safe_dir)) == -1)
553  {
554  fd = -1;
555  goto cleanup;
556  }
557  }
558 
559  fd = open(path, flags & ~O_EXCL, 0600);
560  if (fd < 0)
561  goto cleanup;
562 
563  /* make sure the file is not symlink */
564  if (((lstat(path, &osb) < 0) || (fstat(fd, &nsb) < 0)) || !compare_stat(&osb, &nsb))
565  {
566  close(fd);
567  fd = -1;
568  goto cleanup;
569  }
570 
571 cleanup:
572  mutt_buffer_dealloc(&safe_file);
573  mutt_buffer_dealloc(&safe_dir);
574 
575  return fd;
576 }
577 
588 FILE *mutt_file_fopen(const char *path, const char *mode)
589 {
590  if (!path || !mode)
591  return NULL;
592 
593  if (mode[0] == 'w')
594  {
595  int fd;
596  int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
597 
598  if (mode[1] == '+')
599  flags |= O_RDWR;
600  else
601  flags |= O_WRONLY;
602 
603  fd = mutt_file_open(path, flags);
604  if (fd < 0)
605  return NULL;
606 
607  return fdopen(fd, mode);
608  }
609  else
610  return fopen(path, mode);
611 }
612 
618 void mutt_file_sanitize_filename(char *path, bool slash)
619 {
620  if (!path)
621  return;
622 
623  for (; *path; path++)
624  {
625  if ((slash && (*path == '/')) || !strchr(filename_safe_chars, *path))
626  *path = '_';
627  }
628 }
629 
637 int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
638 {
639  if (!dest || !src)
640  return -1;
641 
642  mutt_buffer_reset(dest);
643  while (*src != '\0')
644  {
645  if (strchr(rx_special_chars, *src))
646  mutt_buffer_addch(dest, '\\');
647  mutt_buffer_addch(dest, *src++);
648  }
649 
650  return 0;
651 }
652 
667 char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
668 {
669  if (!size || !fp)
670  return NULL;
671 
672  size_t offset = 0;
673  char *ch = NULL;
674 
675  if (!line)
676  {
677  *size = 256;
678  line = mutt_mem_malloc(*size);
679  }
680 
681  while (true)
682  {
683  if (!fgets(line + offset, *size - offset, fp))
684  {
685  FREE(&line);
686  return NULL;
687  }
688  ch = strchr(line + offset, '\n');
689  if (ch)
690  {
691  if (line_num)
692  (*line_num)++;
693  if (flags & MUTT_EOL)
694  return line;
695  *ch = '\0';
696  if ((ch > line) && (*(ch - 1) == '\r'))
697  *--ch = '\0';
698  if (!(flags & MUTT_CONT) || (ch == line) || (*(ch - 1) != '\\'))
699  return line;
700  offset = ch - line - 1;
701  }
702  else
703  {
704  int c;
705  c = getc(fp); /* This is kind of a hack. We want to know if the
706  char at the current point in the input stream is EOF.
707  feof() will only tell us if we've already hit EOF, not
708  if the next character is EOF. So, we need to read in
709  the next character and manually check if it is EOF. */
710  if (c == EOF)
711  {
712  /* The last line of fp isn't \n terminated */
713  if (line_num)
714  (*line_num)++;
715  return line;
716  }
717  else
718  {
719  ungetc(c, fp); /* undo our damage */
720  /* There wasn't room for the line -- increase "line" */
721  offset = *size - 1; /* overwrite the terminating 0 */
722  *size += 256;
723  mutt_mem_realloc(&line, *size);
724  }
725  }
726  }
727 }
728 
747 bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, int flags)
748 {
749  if (!iter)
750  return false;
751 
752  char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
753  if (!p)
754  return false;
755  iter->line = p;
756  return true;
757 }
758 
767 bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, int flags)
768 {
769  if (!func || !fp)
770  return false;
771 
772  struct MuttFileIter iter = { 0 };
773  while (mutt_file_iter_line(&iter, fp, flags))
774  {
775  if (!(*func)(iter.line, iter.line_num, user_data))
776  {
777  FREE(&iter.line);
778  return false;
779  }
780  }
781  return true;
782 }
783 
793 size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
794 {
795  if (!buf)
796  return 0;
797 
798  if (!filename)
799  {
800  *buf = '\0';
801  return 0;
802  }
803 
804  size_t j = 0;
805 
806  /* leave some space for the trailing characters. */
807  buflen -= 6;
808 
809  buf[j++] = '\'';
810 
811  for (size_t i = 0; (j < buflen) && filename[i]; i++)
812  {
813  if ((filename[i] == '\'') || (filename[i] == '`'))
814  {
815  buf[j++] = '\'';
816  buf[j++] = '\\';
817  buf[j++] = filename[i];
818  buf[j++] = '\'';
819  }
820  else
821  buf[j++] = filename[i];
822  }
823 
824  buf[j++] = '\'';
825  buf[j] = '\0';
826 
827  return j;
828 }
829 
836 void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
837 {
838  if (!buf || !filename)
839  return;
840 
841  mutt_buffer_reset(buf);
842  if (add_outer)
843  mutt_buffer_addch(buf, '\'');
844 
845  for (; *filename != '\0'; filename++)
846  {
847  if ((*filename == '\'') || (*filename == '`'))
848  {
849  mutt_buffer_addch(buf, '\'');
850  mutt_buffer_addch(buf, '\\');
851  mutt_buffer_addch(buf, *filename);
852  mutt_buffer_addch(buf, '\'');
853  }
854  else
855  mutt_buffer_addch(buf, *filename);
856  }
857 
858  if (add_outer)
859  mutt_buffer_addch(buf, '\'');
860 }
861 
875 int mutt_file_mkdir(const char *path, mode_t mode)
876 {
877  if (!path || (*path == '\0'))
878  {
879  errno = EINVAL;
880  return -1;
881  }
882 
883  errno = 0;
884  char tmp_path[PATH_MAX];
885  const size_t len = strlen(path);
886 
887  if (len >= sizeof(tmp_path))
888  {
889  errno = ENAMETOOLONG;
890  return -1;
891  }
892 
893  struct stat sb;
894  if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode))
895  return 0;
896 
897  /* Create a mutable copy */
898  mutt_str_copy(tmp_path, path, sizeof(tmp_path));
899 
900  for (char *p = tmp_path + 1; *p; p++)
901  {
902  if (*p != '/')
903  continue;
904 
905  /* Temporarily truncate the path */
906  *p = '\0';
907 
908  if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
909  return -1;
910 
911  *p = '/';
912  }
913 
914  if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
915  return -1;
916 
917  return 0;
918 }
919 
930 FILE *mutt_file_mkstemp_full(const char *file, int line, const char *func)
931 {
932  char name[PATH_MAX];
933 
934  int n = snprintf(name, sizeof(name), "%s/neomutt-XXXXXX", NONULL(C_Tmpdir));
935  if (n < 0)
936  return NULL;
937 
938  int fd = mkstemp(name);
939  if (fd == -1)
940  return NULL;
941 
942  FILE *fp = fdopen(fd, "w+");
943 
944  if ((unlink(name) != 0) && (errno != ENOENT))
945  {
946  mutt_file_fclose(&fp);
947  return NULL;
948  }
949 
950  MuttLogger(0, file, line, func, 1, "created temp file '%s'\n", name);
951  return fp;
952 }
953 
963 time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
964 {
965  if (!fp)
966  return -1;
967 
968  struct utimbuf utim;
969  struct stat st2;
970  time_t mtime;
971 
972  if (!st)
973  {
974  if (stat(fp, &st2) == -1)
975  return -1;
976  st = &st2;
977  }
978 
979  mtime = st->st_mtime;
980  if (mtime == mutt_date_epoch())
981  {
982  mtime -= 1;
983  utim.actime = mtime;
984  utim.modtime = mtime;
985  int rc;
986  do
987  {
988  rc = utime(fp, &utim);
989  } while ((rc == -1) && (errno == EINTR));
990 
991  if (rc == -1)
992  return -1;
993  }
994 
995  return mtime;
996 }
997 
1003 void mutt_file_set_mtime(const char *from, const char *to)
1004 {
1005  if (!from || !to)
1006  return;
1007 
1008  struct utimbuf utim;
1009  struct stat st;
1010 
1011  if (stat(from, &st) != -1)
1012  {
1013  utim.actime = st.st_mtime;
1014  utim.modtime = st.st_mtime;
1015  utime(to, &utim);
1016  }
1017 }
1018 
1027 {
1028 #ifdef HAVE_FUTIMENS
1029  struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1030  futimens(fd, times);
1031 #endif
1032 }
1033 
1042 int mutt_file_chmod(const char *path, mode_t mode)
1043 {
1044  if (!path)
1045  return -1;
1046 
1047  return chmod(path, mode);
1048 }
1049 
1067 int mutt_file_chmod_add(const char *path, mode_t mode)
1068 {
1069  return mutt_file_chmod_add_stat(path, mode, NULL);
1070 }
1071 
1090 int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1091 {
1092  if (!path)
1093  return -1;
1094 
1095  struct stat st2;
1096 
1097  if (!st)
1098  {
1099  if (stat(path, &st2) == -1)
1100  return -1;
1101  st = &st2;
1102  }
1103  return chmod(path, st->st_mode | mode);
1104 }
1105 
1123 int mutt_file_chmod_rm(const char *path, mode_t mode)
1124 {
1125  return mutt_file_chmod_rm_stat(path, mode, NULL);
1126 }
1127 
1146 int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1147 {
1148  if (!path)
1149  return -1;
1150 
1151  struct stat st2;
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 
1162 #if defined(USE_FCNTL)
1163 
1175 int mutt_file_lock(int fd, bool excl, bool timeout)
1176 {
1177  struct stat sb = { 0 }, prev_sb = { 0 };
1178  int count = 0;
1179  int attempt = 0;
1180 
1181  struct flock lck;
1182  memset(&lck, 0, sizeof(struct flock));
1183  lck.l_type = excl ? F_WRLCK : F_RDLCK;
1184  lck.l_whence = SEEK_SET;
1185 
1186  while (fcntl(fd, F_SETLK, &lck) == -1)
1187  {
1188  mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1189  if ((errno != EAGAIN) && (errno != EACCES))
1190  {
1191  mutt_perror("fcntl");
1192  return -1;
1193  }
1194 
1195  if (fstat(fd, &sb) != 0)
1196  sb.st_size = 0;
1197 
1198  if (count == 0)
1199  prev_sb = sb;
1200 
1201  /* only unlock file if it is unchanged */
1202  if ((prev_sb.st_size == sb.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1203  {
1204  if (timeout)
1205  mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1206  return -1;
1207  }
1208 
1209  prev_sb = sb;
1210 
1211  mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1212  sleep(1);
1213  }
1214 
1215  return 0;
1216 }
1217 
1223 int mutt_file_unlock(int fd)
1224 {
1225  struct flock unlockit;
1226 
1227  memset(&unlockit, 0, sizeof(struct flock));
1228  unlockit.l_type = F_UNLCK;
1229  unlockit.l_whence = SEEK_SET;
1230  fcntl(fd, F_SETLK, &unlockit);
1231 
1232  return 0;
1233 }
1234 #elif defined(USE_FLOCK)
1235 
1247 int mutt_file_lock(int fd, bool excl, bool timeout)
1248 {
1249  struct stat sb = { 0 }, prev_sb = { 0 };
1250  int rc = 0;
1251  int count = 0;
1252  int attempt = 0;
1253 
1254  while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1255  {
1256  if (errno != EWOULDBLOCK)
1257  {
1258  mutt_perror("flock");
1259  rc = -1;
1260  break;
1261  }
1262 
1263  if (fstat(fd, &sb) != 0)
1264  sb.st_size = 0;
1265 
1266  if (count == 0)
1267  prev_sb = sb;
1268 
1269  /* only unlock file if it is unchanged */
1270  if ((prev_sb.st_size == sb.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1271  {
1272  if (timeout)
1273  mutt_error(_("Timeout exceeded while attempting flock lock"));
1274  rc = -1;
1275  break;
1276  }
1277 
1278  prev_sb = sb;
1279 
1280  mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1281  sleep(1);
1282  }
1283 
1284  /* release any other locks obtained in this routine */
1285  if (rc != 0)
1286  {
1287  flock(fd, LOCK_UN);
1288  }
1289 
1290  return rc;
1291 }
1292 
1298 int mutt_file_unlock(int fd)
1299 {
1300  flock(fd, LOCK_UN);
1301  return 0;
1302 }
1303 #else
1304 #error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1305 #endif
1306 
1311 void mutt_file_unlink_empty(const char *path)
1312 {
1313  if (!path)
1314  return;
1315 
1316  struct stat sb;
1317 
1318  int fd = open(path, O_RDWR);
1319  if (fd == -1)
1320  return;
1321 
1322  if (mutt_file_lock(fd, true, true) == -1)
1323  {
1324  close(fd);
1325  return;
1326  }
1327 
1328  if ((fstat(fd, &sb) == 0) && (sb.st_size == 0))
1329  unlink(path);
1330 
1331  mutt_file_unlock(fd);
1332  close(fd);
1333 }
1334 
1347 int mutt_file_rename(const char *oldfile, const char *newfile)
1348 {
1349  if (!oldfile || !newfile)
1350  return -1;
1351  if (access(oldfile, F_OK) != 0)
1352  return 1;
1353  if (access(newfile, F_OK) == 0)
1354  return 2;
1355 
1356  FILE *fp_old = fopen(oldfile, "r");
1357  if (!fp_old)
1358  return 3;
1359  FILE *fp_new = mutt_file_fopen(newfile, "w");
1360  if (!fp_new)
1361  {
1362  mutt_file_fclose(&fp_old);
1363  return 3;
1364  }
1365  mutt_file_copy_stream(fp_old, fp_new);
1366  mutt_file_fclose(&fp_new);
1367  mutt_file_fclose(&fp_old);
1368  mutt_file_unlink(oldfile);
1369  return 0;
1370 }
1371 
1382 char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1383 {
1384  FILE *fp = mutt_file_fopen(file, "r");
1385  if (!fp)
1386  return NULL;
1387 
1388  buf = fgets(buf, buflen, fp);
1389  mutt_file_fclose(&fp);
1390 
1391  if (!buf)
1392  return NULL;
1393 
1394  SKIPWS(buf);
1395  char *start = buf;
1396 
1397  while ((*buf != '\0') && !isspace(*buf))
1398  buf++;
1399 
1400  *buf = '\0';
1401 
1402  return start;
1403 }
1404 
1412 int mutt_file_check_empty(const char *path)
1413 {
1414  if (!path)
1415  return -1;
1416 
1417  struct stat st;
1418  if (stat(path, &st) == -1)
1419  return -1;
1420 
1421  return st.st_size == 0;
1422 }
1423 
1432 void mutt_buffer_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1433 {
1434  struct Buffer tmp = mutt_buffer_make(PATH_MAX);
1435 
1436  mutt_buffer_quote_filename(&tmp, src, true);
1437  mutt_file_expand_fmt(dest, fmt, mutt_b2s(&tmp));
1438  mutt_buffer_dealloc(&tmp);
1439 }
1440 
1447 void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1448 {
1449  if (!dest || !fmt || !src)
1450  return;
1451 
1452  const char *p = NULL;
1453  bool found = false;
1454 
1455  mutt_buffer_reset(dest);
1456 
1457  for (p = fmt; *p; p++)
1458  {
1459  if (*p == '%')
1460  {
1461  switch (p[1])
1462  {
1463  case '%':
1464  mutt_buffer_addch(dest, *p++);
1465  break;
1466  case 's':
1467  found = true;
1468  mutt_buffer_addstr(dest, src);
1469  p++;
1470  break;
1471  default:
1472  mutt_buffer_addch(dest, *p);
1473  break;
1474  }
1475  }
1476  else
1477  {
1478  mutt_buffer_addch(dest, *p);
1479  }
1480  }
1481 
1482  if (!found)
1483  {
1484  mutt_buffer_addch(dest, ' ');
1485  mutt_buffer_addstr(dest, src);
1486  }
1487 }
1488 
1495 long mutt_file_get_size(const char *path)
1496 {
1497  if (!path)
1498  return 0;
1499 
1500  struct stat sb;
1501  if (stat(path, &sb) != 0)
1502  return 0;
1503 
1504  return sb.st_size;
1505 }
1506 
1516 {
1517  if (!a || !b)
1518  return 0;
1519  if (a->tv_sec < b->tv_sec)
1520  return -1;
1521  if (a->tv_sec > b->tv_sec)
1522  return 1;
1523 
1524  if (a->tv_nsec < b->tv_nsec)
1525  return -1;
1526  if (a->tv_nsec > b->tv_nsec)
1527  return 1;
1528  return 0;
1529 }
1530 
1537 void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *sb, enum MuttStatType type)
1538 {
1539  if (!dest || !sb)
1540  return;
1541 
1542  dest->tv_sec = 0;
1543  dest->tv_nsec = 0;
1544 
1545  switch (type)
1546  {
1547  case MUTT_STAT_ATIME:
1548  dest->tv_sec = sb->st_atime;
1549 #ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1550  dest->tv_nsec = sb->st_atim.tv_nsec;
1551 #endif
1552  break;
1553  case MUTT_STAT_MTIME:
1554  dest->tv_sec = sb->st_mtime;
1555 #ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1556  dest->tv_nsec = sb->st_mtim.tv_nsec;
1557 #endif
1558  break;
1559  case MUTT_STAT_CTIME:
1560  dest->tv_sec = sb->st_ctime;
1561 #ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1562  dest->tv_nsec = sb->st_ctim.tv_nsec;
1563 #endif
1564  break;
1565  }
1566 }
1567 
1577 int mutt_file_stat_timespec_compare(struct stat *sba, enum MuttStatType type,
1578  struct timespec *b)
1579 {
1580  if (!sba || !b)
1581  return 0;
1582 
1583  struct timespec a = { 0 };
1584 
1585  mutt_file_get_stat_timespec(&a, sba, type);
1586  return mutt_file_timespec_compare(&a, b);
1587 }
1588 
1599 int mutt_file_stat_compare(struct stat *sba, enum MuttStatType sba_type,
1600  struct stat *sbb, enum MuttStatType sbb_type)
1601 {
1602  if (!sba || !sbb)
1603  return 0;
1604 
1605  struct timespec a = { 0 };
1606  struct timespec b = { 0 };
1607 
1608  mutt_file_get_stat_timespec(&a, sba, sba_type);
1609  mutt_file_get_stat_timespec(&b, sbb, sbb_type);
1610  return mutt_file_timespec_compare(&a, &b);
1611 }
1612 
1618 {
1619  struct stat st;
1620  int rc = lstat(mutt_b2s(buf), &st);
1621  if ((rc != -1) && S_ISLNK(st.st_mode))
1622  {
1623  char path[PATH_MAX];
1624  if (realpath(mutt_b2s(buf), path))
1625  {
1626  mutt_buffer_strcpy(buf, path);
1627  }
1628  }
1629 }
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:416
void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
Quote a filename to survive the shell&#39;s quoting rules.
Definition: file.c:836
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1515
#define MAX_LOCK_ATTEMPTS
Definition: file.c:64
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition: file.c:1026
#define NONULL(x)
Definition: string2.h:37
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:79
#define MIN(a, b)
Definition: memory.h:31
#define mutt_perror(...)
Definition: logging.h:85
Memory management wrappers.
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition: file.c:1495
File/dir&#39;s ctime - creation time.
Definition: file.h:62
char * line
the line data
Definition: file.h:70
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1003
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition: file.c:169
#define mutt_message(...)
Definition: logging.h:83
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1347
void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *sb, enum MuttStatType type)
Read the stat() time into a time value.
Definition: file.c:1537
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1146
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:195
struct Buffer mutt_buffer_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:61
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition: file.c:468
String manipulation buffer.
Definition: buffer.h:33
#define _(a)
Definition: message.h:28
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:667
static bool compare_stat(struct stat *osb, struct stat *nsb)
Compare the struct stat&#39;s of two files/dirs.
Definition: file.c:80
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:563
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition: file.c:1090
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
log_dispatcher_t MuttLogger
The log dispatcher.
Definition: logging.c:52
File management functions.
#define MUTT_CONT
-continuation
Definition: file.h:37
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1223
int mutt_file_chmod_rm(const char *path, mode_t mode)
Remove permissions from a file.
Definition: file.c:1123
Logging Dispatcher.
time_t tv_sec
Definition: file.h:48
String manipulation functions.
int mutt_file_lock(int fd, bool excl, bool timeout)
(try to) lock a file using fcntl()
Definition: file.c:1175
void mutt_file_unlink_empty(const char *path)
Delete a file if it&#39;s empty.
Definition: file.c:1311
static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
Create a temporary directory next to a file name.
Definition: file.c:94
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:875
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
MuttStatType
Flags for mutt_file_get_stat_timespec.
Definition: file.h:58
#define SKIPWS(ch)
Definition: string2.h:46
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:294
File/dir&#39;s mtime - last modified time.
Definition: file.h:61
bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, int flags)
Process lines of text read from a file pointer.
Definition: file.c:767
int mutt_file_chmod(const char *path, mode_t mode)
Set permissions of a file.
Definition: file.c:1042
long tv_nsec
Definition: file.h:49
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, int flags)
iterate over the lines from an open file pointer
Definition: file.c:747
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Message logging.
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:618
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:637
#define mutt_b2s(buf)
Definition: buffer.h:41
#define PATH_MAX
Definition: mutt.h:44
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
int line_num
line number
Definition: file.h:72
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:1447
static const char rx_special_chars[]
Definition: file.c:59
char * data
Pointer to data.
Definition: buffer.h:35
size_t size
allocated size of line data
Definition: file.h:71
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1617
size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
Quote a filename to survive the shell&#39;s quoting rules.
Definition: file.c:793
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:241
State record for mutt_file_iter_line()
Definition: file.h:68
Path manipulation functions.
int mutt_file_stat_timespec_compare(struct stat *sba, enum MuttStatType type, struct timespec *b)
Compare stat info with a time value.
Definition: file.c:1577
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1382
int mutt_file_stat_compare(struct stat *sba, enum MuttStatType sba_type, struct stat *sbb, enum MuttStatType sbb_type)
Compare two stat infos.
Definition: file.c:1599
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:312
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:137
bool(* mutt_file_map_t)(char *line, int line_num, void *user_data)
Callback function for mutt_file_map_lines()
Definition: file.h:83
General purpose object for storing and parsing strings.
int mutt_file_open(const char *path, int flags)
Open a file.
Definition: file.c:522
Log at debug level 1.
Definition: logging.h:40
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:716
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:271
#define O_NOFOLLOW
Definition: file.c:68
#define mutt_error(...)
Definition: logging.h:84
char * C_Tmpdir
Config: Directory for temporary files.
Definition: file.c:56
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1412
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:1432
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file&#39;s modification time by 1 second.
Definition: file.c:963
#define FREE(x)
Definition: memory.h:40
Time value with nanosecond precision.
Definition: file.h:46
#define MUTT_EOL
don&#39;t strip \n / \r\n
Definition: file.h:38
const char filename_safe_chars[]
Definition: file.c:61
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1067
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:299
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:588
Time and date handling routines.
FILE * mutt_file_mkstemp_full(const char *file, int line, const char *func)
Create temporary file safely.
Definition: file.c:930
File/dir&#39;s atime - last accessed time.
Definition: file.h:60
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
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:354