]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
tostr -> convert and some bformat work
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/tostr.h"
25 #include "support/systemcall.h"
26 #include "support/filetools.h"
27 #include "support/lstrings.h"
28 #include "support/FileInfo.h"
29 #include "support/forkedcontr.h"
30 #include "support/path.h"
31 #include "support/path_defines.h"
32 #include "support/lyxlib.h"
33 #include "support/os.h"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/regex.hpp>
41
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::internal_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::internal_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" + convert<string>(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::internal_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::internal_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::internal_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::internal_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 os::homepath() + '/' + 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::internal_path(path_2);
844
845         if (!path.empty() && path != "." && path != "./") {
846                 buf = os::internal_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::internal_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 // the different filetypes and what they contain in one of the first lines
899 // (dots are any characters).           (Herbert 20020131)
900 // AGR  Grace...
901 // BMP  BM...
902 // EPS  %!PS-Adobe-3.0 EPSF...
903 // FIG  #FIG...
904 // FITS ...BITPIX...
905 // GIF  GIF...
906 // JPG  JFIF
907 // PDF  %PDF-...
908 // PNG  .PNG...
909 // PBM  P1... or P4     (B/W)
910 // PGM  P2... or P5     (Grayscale)
911 // PPM  P3... or P6     (color)
912 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
913 // SGI  \001\332...     (decimal 474)
914 // TGIF %TGIF...
915 // TIFF II... or MM...
916 // XBM  ..._bits[]...
917 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
918 //      ...static char *...
919 // XWD  \000\000\000\151        (0x00006900) decimal 105
920 //
921 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
922 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
923 // Z    \037\235                UNIX compress
924
925 string const getFormatFromContents(string const & filename)
926 {
927         // paranoia check
928         if (filename.empty() || !IsFileReadable(filename))
929                 return string();
930
931         ifstream ifs(filename.c_str());
932         if (!ifs)
933                 // Couldn't open file...
934                 return string();
935
936         // gnuzip
937         string const gzipStamp = "\037\213";
938
939         // PKZIP
940         string const zipStamp = "PK";
941
942         // compress
943         string const compressStamp = "\037\235";
944
945         // Maximum strings to read
946         int const max_count = 50;
947         int count = 0;
948
949         string str;
950         string format;
951         bool firstLine = true;
952         while ((count++ < max_count) && format.empty()) {
953                 if (ifs.eof()) {
954                         lyxerr[Debug::GRAPHICS]
955                                 << "filetools(getFormatFromContents)\n"
956                                 << "\tFile type not recognised before EOF!"
957                                 << endl;
958                         break;
959                 }
960
961                 getline(ifs, str);
962                 string const stamp = str.substr(0,2);
963                 if (firstLine && str.size() >= 2) {
964                         // at first we check for a zipped file, because this
965                         // information is saved in the first bytes of the file!
966                         // also some graphic formats which save the information
967                         // in the first line, too.
968                         if (prefixIs(str, gzipStamp)) {
969                                 format =  "gzip";
970
971                         } else if (stamp == zipStamp) {
972                                 format =  "zip";
973
974                         } else if (stamp == compressStamp) {
975                                 format =  "compress";
976
977                         // the graphics part
978                         } else if (stamp == "BM") {
979                                 format =  "bmp";
980
981                         } else if (stamp == "\001\332") {
982                                 format =  "sgi";
983
984                         // PBM family
985                         // Don't need to use str.at(0), str.at(1) because
986                         // we already know that str.size() >= 2
987                         } else if (str[0] == 'P') {
988                                 switch (str[1]) {
989                                 case '1':
990                                 case '4':
991                                         format =  "pbm";
992                                     break;
993                                 case '2':
994                                 case '5':
995                                         format =  "pgm";
996                                     break;
997                                 case '3':
998                                 case '6':
999                                         format =  "ppm";
1000                                 }
1001                                 break;
1002
1003                         } else if ((stamp == "II") || (stamp == "MM")) {
1004                                 format =  "tiff";
1005
1006                         } else if (prefixIs(str,"%TGIF")) {
1007                                 format =  "tgif";
1008
1009                         } else if (prefixIs(str,"#FIG")) {
1010                                 format =  "fig";
1011
1012                         } else if (prefixIs(str,"GIF")) {
1013                                 format =  "gif";
1014
1015                         } else if (str.size() > 3) {
1016                                 int const c = ((str[0] << 24) & (str[1] << 16) &
1017                                                (str[2] << 8)  & str[3]);
1018                                 if (c == 105) {
1019                                         format =  "xwd";
1020                                 }
1021                         }
1022
1023                         firstLine = false;
1024                 }
1025
1026                 if (!format.empty())
1027                     break;
1028                 else if (contains(str,"EPSF"))
1029                         // dummy, if we have wrong file description like
1030                         // %!PS-Adobe-2.0EPSF"
1031                         format =  "eps";
1032
1033                 else if (contains(str,"Grace"))
1034                         format =  "agr";
1035
1036                 else if (contains(str,"JFIF"))
1037                         format =  "jpg";
1038
1039                 else if (contains(str,"%PDF"))
1040                         format =  "pdf";
1041
1042                 else if (contains(str,"PNG"))
1043                         format =  "png";
1044
1045                 else if (contains(str,"%!PS-Adobe")) {
1046                         // eps or ps
1047                         ifs >> str;
1048                         if (contains(str,"EPSF"))
1049                                 format = "eps";
1050                         else
1051                             format = "ps";
1052                 }
1053
1054                 else if (contains(str,"_bits[]"))
1055                         format = "xbm";
1056
1057                 else if (contains(str,"XPM") || contains(str, "static char *"))
1058                         format = "xpm";
1059
1060                 else if (contains(str,"BITPIX"))
1061                         format = "fits";
1062         }
1063
1064         if (!format.empty()) {
1065                 lyxerr[Debug::GRAPHICS]
1066                         << "Recognised Fileformat: " << format << endl;
1067                 return format;
1068         }
1069
1070         lyxerr[Debug::GRAPHICS]
1071                 << "filetools(getFormatFromContents)\n"
1072                 << "\tCouldn't find a known format!\n";
1073         return string();
1074 }
1075
1076
1077 /// check for zipped file
1078 bool zippedFile(string const & name)
1079 {
1080         string const type = getFormatFromContents(name);
1081         if (contains("gzip zip compress", type) && !type.empty())
1082                 return true;
1083         return false;
1084 }
1085
1086
1087 string const unzippedFileName(string const & zipped_file)
1088 {
1089         string const ext = GetExtension(zipped_file);
1090         if (ext == "gz" || ext == "z" || ext == "Z")
1091                 return ChangeExtension(zipped_file, string());
1092         return "unzipped_" + zipped_file;
1093 }
1094
1095
1096 string const unzipFile(string const & zipped_file, string const & unzipped_file)
1097 {
1098         string const tempfile = unzipped_file.empty() ?
1099                 unzippedFileName(zipped_file) : unzipped_file;
1100         // Run gunzip
1101         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1102         Systemcall one;
1103         one.startscript(Systemcall::Wait, command);
1104         // test that command was executed successfully (anon)
1105         // yes, please do. (Lgb)
1106         return tempfile;
1107 }
1108
1109
1110 string const MakeDisplayPath(string const & path, unsigned int threshold)
1111 {
1112         string str = path;
1113
1114         string const home(os::homepath());
1115
1116         // replace /home/blah with ~/
1117         if (prefixIs(str, home))
1118                 str = subst(str, home, "~");
1119
1120         if (str.length() <= threshold)
1121                 return str;
1122
1123         string const prefix = ".../";
1124         string temp;
1125
1126         while (str.length() > threshold)
1127                 str = split(str, temp, '/');
1128
1129         // Did we shorten everything away?
1130         if (str.empty()) {
1131                 // Yes, filename itself is too long.
1132                 // Pick the start and the end of the filename.
1133                 str = OnlyFilename(path);
1134                 string const head = str.substr(0, threshold / 2 - 3);
1135
1136                 string::size_type len = str.length();
1137                 string const tail =
1138                         str.substr(len - threshold / 2 - 2, len - 1);
1139                 str = head + "..." + tail;
1140         }
1141
1142         return prefix + str;
1143 }
1144
1145
1146 bool LyXReadLink(string const & file, string & link, bool resolve)
1147 {
1148 #ifdef HAVE_READLINK
1149         char linkbuffer[512];
1150         // Should be PATH_MAX but that needs autconf support
1151         int const nRead = ::readlink(file.c_str(),
1152                                      linkbuffer, sizeof(linkbuffer) - 1);
1153         if (nRead <= 0)
1154                 return false;
1155         linkbuffer[nRead] = '\0'; // terminator
1156         if (resolve)
1157                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1158         else
1159                 link = linkbuffer;
1160         return true;
1161 #else
1162         return false;
1163 #endif
1164 }
1165
1166
1167 cmd_ret const RunCommand(string const & cmd)
1168 {
1169         // FIXME: replace all calls to RunCommand with ForkedCall
1170         // (if the output is not needed) or the code in ispell.C
1171         // (if the output is needed).
1172
1173         // One question is if we should use popen or
1174         // create our own popen based on fork, exec, pipe
1175         // of course the best would be to have a
1176         // pstream (process stream), with the
1177         // variants ipstream, opstream
1178
1179         sigset_t newMask, oldMask;
1180         sigemptyset(&oldMask);
1181         sigemptyset(&newMask);
1182         sigaddset(&newMask, SIGCHLD);
1183
1184         // Block the SIGCHLD signal.
1185         sigprocmask(SIG_BLOCK, &newMask, &oldMask);
1186
1187         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1188
1189         // (Claus Hentschel) Check if popen was succesful ;-)
1190         if (!inf) {
1191                 return make_pair(-1, string());
1192                 lyxerr << "RunCommand:: could not start child process" << endl;
1193                 }
1194
1195         string ret;
1196         int c = fgetc(inf);
1197         while (c != EOF) {
1198                 ret += static_cast<char>(c);
1199                 c = fgetc(inf);
1200         }
1201         int const pret = pclose(inf);
1202         if (pret == -1)
1203                 perror("RunCommand:: could not terminate child process");
1204
1205         // Unblock the SIGCHLD signal and restore the old mask.
1206         sigprocmask(SIG_SETMASK, &oldMask, 0);
1207
1208         return make_pair(pret, ret);
1209 }
1210
1211
1212 string const findtexfile(string const & fil, string const & /*format*/)
1213 {
1214         /* There is no problem to extend this function too use other
1215            methods to look for files. It could be setup to look
1216            in environment paths and also if wanted as a last resort
1217            to a recursive find. One of the easier extensions would
1218            perhaps be to use the LyX file lookup methods. But! I am
1219            going to implement this until I see some demand for it.
1220            Lgb
1221         */
1222
1223         // If the file can be found directly, we just return a
1224         // absolute path version of it.
1225         if (FileInfo(fil).exist())
1226                 return MakeAbsPath(fil);
1227
1228         // No we try to find it using kpsewhich.
1229         // It seems from the kpsewhich manual page that it is safe to use
1230         // kpsewhich without --format: "When the --format option is not
1231         // given, the search path used when looking for a file is inferred
1232         // from the name given, by looking for a known extension. If no
1233         // known extension is found, the search path for TeX source files
1234         // is used."
1235         // However, we want to take advantage of the format sine almost all
1236         // the different formats has environment variables that can be used
1237         // to controll which paths to search. f.ex. bib looks in
1238         // BIBINPUTS and TEXBIB. Small list follows:
1239         // bib - BIBINPUTS, TEXBIB
1240         // bst - BSTINPUTS
1241         // graphic/figure - TEXPICTS, TEXINPUTS
1242         // ist - TEXINDEXSTYLE, INDEXSTYLE
1243         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1244         // tex - TEXINPUTS
1245         // tfm - TFMFONTS, TEXFONTS
1246         // This means that to use kpsewhich in the best possible way we
1247         // should help it by setting additional path in the approp. envir.var.
1248         string const kpsecmd = "kpsewhich " + fil;
1249
1250         cmd_ret const c = RunCommand(kpsecmd);
1251
1252         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1253                  << "kpse result = `" << rtrim(c.second, "\n")
1254                  << '\'' << endl;
1255         if (c.first != -1)
1256                 return os::internal_path(rtrim(c.second, "\n\r"));
1257         else
1258                 return string();
1259 }
1260
1261
1262 void removeAutosaveFile(string const & filename)
1263 {
1264         string a = OnlyPath(filename);
1265         a += '#';
1266         a += OnlyFilename(filename);
1267         a += '#';
1268         FileInfo const fileinfo(a);
1269         if (fileinfo.exist())
1270                 unlink(a);
1271 }
1272
1273
1274 void readBB_lyxerrMessage(string const & file, bool & zipped,
1275         string const & message)
1276 {
1277         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1278                 << message << std::endl;
1279 #ifdef WITH_WARNINGS
1280 #warning Why is this func deleting a file? (Lgb)
1281 #endif
1282         if (zipped)
1283                 unlink(file);
1284 }
1285
1286
1287 string const readBB_from_PSFile(string const & file)
1288 {
1289         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1290         // It seems that every command in the header has an own line,
1291         // getline() should work for all files.
1292         // On the other hand some plot programs write the bb at the
1293         // end of the file. Than we have in the header:
1294         // %%BoundingBox: (atend)
1295         // In this case we must check the end.
1296         bool zipped = zippedFile(file);
1297         string const file_ = zipped ?
1298                 string(unzipFile(file)) : string(file);
1299         string const format = getFormatFromContents(file_);
1300
1301         if (format != "eps" && format != "ps") {
1302                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1303                 return string();
1304         }
1305
1306         std::ifstream is(file_.c_str());
1307         while (is) {
1308                 string s;
1309                 getline(is,s);
1310                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1311                         string const bb = ltrim(s.substr(14));
1312                         readBB_lyxerrMessage(file_, zipped, bb);
1313                         return bb;
1314                 }
1315         }
1316         readBB_lyxerrMessage(file_, zipped, "no bb found");
1317         return string();
1318 }
1319
1320
1321 int compare_timestamps(string const & file1, string const & file2)
1322 {
1323         BOOST_ASSERT(AbsolutePath(file1) && AbsolutePath(file2));
1324
1325         // If the original is newer than the copy, then copy the original
1326         // to the new directory.
1327         FileInfo f1(file1);
1328         FileInfo f2(file2);
1329
1330         int cmp = 0;
1331         if (f1.exist() && f2.exist()) {
1332                 double const tmp = difftime(f1.getModificationTime(),
1333                                             f2.getModificationTime());
1334                 if (tmp != 0)
1335                         cmp = tmp > 0 ? 1 : -1;
1336
1337         } else if (f1.exist()) {
1338                 cmp = 1;
1339         } else if (f2.exist()) {
1340                 cmp = -1;
1341         }
1342
1343         return cmp;
1344 }
1345
1346 } //namespace support
1347 } // namespace lyx