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"
29 #define WEXITSTATUS(x) (x)
40 #include "file-utils.h"
42 gboolean
prefs_common_get_flush_metadata(void);
43 gboolean
prefs_common_get_use_shred(void);
45 static int safe_fclose(FILE *fp
)
50 if (fflush(fp
) != 0) {
53 if (prefs_common_get_flush_metadata() && fsync(fileno(fp
)) != 0) {
63 /* Unlock, then safe-close a file pointer
64 * Safe close is done using fflush + fsync
65 * if the according preference says so.
67 int claws_safe_fclose(FILE *fp
)
69 #if HAVE_FGETS_UNLOCKED
72 return safe_fclose(fp
);
75 #if HAVE_FGETS_UNLOCKED
77 /* Open a file and locks it once
78 * so subsequent I/O is faster
80 FILE *claws_fopen(const char *file
, const char *mode
)
82 FILE *fp
= fopen(file
, mode
);
89 FILE *claws_fdopen(int fd
, const char *mode
)
91 FILE *fp
= fdopen(fd
, mode
);
98 /* Unlocks and close a file pointer
101 int claws_fclose(FILE *fp
)
108 int claws_unlink(const char *filename
)
111 static int found_shred
= -1;
112 static const gchar
*args
[4];
114 if (filename
== NULL
)
117 if (prefs_common_get_use_shred()) {
118 if (found_shred
== -1) {
120 args
[0] = g_find_program_in_path("shred");
121 debug_print("found shred: %s\n", args
[0]);
122 found_shred
= (args
[0] != NULL
) ? 1:0;
126 if (found_shred
== 1) {
127 if (g_stat(filename
, &s
) == 0 && S_ISREG(s
.st_mode
)) {
128 if (s
.st_nlink
== 1) {
131 g_spawn_sync(NULL
, (gchar
**)args
, NULL
, 0,
132 NULL
, NULL
, NULL
, NULL
, &status
, NULL
);
133 debug_print("%s %s exited with status %d\n",
134 args
[0], filename
, WEXITSTATUS(status
));
135 if (truncate(filename
, 0) < 0)
136 g_warning("couldn't truncate: %s", filename
);
141 return g_unlink(filename
);
144 gint
file_strip_crs(const gchar
*file
)
146 FILE *fp
= NULL
, *outfp
= NULL
;
148 gchar
*out
= get_tmp_file();
152 fp
= claws_fopen(file
, "rb");
156 outfp
= claws_fopen(out
, "wb");
162 while (claws_fgets(buf
, sizeof (buf
), fp
) != NULL
) {
164 if (claws_fputs(buf
, outfp
) == EOF
) {
172 if (claws_safe_fclose(outfp
) == EOF
) {
176 if (move_file(out
, file
, TRUE
) < 0)
189 * Append src file body to the tail of dest file.
190 * Now keep_backup has no effects.
192 gint
append_file(const gchar
*src
, const gchar
*dest
, gboolean keep_backup
)
194 FILE *src_fp
, *dest_fp
;
198 gboolean err
= FALSE
;
200 if ((src_fp
= claws_fopen(src
, "rb")) == NULL
) {
201 FILE_OP_ERROR(src
, "claws_fopen");
205 if ((dest_fp
= claws_fopen(dest
, "ab")) == NULL
) {
206 FILE_OP_ERROR(dest
, "claws_fopen");
207 claws_fclose(src_fp
);
211 if (change_file_mode_rw(dest_fp
, dest
) < 0) {
212 FILE_OP_ERROR(dest
, "chmod");
213 g_warning("can't change file mode: %s", dest
);
216 while ((n_read
= claws_fread(buf
, sizeof(gchar
), sizeof(buf
), src_fp
)) > 0) {
217 if (n_read
< sizeof(buf
) && claws_ferror(src_fp
))
219 if (claws_fwrite(buf
, 1, n_read
, dest_fp
) < n_read
) {
220 g_warning("writing to %s failed", dest
);
221 claws_fclose(dest_fp
);
222 claws_fclose(src_fp
);
228 if (claws_ferror(src_fp
)) {
229 FILE_OP_ERROR(src
, "claws_fread");
232 claws_fclose(src_fp
);
233 if (claws_fclose(dest_fp
) == EOF
) {
234 FILE_OP_ERROR(dest
, "claws_fclose");
246 gint
copy_file(const gchar
*src
, const gchar
*dest
, gboolean keep_backup
)
248 FILE *src_fp
, *dest_fp
;
251 gchar
*dest_bak
= NULL
;
252 gboolean err
= FALSE
;
254 if ((src_fp
= claws_fopen(src
, "rb")) == NULL
) {
255 FILE_OP_ERROR(src
, "claws_fopen");
258 if (is_file_exist(dest
)) {
259 dest_bak
= g_strconcat(dest
, ".bak", NULL
);
260 if (rename_force(dest
, dest_bak
) < 0) {
261 FILE_OP_ERROR(dest
, "rename");
262 claws_fclose(src_fp
);
268 if ((dest_fp
= claws_fopen(dest
, "wb")) == NULL
) {
269 FILE_OP_ERROR(dest
, "claws_fopen");
270 claws_fclose(src_fp
);
272 if (rename_force(dest_bak
, dest
) < 0)
273 FILE_OP_ERROR(dest_bak
, "rename");
279 if (change_file_mode_rw(dest_fp
, dest
) < 0) {
280 FILE_OP_ERROR(dest
, "chmod");
281 g_warning("can't change file mode: %s", dest
);
284 while ((n_read
= claws_fread(buf
, sizeof(gchar
), sizeof(buf
), src_fp
)) > 0) {
285 if (n_read
< sizeof(buf
) && claws_ferror(src_fp
))
287 if (claws_fwrite(buf
, 1, n_read
, dest_fp
) < n_read
) {
288 g_warning("writing to %s failed", dest
);
289 claws_fclose(dest_fp
);
290 claws_fclose(src_fp
);
291 if (claws_unlink(dest
) < 0)
292 FILE_OP_ERROR(dest
, "claws_unlink");
294 if (rename_force(dest_bak
, dest
) < 0)
295 FILE_OP_ERROR(dest_bak
, "rename");
302 if (claws_ferror(src_fp
)) {
303 FILE_OP_ERROR(src
, "claws_fread");
306 claws_fclose(src_fp
);
307 if (claws_safe_fclose(dest_fp
) == EOF
) {
308 FILE_OP_ERROR(dest
, "claws_fclose");
313 if (claws_unlink(dest
) < 0)
314 FILE_OP_ERROR(dest
, "claws_unlink");
316 if (rename_force(dest_bak
, dest
) < 0)
317 FILE_OP_ERROR(dest_bak
, "rename");
323 if (keep_backup
== FALSE
&& dest_bak
)
324 if (claws_unlink(dest_bak
) < 0)
325 FILE_OP_ERROR(dest_bak
, "claws_unlink");
332 gint
move_file(const gchar
*src
, const gchar
*dest
, gboolean overwrite
)
334 if (overwrite
== FALSE
&& is_file_exist(dest
)) {
335 g_warning("move_file(): file %s already exists", dest
);
339 if (rename_force(src
, dest
) == 0) return 0;
341 if (EXDEV
!= errno
) {
342 FILE_OP_ERROR(src
, "rename");
346 if (copy_file(src
, dest
, FALSE
) < 0) return -1;
353 gint
copy_file_part_to_fp(FILE *fp
, off_t offset
, size_t length
, FILE *dest_fp
)
356 gint bytes_left
, to_read
;
359 if (fseek(fp
, offset
, SEEK_SET
) < 0) {
365 to_read
= MIN(bytes_left
, sizeof(buf
));
367 while ((n_read
= claws_fread(buf
, sizeof(gchar
), to_read
, fp
)) > 0) {
368 if (n_read
< to_read
&& claws_ferror(fp
))
370 if (claws_fwrite(buf
, 1, n_read
, dest_fp
) < n_read
) {
373 bytes_left
-= n_read
;
376 to_read
= MIN(bytes_left
, sizeof(buf
));
379 if (claws_ferror(fp
)) {
380 perror("claws_fread");
387 gint
copy_file_part(FILE *fp
, off_t offset
, size_t length
, const gchar
*dest
)
390 gboolean err
= FALSE
;
392 if ((dest_fp
= claws_fopen(dest
, "wb")) == NULL
) {
393 FILE_OP_ERROR(dest
, "claws_fopen");
397 if (change_file_mode_rw(dest_fp
, dest
) < 0) {
398 FILE_OP_ERROR(dest
, "chmod");
399 g_warning("can't change file mode: %s", dest
);
402 if (copy_file_part_to_fp(fp
, offset
, length
, dest_fp
) < 0)
405 if (claws_safe_fclose(dest_fp
) == EOF
) {
406 FILE_OP_ERROR(dest
, "claws_fclose");
411 g_warning("writing to %s failed", dest
);
419 gint
canonicalize_file(const gchar
*src
, const gchar
*dest
)
421 FILE *src_fp
, *dest_fp
;
424 gboolean err
= FALSE
;
425 gboolean last_linebreak
= FALSE
;
427 if (src
== NULL
|| dest
== NULL
)
430 if ((src_fp
= claws_fopen(src
, "rb")) == NULL
) {
431 FILE_OP_ERROR(src
, "claws_fopen");
435 if ((dest_fp
= claws_fopen(dest
, "wb")) == NULL
) {
436 FILE_OP_ERROR(dest
, "claws_fopen");
437 claws_fclose(src_fp
);
441 if (change_file_mode_rw(dest_fp
, dest
) < 0) {
442 FILE_OP_ERROR(dest
, "chmod");
443 g_warning("can't change file mode: %s", dest
);
446 while (claws_fgets(buf
, sizeof(buf
), src_fp
) != NULL
) {
451 last_linebreak
= FALSE
;
453 if (buf
[len
- 1] != '\n') {
454 last_linebreak
= TRUE
;
455 r
= claws_fputs(buf
, dest_fp
);
456 } else if (len
> 1 && buf
[len
- 1] == '\n' && buf
[len
- 2] == '\r') {
457 r
= claws_fputs(buf
, dest_fp
);
460 r
= claws_fwrite(buf
, 1, len
- 1, dest_fp
);
465 r
= claws_fputs("\r\n", dest_fp
);
469 g_warning("writing to %s failed", dest
);
470 claws_fclose(dest_fp
);
471 claws_fclose(src_fp
);
477 if (last_linebreak
== TRUE
) {
478 if (claws_fputs("\r\n", dest_fp
) == EOF
)
482 if (claws_ferror(src_fp
)) {
483 FILE_OP_ERROR(src
, "claws_fgets");
486 claws_fclose(src_fp
);
487 if (claws_safe_fclose(dest_fp
) == EOF
) {
488 FILE_OP_ERROR(dest
, "claws_fclose");
500 gint
canonicalize_file_replace(const gchar
*file
)
504 tmp_file
= get_tmp_file();
506 if (canonicalize_file(file
, tmp_file
) < 0) {
511 if (move_file(tmp_file
, file
, TRUE
) < 0) {
512 g_warning("can't replace file: %s", file
);
513 claws_unlink(tmp_file
);
523 gint
str_write_to_file(const gchar
*str
, const gchar
*file
, gboolean safe
)
529 cm_return_val_if_fail(str
!= NULL
, -1);
530 cm_return_val_if_fail(file
!= NULL
, -1);
532 if ((fp
= claws_fopen(file
, "wb")) == NULL
) {
533 FILE_OP_ERROR(file
, "claws_fopen");
543 if (claws_fwrite(str
, 1, len
, fp
) != len
) {
544 FILE_OP_ERROR(file
, "claws_fwrite");
551 r
= claws_safe_fclose(fp
);
553 r
= claws_fclose(fp
);
557 FILE_OP_ERROR(file
, "claws_fclose");
565 static gchar
*file_read_stream_to_str_full(FILE *fp
, gboolean recode
)
572 cm_return_val_if_fail(fp
!= NULL
, NULL
);
574 array
= g_byte_array_new();
576 while ((n_read
= claws_fread(buf
, sizeof(gchar
), sizeof(buf
), fp
)) > 0) {
577 if (n_read
< sizeof(buf
) && claws_ferror(fp
))
579 g_byte_array_append(array
, buf
, n_read
);
582 if (claws_ferror(fp
)) {
583 FILE_OP_ERROR("file stream", "claws_fread");
584 g_byte_array_free(array
, TRUE
);
589 g_byte_array_append(array
, buf
, 1);
590 str
= (gchar
*)array
->data
;
591 g_byte_array_free(array
, FALSE
);
593 if (recode
&& !g_utf8_validate(str
, -1, NULL
)) {
594 const gchar
*src_codeset
, *dest_codeset
;
596 src_codeset
= conv_get_locale_charset_str();
597 dest_codeset
= CS_UTF_8
;
598 tmp
= conv_codeset_strdup(str
, src_codeset
, dest_codeset
);
606 static gchar
*file_read_to_str_full(const gchar
*file
, gboolean recode
)
613 struct timeval timeout
= {1, 0};
618 cm_return_val_if_fail(file
!= NULL
, NULL
);
620 if (g_stat(file
, &s
) != 0) {
621 FILE_OP_ERROR(file
, "stat");
624 if (S_ISDIR(s
.st_mode
)) {
625 g_warning("%s: is a directory", file
);
630 fp
= claws_fopen (file
, "rb");
632 FILE_OP_ERROR(file
, "open");
636 /* test whether the file is readable without blocking */
637 fd
= g_open(file
, O_RDONLY
| O_NONBLOCK
, 0);
639 FILE_OP_ERROR(file
, "open");
646 /* allow for one second */
647 err
= select(fd
+1, &fds
, NULL
, NULL
, &timeout
);
648 if (err
<= 0 || !FD_ISSET(fd
, &fds
)) {
650 FILE_OP_ERROR(file
, "select");
652 g_warning("%s: doesn't seem readable", file
);
658 /* Now clear O_NONBLOCK */
659 if ((fflags
= fcntl(fd
, F_GETFL
)) < 0) {
660 FILE_OP_ERROR(file
, "fcntl (F_GETFL)");
664 if (fcntl(fd
, F_SETFL
, (fflags
& ~O_NONBLOCK
)) < 0) {
665 FILE_OP_ERROR(file
, "fcntl (F_SETFL)");
670 /* get the FILE pointer */
671 fp
= claws_fdopen(fd
, "rb");
674 FILE_OP_ERROR(file
, "claws_fdopen");
675 close(fd
); /* if fp isn't NULL, we'll use claws_fclose instead! */
680 str
= file_read_stream_to_str_full(fp
, recode
);
687 gchar
*file_read_to_str(const gchar
*file
)
689 return file_read_to_str_full(file
, TRUE
);
691 gchar
*file_read_stream_to_str(FILE *fp
)
693 return file_read_stream_to_str_full(fp
, TRUE
);
696 gchar
*file_read_to_str_no_recode(const gchar
*file
)
698 return file_read_to_str_full(file
, FALSE
);
700 gchar
*file_read_stream_to_str_no_recode(FILE *fp
)
702 return file_read_stream_to_str_full(fp
, FALSE
);
705 gint
rename_force(const gchar
*oldpath
, const gchar
*newpath
)
708 if (!is_file_entry_exist(oldpath
)) {
712 if (is_file_exist(newpath
)) {
713 if (claws_unlink(newpath
) < 0)
714 FILE_OP_ERROR(newpath
, "unlink");
717 return g_rename(oldpath
, newpath
);
720 gint
copy_dir(const gchar
*src
, const gchar
*dst
)
725 if ((dir
= g_dir_open(src
, 0, NULL
)) == NULL
) {
726 g_warning("failed to open directory: %s", src
);
730 if (make_dir(dst
) < 0) {
735 while ((name
= g_dir_read_name(dir
)) != NULL
) {
736 gchar
*old_file
, *new_file
;
738 old_file
= g_strconcat(src
, G_DIR_SEPARATOR_S
, name
, NULL
);
739 new_file
= g_strconcat(dst
, G_DIR_SEPARATOR_S
, name
, NULL
);
740 debug_print("copying: %s -> %s\n", old_file
, new_file
);
741 if (g_file_test(old_file
, G_FILE_TEST_IS_REGULAR
)) {
742 r
= copy_file(old_file
, new_file
, TRUE
);
745 /* Windows has no symlinks. Or well, Vista seems to
746 have something like this but the semantics might be
747 different. Thus we don't use it under Windows. */
748 else if (g_file_test(old_file
, G_FILE_TEST_IS_SYMLINK
)) {
749 GError
*error
= NULL
;
750 gchar
*target
= g_file_read_link(old_file
, &error
);
752 g_warning("couldn't read link: %s", error
->message
);
756 r
= symlink(target
, new_file
);
760 #endif /*G_OS_WIN32*/
761 else if (g_file_test(old_file
, G_FILE_TEST_IS_DIR
)) {
762 r
= copy_dir(old_file
, new_file
);
775 gint
change_file_mode_rw(FILE *fp
, const gchar
*file
)
778 return fchmod(fileno(fp
), S_IRUSR
|S_IWUSR
);
780 return g_chmod(file
, S_IRUSR
|S_IWUSR
);
784 FILE *my_tmpfile(void)
786 const gchar suffix
[] = ".XXXXXX";
789 const gchar
*progname
;
798 tmpdir
= get_tmp_dir();
799 tmplen
= strlen(tmpdir
);
800 progname
= g_get_prgname();
801 if (progname
== NULL
)
802 progname
= "claws-mail";
803 proglen
= strlen(progname
);
804 Xalloca(fname
, tmplen
+ 1 + proglen
+ sizeof(suffix
),
807 memcpy(fname
, tmpdir
, tmplen
);
808 fname
[tmplen
] = G_DIR_SEPARATOR
;
809 memcpy(fname
+ tmplen
+ 1, progname
, proglen
);
810 memcpy(fname
+ tmplen
+ 1 + proglen
, suffix
, sizeof(suffix
));
812 fd
= g_mkstemp(fname
);
819 /* verify that we can write in the file after unlinking */
820 if (write(fd
, buf
, 1) < 0) {
827 fp
= claws_fdopen(fd
, "w+b");
838 FILE *get_tmpfile_in_dir(const gchar
*dir
, gchar
**filename
)
841 *filename
= g_strdup_printf("%s%cclaws.XXXXXX", dir
, G_DIR_SEPARATOR
);
842 fd
= g_mkstemp(*filename
);
845 return claws_fdopen(fd
, "w+");
848 FILE *str_open_as_stream(const gchar
*str
)
853 cm_return_val_if_fail(str
!= NULL
, NULL
);
857 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
862 if (len
== 0) return fp
;
864 if (claws_fwrite(str
, 1, len
, fp
) != len
) {
865 FILE_OP_ERROR("str_open_as_stream", "claws_fwrite");