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