2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2018 Colin Leroy and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
32 #include "file-utils.h"
34 gboolean prefs_common_get_flush_metadata(void);
35 gboolean prefs_common_get_use_shred(void);
37 static int safe_fclose(FILE *fp)
42 if (fflush(fp) != 0) {
45 if (prefs_common_get_flush_metadata() && fsync(fileno(fp)) != 0) {
55 /* Unlock, then safe-close a file pointer
56 * Safe close is done using fflush + fsync
57 * if the according preference says so.
59 int claws_safe_fclose(FILE *fp)
61 #if HAVE_FGETS_UNLOCKED
64 return safe_fclose(fp);
67 #if HAVE_FGETS_UNLOCKED
69 /* Open a file and locks it once
70 * so subsequent I/O is faster
72 FILE *claws_fopen(const char *file, const char *mode)
74 FILE *fp = fopen(file, mode);
81 FILE *claws_fdopen(int fd, const char *mode)
83 FILE *fp = fdopen(fd, mode);
90 /* Unlocks and close a file pointer
93 int claws_fclose(FILE *fp)
101 #define WEXITSTATUS(x) (x)
104 int claws_unlink(const char *filename)
107 static int found_shred = -1;
108 static const gchar *args[4];
110 if (filename == NULL)
113 if (prefs_common_get_use_shred()) {
114 if (found_shred == -1) {
116 args[0] = g_find_program_in_path("shred");
117 debug_print("found shred: %s\n", args[0]);
118 found_shred = (args[0] != NULL) ? 1:0;
122 if (found_shred == 1) {
123 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
124 if (s.st_nlink == 1) {
127 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
128 NULL, NULL, NULL, NULL, &status, NULL);
129 debug_print("%s %s exited with status %d\n",
130 args[0], filename, WEXITSTATUS(status));
131 if (truncate(filename, 0) < 0)
132 g_warning("couln't truncate: %s", filename);
137 return g_unlink(filename);
140 gint file_strip_crs(const gchar *file)
142 FILE *fp = NULL, *outfp = NULL;
144 gchar *out = get_tmp_file();
148 fp = claws_fopen(file, "rb");
152 outfp = claws_fopen(out, "wb");
158 while (claws_fgets(buf, sizeof (buf), fp) != NULL) {
160 if (claws_fputs(buf, outfp) == EOF) {
168 if (claws_safe_fclose(outfp) == EOF) {
172 if (move_file(out, file, TRUE) < 0)
185 * Append src file body to the tail of dest file.
186 * Now keep_backup has no effects.
188 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
190 FILE *src_fp, *dest_fp;
194 gboolean err = FALSE;
196 if ((src_fp = claws_fopen(src, "rb")) == NULL) {
197 FILE_OP_ERROR(src, "claws_fopen");
201 if ((dest_fp = claws_fopen(dest, "ab")) == NULL) {
202 FILE_OP_ERROR(dest, "claws_fopen");
203 claws_fclose(src_fp);
207 if (change_file_mode_rw(dest_fp, dest) < 0) {
208 FILE_OP_ERROR(dest, "chmod");
209 g_warning("can't change file mode: %s", dest);
212 while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
213 if (n_read < sizeof(buf) && claws_ferror(src_fp))
215 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
216 g_warning("writing to %s failed.", dest);
217 claws_fclose(dest_fp);
218 claws_fclose(src_fp);
224 if (claws_ferror(src_fp)) {
225 FILE_OP_ERROR(src, "claws_fread");
228 claws_fclose(src_fp);
229 if (claws_fclose(dest_fp) == EOF) {
230 FILE_OP_ERROR(dest, "claws_fclose");
242 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
244 FILE *src_fp, *dest_fp;
247 gchar *dest_bak = NULL;
248 gboolean err = FALSE;
250 if ((src_fp = claws_fopen(src, "rb")) == NULL) {
251 FILE_OP_ERROR(src, "claws_fopen");
254 if (is_file_exist(dest)) {
255 dest_bak = g_strconcat(dest, ".bak", NULL);
256 if (rename_force(dest, dest_bak) < 0) {
257 FILE_OP_ERROR(dest, "rename");
258 claws_fclose(src_fp);
264 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
265 FILE_OP_ERROR(dest, "claws_fopen");
266 claws_fclose(src_fp);
268 if (rename_force(dest_bak, dest) < 0)
269 FILE_OP_ERROR(dest_bak, "rename");
275 if (change_file_mode_rw(dest_fp, dest) < 0) {
276 FILE_OP_ERROR(dest, "chmod");
277 g_warning("can't change file mode: %s", dest);
280 while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
281 if (n_read < sizeof(buf) && claws_ferror(src_fp))
283 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
284 g_warning("writing to %s failed.", dest);
285 claws_fclose(dest_fp);
286 claws_fclose(src_fp);
289 if (rename_force(dest_bak, dest) < 0)
290 FILE_OP_ERROR(dest_bak, "rename");
297 if (claws_ferror(src_fp)) {
298 FILE_OP_ERROR(src, "claws_fread");
301 claws_fclose(src_fp);
302 if (claws_safe_fclose(dest_fp) == EOF) {
303 FILE_OP_ERROR(dest, "claws_fclose");
310 if (rename_force(dest_bak, dest) < 0)
311 FILE_OP_ERROR(dest_bak, "rename");
317 if (keep_backup == FALSE && dest_bak)
318 claws_unlink(dest_bak);
325 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
327 if (overwrite == FALSE && is_file_exist(dest)) {
328 g_warning("move_file(): file %s already exists.", dest);
332 if (rename_force(src, dest) == 0) return 0;
334 if (EXDEV != errno) {
335 FILE_OP_ERROR(src, "rename");
339 if (copy_file(src, dest, FALSE) < 0) return -1;
346 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
349 gint bytes_left, to_read;
352 if (fseek(fp, offset, SEEK_SET) < 0) {
358 to_read = MIN(bytes_left, sizeof(buf));
360 while ((n_read = claws_fread(buf, sizeof(gchar), to_read, fp)) > 0) {
361 if (n_read < to_read && claws_ferror(fp))
363 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
366 bytes_left -= n_read;
369 to_read = MIN(bytes_left, sizeof(buf));
372 if (claws_ferror(fp)) {
373 perror("claws_fread");
380 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
383 gboolean err = FALSE;
385 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
386 FILE_OP_ERROR(dest, "claws_fopen");
390 if (change_file_mode_rw(dest_fp, dest) < 0) {
391 FILE_OP_ERROR(dest, "chmod");
392 g_warning("can't change file mode: %s", dest);
395 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
398 if (claws_safe_fclose(dest_fp) == EOF) {
399 FILE_OP_ERROR(dest, "claws_fclose");
404 g_warning("writing to %s failed.", dest);
412 gint canonicalize_file(const gchar *src, const gchar *dest)
414 FILE *src_fp, *dest_fp;
417 gboolean err = FALSE;
418 gboolean last_linebreak = FALSE;
420 if (src == NULL || dest == NULL)
423 if ((src_fp = claws_fopen(src, "rb")) == NULL) {
424 FILE_OP_ERROR(src, "claws_fopen");
428 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
429 FILE_OP_ERROR(dest, "claws_fopen");
430 claws_fclose(src_fp);
434 if (change_file_mode_rw(dest_fp, dest) < 0) {
435 FILE_OP_ERROR(dest, "chmod");
436 g_warning("can't change file mode: %s", dest);
439 while (claws_fgets(buf, sizeof(buf), src_fp) != NULL) {
444 last_linebreak = FALSE;
446 if (buf[len - 1] != '\n') {
447 last_linebreak = TRUE;
448 r = claws_fputs(buf, dest_fp);
449 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
450 r = claws_fputs(buf, dest_fp);
453 r = claws_fwrite(buf, 1, len - 1, dest_fp);
458 r = claws_fputs("\r\n", dest_fp);
462 g_warning("writing to %s failed.", dest);
463 claws_fclose(dest_fp);
464 claws_fclose(src_fp);
470 if (last_linebreak == TRUE) {
471 if (claws_fputs("\r\n", dest_fp) == EOF)
475 if (claws_ferror(src_fp)) {
476 FILE_OP_ERROR(src, "claws_fgets");
479 claws_fclose(src_fp);
480 if (claws_safe_fclose(dest_fp) == EOF) {
481 FILE_OP_ERROR(dest, "claws_fclose");
493 gint canonicalize_file_replace(const gchar *file)
497 tmp_file = get_tmp_file();
499 if (canonicalize_file(file, tmp_file) < 0) {
504 if (move_file(tmp_file, file, TRUE) < 0) {
505 g_warning("can't replace file: %s", file);
506 claws_unlink(tmp_file);
516 gint str_write_to_file(const gchar *str, const gchar *file, gboolean safe)
522 cm_return_val_if_fail(str != NULL, -1);
523 cm_return_val_if_fail(file != NULL, -1);
525 if ((fp = claws_fopen(file, "wb")) == NULL) {
526 FILE_OP_ERROR(file, "claws_fopen");
536 if (claws_fwrite(str, 1, len, fp) != len) {
537 FILE_OP_ERROR(file, "claws_fwrite");
544 r = claws_safe_fclose(fp);
546 r = claws_fclose(fp);
550 FILE_OP_ERROR(file, "claws_fclose");
558 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
565 cm_return_val_if_fail(fp != NULL, NULL);
567 array = g_byte_array_new();
569 while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
570 if (n_read < sizeof(buf) && claws_ferror(fp))
572 g_byte_array_append(array, buf, n_read);
575 if (claws_ferror(fp)) {
576 FILE_OP_ERROR("file stream", "claws_fread");
577 g_byte_array_free(array, TRUE);
582 g_byte_array_append(array, buf, 1);
583 str = (gchar *)array->data;
584 g_byte_array_free(array, FALSE);
586 if (recode && !g_utf8_validate(str, -1, NULL)) {
587 const gchar *src_codeset, *dest_codeset;
589 src_codeset = conv_get_locale_charset_str();
590 dest_codeset = CS_UTF_8;
591 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
599 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
606 struct timeval timeout = {1, 0};
611 cm_return_val_if_fail(file != NULL, NULL);
613 if (g_stat(file, &s) != 0) {
614 FILE_OP_ERROR(file, "stat");
617 if (S_ISDIR(s.st_mode)) {
618 g_warning("%s: is a directory", file);
623 fp = claws_fopen (file, "rb");
625 FILE_OP_ERROR(file, "open");
629 /* test whether the file is readable without blocking */
630 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
632 FILE_OP_ERROR(file, "open");
639 /* allow for one second */
640 err = select(fd+1, &fds, NULL, NULL, &timeout);
641 if (err <= 0 || !FD_ISSET(fd, &fds)) {
643 FILE_OP_ERROR(file, "select");
645 g_warning("%s: doesn't seem readable", file);
651 /* Now clear O_NONBLOCK */
652 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
653 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
657 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
658 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
663 /* get the FILE pointer */
664 fp = claws_fdopen(fd, "rb");
667 FILE_OP_ERROR(file, "claws_fdopen");
668 close(fd); /* if fp isn't NULL, we'll use claws_fclose instead! */
673 str = file_read_stream_to_str_full(fp, recode);
680 gchar *file_read_to_str(const gchar *file)
682 return file_read_to_str_full(file, TRUE);
684 gchar *file_read_stream_to_str(FILE *fp)
686 return file_read_stream_to_str_full(fp, TRUE);
689 gchar *file_read_to_str_no_recode(const gchar *file)
691 return file_read_to_str_full(file, FALSE);
693 gchar *file_read_stream_to_str_no_recode(FILE *fp)
695 return file_read_stream_to_str_full(fp, FALSE);
698 gint rename_force(const gchar *oldpath, const gchar *newpath)
701 if (!is_file_entry_exist(oldpath)) {
705 if (is_file_exist(newpath)) {
706 if (claws_unlink(newpath) < 0)
707 FILE_OP_ERROR(newpath, "unlink");
710 return g_rename(oldpath, newpath);
713 gint copy_dir(const gchar *src, const gchar *dst)
718 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
719 g_warning("failed to open directory: %s", src);
723 if (make_dir(dst) < 0)
726 while ((name = g_dir_read_name(dir)) != NULL) {
727 gchar *old_file, *new_file;
728 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
729 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
730 debug_print("copying: %s -> %s\n", old_file, new_file);
731 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
732 gint r = copy_file(old_file, new_file, TRUE);
739 /* Windows has no symlinks. Or well, Vista seems to
740 have something like this but the semantics might be
741 different. Thus we don't use it under Windows. */
742 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
743 GError *error = NULL;
745 gchar *target = g_file_read_link(old_file, &error);
747 r = symlink(target, new_file);
754 #endif /*G_OS_WIN32*/
755 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
756 gint r = copy_dir(old_file, new_file);
767 gint change_file_mode_rw(FILE *fp, const gchar *file)
770 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
772 return g_chmod(file, S_IRUSR|S_IWUSR);
776 FILE *my_tmpfile(void)
778 const gchar suffix[] = ".XXXXXX";
781 const gchar *progname;
790 tmpdir = get_tmp_dir();
791 tmplen = strlen(tmpdir);
792 progname = g_get_prgname();
793 if (progname == NULL)
794 progname = "claws-mail";
795 proglen = strlen(progname);
796 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
799 memcpy(fname, tmpdir, tmplen);
800 fname[tmplen] = G_DIR_SEPARATOR;
801 memcpy(fname + tmplen + 1, progname, proglen);
802 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
804 fd = g_mkstemp(fname);
811 /* verify that we can write in the file after unlinking */
812 if (write(fd, buf, 1) < 0) {
819 fp = claws_fdopen(fd, "w+b");
830 /* Returns a memory-backed FILE pointer to avoid file I/O
831 * where unnecessary. The "file" size is passed in the len
832 * parameter and is fixed: it's up to the caller to pass a
833 * large enough length to write to the FILE pointer. If the
834 * precise length isn't known, it is possible to ask for more.
836 * In this case, once writing to the pointer is done, the
837 * caller is responsible to call ftruncate(fileno(fp), ftell(fp))
838 * to make sure re-reading the stream will return EOF at the
839 * end of what we wrote.
840 * Otherwise, re-reading the stream will return uninitialized
841 * memory at the end of the stream.
843 FILE *my_tmpfile_with_len(size_t len)
846 FILE *tmpfp = fmemopen(NULL, len, "w+b");
858 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
861 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
862 fd = g_mkstemp(*filename);
865 return claws_fdopen(fd, "w+");
868 FILE *str_open_as_stream(const gchar *str)
873 cm_return_val_if_fail(str != NULL, NULL);
877 fp = my_tmpfile_with_len(len);
880 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
885 if (len == 0) return fp;
887 if (claws_fwrite(str, 1, len, fp) != len) {
888 FILE_OP_ERROR("str_open_as_stream", "claws_fwrite");