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