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