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