plug leaks, leaker tweaks, lang for effect info, c41 spiffs, wm probe tweaks
[goodguy/history.git] / cinelerra-5.1 / guicast / filesystem.C
1 /*
2  * CINELERRA
3  * Copyright (C) 2010 Adam Williams <broadcast at earthling dot net>
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
20
21 #include "bcsignals.h"
22 #include "cstrdup.h"
23 #include <dirent.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <utime.h>
35
36
37 #include "filesystem.h"
38
39 FileItem::FileItem()
40 {
41         path = 0;
42         name = 0;
43         reset();
44 }
45
46 FileItem::FileItem(char *path, char *name, int is_dir,
47         int64_t size, int month, int day, int year,
48         int64_t calendar_time, int item_no)
49 {
50         this->path = new char[strlen(path)];
51         this->name = new char[strlen(name)];
52         if(this->path) strcpy(this->path, path);
53         if(this->name) strcpy(this->name, name);
54         this->is_dir = is_dir;
55         this->size = size;
56         this->month = month;
57         this->day = day;
58         this->year = year;
59         this->calendar_time = calendar_time;
60         this->item_no = item_no;
61 }
62
63 FileItem::~FileItem()
64 {
65         reset();
66 }
67
68 int FileItem::reset()
69 {
70         if(this->path) delete [] this->path;
71         if(this->name) delete [] this->name;
72         path = 0;
73         name = 0;
74         is_dir = 0;
75         size = 0;
76         month = 0;
77         day = 0;
78         year = 0;
79         calendar_time = 0;
80         item_no = -1;
81         return 0;
82 }
83
84 int FileItem::set_path(char *path)
85 {
86         if(this->path) delete [] this->path;
87         this->path = new char[strlen(path) + 1];
88         strcpy(this->path, path);
89         return 0;
90 }
91
92 int FileItem::set_name(char *name)
93 {
94         if(this->name) delete [] this->name;
95         this->name = new char[strlen(name) + 1];
96         strcpy(this->name, name);
97         return 0;
98 }
99
100
101
102 FileSystem::FileSystem()
103 {
104         reset_parameters();
105         (void)getcwd(current_dir, BCTEXTLEN);
106 }
107
108 FileSystem::~FileSystem()
109 {
110         delete_directory();
111 }
112
113 int FileSystem::reset_parameters()
114 {
115         show_all_files = 0;
116         want_directory = 0;
117         strcpy(filter, "");
118         strcpy(current_dir, "");
119         sort_order = SORT_ASCENDING;
120         sort_field = SORT_PATH;
121         return 0;
122 }
123
124 int FileSystem::delete_directory()
125 {
126         dir_list.remove_all_objects();
127         return 0;
128 }
129
130 int FileSystem::set_sort_order(int value)
131 {
132         this->sort_order = value;
133         return 0;
134 }
135
136 int FileSystem::set_sort_field(int field)
137 {
138         this->sort_field = field;
139         return 0;
140 }
141
142 // filename.with.dots.extension
143 //   becomes
144 // extension.dots.with.filename
145
146 int FileSystem::dot_reverse_filename(char *out, const char *in)
147 {
148         int i, i2, j=0, lastdot;
149         lastdot = strlen(in);
150         for ( i=strlen(in); --i >= 0; ) {
151                 if (in[i] == '.') {
152                         i2 = i+1;
153                                 while (i2 < lastdot)
154                                         out[j++] = in[i2++];
155                         out[j++] = in[i];
156                         lastdot = i;
157                 }
158         }
159         if (in[++i] != '.') {
160                 while (i < lastdot) out[j++] = in[i++];
161         }
162         out[j++] = '\0';
163         return 0;
164 }
165
166 int FileSystem::path_ascending(const void *ptr1, const void *ptr2)
167 {
168         FileItem *item1 = *(FileItem**)ptr1;
169         FileItem *item2 = *(FileItem**)ptr2;
170 //printf("path_ascending %p %p\n", ptr1, ptr2);
171         int ret = strcasecmp(item1->name, item2->name);
172         if( ret != 0 ) return ret;
173         return item1->item_no - item2->item_no;
174 }
175
176 int FileSystem::path_descending(const void *ptr1, const void *ptr2)
177 {
178         FileItem *item1 = *(FileItem**)ptr1;
179         FileItem *item2 = *(FileItem**)ptr2;
180         int ret = strcasecmp(item2->name, item1->name);
181         if( ret != 0 ) return ret;
182         return item2->item_no - item1->item_no;
183 }
184
185
186 int FileSystem::size_ascending(const void *ptr1, const void *ptr2)
187 {
188         FileItem *item1 = *(FileItem**)ptr1;
189         FileItem *item2 = *(FileItem**)ptr2;
190         return item1->size == item2->size ?
191                 item1->item_no - item2->item_no :
192                 item1->size > item2->size;
193 }
194
195 int FileSystem::size_descending(const void *ptr1, const void *ptr2)
196 {
197         FileItem *item1 = *(FileItem**)ptr1;
198         FileItem *item2 = *(FileItem**)ptr2;
199         return item2->size == item1->size ?
200                 item2->item_no - item1->item_no :
201                 item2->size > item1->size;
202 }
203
204
205 int FileSystem::date_ascending(const void *ptr1, const void *ptr2)
206 {
207         FileItem *item1 = *(FileItem**)ptr1;
208         FileItem *item2 = *(FileItem**)ptr2;
209         return item1->calendar_time == item2->calendar_time ?
210                 item1->item_no - item2->item_no :
211                 item1->calendar_time > item2->calendar_time;
212 }
213
214 int FileSystem::date_descending(const void *ptr1, const void *ptr2)
215 {
216         FileItem *item1 = *(FileItem**)ptr1;
217         FileItem *item2 = *(FileItem**)ptr2;
218         return item2->calendar_time == item1->calendar_time ?
219                 item2->item_no - item1->item_no :
220                 item2->calendar_time > item1->calendar_time;
221 }
222
223 int FileSystem::ext_ascending(const void *ptr1, const void *ptr2)
224 {
225         FileItem *item1 = *(FileItem**)ptr1;
226         FileItem *item2 = *(FileItem**)ptr2;
227         char *ext1 = strrchr(item1->name,'.');
228         if( !ext1 ) ext1 = item1->name;
229         char *ext2 = strrchr(item2->name,'.');
230         if( !ext2 ) ext2 = item2->name;
231         int ret = strcasecmp(ext1, ext2);
232         if( ret ) return ret;
233         if( item1->item_no >= 0 && item2->item_no >= 0 )
234                 return item1->item_no - item2->item_no;
235         char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
236         dot_reverse_filename(dotreversedname1,item1->name);
237         dot_reverse_filename(dotreversedname2,item2->name);
238         return strcasecmp(dotreversedname1, dotreversedname2);
239 }
240
241 int FileSystem::ext_descending(const void *ptr1, const void *ptr2)
242 {
243         FileItem *item1 = *(FileItem**)ptr1;
244         FileItem *item2 = *(FileItem**)ptr2;
245         char *ext1 = strrchr(item1->name,'.');
246         if( !ext1 ) ext1 = item1->name;
247         char *ext2 = strrchr(item2->name,'.');
248         if( !ext2 ) ext2 = item2->name;
249         int ret = strcasecmp(ext2, ext1);
250         if( ret ) return ret;
251         if( item2->item_no >= 0 && item1->item_no >= 0 )
252                 return item2->item_no - item1->item_no;
253         char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
254         dot_reverse_filename(dotreversedname1,item1->name);
255         dot_reverse_filename(dotreversedname2,item2->name);
256         return strcasecmp(dotreversedname2, dotreversedname1);
257 }
258
259 int FileSystem::sort_table(ArrayList<FileItem*> *dir_list)
260 {
261         if(!dir_list || !dir_list->size()) return 0;
262         static int (*cmpr[][2])(const void *ptr1, const void *ptr2) = {
263                 { &path_ascending, &path_descending },
264                 { &size_ascending, &size_descending },
265                 { &date_ascending, &date_descending },
266                 { &ext_ascending,  &ext_descending  },
267         };
268
269         qsort(dir_list->values,
270                 dir_list->size(), sizeof(FileItem*),
271                 cmpr[sort_field][sort_order]);
272
273         return 0;
274 }
275
276 int FileSystem::combine(ArrayList<FileItem*> *dir_list, ArrayList<FileItem*> *file_list)
277 {
278         int i;
279
280         sort_table(dir_list);
281         for(i = 0; i < dir_list->total; i++)
282         {
283                 this->dir_list.append(dir_list->values[i]);
284         }
285
286         sort_table(file_list);
287         for(i = 0; i < file_list->total; i++)
288         {
289                 this->dir_list.append(file_list->values[i]);
290         }
291         return 0;
292 }
293
294 int FileSystem::test_filter(FileItem *file)
295 {
296         char *filter1 = 0, *filter2 = filter, *subfilter1, *subfilter2;
297         int result = 0;
298         int done = 0, token_done;
299         int token_number = 0;
300
301 // Don't filter directories
302         if(file->is_dir) return 0;
303
304 // Empty filename string
305         if(!file->name) return 1;
306
307         do
308         {
309 // Get next token
310                 filter1 = strchr(filter2, '[');
311                 string[0] = 0;
312
313 // Get next filter
314                 if(filter1)
315                 {
316                         filter1++;
317                         filter2 = strchr(filter1, ']');
318
319                         if(filter2)
320                         {
321                                 int i;
322                                 for(i = 0; filter1 + i < filter2; i++)
323                                         string[i] = filter1[i];
324                                 string[i] = 0;
325                         }
326                         else
327                         {
328                                 strcpy(string, filter1);
329                                 done = 1;
330                         }
331                 }
332                 else
333                 {
334                         if(!token_number)
335                                 strcpy(string, filter);
336                         else
337                                 done = 1;
338                 }
339
340 // Process the token
341                 if(string[0] != 0)
342                 {
343                         char *path = file->name;
344                         subfilter1 = string;
345                         token_done = 0;
346                         result = 0;
347
348                         do
349                         {
350                                 string2[0] = 0;
351                                 subfilter2 = strchr(subfilter1, '*');
352
353                                 if(subfilter2)
354                                 {
355                                         int i;
356                                         for(i = 0; subfilter1 + i < subfilter2; i++)
357                                                 string2[i] = subfilter1[i];
358
359                                         string2[i] = 0;
360                                 }
361                                 else
362                                 {
363                                         strcpy(string2, subfilter1);
364                                         token_done = 1;
365                                 }
366
367                                 if(string2[0] != 0)
368                                 {
369 // Subfilter must exist at some later point in the string
370                                         if(subfilter1 > string)
371                                         {
372                                                 if(!strstr(path, string2))
373                                                 {
374                                                         result = 1;
375                                                         token_done = 1;
376                                                 }
377                                                 else
378                                                 path = strstr(path, string2) + strlen(string2);
379                                         }
380                                         else
381 // Subfilter must exist at this point in the string
382                                         {
383                                                 if(strncmp(path, string2, strlen(string2)))
384 //                                              if(strncasecmp(path, string2, strlen(string2)))
385                                                 {
386                                                         result = 1;
387                                                         token_done = 1;
388                                                 }
389                                                 else
390                                                 path += strlen(string2);
391                                         }
392
393 // String must terminate after subfilter
394                                         if(!subfilter2)
395                                         {
396                                                 if(*path != 0)
397                                                 {
398                                                         result = 1;
399                                                         token_done = 1;
400                                                 }
401                                         }
402                                 }
403                                 subfilter1 = subfilter2 + 1;
404 // Let pass if no subfilter
405                         }while(!token_done && !result);
406                 }
407                 token_number++;
408         }while(!done && result);
409
410         return result;
411 }
412
413
414 int FileSystem::scan_directory(const char *new_dir)
415 {
416         if( new_dir != 0 )
417                 strcpy(current_dir, new_dir);
418         DIR *dirstream = opendir(current_dir);
419         if( !dirstream ) return 1;          // failed to open directory
420
421         struct dirent64 *new_filename;
422         while( (new_filename = readdir64(dirstream)) != 0 ) {
423                 FileItem *new_file = 0;
424                 int include_this = 1;
425
426 // File is directory heirarchy
427                 if(!strcmp(new_filename->d_name, ".") ||
428                         !strcmp(new_filename->d_name, "..")) include_this = 0;
429
430 // File is hidden and we don't want all files
431                 if(include_this &&
432                         !show_all_files &&
433                         new_filename->d_name[0] == '.') include_this = 0;
434
435 // file not hidden
436                 if(include_this)
437                 {
438                         new_file = new FileItem;
439                         char full_path[BCTEXTLEN], name_only[BCTEXTLEN];
440                         sprintf(full_path, "%s", current_dir);
441                         add_end_slash(full_path);
442                         strcat(full_path, new_filename->d_name);
443                         strcpy(name_only, new_filename->d_name);
444                         new_file->set_path(full_path);
445                         new_file->set_name(name_only);
446
447 // Get information about the file.
448                         struct stat ostat;
449                         if(!stat(full_path, &ostat))
450                         {
451                                 new_file->size = ostat.st_size;
452                                 struct tm *mod_time = localtime(&(ostat.st_mtime));
453                                 new_file->month = mod_time->tm_mon + 1;
454                                 new_file->day = mod_time->tm_mday;
455                                 new_file->year = mod_time->tm_year + 1900;
456                                 new_file->calendar_time = ostat.st_mtime;
457
458                                 if(S_ISDIR(ostat.st_mode))
459                                 {
460                                         strcat(name_only, "/"); // is a directory
461                                         new_file->is_dir = 1;
462                                 }
463
464 // File is excluded from filter
465                                 if(include_this && test_filter(new_file)) include_this = 0;
466 //printf("FileSystem::update 3 %d %d\n", include_this, test_filter(new_file));
467
468 // File is not a directory and we just want directories
469                                 if(include_this && want_directory && !new_file->is_dir) include_this = 0;
470                         }
471                         else
472                         {
473                                 printf("FileSystem::update %s %s\n", full_path, strerror(errno));
474                                 include_this = 0;
475                         }
476
477 // add to list
478                         if(include_this)
479                                 dir_list.append(new_file);
480                         else
481                                 delete new_file;
482                 }
483         }
484         closedir(dirstream);
485         return 0;
486 }
487
488 int FileSystem::update(const char *new_dir)
489 {
490         delete_directory();
491         int result = scan_directory(new_dir);
492 // combine the directories and files in the master list
493         return !result ? update_sort() : result;
494 }
495
496 int FileSystem::update_sort()
497 {
498         ArrayList<FileItem*> directories, files;
499         for( int i=0; i< dir_list.size(); ++i ) {
500                 FileItem *item = dir_list[i];
501                 item->item_no = i;
502                 (item->is_dir ? &directories : &files)->append(item);
503         }
504         dir_list.remove_all();
505 // combine the directories and files in the master list
506         combine(&directories, &files);
507         return 0;
508 // success
509 }
510
511
512 int FileSystem::set_filter(const char *new_filter)
513 {
514         strcpy(filter, new_filter);
515         return 0;
516 }
517
518 int FileSystem::set_show_all()
519 {
520         show_all_files = 1;
521         return 0;
522 }
523
524 int FileSystem::set_want_directory()
525 {
526         want_directory = 1;
527         return 0;
528 }
529
530 int FileSystem::is_dir(const char *path)      // return 0 if the text is a directory
531 {
532         if(!strlen(path)) return 0;
533
534         char new_dir[BCTEXTLEN];
535         struct stat ostat;    // entire name is a directory
536
537         strcpy(new_dir, path);
538         complete_path(new_dir);
539         if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
540                 return 1;
541         else
542                 return 0;
543 }
544
545 int FileSystem::create_dir(const char *new_dir_)
546 {
547         char new_dir[BCTEXTLEN];
548         strcpy(new_dir, new_dir_);
549         complete_path(new_dir);
550
551         mkdir(new_dir, S_IREAD | S_IWRITE | S_IEXEC);
552         return 0;
553 }
554
555 int FileSystem::parse_tildas(char *new_dir)
556 {
557         if(new_dir[0] == 0) return 1;
558
559 // Our home directory
560         if(new_dir[0] == '~')
561         {
562
563                 if(new_dir[1] == '/' || new_dir[1] == 0)
564                 {
565 // user's home directory
566                         char *home;
567                         char string[BCTEXTLEN];
568                         home = getenv("HOME");
569
570 // print starting after tilda
571                         if(home) sprintf(string, "%s%s", home, &new_dir[1]);
572                         strcpy(new_dir, string);
573                         return 0;
574                 }
575                 else
576 // Another user's home directory
577                 {
578                         char string[BCTEXTLEN], new_user[BCTEXTLEN];
579                         struct passwd *pw;
580                         int i, j;
581
582                         for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
583                         {                // copy user name
584                                 new_user[j] = new_dir[i];
585                         }
586                         new_user[j] = 0;
587
588                         setpwent();
589                         while( (pw = getpwent()) != 0 )
590                         {
591 // get info for user
592                                 if(!strcmp(pw->pw_name, new_user))
593                                 {
594 // print starting after tilda
595                                 sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
596                                 strcpy(new_dir, string);
597                                 break;
598                         }
599                         }
600                         endpwent();
601                         return 0;
602                 }
603         }
604         return 0;
605 }
606
607 int FileSystem::parse_directories(char *new_dir)
608 {
609 //printf("FileSystem::parse_directories 1 %s\n", new_dir);
610         if( *new_dir != '/' && current_dir[0] ) {  // expand to abs path
611                 char string[BCTEXTLEN];
612                 strcpy(string, current_dir);
613                 add_end_slash(string);
614                 strcat(string, new_dir);
615                 strcpy(new_dir, string);
616         }
617         return 0;
618 }
619
620 int FileSystem::parse_dots(char *new_dir)
621 {
622 // recursively remove ..s
623         int changed = 1;
624         while(changed)
625         {
626                 int i, j, len;
627                 len = strlen(new_dir);
628                 changed = 0;
629                 for(i = 0, j = 1; !changed && j < len; i++, j++)
630                 {
631 // Got 2 dots
632                         if(new_dir[i] == '.' && new_dir[j] == '.')
633                         {
634 // Ignore if character after .. doesn't qualify
635                                 if(j + 1 < len &&
636                                         new_dir[j + 1] != ' ' &&
637                                         new_dir[j + 1] != '/')
638                                         continue;
639
640 // Ignore if character before .. doesn't qualify
641                                 if(i > 0 &&
642                                         new_dir[i - 1] != '/') continue;
643
644                                 changed = 1;
645                                 while(new_dir[i] != '/' && i > 0)
646                                 {
647 // look for first / before ..
648                                         i--;
649                                 }
650
651 // find / before this /
652                                 if(i > 0) i--;
653                                 while(new_dir[i] != '/' && i > 0)
654                                 {
655 // look for first / before first / before ..
656                                         i--;
657                                 }
658
659 // i now equals /first filename before ..
660 // look for first / after ..
661                                 while(new_dir[j] != '/' && j < len)
662                                 {
663                                         j++;
664                                 }
665
666 // j now equals /first filename after ..
667                                 while(j < len)
668                                 {
669                                         new_dir[i++] = new_dir[j++];
670                                 }
671
672                                 new_dir[i] = 0;
673 // default to root directory
674                                 if((new_dir[0]) == 0) sprintf(new_dir, "/");
675                                 break;
676                         }
677                 }
678         }
679         return 0;
680 }
681
682 int FileSystem::complete_path(char *filename)
683 {
684 //printf("FileSystem::complete_path 1\n");
685         if(!strlen(filename)) return 1;
686 //printf("FileSystem::complete_path 1\n");
687         parse_tildas(filename);
688 //printf("FileSystem::complete_path 1\n");
689         parse_directories(filename);
690 //printf("FileSystem::complete_path 1\n");
691         parse_dots(filename);
692 // don't add end slash since this requires checking if dir
693 //printf("FileSystem::complete_path 2\n");
694         return 0;
695 }
696
697 int FileSystem::extract_dir(char *out, const char *in)
698 {
699         strcpy(out, in);
700         if(!is_dir(in))
701         {
702 // complete string is not directory
703                 int i;
704
705                 complete_path(out);
706
707                 for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
708                 {
709                         ;
710                 }
711                 if(i >= 0) out[i] = 0;
712         }
713         return 0;
714 }
715
716 int FileSystem::extract_name(char *out, const char *in, int test_dir)
717 {
718         int i;
719
720         if(test_dir && is_dir(in))
721                 out[0] = 0; // complete string is directory
722         else
723         {
724                 for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
725                 {
726                         ;
727                 }
728                 if(in[i] == '/') i++;
729                 strcpy(out, &in[i]);
730         }
731         return 0;
732 }
733
734 int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
735 {
736         strcpy(out, dir_in);
737         int len = strlen(out);
738         int result = 0;
739
740         while(!result)
741                 if(len == 0 || out[len] != 0) result = 1; else len--;
742
743         if(len != 0)
744         {
745                 if(out[len] != '/') strcat(out, "/");
746         }
747
748         strcat(out, name_in);
749         return 0;
750 }
751
752 int64_t FileSystem::get_date(const char *filename)
753 {
754         struct stat file_status;
755         bzero(&file_status, sizeof(struct stat));
756         int result = stat(filename, &file_status);
757         return !result ? file_status.st_mtime : -1;
758 }
759
760 void FileSystem::set_date(const char *path, int64_t value)
761 {
762         struct utimbuf new_time;
763         new_time.actime = value;
764         new_time.modtime = value;
765         utime(path, &new_time);
766 }
767
768 int64_t FileSystem::get_size(char *filename)
769 {
770         struct stat file_status;
771         bzero(&file_status, sizeof(struct stat));
772         int result = stat(filename, &file_status);
773         return !result ? file_status.st_size : -1;
774 }
775
776 int FileSystem::change_dir(const char *new_dir, int update)
777 {
778         char new_dir_full[BCTEXTLEN];
779
780         strcpy(new_dir_full, new_dir);
781
782         complete_path(new_dir_full);
783 // cut ending slash
784         if(strcmp(new_dir_full, "/") &&
785                 new_dir_full[strlen(new_dir_full) - 1] == '/')
786                 new_dir_full[strlen(new_dir_full) - 1] = 0;
787
788         if(update)
789                 this->update(new_dir_full);
790         else
791         {
792                 strcpy(current_dir, new_dir_full);
793         }
794         return 0;
795 }
796
797 int FileSystem::set_current_dir(const char *new_dir)
798 {
799         strcpy(current_dir, new_dir);
800         return 0;
801 }
802
803 int FileSystem::add_end_slash(char *new_dir)
804 {
805         if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
806         return 0;
807 }
808
809
810 // collapse ".",  "..", "//"  eg. x/./..//y = y
811 char *FileSystem::basepath(const char *path)
812 {
813         char fpath[BCTEXTLEN];
814         unsigned len = strlen(path);
815         if( len >= sizeof(fpath) ) return 0;
816         strcpy(fpath, path);
817         char *flat = cstrdup("");
818
819         int r = 0;
820         char *fn = fpath + len;
821         while( fn > fpath ) {
822                 while( --fn >= fpath )
823                         if( *fn == '/' ) { *fn = 0;  break; }
824                 fn = fn < fpath ? fpath : fn+1;
825                 if( !*fn || !strcmp(fn, ".") ) continue;
826                 if( !strcmp(fn, "..") ) { ++r; continue; }
827                 if( r < 0 ) continue;
828                 if( r > 0 ) { --r;  continue; }
829                 char *cp = cstrcat(3, "/",fn,flat);
830                 delete [] flat;  flat = cp;
831         }
832
833         if( *path != '/' ) {
834                 char *cp = cstrcat(2, ".",flat);
835                 delete [] flat;  flat = cp;
836         }
837
838         return flat;
839 }
840