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