NeoMutt  2025-01-09-104-g5de5ef
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 <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 "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
103int mutt_file_fclose_full(FILE **fp, const char *file, int line, const char *func)
104{
105 if (!fp || !*fp)
106 return 0;
107
108 int fd = fileno(*fp);
109 int rc = fclose(*fp);
110
111 if (rc == 0)
112 {
113 MuttLogger(0, file, line, func, LL_DEBUG2, "File closed (fd=%d)\n", fd);
114 }
115 else
116 {
117 MuttLogger(0, file, line, func, LL_DEBUG2, "File close failed (fd=%d), errno=%d, %s\n",
118 fd, errno, strerror(errno));
119 }
120
121 *fp = NULL;
122 return rc;
123}
124
132{
133 if (!fp || !*fp)
134 return 0;
135
136 int rc = 0;
137
138 if (fflush(*fp) || fsync(fileno(*fp)))
139 {
140 int save_errno = errno;
141 rc = -1;
143 errno = save_errno;
144 }
145 else
146 {
147 rc = mutt_file_fclose(fp);
148 }
149
150 return rc;
151}
152
159void mutt_file_unlink(const char *s)
160{
161 if (!s)
162 return;
163
164 struct stat st = { 0 };
165 /* Defend against symlink attacks */
166
167 const bool is_regular_file = (lstat(s, &st) == 0) && S_ISREG(st.st_mode);
168 if (!is_regular_file)
169 return;
170
171 const int fd = open(s, O_RDWR | O_NOFOLLOW);
172 if (fd < 0)
173 return;
174
175 struct stat st2 = { 0 };
176 if ((fstat(fd, &st2) != 0) || !S_ISREG(st2.st_mode) ||
177 (st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
178 {
179 close(fd);
180 return;
181 }
182
183 unlink(s);
184 close(fd);
185}
186
195int mutt_file_copy_bytes(FILE *fp_in, FILE *fp_out, size_t size)
196{
197 if (!fp_in || !fp_out)
198 return -1;
199
200 while (size > 0)
201 {
202 char buf[2048] = { 0 };
203 size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size;
204 chunk = fread(buf, 1, chunk, fp_in);
205 if (chunk < 1)
206 break;
207 if (fwrite(buf, 1, chunk, fp_out) != chunk)
208 return -1;
209
210 size -= chunk;
211 }
212
213 if (fflush(fp_out) != 0)
214 return -1;
215 return 0;
216}
217
225int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
226{
227 if (!fp_in || !fp_out)
228 return -1;
229
230 size_t total = 0;
231 size_t l;
232 char buf[1024] = { 0 };
233
234 while ((l = fread(buf, 1, sizeof(buf), fp_in)) > 0)
235 {
236 if (fwrite(buf, 1, l, fp_out) != l)
237 return -1;
238 total += l;
239 }
240
241 if (fflush(fp_out) != 0)
242 return -1;
243 return total;
244}
245
253int mutt_file_symlink(const char *oldpath, const char *newpath)
254{
255 struct stat st_old = { 0 };
256 struct stat st_new = { 0 };
257
258 if (!oldpath || !newpath)
259 return -1;
260
261 if ((unlink(newpath) == -1) && (errno != ENOENT))
262 return -1;
263
264 if (oldpath[0] == '/')
265 {
266 if (symlink(oldpath, newpath) == -1)
267 return -1;
268 }
269 else
270 {
271 struct Buffer *abs_oldpath = buf_pool_get();
272
273 if (!mutt_path_getcwd(abs_oldpath))
274 {
275 buf_pool_release(&abs_oldpath);
276 return -1;
277 }
278
279 buf_addch(abs_oldpath, '/');
280 buf_addstr(abs_oldpath, oldpath);
281 if (symlink(buf_string(abs_oldpath), newpath) == -1)
282 {
283 buf_pool_release(&abs_oldpath);
284 return -1;
285 }
286
287 buf_pool_release(&abs_oldpath);
288 }
289
290 if ((stat(oldpath, &st_old) == -1) || (stat(newpath, &st_new) == -1) ||
291 !stat_equal(&st_old, &st_new))
292 {
293 unlink(newpath);
294 return -1;
295 }
296
297 return 0;
298}
299
309int mutt_file_safe_rename(const char *src, const char *target)
310{
311 struct stat st_src = { 0 };
312 struct stat st_target = { 0 };
313 int link_errno;
314
315 if (!src || !target)
316 return -1;
317
318 if (link(src, target) != 0)
319 {
320 link_errno = errno;
321
322 /* It is historically documented that link can return -1 if NFS
323 * dies after creating the link. In that case, we are supposed
324 * to use stat to check if the link was created.
325 *
326 * Derek Martin notes that some implementations of link() follow a
327 * source symlink. It might be more correct to use stat() on src.
328 * I am not doing so to minimize changes in behavior: the function
329 * used lstat() further below for 20 years without issue, and I
330 * believe was never intended to be used on a src symlink. */
331 if ((lstat(src, &st_src) == 0) && (lstat(target, &st_target) == 0) &&
332 (stat_equal(&st_src, &st_target) == 0))
333 {
334 mutt_debug(LL_DEBUG1, "link (%s, %s) reported failure: %s (%d) but actually succeeded\n",
335 src, target, strerror(errno), errno);
336 goto success;
337 }
338
339 errno = link_errno;
340
341 /* Coda does not allow cross-directory links, but tells
342 * us it's a cross-filesystem linking attempt.
343 *
344 * However, the Coda rename call is allegedly safe to use.
345 *
346 * With other file systems, rename should just fail when
347 * the files reside on different file systems, so it's safe
348 * to try it here. */
349 mutt_debug(LL_DEBUG1, "link (%s, %s) failed: %s (%d)\n", src, target,
350 strerror(errno), errno);
351
352 /* FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
353 * msdosfs may return EOPNOTSUPP. ENOTSUP can also appear. */
354 if ((errno == EXDEV) || (errno == ENOSYS) || errno == EPERM
355#ifdef ENOTSUP
356 || errno == ENOTSUP
357#endif
358#ifdef EOPNOTSUPP
359 || errno == EOPNOTSUPP
360#endif
361 )
362 {
363 mutt_debug(LL_DEBUG1, "trying rename\n");
364 if (rename(src, target) == -1)
365 {
366 mutt_debug(LL_DEBUG1, "rename (%s, %s) failed: %s (%d)\n", src, target,
367 strerror(errno), errno);
368 return -1;
369 }
370 mutt_debug(LL_DEBUG1, "rename succeeded\n");
371
372 return 0;
373 }
374
375 return -1;
376 }
377
378 /* Remove the stat_equal() check, because it causes problems with maildir
379 * on filesystems that don't properly support hard links, such as sshfs. The
380 * filesystem creates the link, but the resulting file is given a different
381 * inode number by the sshfs layer. This results in an infinite loop
382 * creating links. */
383#if 0
384 /* Stat both links and check if they are equal. */
385 if (lstat(src, &st_src) == -1)
386 {
387 mutt_debug(LL_DEBUG1, "#1 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
388 return -1;
389 }
390
391 if (lstat(target, &st_target) == -1)
392 {
393 mutt_debug(LL_DEBUG1, "#2 can't stat %s: %s (%d)\n", src, strerror(errno), errno);
394 return -1;
395 }
396
397 /* pretend that the link failed because the target file did already exist. */
398
399 if (!stat_equal(&st_src, &st_target))
400 {
401 mutt_debug(LL_DEBUG1, "stat blocks for %s and %s diverge; pretending EEXIST\n", src, target);
402 errno = EEXIST;
403 return -1;
404 }
405#endif
406
407success:
408 /* Unlink the original link.
409 * Should we really ignore the return value here? XXX */
410 if (unlink(src) == -1)
411 {
412 mutt_debug(LL_DEBUG1, "unlink (%s) failed: %s (%d)\n", src, strerror(errno), errno);
413 }
414
415 return 0;
416}
417
424int mutt_file_rmtree(const char *path)
425{
426 if (!path)
427 return -1;
428
429 struct dirent *de = NULL;
430 struct stat st = { 0 };
431 int rc = 0;
432
433 DIR *dir = mutt_file_opendir(path, MUTT_OPENDIR_NONE);
434 if (!dir)
435 {
436 mutt_debug(LL_DEBUG1, "error opening directory %s\n", path);
437 return -1;
438 }
439
440 /* We avoid using the buffer pool for this function, because it
441 * invokes recursively to an unknown depth. */
442 struct Buffer *cur = buf_pool_get();
443
444 while ((de = readdir(dir)))
445 {
446 if ((mutt_str_equal(".", de->d_name)) || (mutt_str_equal("..", de->d_name)))
447 continue;
448
449 buf_printf(cur, "%s/%s", path, de->d_name);
450 /* XXX make nonrecursive version */
451
452 if (stat(buf_string(cur), &st) == -1)
453 {
454 rc = 1;
455 continue;
456 }
457
458 if (S_ISDIR(st.st_mode))
459 rc |= mutt_file_rmtree(buf_string(cur));
460 else
461 rc |= unlink(buf_string(cur));
462 }
463 closedir(dir);
464
465 rc |= rmdir(path);
466
467 buf_pool_release(&cur);
468 return rc;
469}
470
484const char *mutt_file_rotate(const char *path, int count)
485{
486 if (!path)
487 return NULL;
488
489 struct Buffer *old_file = buf_pool_get();
490 struct Buffer *new_file = buf_pool_get();
491
492 /* rotate the old debug logs */
493 for (count -= 2; count >= 0; count--)
494 {
495 buf_printf(old_file, "%s%d", path, count);
496 buf_printf(new_file, "%s%d", path, count + 1);
497 (void) rename(buf_string(old_file), buf_string(new_file));
498 }
499
500 path = buf_strdup(old_file);
501 buf_pool_release(&old_file);
502 buf_pool_release(&new_file);
503
504 return path;
505}
506
515int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
516{
517 if (!path)
518 return -1;
519
520 int fd = open(path, flags & ~O_EXCL, 0600);
521 if (fd < 0)
522 return -1;
523
524 /* make sure the file is not symlink */
525 struct stat st = { 0 };
526 if ((lstat(path, &st) < 0) || S_ISLNK(st.st_mode))
527 {
528 close(fd);
529 return -1;
530 }
531
532 return fd;
533}
534
542DIR *mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
543{
544 if ((mode == MUTT_OPENDIR_CREATE) && (mutt_file_mkdir(path, S_IRWXU) == -1))
545 {
546 return NULL;
547 }
548 errno = 0;
549 return opendir(path);
550}
551
563FILE *mutt_file_fopen_full(const char *path, const char *mode, const mode_t perms,
564 const char *file, int line, const char *func)
565{
566 if (!path || !mode)
567 return NULL;
568
569 FILE *fp = fopen(path, mode);
570 if (fp)
571 {
572 MuttLogger(0, file, line, func, LL_DEBUG2, "File opened (fd=%d): %s\n",
573 fileno(fp), path);
574 }
575 else
576 {
577 MuttLogger(0, file, line, func, LL_DEBUG2, "File open failed (errno=%d, %s): %s\n",
578 errno, strerror(errno), path);
579 }
580
581 return fp;
582}
583
589void mutt_file_sanitize_filename(char *path, bool slash)
590{
591 if (!path)
592 return;
593
594 size_t size = strlen(path);
595
596 wchar_t c;
597 mbstate_t mbstate = { 0 };
598 for (size_t consumed; size && (consumed = mbrtowc(&c, path, size, &mbstate));
599 size -= consumed, path += consumed)
600 {
601 switch (consumed)
602 {
604 mbstate = (mbstate_t) { 0 };
605 consumed = 1;
606 memset(path, '_', consumed);
607 break;
608
610 consumed = size;
611 memset(path, '_', consumed);
612 break;
613
614 default:
615 if ((slash && (c == L'/')) || ((c <= 0x7F) && !strchr(FilenameSafeChars, c)))
616 {
617 memset(path, '_', consumed);
618 }
619 break;
620 }
621 }
622}
623
631int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
632{
633 if (!dest || !src)
634 return -1;
635
636 buf_reset(dest);
637 while (*src != '\0')
638 {
639 if (strchr(RxSpecialChars, *src))
640 buf_addch(dest, '\\');
641 buf_addch(dest, *src++);
642 }
643
644 return 0;
645}
646
655bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
656{
657 if (!fp)
658 {
659 return false;
660 }
661
662 if (fseeko(fp, offset, whence) != 0)
663 {
664 mutt_perror(_("Failed to seek file: %s"), strerror(errno));
665 return false;
666 }
667
668 return true;
669}
670
685char *mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
686{
687 if (!size || !fp)
688 return NULL;
689
690 size_t offset = 0;
691 char *ch = NULL;
692
693 if (!line)
694 {
695 *size = 256;
696 line = MUTT_MEM_MALLOC(*size, char);
697 }
698
699 while (true)
700 {
701 if (!fgets(line + offset, *size - offset, fp))
702 {
703 FREE(&line);
704 return NULL;
705 }
706 ch = strchr(line + offset, '\n');
707 if (ch)
708 {
709 if (line_num)
710 (*line_num)++;
711 if (flags & MUTT_RL_EOL)
712 return line;
713 *ch = '\0';
714 if ((ch > line) && (*(ch - 1) == '\r'))
715 *--ch = '\0';
716 if (!(flags & MUTT_RL_CONT) || (ch == line) || (*(ch - 1) != '\\'))
717 return line;
718 offset = ch - line - 1;
719 }
720 else
721 {
722 int c;
723 c = getc(fp); /* This is kind of a hack. We want to know if the
724 char at the current point in the input stream is EOF.
725 feof() will only tell us if we've already hit EOF, not
726 if the next character is EOF. So, we need to read in
727 the next character and manually check if it is EOF. */
728 if (c == EOF)
729 {
730 /* The last line of fp isn't \n terminated */
731 if (line_num)
732 (*line_num)++;
733 return line;
734 }
735 else
736 {
737 ungetc(c, fp); /* undo our damage */
738 /* There wasn't room for the line -- increase "line" */
739 offset = *size - 1; /* overwrite the terminating 0 */
740 *size += 256;
741 MUTT_MEM_REALLOC(&line, *size, char);
742 }
743 }
744 }
745}
746
766bool mutt_file_iter_line(struct MuttFileIter *iter, FILE *fp, ReadLineFlags flags)
767{
768 if (!iter)
769 return false;
770
771 char *p = mutt_file_read_line(iter->line, &iter->size, fp, &iter->line_num, flags);
772 if (!p)
773 return false;
774 iter->line = p;
775 return true;
776}
777
787bool mutt_file_map_lines(mutt_file_map_t func, void *user_data, FILE *fp, ReadLineFlags flags)
788{
789 if (!func || !fp)
790 return false;
791
792 struct MuttFileIter iter = { 0 };
793 while (mutt_file_iter_line(&iter, fp, flags))
794 {
795 if (!(*func)(iter.line, iter.line_num, user_data))
796 {
797 FREE(&iter.line);
798 return false;
799 }
800 }
801 return true;
802}
803
810void buf_quote_filename(struct Buffer *buf, const char *filename, bool add_outer)
811{
812 if (!buf || !filename)
813 return;
814
815 buf_reset(buf);
816 if (add_outer)
817 buf_addch(buf, '\'');
818
819 for (; *filename != '\0'; filename++)
820 {
821 if ((*filename == '\'') || (*filename == '`'))
822 {
823 buf_addch(buf, '\'');
824 buf_addch(buf, '\\');
825 buf_addch(buf, *filename);
826 buf_addch(buf, '\'');
827 }
828 else
829 {
830 buf_addch(buf, *filename);
831 }
832 }
833
834 if (add_outer)
835 buf_addch(buf, '\'');
836}
837
851int mutt_file_mkdir(const char *path, mode_t mode)
852{
853 if (!path || (*path == '\0'))
854 {
855 errno = EINVAL;
856 return -1;
857 }
858
859 errno = 0;
860 char tmp_path[PATH_MAX] = { 0 };
861 const size_t len = strlen(path);
862
863 if (len >= sizeof(tmp_path))
864 {
865 errno = ENAMETOOLONG;
866 return -1;
867 }
868
869 struct stat st = { 0 };
870 if ((stat(path, &st) == 0) && S_ISDIR(st.st_mode))
871 return 0;
872
873 /* Create a mutable copy */
874 mutt_str_copy(tmp_path, path, sizeof(tmp_path));
875
876 for (char *p = tmp_path + 1; *p; p++)
877 {
878 if (*p != '/')
879 continue;
880
881 /* Temporarily truncate the path */
882 *p = '\0';
883
884 if ((mkdir(tmp_path, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && (errno != EEXIST))
885 return -1;
886
887 *p = '/';
888 }
889
890 if ((mkdir(tmp_path, mode) != 0) && (errno != EEXIST))
891 return -1;
892
893 return 0;
894}
895
905time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
906{
907 if (!fp)
908 return -1;
909
910 struct utimbuf utim = { 0 };
911 struct stat st2 = { 0 };
912 time_t mtime;
913
914 if (!st)
915 {
916 if (stat(fp, &st2) == -1)
917 return -1;
918 st = &st2;
919 }
920
921 mtime = st->st_mtime;
922 if (mtime == mutt_date_now())
923 {
924 mtime -= 1;
925 utim.actime = mtime;
926 utim.modtime = mtime;
927 int rc;
928 do
929 {
930 rc = utime(fp, &utim);
931 } while ((rc == -1) && (errno == EINTR));
932
933 if (rc == -1)
934 return -1;
935 }
936
937 return mtime;
938}
939
945void mutt_file_set_mtime(const char *from, const char *to)
946{
947 if (!from || !to)
948 return;
949
950 struct utimbuf utim = { 0 };
951 struct stat st = { 0 };
952
953 if (stat(from, &st) != -1)
954 {
955 utim.actime = st.st_mtime;
956 utim.modtime = st.st_mtime;
957 utime(to, &utim);
958 }
959}
960
969{
970#ifdef HAVE_FUTIMENS
971 struct timespec times[2] = { { 0, UTIME_NOW }, { 0, UTIME_OMIT } };
972 futimens(fd, times);
973#endif
974}
975
981bool mutt_file_touch(const char *path)
982{
983 FILE *fp = mutt_file_fopen(path, "w");
984 if (!fp)
985 {
986 return false;
987 }
988 mutt_file_fclose(&fp);
989 return true;
990}
991
1009int mutt_file_chmod_add(const char *path, mode_t mode)
1010{
1011 return mutt_file_chmod_add_stat(path, mode, NULL);
1012}
1013
1032int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
1033{
1034 if (!path)
1035 return -1;
1036
1037 struct stat st2 = { 0 };
1038
1039 if (!st)
1040 {
1041 if (stat(path, &st2) == -1)
1042 return -1;
1043 st = &st2;
1044 }
1045 return chmod(path, st->st_mode | mode);
1046}
1047
1066int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
1067{
1068 if (!path)
1069 return -1;
1070
1071 struct stat st2 = { 0 };
1072
1073 if (!st)
1074 {
1075 if (stat(path, &st2) == -1)
1076 return -1;
1077 st = &st2;
1078 }
1079 return chmod(path, st->st_mode & ~mode);
1080}
1081
1082#if defined(USE_FCNTL)
1095int mutt_file_lock(int fd, bool excl, bool timeout)
1096{
1097 struct stat st = { 0 }, prev_sb = { 0 };
1098 int count = 0;
1099 int attempt = 0;
1100
1101 struct flock lck = { 0 };
1102 lck.l_type = excl ? F_WRLCK : F_RDLCK;
1103 lck.l_whence = SEEK_SET;
1104
1105 while (fcntl(fd, F_SETLK, &lck) == -1)
1106 {
1107 mutt_debug(LL_DEBUG1, "fcntl errno %d\n", errno);
1108 if ((errno != EAGAIN) && (errno != EACCES))
1109 {
1110 mutt_perror("fcntl");
1111 return -1;
1112 }
1113
1114 if (fstat(fd, &st) != 0)
1115 st.st_size = 0;
1116
1117 if (count == 0)
1118 prev_sb = st;
1119
1120 /* only unlock file if it is unchanged */
1121 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1122 {
1123 if (timeout)
1124 mutt_error(_("Timeout exceeded while attempting fcntl lock"));
1125 return -1;
1126 }
1127
1128 prev_sb = st;
1129
1130 mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
1131 sleep(1);
1132 }
1133
1134 return 0;
1135}
1136
1143{
1144 struct flock unlockit = { 0 };
1145 unlockit.l_type = F_UNLCK;
1146 unlockit.l_whence = SEEK_SET;
1147 (void) fcntl(fd, F_SETLK, &unlockit);
1148
1149 return 0;
1150}
1151#elif defined(USE_FLOCK)
1164int mutt_file_lock(int fd, bool excl, bool timeout)
1165{
1166 struct stat st = { 0 }, prev_sb = { 0 };
1167 int rc = 0;
1168 int count = 0;
1169 int attempt = 0;
1170
1171 while (flock(fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
1172 {
1173 if (errno != EWOULDBLOCK)
1174 {
1175 mutt_perror("flock");
1176 rc = -1;
1177 break;
1178 }
1179
1180 if (fstat(fd, &st) != 0)
1181 st.st_size = 0;
1182
1183 if (count == 0)
1184 prev_sb = st;
1185
1186 /* only unlock file if it is unchanged */
1187 if ((prev_sb.st_size == st.st_size) && (++count >= (timeout ? MAX_LOCK_ATTEMPTS : 0)))
1188 {
1189 if (timeout)
1190 mutt_error(_("Timeout exceeded while attempting flock lock"));
1191 rc = -1;
1192 break;
1193 }
1194
1195 prev_sb = st;
1196
1197 mutt_message(_("Waiting for flock attempt... %d"), ++attempt);
1198 sleep(1);
1199 }
1200
1201 /* release any other locks obtained in this routine */
1202 if (rc != 0)
1203 {
1204 flock(fd, LOCK_UN);
1205 }
1206
1207 return rc;
1208}
1209
1215int mutt_file_unlock(int fd)
1216{
1217 flock(fd, LOCK_UN);
1218 return 0;
1219}
1220#else
1221#error "You must select a locking mechanism via USE_FCNTL or USE_FLOCK"
1222#endif
1223
1228void mutt_file_unlink_empty(const char *path)
1229{
1230 if (!path)
1231 return;
1232
1233 struct stat st = { 0 };
1234
1235 int fd = open(path, O_RDWR);
1236 if (fd == -1)
1237 return;
1238
1239 if (mutt_file_lock(fd, true, true) == -1)
1240 {
1241 close(fd);
1242 return;
1243 }
1244
1245 if ((fstat(fd, &st) == 0) && (st.st_size == 0))
1246 unlink(path);
1247
1248 mutt_file_unlock(fd);
1249 close(fd);
1250}
1251
1264int mutt_file_rename(const char *oldfile, const char *newfile)
1265{
1266 if (!oldfile || !newfile)
1267 return -1;
1268 if (access(oldfile, F_OK) != 0)
1269 return 1;
1270 if (access(newfile, F_OK) == 0)
1271 return 2;
1272
1273 FILE *fp_old = mutt_file_fopen(oldfile, "r");
1274 if (!fp_old)
1275 return 3;
1276 FILE *fp_new = mutt_file_fopen(newfile, "w");
1277 if (!fp_new)
1278 {
1279 mutt_file_fclose(&fp_old);
1280 return 3;
1281 }
1282 mutt_file_copy_stream(fp_old, fp_new);
1283 mutt_file_fclose(&fp_new);
1284 mutt_file_fclose(&fp_old);
1285 mutt_file_unlink(oldfile);
1286 return 0;
1287}
1288
1299char *mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
1300{
1301 FILE *fp = mutt_file_fopen(file, "r");
1302 if (!fp)
1303 return NULL;
1304
1305 buf = fgets(buf, buflen, fp);
1306 mutt_file_fclose(&fp);
1307
1308 if (!buf)
1309 return NULL;
1310
1311 SKIPWS(buf);
1312 char *start = buf;
1313
1314 while ((*buf != '\0') && !isspace(*buf))
1315 buf++;
1316
1317 *buf = '\0';
1318
1319 return start;
1320}
1321
1329int mutt_file_check_empty(const char *path)
1330{
1331 if (!path)
1332 return -1;
1333
1334 struct stat st = { 0 };
1335 if (stat(path, &st) == -1)
1336 return -1;
1337
1338 return st.st_size == 0;
1339}
1340
1349void buf_file_expand_fmt_quote(struct Buffer *dest, const char *fmt, const char *src)
1350{
1351 struct Buffer *tmp = buf_pool_get();
1352
1353 buf_quote_filename(tmp, src, true);
1354 mutt_file_expand_fmt(dest, fmt, buf_string(tmp));
1355 buf_pool_release(&tmp);
1356}
1357
1364void mutt_file_expand_fmt(struct Buffer *dest, const char *fmt, const char *src)
1365{
1366 if (!dest || !fmt || !src)
1367 return;
1368
1369 const char *p = NULL;
1370 bool found = false;
1371
1372 buf_reset(dest);
1373
1374 for (p = fmt; *p; p++)
1375 {
1376 if (*p == '%')
1377 {
1378 switch (p[1])
1379 {
1380 case '%':
1381 buf_addch(dest, *p++);
1382 break;
1383 case 's':
1384 found = true;
1385 buf_addstr(dest, src);
1386 p++;
1387 break;
1388 default:
1389 buf_addch(dest, *p);
1390 break;
1391 }
1392 }
1393 else
1394 {
1395 buf_addch(dest, *p);
1396 }
1397 }
1398
1399 if (!found)
1400 {
1401 buf_addch(dest, ' ');
1402 buf_addstr(dest, src);
1403 }
1404}
1405
1412long mutt_file_get_size(const char *path)
1413{
1414 if (!path)
1415 return 0;
1416
1417 struct stat st = { 0 };
1418 if (stat(path, &st) != 0)
1419 return 0;
1420
1421 return st.st_size;
1422}
1423
1431{
1432 if (!fp)
1433 return 0;
1434
1435 struct stat st = { 0 };
1436 if (fstat(fileno(fp), &st) != 0)
1437 return 0;
1438
1439 return st.st_size;
1440}
1441
1450int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
1451{
1452 if (!a || !b)
1453 return 0;
1454 if (a->tv_sec < b->tv_sec)
1455 return -1;
1456 if (a->tv_sec > b->tv_sec)
1457 return 1;
1458
1459 if (a->tv_nsec < b->tv_nsec)
1460 return -1;
1461 if (a->tv_nsec > b->tv_nsec)
1462 return 1;
1463 return 0;
1464}
1465
1472void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
1473{
1474 if (!dest || !st)
1475 return;
1476
1477 dest->tv_sec = 0;
1478 dest->tv_nsec = 0;
1479
1480 switch (type)
1481 {
1482 case MUTT_STAT_ATIME:
1483 dest->tv_sec = st->st_atime;
1484#ifdef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
1485 dest->tv_nsec = st->st_atim.tv_nsec;
1486#endif
1487 break;
1488 case MUTT_STAT_MTIME:
1489 dest->tv_sec = st->st_mtime;
1490#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
1491 dest->tv_nsec = st->st_mtim.tv_nsec;
1492#endif
1493 break;
1494 case MUTT_STAT_CTIME:
1495 dest->tv_sec = st->st_ctime;
1496#ifdef HAVE_STRUCT_STAT_ST_CTIM_TV_NSEC
1497 dest->tv_nsec = st->st_ctim.tv_nsec;
1498#endif
1499 break;
1500 }
1501}
1502
1512int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type,
1513 struct timespec *b)
1514{
1515 if (!st || !b)
1516 return 0;
1517
1518 struct timespec a = { 0 };
1519
1520 mutt_file_get_stat_timespec(&a, st, type);
1521 return mutt_file_timespec_compare(&a, b);
1522}
1523
1534int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type,
1535 struct stat *st2, enum MuttStatType st2_type)
1536{
1537 if (!st1 || !st2)
1538 return 0;
1539
1540 struct timespec a = { 0 };
1541 struct timespec b = { 0 };
1542
1543 mutt_file_get_stat_timespec(&a, st1, st1_type);
1544 mutt_file_get_stat_timespec(&b, st2, st2_type);
1545 return mutt_file_timespec_compare(&a, &b);
1546}
1547
1553{
1554 struct stat st = { 0 };
1555 int rc = lstat(buf_string(buf), &st);
1556 if ((rc != -1) && S_ISLNK(st.st_mode))
1557 {
1558 char path[PATH_MAX] = { 0 };
1559 if (realpath(buf_string(buf), path))
1560 {
1561 buf_strcpy(buf, path);
1562 }
1563 }
1564}
1565
1572size_t mutt_file_save_str(FILE *fp, const char *str)
1573{
1574 if (!fp)
1575 return 0;
1576
1577 size_t len = mutt_str_len(str);
1578 if (len == 0)
1579 return 0;
1580
1581 return fwrite(str, 1, len, fp);
1582}
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
Time and date handling routines.
bool mutt_file_touch(const char *path)
Make sure a file exists.
Definition: file.c:981
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:1472
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:225
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:563
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:810
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:685
int mutt_file_safe_rename(const char *src, const char *target)
NFS-safe renaming of files.
Definition: file.c:309
void mutt_file_unlink_empty(const char *path)
Delete a file if it's empty.
Definition: file.c:1228
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:1534
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:195
char * mutt_file_read_keyword(const char *file, char *buf, size_t buflen)
Read a keyword from a file.
Definition: file.c:1299
#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:1349
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition: file.c:968
int mutt_file_sanitize_regex(struct Buffer *dest, const char *src)
Escape any regex-magic characters in a string.
Definition: file.c:631
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition: file.c:1329
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition: file.c:851
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition: file.c:1095
long mutt_file_get_size_fp(FILE *fp)
Get the size of a file.
Definition: file.c:1430
void mutt_file_sanitize_filename(char *path, bool slash)
Replace unsafe characters in a filename.
Definition: file.c:589
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition: file.c:1450
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition: file.c:1142
time_t mutt_file_decrease_mtime(const char *fp, struct stat *st)
Decrease a file's modification time by 1 second.
Definition: file.c:905
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:787
DIR * mutt_file_opendir(const char *path, enum MuttOpenDirMode mode)
Open a directory.
Definition: file.c:542
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:655
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:103
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:1412
int mutt_file_rename(const char *oldfile, const char *newfile)
Rename a file.
Definition: file.c:1264
#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:766
int mutt_file_symlink(const char *oldpath, const char *newpath)
Create a symlink.
Definition: file.c:253
int mutt_file_chmod_add_stat(const char *path, mode_t mode, struct stat *st)
Add permissions to a file.
Definition: file.c:1032
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:1364
void mutt_file_resolve_symlink(struct Buffer *buf)
Resolve a symlink in place.
Definition: file.c:1552
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:945
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:1512
const char * mutt_file_rotate(const char *path, int count)
Rotate a set of numbered files.
Definition: file.c:484
int mutt_file_chmod_add(const char *path, mode_t mode)
Add permissions to a file.
Definition: file.c:1009
int mutt_file_open(const char *path, uint32_t flags, mode_t mode)
Open a file.
Definition: file.c:515
void mutt_file_unlink(const char *s)
Delete a file, carefully.
Definition: file.c:159
int mutt_file_fsync_close(FILE **fp)
Flush the data, before closing a file (and NULL the pointer)
Definition: file.c:131
int mutt_file_rmtree(const char *path)
Recursively remove a directory.
Definition: file.c:424
size_t mutt_file_save_str(FILE *fp, const char *str)
Save a string to a file.
Definition: file.c:1572
int mutt_file_chmod_rm_stat(const char *path, mode_t mode, struct stat *st)
Remove permissions from a file.
Definition: file.c:1066
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:93
#define mutt_message(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
#define mutt_perror(...)
Definition: logging2.h:94
Logging Dispatcher.
int(*) log_dispatcher_ MuttLogger)
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:45
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
Memory management wrappers.
#define FREE(x)
Definition: memory.h:55
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:41
Conversion between different character encodings.
#define ICONV_BUF_TOO_SMALL
Error value for iconv() - Buffer too small.
Definition: charset.h:98
#define ICONV_ILLEGAL_SEQ
Error value for iconv() - Illegal sequence.
Definition: charset.h:96
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:476
Path manipulation functions.
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:661
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:497
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:582
#define PATH_MAX
Definition: mutt.h:42
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
A global pool of Buffers.
String manipulation functions.
#define SKIPWS(ch)
Definition: string2.h:45
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