Do not send debug data to terminal
[geeqie.git] / src / ui_fileops.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <pwd.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/param.h>
32 #include <dirent.h>
33 #include <utime.h>
34
35 #include <glib.h>
36 #include <gtk/gtk.h>    /* for locale warning dialog */
37
38 #include "main.h"
39 #include "ui_fileops.h"
40
41 #include "ui_utildlg.h" /* for locale warning dialog */
42 #include "md5-util.h"
43
44 #include "filefilter.h"
45 #include "secure_save.h"
46
47 /*
48  *-----------------------------------------------------------------------------
49  * generic file information and manipulation routines (public)
50  *-----------------------------------------------------------------------------
51  */
52
53
54
55 void print_term(const gchar *text_utf8)
56 {
57         gchar *text_l;
58
59         text_l = g_locale_from_utf8(text_utf8, -1, NULL, NULL, NULL);
60         if(command_line && command_line->ssi)
61                 secure_fputs(command_line->ssi, (text_l) ? text_l : text_utf8);
62         g_free(text_l);
63 }
64
65 static void encoding_dialog(const gchar *path)
66 {
67         static gboolean warned_user = FALSE;
68         GenericDialog *gd;
69         GString *string;
70         const gchar *lc;
71         const gchar *bf;
72
73         if (warned_user) return;
74         warned_user = TRUE;
75
76         lc = getenv("LANG");
77         bf = getenv("G_BROKEN_FILENAMES");
78
79         string = g_string_new("");
80         g_string_append(string, _("One or more filenames are not encoded with the preferred locale character set.\n"));
81         g_string_append_printf(string, _("Operations on, and display of these files with %s may not succeed.\n"), PACKAGE);
82         g_string_append(string, "\n");
83         g_string_append(string, _("If your filenames are not encoded in utf-8, try setting the environment variable G_BROKEN_FILENAMES=1\n"));
84         if (bf)
85                 g_string_append_printf(string, _("It appears G_BROKEN_FILENAMES is set to %s\n"), bf);
86         else
87                 g_string_append(string, _("It appears G_BROKEN_FILENAMES is not set\n"));
88         g_string_append(string, "\n");
89         g_string_append_printf(string, _("The locale appears to be set to \"%s\"\n(set by the LANG environment variable)\n"), (lc) ? lc : "undefined");
90         if (lc && (strstr(lc, "UTF-8") || strstr(lc, "utf-8")))
91                 {
92                 gchar *name;
93                 name = g_convert(path, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL);
94                 string = g_string_append(string, _("\nPreferred encoding appears to be UTF-8, however the file:\n"));
95                 g_string_append_printf(string, "\"%s\"\n", (name) ? name : _("[name not displayable]"));
96
97                 if (g_utf8_validate(path, -1, NULL))
98                         g_string_append_printf(string, _("\"%s\" is encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
99                 else
100                         g_string_append_printf(string, _("\"%s\" is not encoded in valid UTF-8."), (name) ? name : _("[name not displayable]"));
101                 g_string_append(string, "\n");
102                 g_free(name);
103                 }
104
105         gd = generic_dialog_new(_("Filename encoding locale mismatch"),
106                                 "locale warning", NULL, TRUE, NULL, NULL);
107         generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE);
108
109         generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING,
110                                    _("Filename encoding locale mismatch"), string->str, TRUE);
111
112         gtk_widget_show(gd->dialog);
113
114         g_string_free(string, TRUE);
115 }
116
117 #if GQ_DEBUG_PATH_UTF8
118 gchar *path_to_utf8_debug(const gchar *path, const gchar *file, gint line)
119 #else
120 gchar *path_to_utf8(const gchar *path)
121 #endif
122 {
123         gchar *utf8;
124         GError *error = NULL;
125
126         if (!path) return NULL;
127
128         utf8 = g_filename_to_utf8(path, -1, NULL, NULL, &error);
129         if (error)
130                 {
131 #if GQ_DEBUG_PATH_UTF8
132                 log_printf("%s:%d: Unable to convert filename to UTF-8:\n%s\n%s\n", file, line, path, error->message);
133 #else
134                 log_printf("Unable to convert filename to UTF-8:\n%s\n%s\n", path, error->message);
135 #endif
136                 g_error_free(error);
137                 encoding_dialog(path);
138                 }
139         if (!utf8)
140                 {
141                 /* just let it through, but bad things may happen */
142                 utf8 = g_strdup(path);
143                 }
144
145         return utf8;
146 }
147
148 #if GQ_DEBUG_PATH_UTF8
149 gchar *path_from_utf8_debug(const gchar *utf8, const gchar *file, gint line)
150 #else
151 gchar *path_from_utf8(const gchar *utf8)
152 #endif
153 {
154         gchar *path;
155         GError *error = NULL;
156
157         if (!utf8) return NULL;
158
159         path = g_filename_from_utf8(utf8, -1, NULL, NULL, &error);
160         if (error)
161                 {
162 #if GQ_DEBUG_PATH_UTF8
163                 log_printf("%s:%d: Unable to convert filename to locale from UTF-8:\n%s\n%s\n", file, line, utf8, error->message);
164 #else
165                 log_printf("Unable to convert filename to locale from UTF-8:\n%s\n%s\n", utf8, error->message);
166 #endif
167                 g_error_free(error);
168                 }
169         if (!path)
170                 {
171                 /* if invalid UTF-8, text probaby still in original form, so just copy it */
172                 path = g_strdup(utf8);
173                 }
174
175         return path;
176 }
177
178 /* first we try the HOME environment var, if that doesn't work, we try g_get_homedir(). */
179 const gchar *homedir(void)
180 {
181         static gchar *home = NULL;
182
183         if (!home)
184                 home = path_to_utf8(getenv("HOME"));
185
186         if (!home)
187                 home = path_to_utf8(g_get_home_dir());
188
189         return home;
190 }
191
192 static gchar *xdg_dir_get(const gchar *key, const gchar *fallback)
193 {
194         gchar *dir = getenv(key);
195
196         if (!dir || dir[0] == '\0')
197                 {
198                 return g_build_filename(homedir(), fallback, NULL);
199                 }
200
201         return path_to_utf8(dir);
202 }
203
204 const gchar *xdg_data_home_get(void)
205 {
206         static const gchar *xdg_data_home = NULL;
207
208         if (xdg_data_home) return xdg_data_home;
209
210         xdg_data_home = xdg_dir_get("XDG_DATA_HOME", ".local/share");
211
212         return xdg_data_home;
213 }
214
215 const gchar *xdg_config_home_get(void)
216 {
217         static const gchar *xdg_config_home = NULL;
218
219         if (xdg_config_home) return xdg_config_home;
220
221         xdg_config_home = xdg_dir_get("XDG_CONFIG_HOME", ".config");
222
223         return xdg_config_home;
224 }
225
226 const gchar *xdg_cache_home_get(void)
227 {
228         static const gchar *xdg_cache_home = NULL;
229
230         if (xdg_cache_home) return xdg_cache_home;
231
232         xdg_cache_home = xdg_dir_get("XDG_CACHE_HOME", ".cache");
233
234         return xdg_cache_home;
235 }
236
237 const gchar *get_rc_dir(void)
238 {
239         static gchar *rc_dir = NULL;
240
241         if (rc_dir) return rc_dir;
242
243         if (USE_XDG)
244                 {
245                 rc_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, NULL);
246                 }
247         else
248                 {
249                 rc_dir = g_build_filename(homedir(), GQ_RC_DIR, NULL);
250                 }
251
252         return rc_dir;
253 }
254
255 const gchar *get_collections_dir(void)
256 {
257         static gchar *collections_dir = NULL;
258
259         if (collections_dir) return collections_dir;
260
261         if (USE_XDG)
262                 {
263                 collections_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_COLLECTIONS_DIR, NULL);
264                 }
265         else
266                 {
267                 collections_dir = g_build_filename(get_rc_dir(), GQ_COLLECTIONS_DIR, NULL);
268                 }
269
270         return collections_dir;
271 }
272
273 const gchar *get_trash_dir(void)
274 {
275         static gchar *trash_dir = NULL;
276
277         if (trash_dir) return trash_dir;
278
279         if (USE_XDG)
280                 {
281                 trash_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_TRASH_DIR, NULL);
282                 }
283         else
284                 {
285                 trash_dir = g_build_filename(get_rc_dir(), GQ_TRASH_DIR, NULL);
286         }
287
288         return trash_dir;
289 }
290
291 gboolean stat_utf8(const gchar *s, struct stat *st)
292 {
293         gchar *sl;
294         gboolean ret;
295
296         if (!s) return FALSE;
297         sl = path_from_utf8(s);
298         ret = (stat(sl, st) == 0);
299         g_free(sl);
300
301         return ret;
302 }
303
304 gboolean lstat_utf8(const gchar *s, struct stat *st)
305 {
306         gchar *sl;
307         gboolean ret;
308
309         if (!s) return FALSE;
310         sl = path_from_utf8(s);
311         ret = (lstat(sl, st) == 0);
312         g_free(sl);
313
314         return ret;
315 }
316
317 gboolean isname(const gchar *s)
318 {
319         struct stat st;
320
321         return stat_utf8(s, &st);
322 }
323
324 gboolean isfile(const gchar *s)
325 {
326         struct stat st;
327
328         return (stat_utf8(s, &st) && S_ISREG(st.st_mode));
329 }
330
331 gboolean isdir(const gchar *s)
332 {
333         struct stat st;
334
335         return (stat_utf8(s, &st) && S_ISDIR(st.st_mode));
336 }
337
338 gboolean islink(const gchar *s)
339 {
340         struct stat st;
341
342         return (lstat_utf8(s, &st) && S_ISLNK(st.st_mode));
343 }
344
345 gint64 filesize(const gchar *s)
346 {
347         struct stat st;
348
349         if (!stat_utf8(s, &st)) return 0;
350         return st.st_size;
351 }
352
353 time_t filetime(const gchar *s)
354 {
355         struct stat st;
356
357         if (!stat_utf8(s, &st)) return 0;
358         return st.st_mtime;
359 }
360
361 gboolean filetime_set(const gchar *s, time_t tval)
362 {
363         gboolean ret = FALSE;
364
365         if (tval > 0)
366                 {
367                 struct utimbuf ut;
368                 gchar *sl;
369
370                 ut.actime = ut.modtime = tval;
371
372                 sl = path_from_utf8(s);
373                 ret = (utime(sl, &ut) == 0);
374                 g_free(sl);
375                 }
376
377         return ret;
378 }
379
380 gboolean is_readable_file(const gchar *s)
381 {
382         if (!s || !s[0] || !isfile(s)) return FALSE;
383         return access_file(s, R_OK);
384 }
385
386 gboolean access_file(const gchar *s, gint mode)
387 {
388         gchar *sl;
389         gint ret;
390
391         if (!s || !s[0]) return FALSE;
392
393         sl = path_from_utf8(s);
394         ret = (access(sl, mode) == 0);
395         g_free(sl);
396
397         return ret;
398 }
399
400 gboolean unlink_file(const gchar *s)
401 {
402         gchar *sl;
403         gboolean ret;
404
405         if (!s) return FALSE;
406
407         sl = path_from_utf8(s);
408         ret = (unlink(sl) == 0);
409         g_free(sl);
410
411         return ret;
412 }
413
414 gboolean symlink_utf8(const gchar *source, const gchar *target)
415 {
416         gchar *sl;
417         gchar *tl;
418         gboolean ret;
419
420         if (!source || !target) return FALSE;
421
422         sl = path_from_utf8(source);
423         tl = path_from_utf8(target);
424
425         ret = (symlink(sl, tl) == 0);
426
427         g_free(sl);
428         g_free(tl);
429
430         return ret;
431 }
432
433 gboolean mkdir_utf8(const gchar *s, gint mode)
434 {
435         gchar *sl;
436         gboolean ret;
437
438         if (!s) return FALSE;
439
440         sl = path_from_utf8(s);
441         ret = (mkdir(sl, mode) == 0);
442         g_free(sl);
443         return ret;
444 }
445
446 gboolean rmdir_utf8(const gchar *s)
447 {
448         gchar *sl;
449         gboolean ret;
450
451         if (!s) return FALSE;
452
453         sl = path_from_utf8(s);
454         ret = (rmdir(sl) == 0);
455         g_free(sl);
456
457         return ret;
458 }
459
460 gboolean copy_file_attributes(const gchar *s, const gchar *t, gint perms, gint mtime)
461 {
462         struct stat st;
463         gchar *sl, *tl;
464         gboolean ret = FALSE;
465
466         if (!s || !t) return FALSE;
467
468         sl = path_from_utf8(s);
469         tl = path_from_utf8(t);
470
471         if (stat(sl, &st) == 0)
472                 {
473                 struct utimbuf tb;
474
475                 ret = TRUE;
476
477                 /* set the dest file attributes to that of source (ignoring errors) */
478
479                 if (perms)
480                         {
481                         ret = chown(tl, st.st_uid, st.st_gid);
482                         /* Ignores chown errors, while still doing chown
483                            (so root still can copy files preserving ownership) */
484                         ret = TRUE;
485                         if (chmod(tl, st.st_mode) < 0) {
486                             struct stat st2;
487                             if (stat(tl, &st2) != 0 || st2.st_mode != st.st_mode) {
488                                 ret = FALSE;
489                             }
490                         }
491                         }
492
493                 tb.actime = st.st_atime;
494                 tb.modtime = st.st_mtime;
495                 if (mtime && utime(tl, &tb) < 0) ret = FALSE;
496                 }
497
498         g_free(sl);
499         g_free(tl);
500
501         return ret;
502 }
503
504 /* paths are in filesystem encoding */
505 static gboolean hard_linked(const gchar *a, const gchar *b)
506 {
507         struct stat sta;
508         struct stat stb;
509
510         if (stat(a, &sta) !=  0 || stat(b, &stb) != 0) return FALSE;
511
512         return (sta.st_dev == stb.st_dev &&
513                 sta.st_ino == stb.st_ino);
514 }
515
516 gboolean copy_file(const gchar *s, const gchar *t)
517 {
518         FILE *fi = NULL;
519         FILE *fo = NULL;
520         gchar *sl = NULL;
521         gchar *tl = NULL;
522         gchar *randname = NULL;
523         gchar buf[16384];
524         size_t b;
525         gint ret = FALSE;
526         gint fd = -1;
527
528         sl = path_from_utf8(s);
529         tl = path_from_utf8(t);
530
531         if (hard_linked(sl, tl))
532                 {
533                 ret = TRUE;
534                 goto end;
535                 }
536
537         fi = fopen(sl, "rb");
538         if (!fi) goto end;
539
540         /* First we write to a temporary file, then we rename it on success,
541            and attributes from original file are copied */
542         randname = g_strconcat(tl, ".tmp_XXXXXX", NULL);
543         if (!randname) goto end;
544
545         fd = g_mkstemp(randname);
546         if (fd == -1) goto end;
547
548         fo = fdopen(fd, "wb");
549         if (!fo) {
550                 close(fd);
551                 goto end;
552         }
553
554         while ((b = fread(buf, sizeof(gchar), sizeof(buf), fi)) && b != 0)
555                 {
556                 if (fwrite(buf, sizeof(gchar), b, fo) != b)
557                         {
558                         unlink(randname);
559                         goto end;
560                         }
561                 }
562
563         fclose(fi); fi = NULL;
564         fclose(fo); fo = NULL;
565
566         if (rename(randname, tl) < 0) {
567                 unlink(randname);
568                 goto end;
569         }
570
571         ret = copy_file_attributes(s, t, TRUE, TRUE);
572
573 end:
574         if (fi) fclose(fi);
575         if (fo) fclose(fo);
576         if (sl) g_free(sl);
577         if (tl) g_free(tl);
578         if (randname) g_free(randname);
579         return ret;
580 }
581
582 gboolean move_file(const gchar *s, const gchar *t)
583 {
584         gchar *sl, *tl;
585         gboolean ret = TRUE;
586
587         if (!s || !t) return FALSE;
588
589         sl = path_from_utf8(s);
590         tl = path_from_utf8(t);
591         if (rename(sl, tl) < 0)
592                 {
593                 /* this may have failed because moving a file across filesystems
594                 was attempted, so try copy and delete instead */
595                 if (copy_file(s, t))
596                         {
597                         if (unlink(sl) < 0)
598                                 {
599                                 /* err, now we can't delete the source file so return FALSE */
600                                 ret = FALSE;
601                                 }
602                         }
603                 else
604                         {
605                         ret = FALSE;
606                         }
607                 }
608         g_free(sl);
609         g_free(tl);
610
611         return ret;
612 }
613
614 gboolean rename_file(const gchar *s, const gchar *t)
615 {
616         gchar *sl, *tl;
617         gboolean ret;
618
619         if (!s || !t) return FALSE;
620
621         sl = path_from_utf8(s);
622         tl = path_from_utf8(t);
623         ret = (rename(sl, tl) == 0);
624         g_free(sl);
625         g_free(tl);
626
627         return ret;
628 }
629
630 gchar *get_current_dir(void)
631 {
632         gchar *pathl;
633         gchar *path8;
634
635         pathl = g_get_current_dir();
636         path8 = path_to_utf8(pathl);
637         g_free(pathl);
638
639         return path8;
640 }
641
642 void string_list_free(GList *list)
643 {
644         g_list_foreach(list, (GFunc)g_free, NULL);
645         g_list_free(list);
646 }
647
648 GList *string_list_copy(const GList *list)
649 {
650         GList *new_list = NULL;
651         GList *work = (GList *) list;
652
653         while (work)
654                 {
655                 gchar *path;
656
657                 path = work->data;
658                 work = work->next;
659
660                 new_list = g_list_prepend(new_list, g_strdup(path));
661                 }
662
663         return g_list_reverse(new_list);
664 }
665
666 gchar *unique_filename(const gchar *path, const gchar *ext, const gchar *divider, gboolean pad)
667 {
668         gchar *unique;
669         gint n = 1;
670
671         if (!ext) ext = "";
672         if (!divider) divider = "";
673
674         unique = g_strconcat(path, ext, NULL);
675         while (isname(unique))
676                 {
677                 g_free(unique);
678                 if (pad)
679                         {
680                         unique = g_strdup_printf("%s%s%03d%s", path, divider, n, ext);
681                         }
682                 else
683                         {
684                         unique = g_strdup_printf("%s%s%d%s", path, divider, n, ext);
685                         }
686                 n++;
687                 if (n > 999)
688                         {
689                         /* well, we tried */
690                         g_free(unique);
691                         return NULL;
692                         }
693                 }
694
695         return unique;
696 }
697
698 gchar *unique_filename_simple(const gchar *path)
699 {
700         gchar *unique;
701         const gchar *name;
702         const gchar *ext;
703
704         if (!path) return NULL;
705
706         name = filename_from_path(path);
707         if (!name) return NULL;
708
709         ext = registered_extension_from_path(name);
710
711         if (!ext)
712                 {
713                 unique = unique_filename(path, NULL, "_", TRUE);
714                 }
715         else
716                 {
717                 gchar *base;
718
719                 base = remove_extension_from_path(path);
720                 unique = unique_filename(base, ext, "_", TRUE);
721                 g_free(base);
722                 }
723
724         return unique;
725 }
726
727 const gchar *filename_from_path(const gchar *path)
728 {
729         const gchar *base;
730
731         if (!path) return NULL;
732
733         base = strrchr(path, G_DIR_SEPARATOR);
734         if (base) return base + 1;
735
736         return path;
737 }
738
739 gchar *remove_level_from_path(const gchar *path)
740 {
741         const gchar *base;
742
743         if (!path) return NULL;
744
745         base = strrchr(path, G_DIR_SEPARATOR);
746         if (base) return g_strndup(path, strlen(path)-strlen(base));
747
748         return NULL;
749 }
750
751 gboolean file_extension_match(const gchar *path, const gchar *ext)
752 {
753         gint p;
754         gint e;
755
756         if (!path) return FALSE;
757         if (!ext) return TRUE;
758
759         p = strlen(path);
760         e = strlen(ext);
761
762         /* FIXME: utf8 */
763         return (p > e && g_ascii_strncasecmp(path + p - e, ext, e) == 0);
764 }
765
766 gchar *remove_extension_from_path(const gchar *path)
767 {
768         const gchar *reg_ext;
769
770         if (!path) return NULL;
771
772         reg_ext = registered_extension_from_path(path);
773
774         return g_strndup(path, strlen(path) - (reg_ext == NULL ? 0 : strlen(reg_ext)));
775 }
776
777 void parse_out_relatives(gchar *path)
778 {
779         gint s, t;
780
781         if (!path) return;
782
783         s = t = 0;
784
785         while (path[s] != '\0')
786                 {
787                 if (path[s] == G_DIR_SEPARATOR && path[s+1] == '.')
788                         {
789                         /* /. occurence, let's see more */
790                         gint p = s + 2;
791
792                         if (path[p] == G_DIR_SEPARATOR || path[p] == '\0')
793                                 {
794                                 /* /./ or /., just skip this part */
795                                 s = p;
796                                 continue;
797                                 }
798                         else if (path[p] == '.' && (path[p+1] == G_DIR_SEPARATOR || path[p+1] == '\0'))
799                                 {
800                                 /* /../ or /.., remove previous part, ie. /a/b/../ becomes /a/ */
801                                 s = p + 1;
802                                 if (t > 0) t--;
803                                 while (path[t] != G_DIR_SEPARATOR && t > 0) t--;
804                                 continue;
805                                 }
806                         }
807
808                 if (s != t) path[t] = path[s];
809                 t++;
810                 s++;
811                 }
812
813         if (t == 0 && path[t] == G_DIR_SEPARATOR) t++;
814         if (t > 1 && path[t-1] == G_DIR_SEPARATOR) t--;
815         path[t] = '\0';
816 }
817
818 gboolean file_in_path(const gchar *name)
819 {
820         gchar *path;
821         gchar *namel;
822         gint p, l;
823         gboolean ret = FALSE;
824
825         if (!name) return FALSE;
826         path = g_strdup(getenv("PATH"));
827         if (!path) return FALSE;
828         namel = path_from_utf8(name);
829
830         p = 0;
831         l = strlen(path);
832         while (p < l && !ret)
833                 {
834                 gchar *f;
835                 gint e = p;
836                 while (path[e] != ':' && path[e] != '\0') e++;
837                 path[e] = '\0';
838                 e++;
839                 f = g_build_filename(path + p, namel, NULL);
840                 if (isfile(f)) ret = TRUE;
841                 g_free(f);
842                 p = e;
843                 }
844         g_free(namel);
845         g_free(path);
846
847         return ret;
848 }
849
850 gboolean recursive_mkdir_if_not_exists(const gchar *path, mode_t mode)
851 {
852         if (!path) return FALSE;
853
854         if (!isdir(path))
855                 {
856                 gchar *npath = g_strdup(path);
857                 gchar *p = npath;
858
859                 while (p[0] != '\0')
860                         {
861                         p++;
862                         if (p[0] == G_DIR_SEPARATOR || p[0] == '\0')
863                                 {
864                                 gboolean end = TRUE;
865
866                                 if (p[0] != '\0')
867                                         {
868                                         p[0] = '\0';
869                                         end = FALSE;
870                                         }
871
872                                 if (!isdir(npath))
873                                         {
874                                         DEBUG_1("creating sub dir:%s", npath);
875                                         if (!mkdir_utf8(npath, mode))
876                                                 {
877                                                 log_printf("create dir failed: %s\n", npath);
878                                                 g_free(npath);
879                                                 return FALSE;
880                                                 }
881                                         }
882
883                                 if (!end) p[0] = G_DIR_SEPARATOR;
884                                 }
885                         }
886                 g_free(npath);
887                 }
888
889         return TRUE;
890 }
891
892 /* does filename utf8 to filesystem encoding first */
893 gboolean md5_get_digest_from_file_utf8(const gchar *path, guchar digest[16])
894 {
895         gboolean success;
896         gchar *pathl;
897
898         pathl = path_from_utf8(path);
899         success = md5_get_digest_from_file(pathl, digest);
900         g_free(pathl);
901
902         return success;
903 }
904
905
906 gchar *md5_text_from_file_utf8(const gchar *path, const gchar *error_text)
907 {
908         guchar digest[16];
909
910         if (!md5_get_digest_from_file_utf8(path, digest)) return g_strdup(error_text);
911
912         return md5_digest_to_text(digest);
913 }
914
915
916 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */