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