Fix performance loss on certain file I/O that doesn't need
[claws.git] / src / common / file-utils.c
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
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <errno.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28
29 #include "defs.h"
30 #include "codeconv.h"
31 #include "timing.h"
32 #include "file-utils.h"
33
34 gboolean prefs_common_get_flush_metadata(void);
35 gboolean prefs_common_get_use_shred(void);
36
37 /* FIXME make static once every file I/O is done using claws_* wrappers */
38 int safe_fclose(FILE *fp)
39 {
40         int r;
41         START_TIMING("");
42
43         if (fflush(fp) != 0) {
44                 return EOF;
45         }
46         if (prefs_common_get_flush_metadata() && fsync(fileno(fp)) != 0) {
47                 return EOF;
48         }
49
50         r = fclose(fp);
51         END_TIMING();
52
53         return r;
54 }
55
56 #if HAVE_FGETS_UNLOCKED
57
58 /* Open a file and locks it once
59  * so subsequent I/O is faster
60  */
61 FILE *claws_fopen(const char *file, const char *mode)
62 {
63         FILE *fp = fopen(file, mode);
64         if (!fp)
65                 return NULL;
66         flockfile(fp);
67         return fp;
68 }
69
70 FILE *claws_fdopen(int fd, const char *mode)
71 {
72         FILE *fp = fdopen(fd, mode);
73         if (!fp)
74                 return NULL;
75         flockfile(fp);
76         return fp;
77 }
78
79 /* Unlocks and close a file pointer
80  */
81
82 int claws_fclose(FILE *fp)
83 {
84         funlockfile(fp);
85         return fclose(fp);
86 }
87
88 /* Unlock, then safe-close a file pointer
89  * Safe close is done using fflush + fsync
90  * if the according preference says so.
91  */
92 int claws_safe_fclose(FILE *fp)
93 {
94         funlockfile(fp);
95         return safe_fclose(fp);
96 }
97
98 #ifdef G_OS_WIN32
99 #define WEXITSTATUS(x) (x)
100 #endif
101
102 int claws_unlink(const char *filename) 
103 {
104         GStatBuf s;
105         static int found_shred = -1;
106         static const gchar *args[4];
107
108         if (filename == NULL)
109                 return 0;
110
111         if (prefs_common_get_use_shred()) {
112                 if (found_shred == -1) {
113                         /* init */
114                         args[0] = g_find_program_in_path("shred");
115                         debug_print("found shred: %s\n", args[0]);
116                         found_shred = (args[0] != NULL) ? 1:0;
117                         args[1] = "-f";
118                         args[3] = NULL;
119                 }
120                 if (found_shred == 1) {
121                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
122                                 if (s.st_nlink == 1) {
123                                         gint status=0;
124                                         args[2] = filename;
125                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
126                                          NULL, NULL, NULL, NULL, &status, NULL);
127                                         debug_print("%s %s exited with status %d\n",
128                                                 args[0], filename, WEXITSTATUS(status));
129                                         if (truncate(filename, 0) < 0)
130                                                 g_warning("couln't truncate: %s", filename);
131                                 }
132                         }
133                 }
134         }
135         return g_unlink(filename);
136 }
137
138 gint file_strip_crs(const gchar *file)
139 {
140         FILE *fp = NULL, *outfp = NULL;
141         gchar buf[4096];
142         gchar *out = get_tmp_file();
143         if (file == NULL)
144                 goto freeout;
145
146         fp = claws_fopen(file, "rb");
147         if (!fp)
148                 goto freeout;
149
150         outfp = claws_fopen(out, "wb");
151         if (!outfp) {
152                 claws_fclose(fp);
153                 goto freeout;
154         }
155
156         while (claws_fgets(buf, sizeof (buf), fp) != NULL) {
157                 strcrchomp(buf);
158                 if (claws_fputs(buf, outfp) == EOF) {
159                         claws_fclose(fp);
160                         claws_fclose(outfp);
161                         goto unlinkout;
162                 }
163         }
164
165         claws_fclose(fp);
166         if (claws_safe_fclose(outfp) == EOF) {
167                 goto unlinkout;
168         }
169         
170         if (move_file(out, file, TRUE) < 0)
171                 goto unlinkout;
172         
173         g_free(out);
174         return 0;
175 unlinkout:
176         claws_unlink(out);
177 freeout:
178         g_free(out);
179         return -1;
180 }
181
182 /*
183  * Append src file body to the tail of dest file.
184  * Now keep_backup has no effects.
185  */
186 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
187 {
188         FILE *src_fp, *dest_fp;
189         gint n_read;
190         gchar buf[BUFSIZ];
191
192         gboolean err = FALSE;
193
194         if ((src_fp = claws_fopen(src, "rb")) == NULL) {
195                 FILE_OP_ERROR(src, "claws_fopen");
196                 return -1;
197         }
198
199         if ((dest_fp = claws_fopen(dest, "ab")) == NULL) {
200                 FILE_OP_ERROR(dest, "claws_fopen");
201                 claws_fclose(src_fp);
202                 return -1;
203         }
204
205         if (change_file_mode_rw(dest_fp, dest) < 0) {
206                 FILE_OP_ERROR(dest, "chmod");
207                 g_warning("can't change file mode: %s", dest);
208         }
209
210         while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
211                 if (n_read < sizeof(buf) && claws_ferror(src_fp))
212                         break;
213                 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
214                         g_warning("writing to %s failed.", dest);
215                         claws_fclose(dest_fp);
216                         claws_fclose(src_fp);
217                         claws_unlink(dest);
218                         return -1;
219                 }
220         }
221
222         if (claws_ferror(src_fp)) {
223                 FILE_OP_ERROR(src, "claws_fread");
224                 err = TRUE;
225         }
226         claws_fclose(src_fp);
227         if (claws_fclose(dest_fp) == EOF) {
228                 FILE_OP_ERROR(dest, "claws_fclose");
229                 err = TRUE;
230         }
231
232         if (err) {
233                 claws_unlink(dest);
234                 return -1;
235         }
236
237         return 0;
238 }
239
240 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
241 {
242         FILE *src_fp, *dest_fp;
243         gint n_read;
244         gchar buf[BUFSIZ];
245         gchar *dest_bak = NULL;
246         gboolean err = FALSE;
247
248         if ((src_fp = claws_fopen(src, "rb")) == NULL) {
249                 FILE_OP_ERROR(src, "claws_fopen");
250                 return -1;
251         }
252         if (is_file_exist(dest)) {
253                 dest_bak = g_strconcat(dest, ".bak", NULL);
254                 if (rename_force(dest, dest_bak) < 0) {
255                         FILE_OP_ERROR(dest, "rename");
256                         claws_fclose(src_fp);
257                         g_free(dest_bak);
258                         return -1;
259                 }
260         }
261
262         if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
263                 FILE_OP_ERROR(dest, "claws_fopen");
264                 claws_fclose(src_fp);
265                 if (dest_bak) {
266                         if (rename_force(dest_bak, dest) < 0)
267                                 FILE_OP_ERROR(dest_bak, "rename");
268                         g_free(dest_bak);
269                 }
270                 return -1;
271         }
272
273         if (change_file_mode_rw(dest_fp, dest) < 0) {
274                 FILE_OP_ERROR(dest, "chmod");
275                 g_warning("can't change file mode: %s", dest);
276         }
277
278         while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
279                 if (n_read < sizeof(buf) && claws_ferror(src_fp))
280                         break;
281                 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
282                         g_warning("writing to %s failed.", dest);
283                         claws_fclose(dest_fp);
284                         claws_fclose(src_fp);
285                         claws_unlink(dest);
286                         if (dest_bak) {
287                                 if (rename_force(dest_bak, dest) < 0)
288                                         FILE_OP_ERROR(dest_bak, "rename");
289                                 g_free(dest_bak);
290                         }
291                         return -1;
292                 }
293         }
294
295         if (claws_ferror(src_fp)) {
296                 FILE_OP_ERROR(src, "claws_fread");
297                 err = TRUE;
298         }
299         claws_fclose(src_fp);
300         if (claws_safe_fclose(dest_fp) == EOF) {
301                 FILE_OP_ERROR(dest, "claws_fclose");
302                 err = TRUE;
303         }
304
305         if (err) {
306                 claws_unlink(dest);
307                 if (dest_bak) {
308                         if (rename_force(dest_bak, dest) < 0)
309                                 FILE_OP_ERROR(dest_bak, "rename");
310                         g_free(dest_bak);
311                 }
312                 return -1;
313         }
314
315         if (keep_backup == FALSE && dest_bak)
316                 claws_unlink(dest_bak);
317
318         g_free(dest_bak);
319
320         return 0;
321 }
322
323 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
324 {
325         if (overwrite == FALSE && is_file_exist(dest)) {
326                 g_warning("move_file(): file %s already exists.", dest);
327                 return -1;
328         }
329
330         if (rename_force(src, dest) == 0) return 0;
331
332         if (EXDEV != errno) {
333                 FILE_OP_ERROR(src, "rename");
334                 return -1;
335         }
336
337         if (copy_file(src, dest, FALSE) < 0) return -1;
338
339         claws_unlink(src);
340
341         return 0;
342 }
343
344 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
345 {
346         gint n_read;
347         gint bytes_left, to_read;
348         gchar buf[BUFSIZ];
349
350         if (fseek(fp, offset, SEEK_SET) < 0) {
351                 perror("fseek");
352                 return -1;
353         }
354
355         bytes_left = length;
356         to_read = MIN(bytes_left, sizeof(buf));
357
358         while ((n_read = claws_fread(buf, sizeof(gchar), to_read, fp)) > 0) {
359                 if (n_read < to_read && claws_ferror(fp))
360                         break;
361                 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
362                         return -1;
363                 }
364                 bytes_left -= n_read;
365                 if (bytes_left == 0)
366                         break;
367                 to_read = MIN(bytes_left, sizeof(buf));
368         }
369
370         if (claws_ferror(fp)) {
371                 perror("claws_fread");
372                 return -1;
373         }
374
375         return 0;
376 }
377
378 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
379 {
380         FILE *dest_fp;
381         gboolean err = FALSE;
382
383         if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
384                 FILE_OP_ERROR(dest, "claws_fopen");
385                 return -1;
386         }
387
388         if (change_file_mode_rw(dest_fp, dest) < 0) {
389                 FILE_OP_ERROR(dest, "chmod");
390                 g_warning("can't change file mode: %s", dest);
391         }
392
393         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
394                 err = TRUE;
395
396         if (claws_safe_fclose(dest_fp) == EOF) {
397                 FILE_OP_ERROR(dest, "claws_fclose");
398                 err = TRUE;
399         }
400
401         if (err) {
402                 g_warning("writing to %s failed.", dest);
403                 claws_unlink(dest);
404                 return -1;
405         }
406
407         return 0;
408 }
409
410 gint canonicalize_file(const gchar *src, const gchar *dest)
411 {
412         FILE *src_fp, *dest_fp;
413         gchar buf[BUFFSIZE];
414         gint len;
415         gboolean err = FALSE;
416         gboolean last_linebreak = FALSE;
417
418         if (src == NULL || dest == NULL)
419                 return -1;
420
421         if ((src_fp = claws_fopen(src, "rb")) == NULL) {
422                 FILE_OP_ERROR(src, "claws_fopen");
423                 return -1;
424         }
425
426         if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
427                 FILE_OP_ERROR(dest, "claws_fopen");
428                 claws_fclose(src_fp);
429                 return -1;
430         }
431
432         if (change_file_mode_rw(dest_fp, dest) < 0) {
433                 FILE_OP_ERROR(dest, "chmod");
434                 g_warning("can't change file mode: %s", dest);
435         }
436
437         while (claws_fgets(buf, sizeof(buf), src_fp) != NULL) {
438                 gint r = 0;
439
440                 len = strlen(buf);
441                 if (len == 0) break;
442                 last_linebreak = FALSE;
443
444                 if (buf[len - 1] != '\n') {
445                         last_linebreak = TRUE;
446                         r = claws_fputs(buf, dest_fp);
447                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
448                         r = claws_fputs(buf, dest_fp);
449                 } else {
450                         if (len > 1) {
451                                 r = claws_fwrite(buf, 1, len - 1, dest_fp);
452                                 if (r != (len -1))
453                                         r = EOF;
454                         }
455                         if (r != EOF)
456                                 r = claws_fputs("\r\n", dest_fp);
457                 }
458
459                 if (r == EOF) {
460                         g_warning("writing to %s failed.", dest);
461                         claws_fclose(dest_fp);
462                         claws_fclose(src_fp);
463                         claws_unlink(dest);
464                         return -1;
465                 }
466         }
467
468         if (last_linebreak == TRUE) {
469                 if (claws_fputs("\r\n", dest_fp) == EOF)
470                         err = TRUE;
471         }
472
473         if (claws_ferror(src_fp)) {
474                 FILE_OP_ERROR(src, "claws_fgets");
475                 err = TRUE;
476         }
477         claws_fclose(src_fp);
478         if (claws_safe_fclose(dest_fp) == EOF) {
479                 FILE_OP_ERROR(dest, "claws_fclose");
480                 err = TRUE;
481         }
482
483         if (err) {
484                 claws_unlink(dest);
485                 return -1;
486         }
487
488         return 0;
489 }
490
491 gint canonicalize_file_replace(const gchar *file)
492 {
493         gchar *tmp_file;
494
495         tmp_file = get_tmp_file();
496
497         if (canonicalize_file(file, tmp_file) < 0) {
498                 g_free(tmp_file);
499                 return -1;
500         }
501
502         if (move_file(tmp_file, file, TRUE) < 0) {
503                 g_warning("can't replace file: %s", file);
504                 claws_unlink(tmp_file);
505                 g_free(tmp_file);
506                 return -1;
507         }
508
509         g_free(tmp_file);
510         return 0;
511 }
512
513
514 gint str_write_to_file(const gchar *str, const gchar *file, gboolean safe)
515 {
516         FILE *fp;
517         size_t len;
518         int r;
519
520         cm_return_val_if_fail(str != NULL, -1);
521         cm_return_val_if_fail(file != NULL, -1);
522
523         if ((fp = claws_fopen(file, "wb")) == NULL) {
524                 FILE_OP_ERROR(file, "claws_fopen");
525                 return -1;
526         }
527
528         len = strlen(str);
529         if (len == 0) {
530                 claws_fclose(fp);
531                 return 0;
532         }
533
534         if (claws_fwrite(str, 1, len, fp) != len) {
535                 FILE_OP_ERROR(file, "claws_fwrite");
536                 claws_fclose(fp);
537                 claws_unlink(file);
538                 return -1;
539         }
540
541         if (safe) {
542                 r = claws_safe_fclose(fp);
543         } else {
544                 r = claws_fclose(fp);
545         }
546
547         if (r == EOF) {
548                 FILE_OP_ERROR(file, "claws_fclose");
549                 claws_unlink(file);
550                 return -1;
551         }
552
553         return 0;
554 }
555
556 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
557 {
558         GByteArray *array;
559         guchar buf[BUFSIZ];
560         gint n_read;
561         gchar *str;
562
563         cm_return_val_if_fail(fp != NULL, NULL);
564
565         array = g_byte_array_new();
566
567         while ((n_read = claws_fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
568                 if (n_read < sizeof(buf) && claws_ferror(fp))
569                         break;
570                 g_byte_array_append(array, buf, n_read);
571         }
572
573         if (claws_ferror(fp)) {
574                 FILE_OP_ERROR("file stream", "claws_fread");
575                 g_byte_array_free(array, TRUE);
576                 return NULL;
577         }
578
579         buf[0] = '\0';
580         g_byte_array_append(array, buf, 1);
581         str = (gchar *)array->data;
582         g_byte_array_free(array, FALSE);
583
584         if (recode && !g_utf8_validate(str, -1, NULL)) {
585                 const gchar *src_codeset, *dest_codeset;
586                 gchar *tmp = NULL;
587                 src_codeset = conv_get_locale_charset_str();
588                 dest_codeset = CS_UTF_8;
589                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
590                 g_free(str);
591                 str = tmp;
592         }
593
594         return str;
595 }
596
597 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
598 {
599         FILE *fp;
600         gchar *str;
601         GStatBuf s;
602 #ifndef G_OS_WIN32
603         gint fd, err;
604         struct timeval timeout = {1, 0};
605         fd_set fds;
606         int fflags = 0;
607 #endif
608
609         cm_return_val_if_fail(file != NULL, NULL);
610
611         if (g_stat(file, &s) != 0) {
612                 FILE_OP_ERROR(file, "stat");
613                 return NULL;
614         }
615         if (S_ISDIR(s.st_mode)) {
616                 g_warning("%s: is a directory", file);
617                 return NULL;
618         }
619
620 #ifdef G_OS_WIN32
621         fp = claws_fopen (file, "rb");
622         if (fp == NULL) {
623                 FILE_OP_ERROR(file, "open");
624                 return NULL;
625         }
626 #else     
627         /* test whether the file is readable without blocking */
628         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
629         if (fd == -1) {
630                 FILE_OP_ERROR(file, "open");
631                 return NULL;
632         }
633
634         FD_ZERO(&fds);
635         FD_SET(fd, &fds);
636
637         /* allow for one second */
638         err = select(fd+1, &fds, NULL, NULL, &timeout);
639         if (err <= 0 || !FD_ISSET(fd, &fds)) {
640                 if (err < 0) {
641                         FILE_OP_ERROR(file, "select");
642                 } else {
643                         g_warning("%s: doesn't seem readable", file);
644                 }
645                 close(fd);
646                 return NULL;
647         }
648         
649         /* Now clear O_NONBLOCK */
650         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
651                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
652                 close(fd);
653                 return NULL;
654         }
655         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
656                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
657                 close(fd);
658                 return NULL;
659         }
660         
661         /* get the FILE pointer */
662         fp = claws_fdopen(fd, "rb");
663
664         if (fp == NULL) {
665                 FILE_OP_ERROR(file, "claws_fdopen");
666                 close(fd); /* if fp isn't NULL, we'll use claws_fclose instead! */
667                 return NULL;
668         }
669 #endif
670
671         str = file_read_stream_to_str_full(fp, recode);
672
673         claws_fclose(fp);
674
675         return str;
676 }
677
678 gchar *file_read_to_str(const gchar *file)
679 {
680         return file_read_to_str_full(file, TRUE);
681 }
682 gchar *file_read_stream_to_str(FILE *fp)
683 {
684         return file_read_stream_to_str_full(fp, TRUE);
685 }
686
687 gchar *file_read_to_str_no_recode(const gchar *file)
688 {
689         return file_read_to_str_full(file, FALSE);
690 }
691 gchar *file_read_stream_to_str_no_recode(FILE *fp)
692 {
693         return file_read_stream_to_str_full(fp, FALSE);
694 }
695
696 gint rename_force(const gchar *oldpath, const gchar *newpath)
697 {
698 #ifndef G_OS_UNIX
699         if (!is_file_entry_exist(oldpath)) {
700                 errno = ENOENT;
701                 return -1;
702         }
703         if (is_file_exist(newpath)) {
704                 if (claws_unlink(newpath) < 0)
705                         FILE_OP_ERROR(newpath, "unlink");
706         }
707 #endif
708         return g_rename(oldpath, newpath);
709 }
710
711 gint copy_dir(const gchar *src, const gchar *dst)
712 {
713         GDir *dir;
714         const gchar *name;
715
716         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
717                 g_warning("failed to open directory: %s", src);
718                 return -1;
719         }
720
721         if (make_dir(dst) < 0)
722                 return -1;
723
724         while ((name = g_dir_read_name(dir)) != NULL) {
725                 gchar *old_file, *new_file;
726                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
727                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
728                 debug_print("copying: %s -> %s\n", old_file, new_file);
729                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
730                         gint r = copy_file(old_file, new_file, TRUE);
731                         if (r < 0) {
732                                 g_dir_close(dir);
733                                 return r;
734                         }
735                 }
736 #ifndef G_OS_WIN32
737                 /* Windows has no symlinks.  Or well, Vista seems to
738                    have something like this but the semantics might be
739                    different.  Thus we don't use it under Windows. */
740                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
741                         GError *error = NULL;
742                         gint r = 0;
743                         gchar *target = g_file_read_link(old_file, &error);
744                         if (target)
745                                 r = symlink(target, new_file);
746                         g_free(target);
747                         if (r < 0) {
748                                 g_dir_close(dir);
749                                 return r;
750                         }
751                  }
752 #endif /*G_OS_WIN32*/
753                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
754                         gint r = copy_dir(old_file, new_file);
755                         if (r < 0) {
756                                 g_dir_close(dir);
757                                 return r;
758                         }
759                 }
760         }
761         g_dir_close(dir);
762         return 0;
763 }
764
765 gint change_file_mode_rw(FILE *fp, const gchar *file)
766 {
767 #if HAVE_FCHMOD
768         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
769 #else
770         return g_chmod(file, S_IRUSR|S_IWUSR);
771 #endif
772 }
773
774 FILE *my_tmpfile(void)
775 {
776         const gchar suffix[] = ".XXXXXX";
777         const gchar *tmpdir;
778         guint tmplen;
779         const gchar *progname;
780         guint proglen;
781         gchar *fname;
782         gint fd;
783         FILE *fp;
784 #ifndef G_OS_WIN32
785         gchar buf[2]="\0";
786 #endif
787
788         tmpdir = get_tmp_dir();
789         tmplen = strlen(tmpdir);
790         progname = g_get_prgname();
791         if (progname == NULL)
792                 progname = "claws-mail";
793         proglen = strlen(progname);
794         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
795                 return tmpfile());
796
797         memcpy(fname, tmpdir, tmplen);
798         fname[tmplen] = G_DIR_SEPARATOR;
799         memcpy(fname + tmplen + 1, progname, proglen);
800         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
801
802         fd = g_mkstemp(fname);
803         if (fd < 0)
804                 return tmpfile();
805
806 #ifndef G_OS_WIN32
807         claws_unlink(fname);
808         
809         /* verify that we can write in the file after unlinking */
810         if (write(fd, buf, 1) < 0) {
811                 close(fd);
812                 return tmpfile();
813         }
814         
815 #endif
816
817         fp = claws_fdopen(fd, "w+b");
818         if (!fp)
819                 close(fd);
820         else {
821                 rewind(fp);
822                 return fp;
823         }
824
825         return tmpfile();
826 }
827
828 FILE *my_tmpfile_with_len(size_t len)
829 {
830 #if HAVE_FMEMOPEN
831         FILE *tmpfp = fmemopen(NULL, len, "w+b");
832
833         if (tmpfp == NULL) {
834                 return my_tmpfile();
835         }
836
837         return tmpfp;
838 #else
839         return my_tmpfile();
840 #endif
841 }
842
843 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
844 {
845         int fd;
846         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
847         fd = g_mkstemp(*filename);
848         if (fd < 0)
849                 return NULL;
850         return claws_fdopen(fd, "w+");
851 }
852
853 FILE *str_open_as_stream(const gchar *str)
854 {
855         FILE *fp;
856         size_t len;
857
858         cm_return_val_if_fail(str != NULL, NULL);
859
860         fp = my_tmpfile();
861         if (!fp) {
862                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
863                 return NULL;
864         }
865
866         len = strlen(str);
867         if (len == 0) return fp;
868
869         if (claws_fwrite(str, 1, len, fp) != len) {
870                 FILE_OP_ERROR("str_open_as_stream", "claws_fwrite");
871                 claws_fclose(fp);
872                 return NULL;
873         }
874
875         rewind(fp);
876         return fp;
877 }
878
879 #endif