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