NeoMutt  2021-10-22-8-g9cb437
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 "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 
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 
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 
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 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 
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 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 
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 
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))
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
1034 {
1035 #ifdef HAVE_FUTIMENS
1036  struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1037  futimens(fd, times);
1038 #endif
1039 }
1040 
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 
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 
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 
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 
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)
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 
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)
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 }
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
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:1562
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:271
int mutt_file_open(const char *path, uint32_t flags)
Open a file.
Definition: file.c:524
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:355
void mutt_file_unlink_empty(const char *path)
Delete a file if it's empty.
Definition: file.c:1318
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:1624
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
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:153
#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:1033
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:641
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:671
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1419
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:881
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:1182
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition: file.c:1520
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:622
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1540
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:842
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1230
int mutt_file_chmod_rm(const char *path, mode_t mode)
Remove permissions from a file.
Definition: file.c:1130
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition: file.c:970
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:773
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:799
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition: file.c:1502
int mutt_file_chmod(const char *path, mode_t mode)
Set permissions of a file.
Definition: file.c:1049
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1354
#define O_NOFOLLOW
Definition: file.c:68
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
Iterate over the lines from an open file pointer.
Definition: file.c:752
static bool compare_stat(struct stat *st_old, struct stat *st_new)
Compare the struct stat's of two files/dirs.
Definition: file.c:80
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1389
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:299
FILE * mutt_file_mkstemp_full(const char *file, int line, const char *func)
Create temporary file safely.
Definition: file.c:936
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition: file.c:1097
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
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:1454
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1642
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1010
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_stat_timespec_compare(struct stat *st, enum MuttStatType type, struct timespec *b)
Compare stat info with a time value.
Definition: file.c:1602
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1074
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:593
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:195
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition: file.c:169
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:1439
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition: file.c:470
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1153
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
#define MIN(a, b)
Definition: memory.h:31
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:749
#define PATH_MAX
Definition: mutt.h:40
Path manipulation functions.
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