Improve some sieve-related translations
[claws.git] / src / common / file-utils.c
blob5fc7e3e831070be5925676e2ae339fea07b48c06
1 /*
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <glib.h>
26 #ifndef G_OS_WIN32
27 #include <sys/wait.h>
28 #else
29 #define WEXITSTATUS(x) (x)
30 #endif
32 #include <errno.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <fcntl.h>
37 #include "defs.h"
38 #include "codeconv.h"
39 #include "timing.h"
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)
47 int r;
48 START_TIMING("");
50 if (fflush(fp) != 0) {
51 return EOF;
53 if (prefs_common_get_flush_metadata() && fsync(fileno(fp)) != 0) {
54 return EOF;
57 r = fclose(fp);
58 END_TIMING();
60 return r;
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
70 funlockfile(fp);
71 #endif
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);
83 if (!fp)
84 return NULL;
85 flockfile(fp);
86 return fp;
89 FILE *claws_fdopen(int fd, const char *mode)
91 FILE *fp = fdopen(fd, mode);
92 if (!fp)
93 return NULL;
94 flockfile(fp);
95 return fp;
98 /* Unlocks and close a file pointer
101 int claws_fclose(FILE *fp)
103 funlockfile(fp);
104 return fclose(fp);
106 #endif
108 int claws_unlink(const char *filename)
110 GStatBuf s;
111 static int found_shred = -1;
112 static const gchar *args[4];
114 if (filename == NULL)
115 return 0;
117 if (prefs_common_get_use_shred()) {
118 if (found_shred == -1) {
119 /* init */
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;
123 args[1] = "-f";
124 args[3] = NULL;
126 if (found_shred == 1) {
127 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
128 if (s.st_nlink == 1) {
129 gint status=0;
130 args[2] = filename;
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;
147 gchar buf[4096];
148 gchar *out = get_tmp_file();
149 if (file == NULL)
150 goto freeout;
152 fp = claws_fopen(file, "rb");
153 if (!fp)
154 goto freeout;
156 outfp = claws_fopen(out, "wb");
157 if (!outfp) {
158 claws_fclose(fp);
159 goto freeout;
162 while (claws_fgets(buf, sizeof (buf), fp) != NULL) {
163 strcrchomp(buf);
164 if (claws_fputs(buf, outfp) == EOF) {
165 claws_fclose(fp);
166 claws_fclose(outfp);
167 goto unlinkout;
171 claws_fclose(fp);
172 if (claws_safe_fclose(outfp) == EOF) {
173 goto unlinkout;
176 if (move_file(out, file, TRUE) < 0)
177 goto unlinkout;
179 g_free(out);
180 return 0;
181 unlinkout:
182 claws_unlink(out);
183 freeout:
184 g_free(out);
185 return -1;
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;
195 gint n_read;
196 gchar buf[BUFSIZ];
198 gboolean err = FALSE;
200 if ((src_fp = claws_fopen(src, "rb")) == NULL) {
201 FILE_OP_ERROR(src, "claws_fopen");
202 return -1;
205 if ((dest_fp = claws_fopen(dest, "ab")) == NULL) {
206 FILE_OP_ERROR(dest, "claws_fopen");
207 claws_fclose(src_fp);
208 return -1;
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))
218 break;
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);
223 claws_unlink(dest);
224 return -1;
228 if (claws_ferror(src_fp)) {
229 FILE_OP_ERROR(src, "claws_fread");
230 err = TRUE;
232 claws_fclose(src_fp);
233 if (claws_fclose(dest_fp) == EOF) {
234 FILE_OP_ERROR(dest, "claws_fclose");
235 err = TRUE;
238 if (err) {
239 claws_unlink(dest);
240 return -1;
243 return 0;
246 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
248 FILE *src_fp, *dest_fp;
249 gint n_read;
250 gchar buf[BUFSIZ];
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");
256 return -1;
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);
263 g_free(dest_bak);
264 return -1;
268 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
269 FILE_OP_ERROR(dest, "claws_fopen");
270 claws_fclose(src_fp);
271 if (dest_bak) {
272 if (rename_force(dest_bak, dest) < 0)
273 FILE_OP_ERROR(dest_bak, "rename");
274 g_free(dest_bak);
276 return -1;
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))
286 break;
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");
293 if (dest_bak) {
294 if (rename_force(dest_bak, dest) < 0)
295 FILE_OP_ERROR(dest_bak, "rename");
296 g_free(dest_bak);
298 return -1;
302 if (claws_ferror(src_fp)) {
303 FILE_OP_ERROR(src, "claws_fread");
304 err = TRUE;
306 claws_fclose(src_fp);
307 if (claws_safe_fclose(dest_fp) == EOF) {
308 FILE_OP_ERROR(dest, "claws_fclose");
309 err = TRUE;
312 if (err) {
313 if (claws_unlink(dest) < 0)
314 FILE_OP_ERROR(dest, "claws_unlink");
315 if (dest_bak) {
316 if (rename_force(dest_bak, dest) < 0)
317 FILE_OP_ERROR(dest_bak, "rename");
318 g_free(dest_bak);
320 return -1;
323 if (keep_backup == FALSE && dest_bak)
324 if (claws_unlink(dest_bak) < 0)
325 FILE_OP_ERROR(dest_bak, "claws_unlink");
327 g_free(dest_bak);
329 return 0;
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);
336 return -1;
339 if (rename_force(src, dest) == 0) return 0;
341 if (EXDEV != errno) {
342 FILE_OP_ERROR(src, "rename");
343 return -1;
346 if (copy_file(src, dest, FALSE) < 0) return -1;
348 claws_unlink(src);
350 return 0;
353 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
355 gint n_read;
356 gint bytes_left, to_read;
357 gchar buf[BUFSIZ];
359 if (fseek(fp, offset, SEEK_SET) < 0) {
360 perror("fseek");
361 return -1;
364 bytes_left = length;
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))
369 break;
370 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
371 return -1;
373 bytes_left -= n_read;
374 if (bytes_left == 0)
375 break;
376 to_read = MIN(bytes_left, sizeof(buf));
379 if (claws_ferror(fp)) {
380 perror("claws_fread");
381 return -1;
384 return 0;
387 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
389 FILE *dest_fp;
390 gboolean err = FALSE;
392 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
393 FILE_OP_ERROR(dest, "claws_fopen");
394 return -1;
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)
403 err = TRUE;
405 if (claws_safe_fclose(dest_fp) == EOF) {
406 FILE_OP_ERROR(dest, "claws_fclose");
407 err = TRUE;
410 if (err) {
411 g_warning("writing to %s failed", dest);
412 claws_unlink(dest);
413 return -1;
416 return 0;
419 gint canonicalize_file(const gchar *src, const gchar *dest)
421 FILE *src_fp, *dest_fp;
422 gchar buf[BUFFSIZE];
423 gint len;
424 gboolean err = FALSE;
425 gboolean last_linebreak = FALSE;
427 if (src == NULL || dest == NULL)
428 return -1;
430 if ((src_fp = claws_fopen(src, "rb")) == NULL) {
431 FILE_OP_ERROR(src, "claws_fopen");
432 return -1;
435 if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
436 FILE_OP_ERROR(dest, "claws_fopen");
437 claws_fclose(src_fp);
438 return -1;
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) {
447 gint r = 0;
449 len = strlen(buf);
450 if (len == 0) break;
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);
458 } else {
459 if (len > 1) {
460 r = claws_fwrite(buf, 1, len - 1, dest_fp);
461 if (r != (len -1))
462 r = EOF;
464 if (r != EOF)
465 r = claws_fputs("\r\n", dest_fp);
468 if (r == EOF) {
469 g_warning("writing to %s failed", dest);
470 claws_fclose(dest_fp);
471 claws_fclose(src_fp);
472 claws_unlink(dest);
473 return -1;
477 if (last_linebreak == TRUE) {
478 if (claws_fputs("\r\n", dest_fp) == EOF)
479 err = TRUE;
482 if (claws_ferror(src_fp)) {
483 FILE_OP_ERROR(src, "claws_fgets");
484 err = TRUE;
486 claws_fclose(src_fp);
487 if (claws_safe_fclose(dest_fp) == EOF) {
488 FILE_OP_ERROR(dest, "claws_fclose");
489 err = TRUE;
492 if (err) {
493 claws_unlink(dest);
494 return -1;
497 return 0;
500 gint canonicalize_file_replace(const gchar *file)
502 gchar *tmp_file;
504 tmp_file = get_tmp_file();
506 if (canonicalize_file(file, tmp_file) < 0) {
507 g_free(tmp_file);
508 return -1;
511 if (move_file(tmp_file, file, TRUE) < 0) {
512 g_warning("can't replace file: %s", file);
513 claws_unlink(tmp_file);
514 g_free(tmp_file);
515 return -1;
518 g_free(tmp_file);
519 return 0;
523 gint str_write_to_file(const gchar *str, const gchar *file, gboolean safe)
525 FILE *fp;
526 size_t len;
527 int r;
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");
534 return -1;
537 len = strlen(str);
538 if (len == 0) {
539 claws_fclose(fp);
540 return 0;
543 if (claws_fwrite(str, 1, len, fp) != len) {
544 FILE_OP_ERROR(file, "claws_fwrite");
545 claws_fclose(fp);
546 claws_unlink(file);
547 return -1;
550 if (safe) {
551 r = claws_safe_fclose(fp);
552 } else {
553 r = claws_fclose(fp);
556 if (r == EOF) {
557 FILE_OP_ERROR(file, "claws_fclose");
558 claws_unlink(file);
559 return -1;
562 return 0;
565 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
567 GByteArray *array;
568 guchar buf[BUFSIZ];
569 gint n_read;
570 gchar *str;
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))
578 break;
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);
585 return NULL;
588 buf[0] = '\0';
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;
595 gchar *tmp = NULL;
596 src_codeset = conv_get_locale_charset_str();
597 dest_codeset = CS_UTF_8;
598 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
599 g_free(str);
600 str = tmp;
603 return str;
606 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
608 FILE *fp;
609 gchar *str;
610 GStatBuf s;
611 #ifndef G_OS_WIN32
612 gint fd, err;
613 struct timeval timeout = {1, 0};
614 fd_set fds;
615 int fflags = 0;
616 #endif
618 cm_return_val_if_fail(file != NULL, NULL);
620 if (g_stat(file, &s) != 0) {
621 FILE_OP_ERROR(file, "stat");
622 return NULL;
624 if (S_ISDIR(s.st_mode)) {
625 g_warning("%s: is a directory", file);
626 return NULL;
629 #ifdef G_OS_WIN32
630 fp = claws_fopen (file, "rb");
631 if (fp == NULL) {
632 FILE_OP_ERROR(file, "open");
633 return NULL;
635 #else
636 /* test whether the file is readable without blocking */
637 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
638 if (fd == -1) {
639 FILE_OP_ERROR(file, "open");
640 return NULL;
643 FD_ZERO(&fds);
644 FD_SET(fd, &fds);
646 /* allow for one second */
647 err = select(fd+1, &fds, NULL, NULL, &timeout);
648 if (err <= 0 || !FD_ISSET(fd, &fds)) {
649 if (err < 0) {
650 FILE_OP_ERROR(file, "select");
651 } else {
652 g_warning("%s: doesn't seem readable", file);
654 close(fd);
655 return NULL;
658 /* Now clear O_NONBLOCK */
659 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
660 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
661 close(fd);
662 return NULL;
664 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
665 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
666 close(fd);
667 return NULL;
670 /* get the FILE pointer */
671 fp = claws_fdopen(fd, "rb");
673 if (fp == NULL) {
674 FILE_OP_ERROR(file, "claws_fdopen");
675 close(fd); /* if fp isn't NULL, we'll use claws_fclose instead! */
676 return NULL;
678 #endif
680 str = file_read_stream_to_str_full(fp, recode);
682 claws_fclose(fp);
684 return str;
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)
707 #ifndef G_OS_UNIX
708 if (!is_file_entry_exist(oldpath)) {
709 errno = ENOENT;
710 return -1;
712 if (is_file_exist(newpath)) {
713 if (claws_unlink(newpath) < 0)
714 FILE_OP_ERROR(newpath, "unlink");
716 #endif
717 return g_rename(oldpath, newpath);
720 gint copy_dir(const gchar *src, const gchar *dst)
722 GDir *dir;
723 const gchar *name;
725 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
726 g_warning("failed to open directory: %s", src);
727 return -1;
730 if (make_dir(dst) < 0) {
731 g_dir_close(dir);
732 return -1;
735 while ((name = g_dir_read_name(dir)) != NULL) {
736 gchar *old_file, *new_file;
737 gint r = 0;
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);
744 #ifndef G_OS_WIN32
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);
751 if (error) {
752 g_warning("couldn't read link: %s", error->message);
753 g_error_free(error);
755 if (target) {
756 r = symlink(target, new_file);
757 g_free(target);
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);
764 g_free(old_file);
765 g_free(new_file);
766 if (r < 0) {
767 g_dir_close(dir);
768 return r;
771 g_dir_close(dir);
772 return 0;
775 gint change_file_mode_rw(FILE *fp, const gchar *file)
777 #if HAVE_FCHMOD
778 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
779 #else
780 return g_chmod(file, S_IRUSR|S_IWUSR);
781 #endif
784 FILE *my_tmpfile(void)
786 const gchar suffix[] = ".XXXXXX";
787 const gchar *tmpdir;
788 guint tmplen;
789 const gchar *progname;
790 guint proglen;
791 gchar *fname;
792 gint fd;
793 FILE *fp;
794 #ifndef G_OS_WIN32
795 gchar buf[2]="\0";
796 #endif
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),
805 return tmpfile());
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);
813 if (fd < 0)
814 return tmpfile();
816 #ifndef G_OS_WIN32
817 claws_unlink(fname);
819 /* verify that we can write in the file after unlinking */
820 if (write(fd, buf, 1) < 0) {
821 close(fd);
822 return tmpfile();
825 #endif
827 fp = claws_fdopen(fd, "w+b");
828 if (!fp)
829 close(fd);
830 else {
831 rewind(fp);
832 return fp;
835 return tmpfile();
838 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
840 int fd;
841 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
842 fd = g_mkstemp(*filename);
843 if (fd < 0)
844 return NULL;
845 return claws_fdopen(fd, "w+");
848 FILE *str_open_as_stream(const gchar *str)
850 FILE *fp;
851 size_t len;
853 cm_return_val_if_fail(str != NULL, NULL);
855 fp = my_tmpfile();
856 if (!fp) {
857 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
858 return NULL;
861 len = strlen(str);
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");
866 claws_fclose(fp);
867 return NULL;
870 rewind(fp);
871 return fp;