]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
fix crash when ${HOME} is empty
[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         string TempBase;
598         string RTemp;
599         string Temp;
600
601         if (os::is_absolute_path(path))
602                 RTemp = path;
603         else
604                 // Make implicit current directory explicit
605                 RTemp = "./" + path;
606
607         // Normalise paths like /foo//bar ==> /foo/bar
608         boost::RegEx regex("/{2,}");
609         RTemp = regex.Merge(RTemp, "/");
610
611         while (!RTemp.empty()) {
612                 // Split by next /
613                 RTemp = split(RTemp, Temp, '/');
614
615                 if (Temp == ".") {
616                         TempBase = "./";
617                 } else if (Temp == "..") {
618                         // Remove one level of TempBase
619                         string::difference_type i = TempBase.length() - 2;
620                         while (i > 0 && TempBase[i] != '/')
621                                 --i;
622                         if (i >= 0 && TempBase[i] == '/')
623                                 TempBase.erase(i + 1, string::npos);
624                         else
625                                 TempBase = "../";
626                 } else {
627                         TempBase += Temp + '/';
628                 }
629         }
630
631         // returns absolute path
632         return TempBase;
633 }
634
635
636 string const GetFileContents(string const & fname)
637 {
638         if (fs::exists(fname)) {
639                 ifstream ifs(fname.c_str());
640                 ostringstream ofs;
641                 if (ifs && ofs) {
642                         ofs << ifs.rdbuf();
643                         ifs.close();
644                         return ofs.str();
645                 }
646         }
647         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
648         return string();
649 }
650
651
652 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
653 string const ReplaceEnvironmentPath(string const & path)
654 {
655         // ${VAR} is defined as
656         // $\{[A-Za-z_][A-Za-z_0-9]*\}
657         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
658
659         // $VAR is defined as:
660         // $\{[A-Za-z_][A-Za-z_0-9]*\}
661         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
662
663         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
664         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
665         boost::smatch what;
666
667         string result = path;
668         while (1) {
669                 regex_match(result, what, envvar_br_re);
670                 if (!what[0].matched) {
671                         regex_match(result, what, envvar_re);
672                         if (!what[0].matched)
673                                 break;
674                 }
675                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
676         }
677         return result;
678 }
679
680
681 // Make relative path out of two absolute paths
682 string const MakeRelPath(string const & abspath, string const & basepath)
683 // Makes relative path out of absolute path. If it is deeper than basepath,
684 // it's easy. If basepath and abspath share something (they are all deeper
685 // than some directory), it'll be rendered using ..'s. If they are completely
686 // different, then the absolute path will be used as relative path.
687 {
688         string::size_type const abslen = abspath.length();
689         string::size_type const baselen = basepath.length();
690
691         string::size_type i = os::common_path(abspath, basepath);
692
693         if (i == 0) {
694                 // actually no match - cannot make it relative
695                 return abspath;
696         }
697
698         // Count how many dirs there are in basepath above match
699         // and append as many '..''s into relpath
700         string buf;
701         string::size_type j = i;
702         while (j < baselen) {
703                 if (basepath[j] == '/') {
704                         if (j + 1 == baselen)
705                                 break;
706                         buf += "../";
707                 }
708                 ++j;
709         }
710
711         // Append relative stuff from common directory to abspath
712         if (abspath[i] == '/')
713                 ++i;
714         for (; i < abslen; ++i)
715                 buf += abspath[i];
716         // Remove trailing /
717         if (suffixIs(buf, '/'))
718                 buf.erase(buf.length() - 1);
719         // Substitute empty with .
720         if (buf.empty())
721                 buf = '.';
722         return buf;
723 }
724
725
726 // Append sub-directory(ies) to a path in an intelligent way
727 string const AddPath(string const & path, string const & path_2)
728 {
729         string buf;
730         string const path2 = os::internal_path(path_2);
731
732         if (!path.empty() && path != "." && path != "./") {
733                 buf = os::internal_path(path);
734                 if (path[path.length() - 1] != '/')
735                         buf += '/';
736         }
737
738         if (!path2.empty()) {
739                 string::size_type const p2start = path2.find_first_not_of('/');
740                 string::size_type const p2end = path2.find_last_not_of('/');
741                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
742                 buf += tmp + '/';
743         }
744         return buf;
745 }
746
747
748 /*
749  Change extension of oldname to extension.
750  Strips path off if no_path == true.
751  If no extension on oldname, just appends.
752  */
753 string const ChangeExtension(string const & oldname, string const & extension)
754 {
755         string::size_type const last_slash = oldname.rfind('/');
756         string::size_type last_dot = oldname.rfind('.');
757         if (last_dot < last_slash && last_slash != string::npos)
758                 last_dot = string::npos;
759
760         string ext;
761         // Make sure the extension starts with a dot
762         if (!extension.empty() && extension[0] != '.')
763                 ext= '.' + extension;
764         else
765                 ext = extension;
766
767         return os::internal_path(oldname.substr(0, last_dot) + ext);
768 }
769
770
771 /// Return the extension of the file (not including the .)
772 string const GetExtension(string const & name)
773 {
774         string::size_type const last_slash = name.rfind('/');
775         string::size_type const last_dot = name.rfind('.');
776         if (last_dot != string::npos &&
777             (last_slash == string::npos || last_dot > last_slash))
778                 return name.substr(last_dot + 1,
779                                    name.length() - (last_dot + 1));
780         else
781                 return string();
782 }
783
784
785 // the different filetypes and what they contain in one of the first lines
786 // (dots are any characters).           (Herbert 20020131)
787 // AGR  Grace...
788 // BMP  BM...
789 // EPS  %!PS-Adobe-3.0 EPSF...
790 // FIG  #FIG...
791 // FITS ...BITPIX...
792 // GIF  GIF...
793 // JPG  JFIF
794 // PDF  %PDF-...
795 // PNG  .PNG...
796 // PBM  P1... or P4     (B/W)
797 // PGM  P2... or P5     (Grayscale)
798 // PPM  P3... or P6     (color)
799 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
800 // SGI  \001\332...     (decimal 474)
801 // TGIF %TGIF...
802 // TIFF II... or MM...
803 // XBM  ..._bits[]...
804 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
805 //      ...static char *...
806 // XWD  \000\000\000\151        (0x00006900) decimal 105
807 //
808 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
809 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
810 // Z    \037\235                UNIX compress
811
812 string const getFormatFromContents(string const & filename)
813 {
814         // paranoia check
815         if (filename.empty() || !IsFileReadable(filename))
816                 return string();
817
818         ifstream ifs(filename.c_str());
819         if (!ifs)
820                 // Couldn't open file...
821                 return string();
822
823         // gnuzip
824         static string const gzipStamp = "\037\213";
825
826         // PKZIP
827         static string const zipStamp = "PK";
828
829         // compress
830         static string const compressStamp = "\037\235";
831
832         // Maximum strings to read
833         int const max_count = 50;
834         int count = 0;
835
836         string str;
837         string format;
838         bool firstLine = true;
839         while ((count++ < max_count) && format.empty()) {
840                 if (ifs.eof()) {
841                         lyxerr[Debug::GRAPHICS]
842                                 << "filetools(getFormatFromContents)\n"
843                                 << "\tFile type not recognised before EOF!"
844                                 << endl;
845                         break;
846                 }
847
848                 getline(ifs, str);
849                 string const stamp = str.substr(0, 2);
850                 if (firstLine && str.size() >= 2) {
851                         // at first we check for a zipped file, because this
852                         // information is saved in the first bytes of the file!
853                         // also some graphic formats which save the information
854                         // in the first line, too.
855                         if (prefixIs(str, gzipStamp)) {
856                                 format =  "gzip";
857
858                         } else if (stamp == zipStamp) {
859                                 format =  "zip";
860
861                         } else if (stamp == compressStamp) {
862                                 format =  "compress";
863
864                         // the graphics part
865                         } else if (stamp == "BM") {
866                                 format =  "bmp";
867
868                         } else if (stamp == "\001\332") {
869                                 format =  "sgi";
870
871                         // PBM family
872                         // Don't need to use str.at(0), str.at(1) because
873                         // we already know that str.size() >= 2
874                         } else if (str[0] == 'P') {
875                                 switch (str[1]) {
876                                 case '1':
877                                 case '4':
878                                         format =  "pbm";
879                                     break;
880                                 case '2':
881                                 case '5':
882                                         format =  "pgm";
883                                     break;
884                                 case '3':
885                                 case '6':
886                                         format =  "ppm";
887                                 }
888                                 break;
889
890                         } else if ((stamp == "II") || (stamp == "MM")) {
891                                 format =  "tiff";
892
893                         } else if (prefixIs(str,"%TGIF")) {
894                                 format =  "tgif";
895
896                         } else if (prefixIs(str,"#FIG")) {
897                                 format =  "fig";
898
899                         } else if (prefixIs(str,"GIF")) {
900                                 format =  "gif";
901
902                         } else if (str.size() > 3) {
903                                 int const c = ((str[0] << 24) & (str[1] << 16) &
904                                                (str[2] << 8)  & str[3]);
905                                 if (c == 105) {
906                                         format =  "xwd";
907                                 }
908                         }
909
910                         firstLine = false;
911                 }
912
913                 if (!format.empty())
914                     break;
915                 else if (contains(str,"EPSF"))
916                         // dummy, if we have wrong file description like
917                         // %!PS-Adobe-2.0EPSF"
918                         format =  "eps";
919
920                 else if (contains(str,"Grace"))
921                         format =  "agr";
922
923                 else if (contains(str,"JFIF"))
924                         format =  "jpg";
925
926                 else if (contains(str,"%PDF"))
927                         format =  "pdf";
928
929                 else if (contains(str,"PNG"))
930                         format =  "png";
931
932                 else if (contains(str,"%!PS-Adobe")) {
933                         // eps or ps
934                         ifs >> str;
935                         if (contains(str,"EPSF"))
936                                 format = "eps";
937                         else
938                             format = "ps";
939                 }
940
941                 else if (contains(str,"_bits[]"))
942                         format = "xbm";
943
944                 else if (contains(str,"XPM") || contains(str, "static char *"))
945                         format = "xpm";
946
947                 else if (contains(str,"BITPIX"))
948                         format = "fits";
949         }
950
951         if (!format.empty()) {
952                 lyxerr[Debug::GRAPHICS]
953                         << "Recognised Fileformat: " << format << endl;
954                 return format;
955         }
956
957         lyxerr[Debug::GRAPHICS]
958                 << "filetools(getFormatFromContents)\n"
959                 << "\tCouldn't find a known format!\n";
960         return string();
961 }
962
963
964 /// check for zipped file
965 bool zippedFile(string const & name)
966 {
967         string const type = getFormatFromContents(name);
968         if (contains("gzip zip compress", type) && !type.empty())
969                 return true;
970         return false;
971 }
972
973
974 string const unzippedFileName(string const & zipped_file)
975 {
976         string const ext = GetExtension(zipped_file);
977         if (ext == "gz" || ext == "z" || ext == "Z")
978                 return ChangeExtension(zipped_file, string());
979         return "unzipped_" + zipped_file;
980 }
981
982
983 string const unzipFile(string const & zipped_file, string const & unzipped_file)
984 {
985         string const tempfile = unzipped_file.empty() ?
986                 unzippedFileName(zipped_file) : unzipped_file;
987         // Run gunzip
988         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
989         Systemcall one;
990         one.startscript(Systemcall::Wait, command);
991         // test that command was executed successfully (anon)
992         // yes, please do. (Lgb)
993         return tempfile;
994 }
995
996
997 string const MakeDisplayPath(string const & path, unsigned int threshold)
998 {
999         string str = path;
1000         string const home = package().home_dir();
1001
1002         // replace /home/blah with ~/
1003         if (!home.empty() && prefixIs(str, home))
1004                 str = subst(str, home, "~");
1005
1006         if (str.length() <= threshold)
1007                 return os::external_path(str);
1008
1009         string const prefix = ".../";
1010         string temp;
1011
1012         while (str.length() > threshold)
1013                 str = split(str, temp, '/');
1014
1015         // Did we shorten everything away?
1016         if (str.empty()) {
1017                 // Yes, filename itself is too long.
1018                 // Pick the start and the end of the filename.
1019                 str = OnlyFilename(path);
1020                 string const head = str.substr(0, threshold / 2 - 3);
1021
1022                 string::size_type len = str.length();
1023                 string const tail =
1024                         str.substr(len - threshold / 2 - 2, len - 1);
1025                 str = head + "..." + tail;
1026         }
1027
1028         return os::external_path(prefix + str);
1029 }
1030
1031
1032 bool LyXReadLink(string const & file, string & link, bool resolve)
1033 {
1034 #ifdef HAVE_READLINK
1035         char linkbuffer[512];
1036         // Should be PATH_MAX but that needs autconf support
1037         int const nRead = ::readlink(file.c_str(),
1038                                      linkbuffer, sizeof(linkbuffer) - 1);
1039         if (nRead <= 0)
1040                 return false;
1041         linkbuffer[nRead] = '\0'; // terminator
1042         if (resolve)
1043                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1044         else
1045                 link = linkbuffer;
1046         return true;
1047 #else
1048         return false;
1049 #endif
1050 }
1051
1052
1053 cmd_ret const RunCommand(string const & cmd)
1054 {
1055         // FIXME: replace all calls to RunCommand with ForkedCall
1056         // (if the output is not needed) or the code in ispell.C
1057         // (if the output is needed).
1058
1059         // One question is if we should use popen or
1060         // create our own popen based on fork, exec, pipe
1061         // of course the best would be to have a
1062         // pstream (process stream), with the
1063         // variants ipstream, opstream
1064
1065 #if defined (HAVE_POPEN)
1066         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1067 #elif defined (HAVE__POPEN)
1068         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1069 #else
1070 #error No popen() function.
1071 #endif
1072
1073         // (Claus Hentschel) Check if popen was succesful ;-)
1074         if (!inf) {
1075                 lyxerr << "RunCommand:: could not start child process" << endl;
1076                 return make_pair(-1, string());
1077         }
1078
1079         string ret;
1080         int c = fgetc(inf);
1081         while (c != EOF) {
1082                 ret += static_cast<char>(c);
1083                 c = fgetc(inf);
1084         }
1085
1086 #if defined (HAVE_PCLOSE)
1087         int const pret = pclose(inf);
1088 #elif defined (HAVE__PCLOSE)
1089         int const pret = _pclose(inf);
1090 #else
1091 #error No pclose() function.
1092 #endif
1093
1094         if (pret == -1)
1095                 perror("RunCommand:: could not terminate child process");
1096
1097         return make_pair(pret, ret);
1098 }
1099
1100
1101 string const findtexfile(string const & fil, string const & /*format*/)
1102 {
1103         /* There is no problem to extend this function too use other
1104            methods to look for files. It could be setup to look
1105            in environment paths and also if wanted as a last resort
1106            to a recursive find. One of the easier extensions would
1107            perhaps be to use the LyX file lookup methods. But! I am
1108            going to implement this until I see some demand for it.
1109            Lgb
1110         */
1111
1112         // If the file can be found directly, we just return a
1113         // absolute path version of it.
1114         if (fs::exists(fil))
1115                 return MakeAbsPath(fil);
1116
1117         // No we try to find it using kpsewhich.
1118         // It seems from the kpsewhich manual page that it is safe to use
1119         // kpsewhich without --format: "When the --format option is not
1120         // given, the search path used when looking for a file is inferred
1121         // from the name given, by looking for a known extension. If no
1122         // known extension is found, the search path for TeX source files
1123         // is used."
1124         // However, we want to take advantage of the format sine almost all
1125         // the different formats has environment variables that can be used
1126         // to controll which paths to search. f.ex. bib looks in
1127         // BIBINPUTS and TEXBIB. Small list follows:
1128         // bib - BIBINPUTS, TEXBIB
1129         // bst - BSTINPUTS
1130         // graphic/figure - TEXPICTS, TEXINPUTS
1131         // ist - TEXINDEXSTYLE, INDEXSTYLE
1132         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1133         // tex - TEXINPUTS
1134         // tfm - TFMFONTS, TEXFONTS
1135         // This means that to use kpsewhich in the best possible way we
1136         // should help it by setting additional path in the approp. envir.var.
1137         string const kpsecmd = "kpsewhich " + fil;
1138
1139         cmd_ret const c = RunCommand(kpsecmd);
1140
1141         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1142                  << "kpse result = `" << rtrim(c.second, "\n")
1143                  << '\'' << endl;
1144         if (c.first != -1)
1145                 return os::internal_path(rtrim(c.second, "\n\r"));
1146         else
1147                 return string();
1148 }
1149
1150
1151 void removeAutosaveFile(string const & filename)
1152 {
1153         string a = OnlyPath(filename);
1154         a += '#';
1155         a += OnlyFilename(filename);
1156         a += '#';
1157         if (fs::exists(a))
1158                 unlink(a);
1159 }
1160
1161
1162 void readBB_lyxerrMessage(string const & file, bool & zipped,
1163         string const & message)
1164 {
1165         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1166                 << message << std::endl;
1167 #ifdef WITH_WARNINGS
1168 #warning Why is this func deleting a file? (Lgb)
1169 #endif
1170         if (zipped)
1171                 unlink(file);
1172 }
1173
1174
1175 string const readBB_from_PSFile(string const & file)
1176 {
1177         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1178         // It seems that every command in the header has an own line,
1179         // getline() should work for all files.
1180         // On the other hand some plot programs write the bb at the
1181         // end of the file. Than we have in the header:
1182         // %%BoundingBox: (atend)
1183         // In this case we must check the end.
1184         bool zipped = zippedFile(file);
1185         string const file_ = zipped ?  unzipFile(file) : file;
1186         string const format = getFormatFromContents(file_);
1187
1188         if (format != "eps" && format != "ps") {
1189                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1190                 return string();
1191         }
1192
1193         std::ifstream is(file_.c_str());
1194         while (is) {
1195                 string s;
1196                 getline(is,s);
1197                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1198                         string const bb = ltrim(s.substr(14));
1199                         readBB_lyxerrMessage(file_, zipped, bb);
1200                         return bb;
1201                 }
1202         }
1203         readBB_lyxerrMessage(file_, zipped, "no bb found");
1204         return string();
1205 }
1206
1207
1208 int compare_timestamps(string const & file1, string const & file2)
1209 {
1210         BOOST_ASSERT(AbsolutePath(file1));
1211         BOOST_ASSERT(AbsolutePath(file2));
1212
1213         // If the original is newer than the copy, then copy the original
1214         // to the new directory.
1215
1216         int cmp = 0;
1217         if (fs::exists(file1) && fs::exists(file2)) {
1218                 double const tmp = difftime(fs::last_write_time(file1),
1219                                             fs::last_write_time(file2));
1220                 if (tmp != 0)
1221                         cmp = tmp > 0 ? 1 : -1;
1222
1223         } else if (fs::exists(file1)) {
1224                 cmp = 1;
1225         } else if (fs::exists(file2)) {
1226                 cmp = -1;
1227         }
1228
1229         return cmp;
1230 }
1231
1232 } //namespace support
1233 } // namespace lyx