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