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