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