NeoMutt  2022-04-29-215-gc12b98
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 */
59static const char rx_special_chars[] = "^.[$()|*+?{\\";
60
61const 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
79static 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
93static 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
123cleanup:
124 mutt_buffer_dealloc(&parent);
125 return rc;
126}
127
136static 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
152int 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
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;
180 errno = save_errno;
181 }
182 else
183 rc = mutt_file_fclose(fp);
184
185 return rc;
186}
187
194void 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
230int 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] = { 0 };
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
260int 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] = { 0 };
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
288int 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
344int 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
442success:
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
459int 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
503 return rc;
504}
505
519const 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
549int 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
601cleanup:
602 mutt_buffer_dealloc(&safe_file);
603 mutt_buffer_dealloc(&safe_dir);
604
605 return fd;
606}
607
618FILE *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
647void 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
666int 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
690bool 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
720char *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
801bool 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
822bool 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
848size_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
891void mutt_buffer_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
892{
893 if (!buf || !filename)
894 return;
895
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
930int 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] = { 0 };
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
985FILE *mutt_file_mkstemp_full(const char *file, int line, const char *func)
986{
987 char name[PATH_MAX] = { 0 };
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
1019time_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
1059void 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
1098int mutt_file_chmod(const char *path, mode_t mode)
1099{
1100 if (!path)
1101 return -1;
1102
1103 return chmod(path, mode);
1104}
1105
1123int mutt_file_chmod_add(const char *path, mode_t mode)
1124{
1125 return mutt_file_chmod_add_stat(path, mode, NULL);
1126}
1127
1146int 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
1179int mutt_file_chmod_rm(const char *path, mode_t mode)
1180{
1181 return mutt_file_chmod_rm_stat(path, mode, NULL);
1182}
1183
1202int 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)
1231int 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
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)
1303int 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
1354int 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
1367void 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
1403int 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
1438char *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
1468int 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
1488void 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
1503void 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
1551long 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
1611void 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
1651int 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
1673int 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] = { 0 };
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:67
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:275
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:327
void mutt_buffer_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:309
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:248
size_t mutt_buffer_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:233
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:168
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:85
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:447
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
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
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:428
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
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
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
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
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1438
#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
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
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:288
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
FILE * mutt_file_mkstemp_full(const char *file, int line, const char *func)
Create temporary file safely.
Definition: file.c:985
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
const char * mutt_file_rotate(const char *path, int count)
Rotate a set of numbered files.
Definition: file.c:519
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1123
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
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:43
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:652
#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