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