NeoMutt  2023-05-17-56-ga67199
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 <errno.h>
32#include <fcntl.h>
33#include <libgen.h>
34#include <limits.h>
35#include <stdbool.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <sys/stat.h>
40#include <unistd.h>
41#include <utime.h>
42#include "file.h"
43#include "buffer.h"
44#include "date.h"
45#include "logging2.h"
46#include "memory.h"
47#include "message.h"
48#include "path.h"
49#include "pool.h"
50#include "string2.h"
51#ifdef USE_FLOCK
52#include <sys/file.h>
53#endif
54
56static const char RxSpecialChars[] = "^.[$()|*+?{\\";
57
59const char FilenameSafeChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
60
61#define MAX_LOCK_ATTEMPTS 5
62
63/* This is defined in POSIX:2008 which isn't a build requirement */
64#ifndef O_NOFOLLOW
65#define O_NOFOLLOW 0
66#endif
67
77static bool compare_stat(struct stat *st_old, struct stat *st_new)
78{
79 return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) &&
80 (st_old->st_rdev == st_new->st_rdev);
81}
82
91static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
92{
93 const char *basename = NULL;
94 int rc = 0;
95
96 struct Buffer parent = buf_make(PATH_MAX);
97 buf_strcpy(&parent, NONULL(path));
98
99 char *p = strrchr(parent.data, '/');
100 if (p)
101 {
102 *p = '\0';
103 basename = p + 1;
104 }
105 else
106 {
107 buf_strcpy(&parent, ".");
108 basename = path;
109 }
110
111 buf_printf(newdir, "%s/%s", buf_string(&parent), ".muttXXXXXX");
112 if (!mkdtemp(newdir->data))
113 {
114 mutt_debug(LL_DEBUG1, "mkdtemp() failed\n");
115 rc = -1;
116 goto cleanup;
117 }
118
119 buf_printf(newfile, "%s/%s", newdir->data, NONULL(basename));
120
121cleanup:
122 buf_dealloc(&parent);
123 return rc;
124}
125
134static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
135{
136 int rc;
137
138 rc = mutt_file_safe_rename(safe_file, path);
139 unlink(safe_file);
140 rmdir(safe_dir);
141 return rc;
142}
143
150int mutt_file_fclose(FILE **fp)
151{
152 if (!fp || !*fp)
153 return 0;
154
155 int rc = fclose(*fp);
156 *fp = NULL;
157 return rc;
158}
159
167{
168 if (!fp || !*fp)
169 return 0;
170
171 int rc = 0;
172
173 if (fflush(*fp) || fsync(fileno(*fp)))
174 {
175 int save_errno = errno;
176 rc = -1;
178 errno = save_errno;
179 }
180 else
181 {
182 rc = mutt_file_fclose(fp);
183 }
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 = buf_make(PATH_MAX);
307
308 if (!mutt_path_getcwd(&abs_oldpath))
309 {
310 buf_dealloc(&abs_oldpath);
311 return -1;
312 }
313
314 buf_addch(&abs_oldpath, '/');
315 buf_addstr(&abs_oldpath, oldpath);
316 if (symlink(buf_string(&abs_oldpath), newpath) == -1)
317 {
318 buf_dealloc(&abs_oldpath);
319 return -1;
320 }
321
322 buf_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 *dir = mutt_file_opendir(path, MUTT_OPENDIR_NONE);
469 if (!dir)
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 = buf_make(PATH_MAX);
478
479 while ((de = readdir(dir)))
480 {
481 if ((mutt_str_equal(".", de->d_name)) || (mutt_str_equal("..", de->d_name)))
482 continue;
483
484 buf_printf(&cur, "%s/%s", path, de->d_name);
485 /* XXX make nonrecursive version */
486
487 if (stat(buf_string(&cur), &st) == -1)
488 {
489 rc = 1;
490 continue;
491 }
492
493 if (S_ISDIR(st.st_mode))
494 rc |= mutt_file_rmtree(buf_string(&cur));
495 else
496 rc |= unlink(buf_string(&cur));
497 }
498 closedir(dir);
499
500 rc |= rmdir(path);
501
502 buf_dealloc(&cur);
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 = buf_pool_get();
525 struct Buffer *new_file = buf_pool_get();
526
527 /* rotate the old debug logs */
528 for (count -= 2; count >= 0; count--)
529 {
530 buf_printf(old_file, "%s%d", path, count);
531 buf_printf(new_file, "%s%d", path, count + 1);
532 (void) rename(buf_string(old_file), buf_string(new_file));
533 }
534
535 path = buf_strdup(old_file);
536 buf_pool_release(&old_file);
537 buf_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 = buf_make(0);
556 struct Buffer safe_dir = buf_make(0);
557
558 if (flags & O_EXCL)
559 {
560 buf_alloc(&safe_file, PATH_MAX);
561 buf_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(buf_string(&safe_file), flags, 0600);
570 if (fd < 0)
571 {
572 rmdir(buf_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, buf_string(&safe_file), buf_string(&safe_dir)) == -1)
579 {
580 fd = -1;
581 goto cleanup;
582 }
583 }
584
585 fd = open(path, flags & ~O_EXCL, 0600);
586 if (fd < 0)
587 goto cleanup;
588
589 /* make sure the file is not symlink */
590 struct stat st_old = { 0 };
591 struct stat st_new = { 0 };
592 if (((lstat(path, &st_old) < 0) || (fstat(fd, &st_new) < 0)) ||
593 !compare_stat(&st_old, &st_new))
594 {
595 close(fd);
596 fd = -1;
597 goto cleanup;
598 }
599
600cleanup:
601 buf_dealloc(&safe_file);
602 buf_dealloc(&safe_dir);
603
604 return fd;
605}
606
614DIR *mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
615{
616 if ((mode == MUTT_OPENDIR_CREATE) && (mutt_file_mkdir(path, S_IRWXU) == -1))
617 {
618 return NULL;
619 }
620 errno = 0;
621 return opendir(path);
622}
623
634FILE *mutt_file_fopen(const char *path, const char *mode)
635{
636 if (!path || !mode)
637 return NULL;
638
639 if (mode[0] == 'w')
640 {
641 uint32_t flags = O_CREAT | O_EXCL | O_NOFOLLOW;
642
643 if (mode[1] == '+')
644 flags |= O_RDWR;
645 else
646 flags |= O_WRONLY;
647
648 int fd = mutt_file_open(path, flags);
649 if (fd < 0)
650 return NULL;
651
652 return fdopen(fd, mode);
653 }
654 else
655 {
656 return fopen(path, mode);
657 }
658}
659
665void mutt_file_sanitize_filename(char *path, bool slash)
666{
667 if (!path)
668 return;
669
670 for (; *path; path++)
671 {
672 if ((slash && (*path == '/')) || !strchr(FilenameSafeChars, *path))
673 *path = '_';
674 }
675}
676
684int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
685{
686 if (!dest || !src)
687 return -1;
688
689 buf_reset(dest);
690 while (*src != '\0')
691 {
692 if (strchr(RxSpecialChars, *src))
693 buf_addch(dest, '\\');
694 buf_addch(dest, *src++);
695 }
696
697 return 0;
698}
699
708bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
709{
710 if (!fp)
711 {
712 return false;
713 }
714
715 if (fseeko(fp, offset, whence) != 0)
716 {
717 mutt_perror(_("Failed to seek file: %s"), strerror(errno));
718 return false;
719 }
720
721 return true;
722}
723
738char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
739{
740 if (!size || !fp)
741 return NULL;
742
743 size_t offset = 0;
744 char *ch = NULL;
745
746 if (!line)
747 {
748 *size = 256;
749 line = mutt_mem_malloc(*size);
750 }
751
752 while (true)
753 {
754 if (!fgets(line + offset, *size - offset, fp))
755 {
756 FREE(&line);
757 return NULL;
758 }
759 ch = strchr(line + offset, '\n');
760 if (ch)
761 {
762 if (line_num)
763 (*line_num)++;
764 if (flags & MUTT_RL_EOL)
765 return line;
766 *ch = '\0';
767 if ((ch > line) && (*(ch - 1) == '\r'))
768 *--ch = '\0';
769 if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
770 return line;
771 offset = ch - line - 1;
772 }
773 else
774 {
775 int c;
776 c = getc(fp); /* This is kind of a hack. We want to know if the
777 char at the current point in the input stream is EOF.
778 feof() will only tell us if we've already hit EOF, not
779 if the next character is EOF. So, we need to read in
780 the next character and manually check if it is EOF. */
781 if (c == EOF)
782 {
783 /* The last line of fp isn't \n terminated */
784 if (line_num)
785 (*line_num)++;
786 return line;
787 }
788 else
789 {
790 ungetc(c, fp); /* undo our damage */
791 /* There wasn't room for the line -- increase "line" */
792 offset = *size - 1; /* overwrite the terminating 0 */
793 *size += 256;
794 mutt_mem_realloc(&line, *size);
795 }
796 }
797 }
798}
799
819bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
820{
821 if (!iter)
822 return false;
823
824 char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
825 if (!p)
826 return false;
827 iter->line = p;
828 return true;
829}
830
840bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
841{
842 if (!func || !fp)
843 return false;
844
845 struct MuttFileIter iter = { 0 };
846 while (mutt_file_iter_line(&iter, fp, flags))
847 {
848 if (!(*func)(iter.line, iter.line_num, user_data))
849 {
850 FREE(&iter.line);
851 return false;
852 }
853 }
854 return true;
855}
856
866size_t mutt_file_quote_filename(const char *filename, char *buf, size_t buflen)
867{
868 if (!buf)
869 return 0;
870
871 if (!filename)
872 {
873 *buf = '\0';
874 return 0;
875 }
876
877 size_t j = 0;
878
879 /* leave some space for the trailing characters. */
880 buflen -= 6;
881
882 buf[j++] = '\'';
883
884 for (size_t i = 0; (j < buflen) && filename[i]; i++)
885 {
886 if ((filename[i] == '\'') || (filename[i] == '`'))
887 {
888 buf[j++] = '\'';
889 buf[j++] = '\\';
890 buf[j++] = filename[i];
891 buf[j++] = '\'';
892 }
893 else
894 {
895 buf[j++] = filename[i];
896 }
897 }
898
899 buf[j++] = '\'';
900 buf[j] = '\0';
901
902 return j;
903}
904
911void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
912{
913 if (!buf || !filename)
914 return;
915
916 buf_reset(buf);
917 if (add_outer)
918 buf_addch(buf, '\'');
919
920 for (; *filename != '\0'; filename++)
921 {
922 if ((*filename == '\'') || (*filename == '`'))
923 {
924 buf_addch(buf, '\'');
925 buf_addch(buf, '\\');
926 buf_addch(buf, *filename);
927 buf_addch(buf, '\'');
928 }
929 else
930 {
931 buf_addch(buf, *filename);
932 }
933 }
934
935 if (add_outer)
936 buf_addch(buf, '\'');
937}
938
952int mutt_file_mkdir(const char *path, mode_t mode)
953{
954 if (!path || (*path == '\0'))
955 {
956 errno = EINVAL;
957 return -1;
958 }
959
960 errno = 0;
961 char tmp_path[PATH_MAX] = { 0 };
962 const size_t len = strlen(path);
963
964 if (len >= sizeof(tmp_path))
965 {
966 errno = ENAMETOOLONG;
967 return -1;
968 }
969
970 struct stat st = { 0 };
971 if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
972 return 0;
973
974 /* Create a mutable copy */
975 mutt_str_copy(tmp_path, path, sizeof(tmp_path));
976
977 for (char *p = tmp_path + 1; *p; p++)
978 {
979 if (*p != '/')
980 continue;
981
982 /* Temporarily truncate the path */
983 *p = '\0';
984
985 if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
986 return -1;
987
988 *p = '/';
989 }
990
991 if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
992 return -1;
993
994 return 0;
995}
996
1006time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
1007{
1008 if (!fp)
1009 return -1;
1010
1011 struct utimbuf utim;
1012 struct stat st2 = { 0 };
1013 time_t mtime;
1014
1015 if (!st)
1016 {
1017 if (stat(fp, &st2) == -1)
1018 return -1;
1019 st = &st2;
1020 }
1021
1022 mtime = st->st_mtime;
1023 if (mtime == mutt_date_now())
1024 {
1025 mtime -= 1;
1026 utim.actime = mtime;
1027 utim.modtime = mtime;
1028 int rc;
1029 do
1030 {
1031 rc = utime(fp, &utim);
1032 } while ((rc == -1) && (errno == EINTR));
1033
1034 if (rc == -1)
1035 return -1;
1036 }
1037
1038 return mtime;
1039}
1040
1046void mutt_file_set_mtime(const char *from, const char *to)
1047{
1048 if (!from || !to)
1049 return;
1050
1051 struct utimbuf utim;
1052 struct stat st = { 0 };
1053
1054 if (stat(from, &st) != -1)
1055 {
1056 utim.actime = st.st_mtime;
1057 utim.modtime = st.st_mtime;
1058 utime(to, &utim);
1059 }
1060}
1061
1070{
1071#ifdef HAVE_FUTIMENS
1072 struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1073 futimens(fd, times);
1074#endif
1075}
1076
1085int mutt_file_chmod(const char *path, mode_t mode)
1086{
1087 if (!path)
1088 return -1;
1089
1090 return chmod(path, mode);
1091}
1092
1110int mutt_file_chmod_add(const char *path, mode_t mode)
1111{
1112 return mutt_file_chmod_add_stat(path, mode, NULL);
1113}
1114
1133int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1134{
1135 if (!path)
1136 return -1;
1137
1138 struct stat st2 = { 0 };
1139
1140 if (!st)
1141 {
1142 if (stat(path, &st2) == -1)
1143 return -1;
1144 st = &st2;
1145 }
1146 return chmod(path, st->st_mode | mode);
1147}
1148
1166int mutt_file_chmod_rm(const char *path, mode_t mode)
1167{
1168 return mutt_file_chmod_rm_stat(path, mode, NULL);
1169}
1170
1189int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1190{
1191 if (!path)
1192 return -1;
1193
1194 struct stat st2 = { 0 };
1195
1196 if (!st)
1197 {
1198 if (stat(path, &st2) == -1)
1199 return -1;
1200 st = &st2;
1201 }
1202 return chmod(path, st->st_mode & ~mode);
1203}
1204
1205#if defined(USE_FCNTL)
1218int mutt_file_lock(int fd, bool excl, bool timeout)
1219{
1220 struct stat st = { 0 }, prev_sb = { 0 };
1221 int count = 0;
1222 int attempt = 0;
1223
1224 struct flock lck;
1225 memset(&lck, 0, sizeof(struct flock));
1226 lck.l_type = excl ? F_WRLCK : F_RDLCK;
1227 lck.l_whence = SEEK_SET;
1228
1229 while (fcntl(fd, F_SETLK, &lck) == -1)
1230 {
1231 mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1232 if ((errno != EAGAIN) && (errno != EACCES))
1233 {
1234 mutt_perror("fcntl");
1235 return -1;
1236 }
1237
1238 if (fstat(fd, &st) != 0)
1239 st.st_size = 0;
1240
1241 if (count == 0)
1242 prev_sb = st;
1243
1244 /* only unlock file if it is unchanged */
1245 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1246 {
1247 if (timeout)
1248 mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1249 return -1;
1250 }
1251
1252 prev_sb = st;
1253
1254 mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1255 sleep(1);
1256 }
1257
1258 return 0;
1259}
1260
1267{
1268 struct flock unlockit;
1269
1270 memset(&unlockit, 0, sizeof(struct flock));
1271 unlockit.l_type = F_UNLCK;
1272 unlockit.l_whence = SEEK_SET;
1273 (void) fcntl(fd, F_SETLK, &unlockit);
1274
1275 return 0;
1276}
1277#elif defined(USE_FLOCK)
1290int mutt_file_lock(int fd, bool excl, bool timeout)
1291{
1292 struct stat st = { 0 }, prev_sb = { 0 };
1293 int rc = 0;
1294 int count = 0;
1295 int attempt = 0;
1296
1297 while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1298 {
1299 if (errno != EWOULDBLOCK)
1300 {
1301 mutt_perror("flock");
1302 rc = -1;
1303 break;
1304 }
1305
1306 if (fstat(fd, &st) != 0)
1307 st.st_size = 0;
1308
1309 if (count == 0)
1310 prev_sb = st;
1311
1312 /* only unlock file if it is unchanged */
1313 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1314 {
1315 if (timeout)
1316 mutt_error(_("Timeout exceeded while attempting flock lock"));
1317 rc = -1;
1318 break;
1319 }
1320
1321 prev_sb = st;
1322
1323 mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1324 sleep(1);
1325 }
1326
1327 /* release any other locks obtained in this routine */
1328 if (rc != 0)
1329 {
1330 flock(fd, LOCK_UN);
1331 }
1332
1333 return rc;
1334}
1335
1341int mutt_file_unlock(int fd)
1342{
1343 flock(fd, LOCK_UN);
1344 return 0;
1345}
1346#else
1347#error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1348#endif
1349
1354void mutt_file_unlink_empty(const char *path)
1355{
1356 if (!path)
1357 return;
1358
1359 struct stat st = { 0 };
1360
1361 int fd = open(path, O_RDWR);
1362 if (fd == -1)
1363 return;
1364
1365 if (mutt_file_lock(fd, true, true) == -1)
1366 {
1367 close(fd);
1368 return;
1369 }
1370
1371 if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1372 unlink(path);
1373
1374 mutt_file_unlock(fd);
1375 close(fd);
1376}
1377
1390int mutt_file_rename(const char *oldfile, const char *newfile)
1391{
1392 if (!oldfile || !newfile)
1393 return -1;
1394 if (access(oldfile, F_OK) != 0)
1395 return 1;
1396 if (access(newfile, F_OK) == 0)
1397 return 2;
1398
1399 FILE *fp_old = fopen(oldfile, "r");
1400 if (!fp_old)
1401 return 3;
1402 FILE *fp_new = mutt_file_fopen(newfile, "w");
1403 if (!fp_new)
1404 {
1405 mutt_file_fclose(&fp_old);
1406 return 3;
1407 }
1408 mutt_file_copy_stream(fp_old, fp_new);
1409 mutt_file_fclose(&fp_new);
1410 mutt_file_fclose(&fp_old);
1411 mutt_file_unlink(oldfile);
1412 return 0;
1413}
1414
1425char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1426{
1427 FILE *fp = mutt_file_fopen(file, "r");
1428 if (!fp)
1429 return NULL;
1430
1431 buf = fgets(buf, buflen, fp);
1432 mutt_file_fclose(&fp);
1433
1434 if (!buf)
1435 return NULL;
1436
1437 SKIPWS(buf);
1438 char *start = buf;
1439
1440 while ((*buf != '\0') && !isspace(*buf))
1441 buf++;
1442
1443 *buf = '\0';
1444
1445 return start;
1446}
1447
1455int mutt_file_check_empty(const char *path)
1456{
1457 if (!path)
1458 return -1;
1459
1460 struct stat st = { 0 };
1461 if (stat(path, &st) == -1)
1462 return -1;
1463
1464 return st.st_size == 0;
1465}
1466
1475void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1476{
1477 struct Buffer tmp = buf_make(PATH_MAX);
1478
1479 buf_quote_filename(&tmp, src, true);
1480 mutt_file_expand_fmt(dest, fmt, buf_string(&tmp));
1481 buf_dealloc(&tmp);
1482}
1483
1490void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1491{
1492 if (!dest || !fmt || !src)
1493 return;
1494
1495 const char *p = NULL;
1496 bool found = false;
1497
1498 buf_reset(dest);
1499
1500 for (p = fmt; *p; p++)
1501 {
1502 if (*p == '%')
1503 {
1504 switch (p[1])
1505 {
1506 case '%':
1507 buf_addch(dest, *p++);
1508 break;
1509 case 's':
1510 found = true;
1511 buf_addstr(dest, src);
1512 p++;
1513 break;
1514 default:
1515 buf_addch(dest, *p);
1516 break;
1517 }
1518 }
1519 else
1520 {
1521 buf_addch(dest, *p);
1522 }
1523 }
1524
1525 if (!found)
1526 {
1527 buf_addch(dest, ' ');
1528 buf_addstr(dest, src);
1529 }
1530}
1531
1538long mutt_file_get_size(const char *path)
1539{
1540 if (!path)
1541 return 0;
1542
1543 struct stat st = { 0 };
1544 if (stat(path, &st) != 0)
1545 return 0;
1546
1547 return st.st_size;
1548}
1549
1557{
1558 if (!fp)
1559 return 0;
1560
1561 struct stat st = { 0 };
1562 if (fstat(fileno(fp), &st) != 0)
1563 return 0;
1564
1565 return st.st_size;
1566}
1567
1577{
1578 if (!a || !b)
1579 return 0;
1580 if (a->tv_sec < b->tv_sec)
1581 return -1;
1582 if (a->tv_sec > b->tv_sec)
1583 return 1;
1584
1585 if (a->tv_nsec < b->tv_nsec)
1586 return -1;
1587 if (a->tv_nsec > b->tv_nsec)
1588 return 1;
1589 return 0;
1590}
1591
1598void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1599{
1600 if (!dest || !st)
1601 return;
1602
1603 dest->tv_sec = 0;
1604 dest->tv_nsec = 0;
1605
1606 switch (type)
1607 {
1608 case MUTT_STAT_ATIME:
1609 dest->tv_sec = st->st_atime;
1610#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1611 dest->tv_nsec = st->st_atim.tv_nsec;
1612#endif
1613 break;
1614 case MUTT_STAT_MTIME:
1615 dest->tv_sec = st->st_mtime;
1616#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1617 dest->tv_nsec = st->st_mtim.tv_nsec;
1618#endif
1619 break;
1620 case MUTT_STAT_CTIME:
1621 dest->tv_sec = st->st_ctime;
1622#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1623 dest->tv_nsec = st->st_ctim.tv_nsec;
1624#endif
1625 break;
1626 }
1627}
1628
1638int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1639 struct timespec *b)
1640{
1641 if (!st || !b)
1642 return 0;
1643
1644 struct timespec a = { 0 };
1645
1646 mutt_file_get_stat_timespec(&a, st, type);
1647 return mutt_file_timespec_compare(&a, b);
1648}
1649
1660int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1661 struct stat *st2, enum MuttStatType st2_type)
1662{
1663 if (!st1 || !st2)
1664 return 0;
1665
1666 struct timespec a = { 0 };
1667 struct timespec b = { 0 };
1668
1669 mutt_file_get_stat_timespec(&a, st1, st1_type);
1670 mutt_file_get_stat_timespec(&b, st2, st2_type);
1671 return mutt_file_timespec_compare(&a, &b);
1672}
1673
1679{
1680 struct stat st = { 0 };
1681 int rc = lstat(buf_string(buf), &st);
1682 if ((rc != -1) && S_ISLNK(st.st_mode))
1683 {
1684 char path[PATH_MAX] = { 0 };
1685 if (realpath(buf_string(buf), path))
1686 {
1687 buf_strcpy(buf, path);
1688 }
1689 }
1690}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:173
void buf_dealloc(struct Buffer *buf)
Release the memory allocated by a buffer.
Definition: buffer.c:383
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:88
struct Buffer buf_make(size_t size)
Make a new buffer on the stack.
Definition: buffer.c:70
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:253
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:238
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:401
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:536
void buf_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:347
General purpose object for storing and parsing strings.
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:90
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:1598
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:260
void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
Quote a filename to survive the shell's quoting rules.
Definition: file.c:911
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:738
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:634
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:1354
const char FilenameSafeChars[]
Set of characters that are safe to use in filenames.
Definition: file.c:59
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:1660
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
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1425
#define MAX_LOCK_ATTEMPTS
Definition: file.c:61
void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
Replace s in a string with a filename.
Definition: file.c:1475
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition: file.c:1069
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:684
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1455
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:952
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition: file.c:1218
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition: file.c:1556
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:665
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1576
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1266
int mutt_file_chmod_rm(const char *path, mode_t mode)
Remove permissions from a file.
Definition: file.c:1166
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition: file.c:1006
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:840
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:866
DIR * mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
Open a directory.
Definition: file.c:614
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:708
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition: file.c:1538
int mutt_file_chmod(const char *path, mode_t mode)
Set permissions of a file.
Definition: file.c:1085
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1390
#define O_NOFOLLOW
Definition: file.c:65
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
Iterate over the lines from an open file pointer.
Definition: file.c:819
static bool compare_stat(struct stat *st_old, struct stat *st_new)
Compare the struct stat's of two files/dirs.
Definition: file.c:77
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:1133
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:134
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:1490
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1678
static const char RxSpecialChars[]
These characters must be escaped in regular expressions.
Definition: file.c:56
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1046
static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
Create a temporary directory next to a file name.
Definition: file.c:91
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:1638
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:1110
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:166
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:1189
File management functions.
MuttOpenDirMode
Mode flag for mutt_file_opendir()
Definition: file.h:72
@ MUTT_OPENDIR_CREATE
Create the directory if it doesn't exist.
Definition: file.h:74
@ MUTT_OPENDIR_NONE
Plain opendir()
Definition: file.h:73
#define MUTT_RL_CONT
-continuation
Definition: file.h:40
bool(* mutt_file_map_t)(char *line, int line_num, void *user_data)
Definition: file.h:98
#define MUTT_RL_EOL
don't strip \n / \r\n
Definition: file.h:41
MuttStatType
Flags for mutt_file_get_stat_timespec.
Definition: file.h:62
@ MUTT_STAT_CTIME
File/dir's ctime - creation time.
Definition: file.h:65
@ MUTT_STAT_ATIME
File/dir's atime - last accessed time.
Definition: file.h:63
@ MUTT_STAT_MTIME
File/dir's mtime - last modified time.
Definition: file.h:64
uint8_t ReadLineFlags
Flags for mutt_file_read_line(), e.g. MUTT_RL_CONT.
Definition: file.h:38
#define mutt_error(...)
Definition: logging2.h:90
#define mutt_message(...)
Definition: logging2.h:89
#define mutt_debug(LEVEL,...)
Definition: logging2.h:87
#define mutt_perror(...)
Definition: logging2.h:91
Logging Dispatcher.
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
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
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:446
Message logging.
#define _(a)
Definition: message.h:28
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:563
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
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:653
#define PATH_MAX
Definition: mutt.h:41
Path manipulation functions.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
A global pool of Buffers.
String manipulation functions.
#define NONULL(x)
Definition: string2.h:37
#define SKIPWS(ch)
Definition: string2.h:45
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:81
char * line
the line data
Definition: file.h:82
int line_num
line number
Definition: file.h:84
size_t size
allocated size of line data
Definition: file.h:83
Time value with nanosecond precision.
Definition: file.h:50
long tv_nsec
Number of nanosecond, on top.
Definition: file.h:52
time_t tv_sec
Number of seconds since the epoch.
Definition: file.h:51