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