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