NeoMutt  2024-04-25-76-g20fe7b
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1
38#include "config.h"
39#include <ctype.h>
40#include <errno.h>
41#include <fcntl.h>
42#include <libgen.h>
43#include <limits.h>
44#include <stdbool.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <sys/stat.h>
49#include <time.h>
50#include <unistd.h>
51#include <utime.h>
52#include <wchar.h>
53#include "file.h"
54#include "buffer.h"
55#include "charset.h"
56#include "date.h"
57#include "logging2.h"
58#include "memory.h"
59#include "message.h"
60#include "path.h"
61#include "pool.h"
62#include "string2.h"
63#ifdef USE_FLOCK
64#include <sys/file.h>
65#endif
66
68static const char RxSpecialChars[] = "^.[$()|*+?{\\";
69
71const char FilenameSafeChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
72
73#define MAX_LOCK_ATTEMPTS 5
74
75/* This is defined in POSIX:2008 which isn't a build requirement */
76#ifndef O_NOFOLLOW
77#define O_NOFOLLOW 0
78#endif
79
89static bool stat_equal(struct stat *st_old, struct stat *st_new)
90{
91 return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) &&
92 (st_old->st_rdev == st_new->st_rdev);
93}
94
103static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
104{
105 const char *basename = NULL;
106 int rc = 0;
107
108 struct Buffer *parent = buf_pool_get();
109 buf_strcpy(parent, path);
110
111 char *p = strrchr(buf_string(parent), '/');
112 if (p)
113 {
114 *p = '\0';
115 basename = p + 1;
116 }
117 else
118 {
119 buf_strcpy(parent, ".");
120 basename = path;
121 }
122
123 buf_printf(newdir, "%s/%s", buf_string(parent), ".muttXXXXXX");
124 if (!mkdtemp(newdir->data))
125 {
126 mutt_debug(LL_DEBUG1, "mkdtemp() failed\n");
127 rc = -1;
128 goto cleanup;
129 }
130
131 buf_printf(newfile, "%s/%s", buf_string(newdir), NONULL(basename));
132
133cleanup:
134 buf_pool_release(&parent);
135 return rc;
136}
137
146static int put_file_in_place(const char *path, const char *safe_file, const char *safe_dir)
147{
148 int rc;
149
150 rc = mutt_file_safe_rename(safe_file, path);
151 unlink(safe_file);
152 rmdir(safe_dir);
153 return rc;
154}
155
165int mutt_file_fclose_full(FILE **fp, const char *file, int line, const char *func)
166{
167 if (!fp || !*fp)
168 return 0;
169
170 int fd = fileno(*fp);
171 int rc = fclose(*fp);
172
173 if (rc == 0)
174 {
175 MuttLogger(0, file, line, func, LL_DEBUG2, "File closed (fd=%d)\n", fd);
176 }
177 else
178 {
179 MuttLogger(0, file, line, func, LL_DEBUG2, "File close failed (fd=%d), errno=%d, %s\n",
180 fd, errno, strerror(errno));
181 }
182
183 *fp = NULL;
184 return rc;
185}
186
194{
195 if (!fp || !*fp)
196 return 0;
197
198 int rc = 0;
199
200 if (fflush(*fp) || fsync(fileno(*fp)))
201 {
202 int save_errno = errno;
203 rc = -1;
205 errno = save_errno;
206 }
207 else
208 {
209 rc = mutt_file_fclose(fp);
210 }
211
212 return rc;
213}
214
221void mutt_file_unlink(const char *s)
222{
223 if (!s)
224 return;
225
226 struct stat st = { 0 };
227 /* Defend against symlink attacks */
228
229 const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode);
230 if (!is_regular_file)
231 return;
232
233 const int fd = open(s, O_RDWR | O_NOFOLLOW);
234 if (fd < 0)
235 return;
236
237 struct stat st2 = { 0 };
238 if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) ||
239 (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
240 {
241 close(fd);
242 return;
243 }
244
245 unlink(s);
246 close(fd);
247}
248
257int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
258{
259 if (!fp_in || !fp_out)
260 return -1;
261
262 while (size > 0)
263 {
264 char buf[2048] = { 0 };
265 size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
266 chunk = fread(buf, 1, chunk, fp_in);
267 if (chunk < 1)
268 break;
269 if (fwrite(buf, 1, chunk, fp_out) != chunk)
270 return -1;
271
272 size -= chunk;
273 }
274
275 if (fflush(fp_out) != 0)
276 return -1;
277 return 0;
278}
279
287int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
288{
289 if (!fp_in || !fp_out)
290 return -1;
291
292 size_t total = 0;
293 size_t l;
294 char buf[1024] = { 0 };
295
296 while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0)
297 {
298 if (fwrite(buf, 1, l, fp_out) != l)
299 return -1;
300 total += l;
301 }
302
303 if (fflush(fp_out) != 0)
304 return -1;
305 return total;
306}
307
315int mutt_file_symlink(const char *oldpath, const char *newpath)
316{
317 struct stat st_old = { 0 };
318 struct stat st_new = { 0 };
319
320 if (!oldpath || !newpath)
321 return -1;
322
323 if ((unlink(newpath) == -1) && (errno != ENOENT))
324 return -1;
325
326 if (oldpath[0] == '/')
327 {
328 if (symlink(oldpath, newpath) == -1)
329 return -1;
330 }
331 else
332 {
333 struct Buffer *abs_oldpath = buf_pool_get();
334
335 if (!mutt_path_getcwd(abs_oldpath))
336 {
337 buf_pool_release(&abs_oldpath);
338 return -1;
339 }
340
341 buf_addch(abs_oldpath, '/');
342 buf_addstr(abs_oldpath, oldpath);
343 if (symlink(buf_string(abs_oldpath), newpath) == -1)
344 {
345 buf_pool_release(&abs_oldpath);
346 return -1;
347 }
348
349 buf_pool_release(&abs_oldpath);
350 }
351
352 if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) ||
353 !stat_equal(&st_old, &st_new))
354 {
355 unlink(newpath);
356 return -1;
357 }
358
359 return 0;
360}
361
371int mutt_file_safe_rename(const char *src, const char *target)
372{
373 struct stat st_src = { 0 };
374 struct stat st_target = { 0 };
375 int link_errno;
376
377 if (!src || !target)
378 return -1;
379
380 if (link(src, target) != 0)
381 {
382 link_errno = errno;
383
384 /* It is historically documented that link can return -1 if NFS
385 * dies after creating the link. In that case, we are supposed
386 * to use stat to check if the link was created.
387 *
388 * Derek Martin notes that some implementations of link() follow a
389 * source symlink. It might be more correct to use stat() on src.
390 * I am not doing so to minimize changes in behavior: the function
391 * used lstat() further below for 20 years without issue, and I
392 * believe was never intended to be used on a src symlink. */
393 if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) &&
394 (stat_equal(&st_src, &st_target) == 0))
395 {
396 mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
397 src, target, strerror(errno), errno);
398 goto success;
399 }
400
401 errno = link_errno;
402
403 /* Coda does not allow cross-directory links, but tells
404 * us it's a cross-filesystem linking attempt.
405 *
406 * However, the Coda rename call is allegedly safe to use.
407 *
408 * With other file systems, rename should just fail when
409 * the files reside on different file systems, so it's safe
410 * to try it here. */
411 mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
412 strerror(errno), errno);
413
414 /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
415 * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */
416 if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
417#ifdef ENOTSUP
418 || errno == ENOTSUP
419#endif
420#ifdef EOPNOTSUPP
421 || errno == EOPNOTSUPP
422#endif
423 )
424 {
425 mutt_debug(LL_DEBUG1, "trying rename\n");
426 if (rename(src, target) == -1)
427 {
428 mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
429 strerror(errno), errno);
430 return -1;
431 }
432 mutt_debug(LL_DEBUG1, "rename succeeded\n");
433
434 return 0;
435 }
436
437 return -1;
438 }
439
440 /* Remove the stat_equal() check, because it causes problems with maildir
441 * on filesystems that don't properly support hard links, such as sshfs. The
442 * filesystem creates the link, but the resulting file is given a different
443 * inode number by the sshfs layer. This results in an infinite loop
444 * creating links. */
445#if 0
446 /* Stat both links and check if they are equal. */
447 if (lstat(src, &st_src) == -1)
448 {
449 mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
450 return -1;
451 }
452
453 if (lstat(target, &st_target) == -1)
454 {
455 mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
456 return -1;
457 }
458
459 /* pretend that the link failed because the target file did already exist. */
460
461 if (!stat_equal(&st_src, &st_target))
462 {
463 mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
464 errno = EEXIST;
465 return -1;
466 }
467#endif
468
469success:
470 /* Unlink the original link.
471 * Should we really ignore the return value here? XXX */
472 if (unlink(src) == -1)
473 {
474 mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
475 }
476
477 return 0;
478}
479
486int mutt_file_rmtree(const char *path)
487{
488 if (!path)
489 return -1;
490
491 struct dirent *de = NULL;
492 struct stat st = { 0 };
493 int rc = 0;
494
495 DIR *dir = mutt_file_opendir(path, MUTT_OPENDIR_NONE);
496 if (!dir)
497 {
498 mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
499 return -1;
500 }
501
502 /* We avoid using the buffer pool for this function, because it
503 * invokes recursively to an unknown depth. */
504 struct Buffer *cur = buf_pool_get();
505
506 while ((de = readdir(dir)))
507 {
508 if ((mutt_str_equal(".", de->d_name)) || (mutt_str_equal("..", de->d_name)))
509 continue;
510
511 buf_printf(cur, "%s/%s", path, de->d_name);
512 /* XXX make nonrecursive version */
513
514 if (stat(buf_string(cur), &st) == -1)
515 {
516 rc = 1;
517 continue;
518 }
519
520 if (S_ISDIR(st.st_mode))
521 rc |= mutt_file_rmtree(buf_string(cur));
522 else
523 rc |= unlink(buf_string(cur));
524 }
525 closedir(dir);
526
527 rc |= rmdir(path);
528
529 buf_pool_release(&cur);
530 return rc;
531}
532
546const char *mutt_file_rotate(const char *path, int count)
547{
548 if (!path)
549 return NULL;
550
551 struct Buffer *old_file = buf_pool_get();
552 struct Buffer *new_file = buf_pool_get();
553
554 /* rotate the old debug logs */
555 for (count -= 2; count >= 0; count--)
556 {
557 buf_printf(old_file, "%s%d", path, count);
558 buf_printf(new_file, "%s%d", path, count + 1);
559 (void) rename(buf_string(old_file), buf_string(new_file));
560 }
561
562 path = buf_strdup(old_file);
563 buf_pool_release(&old_file);
564 buf_pool_release(&new_file);
565
566 return path;
567}
568
577int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
578{
579 if (!path)
580 return -1;
581
582 int fd;
583 struct Buffer *safe_file = buf_pool_get();
584 struct Buffer *safe_dir = buf_pool_get();
585
586 if (flags & O_EXCL)
587 {
588 buf_alloc(safe_file, PATH_MAX);
589 buf_alloc(safe_dir, PATH_MAX);
590
591 if (mkwrapdir(path, safe_file, safe_dir) == -1)
592 {
593 fd = -1;
594 goto cleanup;
595 }
596
597 fd = open(buf_string(safe_file), flags, mode);
598 if (fd < 0)
599 {
600 rmdir(buf_string(safe_dir));
601 goto cleanup;
602 }
603
604 /* NFS and I believe cygwin do not handle movement of open files well */
605 close(fd);
606 if (put_file_in_place(path, buf_string(safe_file), buf_string(safe_dir)) == -1)
607 {
608 fd = -1;
609 goto cleanup;
610 }
611 }
612
613 fd = open(path, flags & ~O_EXCL, 0600);
614 if (fd < 0)
615 goto cleanup;
616
617 /* make sure the file is not symlink */
618 struct stat st_old = { 0 };
619 struct stat st_new = { 0 };
620 if (((lstat(path, &st_old) < 0) || (fstat(fd, &st_new) < 0)) ||
621 !stat_equal(&st_old, &st_new))
622 {
623 close(fd);
624 fd = -1;
625 goto cleanup;
626 }
627
628cleanup:
629 buf_pool_release(&safe_file);
630 buf_pool_release(&safe_dir);
631
632 return fd;
633}
634
642DIR *mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
643{
644 if ((mode == MUTT_OPENDIR_CREATE) && (mutt_file_mkdir(path, S_IRWXU) == -1))
645 {
646 return NULL;
647 }
648 errno = 0;
649 return opendir(path);
650}
651
666FILE *mutt_file_fopen_full(const char *path, const char *mode, const mode_t perms,
667 const char *file, int line, const char *func)
668{
669 if (!path || !mode)
670 return NULL;
671
672 FILE *fp = NULL;
673 if (mode[0] == 'w')
674 {
675 uint32_t flags = O_CREAT | O_EXCL | O_NOFOLLOW;
676
677 if (mode[1] == '+')
678 flags |= O_RDWR;
679 else
680 flags |= O_WRONLY;
681
682 int fd = mutt_file_open(path, flags, perms);
683 if (fd >= 0)
684 {
685 fp = fdopen(fd, mode);
686 }
687 }
688 else
689 {
690 fp = fopen(path, mode);
691 }
692
693 if (fp)
694 {
695 MuttLogger(0, file, line, func, LL_DEBUG2, "File opened (fd=%d): %s\n",
696 fileno(fp), path);
697 }
698 else
699 {
700 MuttLogger(0, file, line, func, LL_DEBUG2, "File open failed (errno=%d, %s): %s\n",
701 errno, strerror(errno), path);
702 }
703
704 return fp;
705}
706
712void mutt_file_sanitize_filename(char *path, bool slash)
713{
714 if (!path)
715 return;
716
717 size_t size = strlen(path);
718
719 wchar_t c;
720 mbstate_t mbstate = { 0 };
721 for (size_t consumed; size && (consumed = mbrtowc(&c, path, size, &mbstate));
722 size -= consumed, path += consumed)
723 {
724 switch (consumed)
725 {
727 mbstate = (mbstate_t){ 0 };
728 consumed = 1;
729 memset(path, '_', consumed);
730 break;
731
733 consumed = size;
734 memset(path, '_', consumed);
735 break;
736
737 default:
738 if ((slash && (c == L'/')) || ((c <= 0x7F) && !strchr(FilenameSafeChars, c)))
739 {
740 memset(path, '_', consumed);
741 }
742 break;
743 }
744 }
745}
746
754int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
755{
756 if (!dest || !src)
757 return -1;
758
759 buf_reset(dest);
760 while (*src != '\0')
761 {
762 if (strchr(RxSpecialChars, *src))
763 buf_addch(dest, '\\');
764 buf_addch(dest, *src++);
765 }
766
767 return 0;
768}
769
778bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
779{
780 if (!fp)
781 {
782 return false;
783 }
784
785 if (fseeko(fp, offset, whence) != 0)
786 {
787 mutt_perror(_("Failed to seek file: %s"), strerror(errno));
788 return false;
789 }
790
791 return true;
792}
793
808char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
809{
810 if (!size || !fp)
811 return NULL;
812
813 size_t offset = 0;
814 char *ch = NULL;
815
816 if (!line)
817 {
818 *size = 256;
819 line = mutt_mem_malloc(*size);
820 }
821
822 while (true)
823 {
824 if (!fgets(line + offset, *size - offset, fp))
825 {
826 FREE(&line);
827 return NULL;
828 }
829 ch = strchr(line + offset, '\n');
830 if (ch)
831 {
832 if (line_num)
833 (*line_num)++;
834 if (flags & MUTT_RL_EOL)
835 return line;
836 *ch = '\0';
837 if ((ch > line) && (*(ch - 1) == '\r'))
838 *--ch = '\0';
839 if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
840 return line;
841 offset = ch - line - 1;
842 }
843 else
844 {
845 int c;
846 c = getc(fp); /* This is kind of a hack. We want to know if the
847 char at the current point in the input stream is EOF.
848 feof() will only tell us if we've already hit EOF, not
849 if the next character is EOF. So, we need to read in
850 the next character and manually check if it is EOF. */
851 if (c == EOF)
852 {
853 /* The last line of fp isn't \n terminated */
854 if (line_num)
855 (*line_num)++;
856 return line;
857 }
858 else
859 {
860 ungetc(c, fp); /* undo our damage */
861 /* There wasn't room for the line -- increase "line" */
862 offset = *size - 1; /* overwrite the terminating 0 */
863 *size += 256;
864 mutt_mem_realloc(&line, *size);
865 }
866 }
867 }
868}
869
889bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
890{
891 if (!iter)
892 return false;
893
894 char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
895 if (!p)
896 return false;
897 iter->line = p;
898 return true;
899}
900
910bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
911{
912 if (!func || !fp)
913 return false;
914
915 struct MuttFileIter iter = { 0 };
916 while (mutt_file_iter_line(&iter, fp, flags))
917 {
918 if (!(*func)(iter.line, iter.line_num, user_data))
919 {
920 FREE(&iter.line);
921 return false;
922 }
923 }
924 return true;
925}
926
933void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
934{
935 if (!buf || !filename)
936 return;
937
938 buf_reset(buf);
939 if (add_outer)
940 buf_addch(buf, '\'');
941
942 for (; *filename != '\0'; filename++)
943 {
944 if ((*filename == '\'') || (*filename == '`'))
945 {
946 buf_addch(buf, '\'');
947 buf_addch(buf, '\\');
948 buf_addch(buf, *filename);
949 buf_addch(buf, '\'');
950 }
951 else
952 {
953 buf_addch(buf, *filename);
954 }
955 }
956
957 if (add_outer)
958 buf_addch(buf, '\'');
959}
960
974int mutt_file_mkdir(const char *path, mode_t mode)
975{
976 if (!path || (*path == '\0'))
977 {
978 errno = EINVAL;
979 return -1;
980 }
981
982 errno = 0;
983 char tmp_path[PATH_MAX] = { 0 };
984 const size_t len = strlen(path);
985
986 if (len >= sizeof(tmp_path))
987 {
988 errno = ENAMETOOLONG;
989 return -1;
990 }
991
992 struct stat st = { 0 };
993 if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
994 return 0;
995
996 /* Create a mutable copy */
997 mutt_str_copy(tmp_path, path, sizeof(tmp_path));
998
999 for (char *p = tmp_path + 1; *p; p++)
1000 {
1001 if (*p != '/')
1002 continue;
1003
1004 /* Temporarily truncate the path */
1005 *p = '\0';
1006
1007 if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
1008 return -1;
1009
1010 *p = '/';
1011 }
1012
1013 if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
1014 return -1;
1015
1016 return 0;
1017}
1018
1028time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
1029{
1030 if (!fp)
1031 return -1;
1032
1033 struct utimbuf utim = { 0 };
1034 struct stat st2 = { 0 };
1035 time_t mtime;
1036
1037 if (!st)
1038 {
1039 if (stat(fp, &st2) == -1)
1040 return -1;
1041 st = &st2;
1042 }
1043
1044 mtime = st->st_mtime;
1045 if (mtime == mutt_date_now())
1046 {
1047 mtime -= 1;
1048 utim.actime = mtime;
1049 utim.modtime = mtime;
1050 int rc;
1051 do
1052 {
1053 rc = utime(fp, &utim);
1054 } while ((rc == -1) && (errno == EINTR));
1055
1056 if (rc == -1)
1057 return -1;
1058 }
1059
1060 return mtime;
1061}
1062
1068void mutt_file_set_mtime(const char *from, const char *to)
1069{
1070 if (!from || !to)
1071 return;
1072
1073 struct utimbuf utim = { 0 };
1074 struct stat st = { 0 };
1075
1076 if (stat(from, &st) != -1)
1077 {
1078 utim.actime = st.st_mtime;
1079 utim.modtime = st.st_mtime;
1080 utime(to, &utim);
1081 }
1082}
1083
1092{
1093#ifdef HAVE_FUTIMENS
1094 struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
1095 futimens(fd, times);
1096#endif
1097}
1098
1116int mutt_file_chmod_add(const char *path, mode_t mode)
1117{
1118 return mutt_file_chmod_add_stat(path, mode, NULL);
1119}
1120
1139int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1140{
1141 if (!path)
1142 return -1;
1143
1144 struct stat st2 = { 0 };
1145
1146 if (!st)
1147 {
1148 if (stat(path, &st2) == -1)
1149 return -1;
1150 st = &st2;
1151 }
1152 return chmod(path, st->st_mode | mode);
1153}
1154
1173int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1174{
1175 if (!path)
1176 return -1;
1177
1178 struct stat st2 = { 0 };
1179
1180 if (!st)
1181 {
1182 if (stat(path, &st2) == -1)
1183 return -1;
1184 st = &st2;
1185 }
1186 return chmod(path, st->st_mode & ~mode);
1187}
1188
1189#if defined(USE_FCNTL)
1202int mutt_file_lock(int fd, bool excl, bool timeout)
1203{
1204 struct stat st = { 0 }, prev_sb = { 0 };
1205 int count = 0;
1206 int attempt = 0;
1207
1208 struct flock lck = { 0 };
1209 lck.l_type = excl ? F_WRLCK : F_RDLCK;
1210 lck.l_whence = SEEK_SET;
1211
1212 while (fcntl(fd, F_SETLK, &lck) == -1)
1213 {
1214 mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1215 if ((errno != EAGAIN) && (errno != EACCES))
1216 {
1217 mutt_perror("fcntl");
1218 return -1;
1219 }
1220
1221 if (fstat(fd, &st) != 0)
1222 st.st_size = 0;
1223
1224 if (count == 0)
1225 prev_sb = st;
1226
1227 /* only unlock file if it is unchanged */
1228 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1229 {
1230 if (timeout)
1231 mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1232 return -1;
1233 }
1234
1235 prev_sb = st;
1236
1237 mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1238 sleep(1);
1239 }
1240
1241 return 0;
1242}
1243
1250{
1251 struct flock unlockit = { 0 };
1252 unlockit.l_type = F_UNLCK;
1253 unlockit.l_whence = SEEK_SET;
1254 (void) fcntl(fd, F_SETLK, &unlockit);
1255
1256 return 0;
1257}
1258#elif defined(USE_FLOCK)
1271int mutt_file_lock(int fd, bool excl, bool timeout)
1272{
1273 struct stat st = { 0 }, prev_sb = { 0 };
1274 int rc = 0;
1275 int count = 0;
1276 int attempt = 0;
1277
1278 while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1279 {
1280 if (errno != EWOULDBLOCK)
1281 {
1282 mutt_perror("flock");
1283 rc = -1;
1284 break;
1285 }
1286
1287 if (fstat(fd, &st) != 0)
1288 st.st_size = 0;
1289
1290 if (count == 0)
1291 prev_sb = st;
1292
1293 /* only unlock file if it is unchanged */
1294 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1295 {
1296 if (timeout)
1297 mutt_error(_("Timeout exceeded while attempting flock lock"));
1298 rc = -1;
1299 break;
1300 }
1301
1302 prev_sb = st;
1303
1304 mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1305 sleep(1);
1306 }
1307
1308 /* release any other locks obtained in this routine */
1309 if (rc != 0)
1310 {
1311 flock(fd, LOCK_UN);
1312 }
1313
1314 return rc;
1315}
1316
1322int mutt_file_unlock(int fd)
1323{
1324 flock(fd, LOCK_UN);
1325 return 0;
1326}
1327#else
1328#error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1329#endif
1330
1335void mutt_file_unlink_empty(const char *path)
1336{
1337 if (!path)
1338 return;
1339
1340 struct stat st = { 0 };
1341
1342 int fd = open(path, O_RDWR);
1343 if (fd == -1)
1344 return;
1345
1346 if (mutt_file_lock(fd, true, true) == -1)
1347 {
1348 close(fd);
1349 return;
1350 }
1351
1352 if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1353 unlink(path);
1354
1355 mutt_file_unlock(fd);
1356 close(fd);
1357}
1358
1371int mutt_file_rename(const char *oldfile, const char *newfile)
1372{
1373 if (!oldfile || !newfile)
1374 return -1;
1375 if (access(oldfile, F_OK) != 0)
1376 return 1;
1377 if (access(newfile, F_OK) == 0)
1378 return 2;
1379
1380 FILE *fp_old = mutt_file_fopen(oldfile, "r");
1381 if (!fp_old)
1382 return 3;
1383 FILE *fp_new = mutt_file_fopen(newfile, "w");
1384 if (!fp_new)
1385 {
1386 mutt_file_fclose(&fp_old);
1387 return 3;
1388 }
1389 mutt_file_copy_stream(fp_old, fp_new);
1390 mutt_file_fclose(&fp_new);
1391 mutt_file_fclose(&fp_old);
1392 mutt_file_unlink(oldfile);
1393 return 0;
1394}
1395
1406char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1407{
1408 FILE *fp = mutt_file_fopen(file, "r");
1409 if (!fp)
1410 return NULL;
1411
1412 buf = fgets(buf, buflen, fp);
1413 mutt_file_fclose(&fp);
1414
1415 if (!buf)
1416 return NULL;
1417
1418 SKIPWS(buf);
1419 char *start = buf;
1420
1421 while ((*buf != '\0') && !isspace(*buf))
1422 buf++;
1423
1424 *buf = '\0';
1425
1426 return start;
1427}
1428
1436int mutt_file_check_empty(const char *path)
1437{
1438 if (!path)
1439 return -1;
1440
1441 struct stat st = { 0 };
1442 if (stat(path, &st) == -1)
1443 return -1;
1444
1445 return st.st_size == 0;
1446}
1447
1456void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1457{
1458 struct Buffer *tmp = buf_pool_get();
1459
1460 buf_quote_filename(tmp, src, true);
1461 mutt_file_expand_fmt(dest, fmt, buf_string(tmp));
1462 buf_pool_release(&tmp);
1463}
1464
1471void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1472{
1473 if (!dest || !fmt || !src)
1474 return;
1475
1476 const char *p = NULL;
1477 bool found = false;
1478
1479 buf_reset(dest);
1480
1481 for (p = fmt; *p; p++)
1482 {
1483 if (*p == '%')
1484 {
1485 switch (p[1])
1486 {
1487 case '%':
1488 buf_addch(dest, *p++);
1489 break;
1490 case 's':
1491 found = true;
1492 buf_addstr(dest, src);
1493 p++;
1494 break;
1495 default:
1496 buf_addch(dest, *p);
1497 break;
1498 }
1499 }
1500 else
1501 {
1502 buf_addch(dest, *p);
1503 }
1504 }
1505
1506 if (!found)
1507 {
1508 buf_addch(dest, ' ');
1509 buf_addstr(dest, src);
1510 }
1511}
1512
1519long mutt_file_get_size(const char *path)
1520{
1521 if (!path)
1522 return 0;
1523
1524 struct stat st = { 0 };
1525 if (stat(path, &st) != 0)
1526 return 0;
1527
1528 return st.st_size;
1529}
1530
1538{
1539 if (!fp)
1540 return 0;
1541
1542 struct stat st = { 0 };
1543 if (fstat(fileno(fp), &st) != 0)
1544 return 0;
1545
1546 return st.st_size;
1547}
1548
1558{
1559 if (!a || !b)
1560 return 0;
1561 if (a->tv_sec < b->tv_sec)
1562 return -1;
1563 if (a->tv_sec > b->tv_sec)
1564 return 1;
1565
1566 if (a->tv_nsec < b->tv_nsec)
1567 return -1;
1568 if (a->tv_nsec > b->tv_nsec)
1569 return 1;
1570 return 0;
1571}
1572
1579void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1580{
1581 if (!dest || !st)
1582 return;
1583
1584 dest->tv_sec = 0;
1585 dest->tv_nsec = 0;
1586
1587 switch (type)
1588 {
1589 case MUTT_STAT_ATIME:
1590 dest->tv_sec = st->st_atime;
1591#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1592 dest->tv_nsec = st->st_atim.tv_nsec;
1593#endif
1594 break;
1595 case MUTT_STAT_MTIME:
1596 dest->tv_sec = st->st_mtime;
1597#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1598 dest->tv_nsec = st->st_mtim.tv_nsec;
1599#endif
1600 break;
1601 case MUTT_STAT_CTIME:
1602 dest->tv_sec = st->st_ctime;
1603#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1604 dest->tv_nsec = st->st_ctim.tv_nsec;
1605#endif
1606 break;
1607 }
1608}
1609
1619int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1620 struct timespec *b)
1621{
1622 if (!st || !b)
1623 return 0;
1624
1625 struct timespec a = { 0 };
1626
1627 mutt_file_get_stat_timespec(&a, st, type);
1628 return mutt_file_timespec_compare(&a, b);
1629}
1630
1641int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1642 struct stat *st2, enum MuttStatType st2_type)
1643{
1644 if (!st1 || !st2)
1645 return 0;
1646
1647 struct timespec a = { 0 };
1648 struct timespec b = { 0 };
1649
1650 mutt_file_get_stat_timespec(&a, st1, st1_type);
1651 mutt_file_get_stat_timespec(&b, st2, st2_type);
1652 return mutt_file_timespec_compare(&a, &b);
1653}
1654
1660{
1661 struct stat st = { 0 };
1662 int rc = lstat(buf_string(buf), &st);
1663 if ((rc != -1) && S_ISLNK(st.st_mode))
1664 {
1665 char path[PATH_MAX] = { 0 };
1666 if (realpath(buf_string(buf), path))
1667 {
1668 buf_strcpy(buf, path);
1669 }
1670 }
1671}
1672
1679size_t mutt_file_save_str(FILE *fp, const char *str)
1680{
1681 if (!fp)
1682 return 0;
1683
1684 size_t len = mutt_str_len(str);
1685 if (len == 0)
1686 return 0;
1687
1688 return fwrite(str, 1, len, fp);
1689}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:161
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:571
void buf_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:337
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:96
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:1579
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:287
FILE * mutt_file_fopen_full(const char *path, const char *mode, const mode_t perms, const char *file, int line, const char *func)
Call fopen() safely.
Definition: file.c:666
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:933
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:808
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:371
void mutt_file_unlink_empty(const char *path)
Delete a file if it's empty.
Definition: file.c:1335
const char FilenameSafeChars[]
Set of characters <=0x7F that are safe to use in filenames.
Definition: file.c:71
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:1641
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:257
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1406
#define MAX_LOCK_ATTEMPTS
Definition: file.c:73
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:1456
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition: file.c:1091
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:754
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1436
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:974
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition: file.c:1202
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition: file.c:1537
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:712
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1557
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1249
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition: file.c:1028
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:910
DIR * mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
Open a directory.
Definition: file.c:642
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:778
int mutt_file_fclose_full(FILE **fp, const char *file, int line, const char *func)
Close a FILE handle (and NULL the pointer)
Definition: file.c:165
static bool stat_equal(struct stat *st_old, struct stat *st_new)
Compare the struct stat's of two files/dirs.
Definition: file.c:89
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition: file.c:1519
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1371
#define O_NOFOLLOW
Definition: file.c:77
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
Iterate over the lines from an open file pointer.
Definition: file.c:889
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:315
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition: file.c:1139
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:146
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:1471
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1659
static const char RxSpecialChars[]
These characters must be escaped in regular expressions.
Definition: file.c:68
void mutt_file_set_mtime(const char *from, const char *to)
Set the modification time of one file from another.
Definition: file.c:1068
static int mkwrapdir(const char *path, struct Buffer *newfile, struct Buffer *newdir)
Create a temporary directory next to a file name.
Definition: file.c:103
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:1619
const char * mutt_file_rotate(const char *path, int count)
Rotate a set of numbered files.
Definition: file.c:546
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1116
int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
Open a file.
Definition: file.c:577
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:221
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition: file.c:193
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition: file.c:486
size_t mutt_file_save_str(FILE *fp, const char *str)
Save a string to a file.
Definition: file.c:1679
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1173
File management functions.
MuttOpenDirMode
Mode flag for mutt_file_opendir()
Definition: file.h:73
@ MUTT_OPENDIR_CREATE
Create the directory if it doesn't exist.
Definition: file.h:75
@ MUTT_OPENDIR_NONE
Plain opendir()
Definition: file.h:74
#define MUTT_RL_CONT
-continuation
Definition: file.h:41
#define mutt_file_fclose(FP)
Definition: file.h:149
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:148
bool(* mutt_file_map_t)(char *line, int line_num, void *user_data)
Definition: file.h:99
#define MUTT_RL_EOL
don't strip \n / \r\n
Definition: file.h:42
MuttStatType
Flags for mutt_file_get_stat_timespec.
Definition: file.h:63
@ MUTT_STAT_CTIME
File/dir's ctime - creation time.
Definition: file.h:66
@ MUTT_STAT_ATIME
File/dir's atime - last accessed time.
Definition: file.h:64
@ MUTT_STAT_MTIME
File/dir's mtime - last modified time.
Definition: file.h:65
uint8_t ReadLineFlags
Flags for mutt_file_read_line(), e.g. MUTT_RL_CONT.
Definition: file.h:39
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
#define mutt_perror(...)
Definition: logging2.h:93
Logging Dispatcher.
int(*) log_dispatcher_ MuttLogger)
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
@ 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:91
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:115
Memory management wrappers.
#define FREE(x)
Definition: memory.h:45
Conversion between different character encodings.
#define ICONV_BUF_TOO_SMALL
Error value for iconv() - Buffer too small.
Definition: charset.h:107
#define ICONV_ILLEGAL_SEQ
Error value for iconv() - Illegal sequence.
Definition: charset.h:105
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
Message logging.
#define _(a)
Definition: message.h:28
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition: path.c:469
Path manipulation functions.
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:496
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:581
#define PATH_MAX
Definition: mutt.h:42
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:36
char * data
Pointer to data.
Definition: buffer.h:37
State record for mutt_file_iter_line()
Definition: file.h:82
char * line
the line data
Definition: file.h:83
int line_num
line number
Definition: file.h:85
size_t size
allocated size of line data
Definition: file.h:84
Time value with nanosecond precision.
Definition: file.h:51
long tv_nsec
Number of nanosecond, on top.
Definition: file.h:53
time_t tv_sec
Number of seconds since the epoch.
Definition: file.h:52