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