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