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