NeoMutt  2025-12-11-694-ga89709
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1
28
38
39#include "config.h"
40#include <errno.h>
41#include <fcntl.h>
42#include <limits.h>
43#include <stdbool.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <sys/stat.h>
48#include <time.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 "ctype2.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
74#define MAX_LOCK_ATTEMPTS 5
75
85static bool stat_equal(struct stat *st_old, struct stat *st_new)
86{
87 return (st_old->st_dev == st_new->st_dev) && (st_old->st_ino == st_new->st_ino) &&
88 (st_old->st_rdev == st_new->st_rdev);
89}
90
100int mutt_file_fclose_full(FILE **fp, const char *file, int line, const char *func)
101{
102 if (!fp || !*fp)
103 return 0;
104
105 int fd = fileno(*fp);
106 int rc = fclose(*fp);
107
108 if (rc == 0)
109 {
110 MuttLogger(0, file, line, func, LL_DEBUG2, "File closed (fd=%d)\n", fd);
111 }
112 else
113 {
114 MuttLogger(0, file, line, func, LL_DEBUG2, "File close failed (fd=%d), errno=%d, %s\n",
115 fd, errno, strerror(errno));
116 }
117
118 *fp = NULL;
119 return rc;
120}
121
129{
130 if (!fp || !*fp)
131 return 0;
132
133 int rc = 0;
134
135 if (fflush(*fp) || fsync(fileno(*fp)))
136 {
137 int save_errno = errno;
138 rc = -1;
140 errno = save_errno;
141 }
142 else
143 {
144 rc = mutt_file_fclose(fp);
145 }
146
147 return rc;
148}
149
156void mutt_file_unlink(const char *s)
157{
158 if (!s)
159 return;
160
161 struct stat st = { 0 };
162 /* Defend against symlink attacks */
163
164 const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode);
165 if (!is_regular_file)
166 return;
167
168 const int fd = open(s, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
169 if (fd < 0)
170 return;
171
172 struct stat st2 = { 0 };
173 if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) ||
174 (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
175 {
176 close(fd);
177 return;
178 }
179
180 unlink(s);
181 close(fd);
182}
183
192int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
193{
194 if (!fp_in || !fp_out)
195 return -1;
196
197 char buf[2048];
198 while (size > 0)
199 {
200 size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
201 chunk = fread(buf, 1, chunk, fp_in);
202 if (chunk < 1)
203 break;
204 if (fwrite(buf, 1, chunk, fp_out) != chunk)
205 return -1;
206
207 size -= chunk;
208 }
209
210 if (ferror(fp_in))
211 return -1;
212 if (fflush(fp_out) != 0)
213 return -1;
214 return 0;
215}
216
224int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
225{
226 if (!fp_in || !fp_out)
227 return -1;
228
229 size_t total = 0;
230 size_t l;
231 char buf[2048] = { 0 };
232
233 while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0)
234 {
235 if (fwrite(buf, 1, l, fp_out) != l)
236 return -1;
237 total += l;
238 }
239
240 if (ferror(fp_in))
241 return -1;
242 if (fflush(fp_out) != 0)
243 return -1;
244 return total;
245}
246
254int mutt_file_symlink(const char *oldpath, const char *newpath)
255{
256 struct stat st_old = { 0 };
257 struct stat st_new = { 0 };
258
259 if (!oldpath || !newpath)
260 return -1;
261
262 if ((unlink(newpath) == -1) && (errno != ENOENT))
263 return -1;
264
265 if (oldpath[0] == '/')
266 {
267 if (symlink(oldpath, newpath) == -1)
268 return -1;
269 }
270 else
271 {
272 struct Buffer *abs_oldpath = buf_pool_get();
273
274 if (!mutt_path_getcwd(abs_oldpath))
275 {
276 buf_pool_release(&abs_oldpath);
277 return -1;
278 }
279
280 buf_addch(abs_oldpath, '/');
281 buf_addstr(abs_oldpath, oldpath);
282 if (symlink(buf_string(abs_oldpath), newpath) == -1)
283 {
284 buf_pool_release(&abs_oldpath);
285 return -1;
286 }
287
288 buf_pool_release(&abs_oldpath);
289 }
290
291 if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) ||
292 !stat_equal(&st_old, &st_new))
293 {
294 unlink(newpath);
295 return -1;
296 }
297
298 return 0;
299}
300
310int mutt_file_safe_rename(const char *src, const char *target)
311{
312 struct stat st_src = { 0 };
313 struct stat st_target = { 0 };
314 int link_errno;
315
316 if (!src || !target)
317 return -1;
318
319 if (link(src, target) != 0)
320 {
321 link_errno = errno;
322
323 /* It is historically documented that link can return -1 if NFS
324 * dies after creating the link. In that case, we are supposed
325 * to use stat to check if the link was created.
326 *
327 * Derek Martin notes that some implementations of link() follow a
328 * source symlink. It might be more correct to use stat() on src.
329 * I am not doing so to minimize changes in behavior: the function
330 * used lstat() further below for 20 years without issue, and I
331 * believe was never intended to be used on a src symlink. */
332 if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) &&
333 (stat_equal(&st_src, &st_target) == 0))
334 {
335 mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
336 src, target, strerror(errno), errno);
337 goto success;
338 }
339
340 errno = link_errno;
341
342 /* Coda does not allow cross-directory links, but tells
343 * us it's a cross-filesystem linking attempt.
344 *
345 * However, the Coda rename call is allegedly safe to use.
346 *
347 * With other file systems, rename should just fail when
348 * the files reside on different file systems, so it's safe
349 * to try it here. */
350 mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
351 strerror(errno), errno);
352
353 /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
354 * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */
355 if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
356#ifdef ENOTSUP
357 || errno == ENOTSUP
358#endif
359#ifdef EOPNOTSUPP
360 || errno == EOPNOTSUPP
361#endif
362 )
363 {
364 mutt_debug(LL_DEBUG1, "trying rename\n");
365 if (rename(src, target) == -1)
366 {
367 mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
368 strerror(errno), errno);
369 return -1;
370 }
371 mutt_debug(LL_DEBUG1, "rename succeeded\n");
372
373 return 0;
374 }
375
376 return -1;
377 }
378
379 /* Remove the stat_equal() check, because it causes problems with maildir
380 * on filesystems that don't properly support hard links, such as sshfs. The
381 * filesystem creates the link, but the resulting file is given a different
382 * inode number by the sshfs layer. This results in an infinite loop
383 * creating links. */
384#if 0
385 /* Stat both links and check if they are equal. */
386 if (lstat(src, &st_src) == -1)
387 {
388 mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
389 return -1;
390 }
391
392 if (lstat(target, &st_target) == -1)
393 {
394 mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
395 return -1;
396 }
397
398 /* pretend that the link failed because the target file did already exist. */
399
400 if (!stat_equal(&st_src, &st_target))
401 {
402 mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
403 errno = EEXIST;
404 return -1;
405 }
406#endif
407
408success:
409 /* Unlink the original link.
410 * Should we really ignore the return value here? XXX */
411 if (unlink(src) == -1)
412 {
413 mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
414 }
415
416 return 0;
417}
418
425int mutt_file_rmtree(const char *path)
426{
427 if (!path)
428 return -1;
429
430 struct dirent *de = NULL;
431 struct stat st = { 0 };
432 int rc = 0;
433
434 DIR *dir = mutt_file_opendir(path, MUTT_OPENDIR_NONE);
435 if (!dir)
436 {
437 mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
438 return -1;
439 }
440
441 /* We avoid using the buffer pool for this function, because it
442 * invokes recursively to an unknown depth. */
443 struct Buffer *cur = buf_pool_get();
444
445 while ((de = readdir(dir)))
446 {
447 if ((mutt_str_equal(".", de->d_name)) || (mutt_str_equal("..", de->d_name)))
448 continue;
449
450 buf_printf(cur, "%s/%s", path, de->d_name);
451 /* XXX make nonrecursive version */
452
453 if (stat(buf_string(cur), &st) == -1)
454 {
455 rc = 1;
456 continue;
457 }
458
459 if (S_ISDIR(st.st_mode))
460 rc |= mutt_file_rmtree(buf_string(cur));
461 else
462 rc |= unlink(buf_string(cur));
463 }
464 closedir(dir);
465
466 rc |= rmdir(path);
467
468 buf_pool_release(&cur);
469 return rc;
470}
471
485const char *mutt_file_rotate(const char *path, int count)
486{
487 if (!path)
488 return NULL;
489
490 struct Buffer *old_file = buf_pool_get();
491 struct Buffer *new_file = buf_pool_get();
492
493 /* rotate the old debug logs */
494 for (count -= 2; count >= 0; count--)
495 {
496 buf_printf(old_file, "%s%d", path, count);
497 buf_printf(new_file, "%s%d", path, count + 1);
498 (void) rename(buf_string(old_file), buf_string(new_file));
499 }
500
501 path = buf_strdup(old_file);
502 buf_pool_release(&old_file);
503 buf_pool_release(&new_file);
504
505 return path;
506}
507
516int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
517{
518 if (!path)
519 return -1;
520
521 int fd = open(path, flags | O_NOFOLLOW | O_CLOEXEC, mode);
522 if (fd < 0)
523 return -1;
524
525 return fd;
526}
527
535DIR *mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
536{
537 if ((mode == MUTT_OPENDIR_CREATE) && (mutt_file_mkdir(path, S_IRWXU) == -1))
538 {
539 return NULL;
540 }
541 errno = 0;
542 return opendir(path);
543}
544
556FILE *mutt_file_fopen_full(const char *path, const char *mode, const mode_t perms,
557 const char *file, int line, const char *func)
558{
559 if (!path || !mode)
560 return NULL;
561
562 FILE *fp = fopen(path, mode);
563 if (fp)
564 {
565 MuttLogger(0, file, line, func, LL_DEBUG2, "File opened (fd=%d): %s\n",
566 fileno(fp), path);
567 }
568 else
569 {
570 MuttLogger(0, file, line, func, LL_DEBUG2, "File open failed (errno=%d, %s): %s\n",
571 errno, strerror(errno), path);
572 }
573
574 return fp;
575}
576
582void mutt_file_sanitize_filename(char *path, bool slash)
583{
584 if (!path)
585 return;
586
587 size_t size = strlen(path);
588
589 wchar_t c;
590 mbstate_t mbstate = { 0 };
591 for (size_t consumed; size && (consumed = mbrtowc(&c, path, size, &mbstate));
592 size -= consumed, path += consumed)
593 {
594 switch (consumed)
595 {
597 mbstate = (mbstate_t) { 0 };
598 consumed = 1;
599 memset(path, '_', consumed);
600 break;
601
603 consumed = size;
604 memset(path, '_', consumed);
605 break;
606
607 default:
608 if ((slash && (c == L'/')) || ((c <= 0x7F) && !strchr(FilenameSafeChars, c)))
609 {
610 memset(path, '_', consumed);
611 }
612 break;
613 }
614 }
615}
616
624int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
625{
626 if (!dest || !src)
627 return -1;
628
629 buf_reset(dest);
630 while (*src != '\0')
631 {
632 if (strchr(RxSpecialChars, *src))
633 buf_addch(dest, '\\');
634 buf_addch(dest, *src++);
635 }
636
637 return 0;
638}
639
648bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
649{
650 if (!fp)
651 {
652 return false;
653 }
654
655 if (fseeko(fp, offset, whence) != 0)
656 {
657 mutt_perror(_("Failed to seek file: %s"), strerror(errno));
658 return false;
659 }
660
661 return true;
662}
663
678char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
679{
680 if (!size || !fp)
681 return NULL;
682
683 size_t offset = 0;
684 char *ch = NULL;
685
686 if (!line)
687 {
688 *size = 256;
689 line = MUTT_MEM_MALLOC(*size, char);
690 }
691
692 while (true)
693 {
694 if (!fgets(line + offset, *size - offset, fp))
695 {
696 FREE(&line);
697 return NULL;
698 }
699 ch = strchr(line + offset, '\n');
700 if (ch)
701 {
702 if (line_num)
703 (*line_num)++;
704 if (flags & MUTT_RL_EOL)
705 return line;
706 *ch = '\0';
707 if ((ch > line) && (*(ch - 1) == '\r'))
708 *--ch = '\0';
709 if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
710 return line;
711 offset = ch - line - 1;
712 }
713 else
714 {
715 int c;
716 c = getc(fp); /* This is kind of a hack. We want to know if the
717 char at the current point in the input stream is EOF.
718 feof() will only tell us if we've already hit EOF, not
719 if the next character is EOF. So, we need to read in
720 the next character and manually check if it is EOF. */
721 if (c == EOF)
722 {
723 /* The last line of fp isn't \n terminated */
724 if (line_num)
725 (*line_num)++;
726 return line;
727 }
728 else
729 {
730 ungetc(c, fp); /* undo our damage */
731 /* There wasn't room for the line -- increase "line" */
732 offset = *size - 1; /* overwrite the terminating 0 */
733 *size += 256;
734 MUTT_MEM_REALLOC(&line, *size, char);
735 }
736 }
737 }
738}
739
759bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
760{
761 if (!iter)
762 return false;
763
764 char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
765 if (!p)
766 return false;
767 iter->line = p;
768 return true;
769}
770
780bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
781{
782 if (!func || !fp)
783 return false;
784
785 struct MuttFileIter iter = { 0 };
786 while (mutt_file_iter_line(&iter, fp, flags))
787 {
788 if (!(*func)(iter.line, iter.line_num, user_data))
789 {
790 FREE(&iter.line);
791 return false;
792 }
793 }
794 return true;
795}
796
803void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
804{
805 if (!buf || !filename)
806 return;
807
808 buf_reset(buf);
809 if (add_outer)
810 buf_addch(buf, '\'');
811
812 for (; *filename != '\0'; filename++)
813 {
814 if ((*filename == '\'') || (*filename == '`'))
815 {
816 buf_addch(buf, '\'');
817 buf_addch(buf, '\\');
818 buf_addch(buf, *filename);
819 buf_addch(buf, '\'');
820 }
821 else
822 {
823 buf_addch(buf, *filename);
824 }
825 }
826
827 if (add_outer)
828 buf_addch(buf, '\'');
829}
830
844int mutt_file_mkdir(const char *path, mode_t mode)
845{
846 if (!path || (*path == '\0'))
847 {
848 errno = EINVAL;
849 return -1;
850 }
851
852 errno = 0;
853 char tmp_path[PATH_MAX] = { 0 };
854 const size_t len = strlen(path);
855
856 if (len >= sizeof(tmp_path))
857 {
858 errno = ENAMETOOLONG;
859 return -1;
860 }
861
862 struct stat st = { 0 };
863 if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
864 return 0;
865
866 /* Create a mutable copy */
867 mutt_str_copy(tmp_path, path, sizeof(tmp_path));
868
869 for (char *p = tmp_path + 1; *p; p++)
870 {
871 if (*p != '/')
872 continue;
873
874 /* Temporarily truncate the path */
875 *p = '\0';
876
877 if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
878 return -1;
879
880 *p = '/';
881 }
882
883 if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
884 return -1;
885
886 return 0;
887}
888
898time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
899{
900 if (!fp)
901 return -1;
902
903 struct utimbuf utim = { 0 };
904 struct stat st2 = { 0 };
905 time_t mtime;
906
907 if (!st)
908 {
909 if (stat(fp, &st2) == -1)
910 return -1;
911 st = &st2;
912 }
913
914 mtime = st->st_mtime;
915 if (mtime == mutt_date_now())
916 {
917 mtime -= 1;
918 utim.actime = mtime;
919 utim.modtime = mtime;
920 int rc;
921 do
922 {
923 rc = utime(fp, &utim);
924 } while ((rc == -1) && (errno == EINTR));
925
926 if (rc == -1)
927 return -1;
928 }
929
930 return mtime;
931}
932
938void mutt_file_set_mtime(const char *from, const char *to)
939{
940 if (!from || !to)
941 return;
942
943 struct utimbuf utim = { 0 };
944 struct stat st = { 0 };
945
946 if (stat(from, &st) != -1)
947 {
948 utim.actime = st.st_mtime;
949 utim.modtime = st.st_mtime;
950 utime(to, &utim);
951 }
952}
953
962{
963#ifdef HAVE_FUTIMENS
964 struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
965 futimens(fd, times);
966#endif
967}
968
974bool mutt_file_touch(const char *path)
975{
976 FILE *fp = mutt_file_fopen(path, "w");
977 if (!fp)
978 {
979 return false;
980 }
981 mutt_file_fclose(&fp);
982 return true;
983}
984
1002int mutt_file_chmod_add(const char *path, mode_t mode)
1003{
1004 return mutt_file_chmod_add_stat(path, mode, NULL);
1005}
1006
1025int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1026{
1027 if (!path)
1028 return -1;
1029
1030 struct stat st2 = { 0 };
1031
1032 if (!st)
1033 {
1034 if (stat(path, &st2) == -1)
1035 return -1;
1036 st = &st2;
1037 }
1038 return chmod(path, st->st_mode | mode);
1039}
1040
1059int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1060{
1061 if (!path)
1062 return -1;
1063
1064 struct stat st2 = { 0 };
1065
1066 if (!st)
1067 {
1068 if (stat(path, &st2) == -1)
1069 return -1;
1070 st = &st2;
1071 }
1072 return chmod(path, st->st_mode & ~mode);
1073}
1074
1075#if defined(USE_FCNTL)
1088int mutt_file_lock(int fd, bool excl, bool timeout)
1089{
1090 struct stat st = { 0 }, prev_sb = { 0 };
1091 int count = 0;
1092 int attempt = 0;
1093
1094 struct flock lck = { 0 };
1095 lck.l_type = excl ? F_WRLCK : F_RDLCK;
1096 lck.l_whence = SEEK_SET;
1097
1098 while (fcntl(fd, F_SETLK, &lck) == -1)
1099 {
1100 mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1101 if ((errno != EAGAIN) && (errno != EACCES))
1102 {
1103 mutt_perror("fcntl");
1104 return -1;
1105 }
1106
1107 if (fstat(fd, &st) != 0)
1108 st.st_size = 0;
1109
1110 if (count == 0)
1111 prev_sb = st;
1112
1113 /* only unlock file if it is unchanged */
1114 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1115 {
1116 if (timeout)
1117 mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1118 return -1;
1119 }
1120
1121 prev_sb = st;
1122
1123 mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1124 sleep(1);
1125 }
1126
1127 return 0;
1128}
1129
1136{
1137 struct flock unlockit = { 0 };
1138 unlockit.l_type = F_UNLCK;
1139 unlockit.l_whence = SEEK_SET;
1140 (void) fcntl(fd, F_SETLK, &unlockit);
1141
1142 return 0;
1143}
1144#elif defined(USE_FLOCK)
1157int mutt_file_lock(int fd, bool excl, bool timeout)
1158{
1159 struct stat st = { 0 }, prev_sb = { 0 };
1160 int rc = 0;
1161 int count = 0;
1162 int attempt = 0;
1163
1164 while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1165 {
1166 if (errno != EWOULDBLOCK)
1167 {
1168 mutt_perror("flock");
1169 rc = -1;
1170 break;
1171 }
1172
1173 if (fstat(fd, &st) != 0)
1174 st.st_size = 0;
1175
1176 if (count == 0)
1177 prev_sb = st;
1178
1179 /* only unlock file if it is unchanged */
1180 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1181 {
1182 if (timeout)
1183 mutt_error(_("Timeout exceeded while attempting flock lock"));
1184 rc = -1;
1185 break;
1186 }
1187
1188 prev_sb = st;
1189
1190 mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1191 sleep(1);
1192 }
1193
1194 /* release any other locks obtained in this routine */
1195 if (rc != 0)
1196 {
1197 flock(fd, LOCK_UN);
1198 }
1199
1200 return rc;
1201}
1202
1208int mutt_file_unlock(int fd)
1209{
1210 flock(fd, LOCK_UN);
1211 return 0;
1212}
1213#else
1214#error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1215#endif
1216
1221void mutt_file_unlink_empty(const char *path)
1222{
1223 if (!path)
1224 return;
1225
1226 struct stat st = { 0 };
1227
1228 int fd = open(path, O_RDWR | O_NOFOLLOW | O_CLOEXEC);
1229 if (fd == -1)
1230 return;
1231
1232 if (mutt_file_lock(fd, true, true) == -1)
1233 {
1234 close(fd);
1235 return;
1236 }
1237
1238 if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1239 unlink(path);
1240
1241 mutt_file_unlock(fd);
1242 close(fd);
1243}
1244
1257int mutt_file_rename(const char *oldfile, const char *newfile)
1258{
1259 if (!oldfile || !newfile)
1260 return -1;
1261 if (access(oldfile, F_OK) != 0)
1262 return 1;
1263 if (access(newfile, F_OK) == 0)
1264 return 2;
1265
1266 FILE *fp_old = mutt_file_fopen(oldfile, "r");
1267 if (!fp_old)
1268 return 3;
1269 FILE *fp_new = mutt_file_fopen(newfile, "w");
1270 if (!fp_new)
1271 {
1272 mutt_file_fclose(&fp_old);
1273 return 3;
1274 }
1275 if (mutt_file_copy_stream(fp_old, fp_new) == -1)
1276 {
1277 mutt_file_fclose(&fp_new);
1278 mutt_file_fclose(&fp_old);
1279 mutt_file_unlink(newfile);
1280 return 3;
1281 }
1282 mutt_file_fclose(&fp_new);
1283 mutt_file_fclose(&fp_old);
1284 mutt_file_unlink(oldfile);
1285 return 0;
1286}
1287
1298char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1299{
1300 if (!buf || (buflen == 0))
1301 return NULL;
1302
1303 FILE *fp = mutt_file_fopen(file, "r");
1304 if (!fp)
1305 return NULL;
1306
1307 buf = fgets(buf, buflen, fp);
1308 mutt_file_fclose(&fp);
1309
1310 if (!buf)
1311 return NULL;
1312
1313 SKIPWS(buf);
1314 char *start = buf;
1315
1316 while ((*buf != '\0') && !mutt_isspace(*buf))
1317 buf++;
1318
1319 *buf = '\0';
1320
1321 return start;
1322}
1323
1331int mutt_file_check_empty(const char *path)
1332{
1333 if (!path)
1334 return -1;
1335
1336 struct stat st = { 0 };
1337 if (stat(path, &st) == -1)
1338 return -1;
1339
1340 return st.st_size == 0;
1341}
1342
1351void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1352{
1353 struct Buffer *tmp = buf_pool_get();
1354
1355 buf_quote_filename(tmp, src, true);
1356 mutt_file_expand_fmt(dest, fmt, buf_string(tmp));
1357 buf_pool_release(&tmp);
1358}
1359
1366void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1367{
1368 if (!dest || !fmt || !src)
1369 return;
1370
1371 const char *p = NULL;
1372 bool found = false;
1373
1374 buf_reset(dest);
1375
1376 for (p = fmt; *p; p++)
1377 {
1378 if (*p == '%')
1379 {
1380 switch (p[1])
1381 {
1382 case '%':
1383 buf_addch(dest, *p++);
1384 break;
1385 case 's':
1386 found = true;
1387 buf_addstr(dest, src);
1388 p++;
1389 break;
1390 default:
1391 buf_addch(dest, *p);
1392 break;
1393 }
1394 }
1395 else
1396 {
1397 buf_addch(dest, *p);
1398 }
1399 }
1400
1401 if (!found)
1402 {
1403 buf_addch(dest, ' ');
1404 buf_addstr(dest, src);
1405 }
1406}
1407
1414long mutt_file_get_size(const char *path)
1415{
1416 if (!path)
1417 return 0;
1418
1419 struct stat st = { 0 };
1420 if (stat(path, &st) != 0)
1421 return 0;
1422
1423 return st.st_size;
1424}
1425
1433{
1434 if (!fp)
1435 return 0;
1436
1437 struct stat st = { 0 };
1438 if (fstat(fileno(fp), &st) != 0)
1439 return 0;
1440
1441 return st.st_size;
1442}
1443
1452int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
1453{
1454 if (!a || !b)
1455 return 0;
1456 if (a->tv_sec < b->tv_sec)
1457 return -1;
1458 if (a->tv_sec > b->tv_sec)
1459 return 1;
1460
1461 if (a->tv_nsec < b->tv_nsec)
1462 return -1;
1463 if (a->tv_nsec > b->tv_nsec)
1464 return 1;
1465 return 0;
1466}
1467
1474void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1475{
1476 if (!dest || !st)
1477 return;
1478
1479 dest->tv_sec = 0;
1480 dest->tv_nsec = 0;
1481
1482 switch (type)
1483 {
1484 case MUTT_STAT_ATIME:
1485 dest->tv_sec = st->st_atime;
1486#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1487 dest->tv_nsec = st->st_atim.tv_nsec;
1488#endif
1489 break;
1490 case MUTT_STAT_MTIME:
1491 dest->tv_sec = st->st_mtime;
1492#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1493 dest->tv_nsec = st->st_mtim.tv_nsec;
1494#endif
1495 break;
1496 case MUTT_STAT_CTIME:
1497 dest->tv_sec = st->st_ctime;
1498#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1499 dest->tv_nsec = st->st_ctim.tv_nsec;
1500#endif
1501 break;
1502 }
1503}
1504
1514int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1515 struct timespec *b)
1516{
1517 if (!st || !b)
1518 return 0;
1519
1520 struct timespec a = { 0 };
1521
1522 mutt_file_get_stat_timespec(&a, st, type);
1523 return mutt_file_timespec_compare(&a, b);
1524}
1525
1536int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1537 struct stat *st2, enum MuttStatType st2_type)
1538{
1539 if (!st1 || !st2)
1540 return 0;
1541
1542 struct timespec a = { 0 };
1543 struct timespec b = { 0 };
1544
1545 mutt_file_get_stat_timespec(&a, st1, st1_type);
1546 mutt_file_get_stat_timespec(&b, st2, st2_type);
1547 return mutt_file_timespec_compare(&a, &b);
1548}
1549
1555{
1556 struct stat st = { 0 };
1557 int rc = lstat(buf_string(buf), &st);
1558 if ((rc != -1) && S_ISLNK(st.st_mode))
1559 {
1560 char path[PATH_MAX] = { 0 };
1561 if (realpath(buf_string(buf), path))
1562 {
1563 buf_strcpy(buf, path);
1564 }
1565 }
1566}
1567
1574size_t mutt_file_save_str(FILE *fp, const char *str)
1575{
1576 if (!fp)
1577 return 0;
1578
1579 size_t len = mutt_str_len(str);
1580 if (len == 0)
1581 return 0;
1582
1583 return fwrite(str, 1, len, fp);
1584}
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
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
ctype(3) wrapper functions
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition ctype.c:96
Time and date handling routines.
bool mutt_file_touch(const char *path)
Make sure a file exists.
Definition file.c:974
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:1474
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition file.c:224
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:556
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:803
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:678
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition file.c:310
void mutt_file_unlink_empty(const char *path)
Delete a file if it's empty.
Definition file.c:1221
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:1536
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:192
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition file.c:1298
#define MAX_LOCK_ATTEMPTS
Maximum number of attempts to lock a file.
Definition file.c:74
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:1351
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition file.c:961
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition file.c:624
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition file.c:1331
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition file.c:844
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition file.c:1088
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition file.c:1432
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition file.c:582
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition file.c:1452
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition file.c:1135
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition file.c:898
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:780
DIR * mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
Open a directory.
Definition file.c:535
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition file.c:648
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:100
static bool stat_equal(struct stat *st_old, struct stat *st_new)
Compare the struct stat's of two files/dirs.
Definition file.c:85
long mutt_file_get_size(const char *path)
Get the size of a file.
Definition file.c:1414
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition file.c:1257
bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
Iterate over the lines from an open file pointer.
Definition file.c:759
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition file.c:254
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition file.c:1025
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:1366
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition file.c:1554
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:938
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:1514
const char * mutt_file_rotate(const char *path, int count)
Rotate a set of numbered files.
Definition file.c:485
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition file.c:1002
int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
Open a file.
Definition file.c:516
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition file.c:156
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition file.c:128
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition file.c:425
size_t mutt_file_save_str(FILE *fp, const char *str)
Save a string to a file.
Definition file.c:1574
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition file.c:1059
File management functions.
MuttOpenDirMode
Mode flag for mutt_file_opendir()
Definition file.h:62
@ MUTT_OPENDIR_CREATE
Create the directory if it doesn't exist.
Definition file.h:64
@ MUTT_OPENDIR_NONE
Plain opendir()
Definition file.h:63
#define MUTT_RL_CONT
-continuation
Definition file.h:41
#define mutt_file_fclose(FP)
Definition file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
bool(* mutt_file_map_t)(char *line, int line_num, void *user_data)
Definition file.h:88
#define MUTT_RL_EOL
don't strip \n / \r\n
Definition file.h:42
MuttStatType
Flags for mutt_file_get_stat_timespec.
Definition file.h:52
@ MUTT_STAT_CTIME
File/dir's ctime - creation time.
Definition file.h:55
@ MUTT_STAT_ATIME
File/dir's atime - last accessed time.
Definition file.h:53
@ MUTT_STAT_MTIME
File/dir's mtime - last modified time.
Definition file.h:54
uint8_t ReadLineFlags
Flags for mutt_file_read_line(), e.g. MUTT_RL_CONT.
Definition file.h:39
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
#define mutt_perror(...)
Definition logging2.h:95
Logging Dispatcher.
int log_dispatcher_t MuttLogger
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
Memory management wrappers.
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
#define MUTT_MEM_MALLOC(n, type)
Definition memory.h:53
Conversion between different character encodings.
#define ICONV_BUF_TOO_SMALL
Error value for iconv() - Buffer too small.
Definition charset.h:116
#define ICONV_ILLEGAL_SEQ
Error value for iconv() - Illegal sequence.
Definition charset.h:114
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition date.c:457
Message logging.
#define _(a)
Definition message.h:28
const char * mutt_path_getcwd(struct Buffer *cwd)
Get the current working directory.
Definition path.c:476
Path manipulation functions.
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:665
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition string.c:503
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:586
#define PATH_MAX
Definition mutt.h:49
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
A global pool of Buffers.
String manipulation functions.
#define SKIPWS(ch)
Definition string2.h:52
String manipulation buffer.
Definition buffer.h:36
State record for mutt_file_iter_line()
Definition file.h:71
char * line
the line data
Definition file.h:72
int line_num
line number
Definition file.h:74
size_t size
allocated size of line data
Definition file.h:73