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