devel Makefile fix, prefs appearance tab/rework, picon stratigy, edl path fix, change...
[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         return 0;
485 }
486
487 int FileSystem::update(const char *new_dir)
488 {
489         delete_directory();
490         int result = scan_directory(new_dir);
491 // combine the directories and files in the master list
492         return !result ? update_sort() : result;
493 }
494
495 int FileSystem::update_sort()
496 {
497         ArrayList<FileItem*> directories, files;
498         for( int i=0; i< dir_list.size(); ++i ) {
499                 FileItem *item = dir_list[i];
500                 item->item_no = i;
501                 (item->is_dir ? &directories : &files)->append(item);
502         }
503         dir_list.remove_all();
504 // combine the directories and files in the master list
505         combine(&directories, &files);
506         return 0;
507 // success
508 }
509
510
511 int FileSystem::set_filter(const char *new_filter)
512 {
513         strcpy(filter, new_filter);
514         return 0;
515 }
516
517 int FileSystem::set_show_all()
518 {
519         show_all_files = 1;
520         return 0;
521 }
522
523 int FileSystem::set_want_directory()
524 {
525         want_directory = 1;
526         return 0;
527 }
528
529 int FileSystem::is_dir(const char *path)      // return 0 if the text is a directory
530 {
531         if(!strlen(path)) return 0;
532
533         char new_dir[BCTEXTLEN];
534         struct stat ostat;    // entire name is a directory
535
536         strcpy(new_dir, path);
537         complete_path(new_dir);
538         if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
539                 return 1;
540         else
541                 return 0;
542 }
543
544 int FileSystem::create_dir(const char *new_dir_)
545 {
546         char new_dir[BCTEXTLEN];
547         strcpy(new_dir, new_dir_);
548         complete_path(new_dir);
549
550         mkdir(new_dir, S_IREAD | S_IWRITE | S_IEXEC);
551         return 0;
552 }
553
554 int FileSystem::parse_tildas(char *new_dir)
555 {
556         if(new_dir[0] == 0) return 1;
557
558 // Our home directory
559         if(new_dir[0] == '~')
560         {
561
562                 if(new_dir[1] == '/' || new_dir[1] == 0)
563                 {
564 // user's home directory
565                         char *home;
566                         char string[BCTEXTLEN];
567                         home = getenv("HOME");
568
569 // print starting after tilda
570                         if(home) sprintf(string, "%s%s", home, &new_dir[1]);
571                         strcpy(new_dir, string);
572                         return 0;
573                 }
574                 else
575 // Another user's home directory
576                 {
577                         char string[BCTEXTLEN], new_user[BCTEXTLEN];
578                         struct passwd *pw;
579                         int i, j;
580
581                         for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
582                         {                // copy user name
583                                 new_user[j] = new_dir[i];
584                         }
585                         new_user[j] = 0;
586
587                         setpwent();
588                         while( (pw = getpwent()) != 0 )
589                         {
590 // get info for user
591                                 if(!strcmp(pw->pw_name, new_user))
592                                 {
593 // print starting after tilda
594                                 sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
595                                 strcpy(new_dir, string);
596                                 break;
597                         }
598                         }
599                         endpwent();
600                         return 0;
601                 }
602         }
603         return 0;
604 }
605
606 int FileSystem::parse_directories(char *new_dir)
607 {
608 //printf("FileSystem::parse_directories 1 %s\n", new_dir);
609         if( *new_dir != '/' && current_dir[0] ) {  // expand to abs path
610                 char string[BCTEXTLEN];
611                 strcpy(string, current_dir);
612                 add_end_slash(string);
613                 strcat(string, new_dir);
614                 strcpy(new_dir, string);
615         }
616         return 0;
617 }
618
619 int FileSystem::parse_dots(char *new_dir)
620 {
621 // recursively remove ..s
622         int changed = 1;
623         while(changed)
624         {
625                 int i, j, len;
626                 len = strlen(new_dir);
627                 changed = 0;
628                 for(i = 0, j = 1; !changed && j < len; i++, j++)
629                 {
630 // Got 2 dots
631                         if(new_dir[i] == '.' && new_dir[j] == '.')
632                         {
633 // Ignore if character after .. doesn't qualify
634                                 if(j + 1 < len &&
635                                         new_dir[j + 1] != ' ' &&
636                                         new_dir[j + 1] != '/')
637                                         continue;
638
639 // Ignore if character before .. doesn't qualify
640                                 if(i > 0 &&
641                                         new_dir[i - 1] != '/') continue;
642
643                                 changed = 1;
644                                 while(new_dir[i] != '/' && i > 0)
645                                 {
646 // look for first / before ..
647                                         i--;
648                                 }
649
650 // find / before this /
651                                 if(i > 0) i--;
652                                 while(new_dir[i] != '/' && i > 0)
653                                 {
654 // look for first / before first / before ..
655                                         i--;
656                                 }
657
658 // i now equals /first filename before ..
659 // look for first / after ..
660                                 while(new_dir[j] != '/' && j < len)
661                                 {
662                                         j++;
663                                 }
664
665 // j now equals /first filename after ..
666                                 while(j < len)
667                                 {
668                                         new_dir[i++] = new_dir[j++];
669                                 }
670
671                                 new_dir[i] = 0;
672 // default to root directory
673                                 if((new_dir[0]) == 0) sprintf(new_dir, "/");
674                                 break;
675                         }
676                 }
677         }
678         return 0;
679 }
680
681 int FileSystem::complete_path(char *filename)
682 {
683 //printf("FileSystem::complete_path 1\n");
684         if(!strlen(filename)) return 1;
685 //printf("FileSystem::complete_path 1\n");
686         parse_tildas(filename);
687 //printf("FileSystem::complete_path 1\n");
688         parse_directories(filename);
689 //printf("FileSystem::complete_path 1\n");
690         parse_dots(filename);
691 // don't add end slash since this requires checking if dir
692 //printf("FileSystem::complete_path 2\n");
693         return 0;
694 }
695
696 int FileSystem::extract_dir(char *out, const char *in)
697 {
698         strcpy(out, in);
699         if(!is_dir(in))
700         {
701 // complete string is not directory
702                 int i;
703
704                 complete_path(out);
705
706                 for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
707                 {
708                         ;
709                 }
710                 if(i >= 0) out[i] = 0;
711         }
712         return 0;
713 }
714
715 int FileSystem::extract_name(char *out, const char *in, int test_dir)
716 {
717         int i;
718
719         if(test_dir && is_dir(in))
720                 out[0] = 0; // complete string is directory
721         else
722         {
723                 for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
724                 {
725                         ;
726                 }
727                 if(in[i] == '/') i++;
728                 strcpy(out, &in[i]);
729         }
730         return 0;
731 }
732
733 int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
734 {
735         strcpy(out, dir_in);
736         int len = strlen(out);
737         int result = 0;
738
739         while(!result)
740                 if(len == 0 || out[len] != 0) result = 1; else len--;
741
742         if(len != 0)
743         {
744                 if(out[len] != '/') strcat(out, "/");
745         }
746
747         strcat(out, name_in);
748         return 0;
749 }
750
751 int64_t FileSystem::get_date(const char *filename)
752 {
753         struct stat file_status;
754         bzero(&file_status, sizeof(struct stat));
755         int result = stat(filename, &file_status);
756         return !result ? file_status.st_mtime : -1;
757 }
758
759 void FileSystem::set_date(const char *path, int64_t value)
760 {
761         struct utimbuf new_time;
762         new_time.actime = value;
763         new_time.modtime = value;
764         utime(path, &new_time);
765 }
766
767 int64_t FileSystem::get_size(char *filename)
768 {
769         struct stat file_status;
770         bzero(&file_status, sizeof(struct stat));
771         int result = stat(filename, &file_status);
772         return !result ? file_status.st_size : -1;
773 }
774
775 int FileSystem::change_dir(const char *new_dir, int update)
776 {
777         char new_dir_full[BCTEXTLEN];
778
779         strcpy(new_dir_full, new_dir);
780
781         complete_path(new_dir_full);
782 // cut ending slash
783         if(strcmp(new_dir_full, "/") &&
784                 new_dir_full[strlen(new_dir_full) - 1] == '/')
785                 new_dir_full[strlen(new_dir_full) - 1] = 0;
786
787         if(update)
788                 this->update(new_dir_full);
789         else
790         {
791                 strcpy(current_dir, new_dir_full);
792         }
793         return 0;
794 }
795
796 int FileSystem::set_current_dir(const char *new_dir)
797 {
798         strcpy(current_dir, new_dir);
799         return 0;
800 }
801
802 int FileSystem::add_end_slash(char *new_dir)
803 {
804         if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
805         return 0;
806 }
807
808
809 // collapse ".",  "..", "//"  eg. x/./..//y = y
810 char *FileSystem::basepath(const char *path)
811 {
812         char fpath[BCTEXTLEN];
813         unsigned len = strlen(path);
814         if( len >= sizeof(fpath) ) return 0;
815         strcpy(fpath, path);
816         char *flat = cstrdup("");
817
818         int r = 0;
819         char *fn = fpath + len;
820         while( fn > fpath ) {
821                 while( --fn >= fpath )
822                         if( *fn == '/' ) { *fn = 0;  break; }
823                 fn = fn < fpath ? fpath : fn+1;
824                 if( !*fn || !strcmp(fn, ".") ) continue;
825                 if( !strcmp(fn, "..") ) { ++r; continue; }
826                 if( r < 0 ) continue;
827                 if( r > 0 ) { --r;  continue; }
828                 char *cp = cstrcat(3, "/",fn,flat);
829                 delete [] flat;  flat = cp;
830         }
831
832         if( *path != '/' ) {
833                 char *cp = cstrcat(2, ".",flat);
834                 delete [] flat;  flat = cp;
835         }
836
837         return flat;
838 }
839