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