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