]> git.lyx.org Git - features.git/blob - src/support/filetools.C
the merge from branch debugstream
[features.git] / src / support / filetools.C
1 /*
2         filetools.C (former paths.C) - part of LyX project
3         General path-mangling functions 
4         Copyright 1996 Ivan Schreter
5         Parts Copyright 1996 Dirk Niggemann
6         Parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7         Parts Copyright 1996 Asger Alstrup
8         
9         See also filetools.H.
10
11         lyx-filetool.C : tools functions for file/path handling
12         this file is part of LyX, the High Level Word Processor
13         Copyright 1995-1996, Matthias Ettrich and the LyX Team
14
15 */
16
17 #include <config.h>
18
19 #include <cctype>
20
21 #ifdef __GNUG__
22 #pragma implementation "filetools.h"
23 #endif
24
25 #include "filetools.h"
26 #include "lyx_gui_misc.h"
27 #include "FileInfo.h"
28 #include "pathstack.h"        // I know it's OS/2 specific (SMiyata)
29 #include "gettext.h"
30
31 // Which part of this is still necessary? (JMarc).
32 #if HAVE_DIRENT_H
33 # include <dirent.h>
34 # define NAMLEN(dirent) strlen((dirent)->d_name)
35 #else
36 # define dirent direct
37 # define NAMLEN(dirent) (dirent)->d_namlen
38 # if HAVE_SYS_NDIR_H
39 #  include <sys/ndir.h>
40 # endif
41 # if HAVE_SYS_DIR_H
42 #  include <sys/dir.h>
43 # endif
44 # if HAVE_NDIR_H
45 #  include <ndir.h>
46 # endif
47 #endif
48
49 extern string system_lyxdir;
50 extern string build_lyxdir;
51 extern string user_lyxdir;
52 extern string system_tempdir;
53
54
55 bool IsLyXFilename(string const & filename)
56 {
57         return contains(filename, ".lyx");
58 }
59
60
61 bool IsSGMLFilename(string const & filename)
62 {
63         return contains(filename, ".sgml");
64 }
65
66
67 // Substitutes spaces with underscores in filename (and path)
68 string SpaceLess(string const & file)
69 {
70         string name = OnlyFilename(file);
71         string path = OnlyPath(file);
72         
73         for (string::size_type i = 0; i < name.length(); ++i) {
74                 name[i] &= 0x7f;
75                 if (!isalnum(name[i]) && name[i] != '.')
76                         name[i] = '_';
77         }
78         string temp = AddName(path, name);
79         // Replace spaces with underscores, also in directory
80         // No!!! I checked it that it is not necessary.
81         // temp.subst(' ','_');
82
83         return temp;
84 }
85
86
87 /// Returns an unique name to be used as a temporary file. 
88 string TmpFileName(string const & dir, string const & mask)
89 {// With all these temporary variables, it should be safe enough :-) (JMarc)
90         string tmpdir;  
91         if (dir.empty())
92                 tmpdir = system_tempdir;
93         else
94                 tmpdir = dir;
95         string tmpfl = AddName(tmpdir, mask);
96
97         // find a uniq postfix for the filename...
98         // using the pid, and...
99         tmpfl += tostr(getpid());
100         // a short string...
101         string ret;
102         FileInfo fnfo;
103         for (int a='a'; a<= 'z'; ++a)
104                 for (int b='a'; b<= 'z'; ++b)
105                         for (int c='a'; c<= 'z'; ++c) {
106                                 // if this is not enough I have no idea what
107                                 // to do.
108                                 ret = tmpfl + char(a) + char(b) + char(c);
109                                 // check if the file exist
110                                 if (!fnfo.newFile(ret).exist())
111                                         return ret;
112                         }
113         lyxerr << "Not able to find a uniq tmpfile name." << endl;
114         return string();
115 }
116
117
118 // Is a file readable ?
119 bool IsFileReadable (string const & path)
120 {
121         FileInfo file(path);
122         if (file.isOK() && file.isRegular() && file.readable())
123                 return true;
124         else
125                 return false;
126 }
127
128
129 // Is a file read_only?
130 // return 1 read-write
131 //        0 read_only
132 //       -1 error (doesn't exist, no access, anything else) 
133 int IsFileWriteable (string const & path)
134 {
135         FilePtr fp(path, FilePtr::update);
136         if (!fp()) {
137                 if ((errno == EACCES) || (errno == EROFS)) {
138                         //fp = FilePtr(path, FilePtr::read);
139                         fp.reopen(path, FilePtr::read);
140                         if (fp()) {
141                                 return 0;
142                         }
143                 }
144                 return -1;
145         }
146         return 1;
147 }
148
149
150 //returns 1: dir writeable
151 //        0: not writeable
152 //       -1: error- couldn't find out
153 int IsDirWriteable (string const & path)
154 {
155         string tmpfl = TmpFileName(path);
156
157         if (tmpfl.empty()) {
158                 WriteFSAlert(_("LyX Internal Error!"), 
159                              _("Could not test if directory is writeable"));
160                 return -1;
161         } else {
162         FilePtr fp(tmpfl, FilePtr::truncate);
163         if (!fp()) {
164                 if (errno == EACCES) {
165                         return 0;
166                 } else { 
167                         WriteFSAlert(_("LyX Internal Error!"), 
168                                      _("Cannot open directory test file"));
169                         return -1;
170                 }
171                 }
172         }
173                 if (remove (tmpfl.c_str())) {
174                         WriteFSAlert(_("LyX Internal Error!"), 
175                                     _("Created test file but cannot remove it?"));
176                         return -1;
177         }
178         return 1;
179 }
180
181
182 // Uses a string of paths separated by ";"s to find a file to open.
183 // Can't cope with pathnames with a ';' in them. Returns full path to file.
184 // If path entry begins with $$LyX/, use system_lyxdir
185 // If path entry begins with $$User/, use user_lyxdir
186 // Example: "$$User/doc;$$LyX/doc"
187 string FileOpenSearch (string const & path, string const & name, 
188                         string const & ext)
189 {
190         string real_file, path_element;
191         bool notfound = true;
192         string tmppath=split(path, path_element, ';');
193         
194         while (notfound && !path_element.empty()) {
195                 path_element = CleanupPath(path_element);
196                 if (!suffixIs(path_element, '/'))
197                         path_element+='/';
198                 path_element = subst(path_element, "$$LyX", system_lyxdir);
199                 path_element = subst(path_element, "$$User", user_lyxdir);
200                 
201                 real_file = FileSearch(path_element, name, ext);
202
203                 if (real_file.empty()) {
204                   tmppath = split(tmppath, path_element, ';');
205                 } else {
206                   notfound = false;
207                 }
208         }
209 #ifdef __EMX__
210         if (ext.empty() && notfound) {
211                 real_file = FileOpenSearch(path, name, "exe");
212                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
213         }
214 #endif
215         return real_file;
216 }
217
218
219 // Returns the real name of file name in directory path, with optional
220 // extension ext.  
221 string FileSearch(string const & path, string const & name, 
222                   string const & ext)
223 {
224         // if `name' is an absolute path, we ignore the setting of `path'
225         // Expand Environmentvariables in 'name'
226         string tmpname = ReplaceEnvironmentPath(name);
227         string fullname = MakeAbsPath(tmpname, path);
228         
229         // search first without extension, then with it.
230         if (IsFileReadable(fullname))
231                 return fullname;
232         else if (ext.empty()) 
233                 return string();
234         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
235                 fullname += '.';
236                 fullname += ext;
237                 if (IsFileReadable(fullname))
238                         return fullname;
239                 else 
240                         return string();
241         }
242 }
243
244
245 // Search the file name.ext in the subdirectory dir of
246 //   1) user_lyxdir
247 //   2) build_lyxdir (if not empty)
248 //   3) system_lyxdir
249 string LibFileSearch(string const & dir, string const & name, 
250                       string const & ext)
251 {
252         string fullname = FileSearch(AddPath(user_lyxdir,dir), name,
253                                       ext); 
254         if (!fullname.empty())
255                 return fullname;
256
257         if (!build_lyxdir.empty()) 
258                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
259                                       name, ext);
260         if (!fullname.empty())
261                 return fullname;
262
263         return FileSearch(AddPath(system_lyxdir,dir), name, ext);
264 }
265
266
267 string i18nLibFileSearch(string const & dir, string const & name, 
268                           string const & ext)
269 {
270         string lang = token(string(GetEnv("LANG")), '_', 0);
271
272         if (lang.empty() || lang == "C")
273                 return LibFileSearch(dir, name, ext);
274         else {
275                 string tmp = LibFileSearch(dir, lang + '_' + name,
276                                             ext);
277                 if (!tmp.empty())
278                         return tmp;
279                 else
280                         return LibFileSearch(dir, name, ext);
281         }
282 }
283
284
285 string GetEnv(string const & envname)
286 {
287         // f.ex. what about error checking?
288         char const * const ch = getenv(envname.c_str());
289         string envstr = !ch ? "" : ch;
290         return envstr;
291 }
292
293
294 string GetEnvPath(string const & name)
295 {
296 #ifndef __EMX__
297         string pathlist = subst(GetEnv(name), ':', ';');
298 #else
299         string pathlist = subst(GetEnv(name), '\\', '/');
300 #endif
301         return strip(pathlist, ';');
302 }
303
304
305 bool PutEnv(string const & envstr)
306 {
307 #warning Look at and fix this.
308         // f.ex. what about error checking?
309         int retval = 0;
310 #if HAVE_PUTENV
311         // this leaks, but what can we do about it?
312         //   Is doing a getenv() and a free() of the older value 
313         //   a good idea? (JMarc)
314         retval = putenv((new string(envstr))->c_str());
315 #else
316 #ifdef HAVE_SETENV 
317         string varname;
318         string str = envstr.split(varname,'=');
319         retval = setenv(varname.c_str(), str.c_str(), true);
320 #endif
321 #endif
322         return retval == 0;
323 }
324
325
326 bool PutEnvPath(string const & envstr)
327 {
328         string pathlist = envstr;
329 #warning Verify that this is correct.
330 #ifdef __EMX__
331         pathlist.subst(':', ';');
332         pathlist.subst('/', '\\');
333 #endif
334         return PutEnv(pathlist);
335 }
336
337
338 static
339 int DeleteAllFilesInDir (string const & path)
340 {
341         DIR * dir;
342         struct dirent * de;
343         dir = opendir(path.c_str());
344         if (!dir) {
345                 WriteFSAlert (_("Error! Cannot open directory:"), path);
346                 return -1;
347         }
348         while ((de = readdir(dir))) {
349                 string temp = de->d_name;
350                 if (temp=="." || temp=="..") 
351                         continue;
352                 string unlinkpath = AddName (path, temp);
353
354                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
355
356                 if (remove (unlinkpath.c_str()))
357                         WriteFSAlert (_("Error! Could not remove file:"), 
358                                       unlinkpath);
359         }
360         closedir (dir);
361         return 0;
362 }
363
364
365 static
366 string CreateTmpDir (string const & tempdir, string const & mask)
367 {
368         string tmpfl = TmpFileName(tempdir, mask);
369         
370         if ((tmpfl.empty()) || mkdir (tmpfl.c_str(), 0777)) {
371                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
372                              tempdir);
373                 return string();
374         }
375         return MakeAbsPath(tmpfl);
376 }
377
378
379 static
380 int DestroyTmpDir (string const & tmpdir, bool Allfiles)
381 {
382         if ((Allfiles) && (DeleteAllFilesInDir (tmpdir))) return -1;
383         if (rmdir(tmpdir.c_str())) { 
384 #ifdef __EMX__
385                 if (errno == EBUSY) {
386                         chdir(user_lyxdir.c_str()); // They are in the same drive.
387                         if (!rmdir(tmpdir.c_str())) return 0;
388                 }
389 #endif
390                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
391                              tmpdir);
392                 return -1;
393         }
394         return 0; 
395
396
397
398 string CreateBufferTmpDir (string const & pathfor)
399 {
400         return CreateTmpDir (pathfor, "lyx_bufrtmp");
401 }
402
403
404 int DestroyBufferTmpDir (string const & tmpdir)
405 {
406         return DestroyTmpDir (tmpdir, true);
407 }
408
409
410 string CreateLyXTmpDir (string const & deflt)
411 {
412         string t;        
413
414         if ((!deflt.empty()) && (deflt  != "/tmp")) {
415                 if (mkdir (deflt.c_str(), 0777)) {
416 #ifdef __EMX__
417                         PathPush(user_lyxdir);
418 #endif
419                         t = CreateTmpDir (deflt.c_str(), "lyx_tmp");
420 #ifdef __EMX__
421                         PathPop();
422 #endif
423                         return t;
424                 } else
425                         return deflt;
426         } else {
427 #ifdef __EMX__
428                 PathPush(user_lyxdir);
429 #endif
430                 t = CreateTmpDir ("/tmp", "lyx_tmp");
431 #ifdef __EMX__
432                 PathPop();
433 #endif
434                 return t;
435         }
436 }
437
438
439 int DestroyLyXTmpDir (string const & tmpdir)
440 {
441        return DestroyTmpDir (tmpdir, false); // Why false?
442 }
443
444
445 // Creates directory. Returns true if succesfull
446 bool createDirectory(string const & path, int permission)
447 {
448         string temp = strip(CleanupPath(path), '/');
449
450         if (temp.empty()) {
451                 WriteAlert(_("Internal error!"),
452                            _("Call to createDirectory with invalid name"));
453                 return false;
454         }
455
456         if (mkdir(temp.c_str(), permission)) {
457                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
458                 return false;
459         }
460         return true;
461 }
462
463
464 // Returns current working directory
465 string GetCWD ()
466 {
467         int n = 256;    // Assume path is less than 256 chars
468         char * err;
469         char * tbuf = new char [n];
470         string result;
471         
472         // Safe. Hopefully all getcwds behave this way!
473         while (((err = getcwd (tbuf, n)) == 0) && (errno == ERANGE)) {
474                 // Buffer too small, double the buffersize and try again
475                 delete[] tbuf;
476                 n = 2*n;
477                 tbuf = new char [n];
478         }
479
480         if (err) result = tbuf;
481         delete[] tbuf;
482         return result;
483 }
484
485
486 // Strip filename from path name
487 string OnlyPath(string const & Filename)
488 {
489         // If empty filename, return empty
490         if (Filename.empty()) return Filename;
491
492         // Find last / or start of filename
493         string::size_type j = Filename.rfind('/');
494         if (j==string::npos)
495                 return "./";
496         return Filename.substr(0, j + 1);
497 }
498
499
500 // Convert relative path into absolute path based on a basepath.
501 // If relpath is absolute, just use that.
502 // If basepath is empty, use CWD as base.
503 string MakeAbsPath(string const & RelPath, string const & BasePath)
504 {
505         // checks for already absolute path
506         if (AbsolutePath(RelPath))
507 #ifdef __EMX__
508                 if(RelPath[0]!='/' || RelPath[0]!='\\')
509 #endif
510                 return RelPath;
511
512         // Copies given paths
513         string TempRel = CleanupPath(RelPath);
514
515         string TempBase;
516
517         if (!BasePath.empty()) {
518 #ifndef __EMX__
519                 TempBase = BasePath;
520 #else
521                 char * with_drive = new char[_MAX_PATH];
522                 _abspath(with_drive, BasePath.c_str(), _MAX_PATH);
523                 TempBase = with_drive;
524                 delete[] with_drive;
525 #endif
526         } else
527                 TempBase = GetCWD();
528 #ifdef __EMX__
529         if (AbsolutePath(TempRel))
530                 return TempBase[0] + TempRel;
531 #endif
532
533         // Handle /./ at the end of the path
534         while(suffixIs(TempBase, "/./"))
535                 TempBase.erase(TempBase.length() - 2);
536
537         // processes relative path
538         string RTemp = TempRel;
539         string Temp;
540
541         while (!RTemp.empty()) {
542                 // Split by next /
543                 RTemp = split(RTemp, Temp, '/');
544                 
545                 if (Temp==".") continue;
546                 if (Temp=="..") {
547                         // Remove one level of TempBase
548                         int i = TempBase.length()-2;
549 #ifndef __EMX__
550                         if (i<0) i=0;
551                         while (i>0 && TempBase[i] != '/') --i;
552                         if (i>0)
553 #else
554                                 if (i<2) i=2;
555                         while (i>2 && TempBase[i] != '/') --i;
556                         if (i>2)
557 #endif
558                                 TempBase.erase(i, string::npos);
559                         else
560                                 TempBase += '/';
561                 } else {
562                         // Add this piece to TempBase
563                         if (!suffixIs(TempBase, '/'))
564                                 TempBase += '/';
565                         TempBase += Temp;
566                 }
567         }
568
569         // returns absolute path
570         return TempBase;        
571 }
572
573
574 // Correctly append filename to the pathname.
575 // If pathname is '.', then don't use pathname.
576 // Chops any path of filename.
577 string AddName(string const & path, string const & fname)
578 {
579         // Get basename
580         string basename = OnlyFilename(fname);
581
582         string buf;
583
584         if (path != "." && path != "./" && !path.empty()) {
585                 buf = CleanupPath(path);
586                 if (!suffixIs(path, '/'))
587                         buf += '/';
588         }
589
590         return buf + basename;
591 }
592
593
594 // Strips path from filename
595 string OnlyFilename(string const & fname)
596 {
597         Assert(!fname.empty()); // We don't allow empty filename. (Lgb)
598
599         string::size_type j = fname.rfind('/');
600         if (j == string::npos) // no '/' in fname
601                 return fname;
602
603         // Strip to basename
604         return fname.substr(j + 1);
605 }
606
607
608 // Is a filename/path absolute?
609 bool AbsolutePath(string const & path)
610 {
611 #ifndef __EMX__
612         return (!path.empty() && path[0] == '/');
613 #else
614         return (!path.empty() && path[0]=='/' || (isalpha((unsigned char) path[0]) && path[1]==':'));
615 #endif
616 }
617
618
619 // Create absolute path. If impossible, don't do anything
620 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
621 string ExpandPath(string const & path)
622 {
623         Assert(!path.empty()); // We don't allow empty path. (Lgb)
624         // checks for already absolute path
625         string RTemp = ReplaceEnvironmentPath(path);
626         if (AbsolutePath(RTemp))
627                 return RTemp;
628
629         string Temp;
630         string copy(RTemp);
631
632         // Split by next /
633         RTemp=split(RTemp, Temp, '/');
634
635         if (Temp==".") {
636                 return GetCWD() + '/' + RTemp;
637         } else if (Temp=="~") {
638                 return GetEnvPath("HOME") + '/' + RTemp;
639         } else if (Temp=="..") {
640                 return MakeAbsPath(copy);
641         } else
642                 // Don't know how to handle this
643                 return copy;
644 }
645
646
647 // Normalize a path
648 // Constracts path/../path
649 // Can't handle "../../" or "/../" (Asger)
650 string NormalizePath(string const & path)
651 {
652         string TempBase;
653         string RTemp;
654         string Temp;
655
656         if (AbsolutePath(path))
657                 RTemp = path;
658         else
659                 // Make implicit current directory explicit
660                 RTemp = "./" +path;
661
662         while (!RTemp.empty()) {
663                 // Split by next /
664                 RTemp = split(RTemp, Temp, '/');
665                 
666                 if (Temp==".") {
667                         TempBase = "./";
668                 } else if (Temp=="..") {
669                         // Remove one level of TempBase
670                         int i = TempBase.length()-2;
671                         while (i>0 && TempBase[i] != '/')
672                                 --i;
673                         if (i>=0 && TempBase[i] == '/')
674                                 TempBase.erase(i+1, string::npos);
675                         else
676                                 TempBase = "../";
677                 } else {
678                         TempBase += Temp + '/';
679                 }
680         }
681
682         // returns absolute path
683         return TempBase;        
684 }
685
686 string CleanupPath(string const & path) 
687 {
688 #ifdef __EMX__    /* SMiyata: This should fix searchpath bug. */
689         string temppath(path);
690         subst(tmppath, '\\', '/');
691         subst(tmppath, "//", "/");
692         return lowercase(temppath);
693 #else // On unix, nothing to do
694         return path;
695 #endif
696 }
697
698
699 //
700 // Search ${...} as Variable-Name inside the string and replace it with
701 // the denoted environmentvariable
702 // Allow Variables according to 
703 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
704 //
705
706 string ReplaceEnvironmentPath(string const & path)
707 {
708         Assert(!path.empty()); // We don't allow empty path. (Lgb)
709 // 
710 // CompareChar: Environmentvariables starts with this character
711 // PathChar:    Next path component start with this character
712 // while CompareChar found do:
713 //       Split String with PathChar
714 //       Search Environmentvariable
715 //       if found: Replace Strings
716 //
717         const char CompareChar = '$';
718         const char FirstChar = '{'; 
719         const char EndChar = '}'; 
720         const char UnderscoreChar = '_'; 
721         string EndString; EndString += EndChar;
722         string FirstString; FirstString += FirstChar;
723         string CompareString; CompareString += CompareChar;
724         const string RegExp("*}*"); // Exist EndChar inside a String?
725
726 // first: Search for a '$' - Sign.
727         //string copy(path);
728         string result1; //(copy);    // for split-calls
729         string result0 = split(path, result1, CompareChar);
730         while (!result0.empty()) {
731                 string copy1(result0); // contains String after $
732                 
733                 // Check, if there is an EndChar inside original String.
734                 
735                 if (!regexMatch(copy1, RegExp)) {
736                         // No EndChar inside. So we are finished
737                         result1 += CompareString + result0;
738                         result0.erase();
739                         continue;
740                 }
741
742                 string res1;
743                 string res0 = split(copy1, res1, EndChar);
744                 // Now res1 holds the environmentvariable
745                 // First, check, if Contents is ok.
746                 if (res1.empty()) { // No environmentvariable. Continue Loop.
747                         result1 += CompareString + FirstString;
748                         result0  = res0;
749                         continue;
750                 }
751                 // check contents of res1
752                 const char * res1_contents = res1.c_str();
753                 if (*res1_contents != FirstChar) {
754                         // Again No Environmentvariable
755                         result1 += CompareString;
756                         result0  = res0;
757                 }
758
759                 // Check for variable names
760                 // Situation ${} is detected as "No Environmentvariable"
761                 const char * cp1 = res1_contents+1;
762                 bool result = isalpha((unsigned char) *cp1) || (*cp1 == UnderscoreChar);
763                 ++cp1;
764                 while (*cp1 && result) {
765                         result = isalnum((unsigned char) *cp1) || 
766                                 (*cp1 == UnderscoreChar); 
767                         ++cp1;
768                 }
769
770                 if (!result) {
771                         // no correct variable name
772                         result1 += CompareString + res1 + EndString;
773                         result0  = split(res0, res1, CompareChar);
774                         result1 += res1;
775                         continue;
776                 }
777             
778                 string env = GetEnv(res1_contents+1);
779                 if (!env.empty()) {
780                         // Congratulations. Environmentvariable found
781                         result1 += env;
782                 } else {
783                         result1 += CompareString + res1 + EndString;
784                 }
785                 // Next $-Sign?
786                 result0  = split(res0, res1, CompareChar);
787                 result1 += res1;
788         } 
789         return result1;
790 }  // ReplaceEnvironmentPath
791
792
793 // Make relative path out of two absolute paths
794 string MakeRelPath(string const & abspath0, string const & basepath0)
795 // Makes relative path out of absolute path. If it is deeper than basepath,
796 // it's easy. If basepath and abspath share something (they are all deeper
797 // than some directory), it'll be rendered using ..'s. If they are completely
798 // different, then the absolute path will be used as relative path.
799 {
800         // This is a hack. It should probaly be done in another way. Lgb.
801         string abspath = CleanupPath(abspath0);
802         string basepath = CleanupPath(basepath0);
803         if (abspath.empty())
804                 return "<unknown_path>";
805
806         const int abslen = abspath.length();
807         const int baselen = basepath.length();
808         
809         // Find first different character
810         int i = 0;
811         while (i < abslen && i < baselen && abspath[i] == basepath[i]) ++i;
812
813         // Go back to last /
814         if (i < abslen && i < baselen
815             || (i<abslen && abspath[i] != '/' && i==baselen)
816             || (i<baselen && basepath[i] != '/' && i==abslen))
817         {
818                 if (i) --i;     // here was the last match
819                 while (i && abspath[i] != '/') --i;
820         }
821
822         if (i == 0) {
823                 // actually no match - cannot make it relative
824                 return abspath;
825         }
826
827         // Count how many dirs there are in basepath above match
828         // and append as many '..''s into relpath
829         string buf;
830         int j = i;
831         while (j < baselen) {
832                 if (basepath[j] == '/') {
833                         if (j+1 == baselen) break;
834                         buf += "../";
835                 }
836                 ++j;
837         }
838
839         // Append relative stuff from common directory to abspath
840         if (abspath[i] == '/') ++i;
841         for (; i<abslen; ++i)
842                 buf += abspath[i];
843         // Remove trailing /
844         if (suffixIs(buf, '/'))
845                 buf.erase(buf.length() - 1);
846         // Substitute empty with .
847         if (buf.empty())
848                 buf = '.';
849         return buf;
850 }
851
852
853 // Append sub-directory(ies) to a path in an intelligent way
854 string AddPath(string const & path, string const & path_2)
855 {
856         string buf;
857         string path2 = CleanupPath(path_2);
858
859         if (!path.empty() && path != "." && path != "./") {
860                 buf = CleanupPath(path);
861                 if (path[path.length() - 1] != '/')
862                                                            
863                         buf += '/';
864         }
865
866         if (!path2.empty()){
867                 int p2start = path2.find_first_not_of('/');
868                 //while (path2[p2start] == '/') ++p2start;
869
870                 int p2end = path2.find_last_not_of('/');
871                 //while (path2[p2end] == '/') --p2end;
872
873                 string tmp = path2.substr(p2start, p2end - p2start + 1);
874                 buf += tmp + '/';
875         }
876         return buf;
877 }
878
879
880 /* 
881  Change extension of oldname to extension.
882  Strips path off if no_path == true.
883  If no extension on oldname, just appends.
884  */
885 string ChangeExtension(string const & oldname, string const & extension, 
886                         bool no_path) 
887 {
888         string::size_type last_slash = oldname.rfind('/');
889         string::size_type last_dot;
890         if (last_slash != string::npos)
891                 last_dot = oldname.find('.', last_slash);
892         else
893                 last_dot = oldname.rfind('.');
894
895         string ext;
896         // Make sure the extension starts with a dot
897         if (!extension.empty() && extension[0] != '.')
898                 ext='.' + extension;
899         else
900                 ext = extension;
901         string ret_str;
902         if (no_path && last_slash != string::npos) {
903                 ++last_slash; // step it
904                 ret_str = oldname.substr(last_slash,
905                                          last_dot - last_slash) + ext;
906         } else
907                 ret_str = oldname.substr(0, last_dot) + ext;
908         return CleanupPath(ret_str);
909 }
910
911
912
913 // Creates a nice compact path for displaying
914 string MakeDisplayPath (string const & path, unsigned int threshold)
915 {
916         const int l1 = path.length();
917
918         // First, we try a relative path compared to home
919         string home = GetEnvPath("HOME");
920         string relhome = MakeRelPath(path, home);
921
922         unsigned int l2 = relhome.length();
923
924         string prefix;
925
926         // If we backup from home or don't have a relative path,
927         // this try is no good
928         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
929                 // relative path was no good, just use the original path
930                 relhome = path;
931                 l2 = l1;
932         } else {
933                 prefix = "~/";
934         }
935
936         // Is the path too long?
937         if (l2 > threshold) {
938                 // Yes, shortend it
939                 prefix += ".../";
940                 
941                 string temp;
942                 
943                 while (relhome.length() > threshold)
944                         relhome = split(relhome, temp, '/');
945
946                 // Did we shortend everything away?
947                 if (relhome.empty()) {
948                         // Yes, filename in itself is too long.
949                         // Pick the start and the end of the filename.
950                         relhome = OnlyFilename(path);
951                         string head = relhome.substr(0, threshold/2 - 3);
952
953                         l2 = relhome.length();
954                         string tail =
955                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
956                         relhome = head + "..." + tail;
957                 }
958         }
959         return prefix + relhome;
960 }
961
962 bool LyXReadLink(string const & File, string & Link)
963 {
964         char LinkBuffer[512];
965                 // Should be PATH_MAX but that needs autconf support
966         int nRead;
967         nRead = readlink(File.c_str(), LinkBuffer,sizeof(LinkBuffer)-1);
968         if (nRead <= 0)
969                 return false;
970         LinkBuffer[nRead] = 0;
971         Link = LinkBuffer;
972         return true;
973 }