]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
more Alert:: work
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  *
14  * Full author contact details are available in file CREDITS
15  *
16  * General path-mangling functions
17  */
18
19 #include <config.h>
20
21 #include "debug.h"
22 #include "support/lstrings.h"
23 #include "support/systemcall.h"
24 #include "support/LAssert.h"
25
26 #include "filetools.h"
27 #include "lstrings.h"
28 #include "FileInfo.h"
29 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
30 #include "gettext.h"
31 #include "lyxlib.h"
32 #include "os.h"
33
34 #include "Lsstream.h"
35
36 #include <cctype>
37 #include <cstdlib>
38 #include <cstdio>
39 #include <fcntl.h>
40 #include <cerrno>
41
42 #include <utility>
43 #include <fstream>
44
45
46 // Which part of this is still necessary? (JMarc).
47 #if HAVE_DIRENT_H
48 # include <dirent.h>
49 # define NAMLEN(dirent) strlen((dirent)->d_name)
50 #else
51 # define dirent direct
52 # define NAMLEN(dirent) (dirent)->d_namlen
53 # if HAVE_SYS_NDIR_H
54 #  include <sys/ndir.h>
55 # endif
56 # if HAVE_SYS_DIR_H
57 #  include <sys/dir.h>
58 # endif
59 # if HAVE_NDIR_H
60 #  include <ndir.h>
61 # endif
62 #endif
63
64 #ifndef CXX_GLOBAL_CSTD
65 using std::fgetc;
66 using std::isalpha;
67 using std::isalnum;
68 #endif
69
70 using std::make_pair;
71 using std::pair;
72 using std::endl;
73 using std::ifstream;
74 using std::vector;
75 using std::getline;
76
77 extern string system_lyxdir;
78 extern string build_lyxdir;
79 extern string user_lyxdir;
80
81
82 bool IsLyXFilename(string const & filename)
83 {
84         return suffixIs(ascii_lowercase(filename), ".lyx");
85 }
86
87
88 bool IsSGMLFilename(string const & filename)
89 {
90         return suffixIs(ascii_lowercase(filename), ".sgml");
91 }
92
93
94 // Substitutes spaces with underscores in filename (and path)
95 string const MakeLatexName(string const & file)
96 {
97         string name = OnlyFilename(file);
98         string const path = OnlyPath(file);
99
100         for (string::size_type i = 0; i < name.length(); ++i) {
101                 name[i] &= 0x7f; // set 8th bit to 0
102         };
103
104         // ok so we scan through the string twice, but who cares.
105         string const keep("abcdefghijklmnopqrstuvwxyz"
106                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
107                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
108
109         string::size_type pos = 0;
110         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
111                 name[pos++] = '_';
112         }
113         return AddName(path, name);
114 }
115
116
117 // Substitutes spaces with underscores in filename (and path)
118 string const QuoteName(string const & name)
119 {
120         return (os::shell() == os::UNIX) ?
121                 '\'' + name + '\'':
122                 '"' + name + '"';
123 }
124
125
126 // Is a file readable ?
127 bool IsFileReadable(string const & path)
128 {
129         FileInfo file(path);
130         return file.isOK() && file.isRegular() && file.readable();
131 }
132
133
134 // Is a file read_only?
135 // return 1 read-write
136 //        0 read_only
137 //       -1 error (doesn't exist, no access, anything else)
138 int IsFileWriteable(string const & path)
139 {
140         FileInfo fi(path);
141
142         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
143                 return 1;
144         if (fi.readable()) // read-only
145                 return 0;
146         return -1; // everything else.
147 }
148
149
150 //returns true: dir writeable
151 //        false: not writeable
152 bool IsDirWriteable(string const & path)
153 {
154         lyxerr[Debug::FILES] << "IsDirWriteable: " << path << endl;
155
156         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
157
158         if (tmpfl.empty())
159                 return false;
160
161         lyx::unlink(tmpfl);
162         return true;
163 }
164
165
166 // Uses a string of paths separated by ";"s to find a file to open.
167 // Can't cope with pathnames with a ';' in them. Returns full path to file.
168 // If path entry begins with $$LyX/, use system_lyxdir
169 // If path entry begins with $$User/, use user_lyxdir
170 // Example: "$$User/doc;$$LyX/doc"
171 string const FileOpenSearch(string const & path, string const & name,
172                              string const & ext)
173 {
174         string real_file;
175         string path_element;
176         bool notfound = true;
177         string tmppath = split(path, path_element, ';');
178
179         while (notfound && !path_element.empty()) {
180                 path_element = os::slashify_path(path_element);
181                 if (!suffixIs(path_element, '/'))
182                         path_element+= '/';
183                 path_element = subst(path_element, "$$LyX", system_lyxdir);
184                 path_element = subst(path_element, "$$User", user_lyxdir);
185
186                 real_file = FileSearch(path_element, name, ext);
187
188                 if (real_file.empty()) {
189                         do {
190                                 tmppath = split(tmppath, path_element, ';');
191                         } while (!tmppath.empty() && path_element.empty());
192                 } else {
193                         notfound = false;
194                 }
195         }
196 #ifdef __EMX__
197         if (ext.empty() && notfound) {
198                 real_file = FileOpenSearch(path, name, "exe");
199                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
200         }
201 #endif
202         return real_file;
203 }
204
205
206 /// Returns a vector of all files in directory dir having extension ext.
207 vector<string> const DirList(string const & dir, string const & ext)
208 {
209         // This is a non-error checking C/system implementation
210         string extension;
211         if (!ext.empty() && ext[0] != '.')
212                 extension += '.';
213         extension += ext;
214
215         vector<string> dirlist;
216         DIR * dirp = ::opendir(dir.c_str());
217         if (!dirp) {
218                 lyxerr[Debug::FILES]
219                         << "Directory \"" << dir
220                         << "\" does not exist to DirList." << endl;
221                 return dirlist;
222         }
223
224         dirent * dire;
225         while ((dire = ::readdir(dirp))) {
226                 string const fil = dire->d_name;
227                 if (suffixIs(fil, extension)) {
228                         dirlist.push_back(fil);
229                 }
230         }
231         ::closedir(dirp);
232         return dirlist;
233         /* I would have prefered to take a vector<string>& as parameter so
234            that we could avoid the copy of the vector when returning.
235            Then we would use:
236            dirlist.swap(argvec);
237            to avoid the copy. (Lgb)
238         */
239         /* A C++ implementaion will look like this:
240            string extension(ext);
241            if (extension[0] != '.') extension.insert(0, 1, '.');
242            vector<string> dirlist;
243            directory_iterator dit("dir");
244            while (dit != directory_iterator()) {
245                    string fil = dit->filename;
246                    if (prefixIs(fil, extension)) {
247                            dirlist.push_back(fil);
248                    }
249                    ++dit;
250            }
251            dirlist.swap(argvec);
252            return;
253         */
254 }
255
256
257 // Returns the real name of file name in directory path, with optional
258 // extension ext.
259 string const FileSearch(string const & path, string const & name,
260                         string const & ext)
261 {
262         // if `name' is an absolute path, we ignore the setting of `path'
263         // Expand Environmentvariables in 'name'
264         string const tmpname = ReplaceEnvironmentPath(name);
265         string fullname = MakeAbsPath(tmpname, path);
266         // search first without extension, then with it.
267         if (IsFileReadable(fullname))
268                 return fullname;
269         else if (ext.empty())
270                 return string();
271         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
272                 fullname += '.';
273                 fullname += ext;
274                 if (IsFileReadable(fullname))
275                         return fullname;
276                 else
277                         return string();
278         }
279 }
280
281
282 // Search the file name.ext in the subdirectory dir of
283 //   1) user_lyxdir
284 //   2) build_lyxdir (if not empty)
285 //   3) system_lyxdir
286 string const LibFileSearch(string const & dir, string const & name,
287                            string const & ext)
288 {
289         string fullname = FileSearch(AddPath(user_lyxdir, dir), name, ext);
290         if (!fullname.empty())
291                 return fullname;
292
293         if (!build_lyxdir.empty())
294                 fullname = FileSearch(AddPath(build_lyxdir, dir), name, ext);
295         if (!fullname.empty())
296                 return fullname;
297
298         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
299 }
300
301
302 string const
303 i18nLibFileSearch(string const & dir, string const & name,
304                   string const & ext)
305 {
306         // this comment is from intl/dcigettext.c. We try to mimick this
307         // behaviour here.
308         /* The highest priority value is the `LANGUAGE' environment
309            variable. But we don't use the value if the currently
310            selected locale is the C locale. This is a GNU extension. */
311
312         string const lc_all = GetEnv("LC_ALL");
313         string lang = GetEnv("LANGUAGE");
314         if (lang.empty() || lc_all == "C") {
315                 lang = lc_all;
316                 if (lang.empty()) {
317                         lang = GetEnv("LANG");
318                 }
319         }
320
321         lang = token(lang, '_', 0);
322
323         if (lang.empty() || lang == "C")
324                 return LibFileSearch(dir, name, ext);
325         else {
326                 string const tmp = LibFileSearch(dir, lang + '_' + name,
327                                                  ext);
328                 if (!tmp.empty())
329                         return tmp;
330                 else
331                         return LibFileSearch(dir, name, ext);
332         }
333 }
334
335
336 string const LibScriptSearch(string const & command)
337 {
338         string script;
339         string args = command;
340         args = split(args, script, ' ');
341         script = LibFileSearch("scripts", script);
342         if (script.empty())
343                 return command;
344         else if (args.empty())
345                 return script;
346         else
347                 return script + ' ' + args;
348 }
349
350
351 string const GetEnv(string const & envname)
352 {
353         // f.ex. what about error checking?
354         char const * const ch = getenv(envname.c_str());
355         string const envstr = !ch ? "" : ch;
356         return envstr;
357 }
358
359
360 string const GetEnvPath(string const & name)
361 {
362 #ifndef __EMX__
363         string const pathlist = subst(GetEnv(name), ':', ';');
364 #else
365         string const pathlist = os::slashify_path(GetEnv(name));
366 #endif
367         return rtrim(pathlist, ";");
368 }
369
370
371 namespace {
372
373 int DeleteAllFilesInDir(string const & path)
374 {
375         // I have decided that we will be using parts from the boost
376         // library. Check out http://www.boost.org/
377         // For directory access we will then use the directory_iterator.
378         // Then the code will be something like:
379         // directory_iterator dit(path);
380         // directory_iterator dend;
381         // if (dit == dend) {
382         //         return -1;
383         // }
384         // for (; dit != dend; ++dit) {
385         //         string filename(*dit);
386         //         if (filename == "." || filename == "..")
387         //                 continue;
388         //         string unlinkpath(AddName(path, filename));
389         //         lyx::unlink(unlinkpath);
390         // }
391         // return 0;
392         DIR * dir = ::opendir(path.c_str());
393         if (!dir)
394                 return -1;
395
396         struct dirent * de;
397         int return_value = 0;
398         while ((de = readdir(dir))) {
399                 string const temp = de->d_name;
400                 if (temp == "." || temp == "..")
401                         continue;
402                 string const unlinkpath = AddName (path, temp);
403
404                 lyxerr[Debug::FILES] << "Deleting file: " << unlinkpath
405                                      << endl;
406
407                 bool deleted = true;
408                 FileInfo fi(unlinkpath);
409                 if (fi.isOK() && fi.isDir())
410                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
411                 deleted &= (lyx::unlink(unlinkpath) == 0);
412                 if (!deleted)
413                         return_value = -1;
414         }
415         closedir(dir);
416         return return_value;
417 }
418
419
420 string const CreateTmpDir(string const & tempdir, string const & mask)
421 {
422         lyxerr[Debug::FILES]
423                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
424                 << "CreateTmpDir:    mask=`" << mask << '\'' << endl;
425
426         string const tmpfl(lyx::tempName(tempdir, mask));
427         // lyx::tempName actually creates a file to make sure that it
428         // stays unique. So we have to delete it before we can create
429         // a dir with the same name. Note also that we are not thread
430         // safe because of the gap between unlink and mkdir. (Lgb)
431         lyx::unlink(tmpfl.c_str());
432
433         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700))
434                 return string();
435
436         return MakeAbsPath(tmpfl);
437 }
438
439 } // namespace anon
440
441
442 int destroyDir(string const & tmpdir)
443 {
444 #ifdef __EMX__
445         Path p(user_lyxdir);
446 #endif
447         if (DeleteAllFilesInDir(tmpdir))
448                 return -1;
449
450         if (lyx::rmdir(tmpdir))
451                 return -1;
452
453         return 0;
454 }
455
456
457 string const CreateBufferTmpDir(string const & pathfor)
458 {
459         static int count;
460         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
461         // We are in our own directory.  Why bother to mangle name?
462         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
463         // of EMX mkstemp().
464         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
465         if (lyx::mkdir(tmpfl, 0777)) {
466                 return string();
467         }
468         return tmpfl;
469 }
470
471
472 string const CreateLyXTmpDir(string const & deflt)
473 {
474         if ((!deflt.empty()) && (deflt  != "/tmp")) {
475                 if (lyx::mkdir(deflt, 0777)) {
476 #ifdef __EMX__
477                 Path p(user_lyxdir);
478 #endif
479                         return CreateTmpDir(deflt, "lyx_tmpdir");
480                 } else
481                         return deflt;
482         } else {
483 #ifdef __EMX__
484                 Path p(user_lyxdir);
485 #endif
486                 return CreateTmpDir("/tmp", "lyx_tmpdir");
487         }
488 }
489
490
491 bool createDirectory(string const & path, int permission)
492 {
493         string temp(rtrim(os::slashify_path(path), "/"));
494
495         lyx::Assert(!temp.empty());
496
497         if (lyx::mkdir(temp, permission))
498                 return false;
499
500         return true;
501 }
502
503
504 // Strip filename from path name
505 string const OnlyPath(string const & Filename)
506 {
507         // If empty filename, return empty
508         if (Filename.empty()) return Filename;
509
510         // Find last / or start of filename
511         string::size_type j = Filename.rfind('/');
512         if (j == string::npos)
513                 return "./";
514         return Filename.substr(0, j + 1);
515 }
516
517
518 // Convert relative path into absolute path based on a basepath.
519 // If relpath is absolute, just use that.
520 // If basepath is empty, use CWD as base.
521 string const MakeAbsPath(string const & RelPath, string const & BasePath)
522 {
523         // checks for already absolute path
524         if (os::is_absolute_path(RelPath))
525                 return RelPath;
526
527         // Copies given paths
528         string TempRel(os::slashify_path(RelPath));
529         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
530         TempRel = subst(TempRel, "//", "/");
531
532         string TempBase;
533
534         if (os::is_absolute_path(BasePath))
535                 TempBase = BasePath;
536         else
537                 TempBase = AddPath(lyx::getcwd(), BasePath);
538
539         // Handle /./ at the end of the path
540         while (suffixIs(TempBase, "/./"))
541                 TempBase.erase(TempBase.length() - 2);
542
543         // processes relative path
544         string RTemp(TempRel);
545         string Temp;
546
547         while (!RTemp.empty()) {
548                 // Split by next /
549                 RTemp = split(RTemp, Temp, '/');
550
551                 if (Temp == ".") continue;
552                 if (Temp == "..") {
553                         // Remove one level of TempBase
554                         string::difference_type i = TempBase.length() - 2;
555 #ifndef __EMX__
556                         if (i < 0) i = 0;
557                         while (i > 0 && TempBase[i] != '/') --i;
558                         if (i > 0)
559 #else
560                         if (i < 2) i = 2;
561                         while (i > 2 && TempBase[i] != '/') --i;
562                         if (i > 2)
563 #endif
564                                 TempBase.erase(i, string::npos);
565                         else
566                                 TempBase += '/';
567                 } else if (Temp.empty() && !RTemp.empty()) {
568                                 TempBase = os::current_root() + RTemp;
569                                 RTemp.erase();
570                 } else {
571                         // Add this piece to TempBase
572                         if (!suffixIs(TempBase, '/'))
573                                 TempBase += '/';
574                         TempBase += Temp;
575                 }
576         }
577
578         // returns absolute path
579         return os::slashify_path(TempBase);
580 }
581
582
583 // Correctly append filename to the pathname.
584 // If pathname is '.', then don't use pathname.
585 // Chops any path of filename.
586 string const AddName(string const & path, string const & fname)
587 {
588         // Get basename
589         string const basename(OnlyFilename(fname));
590
591         string buf;
592
593         if (path != "." && path != "./" && !path.empty()) {
594                 buf = os::slashify_path(path);
595                 if (!suffixIs(path, '/'))
596                         buf += '/';
597         }
598
599         return buf + basename;
600 }
601
602
603 // Strips path from filename
604 string const OnlyFilename(string const & fname)
605 {
606         if (fname.empty())
607                 return fname;
608
609         string::size_type j = fname.rfind('/');
610         if (j == string::npos) // no '/' in fname
611                 return fname;
612
613         // Strip to basename
614         return fname.substr(j + 1);
615 }
616
617
618 /// Returns true is path is absolute
619 bool AbsolutePath(string const & path)
620 {
621         return os::is_absolute_path(path);
622 }
623
624
625
626 // Create absolute path. If impossible, don't do anything
627 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
628 string const ExpandPath(string const & path)
629 {
630         // checks for already absolute path
631         string RTemp(ReplaceEnvironmentPath(path));
632         if (os::is_absolute_path(RTemp))
633                 return RTemp;
634
635         string Temp;
636         string const copy(RTemp);
637
638         // Split by next /
639         RTemp = split(RTemp, Temp, '/');
640
641         if (Temp == ".") {
642                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
643         }
644         if (Temp == "~") {
645                 return GetEnvPath("HOME") + '/' + RTemp;
646         }
647         if (Temp == "..") {
648                 return MakeAbsPath(copy);
649         }
650         // Don't know how to handle this
651         return copy;
652 }
653
654
655 // Normalize a path
656 // Constracts path/../path
657 // Can't handle "../../" or "/../" (Asger)
658 string const NormalizePath(string const & path)
659 {
660         string TempBase;
661         string RTemp;
662         string Temp;
663
664         if (os::is_absolute_path(path))
665                 RTemp = path;
666         else
667                 // Make implicit current directory explicit
668                 RTemp = "./" +path;
669
670         while (!RTemp.empty()) {
671                 // Split by next /
672                 RTemp = split(RTemp, Temp, '/');
673
674                 if (Temp == ".") {
675                         TempBase = "./";
676                 } else if (Temp == "..") {
677                         // Remove one level of TempBase
678                         string::difference_type i = TempBase.length() - 2;
679                         while (i > 0 && TempBase[i] != '/')
680                                 --i;
681                         if (i >= 0 && TempBase[i] == '/')
682                                 TempBase.erase(i + 1, string::npos);
683                         else
684                                 TempBase = "../";
685                 } else {
686                         TempBase += Temp + '/';
687                 }
688         }
689
690         // returns absolute path
691         return TempBase;
692 }
693
694
695 string const GetFileContents(string const & fname)
696 {
697         FileInfo finfo(fname);
698         if (finfo.exist()) {
699                 ifstream ifs(fname.c_str());
700                 ostringstream ofs;
701                 if (ifs && ofs) {
702                         ofs << ifs.rdbuf();
703                         ifs.close();
704                         return STRCONV(ofs.str());
705                 }
706         }
707         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
708         return string();
709 }
710
711
712 //
713 // Search ${...} as Variable-Name inside the string and replace it with
714 // the denoted environmentvariable
715 // Allow Variables according to
716 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
717 //
718
719 string const ReplaceEnvironmentPath(string const & path)
720 {
721         //
722         // CompareChar: Environment variables starts with this character
723         // PathChar:    Next path component start with this character
724         // while CompareChar found do:
725         //       Split String with PathChar
726         //       Search Environmentvariable
727         //       if found: Replace Strings
728         //
729         char const CompareChar = '$';
730         char const FirstChar = '{';
731         char const EndChar = '}';
732         char const UnderscoreChar = '_';
733         string EndString; EndString += EndChar;
734         string FirstString; FirstString += FirstChar;
735         string CompareString; CompareString += CompareChar;
736         string const RegExp("*}*"); // Exist EndChar inside a String?
737
738 // first: Search for a '$' - Sign.
739         //string copy(path);
740         string result1; //(copy);    // for split-calls
741         string result0 = split(path, result1, CompareChar);
742         while (!result0.empty()) {
743                 string copy1(result0); // contains String after $
744
745                 // Check, if there is an EndChar inside original String.
746
747                 if (!regexMatch(copy1, RegExp)) {
748                         // No EndChar inside. So we are finished
749                         result1 += CompareString + result0;
750                         result0.erase();
751                         continue;
752                 }
753
754                 string res1;
755                 string res0 = split(copy1, res1, EndChar);
756                 // Now res1 holds the environmentvariable
757                 // First, check, if Contents is ok.
758                 if (res1.empty()) { // No environmentvariable. Continue Loop.
759                         result1 += CompareString + FirstString;
760                         result0  = res0;
761                         continue;
762                 }
763                 // check contents of res1
764                 char const * res1_contents = res1.c_str();
765                 if (*res1_contents != FirstChar) {
766                         // Again No Environmentvariable
767                         result1 += CompareString;
768                         result0 = res0;
769                 }
770
771                 // Check for variable names
772                 // Situation ${} is detected as "No Environmentvariable"
773                 char const * cp1 = res1_contents + 1;
774                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
775                 ++cp1;
776                 while (*cp1 && result) {
777                         result = isalnum(*cp1) ||
778                                 (*cp1 == UnderscoreChar);
779                         ++cp1;
780                 }
781
782                 if (!result) {
783                         // no correct variable name
784                         result1 += CompareString + res1 + EndString;
785                         result0  = split(res0, res1, CompareChar);
786                         result1 += res1;
787                         continue;
788                 }
789
790                 string env(GetEnv(res1_contents + 1));
791                 if (!env.empty()) {
792                         // Congratulations. Environmentvariable found
793                         result1 += env;
794                 } else {
795                         result1 += CompareString + res1 + EndString;
796                 }
797                 // Next $-Sign?
798                 result0  = split(res0, res1, CompareChar);
799                 result1 += res1;
800         }
801         return result1;
802 }
803
804
805 // Make relative path out of two absolute paths
806 string const MakeRelPath(string const & abspath, string const & basepath)
807 // Makes relative path out of absolute path. If it is deeper than basepath,
808 // it's easy. If basepath and abspath share something (they are all deeper
809 // than some directory), it'll be rendered using ..'s. If they are completely
810 // different, then the absolute path will be used as relative path.
811 {
812         string::size_type const abslen = abspath.length();
813         string::size_type const baselen = basepath.length();
814
815         string::size_type i = os::common_path(abspath, basepath);
816
817         if (i == 0) {
818                 // actually no match - cannot make it relative
819                 return abspath;
820         }
821
822         // Count how many dirs there are in basepath above match
823         // and append as many '..''s into relpath
824         string buf;
825         string::size_type j = i;
826         while (j < baselen) {
827                 if (basepath[j] == '/') {
828                         if (j + 1 == baselen)
829                                 break;
830                         buf += "../";
831                 }
832                 ++j;
833         }
834
835         // Append relative stuff from common directory to abspath
836         if (abspath[i] == '/')
837                 ++i;
838         for (; i < abslen; ++i)
839                 buf += abspath[i];
840         // Remove trailing /
841         if (suffixIs(buf, '/'))
842                 buf.erase(buf.length() - 1);
843         // Substitute empty with .
844         if (buf.empty())
845                 buf = '.';
846         return buf;
847 }
848
849
850 // Append sub-directory(ies) to a path in an intelligent way
851 string const AddPath(string const & path, string const & path_2)
852 {
853         string buf;
854         string const path2 = os::slashify_path(path_2);
855
856         if (!path.empty() && path != "." && path != "./") {
857                 buf = os::slashify_path(path);
858                 if (path[path.length() - 1] != '/')
859                         buf += '/';
860         }
861
862         if (!path2.empty()) {
863                 string::size_type const p2start = path2.find_first_not_of('/');
864                 string::size_type const p2end = path2.find_last_not_of('/');
865                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
866                 buf += tmp + '/';
867         }
868         return buf;
869 }
870
871
872 /*
873  Change extension of oldname to extension.
874  Strips path off if no_path == true.
875  If no extension on oldname, just appends.
876  */
877 string const ChangeExtension(string const & oldname, string const & extension)
878 {
879         string::size_type const last_slash = oldname.rfind('/');
880         string::size_type last_dot = oldname.rfind('.');
881         if (last_dot < last_slash && last_slash != string::npos)
882                 last_dot = string::npos;
883
884         string ext;
885         // Make sure the extension starts with a dot
886         if (!extension.empty() && extension[0] != '.')
887                 ext= '.' + extension;
888         else
889                 ext = extension;
890
891         return os::slashify_path(oldname.substr(0, last_dot) + ext);
892 }
893
894
895 /// Return the extension of the file (not including the .)
896 string const GetExtension(string const & name)
897 {
898         string::size_type const last_slash = name.rfind('/');
899         string::size_type const last_dot = name.rfind('.');
900         if (last_dot != string::npos &&
901             (last_slash == string::npos || last_dot > last_slash))
902                 return name.substr(last_dot + 1,
903                                    name.length() - (last_dot + 1));
904         else
905                 return string();
906 }
907
908 // the different filetypes and what they contain in one of the first lines
909 // (dots are any characters).           (Herbert 20020131)
910 // AGR  Grace...
911 // BMP  BM...
912 // EPS  %!PS-Adobe-3.0 EPSF...
913 // FIG  #FIG...
914 // FITS ...BITPIX...
915 // GIF  GIF...
916 // JPG  JFIF
917 // PDF  %PDF-...
918 // PNG  .PNG...
919 // PBM  P1... or P4     (B/W)
920 // PGM  P2... or P5     (Grayscale)
921 // PPM  P3... or P6     (color)
922 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
923 // SGI  \001\332...     (decimal 474)
924 // TGIF %TGIF...
925 // TIFF II... or MM...
926 // XBM  ..._bits[]...
927 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
928 //      ...static char *...
929 // XWD  \000\000\000\151        (0x00006900) decimal 105
930 //
931 // GZIP \037\213\010\010...     http://www.ietf.org/rfc/rfc1952.txt
932 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
933 // Z    \037\177                UNIX compress
934
935 /// return the "extension" which belongs to the contents.
936 /// for no knowing contents return the extension. Without
937 /// an extension and unknown contents we return "user"
938 string const getExtFromContents(string const & filename)
939 {
940         // paranoia check
941         if (filename.empty() || !IsFileReadable(filename))
942                 return string();
943
944
945         ifstream ifs(filename.c_str());
946         if (!ifs)
947                 // Couldn't open file...
948                 return string();
949
950         // gnuzip
951         string const gzipStamp = "\037\213\010\010";
952
953         // PKZIP
954         string const zipStamp = "PK";
955
956         // compress
957         string const compressStamp = "\037\177";
958
959         // Maximum strings to read
960         int const max_count = 50;
961         int count = 0;
962
963         string str, format;
964         bool firstLine = true;
965         while ((count++ < max_count) && format.empty()) {
966                 if (ifs.eof()) {
967                         lyxerr[Debug::GRAPHICS]
968                                 << "filetools(getExtFromContents)\n"
969                                 << "\tFile type not recognised before EOF!"
970                                 << endl;
971                         break;
972                 }
973
974                 getline(ifs, str);
975                 string const stamp = str.substr(0,2);
976                 if (firstLine && str.size() >= 2) {
977                         // at first we check for a zipped file, because this
978                         // information is saved in the first bytes of the file!
979                         // also some graphic formats which save the information
980                         // in the first line, too.
981                         if (prefixIs(str, gzipStamp)) {
982                                 format =  "gzip";
983
984                         } else if (stamp == zipStamp) {
985                                 format =  "zip";
986
987                         } else if (stamp == compressStamp) {
988                                 format =  "compress";
989
990                         // the graphics part
991                         } else if (stamp == "BM") {
992                                 format =  "bmp";
993
994                         } else if (stamp == "\001\332") {
995                                 format =  "sgi";
996
997                         // PBM family
998                         // Don't need to use str.at(0), str.at(1) because
999                         // we already know that str.size() >= 2
1000                         } else if (str[0] == 'P') {
1001                                 switch (str[1]) {
1002                                 case '1':
1003                                 case '4':
1004                                         format =  "pbm";
1005                                     break;
1006                                 case '2':
1007                                 case '5':
1008                                         format =  "pgm";
1009                                     break;
1010                                 case '3':
1011                                 case '6':
1012                                         format =  "ppm";
1013                                 }
1014                                 break;
1015
1016                         } else if ((stamp == "II") || (stamp == "MM")) {
1017                                 format =  "tiff";
1018
1019                         } else if (prefixIs(str,"%TGIF")) {
1020                                 format =  "tgif";
1021
1022                         } else if (prefixIs(str,"#FIG")) {
1023                                 format =  "fig";
1024
1025                         } else if (prefixIs(str,"GIF")) {
1026                                 format =  "gif";
1027
1028                         } else if (str.size() > 3) {
1029                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1030                                                (str[2] << 8)  & str[3]);
1031                                 if (c == 105) {
1032                                         format =  "xwd";
1033                                 }
1034                         }
1035
1036                         firstLine = false;
1037                 }
1038
1039                 if (!format.empty())
1040                     break;
1041                 else if (contains(str,"EPSF"))
1042                         // dummy, if we have wrong file description like
1043                         // %!PS-Adobe-2.0EPSF"
1044                         format =  "eps";
1045
1046                 else if (contains(str,"Grace"))
1047                         format =  "agr";
1048
1049                 else if (contains(str,"JFIF"))
1050                         format =  "jpg";
1051
1052                 else if (contains(str,"%PDF"))
1053                         format =  "pdf";
1054
1055                 else if (contains(str,"PNG"))
1056                         format =  "png";
1057
1058                 else if (contains(str,"%!PS-Adobe")) {
1059                         // eps or ps
1060                         ifs >> str;
1061                         if (contains(str,"EPSF"))
1062                                 format = "eps";
1063                         else
1064                             format = "ps";
1065                 }
1066
1067                 else if (contains(str,"_bits[]"))
1068                         format = "xbm";
1069
1070                 else if (contains(str,"XPM") || contains(str, "static char *"))
1071                         format = "xpm";
1072
1073                 else if (contains(str,"BITPIX"))
1074                         format = "fits";
1075         }
1076
1077         if (!format.empty()) {
1078                 lyxerr[Debug::GRAPHICS]
1079                         << "Recognised Fileformat: " << format << endl;
1080                 return format;
1081         }
1082
1083         string const ext(GetExtension(filename));
1084         lyxerr[Debug::GRAPHICS]
1085                 << "filetools(getExtFromContents)\n"
1086                 << "\tCouldn't find a known Type!\n";
1087         if (!ext.empty()) {
1088             lyxerr[Debug::GRAPHICS]
1089                 << "\twill take the file extension -> "
1090                 << ext << endl;
1091                 return ext;
1092         } else {
1093             lyxerr[Debug::GRAPHICS]
1094                 << "\twill use ext or a \"user\" defined format" << endl;
1095             return "user";
1096         }
1097 }
1098
1099
1100 /// check for zipped file
1101 bool zippedFile(string const & name)
1102 {
1103         string const type = getExtFromContents(name);
1104         if (contains("gzip zip compress", type) && !type.empty())
1105                 return true;
1106         return false;
1107 }
1108
1109
1110 string const unzipFile(string const & zipped_file)
1111 {
1112         string const file = ChangeExtension(zipped_file, string());
1113         string  const tempfile = lyx::tempName(string(), file);
1114         // Run gunzip
1115         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1116         Systemcall one;
1117         one.startscript(Systemcall::Wait, command);
1118         // test that command was executed successfully (anon)
1119         // yes, please do. (Lgb)
1120         return tempfile;
1121 }
1122
1123
1124 // Creates a nice compact path for displaying
1125 string const
1126 MakeDisplayPath (string const & path, unsigned int threshold)
1127 {
1128         string::size_type const l1 = path.length();
1129
1130         // First, we try a relative path compared to home
1131         string const home(GetEnvPath("HOME"));
1132         string relhome = MakeRelPath(path, home);
1133
1134         string::size_type l2 = relhome.length();
1135
1136         string prefix;
1137
1138         // If we backup from home or don't have a relative path,
1139         // this try is no good
1140         if (prefixIs(relhome, "../") || os::is_absolute_path(relhome)) {
1141                 // relative path was no good, just use the original path
1142                 relhome = path;
1143                 l2 = l1;
1144         } else {
1145                 prefix = "~/";
1146         }
1147
1148         // Is the path too long?
1149         if (l2 > threshold) {
1150                 // Yes, shortend it
1151                 prefix += ".../";
1152
1153                 string temp;
1154
1155                 while (relhome.length() > threshold)
1156                         relhome = split(relhome, temp, '/');
1157
1158                 // Did we shortend everything away?
1159                 if (relhome.empty()) {
1160                         // Yes, filename in itself is too long.
1161                         // Pick the start and the end of the filename.
1162                         relhome = OnlyFilename(path);
1163                         string const head = relhome.substr(0, threshold/2 - 3);
1164
1165                         l2 = relhome.length();
1166                         string const tail =
1167                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
1168                         relhome = head + "..." + tail;
1169                 }
1170         }
1171         return prefix + relhome;
1172 }
1173
1174
1175 bool LyXReadLink(string const & file, string & link, bool resolve)
1176 {
1177         char linkbuffer[512];
1178         // Should be PATH_MAX but that needs autconf support
1179         int const nRead = ::readlink(file.c_str(),
1180                                      linkbuffer, sizeof(linkbuffer) - 1);
1181         if (nRead <= 0)
1182                 return false;
1183         linkbuffer[nRead] = '\0'; // terminator
1184         if (resolve)
1185                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1186         else
1187                 link = linkbuffer;
1188         return true;
1189 }
1190
1191
1192 cmd_ret const RunCommand(string const & cmd)
1193 {
1194         // One question is if we should use popen or
1195         // create our own popen based on fork, exec, pipe
1196         // of course the best would be to have a
1197         // pstream (process stream), with the
1198         // variants ipstream, opstream
1199
1200         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1201
1202         // (Claus Hentschel) Check if popen was succesful ;-)
1203         if (!inf)
1204                 return make_pair(-1, string());
1205
1206         string ret;
1207         int c = fgetc(inf);
1208         while (c != EOF) {
1209                 ret += static_cast<char>(c);
1210                 c = fgetc(inf);
1211         }
1212         int const pret = pclose(inf);
1213         return make_pair(pret, ret);
1214 }
1215
1216
1217 string const findtexfile(string const & fil, string const & /*format*/)
1218 {
1219         /* There is no problem to extend this function too use other
1220            methods to look for files. It could be setup to look
1221            in environment paths and also if wanted as a last resort
1222            to a recursive find. One of the easier extensions would
1223            perhaps be to use the LyX file lookup methods. But! I am
1224            going to implement this until I see some demand for it.
1225            Lgb
1226         */
1227
1228         // If the file can be found directly, we just return a
1229         // absolute path version of it.
1230         if (FileInfo(fil).exist())
1231                 return MakeAbsPath(fil);
1232
1233         // No we try to find it using kpsewhich.
1234         // It seems from the kpsewhich manual page that it is safe to use
1235         // kpsewhich without --format: "When the --format option is not
1236         // given, the search path used when looking for a file is inferred
1237         // from the name given, by looking for a known extension. If no
1238         // known extension is found, the search path for TeX source files
1239         // is used."
1240         // However, we want to take advantage of the format sine almost all
1241         // the different formats has environment variables that can be used
1242         // to controll which paths to search. f.ex. bib looks in
1243         // BIBINPUTS and TEXBIB. Small list follows:
1244         // bib - BIBINPUTS, TEXBIB
1245         // bst - BSTINPUTS
1246         // graphic/figure - TEXPICTS, TEXINPUTS
1247         // ist - TEXINDEXSTYLE, INDEXSTYLE
1248         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1249         // tex - TEXINPUTS
1250         // tfm - TFMFONTS, TEXFONTS
1251         // This means that to use kpsewhich in the best possible way we
1252         // should help it by setting additional path in the approp. envir.var.
1253         string const kpsecmd = "kpsewhich " + fil;
1254
1255         cmd_ret const c = RunCommand(kpsecmd);
1256
1257         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1258                  << "kpse result = `" << rtrim(c.second, "\n")
1259                  << '\'' << endl;
1260         if (c.first != -1)
1261                 return os::internal_path(rtrim(c.second, "\n\r"));
1262         else
1263                 return string();
1264 }
1265
1266
1267 void removeAutosaveFile(string const & filename)
1268 {
1269         string a = OnlyPath(filename);
1270         a += '#';
1271         a += OnlyFilename(filename);
1272         a += '#';
1273         FileInfo const fileinfo(a);
1274         if (fileinfo.exist())
1275                 lyx::unlink(a);
1276 }
1277
1278
1279 void readBB_lyxerrMessage(string const & file, bool & zipped,
1280         string const & message)
1281 {
1282         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1283                 << message << std::endl;
1284         if (zipped)
1285                 lyx::unlink(file);
1286 }
1287
1288
1289 string const readBB_from_PSFile(string const & file)
1290 {
1291         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1292         // It seems that every command in the header has an own line,
1293         // getline() should work for all files.
1294         // On the other hand some plot programs write the bb at the
1295         // end of the file. Than we have in the header:
1296         // %%BoundingBox: (atend)
1297         // In this case we must check the end.
1298         bool zipped = zippedFile(file);
1299         string const file_ = zipped ?
1300                 string(unzipFile(file)) : string(file);
1301         string const format = getExtFromContents(file_);
1302
1303         if (format != "eps" && format != "ps") {
1304                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1305                 return string();
1306         }
1307
1308         std::ifstream is(file_.c_str());
1309         while (is) {
1310                 string s;
1311                 getline(is,s);
1312                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1313                         string const bb = ltrim(s.substr(14));
1314                         readBB_lyxerrMessage(file_, zipped, bb);
1315                         return bb;
1316                 }
1317         }
1318         readBB_lyxerrMessage(file_, zipped, "no bb found");
1319         return string();
1320 }